@fieldnotes/core 0.38.6 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +524 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +523 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -843,6 +843,11 @@ var KeyboardActions = class {
|
|
|
843
843
|
this.nudgeTxId = recorder?.currentTransactionId ?? null;
|
|
844
844
|
} else {
|
|
845
845
|
clearTimeout(this.nudgeTimer);
|
|
846
|
+
const recorder = this.deps.getHistoryRecorder();
|
|
847
|
+
if (recorder?.currentTransactionId !== this.nudgeTxId) {
|
|
848
|
+
recorder?.begin();
|
|
849
|
+
this.nudgeTxId = recorder?.currentTransactionId ?? null;
|
|
850
|
+
}
|
|
846
851
|
}
|
|
847
852
|
const moved = sel.tool.nudgeSelection(dx * step, dy * step, sel.ctx);
|
|
848
853
|
this.nudgeTimer = setTimeout(() => this.flushPendingNudge(), 400);
|
|
@@ -853,7 +858,7 @@ var KeyboardActions = class {
|
|
|
853
858
|
clearTimeout(this.nudgeTimer);
|
|
854
859
|
this.nudgeTimer = null;
|
|
855
860
|
const recorder = this.deps.getHistoryRecorder();
|
|
856
|
-
if (
|
|
861
|
+
if (recorder?.currentTransactionId === this.nudgeTxId) {
|
|
857
862
|
recorder?.commit();
|
|
858
863
|
}
|
|
859
864
|
this.nudgeTxId = null;
|
|
@@ -1079,7 +1084,6 @@ var DEFAULT_BINDINGS = [
|
|
|
1079
1084
|
["redo", ["mod+y", "mod+shift+z"]],
|
|
1080
1085
|
["select-all", ["mod+a"]],
|
|
1081
1086
|
["copy", ["mod+c"]],
|
|
1082
|
-
["paste", ["mod+v"]],
|
|
1083
1087
|
["duplicate", ["mod+d"]],
|
|
1084
1088
|
["z-forward", ["]"]],
|
|
1085
1089
|
["z-backward", ["["]],
|
|
@@ -1164,6 +1168,9 @@ function bindingMatches(p, e, allowShift) {
|
|
|
1164
1168
|
if (e.altKey !== p.alt) return false;
|
|
1165
1169
|
return p.digit ? e.code === `Digit${p.key}` : e.key.toLowerCase() === p.key;
|
|
1166
1170
|
}
|
|
1171
|
+
function sameBinding(a, b) {
|
|
1172
|
+
return a.key === b.key && a.mod === b.mod && a.ctrl === b.ctrl && a.meta === b.meta && a.shift === b.shift && a.alt === b.alt;
|
|
1173
|
+
}
|
|
1167
1174
|
function toArray(bindings) {
|
|
1168
1175
|
if (bindings === null) return [];
|
|
1169
1176
|
return Array.isArray(bindings) ? [...bindings] : [bindings];
|
|
@@ -1192,6 +1199,16 @@ var ShortcutMap = class {
|
|
|
1192
1199
|
rebind(action, bindings) {
|
|
1193
1200
|
const list = toArray(bindings);
|
|
1194
1201
|
const parsedList = list.map(parseBinding);
|
|
1202
|
+
for (const p of parsedList) {
|
|
1203
|
+
for (const [otherAction, otherList] of this.parsed) {
|
|
1204
|
+
if (otherAction === action) continue;
|
|
1205
|
+
if (otherList.some((q) => sameBinding(p, q))) {
|
|
1206
|
+
console.warn(
|
|
1207
|
+
`[fieldnotes] shortcut binding for "${action}" conflicts with "${otherAction}"; first registered wins`
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1195
1212
|
this.raw.set(action, list);
|
|
1196
1213
|
this.parsed.set(action, parsedList);
|
|
1197
1214
|
}
|
|
@@ -1241,6 +1258,7 @@ var KeyboardHandler = class {
|
|
|
1241
1258
|
this.shortcutMap = new ShortcutMap(deps.shortcuts?.bindings);
|
|
1242
1259
|
window.addEventListener("keydown", this.onKeyDown, { signal: deps.abortSignal });
|
|
1243
1260
|
window.addEventListener("keyup", this.onKeyUp, { signal: deps.abortSignal });
|
|
1261
|
+
window.addEventListener("paste", this.onPaste, { signal: deps.abortSignal });
|
|
1244
1262
|
}
|
|
1245
1263
|
shortcutMap;
|
|
1246
1264
|
get shortcuts() {
|
|
@@ -1256,12 +1274,15 @@ var KeyboardHandler = class {
|
|
|
1256
1274
|
zoomToLevel(level) {
|
|
1257
1275
|
this.deps.camera.zoomAt(level, this.viewportCenter());
|
|
1258
1276
|
}
|
|
1277
|
+
shouldHandle(target) {
|
|
1278
|
+
const el = target;
|
|
1279
|
+
if (el?.isContentEditable) return false;
|
|
1280
|
+
const tag = el?.tagName;
|
|
1281
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return false;
|
|
1282
|
+
return this.isInScope();
|
|
1283
|
+
}
|
|
1259
1284
|
onKeyDown = (e) => {
|
|
1260
|
-
|
|
1261
|
-
if (target?.isContentEditable) return;
|
|
1262
|
-
const tag = target?.tagName;
|
|
1263
|
-
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
|
|
1264
|
-
if (!this.isInScope()) return;
|
|
1285
|
+
if (!this.shouldHandle(e.target)) return;
|
|
1265
1286
|
if (e.key === " ") {
|
|
1266
1287
|
this.deps.setSpaceHeld(true);
|
|
1267
1288
|
}
|
|
@@ -1283,6 +1304,34 @@ var KeyboardHandler = class {
|
|
|
1283
1304
|
}
|
|
1284
1305
|
}
|
|
1285
1306
|
};
|
|
1307
|
+
onPaste = (e) => {
|
|
1308
|
+
if (!this.shouldHandle(e.target)) return;
|
|
1309
|
+
const items = e.clipboardData?.items;
|
|
1310
|
+
let file = null;
|
|
1311
|
+
if (items) {
|
|
1312
|
+
for (const it of items) {
|
|
1313
|
+
if (it.kind === "file" && it.type.startsWith("image/")) {
|
|
1314
|
+
file = it.getAsFile();
|
|
1315
|
+
break;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (file) {
|
|
1320
|
+
e.preventDefault();
|
|
1321
|
+
const world = this.deps.getLastPointerWorld() ?? this.deps.getCenteredWorld();
|
|
1322
|
+
if (this.deps.onPaste) {
|
|
1323
|
+
this.deps.onPaste(e, world);
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
const reader = new FileReader();
|
|
1327
|
+
reader.onload = () => {
|
|
1328
|
+
if (typeof reader.result === "string") this.deps.addImage(reader.result, world);
|
|
1329
|
+
};
|
|
1330
|
+
reader.readAsDataURL(file);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
this.deps.actions.paste();
|
|
1334
|
+
};
|
|
1286
1335
|
runAction(action, e) {
|
|
1287
1336
|
switch (action) {
|
|
1288
1337
|
case "delete":
|
|
@@ -1431,7 +1480,11 @@ var InputHandler = class {
|
|
|
1431
1480
|
this.spaceHeld = v;
|
|
1432
1481
|
},
|
|
1433
1482
|
getActivePointerCount: () => this.activePointers.size,
|
|
1434
|
-
dispatchToolHover: (e) => this.dispatchToolHover(e)
|
|
1483
|
+
dispatchToolHover: (e) => this.dispatchToolHover(e),
|
|
1484
|
+
addImage: options.addImage ?? (() => ""),
|
|
1485
|
+
getLastPointerWorld: () => this.lastPointerWorld(),
|
|
1486
|
+
getCenteredWorld: options.getCenteredWorld ?? (() => ({ x: 0, y: 0 })),
|
|
1487
|
+
onPaste: options.onPaste
|
|
1435
1488
|
});
|
|
1436
1489
|
this.element.style.touchAction = "none";
|
|
1437
1490
|
if (this.scope === "focus") {
|
|
@@ -2091,12 +2144,12 @@ function smoothToSegments(points) {
|
|
|
2091
2144
|
return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
|
|
2092
2145
|
}
|
|
2093
2146
|
const segments = [];
|
|
2094
|
-
const
|
|
2095
|
-
for (let i = 0; i <
|
|
2147
|
+
const n2 = points.length;
|
|
2148
|
+
for (let i = 0; i < n2 - 1; i++) {
|
|
2096
2149
|
const p0 = points[Math.max(0, i - 1)];
|
|
2097
2150
|
const p1 = points[i];
|
|
2098
2151
|
const p2 = points[i + 1];
|
|
2099
|
-
const p3 = points[Math.min(
|
|
2152
|
+
const p3 = points[Math.min(n2 - 1, i + 2)];
|
|
2100
2153
|
if (!p0 || !p1 || !p2 || !p3) continue;
|
|
2101
2154
|
const cp1 = {
|
|
2102
2155
|
x: p1.x + (p2.x - p0.x) / 6,
|
|
@@ -2786,11 +2839,11 @@ function pixelToOffset(x, y, cellSize, orientation) {
|
|
|
2786
2839
|
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
2787
2840
|
return { col, row: Math.round((y - offsetY) / hexH) };
|
|
2788
2841
|
}
|
|
2789
|
-
function enumerateHexRing(centerQ, centerR,
|
|
2842
|
+
function enumerateHexRing(centerQ, centerR, n2, orientation, cellSize) {
|
|
2790
2843
|
const cells = [];
|
|
2791
|
-
for (let dq = -
|
|
2792
|
-
const rMin = Math.max(-
|
|
2793
|
-
const rMax = Math.min(
|
|
2844
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
2845
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
2846
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
2794
2847
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2795
2848
|
const absQ = centerQ + dq;
|
|
2796
2849
|
const absR = centerR + dr;
|
|
@@ -2811,28 +2864,28 @@ function getHexDistance(a, b, cellSize, orientation) {
|
|
|
2811
2864
|
return Math.max(Math.abs(dq), Math.abs(dr), Math.abs(ds));
|
|
2812
2865
|
}
|
|
2813
2866
|
function getHexCellsInRadius(center2, radiusCells, cellSize, orientation) {
|
|
2814
|
-
const
|
|
2867
|
+
const n2 = Math.round(radiusCells);
|
|
2815
2868
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2816
2869
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2817
|
-
if (
|
|
2870
|
+
if (n2 <= 0) {
|
|
2818
2871
|
return [offsetToPixel(off.col, off.row, cellSize, orientation)];
|
|
2819
2872
|
}
|
|
2820
|
-
return enumerateHexRing(cube.q, cube.r,
|
|
2873
|
+
return enumerateHexRing(cube.q, cube.r, n2, orientation, cellSize);
|
|
2821
2874
|
}
|
|
2822
2875
|
function getHexCellsInCone(center2, angle, radiusCells, cellSize, orientation) {
|
|
2823
|
-
const
|
|
2876
|
+
const n2 = Math.round(radiusCells);
|
|
2824
2877
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2825
2878
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2826
2879
|
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
2827
|
-
if (
|
|
2880
|
+
if (n2 <= 0) return [centerPixel];
|
|
2828
2881
|
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
2829
2882
|
const step = Math.PI / 3;
|
|
2830
2883
|
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
2831
2884
|
const halfAngle = Math.PI / 6 + 1e-6;
|
|
2832
2885
|
const cells = [centerPixel];
|
|
2833
|
-
for (let dq = -
|
|
2834
|
-
const rMin = Math.max(-
|
|
2835
|
-
const rMax = Math.min(
|
|
2886
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
2887
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
2888
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
2836
2889
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2837
2890
|
if (dq === 0 && dr === 0) continue;
|
|
2838
2891
|
const absQ = cube.q + dq;
|
|
@@ -2856,23 +2909,23 @@ function getHexCellsInCone(center2, angle, radiusCells, cellSize, orientation) {
|
|
|
2856
2909
|
return cells;
|
|
2857
2910
|
}
|
|
2858
2911
|
function getHexCellsInLine(center2, angle, radiusCells, cellSize, orientation) {
|
|
2859
|
-
const
|
|
2912
|
+
const n2 = Math.round(radiusCells);
|
|
2860
2913
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2861
2914
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2862
2915
|
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
2863
|
-
if (
|
|
2916
|
+
if (n2 <= 0) return [centerPixel];
|
|
2864
2917
|
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
2865
2918
|
const step = Math.PI / 3;
|
|
2866
2919
|
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
2867
2920
|
const cos = Math.cos(snappedAngle);
|
|
2868
2921
|
const sin = Math.sin(snappedAngle);
|
|
2869
2922
|
const snapUnit = Math.sqrt(3) * cellSize;
|
|
2870
|
-
const lineLength =
|
|
2923
|
+
const lineLength = n2 * snapUnit;
|
|
2871
2924
|
const halfWidth = snapUnit * 0.5 + 1e-6;
|
|
2872
2925
|
const cells = [];
|
|
2873
|
-
for (let dq = -
|
|
2874
|
-
const rMin = Math.max(-
|
|
2875
|
-
const rMax = Math.min(
|
|
2926
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
2927
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
2928
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
2876
2929
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2877
2930
|
const absQ = cube.q + dq;
|
|
2878
2931
|
const absR = cube.r + dr;
|
|
@@ -2894,17 +2947,17 @@ function getHexCellsInLine(center2, angle, radiusCells, cellSize, orientation) {
|
|
|
2894
2947
|
return cells;
|
|
2895
2948
|
}
|
|
2896
2949
|
function getHexCellsInSquare(center2, radiusCells, cellSize, orientation) {
|
|
2897
|
-
const
|
|
2950
|
+
const n2 = Math.round(radiusCells);
|
|
2898
2951
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2899
2952
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2900
2953
|
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
2901
|
-
if (
|
|
2954
|
+
if (n2 <= 0) return [centerPixel];
|
|
2902
2955
|
const snapUnit = Math.sqrt(3) * cellSize;
|
|
2903
|
-
const halfSide =
|
|
2956
|
+
const halfSide = n2 * snapUnit / 2;
|
|
2904
2957
|
const cells = [];
|
|
2905
|
-
for (let dq = -
|
|
2906
|
-
const rMin = Math.max(-
|
|
2907
|
-
const rMax = Math.min(
|
|
2958
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
2959
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
2960
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
2908
2961
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2909
2962
|
const absQ = cube.q + dq;
|
|
2910
2963
|
const absR = cube.r + dr;
|
|
@@ -3091,6 +3144,58 @@ function getSquareGridLines(bounds, cellSize) {
|
|
|
3091
3144
|
}
|
|
3092
3145
|
return { verticals, horizontals };
|
|
3093
3146
|
}
|
|
3147
|
+
function getHexVertices(cx, cy, circumradius, orientation) {
|
|
3148
|
+
const vertices = [];
|
|
3149
|
+
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
3150
|
+
for (let i = 0; i < 6; i++) {
|
|
3151
|
+
const angle = Math.PI / 3 * i + angleOffset;
|
|
3152
|
+
vertices.push({
|
|
3153
|
+
x: cx + circumradius * Math.cos(angle),
|
|
3154
|
+
y: cy + circumradius * Math.sin(angle)
|
|
3155
|
+
});
|
|
3156
|
+
}
|
|
3157
|
+
return vertices;
|
|
3158
|
+
}
|
|
3159
|
+
function getHexCenters(bounds, circumradius, orientation) {
|
|
3160
|
+
if (circumradius <= 0) return [];
|
|
3161
|
+
const centers = [];
|
|
3162
|
+
if (orientation === "pointy") {
|
|
3163
|
+
const hexW = Math.sqrt(3) * circumradius;
|
|
3164
|
+
const hexH = 2 * circumradius;
|
|
3165
|
+
const rowH = hexH * 0.75;
|
|
3166
|
+
const startRow = Math.floor((bounds.minY - circumradius) / rowH);
|
|
3167
|
+
const endRow = Math.ceil((bounds.maxY + circumradius) / rowH);
|
|
3168
|
+
const startCol = Math.floor((bounds.minX - hexW) / hexW);
|
|
3169
|
+
const endCol = Math.ceil((bounds.maxX + hexW) / hexW);
|
|
3170
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
3171
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
3172
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
3173
|
+
centers.push({
|
|
3174
|
+
x: col * hexW + offsetX,
|
|
3175
|
+
y: row * rowH
|
|
3176
|
+
});
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
} else {
|
|
3180
|
+
const hexW = 2 * circumradius;
|
|
3181
|
+
const hexH = Math.sqrt(3) * circumradius;
|
|
3182
|
+
const colW = hexW * 0.75;
|
|
3183
|
+
const startCol = Math.floor((bounds.minX - circumradius) / colW);
|
|
3184
|
+
const endCol = Math.ceil((bounds.maxX + circumradius) / colW);
|
|
3185
|
+
const startRow = Math.floor((bounds.minY - hexH) / hexH);
|
|
3186
|
+
const endRow = Math.ceil((bounds.maxY + hexH) / hexH);
|
|
3187
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
3188
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
3189
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
3190
|
+
centers.push({
|
|
3191
|
+
x: col * colW,
|
|
3192
|
+
y: row * hexH + offsetY
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
return centers;
|
|
3198
|
+
}
|
|
3094
3199
|
function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opacity) {
|
|
3095
3200
|
if (cellSize <= 0) return;
|
|
3096
3201
|
const { verticals, horizontals } = getSquareGridLines(bounds, cellSize);
|
|
@@ -3489,6 +3594,8 @@ function createHtmlElement(input) {
|
|
|
3489
3594
|
};
|
|
3490
3595
|
if (input.domId) el.domId = input.domId;
|
|
3491
3596
|
if (input.interactive) el.interactive = input.interactive;
|
|
3597
|
+
if (input.htmlType) el.htmlType = input.htmlType;
|
|
3598
|
+
if (input.data) el.data = input.data;
|
|
3492
3599
|
return el;
|
|
3493
3600
|
}
|
|
3494
3601
|
function createShape(input) {
|
|
@@ -4769,6 +4876,350 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
4769
4876
|
});
|
|
4770
4877
|
}
|
|
4771
4878
|
|
|
4879
|
+
// src/canvas/export-svg.ts
|
|
4880
|
+
var ARROWHEAD_LENGTH2 = 12;
|
|
4881
|
+
var ARROWHEAD_ANGLE2 = Math.PI / 6;
|
|
4882
|
+
var ARROW_LABEL_FONT_SIZE2 = 14;
|
|
4883
|
+
function esc(s) {
|
|
4884
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4885
|
+
}
|
|
4886
|
+
var n = (v) => Number.isFinite(v) ? `${Math.round(v * 1e3) / 1e3}` : "0";
|
|
4887
|
+
function elementCenter(el) {
|
|
4888
|
+
const b = getElementBounds(el);
|
|
4889
|
+
if (!b) return null;
|
|
4890
|
+
return { x: b.x + b.w / 2, y: b.y + b.h / 2 };
|
|
4891
|
+
}
|
|
4892
|
+
function withRotationSvg(el, fragment) {
|
|
4893
|
+
const angle = el.rotation ?? 0;
|
|
4894
|
+
if (!angle || !fragment) return fragment;
|
|
4895
|
+
const c = elementCenter(el);
|
|
4896
|
+
if (!c) return fragment;
|
|
4897
|
+
const deg = angle * 180 / Math.PI;
|
|
4898
|
+
return `<g transform="rotate(${n(deg)} ${n(c.x)} ${n(c.y)})">${fragment}</g>`;
|
|
4899
|
+
}
|
|
4900
|
+
var WIDTH_QUANTUM2 = 0.25;
|
|
4901
|
+
function emitStroke(stroke) {
|
|
4902
|
+
if (stroke.points.length < 2) return "";
|
|
4903
|
+
const data = getStrokeRenderData(stroke);
|
|
4904
|
+
const { x: ox, y: oy } = stroke.position;
|
|
4905
|
+
const byWidth = /* @__PURE__ */ new Map();
|
|
4906
|
+
for (let i = 0; i < data.segments.length; i++) {
|
|
4907
|
+
const seg = data.segments[i];
|
|
4908
|
+
const w = data.widths[i];
|
|
4909
|
+
if (!seg || w === void 0) continue;
|
|
4910
|
+
const q = Math.max(WIDTH_QUANTUM2, Math.round(w / WIDTH_QUANTUM2) * WIDTH_QUANTUM2);
|
|
4911
|
+
let parts = byWidth.get(q);
|
|
4912
|
+
if (!parts) {
|
|
4913
|
+
parts = [];
|
|
4914
|
+
byWidth.set(q, parts);
|
|
4915
|
+
}
|
|
4916
|
+
parts.push(
|
|
4917
|
+
`M${n(ox + seg.start.x)} ${n(oy + seg.start.y)} C${n(ox + seg.cp1.x)} ${n(oy + seg.cp1.y)} ${n(ox + seg.cp2.x)} ${n(oy + seg.cp2.y)} ${n(ox + seg.end.x)} ${n(oy + seg.end.y)}`
|
|
4918
|
+
);
|
|
4919
|
+
}
|
|
4920
|
+
const blend = stroke.blendMode === "multiply" ? ' style="mix-blend-mode:multiply"' : "";
|
|
4921
|
+
let out = "";
|
|
4922
|
+
for (const [width, parts] of byWidth) {
|
|
4923
|
+
out += `<path d="${parts.join(" ")}" fill="none" stroke="${esc(stroke.color)}" stroke-width="${n(width)}" stroke-linecap="round" stroke-linejoin="round" opacity="${n(stroke.opacity)}"${blend} />`;
|
|
4924
|
+
}
|
|
4925
|
+
return out;
|
|
4926
|
+
}
|
|
4927
|
+
function emitShape(shape) {
|
|
4928
|
+
const { x, y } = shape.position;
|
|
4929
|
+
const { w, h } = shape.size;
|
|
4930
|
+
const fill = shape.fillColor !== "none" && shape.shape !== "line" ? esc(shape.fillColor) : "none";
|
|
4931
|
+
const stroke = shape.strokeWidth > 0 ? esc(shape.strokeColor) : "none";
|
|
4932
|
+
const sw = shape.strokeWidth > 0 ? ` stroke-width="${n(shape.strokeWidth)}"` : "";
|
|
4933
|
+
switch (shape.shape) {
|
|
4934
|
+
case "rectangle":
|
|
4935
|
+
return `<rect x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" fill="${fill}" stroke="${stroke}"${sw} />`;
|
|
4936
|
+
case "ellipse":
|
|
4937
|
+
return `<ellipse cx="${n(x + w / 2)}" cy="${n(y + h / 2)}" rx="${n(w / 2)}" ry="${n(h / 2)}" fill="${fill}" stroke="${stroke}"${sw} />`;
|
|
4938
|
+
case "line": {
|
|
4939
|
+
const [a, b] = lineEndpoints(shape);
|
|
4940
|
+
return `<line x1="${n(a.x)}" y1="${n(a.y)}" x2="${n(b.x)}" y2="${n(b.y)}" stroke="${stroke}"${sw} stroke-linecap="round" />`;
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
function emitArrow(arrow, store) {
|
|
4945
|
+
const geometry = getArrowRenderGeometry(arrow);
|
|
4946
|
+
const { visualFrom: from, visualTo: to } = getVisualEndpoints(arrow, geometry, store);
|
|
4947
|
+
let d;
|
|
4948
|
+
if (arrow.bend !== 0) {
|
|
4949
|
+
const cp = geometry.controlPoint ?? getArrowControlPoint(from, to, arrow.bend);
|
|
4950
|
+
d = `M${n(from.x)} ${n(from.y)} Q${n(cp.x)} ${n(cp.y)} ${n(to.x)} ${n(to.y)}`;
|
|
4951
|
+
} else {
|
|
4952
|
+
d = `M${n(from.x)} ${n(from.y)} L${n(to.x)} ${n(to.y)}`;
|
|
4953
|
+
}
|
|
4954
|
+
const dash = arrow.fromBinding || arrow.toBinding ? ' stroke-dasharray="8 4"' : "";
|
|
4955
|
+
let out = `<path d="${d}" fill="none" stroke="${esc(arrow.color)}" stroke-width="${n(arrow.width)}" stroke-linecap="round"${dash} />`;
|
|
4956
|
+
const angle = geometry.tangentEnd;
|
|
4957
|
+
const p1x = to.x - ARROWHEAD_LENGTH2 * Math.cos(angle - ARROWHEAD_ANGLE2);
|
|
4958
|
+
const p1y = to.y - ARROWHEAD_LENGTH2 * Math.sin(angle - ARROWHEAD_ANGLE2);
|
|
4959
|
+
const p2x = to.x - ARROWHEAD_LENGTH2 * Math.cos(angle + ARROWHEAD_ANGLE2);
|
|
4960
|
+
const p2y = to.y - ARROWHEAD_LENGTH2 * Math.sin(angle + ARROWHEAD_ANGLE2);
|
|
4961
|
+
out += `<polygon points="${n(to.x)},${n(to.y)} ${n(p1x)},${n(p1y)} ${n(p2x)},${n(p2y)}" fill="${esc(arrow.color)}" />`;
|
|
4962
|
+
if (arrow.label && arrow.label.length > 0) {
|
|
4963
|
+
const mid = getArrowMidpoint(arrow.from, arrow.to, arrow.bend);
|
|
4964
|
+
const approxW = arrow.label.length * ARROW_LABEL_FONT_SIZE2 * 0.6;
|
|
4965
|
+
const padX = 6;
|
|
4966
|
+
const padY = 4;
|
|
4967
|
+
const lw = approxW + padX * 2;
|
|
4968
|
+
const lh = ARROW_LABEL_FONT_SIZE2 + padY * 2;
|
|
4969
|
+
out += `<rect x="${n(mid.x - lw / 2)}" y="${n(mid.y - lh / 2)}" width="${n(lw)}" height="${n(lh)}" rx="4" fill="rgba(255,255,255,0.9)" />`;
|
|
4970
|
+
out += `<text x="${n(mid.x)}" y="${n(mid.y)}" font-family="system-ui, sans-serif" font-size="${ARROW_LABEL_FONT_SIZE2}" fill="#1a1a1a" text-anchor="middle" dominant-baseline="central">${esc(arrow.label)}</text>`;
|
|
4971
|
+
}
|
|
4972
|
+
return out;
|
|
4973
|
+
}
|
|
4974
|
+
function emitImage(image, dataUri) {
|
|
4975
|
+
const href = dataUri ?? image.src;
|
|
4976
|
+
if (!href) return "";
|
|
4977
|
+
const { x, y } = image.position;
|
|
4978
|
+
const { w, h } = image.size;
|
|
4979
|
+
return `<image href="${esc(href)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
4980
|
+
}
|
|
4981
|
+
function emitText(text) {
|
|
4982
|
+
if (!text.text) return "";
|
|
4983
|
+
const pad = 2;
|
|
4984
|
+
let anchor = "start";
|
|
4985
|
+
let textX = text.position.x + pad;
|
|
4986
|
+
if (text.textAlign === "center") {
|
|
4987
|
+
anchor = "middle";
|
|
4988
|
+
textX = text.position.x + text.size.w / 2;
|
|
4989
|
+
} else if (text.textAlign === "right") {
|
|
4990
|
+
anchor = "end";
|
|
4991
|
+
textX = text.position.x + text.size.w - pad;
|
|
4992
|
+
}
|
|
4993
|
+
const lineHeight = text.fontSize * 1.4;
|
|
4994
|
+
const lines = text.text.split("\n");
|
|
4995
|
+
let out = "";
|
|
4996
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4997
|
+
const line = lines[i];
|
|
4998
|
+
if (line === void 0) continue;
|
|
4999
|
+
const y = text.position.y + pad + i * lineHeight;
|
|
5000
|
+
out += `<text x="${n(textX)}" y="${n(y)}" font-family="system-ui, sans-serif" font-size="${n(text.fontSize)}" fill="${esc(text.color)}" text-anchor="${anchor}" dominant-baseline="text-before-edge">${esc(line)}</text>`;
|
|
5001
|
+
}
|
|
5002
|
+
return out;
|
|
5003
|
+
}
|
|
5004
|
+
function emitNote(note, rasterScale) {
|
|
5005
|
+
const { x, y } = note.position;
|
|
5006
|
+
const { w, h } = note.size;
|
|
5007
|
+
if (typeof document === "undefined") return emitNotePlaceholder(note);
|
|
5008
|
+
const canvas = document.createElement("canvas");
|
|
5009
|
+
canvas.width = Math.max(1, Math.ceil(w * rasterScale));
|
|
5010
|
+
canvas.height = Math.max(1, Math.ceil(h * rasterScale));
|
|
5011
|
+
const ctx = canvas.getContext("2d");
|
|
5012
|
+
if (!ctx) return emitNotePlaceholder(note);
|
|
5013
|
+
ctx.scale(rasterScale, rasterScale);
|
|
5014
|
+
ctx.translate(-x, -y);
|
|
5015
|
+
renderNoteOnCanvas(ctx, note);
|
|
5016
|
+
let dataUri;
|
|
5017
|
+
try {
|
|
5018
|
+
dataUri = canvas.toDataURL();
|
|
5019
|
+
} catch {
|
|
5020
|
+
return emitNotePlaceholder(note);
|
|
5021
|
+
}
|
|
5022
|
+
if (!dataUri || !dataUri.startsWith("data:")) return emitNotePlaceholder(note);
|
|
5023
|
+
return `<image href="${esc(dataUri)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5024
|
+
}
|
|
5025
|
+
function emitNotePlaceholder(note) {
|
|
5026
|
+
const { x, y } = note.position;
|
|
5027
|
+
const { w, h } = note.size;
|
|
5028
|
+
return `<rect x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" rx="4" fill="${esc(note.backgroundColor)}" />`;
|
|
5029
|
+
}
|
|
5030
|
+
function emitGrid(grid, bounds) {
|
|
5031
|
+
if (grid.cellSize <= 0) return "";
|
|
5032
|
+
const vb = {
|
|
5033
|
+
minX: bounds.x,
|
|
5034
|
+
minY: bounds.y,
|
|
5035
|
+
maxX: bounds.x + bounds.w,
|
|
5036
|
+
maxY: bounds.y + bounds.h
|
|
5037
|
+
};
|
|
5038
|
+
const stroke = esc(grid.strokeColor);
|
|
5039
|
+
const sw = n(grid.strokeWidth);
|
|
5040
|
+
const op = n(grid.opacity);
|
|
5041
|
+
if (grid.gridType === "hex") {
|
|
5042
|
+
const centers = getHexCenters(vb, grid.cellSize, grid.hexOrientation);
|
|
5043
|
+
let d2 = "";
|
|
5044
|
+
for (const c of centers) {
|
|
5045
|
+
const verts = getHexVertices(c.x, c.y, grid.cellSize, grid.hexOrientation);
|
|
5046
|
+
const first = verts[0];
|
|
5047
|
+
if (!first) continue;
|
|
5048
|
+
d2 += `M${n(first.x)} ${n(first.y)}`;
|
|
5049
|
+
for (let i = 1; i < verts.length; i++) {
|
|
5050
|
+
const v = verts[i];
|
|
5051
|
+
if (v) d2 += `L${n(v.x)} ${n(v.y)}`;
|
|
5052
|
+
}
|
|
5053
|
+
d2 += "Z";
|
|
5054
|
+
}
|
|
5055
|
+
return `<path d="${d2}" fill="none" stroke="${stroke}" stroke-width="${sw}" opacity="${op}" />`;
|
|
5056
|
+
}
|
|
5057
|
+
const { verticals, horizontals } = getSquareGridLines(vb, grid.cellSize);
|
|
5058
|
+
let d = "";
|
|
5059
|
+
for (const gx of verticals) d += `M${n(gx)} ${n(vb.minY)}L${n(gx)} ${n(vb.maxY)}`;
|
|
5060
|
+
for (const gy of horizontals) d += `M${n(vb.minX)} ${n(gy)}L${n(vb.maxX)} ${n(gy)}`;
|
|
5061
|
+
return `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${sw}" opacity="${op}" />`;
|
|
5062
|
+
}
|
|
5063
|
+
function emitTemplate(template, grid) {
|
|
5064
|
+
if (grid && grid.gridType === "hex") {
|
|
5065
|
+
return emitHexTemplate(template, grid);
|
|
5066
|
+
}
|
|
5067
|
+
return emitGeometricTemplate(template);
|
|
5068
|
+
}
|
|
5069
|
+
function emitGeometricTemplate(t) {
|
|
5070
|
+
const { x: cx, y: cy } = t.position;
|
|
5071
|
+
const r = t.radius;
|
|
5072
|
+
const fill = esc(t.fillColor);
|
|
5073
|
+
const stroke = esc(t.strokeColor);
|
|
5074
|
+
const sw = n(t.strokeWidth);
|
|
5075
|
+
const op = n(t.opacity);
|
|
5076
|
+
const attrs = `fill="${fill}" stroke="${stroke}" stroke-width="${sw}" opacity="${op}"`;
|
|
5077
|
+
switch (t.templateShape) {
|
|
5078
|
+
case "circle":
|
|
5079
|
+
return `<circle cx="${n(cx)}" cy="${n(cy)}" r="${n(r)}" ${attrs} />`;
|
|
5080
|
+
case "square":
|
|
5081
|
+
return `<rect x="${n(cx - r / 2)}" y="${n(cy - r / 2)}" width="${n(r)}" height="${n(r)}" ${attrs} />`;
|
|
5082
|
+
case "cone": {
|
|
5083
|
+
const halfAngle = Math.atan(0.5);
|
|
5084
|
+
const a0 = t.angle - halfAngle;
|
|
5085
|
+
const a1 = t.angle + halfAngle;
|
|
5086
|
+
const p0x = cx + r * Math.cos(a0);
|
|
5087
|
+
const p0y = cy + r * Math.sin(a0);
|
|
5088
|
+
const p1x = cx + r * Math.cos(a1);
|
|
5089
|
+
const p1y = cy + r * Math.sin(a1);
|
|
5090
|
+
const large = a1 - a0 > Math.PI ? 1 : 0;
|
|
5091
|
+
return `<path d="M${n(cx)} ${n(cy)} L${n(p0x)} ${n(p0y)} A${n(r)} ${n(r)} 0 ${large} 1 ${n(p1x)} ${n(p1y)} Z" ${attrs} />`;
|
|
5092
|
+
}
|
|
5093
|
+
case "line": {
|
|
5094
|
+
const halfW = r / 12;
|
|
5095
|
+
const cos = Math.cos(t.angle);
|
|
5096
|
+
const sin = Math.sin(t.angle);
|
|
5097
|
+
const perpX = -sin * halfW;
|
|
5098
|
+
const perpY = cos * halfW;
|
|
5099
|
+
const pts = [
|
|
5100
|
+
[cx + perpX, cy + perpY],
|
|
5101
|
+
[cx + r * cos + perpX, cy + r * sin + perpY],
|
|
5102
|
+
[cx + r * cos - perpX, cy + r * sin - perpY],
|
|
5103
|
+
[cx - perpX, cy - perpY]
|
|
5104
|
+
].map(([px, py]) => `${n(px ?? 0)},${n(py ?? 0)}`).join(" ");
|
|
5105
|
+
return `<polygon points="${pts}" ${attrs} />`;
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
function emitHexTemplate(t, grid) {
|
|
5110
|
+
const cellSize = grid.cellSize;
|
|
5111
|
+
const orientation = grid.hexOrientation;
|
|
5112
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
5113
|
+
const radiusCells = t.radius / snapUnit;
|
|
5114
|
+
const center2 = t.position;
|
|
5115
|
+
let cells;
|
|
5116
|
+
switch (t.templateShape) {
|
|
5117
|
+
case "circle":
|
|
5118
|
+
cells = getHexCellsInRadius(center2, radiusCells, cellSize, orientation);
|
|
5119
|
+
break;
|
|
5120
|
+
case "cone":
|
|
5121
|
+
cells = getHexCellsInCone(center2, t.angle, radiusCells, cellSize, orientation);
|
|
5122
|
+
break;
|
|
5123
|
+
case "line":
|
|
5124
|
+
cells = getHexCellsInLine(center2, t.angle, radiusCells, cellSize, orientation);
|
|
5125
|
+
break;
|
|
5126
|
+
case "square":
|
|
5127
|
+
cells = getHexCellsInSquare(center2, radiusCells, cellSize, orientation);
|
|
5128
|
+
break;
|
|
5129
|
+
}
|
|
5130
|
+
let d = "";
|
|
5131
|
+
for (const cell of cells) {
|
|
5132
|
+
const verts = getHexVertices(cell.x, cell.y, cellSize, orientation);
|
|
5133
|
+
const first = verts[0];
|
|
5134
|
+
if (!first) continue;
|
|
5135
|
+
d += `M${n(first.x)} ${n(first.y)}`;
|
|
5136
|
+
for (let i = 1; i < verts.length; i++) {
|
|
5137
|
+
const v = verts[i];
|
|
5138
|
+
if (v) d += `L${n(v.x)} ${n(v.y)}`;
|
|
5139
|
+
}
|
|
5140
|
+
d += "Z";
|
|
5141
|
+
}
|
|
5142
|
+
return `<path d="${d}" fill="${esc(t.fillColor)}" stroke="${esc(t.strokeColor)}" stroke-width="${n(t.strokeWidth)}" opacity="${n(t.opacity)}" />`;
|
|
5143
|
+
}
|
|
5144
|
+
async function exportSvg(store, options = {}, layerManager) {
|
|
5145
|
+
const padding = options.padding ?? 0;
|
|
5146
|
+
const rasterScale = options.rasterScale ?? 2;
|
|
5147
|
+
const filter = options.filter;
|
|
5148
|
+
const allElements = store.getAll();
|
|
5149
|
+
let visibleElements = layerManager ? allElements.filter((el) => layerManager.isLayerVisible(el.layerId)) : allElements;
|
|
5150
|
+
if (filter) visibleElements = visibleElements.filter(filter);
|
|
5151
|
+
const bounds = computeBounds(visibleElements, padding);
|
|
5152
|
+
if (!bounds) {
|
|
5153
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
5154
|
+
}
|
|
5155
|
+
const remoteImages = visibleElements.filter(
|
|
5156
|
+
(el) => el.type === "image" && !el.src.startsWith("data:")
|
|
5157
|
+
);
|
|
5158
|
+
const imageCache = await loadImages(remoteImages);
|
|
5159
|
+
const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale);
|
|
5160
|
+
const grids = visibleElements.filter((el) => el.type === "grid");
|
|
5161
|
+
const firstGrid = grids[0];
|
|
5162
|
+
let body = "";
|
|
5163
|
+
if (options.background) {
|
|
5164
|
+
body += `<rect x="${n(bounds.x)}" y="${n(bounds.y)}" width="${n(bounds.w)}" height="${n(bounds.h)}" fill="${esc(options.background)}" />`;
|
|
5165
|
+
}
|
|
5166
|
+
for (const el of visibleElements) {
|
|
5167
|
+
body += emitElement(el, imageDataUris, rasterScale, firstGrid, store);
|
|
5168
|
+
}
|
|
5169
|
+
for (const grid of grids) {
|
|
5170
|
+
body += emitGrid(grid, bounds);
|
|
5171
|
+
}
|
|
5172
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${n(bounds.w)}" height="${n(bounds.h)}" viewBox="${n(bounds.x)} ${n(bounds.y)} ${n(bounds.w)} ${n(bounds.h)}">${body}</svg>`;
|
|
5173
|
+
}
|
|
5174
|
+
function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
5175
|
+
switch (el.type) {
|
|
5176
|
+
case "stroke":
|
|
5177
|
+
return withRotationSvg(el, emitStroke(el));
|
|
5178
|
+
case "shape":
|
|
5179
|
+
return withRotationSvg(el, emitShape(el));
|
|
5180
|
+
case "arrow":
|
|
5181
|
+
return emitArrow(el, store);
|
|
5182
|
+
case "image":
|
|
5183
|
+
return withRotationSvg(el, emitImage(el, imageDataUris.get(el.id)));
|
|
5184
|
+
case "text":
|
|
5185
|
+
return withRotationSvg(el, emitText(el));
|
|
5186
|
+
case "note":
|
|
5187
|
+
return withRotationSvg(el, emitNote(el, rasterScale));
|
|
5188
|
+
case "template":
|
|
5189
|
+
return emitTemplate(el, firstGrid);
|
|
5190
|
+
case "grid":
|
|
5191
|
+
return "";
|
|
5192
|
+
case "html":
|
|
5193
|
+
return "";
|
|
5194
|
+
default:
|
|
5195
|
+
return "";
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
function encodeImages(elements, imageCache, rasterScale) {
|
|
5199
|
+
const out = /* @__PURE__ */ new Map();
|
|
5200
|
+
for (const el of elements) {
|
|
5201
|
+
if (el.type !== "image") continue;
|
|
5202
|
+
if (el.src.startsWith("data:")) {
|
|
5203
|
+
out.set(el.id, el.src);
|
|
5204
|
+
continue;
|
|
5205
|
+
}
|
|
5206
|
+
const img = imageCache.get(el.id);
|
|
5207
|
+
if (!img || typeof document === "undefined") continue;
|
|
5208
|
+
const canvas = document.createElement("canvas");
|
|
5209
|
+
canvas.width = Math.max(1, Math.ceil(el.size.w * rasterScale));
|
|
5210
|
+
canvas.height = Math.max(1, Math.ceil(el.size.h * rasterScale));
|
|
5211
|
+
const ctx = canvas.getContext("2d");
|
|
5212
|
+
if (!ctx) continue;
|
|
5213
|
+
try {
|
|
5214
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
5215
|
+
const uri = canvas.toDataURL();
|
|
5216
|
+
if (uri.startsWith("data:")) out.set(el.id, uri);
|
|
5217
|
+
} catch {
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
return out;
|
|
5221
|
+
}
|
|
5222
|
+
|
|
4772
5223
|
// src/layers/layer-manager.ts
|
|
4773
5224
|
var LayerManager = class {
|
|
4774
5225
|
constructor(store) {
|
|
@@ -5929,13 +6380,13 @@ var SelectionOps = class {
|
|
|
5929
6380
|
if (!first || !last) return;
|
|
5930
6381
|
const c0 = center2(first.bounds);
|
|
5931
6382
|
const cN = center2(last.bounds);
|
|
5932
|
-
const
|
|
6383
|
+
const n2 = sorted.length;
|
|
5933
6384
|
this.deps.recorder.begin();
|
|
5934
6385
|
const moved = [];
|
|
5935
|
-
for (let i = 1; i <
|
|
6386
|
+
for (let i = 1; i < n2 - 1; i++) {
|
|
5936
6387
|
const item = sorted[i];
|
|
5937
6388
|
if (!item || !this.isMovable(item.el)) continue;
|
|
5938
|
-
const target = c0 + i * (cN - c0) / (
|
|
6389
|
+
const target = c0 + i * (cN - c0) / (n2 - 1);
|
|
5939
6390
|
const delta = target - center2(item.bounds);
|
|
5940
6391
|
if (delta === 0) continue;
|
|
5941
6392
|
const [dx, dy] = axis === "horizontal" ? [delta, 0] : [0, delta];
|
|
@@ -6273,7 +6724,10 @@ var Viewport = class {
|
|
|
6273
6724
|
this.getSelectTool()?.selectAtPoint(world, this.toolContext);
|
|
6274
6725
|
this.openContextMenu(screenPos);
|
|
6275
6726
|
},
|
|
6276
|
-
shortcuts: options.shortcuts
|
|
6727
|
+
shortcuts: options.shortcuts,
|
|
6728
|
+
addImage: (src, world) => this.addImage(src, world),
|
|
6729
|
+
getCenteredWorld: () => this.centeredPosition({ w: 300, h: 200 }),
|
|
6730
|
+
onPaste: options.onPaste
|
|
6277
6731
|
});
|
|
6278
6732
|
if (options.contextMenu !== false) {
|
|
6279
6733
|
this.contextMenu = new ContextMenu({
|
|
@@ -6413,6 +6867,7 @@ var Viewport = class {
|
|
|
6413
6867
|
gridController;
|
|
6414
6868
|
interactions;
|
|
6415
6869
|
contextMenu = null;
|
|
6870
|
+
htmlRenderers = /* @__PURE__ */ new Map();
|
|
6416
6871
|
get ctx() {
|
|
6417
6872
|
return this.canvasEl.getContext("2d");
|
|
6418
6873
|
}
|
|
@@ -6454,6 +6909,9 @@ var Viewport = class {
|
|
|
6454
6909
|
async exportImage(options) {
|
|
6455
6910
|
return exportImage(this.store, options, this.layerManager);
|
|
6456
6911
|
}
|
|
6912
|
+
async exportSVG(options) {
|
|
6913
|
+
return exportSvg(this.store, options, this.layerManager);
|
|
6914
|
+
}
|
|
6457
6915
|
loadState(state) {
|
|
6458
6916
|
this.inputHandler.flushPendingHistory();
|
|
6459
6917
|
this.historyRecorder.pause();
|
|
@@ -6467,19 +6925,24 @@ var Viewport = class {
|
|
|
6467
6925
|
this.layerManager.setActiveLayer(state.activeLayerId);
|
|
6468
6926
|
}
|
|
6469
6927
|
this.domNodeManager.reattachHtmlContent(this.store);
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6928
|
+
for (const el of this.store.getElementsByType("html")) {
|
|
6929
|
+
if (this.domNodeManager.hasContent(el.id)) continue;
|
|
6930
|
+
const factory = el.htmlType ? this.htmlRenderers.get(el.htmlType) : void 0;
|
|
6931
|
+
const rebuilt = factory ? factory(el) : null;
|
|
6932
|
+
if (rebuilt) {
|
|
6933
|
+
this.domNodeManager.storeHtmlContent(el.id, rebuilt);
|
|
6934
|
+
this.domNodeManager.syncDomNode(el);
|
|
6935
|
+
}
|
|
6936
|
+
if (this.onHtmlElementMount) {
|
|
6937
|
+
if (!rebuilt) this.domNodeManager.syncDomNode(el);
|
|
6938
|
+
const node = this.domNodeManager.getNode(el.id);
|
|
6939
|
+
if (node) {
|
|
6940
|
+
this.onHtmlElementMount(el.id, el.domId, node);
|
|
6941
|
+
node.dataset["initialized"] = "true";
|
|
6942
|
+
Object.assign(node.style, {
|
|
6943
|
+
overflow: "hidden",
|
|
6944
|
+
pointerEvents: el.interactive ? "auto" : "none"
|
|
6945
|
+
});
|
|
6483
6946
|
}
|
|
6484
6947
|
}
|
|
6485
6948
|
}
|
|
@@ -6525,12 +6988,14 @@ var Viewport = class {
|
|
|
6525
6988
|
this.requestRender();
|
|
6526
6989
|
return image.id;
|
|
6527
6990
|
}
|
|
6528
|
-
addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
|
|
6991
|
+
addHtmlElement(dom, position, size = { w: 200, h: 150 }, opts) {
|
|
6529
6992
|
const domId = dom.id || void 0;
|
|
6530
6993
|
const el = createHtmlElement({
|
|
6531
6994
|
position,
|
|
6532
6995
|
size,
|
|
6533
6996
|
domId,
|
|
6997
|
+
htmlType: opts?.htmlType,
|
|
6998
|
+
data: opts?.data,
|
|
6534
6999
|
layerId: this.layerManager.activeLayerId
|
|
6535
7000
|
});
|
|
6536
7001
|
this.domNodeManager.storeHtmlContent(el.id, dom);
|
|
@@ -6571,6 +7036,9 @@ var Viewport = class {
|
|
|
6571
7036
|
this.layerManager.removeLayer(id);
|
|
6572
7037
|
this.historyRecorder.commit();
|
|
6573
7038
|
}
|
|
7039
|
+
registerHtmlRenderer(htmlType, factory) {
|
|
7040
|
+
this.htmlRenderers.set(htmlType, factory);
|
|
7041
|
+
}
|
|
6574
7042
|
updateHtmlElement(id, newContent) {
|
|
6575
7043
|
const el = this.store.getById(id);
|
|
6576
7044
|
if (!el) throw new Error(`Element not found: ${id}`);
|
|
@@ -9043,7 +9511,7 @@ var TemplateTool = class {
|
|
|
9043
9511
|
};
|
|
9044
9512
|
|
|
9045
9513
|
// src/index.ts
|
|
9046
|
-
var VERSION = "0.
|
|
9514
|
+
var VERSION = "0.39.0";
|
|
9047
9515
|
export {
|
|
9048
9516
|
ArrowTool,
|
|
9049
9517
|
AutoSave,
|
|
@@ -9077,6 +9545,7 @@ export {
|
|
|
9077
9545
|
createText,
|
|
9078
9546
|
drawHexPath,
|
|
9079
9547
|
exportImage,
|
|
9548
|
+
exportSvg,
|
|
9080
9549
|
getActiveFormats,
|
|
9081
9550
|
getArrowBounds,
|
|
9082
9551
|
getArrowControlPoint,
|