@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.cjs
CHANGED
|
@@ -52,6 +52,7 @@ __export(index_exports, {
|
|
|
52
52
|
createText: () => createText,
|
|
53
53
|
drawHexPath: () => drawHexPath,
|
|
54
54
|
exportImage: () => exportImage,
|
|
55
|
+
exportSvg: () => exportSvg,
|
|
55
56
|
getActiveFormats: () => getActiveFormats,
|
|
56
57
|
getArrowBounds: () => getArrowBounds,
|
|
57
58
|
getArrowControlPoint: () => getArrowControlPoint,
|
|
@@ -924,6 +925,11 @@ var KeyboardActions = class {
|
|
|
924
925
|
this.nudgeTxId = recorder?.currentTransactionId ?? null;
|
|
925
926
|
} else {
|
|
926
927
|
clearTimeout(this.nudgeTimer);
|
|
928
|
+
const recorder = this.deps.getHistoryRecorder();
|
|
929
|
+
if (recorder?.currentTransactionId !== this.nudgeTxId) {
|
|
930
|
+
recorder?.begin();
|
|
931
|
+
this.nudgeTxId = recorder?.currentTransactionId ?? null;
|
|
932
|
+
}
|
|
927
933
|
}
|
|
928
934
|
const moved = sel.tool.nudgeSelection(dx * step, dy * step, sel.ctx);
|
|
929
935
|
this.nudgeTimer = setTimeout(() => this.flushPendingNudge(), 400);
|
|
@@ -934,7 +940,7 @@ var KeyboardActions = class {
|
|
|
934
940
|
clearTimeout(this.nudgeTimer);
|
|
935
941
|
this.nudgeTimer = null;
|
|
936
942
|
const recorder = this.deps.getHistoryRecorder();
|
|
937
|
-
if (
|
|
943
|
+
if (recorder?.currentTransactionId === this.nudgeTxId) {
|
|
938
944
|
recorder?.commit();
|
|
939
945
|
}
|
|
940
946
|
this.nudgeTxId = null;
|
|
@@ -1160,7 +1166,6 @@ var DEFAULT_BINDINGS = [
|
|
|
1160
1166
|
["redo", ["mod+y", "mod+shift+z"]],
|
|
1161
1167
|
["select-all", ["mod+a"]],
|
|
1162
1168
|
["copy", ["mod+c"]],
|
|
1163
|
-
["paste", ["mod+v"]],
|
|
1164
1169
|
["duplicate", ["mod+d"]],
|
|
1165
1170
|
["z-forward", ["]"]],
|
|
1166
1171
|
["z-backward", ["["]],
|
|
@@ -1245,6 +1250,9 @@ function bindingMatches(p, e, allowShift) {
|
|
|
1245
1250
|
if (e.altKey !== p.alt) return false;
|
|
1246
1251
|
return p.digit ? e.code === `Digit${p.key}` : e.key.toLowerCase() === p.key;
|
|
1247
1252
|
}
|
|
1253
|
+
function sameBinding(a, b) {
|
|
1254
|
+
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;
|
|
1255
|
+
}
|
|
1248
1256
|
function toArray(bindings) {
|
|
1249
1257
|
if (bindings === null) return [];
|
|
1250
1258
|
return Array.isArray(bindings) ? [...bindings] : [bindings];
|
|
@@ -1273,6 +1281,16 @@ var ShortcutMap = class {
|
|
|
1273
1281
|
rebind(action, bindings) {
|
|
1274
1282
|
const list = toArray(bindings);
|
|
1275
1283
|
const parsedList = list.map(parseBinding);
|
|
1284
|
+
for (const p of parsedList) {
|
|
1285
|
+
for (const [otherAction, otherList] of this.parsed) {
|
|
1286
|
+
if (otherAction === action) continue;
|
|
1287
|
+
if (otherList.some((q) => sameBinding(p, q))) {
|
|
1288
|
+
console.warn(
|
|
1289
|
+
`[fieldnotes] shortcut binding for "${action}" conflicts with "${otherAction}"; first registered wins`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1276
1294
|
this.raw.set(action, list);
|
|
1277
1295
|
this.parsed.set(action, parsedList);
|
|
1278
1296
|
}
|
|
@@ -1322,6 +1340,7 @@ var KeyboardHandler = class {
|
|
|
1322
1340
|
this.shortcutMap = new ShortcutMap(deps.shortcuts?.bindings);
|
|
1323
1341
|
window.addEventListener("keydown", this.onKeyDown, { signal: deps.abortSignal });
|
|
1324
1342
|
window.addEventListener("keyup", this.onKeyUp, { signal: deps.abortSignal });
|
|
1343
|
+
window.addEventListener("paste", this.onPaste, { signal: deps.abortSignal });
|
|
1325
1344
|
}
|
|
1326
1345
|
shortcutMap;
|
|
1327
1346
|
get shortcuts() {
|
|
@@ -1337,12 +1356,15 @@ var KeyboardHandler = class {
|
|
|
1337
1356
|
zoomToLevel(level) {
|
|
1338
1357
|
this.deps.camera.zoomAt(level, this.viewportCenter());
|
|
1339
1358
|
}
|
|
1359
|
+
shouldHandle(target) {
|
|
1360
|
+
const el = target;
|
|
1361
|
+
if (el?.isContentEditable) return false;
|
|
1362
|
+
const tag = el?.tagName;
|
|
1363
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return false;
|
|
1364
|
+
return this.isInScope();
|
|
1365
|
+
}
|
|
1340
1366
|
onKeyDown = (e) => {
|
|
1341
|
-
|
|
1342
|
-
if (target?.isContentEditable) return;
|
|
1343
|
-
const tag = target?.tagName;
|
|
1344
|
-
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
|
|
1345
|
-
if (!this.isInScope()) return;
|
|
1367
|
+
if (!this.shouldHandle(e.target)) return;
|
|
1346
1368
|
if (e.key === " ") {
|
|
1347
1369
|
this.deps.setSpaceHeld(true);
|
|
1348
1370
|
}
|
|
@@ -1364,6 +1386,34 @@ var KeyboardHandler = class {
|
|
|
1364
1386
|
}
|
|
1365
1387
|
}
|
|
1366
1388
|
};
|
|
1389
|
+
onPaste = (e) => {
|
|
1390
|
+
if (!this.shouldHandle(e.target)) return;
|
|
1391
|
+
const items = e.clipboardData?.items;
|
|
1392
|
+
let file = null;
|
|
1393
|
+
if (items) {
|
|
1394
|
+
for (const it of items) {
|
|
1395
|
+
if (it.kind === "file" && it.type.startsWith("image/")) {
|
|
1396
|
+
file = it.getAsFile();
|
|
1397
|
+
break;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
if (file) {
|
|
1402
|
+
e.preventDefault();
|
|
1403
|
+
const world = this.deps.getLastPointerWorld() ?? this.deps.getCenteredWorld();
|
|
1404
|
+
if (this.deps.onPaste) {
|
|
1405
|
+
this.deps.onPaste(e, world);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
const reader = new FileReader();
|
|
1409
|
+
reader.onload = () => {
|
|
1410
|
+
if (typeof reader.result === "string") this.deps.addImage(reader.result, world);
|
|
1411
|
+
};
|
|
1412
|
+
reader.readAsDataURL(file);
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
this.deps.actions.paste();
|
|
1416
|
+
};
|
|
1367
1417
|
runAction(action, e) {
|
|
1368
1418
|
switch (action) {
|
|
1369
1419
|
case "delete":
|
|
@@ -1512,7 +1562,11 @@ var InputHandler = class {
|
|
|
1512
1562
|
this.spaceHeld = v;
|
|
1513
1563
|
},
|
|
1514
1564
|
getActivePointerCount: () => this.activePointers.size,
|
|
1515
|
-
dispatchToolHover: (e) => this.dispatchToolHover(e)
|
|
1565
|
+
dispatchToolHover: (e) => this.dispatchToolHover(e),
|
|
1566
|
+
addImage: options.addImage ?? (() => ""),
|
|
1567
|
+
getLastPointerWorld: () => this.lastPointerWorld(),
|
|
1568
|
+
getCenteredWorld: options.getCenteredWorld ?? (() => ({ x: 0, y: 0 })),
|
|
1569
|
+
onPaste: options.onPaste
|
|
1516
1570
|
});
|
|
1517
1571
|
this.element.style.touchAction = "none";
|
|
1518
1572
|
if (this.scope === "focus") {
|
|
@@ -2172,12 +2226,12 @@ function smoothToSegments(points) {
|
|
|
2172
2226
|
return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
|
|
2173
2227
|
}
|
|
2174
2228
|
const segments = [];
|
|
2175
|
-
const
|
|
2176
|
-
for (let i = 0; i <
|
|
2229
|
+
const n2 = points.length;
|
|
2230
|
+
for (let i = 0; i < n2 - 1; i++) {
|
|
2177
2231
|
const p0 = points[Math.max(0, i - 1)];
|
|
2178
2232
|
const p1 = points[i];
|
|
2179
2233
|
const p2 = points[i + 1];
|
|
2180
|
-
const p3 = points[Math.min(
|
|
2234
|
+
const p3 = points[Math.min(n2 - 1, i + 2)];
|
|
2181
2235
|
if (!p0 || !p1 || !p2 || !p3) continue;
|
|
2182
2236
|
const cp1 = {
|
|
2183
2237
|
x: p1.x + (p2.x - p0.x) / 6,
|
|
@@ -2867,11 +2921,11 @@ function pixelToOffset(x, y, cellSize, orientation) {
|
|
|
2867
2921
|
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
2868
2922
|
return { col, row: Math.round((y - offsetY) / hexH) };
|
|
2869
2923
|
}
|
|
2870
|
-
function enumerateHexRing(centerQ, centerR,
|
|
2924
|
+
function enumerateHexRing(centerQ, centerR, n2, orientation, cellSize) {
|
|
2871
2925
|
const cells = [];
|
|
2872
|
-
for (let dq = -
|
|
2873
|
-
const rMin = Math.max(-
|
|
2874
|
-
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);
|
|
2875
2929
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2876
2930
|
const absQ = centerQ + dq;
|
|
2877
2931
|
const absR = centerR + dr;
|
|
@@ -2892,28 +2946,28 @@ function getHexDistance(a, b, cellSize, orientation) {
|
|
|
2892
2946
|
return Math.max(Math.abs(dq), Math.abs(dr), Math.abs(ds));
|
|
2893
2947
|
}
|
|
2894
2948
|
function getHexCellsInRadius(center2, radiusCells, cellSize, orientation) {
|
|
2895
|
-
const
|
|
2949
|
+
const n2 = Math.round(radiusCells);
|
|
2896
2950
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2897
2951
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2898
|
-
if (
|
|
2952
|
+
if (n2 <= 0) {
|
|
2899
2953
|
return [offsetToPixel(off.col, off.row, cellSize, orientation)];
|
|
2900
2954
|
}
|
|
2901
|
-
return enumerateHexRing(cube.q, cube.r,
|
|
2955
|
+
return enumerateHexRing(cube.q, cube.r, n2, orientation, cellSize);
|
|
2902
2956
|
}
|
|
2903
2957
|
function getHexCellsInCone(center2, angle, radiusCells, cellSize, orientation) {
|
|
2904
|
-
const
|
|
2958
|
+
const n2 = Math.round(radiusCells);
|
|
2905
2959
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2906
2960
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2907
2961
|
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
2908
|
-
if (
|
|
2962
|
+
if (n2 <= 0) return [centerPixel];
|
|
2909
2963
|
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
2910
2964
|
const step = Math.PI / 3;
|
|
2911
2965
|
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
2912
2966
|
const halfAngle = Math.PI / 6 + 1e-6;
|
|
2913
2967
|
const cells = [centerPixel];
|
|
2914
|
-
for (let dq = -
|
|
2915
|
-
const rMin = Math.max(-
|
|
2916
|
-
const rMax = Math.min(
|
|
2968
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
2969
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
2970
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
2917
2971
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2918
2972
|
if (dq === 0 && dr === 0) continue;
|
|
2919
2973
|
const absQ = cube.q + dq;
|
|
@@ -2937,23 +2991,23 @@ function getHexCellsInCone(center2, angle, radiusCells, cellSize, orientation) {
|
|
|
2937
2991
|
return cells;
|
|
2938
2992
|
}
|
|
2939
2993
|
function getHexCellsInLine(center2, angle, radiusCells, cellSize, orientation) {
|
|
2940
|
-
const
|
|
2994
|
+
const n2 = Math.round(radiusCells);
|
|
2941
2995
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2942
2996
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2943
2997
|
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
2944
|
-
if (
|
|
2998
|
+
if (n2 <= 0) return [centerPixel];
|
|
2945
2999
|
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
2946
3000
|
const step = Math.PI / 3;
|
|
2947
3001
|
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
2948
3002
|
const cos = Math.cos(snappedAngle);
|
|
2949
3003
|
const sin = Math.sin(snappedAngle);
|
|
2950
3004
|
const snapUnit = Math.sqrt(3) * cellSize;
|
|
2951
|
-
const lineLength =
|
|
3005
|
+
const lineLength = n2 * snapUnit;
|
|
2952
3006
|
const halfWidth = snapUnit * 0.5 + 1e-6;
|
|
2953
3007
|
const cells = [];
|
|
2954
|
-
for (let dq = -
|
|
2955
|
-
const rMin = Math.max(-
|
|
2956
|
-
const rMax = Math.min(
|
|
3008
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
3009
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
3010
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
2957
3011
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2958
3012
|
const absQ = cube.q + dq;
|
|
2959
3013
|
const absR = cube.r + dr;
|
|
@@ -2975,17 +3029,17 @@ function getHexCellsInLine(center2, angle, radiusCells, cellSize, orientation) {
|
|
|
2975
3029
|
return cells;
|
|
2976
3030
|
}
|
|
2977
3031
|
function getHexCellsInSquare(center2, radiusCells, cellSize, orientation) {
|
|
2978
|
-
const
|
|
3032
|
+
const n2 = Math.round(radiusCells);
|
|
2979
3033
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2980
3034
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2981
3035
|
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
2982
|
-
if (
|
|
3036
|
+
if (n2 <= 0) return [centerPixel];
|
|
2983
3037
|
const snapUnit = Math.sqrt(3) * cellSize;
|
|
2984
|
-
const halfSide =
|
|
3038
|
+
const halfSide = n2 * snapUnit / 2;
|
|
2985
3039
|
const cells = [];
|
|
2986
|
-
for (let dq = -
|
|
2987
|
-
const rMin = Math.max(-
|
|
2988
|
-
const rMax = Math.min(
|
|
3040
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
3041
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
3042
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
2989
3043
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2990
3044
|
const absQ = cube.q + dq;
|
|
2991
3045
|
const absR = cube.r + dr;
|
|
@@ -3172,6 +3226,58 @@ function getSquareGridLines(bounds, cellSize) {
|
|
|
3172
3226
|
}
|
|
3173
3227
|
return { verticals, horizontals };
|
|
3174
3228
|
}
|
|
3229
|
+
function getHexVertices(cx, cy, circumradius, orientation) {
|
|
3230
|
+
const vertices = [];
|
|
3231
|
+
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
3232
|
+
for (let i = 0; i < 6; i++) {
|
|
3233
|
+
const angle = Math.PI / 3 * i + angleOffset;
|
|
3234
|
+
vertices.push({
|
|
3235
|
+
x: cx + circumradius * Math.cos(angle),
|
|
3236
|
+
y: cy + circumradius * Math.sin(angle)
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
3239
|
+
return vertices;
|
|
3240
|
+
}
|
|
3241
|
+
function getHexCenters(bounds, circumradius, orientation) {
|
|
3242
|
+
if (circumradius <= 0) return [];
|
|
3243
|
+
const centers = [];
|
|
3244
|
+
if (orientation === "pointy") {
|
|
3245
|
+
const hexW = Math.sqrt(3) * circumradius;
|
|
3246
|
+
const hexH = 2 * circumradius;
|
|
3247
|
+
const rowH = hexH * 0.75;
|
|
3248
|
+
const startRow = Math.floor((bounds.minY - circumradius) / rowH);
|
|
3249
|
+
const endRow = Math.ceil((bounds.maxY + circumradius) / rowH);
|
|
3250
|
+
const startCol = Math.floor((bounds.minX - hexW) / hexW);
|
|
3251
|
+
const endCol = Math.ceil((bounds.maxX + hexW) / hexW);
|
|
3252
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
3253
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
3254
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
3255
|
+
centers.push({
|
|
3256
|
+
x: col * hexW + offsetX,
|
|
3257
|
+
y: row * rowH
|
|
3258
|
+
});
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
} else {
|
|
3262
|
+
const hexW = 2 * circumradius;
|
|
3263
|
+
const hexH = Math.sqrt(3) * circumradius;
|
|
3264
|
+
const colW = hexW * 0.75;
|
|
3265
|
+
const startCol = Math.floor((bounds.minX - circumradius) / colW);
|
|
3266
|
+
const endCol = Math.ceil((bounds.maxX + circumradius) / colW);
|
|
3267
|
+
const startRow = Math.floor((bounds.minY - hexH) / hexH);
|
|
3268
|
+
const endRow = Math.ceil((bounds.maxY + hexH) / hexH);
|
|
3269
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
3270
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
3271
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
3272
|
+
centers.push({
|
|
3273
|
+
x: col * colW,
|
|
3274
|
+
y: row * hexH + offsetY
|
|
3275
|
+
});
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
return centers;
|
|
3280
|
+
}
|
|
3175
3281
|
function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opacity) {
|
|
3176
3282
|
if (cellSize <= 0) return;
|
|
3177
3283
|
const { verticals, horizontals } = getSquareGridLines(bounds, cellSize);
|
|
@@ -3570,6 +3676,8 @@ function createHtmlElement(input) {
|
|
|
3570
3676
|
};
|
|
3571
3677
|
if (input.domId) el.domId = input.domId;
|
|
3572
3678
|
if (input.interactive) el.interactive = input.interactive;
|
|
3679
|
+
if (input.htmlType) el.htmlType = input.htmlType;
|
|
3680
|
+
if (input.data) el.data = input.data;
|
|
3573
3681
|
return el;
|
|
3574
3682
|
}
|
|
3575
3683
|
function createShape(input) {
|
|
@@ -4850,6 +4958,350 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
4850
4958
|
});
|
|
4851
4959
|
}
|
|
4852
4960
|
|
|
4961
|
+
// src/canvas/export-svg.ts
|
|
4962
|
+
var ARROWHEAD_LENGTH2 = 12;
|
|
4963
|
+
var ARROWHEAD_ANGLE2 = Math.PI / 6;
|
|
4964
|
+
var ARROW_LABEL_FONT_SIZE2 = 14;
|
|
4965
|
+
function esc(s) {
|
|
4966
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4967
|
+
}
|
|
4968
|
+
var n = (v) => Number.isFinite(v) ? `${Math.round(v * 1e3) / 1e3}` : "0";
|
|
4969
|
+
function elementCenter(el) {
|
|
4970
|
+
const b = getElementBounds(el);
|
|
4971
|
+
if (!b) return null;
|
|
4972
|
+
return { x: b.x + b.w / 2, y: b.y + b.h / 2 };
|
|
4973
|
+
}
|
|
4974
|
+
function withRotationSvg(el, fragment) {
|
|
4975
|
+
const angle = el.rotation ?? 0;
|
|
4976
|
+
if (!angle || !fragment) return fragment;
|
|
4977
|
+
const c = elementCenter(el);
|
|
4978
|
+
if (!c) return fragment;
|
|
4979
|
+
const deg = angle * 180 / Math.PI;
|
|
4980
|
+
return `<g transform="rotate(${n(deg)} ${n(c.x)} ${n(c.y)})">${fragment}</g>`;
|
|
4981
|
+
}
|
|
4982
|
+
var WIDTH_QUANTUM2 = 0.25;
|
|
4983
|
+
function emitStroke(stroke) {
|
|
4984
|
+
if (stroke.points.length < 2) return "";
|
|
4985
|
+
const data = getStrokeRenderData(stroke);
|
|
4986
|
+
const { x: ox, y: oy } = stroke.position;
|
|
4987
|
+
const byWidth = /* @__PURE__ */ new Map();
|
|
4988
|
+
for (let i = 0; i < data.segments.length; i++) {
|
|
4989
|
+
const seg = data.segments[i];
|
|
4990
|
+
const w = data.widths[i];
|
|
4991
|
+
if (!seg || w === void 0) continue;
|
|
4992
|
+
const q = Math.max(WIDTH_QUANTUM2, Math.round(w / WIDTH_QUANTUM2) * WIDTH_QUANTUM2);
|
|
4993
|
+
let parts = byWidth.get(q);
|
|
4994
|
+
if (!parts) {
|
|
4995
|
+
parts = [];
|
|
4996
|
+
byWidth.set(q, parts);
|
|
4997
|
+
}
|
|
4998
|
+
parts.push(
|
|
4999
|
+
`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)}`
|
|
5000
|
+
);
|
|
5001
|
+
}
|
|
5002
|
+
const blend = stroke.blendMode === "multiply" ? ' style="mix-blend-mode:multiply"' : "";
|
|
5003
|
+
let out = "";
|
|
5004
|
+
for (const [width, parts] of byWidth) {
|
|
5005
|
+
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} />`;
|
|
5006
|
+
}
|
|
5007
|
+
return out;
|
|
5008
|
+
}
|
|
5009
|
+
function emitShape(shape) {
|
|
5010
|
+
const { x, y } = shape.position;
|
|
5011
|
+
const { w, h } = shape.size;
|
|
5012
|
+
const fill = shape.fillColor !== "none" && shape.shape !== "line" ? esc(shape.fillColor) : "none";
|
|
5013
|
+
const stroke = shape.strokeWidth > 0 ? esc(shape.strokeColor) : "none";
|
|
5014
|
+
const sw = shape.strokeWidth > 0 ? ` stroke-width="${n(shape.strokeWidth)}"` : "";
|
|
5015
|
+
switch (shape.shape) {
|
|
5016
|
+
case "rectangle":
|
|
5017
|
+
return `<rect x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" fill="${fill}" stroke="${stroke}"${sw} />`;
|
|
5018
|
+
case "ellipse":
|
|
5019
|
+
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} />`;
|
|
5020
|
+
case "line": {
|
|
5021
|
+
const [a, b] = lineEndpoints(shape);
|
|
5022
|
+
return `<line x1="${n(a.x)}" y1="${n(a.y)}" x2="${n(b.x)}" y2="${n(b.y)}" stroke="${stroke}"${sw} stroke-linecap="round" />`;
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
function emitArrow(arrow, store) {
|
|
5027
|
+
const geometry = getArrowRenderGeometry(arrow);
|
|
5028
|
+
const { visualFrom: from, visualTo: to } = getVisualEndpoints(arrow, geometry, store);
|
|
5029
|
+
let d;
|
|
5030
|
+
if (arrow.bend !== 0) {
|
|
5031
|
+
const cp = geometry.controlPoint ?? getArrowControlPoint(from, to, arrow.bend);
|
|
5032
|
+
d = `M${n(from.x)} ${n(from.y)} Q${n(cp.x)} ${n(cp.y)} ${n(to.x)} ${n(to.y)}`;
|
|
5033
|
+
} else {
|
|
5034
|
+
d = `M${n(from.x)} ${n(from.y)} L${n(to.x)} ${n(to.y)}`;
|
|
5035
|
+
}
|
|
5036
|
+
const dash = arrow.fromBinding || arrow.toBinding ? ' stroke-dasharray="8 4"' : "";
|
|
5037
|
+
let out = `<path d="${d}" fill="none" stroke="${esc(arrow.color)}" stroke-width="${n(arrow.width)}" stroke-linecap="round"${dash} />`;
|
|
5038
|
+
const angle = geometry.tangentEnd;
|
|
5039
|
+
const p1x = to.x - ARROWHEAD_LENGTH2 * Math.cos(angle - ARROWHEAD_ANGLE2);
|
|
5040
|
+
const p1y = to.y - ARROWHEAD_LENGTH2 * Math.sin(angle - ARROWHEAD_ANGLE2);
|
|
5041
|
+
const p2x = to.x - ARROWHEAD_LENGTH2 * Math.cos(angle + ARROWHEAD_ANGLE2);
|
|
5042
|
+
const p2y = to.y - ARROWHEAD_LENGTH2 * Math.sin(angle + ARROWHEAD_ANGLE2);
|
|
5043
|
+
out += `<polygon points="${n(to.x)},${n(to.y)} ${n(p1x)},${n(p1y)} ${n(p2x)},${n(p2y)}" fill="${esc(arrow.color)}" />`;
|
|
5044
|
+
if (arrow.label && arrow.label.length > 0) {
|
|
5045
|
+
const mid = getArrowMidpoint(arrow.from, arrow.to, arrow.bend);
|
|
5046
|
+
const approxW = arrow.label.length * ARROW_LABEL_FONT_SIZE2 * 0.6;
|
|
5047
|
+
const padX = 6;
|
|
5048
|
+
const padY = 4;
|
|
5049
|
+
const lw = approxW + padX * 2;
|
|
5050
|
+
const lh = ARROW_LABEL_FONT_SIZE2 + padY * 2;
|
|
5051
|
+
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)" />`;
|
|
5052
|
+
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>`;
|
|
5053
|
+
}
|
|
5054
|
+
return out;
|
|
5055
|
+
}
|
|
5056
|
+
function emitImage(image, dataUri) {
|
|
5057
|
+
const href = dataUri ?? image.src;
|
|
5058
|
+
if (!href) return "";
|
|
5059
|
+
const { x, y } = image.position;
|
|
5060
|
+
const { w, h } = image.size;
|
|
5061
|
+
return `<image href="${esc(href)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5062
|
+
}
|
|
5063
|
+
function emitText(text) {
|
|
5064
|
+
if (!text.text) return "";
|
|
5065
|
+
const pad = 2;
|
|
5066
|
+
let anchor = "start";
|
|
5067
|
+
let textX = text.position.x + pad;
|
|
5068
|
+
if (text.textAlign === "center") {
|
|
5069
|
+
anchor = "middle";
|
|
5070
|
+
textX = text.position.x + text.size.w / 2;
|
|
5071
|
+
} else if (text.textAlign === "right") {
|
|
5072
|
+
anchor = "end";
|
|
5073
|
+
textX = text.position.x + text.size.w - pad;
|
|
5074
|
+
}
|
|
5075
|
+
const lineHeight = text.fontSize * 1.4;
|
|
5076
|
+
const lines = text.text.split("\n");
|
|
5077
|
+
let out = "";
|
|
5078
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5079
|
+
const line = lines[i];
|
|
5080
|
+
if (line === void 0) continue;
|
|
5081
|
+
const y = text.position.y + pad + i * lineHeight;
|
|
5082
|
+
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>`;
|
|
5083
|
+
}
|
|
5084
|
+
return out;
|
|
5085
|
+
}
|
|
5086
|
+
function emitNote(note, rasterScale) {
|
|
5087
|
+
const { x, y } = note.position;
|
|
5088
|
+
const { w, h } = note.size;
|
|
5089
|
+
if (typeof document === "undefined") return emitNotePlaceholder(note);
|
|
5090
|
+
const canvas = document.createElement("canvas");
|
|
5091
|
+
canvas.width = Math.max(1, Math.ceil(w * rasterScale));
|
|
5092
|
+
canvas.height = Math.max(1, Math.ceil(h * rasterScale));
|
|
5093
|
+
const ctx = canvas.getContext("2d");
|
|
5094
|
+
if (!ctx) return emitNotePlaceholder(note);
|
|
5095
|
+
ctx.scale(rasterScale, rasterScale);
|
|
5096
|
+
ctx.translate(-x, -y);
|
|
5097
|
+
renderNoteOnCanvas(ctx, note);
|
|
5098
|
+
let dataUri;
|
|
5099
|
+
try {
|
|
5100
|
+
dataUri = canvas.toDataURL();
|
|
5101
|
+
} catch {
|
|
5102
|
+
return emitNotePlaceholder(note);
|
|
5103
|
+
}
|
|
5104
|
+
if (!dataUri || !dataUri.startsWith("data:")) return emitNotePlaceholder(note);
|
|
5105
|
+
return `<image href="${esc(dataUri)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5106
|
+
}
|
|
5107
|
+
function emitNotePlaceholder(note) {
|
|
5108
|
+
const { x, y } = note.position;
|
|
5109
|
+
const { w, h } = note.size;
|
|
5110
|
+
return `<rect x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" rx="4" fill="${esc(note.backgroundColor)}" />`;
|
|
5111
|
+
}
|
|
5112
|
+
function emitGrid(grid, bounds) {
|
|
5113
|
+
if (grid.cellSize <= 0) return "";
|
|
5114
|
+
const vb = {
|
|
5115
|
+
minX: bounds.x,
|
|
5116
|
+
minY: bounds.y,
|
|
5117
|
+
maxX: bounds.x + bounds.w,
|
|
5118
|
+
maxY: bounds.y + bounds.h
|
|
5119
|
+
};
|
|
5120
|
+
const stroke = esc(grid.strokeColor);
|
|
5121
|
+
const sw = n(grid.strokeWidth);
|
|
5122
|
+
const op = n(grid.opacity);
|
|
5123
|
+
if (grid.gridType === "hex") {
|
|
5124
|
+
const centers = getHexCenters(vb, grid.cellSize, grid.hexOrientation);
|
|
5125
|
+
let d2 = "";
|
|
5126
|
+
for (const c of centers) {
|
|
5127
|
+
const verts = getHexVertices(c.x, c.y, grid.cellSize, grid.hexOrientation);
|
|
5128
|
+
const first = verts[0];
|
|
5129
|
+
if (!first) continue;
|
|
5130
|
+
d2 += `M${n(first.x)} ${n(first.y)}`;
|
|
5131
|
+
for (let i = 1; i < verts.length; i++) {
|
|
5132
|
+
const v = verts[i];
|
|
5133
|
+
if (v) d2 += `L${n(v.x)} ${n(v.y)}`;
|
|
5134
|
+
}
|
|
5135
|
+
d2 += "Z";
|
|
5136
|
+
}
|
|
5137
|
+
return `<path d="${d2}" fill="none" stroke="${stroke}" stroke-width="${sw}" opacity="${op}" />`;
|
|
5138
|
+
}
|
|
5139
|
+
const { verticals, horizontals } = getSquareGridLines(vb, grid.cellSize);
|
|
5140
|
+
let d = "";
|
|
5141
|
+
for (const gx of verticals) d += `M${n(gx)} ${n(vb.minY)}L${n(gx)} ${n(vb.maxY)}`;
|
|
5142
|
+
for (const gy of horizontals) d += `M${n(vb.minX)} ${n(gy)}L${n(vb.maxX)} ${n(gy)}`;
|
|
5143
|
+
return `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${sw}" opacity="${op}" />`;
|
|
5144
|
+
}
|
|
5145
|
+
function emitTemplate(template, grid) {
|
|
5146
|
+
if (grid && grid.gridType === "hex") {
|
|
5147
|
+
return emitHexTemplate(template, grid);
|
|
5148
|
+
}
|
|
5149
|
+
return emitGeometricTemplate(template);
|
|
5150
|
+
}
|
|
5151
|
+
function emitGeometricTemplate(t) {
|
|
5152
|
+
const { x: cx, y: cy } = t.position;
|
|
5153
|
+
const r = t.radius;
|
|
5154
|
+
const fill = esc(t.fillColor);
|
|
5155
|
+
const stroke = esc(t.strokeColor);
|
|
5156
|
+
const sw = n(t.strokeWidth);
|
|
5157
|
+
const op = n(t.opacity);
|
|
5158
|
+
const attrs = `fill="${fill}" stroke="${stroke}" stroke-width="${sw}" opacity="${op}"`;
|
|
5159
|
+
switch (t.templateShape) {
|
|
5160
|
+
case "circle":
|
|
5161
|
+
return `<circle cx="${n(cx)}" cy="${n(cy)}" r="${n(r)}" ${attrs} />`;
|
|
5162
|
+
case "square":
|
|
5163
|
+
return `<rect x="${n(cx - r / 2)}" y="${n(cy - r / 2)}" width="${n(r)}" height="${n(r)}" ${attrs} />`;
|
|
5164
|
+
case "cone": {
|
|
5165
|
+
const halfAngle = Math.atan(0.5);
|
|
5166
|
+
const a0 = t.angle - halfAngle;
|
|
5167
|
+
const a1 = t.angle + halfAngle;
|
|
5168
|
+
const p0x = cx + r * Math.cos(a0);
|
|
5169
|
+
const p0y = cy + r * Math.sin(a0);
|
|
5170
|
+
const p1x = cx + r * Math.cos(a1);
|
|
5171
|
+
const p1y = cy + r * Math.sin(a1);
|
|
5172
|
+
const large = a1 - a0 > Math.PI ? 1 : 0;
|
|
5173
|
+
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} />`;
|
|
5174
|
+
}
|
|
5175
|
+
case "line": {
|
|
5176
|
+
const halfW = r / 12;
|
|
5177
|
+
const cos = Math.cos(t.angle);
|
|
5178
|
+
const sin = Math.sin(t.angle);
|
|
5179
|
+
const perpX = -sin * halfW;
|
|
5180
|
+
const perpY = cos * halfW;
|
|
5181
|
+
const pts = [
|
|
5182
|
+
[cx + perpX, cy + perpY],
|
|
5183
|
+
[cx + r * cos + perpX, cy + r * sin + perpY],
|
|
5184
|
+
[cx + r * cos - perpX, cy + r * sin - perpY],
|
|
5185
|
+
[cx - perpX, cy - perpY]
|
|
5186
|
+
].map(([px, py]) => `${n(px ?? 0)},${n(py ?? 0)}`).join(" ");
|
|
5187
|
+
return `<polygon points="${pts}" ${attrs} />`;
|
|
5188
|
+
}
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
function emitHexTemplate(t, grid) {
|
|
5192
|
+
const cellSize = grid.cellSize;
|
|
5193
|
+
const orientation = grid.hexOrientation;
|
|
5194
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
5195
|
+
const radiusCells = t.radius / snapUnit;
|
|
5196
|
+
const center2 = t.position;
|
|
5197
|
+
let cells;
|
|
5198
|
+
switch (t.templateShape) {
|
|
5199
|
+
case "circle":
|
|
5200
|
+
cells = getHexCellsInRadius(center2, radiusCells, cellSize, orientation);
|
|
5201
|
+
break;
|
|
5202
|
+
case "cone":
|
|
5203
|
+
cells = getHexCellsInCone(center2, t.angle, radiusCells, cellSize, orientation);
|
|
5204
|
+
break;
|
|
5205
|
+
case "line":
|
|
5206
|
+
cells = getHexCellsInLine(center2, t.angle, radiusCells, cellSize, orientation);
|
|
5207
|
+
break;
|
|
5208
|
+
case "square":
|
|
5209
|
+
cells = getHexCellsInSquare(center2, radiusCells, cellSize, orientation);
|
|
5210
|
+
break;
|
|
5211
|
+
}
|
|
5212
|
+
let d = "";
|
|
5213
|
+
for (const cell of cells) {
|
|
5214
|
+
const verts = getHexVertices(cell.x, cell.y, cellSize, orientation);
|
|
5215
|
+
const first = verts[0];
|
|
5216
|
+
if (!first) continue;
|
|
5217
|
+
d += `M${n(first.x)} ${n(first.y)}`;
|
|
5218
|
+
for (let i = 1; i < verts.length; i++) {
|
|
5219
|
+
const v = verts[i];
|
|
5220
|
+
if (v) d += `L${n(v.x)} ${n(v.y)}`;
|
|
5221
|
+
}
|
|
5222
|
+
d += "Z";
|
|
5223
|
+
}
|
|
5224
|
+
return `<path d="${d}" fill="${esc(t.fillColor)}" stroke="${esc(t.strokeColor)}" stroke-width="${n(t.strokeWidth)}" opacity="${n(t.opacity)}" />`;
|
|
5225
|
+
}
|
|
5226
|
+
async function exportSvg(store, options = {}, layerManager) {
|
|
5227
|
+
const padding = options.padding ?? 0;
|
|
5228
|
+
const rasterScale = options.rasterScale ?? 2;
|
|
5229
|
+
const filter = options.filter;
|
|
5230
|
+
const allElements = store.getAll();
|
|
5231
|
+
let visibleElements = layerManager ? allElements.filter((el) => layerManager.isLayerVisible(el.layerId)) : allElements;
|
|
5232
|
+
if (filter) visibleElements = visibleElements.filter(filter);
|
|
5233
|
+
const bounds = computeBounds(visibleElements, padding);
|
|
5234
|
+
if (!bounds) {
|
|
5235
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
|
|
5236
|
+
}
|
|
5237
|
+
const remoteImages = visibleElements.filter(
|
|
5238
|
+
(el) => el.type === "image" && !el.src.startsWith("data:")
|
|
5239
|
+
);
|
|
5240
|
+
const imageCache = await loadImages(remoteImages);
|
|
5241
|
+
const imageDataUris = encodeImages(visibleElements, imageCache, rasterScale);
|
|
5242
|
+
const grids = visibleElements.filter((el) => el.type === "grid");
|
|
5243
|
+
const firstGrid = grids[0];
|
|
5244
|
+
let body = "";
|
|
5245
|
+
if (options.background) {
|
|
5246
|
+
body += `<rect x="${n(bounds.x)}" y="${n(bounds.y)}" width="${n(bounds.w)}" height="${n(bounds.h)}" fill="${esc(options.background)}" />`;
|
|
5247
|
+
}
|
|
5248
|
+
for (const el of visibleElements) {
|
|
5249
|
+
body += emitElement(el, imageDataUris, rasterScale, firstGrid, store);
|
|
5250
|
+
}
|
|
5251
|
+
for (const grid of grids) {
|
|
5252
|
+
body += emitGrid(grid, bounds);
|
|
5253
|
+
}
|
|
5254
|
+
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>`;
|
|
5255
|
+
}
|
|
5256
|
+
function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
5257
|
+
switch (el.type) {
|
|
5258
|
+
case "stroke":
|
|
5259
|
+
return withRotationSvg(el, emitStroke(el));
|
|
5260
|
+
case "shape":
|
|
5261
|
+
return withRotationSvg(el, emitShape(el));
|
|
5262
|
+
case "arrow":
|
|
5263
|
+
return emitArrow(el, store);
|
|
5264
|
+
case "image":
|
|
5265
|
+
return withRotationSvg(el, emitImage(el, imageDataUris.get(el.id)));
|
|
5266
|
+
case "text":
|
|
5267
|
+
return withRotationSvg(el, emitText(el));
|
|
5268
|
+
case "note":
|
|
5269
|
+
return withRotationSvg(el, emitNote(el, rasterScale));
|
|
5270
|
+
case "template":
|
|
5271
|
+
return emitTemplate(el, firstGrid);
|
|
5272
|
+
case "grid":
|
|
5273
|
+
return "";
|
|
5274
|
+
case "html":
|
|
5275
|
+
return "";
|
|
5276
|
+
default:
|
|
5277
|
+
return "";
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5280
|
+
function encodeImages(elements, imageCache, rasterScale) {
|
|
5281
|
+
const out = /* @__PURE__ */ new Map();
|
|
5282
|
+
for (const el of elements) {
|
|
5283
|
+
if (el.type !== "image") continue;
|
|
5284
|
+
if (el.src.startsWith("data:")) {
|
|
5285
|
+
out.set(el.id, el.src);
|
|
5286
|
+
continue;
|
|
5287
|
+
}
|
|
5288
|
+
const img = imageCache.get(el.id);
|
|
5289
|
+
if (!img || typeof document === "undefined") continue;
|
|
5290
|
+
const canvas = document.createElement("canvas");
|
|
5291
|
+
canvas.width = Math.max(1, Math.ceil(el.size.w * rasterScale));
|
|
5292
|
+
canvas.height = Math.max(1, Math.ceil(el.size.h * rasterScale));
|
|
5293
|
+
const ctx = canvas.getContext("2d");
|
|
5294
|
+
if (!ctx) continue;
|
|
5295
|
+
try {
|
|
5296
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
5297
|
+
const uri = canvas.toDataURL();
|
|
5298
|
+
if (uri.startsWith("data:")) out.set(el.id, uri);
|
|
5299
|
+
} catch {
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
return out;
|
|
5303
|
+
}
|
|
5304
|
+
|
|
4853
5305
|
// src/layers/layer-manager.ts
|
|
4854
5306
|
var LayerManager = class {
|
|
4855
5307
|
constructor(store) {
|
|
@@ -6010,13 +6462,13 @@ var SelectionOps = class {
|
|
|
6010
6462
|
if (!first || !last) return;
|
|
6011
6463
|
const c0 = center2(first.bounds);
|
|
6012
6464
|
const cN = center2(last.bounds);
|
|
6013
|
-
const
|
|
6465
|
+
const n2 = sorted.length;
|
|
6014
6466
|
this.deps.recorder.begin();
|
|
6015
6467
|
const moved = [];
|
|
6016
|
-
for (let i = 1; i <
|
|
6468
|
+
for (let i = 1; i < n2 - 1; i++) {
|
|
6017
6469
|
const item = sorted[i];
|
|
6018
6470
|
if (!item || !this.isMovable(item.el)) continue;
|
|
6019
|
-
const target = c0 + i * (cN - c0) / (
|
|
6471
|
+
const target = c0 + i * (cN - c0) / (n2 - 1);
|
|
6020
6472
|
const delta = target - center2(item.bounds);
|
|
6021
6473
|
if (delta === 0) continue;
|
|
6022
6474
|
const [dx, dy] = axis === "horizontal" ? [delta, 0] : [0, delta];
|
|
@@ -6354,7 +6806,10 @@ var Viewport = class {
|
|
|
6354
6806
|
this.getSelectTool()?.selectAtPoint(world, this.toolContext);
|
|
6355
6807
|
this.openContextMenu(screenPos);
|
|
6356
6808
|
},
|
|
6357
|
-
shortcuts: options.shortcuts
|
|
6809
|
+
shortcuts: options.shortcuts,
|
|
6810
|
+
addImage: (src, world) => this.addImage(src, world),
|
|
6811
|
+
getCenteredWorld: () => this.centeredPosition({ w: 300, h: 200 }),
|
|
6812
|
+
onPaste: options.onPaste
|
|
6358
6813
|
});
|
|
6359
6814
|
if (options.contextMenu !== false) {
|
|
6360
6815
|
this.contextMenu = new ContextMenu({
|
|
@@ -6494,6 +6949,7 @@ var Viewport = class {
|
|
|
6494
6949
|
gridController;
|
|
6495
6950
|
interactions;
|
|
6496
6951
|
contextMenu = null;
|
|
6952
|
+
htmlRenderers = /* @__PURE__ */ new Map();
|
|
6497
6953
|
get ctx() {
|
|
6498
6954
|
return this.canvasEl.getContext("2d");
|
|
6499
6955
|
}
|
|
@@ -6535,6 +6991,9 @@ var Viewport = class {
|
|
|
6535
6991
|
async exportImage(options) {
|
|
6536
6992
|
return exportImage(this.store, options, this.layerManager);
|
|
6537
6993
|
}
|
|
6994
|
+
async exportSVG(options) {
|
|
6995
|
+
return exportSvg(this.store, options, this.layerManager);
|
|
6996
|
+
}
|
|
6538
6997
|
loadState(state) {
|
|
6539
6998
|
this.inputHandler.flushPendingHistory();
|
|
6540
6999
|
this.historyRecorder.pause();
|
|
@@ -6548,19 +7007,24 @@ var Viewport = class {
|
|
|
6548
7007
|
this.layerManager.setActiveLayer(state.activeLayerId);
|
|
6549
7008
|
}
|
|
6550
7009
|
this.domNodeManager.reattachHtmlContent(this.store);
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
7010
|
+
for (const el of this.store.getElementsByType("html")) {
|
|
7011
|
+
if (this.domNodeManager.hasContent(el.id)) continue;
|
|
7012
|
+
const factory = el.htmlType ? this.htmlRenderers.get(el.htmlType) : void 0;
|
|
7013
|
+
const rebuilt = factory ? factory(el) : null;
|
|
7014
|
+
if (rebuilt) {
|
|
7015
|
+
this.domNodeManager.storeHtmlContent(el.id, rebuilt);
|
|
7016
|
+
this.domNodeManager.syncDomNode(el);
|
|
7017
|
+
}
|
|
7018
|
+
if (this.onHtmlElementMount) {
|
|
7019
|
+
if (!rebuilt) this.domNodeManager.syncDomNode(el);
|
|
7020
|
+
const node = this.domNodeManager.getNode(el.id);
|
|
7021
|
+
if (node) {
|
|
7022
|
+
this.onHtmlElementMount(el.id, el.domId, node);
|
|
7023
|
+
node.dataset["initialized"] = "true";
|
|
7024
|
+
Object.assign(node.style, {
|
|
7025
|
+
overflow: "hidden",
|
|
7026
|
+
pointerEvents: el.interactive ? "auto" : "none"
|
|
7027
|
+
});
|
|
6564
7028
|
}
|
|
6565
7029
|
}
|
|
6566
7030
|
}
|
|
@@ -6606,12 +7070,14 @@ var Viewport = class {
|
|
|
6606
7070
|
this.requestRender();
|
|
6607
7071
|
return image.id;
|
|
6608
7072
|
}
|
|
6609
|
-
addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
|
|
7073
|
+
addHtmlElement(dom, position, size = { w: 200, h: 150 }, opts) {
|
|
6610
7074
|
const domId = dom.id || void 0;
|
|
6611
7075
|
const el = createHtmlElement({
|
|
6612
7076
|
position,
|
|
6613
7077
|
size,
|
|
6614
7078
|
domId,
|
|
7079
|
+
htmlType: opts?.htmlType,
|
|
7080
|
+
data: opts?.data,
|
|
6615
7081
|
layerId: this.layerManager.activeLayerId
|
|
6616
7082
|
});
|
|
6617
7083
|
this.domNodeManager.storeHtmlContent(el.id, dom);
|
|
@@ -6652,6 +7118,9 @@ var Viewport = class {
|
|
|
6652
7118
|
this.layerManager.removeLayer(id);
|
|
6653
7119
|
this.historyRecorder.commit();
|
|
6654
7120
|
}
|
|
7121
|
+
registerHtmlRenderer(htmlType, factory) {
|
|
7122
|
+
this.htmlRenderers.set(htmlType, factory);
|
|
7123
|
+
}
|
|
6655
7124
|
updateHtmlElement(id, newContent) {
|
|
6656
7125
|
const el = this.store.getById(id);
|
|
6657
7126
|
if (!el) throw new Error(`Element not found: ${id}`);
|
|
@@ -9124,7 +9593,7 @@ var TemplateTool = class {
|
|
|
9124
9593
|
};
|
|
9125
9594
|
|
|
9126
9595
|
// src/index.ts
|
|
9127
|
-
var VERSION = "0.
|
|
9596
|
+
var VERSION = "0.39.0";
|
|
9128
9597
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9129
9598
|
0 && (module.exports = {
|
|
9130
9599
|
ArrowTool,
|
|
@@ -9159,6 +9628,7 @@ var VERSION = "0.38.6";
|
|
|
9159
9628
|
createText,
|
|
9160
9629
|
drawHexPath,
|
|
9161
9630
|
exportImage,
|
|
9631
|
+
exportSvg,
|
|
9162
9632
|
getActiveFormats,
|
|
9163
9633
|
getArrowBounds,
|
|
9164
9634
|
getArrowControlPoint,
|