@fieldnotes/core 0.8.11 → 0.10.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/README.md +63 -9
- package/dist/index.cjs +1582 -199
- package/dist/index.d.cts +244 -65
- package/dist/index.d.ts +244 -65
- package/dist/index.js +1561 -199
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -169,6 +169,120 @@ var Quadtree = class {
|
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
+
// src/elements/note-sanitizer.ts
|
|
173
|
+
var BOLD_TAGS = /* @__PURE__ */ new Set(["b", "strong"]);
|
|
174
|
+
var ITALIC_TAGS = /* @__PURE__ */ new Set(["i", "em"]);
|
|
175
|
+
var UNDERLINE_TAGS = /* @__PURE__ */ new Set(["u"]);
|
|
176
|
+
var STRIKE_TAGS = /* @__PURE__ */ new Set(["s", "strike", "del"]);
|
|
177
|
+
var BLOCK_TAGS = /* @__PURE__ */ new Set(["div"]);
|
|
178
|
+
function parseStyledRuns(html, baseFontSize) {
|
|
179
|
+
if (!html) return [];
|
|
180
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
181
|
+
const runs = [];
|
|
182
|
+
const baseStyle = {
|
|
183
|
+
bold: false,
|
|
184
|
+
italic: false,
|
|
185
|
+
underline: false,
|
|
186
|
+
strikethrough: false,
|
|
187
|
+
fontSize: baseFontSize
|
|
188
|
+
};
|
|
189
|
+
walkNodes(doc.body, baseStyle, runs);
|
|
190
|
+
return runs;
|
|
191
|
+
}
|
|
192
|
+
function walkNodes(node, style, runs) {
|
|
193
|
+
for (const child of Array.from(node.childNodes)) {
|
|
194
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
195
|
+
const text = child.textContent ?? "";
|
|
196
|
+
if (text) {
|
|
197
|
+
runs.push({ text, ...style });
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (child.nodeType !== Node.ELEMENT_NODE) continue;
|
|
202
|
+
const el = child;
|
|
203
|
+
const tag = el.tagName.toLowerCase();
|
|
204
|
+
if (tag === "br") {
|
|
205
|
+
runs.push({ text: "\n", ...style });
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (BLOCK_TAGS.has(tag) && runs.length > 0) {
|
|
209
|
+
const lastRun = runs[runs.length - 1];
|
|
210
|
+
if (lastRun && !lastRun.text.endsWith("\n")) {
|
|
211
|
+
runs.push({ text: "\n", ...style });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const childStyle = { ...style };
|
|
215
|
+
if (BOLD_TAGS.has(tag)) childStyle.bold = true;
|
|
216
|
+
if (ITALIC_TAGS.has(tag)) childStyle.italic = true;
|
|
217
|
+
if (UNDERLINE_TAGS.has(tag)) childStyle.underline = true;
|
|
218
|
+
if (STRIKE_TAGS.has(tag)) childStyle.strikethrough = true;
|
|
219
|
+
if (tag === "span") {
|
|
220
|
+
const fontSize = el.style.fontSize;
|
|
221
|
+
if (fontSize) {
|
|
222
|
+
childStyle.fontSize = parseInt(fontSize, 10) || style.fontSize;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
walkNodes(el, childStyle, runs);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
var ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
229
|
+
"b",
|
|
230
|
+
"strong",
|
|
231
|
+
"i",
|
|
232
|
+
"em",
|
|
233
|
+
"u",
|
|
234
|
+
"s",
|
|
235
|
+
"strike",
|
|
236
|
+
"del",
|
|
237
|
+
"span",
|
|
238
|
+
"br",
|
|
239
|
+
"div"
|
|
240
|
+
]);
|
|
241
|
+
function sanitizeNoteHtml(html) {
|
|
242
|
+
if (!html) return "";
|
|
243
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
244
|
+
sanitizeNode(doc.body);
|
|
245
|
+
return doc.body.innerHTML;
|
|
246
|
+
}
|
|
247
|
+
function sanitizeNode(node) {
|
|
248
|
+
const children = Array.from(node.childNodes);
|
|
249
|
+
for (const child of children) {
|
|
250
|
+
if (child.nodeType === Node.TEXT_NODE) continue;
|
|
251
|
+
if (child.nodeType !== Node.ELEMENT_NODE) {
|
|
252
|
+
child.remove();
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const el = child;
|
|
256
|
+
const tag = el.tagName.toLowerCase();
|
|
257
|
+
if (!ALLOWED_TAGS.has(tag)) {
|
|
258
|
+
const fragment = document.createDocumentFragment();
|
|
259
|
+
while (el.firstChild) {
|
|
260
|
+
fragment.appendChild(el.firstChild);
|
|
261
|
+
}
|
|
262
|
+
node.replaceChild(fragment, el);
|
|
263
|
+
sanitizeNode(node);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
sanitizeAttributes(el, tag);
|
|
267
|
+
sanitizeNode(el);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function sanitizeAttributes(el, tag) {
|
|
271
|
+
const attrs = Array.from(el.attributes);
|
|
272
|
+
for (const attr of attrs) {
|
|
273
|
+
if (tag === "span" && attr.name === "style") {
|
|
274
|
+
const fontSize = el.style.fontSize;
|
|
275
|
+
if (fontSize) {
|
|
276
|
+
el.setAttribute("style", `font-size: ${fontSize};`);
|
|
277
|
+
} else {
|
|
278
|
+
el.removeAttribute("style");
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
el.removeAttribute(attr.name);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
172
286
|
// src/core/state-serializer.ts
|
|
173
287
|
var CURRENT_VERSION = 2;
|
|
174
288
|
function exportState(elements, camera, layers = []) {
|
|
@@ -231,7 +345,17 @@ function validateState(data) {
|
|
|
231
345
|
];
|
|
232
346
|
}
|
|
233
347
|
}
|
|
234
|
-
var VALID_TYPES = /* @__PURE__ */ new Set([
|
|
348
|
+
var VALID_TYPES = /* @__PURE__ */ new Set([
|
|
349
|
+
"stroke",
|
|
350
|
+
"note",
|
|
351
|
+
"arrow",
|
|
352
|
+
"image",
|
|
353
|
+
"html",
|
|
354
|
+
"text",
|
|
355
|
+
"shape",
|
|
356
|
+
"grid",
|
|
357
|
+
"template"
|
|
358
|
+
]);
|
|
235
359
|
function validateElement(el) {
|
|
236
360
|
if (!el || typeof el !== "object") {
|
|
237
361
|
throw new Error("Invalid element: expected an object");
|
|
@@ -281,6 +405,9 @@ function migrateElement(obj) {
|
|
|
281
405
|
if (obj["type"] === "note" && typeof obj["textColor"] !== "string") {
|
|
282
406
|
obj["textColor"] = "#000000";
|
|
283
407
|
}
|
|
408
|
+
if (obj["type"] === "note" && typeof obj["text"] === "string") {
|
|
409
|
+
obj["text"] = sanitizeNoteHtml(obj["text"]);
|
|
410
|
+
}
|
|
284
411
|
}
|
|
285
412
|
|
|
286
413
|
// src/core/snap.ts
|
|
@@ -290,6 +417,30 @@ function snapPoint(point, gridSize) {
|
|
|
290
417
|
y: Math.round(point.y / gridSize) * gridSize || 0
|
|
291
418
|
};
|
|
292
419
|
}
|
|
420
|
+
function snapToHexCenter(point, cellSize, orientation) {
|
|
421
|
+
if (orientation === "pointy") {
|
|
422
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
423
|
+
const rowH = 1.5 * cellSize;
|
|
424
|
+
const row = Math.round(point.y / rowH);
|
|
425
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
426
|
+
const col = Math.round((point.x - offsetX) / hexW);
|
|
427
|
+
return { x: col * hexW + offsetX || 0, y: row * rowH || 0 };
|
|
428
|
+
} else {
|
|
429
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
430
|
+
const colW = 1.5 * cellSize;
|
|
431
|
+
const col = Math.round(point.x / colW);
|
|
432
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
433
|
+
const row = Math.round((point.y - offsetY) / hexH);
|
|
434
|
+
return { x: col * colW || 0, y: row * hexH + offsetY || 0 };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function smartSnap(point, ctx) {
|
|
438
|
+
if (!ctx.snapToGrid || !ctx.gridSize) return point;
|
|
439
|
+
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
440
|
+
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
441
|
+
}
|
|
442
|
+
return snapPoint(point, ctx.gridSize);
|
|
443
|
+
}
|
|
293
444
|
|
|
294
445
|
// src/core/auto-save.ts
|
|
295
446
|
var DEFAULT_KEY = "fieldnotes-autosave";
|
|
@@ -942,6 +1093,9 @@ function getElementBounds(element) {
|
|
|
942
1093
|
if (element.type === "arrow") {
|
|
943
1094
|
return getArrowBoundsAnalytical(element.from, element.to, element.bend);
|
|
944
1095
|
}
|
|
1096
|
+
if (element.type === "template") {
|
|
1097
|
+
return getTemplateBounds(element);
|
|
1098
|
+
}
|
|
945
1099
|
return null;
|
|
946
1100
|
}
|
|
947
1101
|
function getArrowBoundsAnalytical(from, to, bend) {
|
|
@@ -982,6 +1136,62 @@ function getArrowBoundsAnalytical(from, to, bend) {
|
|
|
982
1136
|
}
|
|
983
1137
|
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
984
1138
|
}
|
|
1139
|
+
function getTemplateBounds(el) {
|
|
1140
|
+
const { x: cx, y: cy } = el.position;
|
|
1141
|
+
const r = el.radius;
|
|
1142
|
+
switch (el.templateShape) {
|
|
1143
|
+
case "circle":
|
|
1144
|
+
return { x: cx - r, y: cy - r, w: 2 * r, h: 2 * r };
|
|
1145
|
+
case "square":
|
|
1146
|
+
return { x: cx - r / 2, y: cy - r / 2, w: r, h: r };
|
|
1147
|
+
case "cone": {
|
|
1148
|
+
const halfAngle = Math.atan(0.5);
|
|
1149
|
+
const tipX = cx;
|
|
1150
|
+
const tipY = cy;
|
|
1151
|
+
const leftX = cx + r * Math.cos(el.angle - halfAngle);
|
|
1152
|
+
const leftY = cy + r * Math.sin(el.angle - halfAngle);
|
|
1153
|
+
const rightX = cx + r * Math.cos(el.angle + halfAngle);
|
|
1154
|
+
const rightY = cy + r * Math.sin(el.angle + halfAngle);
|
|
1155
|
+
const farX = cx + r * Math.cos(el.angle);
|
|
1156
|
+
const farY = cy + r * Math.sin(el.angle);
|
|
1157
|
+
const xs = [tipX, leftX, rightX, farX];
|
|
1158
|
+
const ys = [tipY, leftY, rightY, farY];
|
|
1159
|
+
let minX = Infinity;
|
|
1160
|
+
let minY = Infinity;
|
|
1161
|
+
let maxX = -Infinity;
|
|
1162
|
+
let maxY = -Infinity;
|
|
1163
|
+
for (let i = 0; i < xs.length; i++) {
|
|
1164
|
+
const px = xs[i];
|
|
1165
|
+
const py = ys[i];
|
|
1166
|
+
if (px !== void 0 && px < minX) minX = px;
|
|
1167
|
+
if (px !== void 0 && px > maxX) maxX = px;
|
|
1168
|
+
if (py !== void 0 && py < minY) minY = py;
|
|
1169
|
+
if (py !== void 0 && py > maxY) maxY = py;
|
|
1170
|
+
}
|
|
1171
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1172
|
+
}
|
|
1173
|
+
case "line": {
|
|
1174
|
+
const halfW = r / 12;
|
|
1175
|
+
const cos = Math.cos(el.angle);
|
|
1176
|
+
const sin = Math.sin(el.angle);
|
|
1177
|
+
const perpX = -sin * halfW;
|
|
1178
|
+
const perpY = cos * halfW;
|
|
1179
|
+
const x0 = cx + perpX;
|
|
1180
|
+
const y0 = cy + perpY;
|
|
1181
|
+
const x1 = cx + r * cos + perpX;
|
|
1182
|
+
const y1 = cy + r * sin + perpY;
|
|
1183
|
+
const x2 = cx + r * cos - perpX;
|
|
1184
|
+
const y2 = cy + r * sin - perpY;
|
|
1185
|
+
const x3 = cx - perpX;
|
|
1186
|
+
const y3 = cy - perpY;
|
|
1187
|
+
const minX = Math.min(x0, x1, x2, x3);
|
|
1188
|
+
const minY = Math.min(y0, y1, y2, y3);
|
|
1189
|
+
const maxX = Math.max(x0, x1, x2, x3);
|
|
1190
|
+
const maxY = Math.max(y0, y1, y2, y3);
|
|
1191
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
985
1195
|
function boundsIntersect(a, b) {
|
|
986
1196
|
return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
|
|
987
1197
|
}
|
|
@@ -1502,6 +1712,190 @@ function renderHexGridTiled(ctx, bounds, cellSize, tile) {
|
|
|
1502
1712
|
}
|
|
1503
1713
|
}
|
|
1504
1714
|
|
|
1715
|
+
// src/elements/hex-fill.ts
|
|
1716
|
+
function offsetToCube(col, row, orientation) {
|
|
1717
|
+
if (orientation === "pointy") {
|
|
1718
|
+
return { q: col - (row - (row & 1)) / 2, r: row };
|
|
1719
|
+
}
|
|
1720
|
+
return { q: col, r: row - (col - (col & 1)) / 2 };
|
|
1721
|
+
}
|
|
1722
|
+
function cubeToOffset(q, r, orientation) {
|
|
1723
|
+
if (orientation === "pointy") {
|
|
1724
|
+
return { col: q + (r - (r & 1)) / 2, row: r };
|
|
1725
|
+
}
|
|
1726
|
+
return { col: q, row: r + (q - (q & 1)) / 2 };
|
|
1727
|
+
}
|
|
1728
|
+
function offsetToPixel(col, row, cellSize, orientation) {
|
|
1729
|
+
if (orientation === "pointy") {
|
|
1730
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
1731
|
+
const rowH = 1.5 * cellSize;
|
|
1732
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1733
|
+
return { x: col * hexW + offsetX, y: row * rowH };
|
|
1734
|
+
}
|
|
1735
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
1736
|
+
const colW = 1.5 * cellSize;
|
|
1737
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1738
|
+
return { x: col * colW, y: row * hexH + offsetY };
|
|
1739
|
+
}
|
|
1740
|
+
function pixelToOffset(x, y, cellSize, orientation) {
|
|
1741
|
+
if (orientation === "pointy") {
|
|
1742
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
1743
|
+
const rowH = 1.5 * cellSize;
|
|
1744
|
+
const row = Math.round(y / rowH);
|
|
1745
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1746
|
+
return { col: Math.round((x - offsetX) / hexW), row };
|
|
1747
|
+
}
|
|
1748
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
1749
|
+
const colW = 1.5 * cellSize;
|
|
1750
|
+
const col = Math.round(x / colW);
|
|
1751
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1752
|
+
return { col, row: Math.round((y - offsetY) / hexH) };
|
|
1753
|
+
}
|
|
1754
|
+
function enumerateHexRing(centerQ, centerR, n, orientation, cellSize) {
|
|
1755
|
+
const cells = [];
|
|
1756
|
+
for (let dq = -n; dq <= n; dq++) {
|
|
1757
|
+
const rMin = Math.max(-n, -dq - n);
|
|
1758
|
+
const rMax = Math.min(n, -dq + n);
|
|
1759
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
1760
|
+
const absQ = centerQ + dq;
|
|
1761
|
+
const absR = centerR + dr;
|
|
1762
|
+
const off = cubeToOffset(absQ, absR, orientation);
|
|
1763
|
+
cells.push(offsetToPixel(off.col, off.row, cellSize, orientation));
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
return cells;
|
|
1767
|
+
}
|
|
1768
|
+
function getHexDistance(a, b, cellSize, orientation) {
|
|
1769
|
+
const offA = pixelToOffset(a.x, a.y, cellSize, orientation);
|
|
1770
|
+
const offB = pixelToOffset(b.x, b.y, cellSize, orientation);
|
|
1771
|
+
const cubeA = offsetToCube(offA.col, offA.row, orientation);
|
|
1772
|
+
const cubeB = offsetToCube(offB.col, offB.row, orientation);
|
|
1773
|
+
const dq = cubeA.q - cubeB.q;
|
|
1774
|
+
const dr = cubeA.r - cubeB.r;
|
|
1775
|
+
const ds = -dq - dr;
|
|
1776
|
+
return Math.max(Math.abs(dq), Math.abs(dr), Math.abs(ds));
|
|
1777
|
+
}
|
|
1778
|
+
function getHexCellsInRadius(center, radiusCells, cellSize, orientation) {
|
|
1779
|
+
const n = Math.round(radiusCells);
|
|
1780
|
+
const off = pixelToOffset(center.x, center.y, cellSize, orientation);
|
|
1781
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
1782
|
+
if (n <= 0) {
|
|
1783
|
+
return [offsetToPixel(off.col, off.row, cellSize, orientation)];
|
|
1784
|
+
}
|
|
1785
|
+
return enumerateHexRing(cube.q, cube.r, n, orientation, cellSize);
|
|
1786
|
+
}
|
|
1787
|
+
function getHexCellsInCone(center, angle, radiusCells, cellSize, orientation) {
|
|
1788
|
+
const n = Math.round(radiusCells);
|
|
1789
|
+
const off = pixelToOffset(center.x, center.y, cellSize, orientation);
|
|
1790
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
1791
|
+
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
1792
|
+
if (n <= 0) return [centerPixel];
|
|
1793
|
+
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
1794
|
+
const step = Math.PI / 3;
|
|
1795
|
+
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
1796
|
+
const halfAngle = Math.PI / 6 + 1e-6;
|
|
1797
|
+
const cells = [centerPixel];
|
|
1798
|
+
for (let dq = -n; dq <= n; dq++) {
|
|
1799
|
+
const rMin = Math.max(-n, -dq - n);
|
|
1800
|
+
const rMax = Math.min(n, -dq + n);
|
|
1801
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
1802
|
+
if (dq === 0 && dr === 0) continue;
|
|
1803
|
+
const absQ = cube.q + dq;
|
|
1804
|
+
const absR = cube.r + dr;
|
|
1805
|
+
const pixel = offsetToPixel(
|
|
1806
|
+
cubeToOffset(absQ, absR, orientation).col,
|
|
1807
|
+
cubeToOffset(absQ, absR, orientation).row,
|
|
1808
|
+
cellSize,
|
|
1809
|
+
orientation
|
|
1810
|
+
);
|
|
1811
|
+
const dx = pixel.x - centerPixel.x;
|
|
1812
|
+
const dy = pixel.y - centerPixel.y;
|
|
1813
|
+
let diff = Math.atan2(dy, dx) - snappedAngle;
|
|
1814
|
+
if (diff > Math.PI) diff -= 2 * Math.PI;
|
|
1815
|
+
if (diff < -Math.PI) diff += 2 * Math.PI;
|
|
1816
|
+
if (Math.abs(diff) <= halfAngle) {
|
|
1817
|
+
cells.push(pixel);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
return cells;
|
|
1822
|
+
}
|
|
1823
|
+
function getHexCellsInLine(center, angle, radiusCells, cellSize, orientation) {
|
|
1824
|
+
const n = Math.round(radiusCells);
|
|
1825
|
+
const off = pixelToOffset(center.x, center.y, cellSize, orientation);
|
|
1826
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
1827
|
+
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
1828
|
+
if (n <= 0) return [centerPixel];
|
|
1829
|
+
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
1830
|
+
const step = Math.PI / 3;
|
|
1831
|
+
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
1832
|
+
const cos = Math.cos(snappedAngle);
|
|
1833
|
+
const sin = Math.sin(snappedAngle);
|
|
1834
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
1835
|
+
const lineLength = n * snapUnit;
|
|
1836
|
+
const halfWidth = snapUnit * 0.5 + 1e-6;
|
|
1837
|
+
const cells = [];
|
|
1838
|
+
for (let dq = -n; dq <= n; dq++) {
|
|
1839
|
+
const rMin = Math.max(-n, -dq - n);
|
|
1840
|
+
const rMax = Math.min(n, -dq + n);
|
|
1841
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
1842
|
+
const absQ = cube.q + dq;
|
|
1843
|
+
const absR = cube.r + dr;
|
|
1844
|
+
const pixel = offsetToPixel(
|
|
1845
|
+
cubeToOffset(absQ, absR, orientation).col,
|
|
1846
|
+
cubeToOffset(absQ, absR, orientation).row,
|
|
1847
|
+
cellSize,
|
|
1848
|
+
orientation
|
|
1849
|
+
);
|
|
1850
|
+
const dx = pixel.x - centerPixel.x;
|
|
1851
|
+
const dy = pixel.y - centerPixel.y;
|
|
1852
|
+
const along = dx * cos + dy * sin;
|
|
1853
|
+
const perp = Math.abs(-dx * sin + dy * cos);
|
|
1854
|
+
if (along >= -snapUnit * 0.1 && along <= lineLength + snapUnit * 0.1 && perp <= halfWidth) {
|
|
1855
|
+
cells.push(pixel);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
return cells;
|
|
1860
|
+
}
|
|
1861
|
+
function getHexCellsInSquare(center, radiusCells, cellSize, orientation) {
|
|
1862
|
+
const n = Math.round(radiusCells);
|
|
1863
|
+
const off = pixelToOffset(center.x, center.y, cellSize, orientation);
|
|
1864
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
1865
|
+
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
1866
|
+
if (n <= 0) return [centerPixel];
|
|
1867
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
1868
|
+
const halfSide = n * snapUnit / 2;
|
|
1869
|
+
const cells = [];
|
|
1870
|
+
for (let dq = -n; dq <= n; dq++) {
|
|
1871
|
+
const rMin = Math.max(-n, -dq - n);
|
|
1872
|
+
const rMax = Math.min(n, -dq + n);
|
|
1873
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
1874
|
+
const absQ = cube.q + dq;
|
|
1875
|
+
const absR = cube.r + dr;
|
|
1876
|
+
const pixel = offsetToPixel(
|
|
1877
|
+
cubeToOffset(absQ, absR, orientation).col,
|
|
1878
|
+
cubeToOffset(absQ, absR, orientation).row,
|
|
1879
|
+
cellSize,
|
|
1880
|
+
orientation
|
|
1881
|
+
);
|
|
1882
|
+
if (Math.abs(pixel.x - centerPixel.x) <= halfSide && Math.abs(pixel.y - centerPixel.y) <= halfSide) {
|
|
1883
|
+
cells.push(pixel);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
return cells;
|
|
1888
|
+
}
|
|
1889
|
+
function drawHexPath(ctx, cx, cy, cellSize, orientation) {
|
|
1890
|
+
const angleOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
1891
|
+
ctx.moveTo(cx + cellSize * Math.cos(angleOffset), cy + cellSize * Math.sin(angleOffset));
|
|
1892
|
+
for (let i = 1; i < 6; i++) {
|
|
1893
|
+
const a = angleOffset + Math.PI / 3 * i;
|
|
1894
|
+
ctx.lineTo(cx + cellSize * Math.cos(a), cy + cellSize * Math.sin(a));
|
|
1895
|
+
}
|
|
1896
|
+
ctx.closePath();
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1505
1899
|
// src/elements/element-renderer.ts
|
|
1506
1900
|
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
|
|
1507
1901
|
var ARROWHEAD_LENGTH = 12;
|
|
@@ -1546,6 +1940,9 @@ var ElementRenderer = class {
|
|
|
1546
1940
|
case "grid":
|
|
1547
1941
|
this.renderGrid(ctx, element);
|
|
1548
1942
|
break;
|
|
1943
|
+
case "template":
|
|
1944
|
+
this.renderTemplate(ctx, element);
|
|
1945
|
+
break;
|
|
1549
1946
|
}
|
|
1550
1947
|
}
|
|
1551
1948
|
renderStroke(ctx, stroke) {
|
|
@@ -1733,6 +2130,147 @@ var ElementRenderer = class {
|
|
|
1733
2130
|
);
|
|
1734
2131
|
}
|
|
1735
2132
|
}
|
|
2133
|
+
renderTemplate(ctx, template) {
|
|
2134
|
+
const grid = this.store?.getElementsByType("grid")[0];
|
|
2135
|
+
if (grid && grid.gridType === "hex") {
|
|
2136
|
+
this.renderHexTemplate(ctx, template, grid.cellSize, grid.hexOrientation);
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
this.renderGeometricTemplate(ctx, template);
|
|
2140
|
+
}
|
|
2141
|
+
renderGeometricTemplate(ctx, template) {
|
|
2142
|
+
const { x: cx, y: cy } = template.position;
|
|
2143
|
+
const r = template.radius;
|
|
2144
|
+
ctx.save();
|
|
2145
|
+
ctx.globalAlpha = template.opacity;
|
|
2146
|
+
ctx.fillStyle = template.fillColor;
|
|
2147
|
+
ctx.strokeStyle = template.strokeColor;
|
|
2148
|
+
ctx.lineWidth = template.strokeWidth;
|
|
2149
|
+
switch (template.templateShape) {
|
|
2150
|
+
case "circle":
|
|
2151
|
+
ctx.beginPath();
|
|
2152
|
+
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
2153
|
+
ctx.fill();
|
|
2154
|
+
ctx.stroke();
|
|
2155
|
+
if (template.radiusFeet != null && template.radiusFeet > 0) {
|
|
2156
|
+
this.renderRadiusMarker(ctx, cx, cy, r, template.radiusFeet);
|
|
2157
|
+
}
|
|
2158
|
+
break;
|
|
2159
|
+
case "square":
|
|
2160
|
+
ctx.fillRect(cx - r / 2, cy - r / 2, r, r);
|
|
2161
|
+
ctx.strokeRect(cx - r / 2, cy - r / 2, r, r);
|
|
2162
|
+
break;
|
|
2163
|
+
case "cone": {
|
|
2164
|
+
const halfAngle = Math.atan(0.5);
|
|
2165
|
+
ctx.beginPath();
|
|
2166
|
+
ctx.moveTo(cx, cy);
|
|
2167
|
+
ctx.arc(cx, cy, r, template.angle - halfAngle, template.angle + halfAngle);
|
|
2168
|
+
ctx.closePath();
|
|
2169
|
+
ctx.fill();
|
|
2170
|
+
ctx.stroke();
|
|
2171
|
+
break;
|
|
2172
|
+
}
|
|
2173
|
+
case "line": {
|
|
2174
|
+
const halfW = r / 12;
|
|
2175
|
+
const cos = Math.cos(template.angle);
|
|
2176
|
+
const sin = Math.sin(template.angle);
|
|
2177
|
+
const perpX = -sin * halfW;
|
|
2178
|
+
const perpY = cos * halfW;
|
|
2179
|
+
ctx.beginPath();
|
|
2180
|
+
ctx.moveTo(cx + perpX, cy + perpY);
|
|
2181
|
+
ctx.lineTo(cx + r * cos + perpX, cy + r * sin + perpY);
|
|
2182
|
+
ctx.lineTo(cx + r * cos - perpX, cy + r * sin - perpY);
|
|
2183
|
+
ctx.lineTo(cx - perpX, cy - perpY);
|
|
2184
|
+
ctx.closePath();
|
|
2185
|
+
ctx.fill();
|
|
2186
|
+
ctx.stroke();
|
|
2187
|
+
break;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
ctx.restore();
|
|
2191
|
+
}
|
|
2192
|
+
renderHexTemplate(ctx, template, cellSize, orientation) {
|
|
2193
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
2194
|
+
const radiusCells = template.radius / snapUnit;
|
|
2195
|
+
const center = template.position;
|
|
2196
|
+
let cells;
|
|
2197
|
+
switch (template.templateShape) {
|
|
2198
|
+
case "circle":
|
|
2199
|
+
cells = getHexCellsInRadius(center, radiusCells, cellSize, orientation);
|
|
2200
|
+
break;
|
|
2201
|
+
case "cone":
|
|
2202
|
+
cells = getHexCellsInCone(center, template.angle, radiusCells, cellSize, orientation);
|
|
2203
|
+
break;
|
|
2204
|
+
case "line":
|
|
2205
|
+
cells = getHexCellsInLine(center, template.angle, radiusCells, cellSize, orientation);
|
|
2206
|
+
break;
|
|
2207
|
+
case "square":
|
|
2208
|
+
cells = getHexCellsInSquare(center, radiusCells, cellSize, orientation);
|
|
2209
|
+
break;
|
|
2210
|
+
}
|
|
2211
|
+
ctx.save();
|
|
2212
|
+
ctx.globalAlpha = template.opacity;
|
|
2213
|
+
ctx.beginPath();
|
|
2214
|
+
for (const cell of cells) {
|
|
2215
|
+
drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
|
|
2216
|
+
}
|
|
2217
|
+
ctx.fillStyle = template.fillColor;
|
|
2218
|
+
ctx.fill();
|
|
2219
|
+
ctx.beginPath();
|
|
2220
|
+
for (const cell of cells) {
|
|
2221
|
+
drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
|
|
2222
|
+
}
|
|
2223
|
+
ctx.strokeStyle = template.strokeColor;
|
|
2224
|
+
ctx.lineWidth = template.strokeWidth;
|
|
2225
|
+
ctx.stroke();
|
|
2226
|
+
{
|
|
2227
|
+
ctx.globalAlpha = Math.min(template.opacity + 0.1, 1);
|
|
2228
|
+
ctx.beginPath();
|
|
2229
|
+
drawHexPath(ctx, center.x, center.y, cellSize, orientation);
|
|
2230
|
+
ctx.fillStyle = template.strokeColor;
|
|
2231
|
+
ctx.fill();
|
|
2232
|
+
ctx.strokeStyle = template.strokeColor;
|
|
2233
|
+
ctx.lineWidth = template.strokeWidth;
|
|
2234
|
+
ctx.stroke();
|
|
2235
|
+
}
|
|
2236
|
+
if (template.templateShape === "circle" && template.radiusFeet != null && template.radiusFeet > 0) {
|
|
2237
|
+
const r = template.radius;
|
|
2238
|
+
this.renderRadiusMarker(ctx, center.x, center.y, r, template.radiusFeet);
|
|
2239
|
+
}
|
|
2240
|
+
ctx.restore();
|
|
2241
|
+
}
|
|
2242
|
+
renderRadiusMarker(ctx, cx, cy, r, feet) {
|
|
2243
|
+
const markerColor = ctx.strokeStyle;
|
|
2244
|
+
ctx.save();
|
|
2245
|
+
ctx.globalAlpha = 1;
|
|
2246
|
+
ctx.beginPath();
|
|
2247
|
+
ctx.setLineDash([4, 4]);
|
|
2248
|
+
ctx.strokeStyle = markerColor;
|
|
2249
|
+
ctx.lineWidth = 1.5;
|
|
2250
|
+
ctx.moveTo(cx, cy);
|
|
2251
|
+
ctx.lineTo(cx + r, cy);
|
|
2252
|
+
ctx.stroke();
|
|
2253
|
+
ctx.setLineDash([]);
|
|
2254
|
+
const label = `${Math.round(feet)} ft`;
|
|
2255
|
+
const fontSize = Math.max(10, Math.min(14, r * 0.15));
|
|
2256
|
+
ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
|
|
2257
|
+
ctx.textAlign = "center";
|
|
2258
|
+
ctx.textBaseline = "bottom";
|
|
2259
|
+
const textX = cx + r / 2;
|
|
2260
|
+
const textY = cy - 4;
|
|
2261
|
+
const metrics = ctx.measureText(label);
|
|
2262
|
+
const padX = 4;
|
|
2263
|
+
const padY = 2;
|
|
2264
|
+
const textW = metrics.width + padX * 2;
|
|
2265
|
+
const textH = fontSize + padY * 2;
|
|
2266
|
+
ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
|
|
2267
|
+
ctx.beginPath();
|
|
2268
|
+
ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
|
|
2269
|
+
ctx.fill();
|
|
2270
|
+
ctx.fillStyle = markerColor;
|
|
2271
|
+
ctx.fillText(label, textX, textY - padY);
|
|
2272
|
+
ctx.restore();
|
|
2273
|
+
}
|
|
1736
2274
|
renderImage(ctx, image) {
|
|
1737
2275
|
const img = this.getImage(image.src);
|
|
1738
2276
|
if (!img) return;
|
|
@@ -1779,7 +2317,359 @@ var ElementRenderer = class {
|
|
|
1779
2317
|
}
|
|
1780
2318
|
};
|
|
1781
2319
|
|
|
2320
|
+
// src/elements/create-id.ts
|
|
2321
|
+
var counter = 0;
|
|
2322
|
+
function createId(prefix) {
|
|
2323
|
+
return `${prefix}_${Date.now().toString(36)}_${(counter++).toString(36)}`;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
// src/elements/element-factory.ts
|
|
2327
|
+
var DEFAULT_NOTE_FONT_SIZE = 18;
|
|
2328
|
+
function createStroke(input) {
|
|
2329
|
+
return {
|
|
2330
|
+
id: createId("stroke"),
|
|
2331
|
+
type: "stroke",
|
|
2332
|
+
position: input.position ?? { x: 0, y: 0 },
|
|
2333
|
+
zIndex: input.zIndex ?? 0,
|
|
2334
|
+
locked: input.locked ?? false,
|
|
2335
|
+
layerId: input.layerId ?? "",
|
|
2336
|
+
points: input.points,
|
|
2337
|
+
color: input.color ?? "#000000",
|
|
2338
|
+
width: input.width ?? 2,
|
|
2339
|
+
opacity: input.opacity ?? 1
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
function createNote(input) {
|
|
2343
|
+
return {
|
|
2344
|
+
id: createId("note"),
|
|
2345
|
+
type: "note",
|
|
2346
|
+
position: input.position,
|
|
2347
|
+
zIndex: input.zIndex ?? 0,
|
|
2348
|
+
locked: input.locked ?? false,
|
|
2349
|
+
layerId: input.layerId ?? "",
|
|
2350
|
+
size: input.size ?? { w: 200, h: 100 },
|
|
2351
|
+
text: input.text ?? "",
|
|
2352
|
+
backgroundColor: input.backgroundColor ?? "#ffeb3b",
|
|
2353
|
+
textColor: input.textColor ?? "#000000",
|
|
2354
|
+
fontSize: input.fontSize ?? DEFAULT_NOTE_FONT_SIZE
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
function createArrow(input) {
|
|
2358
|
+
const bend = input.bend ?? 0;
|
|
2359
|
+
const result = {
|
|
2360
|
+
id: createId("arrow"),
|
|
2361
|
+
type: "arrow",
|
|
2362
|
+
position: input.position ?? { x: 0, y: 0 },
|
|
2363
|
+
zIndex: input.zIndex ?? 0,
|
|
2364
|
+
locked: input.locked ?? false,
|
|
2365
|
+
layerId: input.layerId ?? "",
|
|
2366
|
+
from: input.from,
|
|
2367
|
+
to: input.to,
|
|
2368
|
+
bend,
|
|
2369
|
+
color: input.color ?? "#000000",
|
|
2370
|
+
width: input.width ?? 2,
|
|
2371
|
+
cachedControlPoint: getArrowControlPoint(input.from, input.to, bend)
|
|
2372
|
+
};
|
|
2373
|
+
if (input.fromBinding) result.fromBinding = input.fromBinding;
|
|
2374
|
+
if (input.toBinding) result.toBinding = input.toBinding;
|
|
2375
|
+
return result;
|
|
2376
|
+
}
|
|
2377
|
+
function createImage(input) {
|
|
2378
|
+
return {
|
|
2379
|
+
id: createId("image"),
|
|
2380
|
+
type: "image",
|
|
2381
|
+
position: input.position,
|
|
2382
|
+
zIndex: input.zIndex ?? 0,
|
|
2383
|
+
locked: input.locked ?? false,
|
|
2384
|
+
layerId: input.layerId ?? "",
|
|
2385
|
+
size: input.size,
|
|
2386
|
+
src: input.src
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
function createHtmlElement(input) {
|
|
2390
|
+
const el = {
|
|
2391
|
+
id: createId("html"),
|
|
2392
|
+
type: "html",
|
|
2393
|
+
position: input.position,
|
|
2394
|
+
zIndex: input.zIndex ?? 0,
|
|
2395
|
+
locked: input.locked ?? false,
|
|
2396
|
+
layerId: input.layerId ?? "",
|
|
2397
|
+
size: input.size
|
|
2398
|
+
};
|
|
2399
|
+
if (input.domId) el.domId = input.domId;
|
|
2400
|
+
return el;
|
|
2401
|
+
}
|
|
2402
|
+
function createShape(input) {
|
|
2403
|
+
return {
|
|
2404
|
+
id: createId("shape"),
|
|
2405
|
+
type: "shape",
|
|
2406
|
+
position: input.position,
|
|
2407
|
+
zIndex: input.zIndex ?? 0,
|
|
2408
|
+
locked: input.locked ?? false,
|
|
2409
|
+
layerId: input.layerId ?? "",
|
|
2410
|
+
shape: input.shape ?? "rectangle",
|
|
2411
|
+
size: input.size,
|
|
2412
|
+
strokeColor: input.strokeColor ?? "#000000",
|
|
2413
|
+
strokeWidth: input.strokeWidth ?? 2,
|
|
2414
|
+
fillColor: input.fillColor ?? "none"
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
function createGrid(input) {
|
|
2418
|
+
return {
|
|
2419
|
+
id: createId("grid"),
|
|
2420
|
+
type: "grid",
|
|
2421
|
+
position: input.position ?? { x: 0, y: 0 },
|
|
2422
|
+
zIndex: input.zIndex ?? 0,
|
|
2423
|
+
locked: input.locked ?? false,
|
|
2424
|
+
layerId: input.layerId ?? "",
|
|
2425
|
+
gridType: input.gridType ?? "square",
|
|
2426
|
+
hexOrientation: input.hexOrientation ?? "pointy",
|
|
2427
|
+
cellSize: input.cellSize ?? 40,
|
|
2428
|
+
strokeColor: input.strokeColor ?? "#000000",
|
|
2429
|
+
strokeWidth: input.strokeWidth ?? 1,
|
|
2430
|
+
opacity: input.opacity ?? 1
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
function createText(input) {
|
|
2434
|
+
return {
|
|
2435
|
+
id: createId("text"),
|
|
2436
|
+
type: "text",
|
|
2437
|
+
position: input.position,
|
|
2438
|
+
zIndex: input.zIndex ?? 0,
|
|
2439
|
+
locked: input.locked ?? false,
|
|
2440
|
+
layerId: input.layerId ?? "",
|
|
2441
|
+
size: input.size ?? { w: 200, h: 28 },
|
|
2442
|
+
text: input.text ?? "",
|
|
2443
|
+
fontSize: input.fontSize ?? 16,
|
|
2444
|
+
color: input.color ?? "#1a1a1a",
|
|
2445
|
+
textAlign: input.textAlign ?? "left"
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
function createTemplate(input) {
|
|
2449
|
+
return {
|
|
2450
|
+
id: createId("template"),
|
|
2451
|
+
type: "template",
|
|
2452
|
+
position: input.position,
|
|
2453
|
+
zIndex: input.zIndex ?? 0,
|
|
2454
|
+
locked: input.locked ?? false,
|
|
2455
|
+
layerId: input.layerId ?? "",
|
|
2456
|
+
templateShape: input.templateShape,
|
|
2457
|
+
radius: input.radius,
|
|
2458
|
+
angle: input.angle ?? 0,
|
|
2459
|
+
fillColor: input.fillColor ?? "rgba(255, 87, 34, 0.2)",
|
|
2460
|
+
strokeColor: input.strokeColor ?? "#FF5722",
|
|
2461
|
+
strokeWidth: input.strokeWidth ?? 2,
|
|
2462
|
+
opacity: input.opacity ?? 0.6,
|
|
2463
|
+
feetPerCell: input.feetPerCell,
|
|
2464
|
+
radiusFeet: input.radiusFeet
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
// src/elements/note-formatting.ts
|
|
2469
|
+
function toggleBold() {
|
|
2470
|
+
document.execCommand("bold");
|
|
2471
|
+
}
|
|
2472
|
+
function toggleItalic() {
|
|
2473
|
+
document.execCommand("italic");
|
|
2474
|
+
}
|
|
2475
|
+
function toggleUnderline() {
|
|
2476
|
+
document.execCommand("underline");
|
|
2477
|
+
}
|
|
2478
|
+
function toggleStrikethrough() {
|
|
2479
|
+
document.execCommand("strikeThrough");
|
|
2480
|
+
}
|
|
2481
|
+
function setFontSize(size) {
|
|
2482
|
+
const sel = window.getSelection();
|
|
2483
|
+
if (!sel || sel.rangeCount === 0) return;
|
|
2484
|
+
const range = sel.getRangeAt(0);
|
|
2485
|
+
if (range.collapsed) return;
|
|
2486
|
+
const span = document.createElement("span");
|
|
2487
|
+
span.style.fontSize = `${size}px`;
|
|
2488
|
+
try {
|
|
2489
|
+
range.surroundContents(span);
|
|
2490
|
+
} catch {
|
|
2491
|
+
span.appendChild(range.extractContents());
|
|
2492
|
+
range.insertNode(span);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
function getActiveFormats() {
|
|
2496
|
+
const query = (cmd) => {
|
|
2497
|
+
try {
|
|
2498
|
+
return document.queryCommandState(cmd);
|
|
2499
|
+
} catch {
|
|
2500
|
+
return false;
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
return {
|
|
2504
|
+
bold: query("bold"),
|
|
2505
|
+
italic: query("italic"),
|
|
2506
|
+
underline: query("underline"),
|
|
2507
|
+
strikethrough: query("strikeThrough")
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// src/elements/note-toolbar.ts
|
|
2512
|
+
var TOOLBAR_HEIGHT = 32;
|
|
2513
|
+
var TOOLBAR_GAP = 4;
|
|
2514
|
+
var FORMAT_BUTTONS = [
|
|
2515
|
+
{ label: "B", format: "bold", command: "bold" },
|
|
2516
|
+
{ label: "I", format: "italic", command: "italic" },
|
|
2517
|
+
{ label: "U", format: "underline", command: "underline" },
|
|
2518
|
+
{ label: "S", format: "strikethrough", command: "strikeThrough" }
|
|
2519
|
+
];
|
|
2520
|
+
var DEFAULT_FONT_SIZE_PRESETS = [
|
|
2521
|
+
{ label: "Small", size: 14 },
|
|
2522
|
+
{ label: "Normal", size: 18 },
|
|
2523
|
+
{ label: "Large", size: 24 },
|
|
2524
|
+
{ label: "Heading", size: 32 }
|
|
2525
|
+
];
|
|
2526
|
+
var NoteToolbar = class {
|
|
2527
|
+
el = null;
|
|
2528
|
+
anchor = null;
|
|
2529
|
+
selectionListener = null;
|
|
2530
|
+
fontSizePresets;
|
|
2531
|
+
constructor(fontSizePresets) {
|
|
2532
|
+
this.fontSizePresets = fontSizePresets ?? DEFAULT_FONT_SIZE_PRESETS;
|
|
2533
|
+
}
|
|
2534
|
+
show(anchor) {
|
|
2535
|
+
this.hide();
|
|
2536
|
+
this.anchor = anchor;
|
|
2537
|
+
this.el = this.createToolbarElement();
|
|
2538
|
+
document.body.appendChild(this.el);
|
|
2539
|
+
this.positionToolbar(anchor);
|
|
2540
|
+
this.selectionListener = () => this.updateActiveStates();
|
|
2541
|
+
document.addEventListener("selectionchange", this.selectionListener);
|
|
2542
|
+
}
|
|
2543
|
+
hide() {
|
|
2544
|
+
if (this.selectionListener) {
|
|
2545
|
+
document.removeEventListener("selectionchange", this.selectionListener);
|
|
2546
|
+
this.selectionListener = null;
|
|
2547
|
+
}
|
|
2548
|
+
if (this.el) {
|
|
2549
|
+
this.el.remove();
|
|
2550
|
+
this.el = null;
|
|
2551
|
+
}
|
|
2552
|
+
this.anchor = null;
|
|
2553
|
+
}
|
|
2554
|
+
getElement() {
|
|
2555
|
+
return this.el;
|
|
2556
|
+
}
|
|
2557
|
+
updatePosition(anchor) {
|
|
2558
|
+
if (this.el) {
|
|
2559
|
+
this.positionToolbar(anchor);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
createToolbarElement() {
|
|
2563
|
+
const toolbar = document.createElement("div");
|
|
2564
|
+
toolbar.dataset["noteToolbar"] = "";
|
|
2565
|
+
Object.assign(toolbar.style, {
|
|
2566
|
+
position: "fixed",
|
|
2567
|
+
display: "flex",
|
|
2568
|
+
alignItems: "center",
|
|
2569
|
+
gap: "2px",
|
|
2570
|
+
padding: "2px 4px",
|
|
2571
|
+
background: "#fff",
|
|
2572
|
+
border: "1px solid #ccc",
|
|
2573
|
+
borderRadius: "4px",
|
|
2574
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
|
|
2575
|
+
zIndex: "10000",
|
|
2576
|
+
height: `${TOOLBAR_HEIGHT}px`,
|
|
2577
|
+
userSelect: "none"
|
|
2578
|
+
});
|
|
2579
|
+
for (const btn of FORMAT_BUTTONS) {
|
|
2580
|
+
toolbar.appendChild(this.createFormatButton(btn));
|
|
2581
|
+
}
|
|
2582
|
+
toolbar.appendChild(this.createFontSizeSelect());
|
|
2583
|
+
return toolbar;
|
|
2584
|
+
}
|
|
2585
|
+
createFormatButton(config) {
|
|
2586
|
+
const btn = document.createElement("button");
|
|
2587
|
+
btn.dataset["format"] = config.format;
|
|
2588
|
+
btn.textContent = config.label;
|
|
2589
|
+
Object.assign(btn.style, {
|
|
2590
|
+
border: "1px solid transparent",
|
|
2591
|
+
borderRadius: "3px",
|
|
2592
|
+
background: "none",
|
|
2593
|
+
cursor: "pointer",
|
|
2594
|
+
padding: "2px 6px",
|
|
2595
|
+
fontSize: "13px",
|
|
2596
|
+
fontWeight: config.format === "bold" ? "bold" : "normal",
|
|
2597
|
+
fontStyle: config.format === "italic" ? "italic" : "normal",
|
|
2598
|
+
textDecoration: config.format === "underline" ? "underline" : config.format === "strikethrough" ? "line-through" : "none",
|
|
2599
|
+
minWidth: "24px",
|
|
2600
|
+
height: "24px",
|
|
2601
|
+
lineHeight: "24px"
|
|
2602
|
+
});
|
|
2603
|
+
btn.addEventListener("pointerdown", (e) => {
|
|
2604
|
+
e.preventDefault();
|
|
2605
|
+
document.execCommand(config.command);
|
|
2606
|
+
this.updateActiveStates();
|
|
2607
|
+
});
|
|
2608
|
+
return btn;
|
|
2609
|
+
}
|
|
2610
|
+
createFontSizeSelect() {
|
|
2611
|
+
const select = document.createElement("select");
|
|
2612
|
+
Object.assign(select.style, {
|
|
2613
|
+
border: "1px solid #ccc",
|
|
2614
|
+
borderRadius: "3px",
|
|
2615
|
+
background: "#fff",
|
|
2616
|
+
cursor: "pointer",
|
|
2617
|
+
padding: "2px",
|
|
2618
|
+
fontSize: "12px",
|
|
2619
|
+
height: "24px",
|
|
2620
|
+
marginLeft: "4px"
|
|
2621
|
+
});
|
|
2622
|
+
for (const preset of this.fontSizePresets) {
|
|
2623
|
+
const option = document.createElement("option");
|
|
2624
|
+
option.value = String(preset.size);
|
|
2625
|
+
option.textContent = preset.label;
|
|
2626
|
+
select.appendChild(option);
|
|
2627
|
+
}
|
|
2628
|
+
select.value = String(DEFAULT_NOTE_FONT_SIZE);
|
|
2629
|
+
select.addEventListener("pointerdown", (e) => {
|
|
2630
|
+
e.stopPropagation();
|
|
2631
|
+
});
|
|
2632
|
+
select.addEventListener("change", () => {
|
|
2633
|
+
setFontSize(Number(select.value));
|
|
2634
|
+
this.updateActiveStates();
|
|
2635
|
+
this.anchor?.focus();
|
|
2636
|
+
});
|
|
2637
|
+
return select;
|
|
2638
|
+
}
|
|
2639
|
+
positionToolbar(anchor) {
|
|
2640
|
+
if (!this.el) return;
|
|
2641
|
+
const rect = anchor.getBoundingClientRect();
|
|
2642
|
+
const toolbarWidth = this.el.offsetWidth || 200;
|
|
2643
|
+
let top = rect.top - TOOLBAR_HEIGHT - TOOLBAR_GAP;
|
|
2644
|
+
if (top < 0) {
|
|
2645
|
+
top = rect.bottom + TOOLBAR_GAP;
|
|
2646
|
+
}
|
|
2647
|
+
let left = rect.left + (rect.width - toolbarWidth) / 2;
|
|
2648
|
+
left = Math.max(4, left);
|
|
2649
|
+
Object.assign(this.el.style, {
|
|
2650
|
+
top: `${top}px`,
|
|
2651
|
+
left: `${left}px`
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
updateActiveStates() {
|
|
2655
|
+
if (!this.el) return;
|
|
2656
|
+
const active = getActiveFormats();
|
|
2657
|
+
for (const config of FORMAT_BUTTONS) {
|
|
2658
|
+
const btn = this.el.querySelector(`[data-format="${config.format}"]`);
|
|
2659
|
+
if (!btn) continue;
|
|
2660
|
+
const isActive = active[config.format] ?? false;
|
|
2661
|
+
btn.style.background = isActive ? "#e0e0e0" : "none";
|
|
2662
|
+
btn.style.borderColor = isActive ? "#bbb" : "transparent";
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
|
|
1782
2667
|
// src/elements/note-editor.ts
|
|
2668
|
+
var FORMAT_SHORTCUTS = {
|
|
2669
|
+
b: toggleBold,
|
|
2670
|
+
i: toggleItalic,
|
|
2671
|
+
u: toggleUnderline
|
|
2672
|
+
};
|
|
1783
2673
|
var NoteEditor = class {
|
|
1784
2674
|
editingId = null;
|
|
1785
2675
|
editingNode = null;
|
|
@@ -1788,6 +2678,10 @@ var NoteEditor = class {
|
|
|
1788
2678
|
pointerHandler = null;
|
|
1789
2679
|
pendingEditId = null;
|
|
1790
2680
|
onStopCallback = null;
|
|
2681
|
+
toolbar;
|
|
2682
|
+
constructor(options) {
|
|
2683
|
+
this.toolbar = options?.toolbar === false ? null : new NoteToolbar(options?.fontSizePresets);
|
|
2684
|
+
}
|
|
1791
2685
|
get isEditing() {
|
|
1792
2686
|
return this.editingId !== null;
|
|
1793
2687
|
}
|
|
@@ -1812,13 +2706,6 @@ var NoteEditor = class {
|
|
|
1812
2706
|
stopEditing(store) {
|
|
1813
2707
|
this.pendingEditId = null;
|
|
1814
2708
|
if (!this.editingId || !this.editingNode) return;
|
|
1815
|
-
const text = this.editingNode.textContent ?? "";
|
|
1816
|
-
store.update(this.editingId, { text });
|
|
1817
|
-
this.editingNode.contentEditable = "false";
|
|
1818
|
-
Object.assign(this.editingNode.style, {
|
|
1819
|
-
userSelect: "none",
|
|
1820
|
-
cursor: "default"
|
|
1821
|
-
});
|
|
1822
2709
|
if (this.blurHandler) {
|
|
1823
2710
|
this.editingNode.removeEventListener("blur", this.blurHandler);
|
|
1824
2711
|
}
|
|
@@ -1828,6 +2715,14 @@ var NoteEditor = class {
|
|
|
1828
2715
|
if (this.pointerHandler) {
|
|
1829
2716
|
this.editingNode.removeEventListener("pointerdown", this.pointerHandler);
|
|
1830
2717
|
}
|
|
2718
|
+
const text = sanitizeNoteHtml(this.editingNode.innerHTML);
|
|
2719
|
+
store.update(this.editingId, { text });
|
|
2720
|
+
this.editingNode.contentEditable = "false";
|
|
2721
|
+
Object.assign(this.editingNode.style, {
|
|
2722
|
+
userSelect: "none",
|
|
2723
|
+
cursor: "default"
|
|
2724
|
+
});
|
|
2725
|
+
this.toolbar?.hide();
|
|
1831
2726
|
if (this.editingId && this.onStopCallback) {
|
|
1832
2727
|
this.onStopCallback(this.editingId);
|
|
1833
2728
|
}
|
|
@@ -1843,6 +2738,11 @@ var NoteEditor = class {
|
|
|
1843
2738
|
this.stopEditing(store);
|
|
1844
2739
|
}
|
|
1845
2740
|
}
|
|
2741
|
+
updateToolbarPosition() {
|
|
2742
|
+
if (this.editingNode) {
|
|
2743
|
+
this.toolbar?.updatePosition(this.editingNode);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
1846
2746
|
activateEditing(node, elementId, store) {
|
|
1847
2747
|
this.editingId = elementId;
|
|
1848
2748
|
this.editingNode = node;
|
|
@@ -1861,8 +2761,21 @@ var NoteEditor = class {
|
|
|
1861
2761
|
selection.removeAllRanges();
|
|
1862
2762
|
selection.addRange(range);
|
|
1863
2763
|
}
|
|
1864
|
-
this.
|
|
2764
|
+
this.toolbar?.show(node);
|
|
2765
|
+
this.blurHandler = (e) => {
|
|
2766
|
+
const related = e.relatedTarget;
|
|
2767
|
+
if (related && this.toolbar?.getElement()?.contains(related)) return;
|
|
2768
|
+
this.stopEditing(store);
|
|
2769
|
+
};
|
|
1865
2770
|
this.keyHandler = (e) => {
|
|
2771
|
+
if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
|
|
2772
|
+
const action = FORMAT_SHORTCUTS[e.key.toLowerCase()];
|
|
2773
|
+
if (action) {
|
|
2774
|
+
e.preventDefault();
|
|
2775
|
+
action();
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
1866
2779
|
if (e.key === "Escape") {
|
|
1867
2780
|
node.blur();
|
|
1868
2781
|
}
|
|
@@ -2114,131 +3027,86 @@ var HistoryRecorder = class {
|
|
|
2114
3027
|
}
|
|
2115
3028
|
};
|
|
2116
3029
|
|
|
2117
|
-
// src/
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
}
|
|
2197
|
-
function createShape(input) {
|
|
2198
|
-
return {
|
|
2199
|
-
id: createId("shape"),
|
|
2200
|
-
type: "shape",
|
|
2201
|
-
position: input.position,
|
|
2202
|
-
zIndex: input.zIndex ?? 0,
|
|
2203
|
-
locked: input.locked ?? false,
|
|
2204
|
-
layerId: input.layerId ?? "",
|
|
2205
|
-
shape: input.shape ?? "rectangle",
|
|
2206
|
-
size: input.size,
|
|
2207
|
-
strokeColor: input.strokeColor ?? "#000000",
|
|
2208
|
-
strokeWidth: input.strokeWidth ?? 2,
|
|
2209
|
-
fillColor: input.fillColor ?? "none"
|
|
2210
|
-
};
|
|
2211
|
-
}
|
|
2212
|
-
function createGrid(input) {
|
|
2213
|
-
return {
|
|
2214
|
-
id: createId("grid"),
|
|
2215
|
-
type: "grid",
|
|
2216
|
-
position: input.position ?? { x: 0, y: 0 },
|
|
2217
|
-
zIndex: input.zIndex ?? 0,
|
|
2218
|
-
locked: input.locked ?? false,
|
|
2219
|
-
layerId: input.layerId ?? "",
|
|
2220
|
-
gridType: input.gridType ?? "square",
|
|
2221
|
-
hexOrientation: input.hexOrientation ?? "pointy",
|
|
2222
|
-
cellSize: input.cellSize ?? 40,
|
|
2223
|
-
strokeColor: input.strokeColor ?? "#000000",
|
|
2224
|
-
strokeWidth: input.strokeWidth ?? 1,
|
|
2225
|
-
opacity: input.opacity ?? 1
|
|
2226
|
-
};
|
|
2227
|
-
}
|
|
2228
|
-
function createText(input) {
|
|
2229
|
-
return {
|
|
2230
|
-
id: createId("text"),
|
|
2231
|
-
type: "text",
|
|
2232
|
-
position: input.position,
|
|
2233
|
-
zIndex: input.zIndex ?? 0,
|
|
2234
|
-
locked: input.locked ?? false,
|
|
2235
|
-
layerId: input.layerId ?? "",
|
|
2236
|
-
size: input.size ?? { w: 200, h: 28 },
|
|
2237
|
-
text: input.text ?? "",
|
|
2238
|
-
fontSize: input.fontSize ?? 16,
|
|
2239
|
-
color: input.color ?? "#1a1a1a",
|
|
2240
|
-
textAlign: input.textAlign ?? "left"
|
|
2241
|
-
};
|
|
3030
|
+
// src/canvas/note-canvas-renderer.ts
|
|
3031
|
+
function renderNoteOnCanvas(ctx, note) {
|
|
3032
|
+
const { x, y } = note.position;
|
|
3033
|
+
const { w, h } = note.size;
|
|
3034
|
+
const r = 4;
|
|
3035
|
+
const pad = 8;
|
|
3036
|
+
const baseFontSize = note.fontSize ?? DEFAULT_NOTE_FONT_SIZE;
|
|
3037
|
+
ctx.save();
|
|
3038
|
+
ctx.fillStyle = note.backgroundColor;
|
|
3039
|
+
ctx.beginPath();
|
|
3040
|
+
ctx.moveTo(x + r, y);
|
|
3041
|
+
ctx.lineTo(x + w - r, y);
|
|
3042
|
+
ctx.arcTo(x + w, y, x + w, y + r, r);
|
|
3043
|
+
ctx.lineTo(x + w, y + h - r);
|
|
3044
|
+
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
|
3045
|
+
ctx.lineTo(x + r, y + h);
|
|
3046
|
+
ctx.arcTo(x, y + h, x, y + h - r, r);
|
|
3047
|
+
ctx.lineTo(x, y + r);
|
|
3048
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
3049
|
+
ctx.closePath();
|
|
3050
|
+
ctx.fill();
|
|
3051
|
+
if (note.text) {
|
|
3052
|
+
ctx.fillStyle = note.textColor;
|
|
3053
|
+
const runs = parseStyledRuns(note.text, baseFontSize);
|
|
3054
|
+
renderStyledRuns(ctx, runs, x + pad, y + pad, w - pad * 2);
|
|
3055
|
+
}
|
|
3056
|
+
ctx.restore();
|
|
3057
|
+
}
|
|
3058
|
+
function buildFontString(run) {
|
|
3059
|
+
const style = run.italic ? "italic" : "normal";
|
|
3060
|
+
const weight = run.bold ? "bold" : "normal";
|
|
3061
|
+
return `${style} ${weight} ${run.fontSize}px system-ui, sans-serif`;
|
|
3062
|
+
}
|
|
3063
|
+
function renderStyledRuns(ctx, runs, startX, startY, maxWidth) {
|
|
3064
|
+
ctx.textBaseline = "top";
|
|
3065
|
+
let cursorX = startX;
|
|
3066
|
+
let cursorY = startY;
|
|
3067
|
+
let lineHeight = 0;
|
|
3068
|
+
for (const run of runs) {
|
|
3069
|
+
ctx.font = buildFontString(run);
|
|
3070
|
+
const runLineHeight = run.fontSize * 1.3;
|
|
3071
|
+
lineHeight = Math.max(lineHeight, runLineHeight);
|
|
3072
|
+
const words = run.text.split(/(\n| )/);
|
|
3073
|
+
for (const word of words) {
|
|
3074
|
+
if (word === "\n") {
|
|
3075
|
+
cursorX = startX;
|
|
3076
|
+
cursorY += lineHeight;
|
|
3077
|
+
lineHeight = runLineHeight;
|
|
3078
|
+
continue;
|
|
3079
|
+
}
|
|
3080
|
+
if (word === " ") {
|
|
3081
|
+
const spaceWidth = ctx.measureText(" ").width;
|
|
3082
|
+
if (cursorX + spaceWidth > startX + maxWidth && cursorX > startX) {
|
|
3083
|
+
cursorX = startX;
|
|
3084
|
+
cursorY += lineHeight;
|
|
3085
|
+
lineHeight = runLineHeight;
|
|
3086
|
+
} else {
|
|
3087
|
+
cursorX += spaceWidth;
|
|
3088
|
+
}
|
|
3089
|
+
continue;
|
|
3090
|
+
}
|
|
3091
|
+
if (!word) continue;
|
|
3092
|
+
const metrics = ctx.measureText(word);
|
|
3093
|
+
if (cursorX + metrics.width > startX + maxWidth && cursorX > startX) {
|
|
3094
|
+
cursorX = startX;
|
|
3095
|
+
cursorY += lineHeight;
|
|
3096
|
+
lineHeight = runLineHeight;
|
|
3097
|
+
}
|
|
3098
|
+
ctx.fillText(word, cursorX, cursorY);
|
|
3099
|
+
if (run.underline) {
|
|
3100
|
+
const underY = cursorY + run.fontSize + 1;
|
|
3101
|
+
ctx.fillRect(cursorX, underY, metrics.width, 1);
|
|
3102
|
+
}
|
|
3103
|
+
if (run.strikethrough) {
|
|
3104
|
+
const strikeY = cursorY + run.fontSize * 0.55;
|
|
3105
|
+
ctx.fillRect(cursorX, strikeY, metrics.width, 1);
|
|
3106
|
+
}
|
|
3107
|
+
cursorX += metrics.width;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
2242
3110
|
}
|
|
2243
3111
|
|
|
2244
3112
|
// src/canvas/export-image.ts
|
|
@@ -2276,6 +3144,11 @@ function getElementRect(el) {
|
|
|
2276
3144
|
}
|
|
2277
3145
|
case "grid":
|
|
2278
3146
|
return null;
|
|
3147
|
+
case "template": {
|
|
3148
|
+
const bounds = getElementBounds(el);
|
|
3149
|
+
if (!bounds) return null;
|
|
3150
|
+
return bounds;
|
|
3151
|
+
}
|
|
2279
3152
|
case "note":
|
|
2280
3153
|
case "image":
|
|
2281
3154
|
case "html":
|
|
@@ -2312,33 +3185,6 @@ function computeBounds(elements, padding) {
|
|
|
2312
3185
|
h: maxY - minY + padding * 2
|
|
2313
3186
|
};
|
|
2314
3187
|
}
|
|
2315
|
-
function renderNoteOnCanvas(ctx, note) {
|
|
2316
|
-
const { x, y } = note.position;
|
|
2317
|
-
const { w, h } = note.size;
|
|
2318
|
-
const r = 4;
|
|
2319
|
-
const pad = 8;
|
|
2320
|
-
ctx.save();
|
|
2321
|
-
ctx.fillStyle = note.backgroundColor;
|
|
2322
|
-
ctx.beginPath();
|
|
2323
|
-
ctx.moveTo(x + r, y);
|
|
2324
|
-
ctx.lineTo(x + w - r, y);
|
|
2325
|
-
ctx.arcTo(x + w, y, x + w, y + r, r);
|
|
2326
|
-
ctx.lineTo(x + w, y + h - r);
|
|
2327
|
-
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
|
2328
|
-
ctx.lineTo(x + r, y + h);
|
|
2329
|
-
ctx.arcTo(x, y + h, x, y + h - r, r);
|
|
2330
|
-
ctx.lineTo(x, y + r);
|
|
2331
|
-
ctx.arcTo(x, y, x + r, y, r);
|
|
2332
|
-
ctx.closePath();
|
|
2333
|
-
ctx.fill();
|
|
2334
|
-
if (note.text) {
|
|
2335
|
-
ctx.fillStyle = note.textColor;
|
|
2336
|
-
ctx.font = "14px system-ui, sans-serif";
|
|
2337
|
-
ctx.textBaseline = "top";
|
|
2338
|
-
wrapText(ctx, note.text, x + pad, y + pad, w - pad * 2, 18);
|
|
2339
|
-
}
|
|
2340
|
-
ctx.restore();
|
|
2341
|
-
}
|
|
2342
3188
|
function renderTextOnCanvas(ctx, text) {
|
|
2343
3189
|
if (!text.text) return;
|
|
2344
3190
|
ctx.save();
|
|
@@ -2363,25 +3209,6 @@ function renderTextOnCanvas(ctx, text) {
|
|
|
2363
3209
|
}
|
|
2364
3210
|
ctx.restore();
|
|
2365
3211
|
}
|
|
2366
|
-
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
|
|
2367
|
-
const words = text.split(" ");
|
|
2368
|
-
let line = "";
|
|
2369
|
-
let offsetY = 0;
|
|
2370
|
-
for (const word of words) {
|
|
2371
|
-
const testLine = line ? `${line} ${word}` : word;
|
|
2372
|
-
const metrics = ctx.measureText(testLine);
|
|
2373
|
-
if (metrics.width > maxWidth && line) {
|
|
2374
|
-
ctx.fillText(line, x, y + offsetY);
|
|
2375
|
-
line = word;
|
|
2376
|
-
offsetY += lineHeight;
|
|
2377
|
-
} else {
|
|
2378
|
-
line = testLine;
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
if (line) {
|
|
2382
|
-
ctx.fillText(line, x, y + offsetY);
|
|
2383
|
-
}
|
|
2384
|
-
}
|
|
2385
3212
|
function renderGridForBounds(ctx, grid, bounds) {
|
|
2386
3213
|
const visibleBounds = {
|
|
2387
3214
|
minX: bounds.x,
|
|
@@ -2780,13 +3607,13 @@ var DomNodeManager = class {
|
|
|
2780
3607
|
padding: "8px",
|
|
2781
3608
|
borderRadius: "4px",
|
|
2782
3609
|
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
|
|
2783
|
-
fontSize:
|
|
3610
|
+
fontSize: `${element.fontSize ?? DEFAULT_NOTE_FONT_SIZE}px`,
|
|
2784
3611
|
overflow: "hidden",
|
|
2785
3612
|
cursor: "default",
|
|
2786
3613
|
userSelect: "none",
|
|
2787
3614
|
wordWrap: "break-word"
|
|
2788
3615
|
});
|
|
2789
|
-
node.
|
|
3616
|
+
node.innerHTML = element.text || "";
|
|
2790
3617
|
node.addEventListener("dblclick", (e) => {
|
|
2791
3618
|
e.stopPropagation();
|
|
2792
3619
|
const id = node.dataset["elementId"];
|
|
@@ -2794,11 +3621,13 @@ var DomNodeManager = class {
|
|
|
2794
3621
|
});
|
|
2795
3622
|
}
|
|
2796
3623
|
if (!this.isEditingElement(element.id)) {
|
|
2797
|
-
|
|
2798
|
-
|
|
3624
|
+
const text = element.text || "";
|
|
3625
|
+
if (node.innerHTML !== text) {
|
|
3626
|
+
node.innerHTML = text;
|
|
2799
3627
|
}
|
|
2800
3628
|
node.style.backgroundColor = element.backgroundColor;
|
|
2801
3629
|
node.style.color = element.textColor;
|
|
3630
|
+
node.style.fontSize = `${element.fontSize ?? DEFAULT_NOTE_FONT_SIZE}px`;
|
|
2802
3631
|
}
|
|
2803
3632
|
}
|
|
2804
3633
|
if (element.type === "html" && !node.dataset["initialized"]) {
|
|
@@ -3020,7 +3849,15 @@ var RenderLoop = class {
|
|
|
3020
3849
|
ctx.save();
|
|
3021
3850
|
ctx.scale(dpr, dpr);
|
|
3022
3851
|
this.renderer.setCanvasSize(cssWidth, cssHeight);
|
|
3023
|
-
this.
|
|
3852
|
+
const hasGridElement = this.store.getElementsByType("grid").length > 0;
|
|
3853
|
+
if (hasGridElement) {
|
|
3854
|
+
ctx.save();
|
|
3855
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
3856
|
+
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
|
3857
|
+
ctx.restore();
|
|
3858
|
+
} else {
|
|
3859
|
+
this.background.render(ctx, this.camera);
|
|
3860
|
+
}
|
|
3024
3861
|
ctx.save();
|
|
3025
3862
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
3026
3863
|
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
@@ -3221,7 +4058,10 @@ var Viewport = class {
|
|
|
3221
4058
|
this.renderLoop.markAllLayersDirty();
|
|
3222
4059
|
this.requestRender();
|
|
3223
4060
|
});
|
|
3224
|
-
this.noteEditor = new NoteEditor(
|
|
4061
|
+
this.noteEditor = new NoteEditor({
|
|
4062
|
+
fontSizePresets: options.fontSizePresets,
|
|
4063
|
+
toolbar: options.toolbar
|
|
4064
|
+
});
|
|
3225
4065
|
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
3226
4066
|
this.history = new HistoryStack();
|
|
3227
4067
|
this.historyRecorder = new HistoryRecorder(this.store, this.history);
|
|
@@ -3277,20 +4117,24 @@ var Viewport = class {
|
|
|
3277
4117
|
});
|
|
3278
4118
|
this.unsubCamera = this.camera.onChange(() => {
|
|
3279
4119
|
this.applyCameraTransform();
|
|
4120
|
+
this.noteEditor.updateToolbarPosition();
|
|
3280
4121
|
this.requestRender();
|
|
3281
4122
|
});
|
|
3282
4123
|
this.unsubStore = [
|
|
3283
4124
|
this.store.on("add", (el) => {
|
|
4125
|
+
if (el.type === "grid") this.syncGridContext();
|
|
3284
4126
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
3285
4127
|
this.requestRender();
|
|
3286
4128
|
}),
|
|
3287
4129
|
this.store.on("remove", (el) => {
|
|
4130
|
+
if (el.type === "grid") this.syncGridContext();
|
|
3288
4131
|
this.unbindArrowsFrom(el);
|
|
3289
4132
|
this.domNodeManager.removeDomNode(el.id);
|
|
3290
4133
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
3291
4134
|
this.requestRender();
|
|
3292
4135
|
}),
|
|
3293
4136
|
this.store.on("update", ({ previous, current }) => {
|
|
4137
|
+
if (current.type === "grid") this.syncGridContext();
|
|
3294
4138
|
this.renderLoop.markLayerDirty(current.layerId);
|
|
3295
4139
|
if (previous.layerId !== current.layerId) {
|
|
3296
4140
|
this.renderLoop.markLayerDirty(previous.layerId);
|
|
@@ -3300,6 +4144,7 @@ var Viewport = class {
|
|
|
3300
4144
|
this.store.on("clear", () => {
|
|
3301
4145
|
this.domNodeManager.clearDomNodes();
|
|
3302
4146
|
this.renderLoop.markAllLayersDirty();
|
|
4147
|
+
this.syncGridContext();
|
|
3303
4148
|
this.requestRender();
|
|
3304
4149
|
})
|
|
3305
4150
|
];
|
|
@@ -3313,6 +4158,7 @@ var Viewport = class {
|
|
|
3313
4158
|
this.observeResize();
|
|
3314
4159
|
this.syncCanvasSize();
|
|
3315
4160
|
this.renderLoop.start();
|
|
4161
|
+
this.syncGridContext();
|
|
3316
4162
|
}
|
|
3317
4163
|
camera;
|
|
3318
4164
|
store;
|
|
@@ -3629,6 +4475,18 @@ var Viewport = class {
|
|
|
3629
4475
|
this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
|
|
3630
4476
|
this.requestRender();
|
|
3631
4477
|
}
|
|
4478
|
+
syncGridContext() {
|
|
4479
|
+
const grid = this.store.getElementsByType("grid")[0];
|
|
4480
|
+
if (grid) {
|
|
4481
|
+
this.toolContext.gridSize = grid.cellSize;
|
|
4482
|
+
this.toolContext.gridType = grid.gridType;
|
|
4483
|
+
this.toolContext.hexOrientation = grid.hexOrientation;
|
|
4484
|
+
} else {
|
|
4485
|
+
this.toolContext.gridSize = this._gridSize;
|
|
4486
|
+
this.toolContext.gridType = void 0;
|
|
4487
|
+
this.toolContext.hexOrientation = void 0;
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
3632
4490
|
observeResize() {
|
|
3633
4491
|
if (typeof ResizeObserver === "undefined") return;
|
|
3634
4492
|
this.resizeObserver = new ResizeObserver(() => this.syncCanvasSize());
|
|
@@ -4003,7 +4861,7 @@ var SelectTool = class {
|
|
|
4003
4861
|
ctx.setCursor?.("default");
|
|
4004
4862
|
}
|
|
4005
4863
|
snap(point, ctx) {
|
|
4006
|
-
return
|
|
4864
|
+
return smartSnap(point, ctx);
|
|
4007
4865
|
}
|
|
4008
4866
|
onPointerDown(state, ctx) {
|
|
4009
4867
|
this.ctx = ctx;
|
|
@@ -4020,6 +4878,12 @@ var SelectTool = class {
|
|
|
4020
4878
|
ctx.requestRender();
|
|
4021
4879
|
return;
|
|
4022
4880
|
}
|
|
4881
|
+
const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
|
|
4882
|
+
if (templateResizeHit) {
|
|
4883
|
+
this.mode = { type: "resizing-template", elementId: templateResizeHit };
|
|
4884
|
+
ctx.requestRender();
|
|
4885
|
+
return;
|
|
4886
|
+
}
|
|
4023
4887
|
const resizeHit = this.hitTestResizeHandle(world, ctx);
|
|
4024
4888
|
if (resizeHit) {
|
|
4025
4889
|
const el = ctx.store.getById(resizeHit.elementId);
|
|
@@ -4054,6 +4918,11 @@ var SelectTool = class {
|
|
|
4054
4918
|
applyArrowHandleDrag(this.mode.handle, this.mode.elementId, world, ctx);
|
|
4055
4919
|
return;
|
|
4056
4920
|
}
|
|
4921
|
+
if (this.mode.type === "resizing-template") {
|
|
4922
|
+
ctx.setCursor?.("nwse-resize");
|
|
4923
|
+
this.handleTemplateResize(world, ctx);
|
|
4924
|
+
return;
|
|
4925
|
+
}
|
|
4057
4926
|
if (this.mode.type === "resizing") {
|
|
4058
4927
|
ctx.setCursor?.(HANDLE_CURSORS[this.mode.handle]);
|
|
4059
4928
|
this.handleResize(world, ctx);
|
|
@@ -4077,6 +4946,16 @@ var SelectTool = class {
|
|
|
4077
4946
|
from: { x: el.from.x + dx, y: el.from.y + dy },
|
|
4078
4947
|
to: { x: el.to.x + dx, y: el.to.y + dy }
|
|
4079
4948
|
});
|
|
4949
|
+
} else if (ctx.gridType && "size" in el) {
|
|
4950
|
+
const centerX = el.position.x + el.size.w / 2 + dx;
|
|
4951
|
+
const centerY = el.position.y + el.size.h / 2 + dy;
|
|
4952
|
+
const snappedCenter = this.snap({ x: centerX, y: centerY }, ctx);
|
|
4953
|
+
ctx.store.update(id, {
|
|
4954
|
+
position: {
|
|
4955
|
+
x: snappedCenter.x - el.size.w / 2,
|
|
4956
|
+
y: snappedCenter.y - el.size.h / 2
|
|
4957
|
+
}
|
|
4958
|
+
});
|
|
4080
4959
|
} else {
|
|
4081
4960
|
ctx.store.update(id, {
|
|
4082
4961
|
position: { x: el.position.x + dx, y: el.position.y + dy }
|
|
@@ -4151,6 +5030,11 @@ var SelectTool = class {
|
|
|
4151
5030
|
ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
|
|
4152
5031
|
return;
|
|
4153
5032
|
}
|
|
5033
|
+
const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
|
|
5034
|
+
if (templateResizeHit) {
|
|
5035
|
+
ctx.setCursor?.("nwse-resize");
|
|
5036
|
+
return;
|
|
5037
|
+
}
|
|
4154
5038
|
const resizeHit = this.hitTestResizeHandle(world, ctx);
|
|
4155
5039
|
if (resizeHit) {
|
|
4156
5040
|
ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
|
|
@@ -4287,6 +5171,24 @@ var SelectTool = class {
|
|
|
4287
5171
|
);
|
|
4288
5172
|
}
|
|
4289
5173
|
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
5174
|
+
} else if (el.type === "template") {
|
|
5175
|
+
canvasCtx.setLineDash([]);
|
|
5176
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
5177
|
+
const hx = bounds.x + bounds.w;
|
|
5178
|
+
const hy = bounds.y + bounds.h;
|
|
5179
|
+
canvasCtx.fillRect(
|
|
5180
|
+
hx - handleWorldSize / 2,
|
|
5181
|
+
hy - handleWorldSize / 2,
|
|
5182
|
+
handleWorldSize,
|
|
5183
|
+
handleWorldSize
|
|
5184
|
+
);
|
|
5185
|
+
canvasCtx.strokeRect(
|
|
5186
|
+
hx - handleWorldSize / 2,
|
|
5187
|
+
hy - handleWorldSize / 2,
|
|
5188
|
+
handleWorldSize,
|
|
5189
|
+
handleWorldSize
|
|
5190
|
+
);
|
|
5191
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
4290
5192
|
}
|
|
4291
5193
|
}
|
|
4292
5194
|
canvasCtx.restore();
|
|
@@ -4311,6 +5213,43 @@ var SelectTool = class {
|
|
|
4311
5213
|
}
|
|
4312
5214
|
canvasCtx.restore();
|
|
4313
5215
|
}
|
|
5216
|
+
hitTestTemplateResizeHandle(world, ctx) {
|
|
5217
|
+
if (this._selectedIds.length === 0) return null;
|
|
5218
|
+
const zoom = ctx.camera.zoom;
|
|
5219
|
+
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
5220
|
+
for (const id of this._selectedIds) {
|
|
5221
|
+
const el = ctx.store.getById(id);
|
|
5222
|
+
if (!el || el.type !== "template") continue;
|
|
5223
|
+
const bounds = getElementBounds(el);
|
|
5224
|
+
if (!bounds) continue;
|
|
5225
|
+
const hx = bounds.x + bounds.w;
|
|
5226
|
+
const hy = bounds.y + bounds.h;
|
|
5227
|
+
if (Math.abs(world.x - hx) <= handleHalf && Math.abs(world.y - hy) <= handleHalf) {
|
|
5228
|
+
return id;
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
return null;
|
|
5232
|
+
}
|
|
5233
|
+
handleTemplateResize(world, ctx) {
|
|
5234
|
+
if (this.mode.type !== "resizing-template") return;
|
|
5235
|
+
const el = ctx.store.getById(this.mode.elementId);
|
|
5236
|
+
if (!el || el.type !== "template" || el.locked) return;
|
|
5237
|
+
const dx = world.x - el.position.x;
|
|
5238
|
+
const dy = world.y - el.position.y;
|
|
5239
|
+
let newRadius = Math.sqrt(dx * dx + dy * dy);
|
|
5240
|
+
if (ctx.snapToGrid && ctx.gridSize && ctx.gridSize > 0) {
|
|
5241
|
+
const snapUnit = ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize;
|
|
5242
|
+
newRadius = Math.max(snapUnit, Math.round(newRadius / snapUnit) * snapUnit);
|
|
5243
|
+
}
|
|
5244
|
+
newRadius = Math.max(MIN_ELEMENT_SIZE, newRadius);
|
|
5245
|
+
const updates = { radius: newRadius };
|
|
5246
|
+
if (el.feetPerCell != null && ctx.gridSize && ctx.gridSize > 0) {
|
|
5247
|
+
const snapUnit = ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize;
|
|
5248
|
+
updates.radiusFeet = newRadius / snapUnit * el.feetPerCell;
|
|
5249
|
+
}
|
|
5250
|
+
ctx.store.update(this.mode.elementId, updates);
|
|
5251
|
+
ctx.requestRender();
|
|
5252
|
+
}
|
|
4314
5253
|
getMarqueeRect() {
|
|
4315
5254
|
if (this.mode.type !== "marquee") return null;
|
|
4316
5255
|
const { start } = this.mode;
|
|
@@ -4367,6 +5306,11 @@ var SelectTool = class {
|
|
|
4367
5306
|
if (el.type === "arrow") {
|
|
4368
5307
|
return isNearBezier(point, el.from, el.to, el.bend, 10);
|
|
4369
5308
|
}
|
|
5309
|
+
if (el.type === "template") {
|
|
5310
|
+
const bounds = getElementBounds(el);
|
|
5311
|
+
if (!bounds) return false;
|
|
5312
|
+
return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
|
|
5313
|
+
}
|
|
4370
5314
|
return false;
|
|
4371
5315
|
}
|
|
4372
5316
|
};
|
|
@@ -4424,7 +5368,7 @@ var ArrowTool = class {
|
|
|
4424
5368
|
this.fromBinding = { elementId: target.id };
|
|
4425
5369
|
this.fromTarget = target;
|
|
4426
5370
|
} else {
|
|
4427
|
-
this.start =
|
|
5371
|
+
this.start = smartSnap(world, ctx);
|
|
4428
5372
|
this.fromBinding = void 0;
|
|
4429
5373
|
this.fromTarget = null;
|
|
4430
5374
|
}
|
|
@@ -4442,7 +5386,7 @@ var ArrowTool = class {
|
|
|
4442
5386
|
this.end = getElementCenter(target);
|
|
4443
5387
|
this.toTarget = target;
|
|
4444
5388
|
} else {
|
|
4445
|
-
this.end =
|
|
5389
|
+
this.end = smartSnap(world, ctx);
|
|
4446
5390
|
this.toTarget = null;
|
|
4447
5391
|
}
|
|
4448
5392
|
ctx.requestRender();
|
|
@@ -4523,17 +5467,20 @@ var NoteTool = class {
|
|
|
4523
5467
|
backgroundColor;
|
|
4524
5468
|
textColor;
|
|
4525
5469
|
size;
|
|
5470
|
+
fontSize;
|
|
4526
5471
|
optionListeners = /* @__PURE__ */ new Set();
|
|
4527
5472
|
constructor(options = {}) {
|
|
4528
5473
|
this.backgroundColor = options.backgroundColor ?? "#ffeb3b";
|
|
4529
5474
|
this.textColor = options.textColor ?? "#000000";
|
|
4530
5475
|
this.size = options.size ?? { w: 200, h: 100 };
|
|
5476
|
+
this.fontSize = options.fontSize ?? DEFAULT_NOTE_FONT_SIZE;
|
|
4531
5477
|
}
|
|
4532
5478
|
getOptions() {
|
|
4533
5479
|
return {
|
|
4534
5480
|
backgroundColor: this.backgroundColor,
|
|
4535
5481
|
textColor: this.textColor,
|
|
4536
|
-
size: { ...this.size }
|
|
5482
|
+
size: { ...this.size },
|
|
5483
|
+
fontSize: this.fontSize
|
|
4537
5484
|
};
|
|
4538
5485
|
}
|
|
4539
5486
|
onOptionsChange(listener) {
|
|
@@ -4544,6 +5491,7 @@ var NoteTool = class {
|
|
|
4544
5491
|
if (options.backgroundColor !== void 0) this.backgroundColor = options.backgroundColor;
|
|
4545
5492
|
if (options.textColor !== void 0) this.textColor = options.textColor;
|
|
4546
5493
|
if (options.size !== void 0) this.size = options.size;
|
|
5494
|
+
if (options.fontSize !== void 0) this.fontSize = options.fontSize;
|
|
4547
5495
|
this.notifyOptionsChange();
|
|
4548
5496
|
}
|
|
4549
5497
|
notifyOptionsChange() {
|
|
@@ -4555,14 +5503,13 @@ var NoteTool = class {
|
|
|
4555
5503
|
}
|
|
4556
5504
|
onPointerUp(state, ctx) {
|
|
4557
5505
|
let world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
4558
|
-
|
|
4559
|
-
world = snapPoint(world, ctx.gridSize);
|
|
4560
|
-
}
|
|
5506
|
+
world = smartSnap(world, ctx);
|
|
4561
5507
|
const note = createNote({
|
|
4562
5508
|
position: world,
|
|
4563
5509
|
size: { ...this.size },
|
|
4564
5510
|
backgroundColor: this.backgroundColor,
|
|
4565
5511
|
textColor: this.textColor,
|
|
5512
|
+
fontSize: this.fontSize,
|
|
4566
5513
|
layerId: ctx.activeLayerId ?? ""
|
|
4567
5514
|
});
|
|
4568
5515
|
ctx.store.add(note);
|
|
@@ -4612,9 +5559,7 @@ var TextTool = class {
|
|
|
4612
5559
|
}
|
|
4613
5560
|
onPointerUp(state, ctx) {
|
|
4614
5561
|
let world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
4615
|
-
|
|
4616
|
-
world = snapPoint(world, ctx.gridSize);
|
|
4617
|
-
}
|
|
5562
|
+
world = smartSnap(world, ctx);
|
|
4618
5563
|
const textEl = createText({
|
|
4619
5564
|
position: world,
|
|
4620
5565
|
fontSize: this.fontSize,
|
|
@@ -4647,8 +5592,12 @@ var ImageTool = class {
|
|
|
4647
5592
|
onPointerUp(state, ctx) {
|
|
4648
5593
|
if (!this.src) return;
|
|
4649
5594
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5595
|
+
const snapped = smartSnap(world, ctx);
|
|
4650
5596
|
const image = createImage({
|
|
4651
|
-
position:
|
|
5597
|
+
position: {
|
|
5598
|
+
x: snapped.x - this.size.w / 2,
|
|
5599
|
+
y: snapped.y - this.size.h / 2
|
|
5600
|
+
},
|
|
4652
5601
|
size: { ...this.size },
|
|
4653
5602
|
src: this.src
|
|
4654
5603
|
});
|
|
@@ -4785,7 +5734,7 @@ var ShapeTool = class {
|
|
|
4785
5734
|
for (const listener of this.optionListeners) listener();
|
|
4786
5735
|
}
|
|
4787
5736
|
snap(point, ctx) {
|
|
4788
|
-
return
|
|
5737
|
+
return smartSnap(point, ctx);
|
|
4789
5738
|
}
|
|
4790
5739
|
onKeyDown = (e) => {
|
|
4791
5740
|
if (e.key === "Shift") this.shiftHeld = true;
|
|
@@ -4795,6 +5744,398 @@ var ShapeTool = class {
|
|
|
4795
5744
|
};
|
|
4796
5745
|
};
|
|
4797
5746
|
|
|
5747
|
+
// src/tools/measure-tool.ts
|
|
5748
|
+
var MeasureTool = class {
|
|
5749
|
+
name = "measure";
|
|
5750
|
+
start = null;
|
|
5751
|
+
end = null;
|
|
5752
|
+
gridSize = 1;
|
|
5753
|
+
gridType;
|
|
5754
|
+
hexOrientation;
|
|
5755
|
+
feetPerCell;
|
|
5756
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
5757
|
+
constructor(options = {}) {
|
|
5758
|
+
this.feetPerCell = options.feetPerCell ?? 5;
|
|
5759
|
+
}
|
|
5760
|
+
getOptions() {
|
|
5761
|
+
return { feetPerCell: this.feetPerCell };
|
|
5762
|
+
}
|
|
5763
|
+
setOptions(options) {
|
|
5764
|
+
if (options.feetPerCell !== void 0) this.feetPerCell = options.feetPerCell;
|
|
5765
|
+
this.notifyOptionsChange();
|
|
5766
|
+
}
|
|
5767
|
+
onOptionsChange(listener) {
|
|
5768
|
+
this.optionListeners.add(listener);
|
|
5769
|
+
return () => this.optionListeners.delete(listener);
|
|
5770
|
+
}
|
|
5771
|
+
onPointerDown(state, ctx) {
|
|
5772
|
+
this.gridSize = ctx.gridSize ?? 1;
|
|
5773
|
+
this.gridType = ctx.gridType;
|
|
5774
|
+
this.hexOrientation = ctx.hexOrientation;
|
|
5775
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5776
|
+
this.start = this.snapToGrid(world, ctx);
|
|
5777
|
+
this.end = { ...this.start };
|
|
5778
|
+
}
|
|
5779
|
+
onPointerMove(state, ctx) {
|
|
5780
|
+
if (!this.start) return;
|
|
5781
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5782
|
+
this.end = this.snapToGrid(world, ctx);
|
|
5783
|
+
ctx.requestRender();
|
|
5784
|
+
}
|
|
5785
|
+
onPointerUp(_state, ctx) {
|
|
5786
|
+
if (!this.start) return;
|
|
5787
|
+
this.start = null;
|
|
5788
|
+
this.end = null;
|
|
5789
|
+
ctx.requestRender();
|
|
5790
|
+
}
|
|
5791
|
+
onDeactivate(_ctx) {
|
|
5792
|
+
this.start = null;
|
|
5793
|
+
this.end = null;
|
|
5794
|
+
}
|
|
5795
|
+
getMeasurement() {
|
|
5796
|
+
if (!this.start || !this.end) return null;
|
|
5797
|
+
const dx = this.end.x - this.start.x;
|
|
5798
|
+
const dy = this.end.y - this.start.y;
|
|
5799
|
+
const worldDistance = Math.sqrt(dx * dx + dy * dy);
|
|
5800
|
+
let cells;
|
|
5801
|
+
if (this.gridType === "hex" && this.hexOrientation) {
|
|
5802
|
+
cells = getHexDistance(this.start, this.end, this.gridSize, this.hexOrientation);
|
|
5803
|
+
} else {
|
|
5804
|
+
const snapUnit = this.gridSize;
|
|
5805
|
+
cells = worldDistance / snapUnit;
|
|
5806
|
+
}
|
|
5807
|
+
const feet = cells * this.feetPerCell;
|
|
5808
|
+
return {
|
|
5809
|
+
start: { ...this.start },
|
|
5810
|
+
end: { ...this.end },
|
|
5811
|
+
worldDistance,
|
|
5812
|
+
cells,
|
|
5813
|
+
feet
|
|
5814
|
+
};
|
|
5815
|
+
}
|
|
5816
|
+
renderOverlay(ctx) {
|
|
5817
|
+
const m = this.getMeasurement();
|
|
5818
|
+
if (!m) return;
|
|
5819
|
+
ctx.save();
|
|
5820
|
+
ctx.strokeStyle = "#FF5722";
|
|
5821
|
+
ctx.setLineDash([8, 4]);
|
|
5822
|
+
ctx.lineWidth = 2;
|
|
5823
|
+
ctx.beginPath();
|
|
5824
|
+
ctx.moveTo(m.start.x, m.start.y);
|
|
5825
|
+
ctx.lineTo(m.end.x, m.end.y);
|
|
5826
|
+
ctx.stroke();
|
|
5827
|
+
ctx.setLineDash([]);
|
|
5828
|
+
ctx.fillStyle = "#FF5722";
|
|
5829
|
+
const dotRadius = 4;
|
|
5830
|
+
ctx.beginPath();
|
|
5831
|
+
ctx.arc(m.start.x, m.start.y, dotRadius, 0, Math.PI * 2);
|
|
5832
|
+
ctx.fill();
|
|
5833
|
+
ctx.beginPath();
|
|
5834
|
+
ctx.arc(m.end.x, m.end.y, dotRadius, 0, Math.PI * 2);
|
|
5835
|
+
ctx.fill();
|
|
5836
|
+
const label = `${Math.round(m.feet)} ft`;
|
|
5837
|
+
const midX = (m.start.x + m.end.x) / 2;
|
|
5838
|
+
const midY = (m.start.y + m.end.y) / 2;
|
|
5839
|
+
ctx.font = "14px sans-serif";
|
|
5840
|
+
const metrics = ctx.measureText(label);
|
|
5841
|
+
const padX = 6;
|
|
5842
|
+
const padY = 4;
|
|
5843
|
+
const textH = 14;
|
|
5844
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
|
|
5845
|
+
ctx.beginPath();
|
|
5846
|
+
ctx.roundRect(
|
|
5847
|
+
midX - metrics.width / 2 - padX,
|
|
5848
|
+
midY - textH / 2 - padY,
|
|
5849
|
+
metrics.width + padX * 2,
|
|
5850
|
+
textH + padY * 2,
|
|
5851
|
+
4
|
|
5852
|
+
);
|
|
5853
|
+
ctx.fill();
|
|
5854
|
+
ctx.fillStyle = "#FFFFFF";
|
|
5855
|
+
ctx.textAlign = "center";
|
|
5856
|
+
ctx.textBaseline = "middle";
|
|
5857
|
+
ctx.fillText(label, midX, midY);
|
|
5858
|
+
ctx.restore();
|
|
5859
|
+
}
|
|
5860
|
+
snapToGrid(point, ctx) {
|
|
5861
|
+
if (!ctx.gridSize) return point;
|
|
5862
|
+
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
5863
|
+
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
5864
|
+
}
|
|
5865
|
+
if (ctx.gridType === "square") {
|
|
5866
|
+
return snapPoint(point, ctx.gridSize);
|
|
5867
|
+
}
|
|
5868
|
+
if (ctx.snapToGrid) {
|
|
5869
|
+
return snapPoint(point, ctx.gridSize);
|
|
5870
|
+
}
|
|
5871
|
+
return point;
|
|
5872
|
+
}
|
|
5873
|
+
notifyOptionsChange() {
|
|
5874
|
+
for (const listener of this.optionListeners) listener();
|
|
5875
|
+
}
|
|
5876
|
+
};
|
|
5877
|
+
|
|
5878
|
+
// src/tools/template-tool.ts
|
|
5879
|
+
var TemplateTool = class {
|
|
5880
|
+
name = "template";
|
|
5881
|
+
drawing = false;
|
|
5882
|
+
origin = { x: 0, y: 0 };
|
|
5883
|
+
current = { x: 0, y: 0 };
|
|
5884
|
+
gridSize = 1;
|
|
5885
|
+
gridType;
|
|
5886
|
+
hexOrientation;
|
|
5887
|
+
snapEnabled = false;
|
|
5888
|
+
templateShape;
|
|
5889
|
+
fillColor;
|
|
5890
|
+
strokeColor;
|
|
5891
|
+
strokeWidth;
|
|
5892
|
+
opacity;
|
|
5893
|
+
feetPerCell;
|
|
5894
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
5895
|
+
constructor(options = {}) {
|
|
5896
|
+
this.templateShape = options.templateShape ?? "circle";
|
|
5897
|
+
this.fillColor = options.fillColor ?? "rgba(255, 87, 34, 0.2)";
|
|
5898
|
+
this.strokeColor = options.strokeColor ?? "#FF5722";
|
|
5899
|
+
this.strokeWidth = options.strokeWidth ?? 2;
|
|
5900
|
+
this.opacity = options.opacity ?? 0.6;
|
|
5901
|
+
this.feetPerCell = options.feetPerCell ?? 5;
|
|
5902
|
+
}
|
|
5903
|
+
getOptions() {
|
|
5904
|
+
return {
|
|
5905
|
+
templateShape: this.templateShape,
|
|
5906
|
+
fillColor: this.fillColor,
|
|
5907
|
+
strokeColor: this.strokeColor,
|
|
5908
|
+
strokeWidth: this.strokeWidth,
|
|
5909
|
+
opacity: this.opacity,
|
|
5910
|
+
feetPerCell: this.feetPerCell
|
|
5911
|
+
};
|
|
5912
|
+
}
|
|
5913
|
+
setOptions(options) {
|
|
5914
|
+
if (options.templateShape !== void 0) this.templateShape = options.templateShape;
|
|
5915
|
+
if (options.fillColor !== void 0) this.fillColor = options.fillColor;
|
|
5916
|
+
if (options.strokeColor !== void 0) this.strokeColor = options.strokeColor;
|
|
5917
|
+
if (options.strokeWidth !== void 0) this.strokeWidth = options.strokeWidth;
|
|
5918
|
+
if (options.opacity !== void 0) this.opacity = options.opacity;
|
|
5919
|
+
if (options.feetPerCell !== void 0) this.feetPerCell = options.feetPerCell;
|
|
5920
|
+
this.notifyOptionsChange();
|
|
5921
|
+
}
|
|
5922
|
+
onOptionsChange(listener) {
|
|
5923
|
+
this.optionListeners.add(listener);
|
|
5924
|
+
return () => this.optionListeners.delete(listener);
|
|
5925
|
+
}
|
|
5926
|
+
onPointerDown(state, ctx) {
|
|
5927
|
+
this.drawing = true;
|
|
5928
|
+
this.gridSize = ctx.gridSize ?? 1;
|
|
5929
|
+
this.gridType = ctx.gridType;
|
|
5930
|
+
this.hexOrientation = ctx.hexOrientation;
|
|
5931
|
+
this.snapEnabled = !!ctx.gridType || (ctx.snapToGrid ?? false);
|
|
5932
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5933
|
+
this.origin = this.snapToGrid(world, ctx);
|
|
5934
|
+
this.current = { ...this.origin };
|
|
5935
|
+
}
|
|
5936
|
+
onPointerMove(state, ctx) {
|
|
5937
|
+
if (!this.drawing) return;
|
|
5938
|
+
this.current = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5939
|
+
ctx.requestRender();
|
|
5940
|
+
}
|
|
5941
|
+
onPointerUp(_state, ctx) {
|
|
5942
|
+
if (!this.drawing) return;
|
|
5943
|
+
this.drawing = false;
|
|
5944
|
+
const radius = this.computeRadius();
|
|
5945
|
+
if (radius <= 0) return;
|
|
5946
|
+
const angle = this.computeAngle();
|
|
5947
|
+
const gridSize = ctx.gridSize;
|
|
5948
|
+
const snapUnit = gridSize && gridSize > 0 ? ctx.gridType === "hex" ? Math.sqrt(3) * gridSize : gridSize : 0;
|
|
5949
|
+
const cells = snapUnit > 0 ? radius / snapUnit : 0;
|
|
5950
|
+
const radiusFeet = cells * this.feetPerCell;
|
|
5951
|
+
const element = createTemplate({
|
|
5952
|
+
position: { ...this.origin },
|
|
5953
|
+
templateShape: this.templateShape,
|
|
5954
|
+
radius,
|
|
5955
|
+
angle,
|
|
5956
|
+
fillColor: this.fillColor,
|
|
5957
|
+
strokeColor: this.strokeColor,
|
|
5958
|
+
strokeWidth: this.strokeWidth,
|
|
5959
|
+
opacity: this.opacity,
|
|
5960
|
+
feetPerCell: this.feetPerCell,
|
|
5961
|
+
radiusFeet: radiusFeet > 0 ? radiusFeet : void 0,
|
|
5962
|
+
layerId: ctx.activeLayerId ?? ""
|
|
5963
|
+
});
|
|
5964
|
+
ctx.store.add(element);
|
|
5965
|
+
ctx.requestRender();
|
|
5966
|
+
ctx.switchTool?.("select");
|
|
5967
|
+
}
|
|
5968
|
+
onDeactivate(_ctx) {
|
|
5969
|
+
this.drawing = false;
|
|
5970
|
+
this.origin = { x: 0, y: 0 };
|
|
5971
|
+
this.current = { x: 0, y: 0 };
|
|
5972
|
+
}
|
|
5973
|
+
renderOverlay(ctx) {
|
|
5974
|
+
if (!this.drawing) return;
|
|
5975
|
+
const radius = this.computeRadius();
|
|
5976
|
+
if (radius <= 0) return;
|
|
5977
|
+
if (this.gridType === "hex" && this.hexOrientation) {
|
|
5978
|
+
this.renderHexOverlay(ctx, radius);
|
|
5979
|
+
return;
|
|
5980
|
+
}
|
|
5981
|
+
this.renderGeometricOverlay(ctx, radius);
|
|
5982
|
+
}
|
|
5983
|
+
renderGeometricOverlay(ctx, radius) {
|
|
5984
|
+
const cx = this.origin.x;
|
|
5985
|
+
const cy = this.origin.y;
|
|
5986
|
+
const angle = this.computeAngle();
|
|
5987
|
+
ctx.save();
|
|
5988
|
+
ctx.globalAlpha = 0.4;
|
|
5989
|
+
ctx.fillStyle = this.fillColor;
|
|
5990
|
+
ctx.strokeStyle = this.strokeColor;
|
|
5991
|
+
ctx.lineWidth = this.strokeWidth;
|
|
5992
|
+
switch (this.templateShape) {
|
|
5993
|
+
case "circle":
|
|
5994
|
+
ctx.beginPath();
|
|
5995
|
+
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
|
|
5996
|
+
ctx.fill();
|
|
5997
|
+
ctx.stroke();
|
|
5998
|
+
break;
|
|
5999
|
+
case "square":
|
|
6000
|
+
ctx.fillRect(cx - radius / 2, cy - radius / 2, radius, radius);
|
|
6001
|
+
ctx.strokeRect(cx - radius / 2, cy - radius / 2, radius, radius);
|
|
6002
|
+
break;
|
|
6003
|
+
case "cone": {
|
|
6004
|
+
const halfAngle = Math.atan(0.5);
|
|
6005
|
+
ctx.beginPath();
|
|
6006
|
+
ctx.moveTo(cx, cy);
|
|
6007
|
+
ctx.arc(cx, cy, radius, angle - halfAngle, angle + halfAngle);
|
|
6008
|
+
ctx.closePath();
|
|
6009
|
+
ctx.fill();
|
|
6010
|
+
ctx.stroke();
|
|
6011
|
+
break;
|
|
6012
|
+
}
|
|
6013
|
+
case "line": {
|
|
6014
|
+
const halfW = radius / 12;
|
|
6015
|
+
const cos = Math.cos(angle);
|
|
6016
|
+
const sin = Math.sin(angle);
|
|
6017
|
+
const perpX = -sin * halfW;
|
|
6018
|
+
const perpY = cos * halfW;
|
|
6019
|
+
ctx.beginPath();
|
|
6020
|
+
ctx.moveTo(cx + perpX, cy + perpY);
|
|
6021
|
+
ctx.lineTo(cx + radius * cos + perpX, cy + radius * sin + perpY);
|
|
6022
|
+
ctx.lineTo(cx + radius * cos - perpX, cy + radius * sin - perpY);
|
|
6023
|
+
ctx.lineTo(cx - perpX, cy - perpY);
|
|
6024
|
+
ctx.closePath();
|
|
6025
|
+
ctx.fill();
|
|
6026
|
+
ctx.stroke();
|
|
6027
|
+
break;
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
ctx.restore();
|
|
6031
|
+
}
|
|
6032
|
+
renderHexOverlay(ctx, radius) {
|
|
6033
|
+
const orientation = this.hexOrientation;
|
|
6034
|
+
if (!orientation) return;
|
|
6035
|
+
const cellSize = this.gridSize;
|
|
6036
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
6037
|
+
const radiusCells = radius / snapUnit;
|
|
6038
|
+
const angle = this.computeAngle();
|
|
6039
|
+
const center = this.origin;
|
|
6040
|
+
let hexCells;
|
|
6041
|
+
switch (this.templateShape) {
|
|
6042
|
+
case "circle":
|
|
6043
|
+
hexCells = getHexCellsInRadius(center, radiusCells, cellSize, orientation);
|
|
6044
|
+
break;
|
|
6045
|
+
case "cone":
|
|
6046
|
+
hexCells = getHexCellsInCone(center, angle, radiusCells, cellSize, orientation);
|
|
6047
|
+
break;
|
|
6048
|
+
case "line":
|
|
6049
|
+
hexCells = getHexCellsInLine(center, angle, radiusCells, cellSize, orientation);
|
|
6050
|
+
break;
|
|
6051
|
+
case "square":
|
|
6052
|
+
hexCells = getHexCellsInSquare(center, radiusCells, cellSize, orientation);
|
|
6053
|
+
break;
|
|
6054
|
+
}
|
|
6055
|
+
ctx.save();
|
|
6056
|
+
ctx.globalAlpha = 0.4;
|
|
6057
|
+
ctx.beginPath();
|
|
6058
|
+
for (const cell of hexCells) {
|
|
6059
|
+
drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
|
|
6060
|
+
}
|
|
6061
|
+
ctx.fillStyle = this.fillColor;
|
|
6062
|
+
ctx.fill();
|
|
6063
|
+
ctx.beginPath();
|
|
6064
|
+
for (const cell of hexCells) {
|
|
6065
|
+
drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
|
|
6066
|
+
}
|
|
6067
|
+
ctx.strokeStyle = this.strokeColor;
|
|
6068
|
+
ctx.lineWidth = this.strokeWidth;
|
|
6069
|
+
ctx.stroke();
|
|
6070
|
+
if (this.templateShape === "cone" || this.templateShape === "line" || this.templateShape === "circle" || this.templateShape === "square") {
|
|
6071
|
+
ctx.globalAlpha = 0.5;
|
|
6072
|
+
ctx.beginPath();
|
|
6073
|
+
drawHexPath(ctx, center.x, center.y, cellSize, orientation);
|
|
6074
|
+
ctx.fillStyle = this.strokeColor;
|
|
6075
|
+
ctx.fill();
|
|
6076
|
+
ctx.strokeStyle = this.strokeColor;
|
|
6077
|
+
ctx.lineWidth = this.strokeWidth;
|
|
6078
|
+
ctx.stroke();
|
|
6079
|
+
}
|
|
6080
|
+
if (this.templateShape === "circle") {
|
|
6081
|
+
const feet = radiusCells * this.feetPerCell;
|
|
6082
|
+
if (feet > 0) {
|
|
6083
|
+
ctx.globalAlpha = 1;
|
|
6084
|
+
const label = `${Math.round(feet)} ft`;
|
|
6085
|
+
const fontSize = Math.max(10, Math.min(14, radius * 0.15));
|
|
6086
|
+
ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
|
|
6087
|
+
ctx.textAlign = "center";
|
|
6088
|
+
ctx.textBaseline = "bottom";
|
|
6089
|
+
const textX = center.x;
|
|
6090
|
+
const textY = center.y - 4;
|
|
6091
|
+
const metrics = ctx.measureText(label);
|
|
6092
|
+
const padX = 4;
|
|
6093
|
+
const padY = 2;
|
|
6094
|
+
const textW = metrics.width + padX * 2;
|
|
6095
|
+
const textH = fontSize + padY * 2;
|
|
6096
|
+
ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
|
|
6097
|
+
ctx.beginPath();
|
|
6098
|
+
ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
|
|
6099
|
+
ctx.fill();
|
|
6100
|
+
ctx.fillStyle = this.strokeColor;
|
|
6101
|
+
ctx.fillText(label, textX, textY - padY);
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
ctx.restore();
|
|
6105
|
+
}
|
|
6106
|
+
computeRadius() {
|
|
6107
|
+
const dx = this.current.x - this.origin.x;
|
|
6108
|
+
const dy = this.current.y - this.origin.y;
|
|
6109
|
+
const raw = Math.sqrt(dx * dx + dy * dy);
|
|
6110
|
+
if (this.snapEnabled && this.gridSize > 0) {
|
|
6111
|
+
const snapUnit = this.gridType === "hex" ? Math.sqrt(3) * this.gridSize : this.gridSize;
|
|
6112
|
+
return Math.max(snapUnit, Math.round(raw / snapUnit) * snapUnit);
|
|
6113
|
+
}
|
|
6114
|
+
return raw;
|
|
6115
|
+
}
|
|
6116
|
+
computeAngle() {
|
|
6117
|
+
const dx = this.current.x - this.origin.x;
|
|
6118
|
+
const dy = this.current.y - this.origin.y;
|
|
6119
|
+
return Math.atan2(dy, dx);
|
|
6120
|
+
}
|
|
6121
|
+
snapToGrid(point, ctx) {
|
|
6122
|
+
if (!ctx.gridSize) return point;
|
|
6123
|
+
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
6124
|
+
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
6125
|
+
}
|
|
6126
|
+
if (ctx.gridType === "square") {
|
|
6127
|
+
return snapPoint(point, ctx.gridSize);
|
|
6128
|
+
}
|
|
6129
|
+
if (ctx.snapToGrid) {
|
|
6130
|
+
return snapPoint(point, ctx.gridSize);
|
|
6131
|
+
}
|
|
6132
|
+
return point;
|
|
6133
|
+
}
|
|
6134
|
+
notifyOptionsChange() {
|
|
6135
|
+
for (const listener of this.optionListeners) listener();
|
|
6136
|
+
}
|
|
6137
|
+
};
|
|
6138
|
+
|
|
4798
6139
|
// src/history/layer-commands.ts
|
|
4799
6140
|
var CreateLayerCommand = class {
|
|
4800
6141
|
constructor(manager, layer) {
|
|
@@ -4836,7 +6177,7 @@ var UpdateLayerCommand = class {
|
|
|
4836
6177
|
};
|
|
4837
6178
|
|
|
4838
6179
|
// src/index.ts
|
|
4839
|
-
var VERSION = "0.
|
|
6180
|
+
var VERSION = "0.10.0";
|
|
4840
6181
|
export {
|
|
4841
6182
|
AddElementCommand,
|
|
4842
6183
|
ArrowTool,
|
|
@@ -4845,6 +6186,8 @@ export {
|
|
|
4845
6186
|
BatchCommand,
|
|
4846
6187
|
Camera,
|
|
4847
6188
|
CreateLayerCommand,
|
|
6189
|
+
DEFAULT_FONT_SIZE_PRESETS,
|
|
6190
|
+
DEFAULT_NOTE_FONT_SIZE,
|
|
4848
6191
|
ElementRenderer,
|
|
4849
6192
|
ElementStore,
|
|
4850
6193
|
EraserTool,
|
|
@@ -4855,14 +6198,17 @@ export {
|
|
|
4855
6198
|
ImageTool,
|
|
4856
6199
|
InputHandler,
|
|
4857
6200
|
LayerManager,
|
|
6201
|
+
MeasureTool,
|
|
4858
6202
|
NoteEditor,
|
|
4859
6203
|
NoteTool,
|
|
6204
|
+
NoteToolbar,
|
|
4860
6205
|
PencilTool,
|
|
4861
6206
|
Quadtree,
|
|
4862
6207
|
RemoveElementCommand,
|
|
4863
6208
|
RemoveLayerCommand,
|
|
4864
6209
|
SelectTool,
|
|
4865
6210
|
ShapeTool,
|
|
6211
|
+
TemplateTool,
|
|
4866
6212
|
TextTool,
|
|
4867
6213
|
ToolManager,
|
|
4868
6214
|
UpdateElementCommand,
|
|
@@ -4879,11 +6225,14 @@ export {
|
|
|
4879
6225
|
createNote,
|
|
4880
6226
|
createShape,
|
|
4881
6227
|
createStroke,
|
|
6228
|
+
createTemplate,
|
|
4882
6229
|
createText,
|
|
6230
|
+
drawHexPath,
|
|
4883
6231
|
exportImage,
|
|
4884
6232
|
exportState,
|
|
4885
6233
|
findBindTarget,
|
|
4886
6234
|
findBoundArrows,
|
|
6235
|
+
getActiveFormats,
|
|
4887
6236
|
getArrowBounds,
|
|
4888
6237
|
getArrowControlPoint,
|
|
4889
6238
|
getArrowMidpoint,
|
|
@@ -4892,10 +6241,23 @@ export {
|
|
|
4892
6241
|
getEdgeIntersection,
|
|
4893
6242
|
getElementBounds,
|
|
4894
6243
|
getElementCenter,
|
|
6244
|
+
getHexCellsInCone,
|
|
6245
|
+
getHexCellsInLine,
|
|
6246
|
+
getHexCellsInRadius,
|
|
6247
|
+
getHexCellsInSquare,
|
|
6248
|
+
getHexDistance,
|
|
4895
6249
|
isBindable,
|
|
4896
6250
|
isNearBezier,
|
|
4897
6251
|
parseState,
|
|
6252
|
+
sanitizeNoteHtml,
|
|
6253
|
+
setFontSize,
|
|
6254
|
+
smartSnap,
|
|
4898
6255
|
snapPoint,
|
|
6256
|
+
snapToHexCenter,
|
|
6257
|
+
toggleBold,
|
|
6258
|
+
toggleItalic,
|
|
6259
|
+
toggleStrikethrough,
|
|
6260
|
+
toggleUnderline,
|
|
4899
6261
|
unbindArrow,
|
|
4900
6262
|
updateBoundArrow
|
|
4901
6263
|
};
|