@fieldnotes/core 0.38.7 → 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 +505 -53
- 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 +504 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1084,7 +1084,6 @@ var DEFAULT_BINDINGS = [
|
|
|
1084
1084
|
["redo", ["mod+y", "mod+shift+z"]],
|
|
1085
1085
|
["select-all", ["mod+a"]],
|
|
1086
1086
|
["copy", ["mod+c"]],
|
|
1087
|
-
["paste", ["mod+v"]],
|
|
1088
1087
|
["duplicate", ["mod+d"]],
|
|
1089
1088
|
["z-forward", ["]"]],
|
|
1090
1089
|
["z-backward", ["["]],
|
|
@@ -1259,6 +1258,7 @@ var KeyboardHandler = class {
|
|
|
1259
1258
|
this.shortcutMap = new ShortcutMap(deps.shortcuts?.bindings);
|
|
1260
1259
|
window.addEventListener("keydown", this.onKeyDown, { signal: deps.abortSignal });
|
|
1261
1260
|
window.addEventListener("keyup", this.onKeyUp, { signal: deps.abortSignal });
|
|
1261
|
+
window.addEventListener("paste", this.onPaste, { signal: deps.abortSignal });
|
|
1262
1262
|
}
|
|
1263
1263
|
shortcutMap;
|
|
1264
1264
|
get shortcuts() {
|
|
@@ -1274,12 +1274,15 @@ var KeyboardHandler = class {
|
|
|
1274
1274
|
zoomToLevel(level) {
|
|
1275
1275
|
this.deps.camera.zoomAt(level, this.viewportCenter());
|
|
1276
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
|
+
}
|
|
1277
1284
|
onKeyDown = (e) => {
|
|
1278
|
-
|
|
1279
|
-
if (target?.isContentEditable) return;
|
|
1280
|
-
const tag = target?.tagName;
|
|
1281
|
-
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
|
|
1282
|
-
if (!this.isInScope()) return;
|
|
1285
|
+
if (!this.shouldHandle(e.target)) return;
|
|
1283
1286
|
if (e.key === " ") {
|
|
1284
1287
|
this.deps.setSpaceHeld(true);
|
|
1285
1288
|
}
|
|
@@ -1301,6 +1304,34 @@ var KeyboardHandler = class {
|
|
|
1301
1304
|
}
|
|
1302
1305
|
}
|
|
1303
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
|
+
};
|
|
1304
1335
|
runAction(action, e) {
|
|
1305
1336
|
switch (action) {
|
|
1306
1337
|
case "delete":
|
|
@@ -1449,7 +1480,11 @@ var InputHandler = class {
|
|
|
1449
1480
|
this.spaceHeld = v;
|
|
1450
1481
|
},
|
|
1451
1482
|
getActivePointerCount: () => this.activePointers.size,
|
|
1452
|
-
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
|
|
1453
1488
|
});
|
|
1454
1489
|
this.element.style.touchAction = "none";
|
|
1455
1490
|
if (this.scope === "focus") {
|
|
@@ -2109,12 +2144,12 @@ function smoothToSegments(points) {
|
|
|
2109
2144
|
return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
|
|
2110
2145
|
}
|
|
2111
2146
|
const segments = [];
|
|
2112
|
-
const
|
|
2113
|
-
for (let i = 0; i <
|
|
2147
|
+
const n2 = points.length;
|
|
2148
|
+
for (let i = 0; i < n2 - 1; i++) {
|
|
2114
2149
|
const p0 = points[Math.max(0, i - 1)];
|
|
2115
2150
|
const p1 = points[i];
|
|
2116
2151
|
const p2 = points[i + 1];
|
|
2117
|
-
const p3 = points[Math.min(
|
|
2152
|
+
const p3 = points[Math.min(n2 - 1, i + 2)];
|
|
2118
2153
|
if (!p0 || !p1 || !p2 || !p3) continue;
|
|
2119
2154
|
const cp1 = {
|
|
2120
2155
|
x: p1.x + (p2.x - p0.x) / 6,
|
|
@@ -2804,11 +2839,11 @@ function pixelToOffset(x, y, cellSize, orientation) {
|
|
|
2804
2839
|
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
2805
2840
|
return { col, row: Math.round((y - offsetY) / hexH) };
|
|
2806
2841
|
}
|
|
2807
|
-
function enumerateHexRing(centerQ, centerR,
|
|
2842
|
+
function enumerateHexRing(centerQ, centerR, n2, orientation, cellSize) {
|
|
2808
2843
|
const cells = [];
|
|
2809
|
-
for (let dq = -
|
|
2810
|
-
const rMin = Math.max(-
|
|
2811
|
-
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);
|
|
2812
2847
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2813
2848
|
const absQ = centerQ + dq;
|
|
2814
2849
|
const absR = centerR + dr;
|
|
@@ -2829,28 +2864,28 @@ function getHexDistance(a, b, cellSize, orientation) {
|
|
|
2829
2864
|
return Math.max(Math.abs(dq), Math.abs(dr), Math.abs(ds));
|
|
2830
2865
|
}
|
|
2831
2866
|
function getHexCellsInRadius(center2, radiusCells, cellSize, orientation) {
|
|
2832
|
-
const
|
|
2867
|
+
const n2 = Math.round(radiusCells);
|
|
2833
2868
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2834
2869
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2835
|
-
if (
|
|
2870
|
+
if (n2 <= 0) {
|
|
2836
2871
|
return [offsetToPixel(off.col, off.row, cellSize, orientation)];
|
|
2837
2872
|
}
|
|
2838
|
-
return enumerateHexRing(cube.q, cube.r,
|
|
2873
|
+
return enumerateHexRing(cube.q, cube.r, n2, orientation, cellSize);
|
|
2839
2874
|
}
|
|
2840
2875
|
function getHexCellsInCone(center2, angle, radiusCells, cellSize, orientation) {
|
|
2841
|
-
const
|
|
2876
|
+
const n2 = Math.round(radiusCells);
|
|
2842
2877
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2843
2878
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2844
2879
|
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
2845
|
-
if (
|
|
2880
|
+
if (n2 <= 0) return [centerPixel];
|
|
2846
2881
|
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
2847
2882
|
const step = Math.PI / 3;
|
|
2848
2883
|
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
2849
2884
|
const halfAngle = Math.PI / 6 + 1e-6;
|
|
2850
2885
|
const cells = [centerPixel];
|
|
2851
|
-
for (let dq = -
|
|
2852
|
-
const rMin = Math.max(-
|
|
2853
|
-
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);
|
|
2854
2889
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2855
2890
|
if (dq === 0 && dr === 0) continue;
|
|
2856
2891
|
const absQ = cube.q + dq;
|
|
@@ -2874,23 +2909,23 @@ function getHexCellsInCone(center2, angle, radiusCells, cellSize, orientation) {
|
|
|
2874
2909
|
return cells;
|
|
2875
2910
|
}
|
|
2876
2911
|
function getHexCellsInLine(center2, angle, radiusCells, cellSize, orientation) {
|
|
2877
|
-
const
|
|
2912
|
+
const n2 = Math.round(radiusCells);
|
|
2878
2913
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2879
2914
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2880
2915
|
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
2881
|
-
if (
|
|
2916
|
+
if (n2 <= 0) return [centerPixel];
|
|
2882
2917
|
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
2883
2918
|
const step = Math.PI / 3;
|
|
2884
2919
|
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
2885
2920
|
const cos = Math.cos(snappedAngle);
|
|
2886
2921
|
const sin = Math.sin(snappedAngle);
|
|
2887
2922
|
const snapUnit = Math.sqrt(3) * cellSize;
|
|
2888
|
-
const lineLength =
|
|
2923
|
+
const lineLength = n2 * snapUnit;
|
|
2889
2924
|
const halfWidth = snapUnit * 0.5 + 1e-6;
|
|
2890
2925
|
const cells = [];
|
|
2891
|
-
for (let dq = -
|
|
2892
|
-
const rMin = Math.max(-
|
|
2893
|
-
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);
|
|
2894
2929
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2895
2930
|
const absQ = cube.q + dq;
|
|
2896
2931
|
const absR = cube.r + dr;
|
|
@@ -2912,17 +2947,17 @@ function getHexCellsInLine(center2, angle, radiusCells, cellSize, orientation) {
|
|
|
2912
2947
|
return cells;
|
|
2913
2948
|
}
|
|
2914
2949
|
function getHexCellsInSquare(center2, radiusCells, cellSize, orientation) {
|
|
2915
|
-
const
|
|
2950
|
+
const n2 = Math.round(radiusCells);
|
|
2916
2951
|
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
2917
2952
|
const cube = offsetToCube(off.col, off.row, orientation);
|
|
2918
2953
|
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
2919
|
-
if (
|
|
2954
|
+
if (n2 <= 0) return [centerPixel];
|
|
2920
2955
|
const snapUnit = Math.sqrt(3) * cellSize;
|
|
2921
|
-
const halfSide =
|
|
2956
|
+
const halfSide = n2 * snapUnit / 2;
|
|
2922
2957
|
const cells = [];
|
|
2923
|
-
for (let dq = -
|
|
2924
|
-
const rMin = Math.max(-
|
|
2925
|
-
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);
|
|
2926
2961
|
for (let dr = rMin; dr <= rMax; dr++) {
|
|
2927
2962
|
const absQ = cube.q + dq;
|
|
2928
2963
|
const absR = cube.r + dr;
|
|
@@ -3109,6 +3144,58 @@ function getSquareGridLines(bounds, cellSize) {
|
|
|
3109
3144
|
}
|
|
3110
3145
|
return { verticals, horizontals };
|
|
3111
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
|
+
}
|
|
3112
3199
|
function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opacity) {
|
|
3113
3200
|
if (cellSize <= 0) return;
|
|
3114
3201
|
const { verticals, horizontals } = getSquareGridLines(bounds, cellSize);
|
|
@@ -3507,6 +3594,8 @@ function createHtmlElement(input) {
|
|
|
3507
3594
|
};
|
|
3508
3595
|
if (input.domId) el.domId = input.domId;
|
|
3509
3596
|
if (input.interactive) el.interactive = input.interactive;
|
|
3597
|
+
if (input.htmlType) el.htmlType = input.htmlType;
|
|
3598
|
+
if (input.data) el.data = input.data;
|
|
3510
3599
|
return el;
|
|
3511
3600
|
}
|
|
3512
3601
|
function createShape(input) {
|
|
@@ -4787,6 +4876,350 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
4787
4876
|
});
|
|
4788
4877
|
}
|
|
4789
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
|
+
|
|
4790
5223
|
// src/layers/layer-manager.ts
|
|
4791
5224
|
var LayerManager = class {
|
|
4792
5225
|
constructor(store) {
|
|
@@ -5947,13 +6380,13 @@ var SelectionOps = class {
|
|
|
5947
6380
|
if (!first || !last) return;
|
|
5948
6381
|
const c0 = center2(first.bounds);
|
|
5949
6382
|
const cN = center2(last.bounds);
|
|
5950
|
-
const
|
|
6383
|
+
const n2 = sorted.length;
|
|
5951
6384
|
this.deps.recorder.begin();
|
|
5952
6385
|
const moved = [];
|
|
5953
|
-
for (let i = 1; i <
|
|
6386
|
+
for (let i = 1; i < n2 - 1; i++) {
|
|
5954
6387
|
const item = sorted[i];
|
|
5955
6388
|
if (!item || !this.isMovable(item.el)) continue;
|
|
5956
|
-
const target = c0 + i * (cN - c0) / (
|
|
6389
|
+
const target = c0 + i * (cN - c0) / (n2 - 1);
|
|
5957
6390
|
const delta = target - center2(item.bounds);
|
|
5958
6391
|
if (delta === 0) continue;
|
|
5959
6392
|
const [dx, dy] = axis === "horizontal" ? [delta, 0] : [0, delta];
|
|
@@ -6291,7 +6724,10 @@ var Viewport = class {
|
|
|
6291
6724
|
this.getSelectTool()?.selectAtPoint(world, this.toolContext);
|
|
6292
6725
|
this.openContextMenu(screenPos);
|
|
6293
6726
|
},
|
|
6294
|
-
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
|
|
6295
6731
|
});
|
|
6296
6732
|
if (options.contextMenu !== false) {
|
|
6297
6733
|
this.contextMenu = new ContextMenu({
|
|
@@ -6431,6 +6867,7 @@ var Viewport = class {
|
|
|
6431
6867
|
gridController;
|
|
6432
6868
|
interactions;
|
|
6433
6869
|
contextMenu = null;
|
|
6870
|
+
htmlRenderers = /* @__PURE__ */ new Map();
|
|
6434
6871
|
get ctx() {
|
|
6435
6872
|
return this.canvasEl.getContext("2d");
|
|
6436
6873
|
}
|
|
@@ -6472,6 +6909,9 @@ var Viewport = class {
|
|
|
6472
6909
|
async exportImage(options) {
|
|
6473
6910
|
return exportImage(this.store, options, this.layerManager);
|
|
6474
6911
|
}
|
|
6912
|
+
async exportSVG(options) {
|
|
6913
|
+
return exportSvg(this.store, options, this.layerManager);
|
|
6914
|
+
}
|
|
6475
6915
|
loadState(state) {
|
|
6476
6916
|
this.inputHandler.flushPendingHistory();
|
|
6477
6917
|
this.historyRecorder.pause();
|
|
@@ -6485,19 +6925,24 @@ var Viewport = class {
|
|
|
6485
6925
|
this.layerManager.setActiveLayer(state.activeLayerId);
|
|
6486
6926
|
}
|
|
6487
6927
|
this.domNodeManager.reattachHtmlContent(this.store);
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
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
|
+
});
|
|
6501
6946
|
}
|
|
6502
6947
|
}
|
|
6503
6948
|
}
|
|
@@ -6543,12 +6988,14 @@ var Viewport = class {
|
|
|
6543
6988
|
this.requestRender();
|
|
6544
6989
|
return image.id;
|
|
6545
6990
|
}
|
|
6546
|
-
addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
|
|
6991
|
+
addHtmlElement(dom, position, size = { w: 200, h: 150 }, opts) {
|
|
6547
6992
|
const domId = dom.id || void 0;
|
|
6548
6993
|
const el = createHtmlElement({
|
|
6549
6994
|
position,
|
|
6550
6995
|
size,
|
|
6551
6996
|
domId,
|
|
6997
|
+
htmlType: opts?.htmlType,
|
|
6998
|
+
data: opts?.data,
|
|
6552
6999
|
layerId: this.layerManager.activeLayerId
|
|
6553
7000
|
});
|
|
6554
7001
|
this.domNodeManager.storeHtmlContent(el.id, dom);
|
|
@@ -6589,6 +7036,9 @@ var Viewport = class {
|
|
|
6589
7036
|
this.layerManager.removeLayer(id);
|
|
6590
7037
|
this.historyRecorder.commit();
|
|
6591
7038
|
}
|
|
7039
|
+
registerHtmlRenderer(htmlType, factory) {
|
|
7040
|
+
this.htmlRenderers.set(htmlType, factory);
|
|
7041
|
+
}
|
|
6592
7042
|
updateHtmlElement(id, newContent) {
|
|
6593
7043
|
const el = this.store.getById(id);
|
|
6594
7044
|
if (!el) throw new Error(`Element not found: ${id}`);
|
|
@@ -9061,7 +9511,7 @@ var TemplateTool = class {
|
|
|
9061
9511
|
};
|
|
9062
9512
|
|
|
9063
9513
|
// src/index.ts
|
|
9064
|
-
var VERSION = "0.
|
|
9514
|
+
var VERSION = "0.39.0";
|
|
9065
9515
|
export {
|
|
9066
9516
|
ArrowTool,
|
|
9067
9517
|
AutoSave,
|
|
@@ -9095,6 +9545,7 @@ export {
|
|
|
9095
9545
|
createText,
|
|
9096
9546
|
drawHexPath,
|
|
9097
9547
|
exportImage,
|
|
9548
|
+
exportSvg,
|
|
9098
9549
|
getActiveFormats,
|
|
9099
9550
|
getArrowBounds,
|
|
9100
9551
|
getArrowControlPoint,
|