@fieldnotes/core 0.8.11 → 0.9.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 +973 -13
- package/dist/index.d.cts +183 -65
- package/dist/index.d.ts +183 -65
- package/dist/index.js +962 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -290,6 +290,30 @@ function snapPoint(point, gridSize) {
|
|
|
290
290
|
y: Math.round(point.y / gridSize) * gridSize || 0
|
|
291
291
|
};
|
|
292
292
|
}
|
|
293
|
+
function snapToHexCenter(point, cellSize, orientation) {
|
|
294
|
+
if (orientation === "pointy") {
|
|
295
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
296
|
+
const rowH = 1.5 * cellSize;
|
|
297
|
+
const row = Math.round(point.y / rowH);
|
|
298
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
299
|
+
const col = Math.round((point.x - offsetX) / hexW);
|
|
300
|
+
return { x: col * hexW + offsetX || 0, y: row * rowH || 0 };
|
|
301
|
+
} else {
|
|
302
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
303
|
+
const colW = 1.5 * cellSize;
|
|
304
|
+
const col = Math.round(point.x / colW);
|
|
305
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
306
|
+
const row = Math.round((point.y - offsetY) / hexH);
|
|
307
|
+
return { x: col * colW || 0, y: row * hexH + offsetY || 0 };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function smartSnap(point, ctx) {
|
|
311
|
+
if (!ctx.snapToGrid || !ctx.gridSize) return point;
|
|
312
|
+
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
313
|
+
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
314
|
+
}
|
|
315
|
+
return snapPoint(point, ctx.gridSize);
|
|
316
|
+
}
|
|
293
317
|
|
|
294
318
|
// src/core/auto-save.ts
|
|
295
319
|
var DEFAULT_KEY = "fieldnotes-autosave";
|
|
@@ -942,6 +966,9 @@ function getElementBounds(element) {
|
|
|
942
966
|
if (element.type === "arrow") {
|
|
943
967
|
return getArrowBoundsAnalytical(element.from, element.to, element.bend);
|
|
944
968
|
}
|
|
969
|
+
if (element.type === "template") {
|
|
970
|
+
return getTemplateBounds(element);
|
|
971
|
+
}
|
|
945
972
|
return null;
|
|
946
973
|
}
|
|
947
974
|
function getArrowBoundsAnalytical(from, to, bend) {
|
|
@@ -982,6 +1009,62 @@ function getArrowBoundsAnalytical(from, to, bend) {
|
|
|
982
1009
|
}
|
|
983
1010
|
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
984
1011
|
}
|
|
1012
|
+
function getTemplateBounds(el) {
|
|
1013
|
+
const { x: cx, y: cy } = el.position;
|
|
1014
|
+
const r = el.radius;
|
|
1015
|
+
switch (el.templateShape) {
|
|
1016
|
+
case "circle":
|
|
1017
|
+
return { x: cx - r, y: cy - r, w: 2 * r, h: 2 * r };
|
|
1018
|
+
case "square":
|
|
1019
|
+
return { x: cx - r / 2, y: cy - r / 2, w: r, h: r };
|
|
1020
|
+
case "cone": {
|
|
1021
|
+
const halfAngle = Math.atan(0.5);
|
|
1022
|
+
const tipX = cx;
|
|
1023
|
+
const tipY = cy;
|
|
1024
|
+
const leftX = cx + r * Math.cos(el.angle - halfAngle);
|
|
1025
|
+
const leftY = cy + r * Math.sin(el.angle - halfAngle);
|
|
1026
|
+
const rightX = cx + r * Math.cos(el.angle + halfAngle);
|
|
1027
|
+
const rightY = cy + r * Math.sin(el.angle + halfAngle);
|
|
1028
|
+
const farX = cx + r * Math.cos(el.angle);
|
|
1029
|
+
const farY = cy + r * Math.sin(el.angle);
|
|
1030
|
+
const xs = [tipX, leftX, rightX, farX];
|
|
1031
|
+
const ys = [tipY, leftY, rightY, farY];
|
|
1032
|
+
let minX = Infinity;
|
|
1033
|
+
let minY = Infinity;
|
|
1034
|
+
let maxX = -Infinity;
|
|
1035
|
+
let maxY = -Infinity;
|
|
1036
|
+
for (let i = 0; i < xs.length; i++) {
|
|
1037
|
+
const px = xs[i];
|
|
1038
|
+
const py = ys[i];
|
|
1039
|
+
if (px !== void 0 && px < minX) minX = px;
|
|
1040
|
+
if (px !== void 0 && px > maxX) maxX = px;
|
|
1041
|
+
if (py !== void 0 && py < minY) minY = py;
|
|
1042
|
+
if (py !== void 0 && py > maxY) maxY = py;
|
|
1043
|
+
}
|
|
1044
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1045
|
+
}
|
|
1046
|
+
case "line": {
|
|
1047
|
+
const halfW = r / 12;
|
|
1048
|
+
const cos = Math.cos(el.angle);
|
|
1049
|
+
const sin = Math.sin(el.angle);
|
|
1050
|
+
const perpX = -sin * halfW;
|
|
1051
|
+
const perpY = cos * halfW;
|
|
1052
|
+
const x0 = cx + perpX;
|
|
1053
|
+
const y0 = cy + perpY;
|
|
1054
|
+
const x1 = cx + r * cos + perpX;
|
|
1055
|
+
const y1 = cy + r * sin + perpY;
|
|
1056
|
+
const x2 = cx + r * cos - perpX;
|
|
1057
|
+
const y2 = cy + r * sin - perpY;
|
|
1058
|
+
const x3 = cx - perpX;
|
|
1059
|
+
const y3 = cy - perpY;
|
|
1060
|
+
const minX = Math.min(x0, x1, x2, x3);
|
|
1061
|
+
const minY = Math.min(y0, y1, y2, y3);
|
|
1062
|
+
const maxX = Math.max(x0, x1, x2, x3);
|
|
1063
|
+
const maxY = Math.max(y0, y1, y2, y3);
|
|
1064
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
985
1068
|
function boundsIntersect(a, b) {
|
|
986
1069
|
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
1070
|
}
|
|
@@ -1502,6 +1585,190 @@ function renderHexGridTiled(ctx, bounds, cellSize, tile) {
|
|
|
1502
1585
|
}
|
|
1503
1586
|
}
|
|
1504
1587
|
|
|
1588
|
+
// src/elements/hex-fill.ts
|
|
1589
|
+
function offsetToCube(col, row, orientation) {
|
|
1590
|
+
if (orientation === "pointy") {
|
|
1591
|
+
return { q: col - (row - (row & 1)) / 2, r: row };
|
|
1592
|
+
}
|
|
1593
|
+
return { q: col, r: row - (col - (col & 1)) / 2 };
|
|
1594
|
+
}
|
|
1595
|
+
function cubeToOffset(q, r, orientation) {
|
|
1596
|
+
if (orientation === "pointy") {
|
|
1597
|
+
return { col: q + (r - (r & 1)) / 2, row: r };
|
|
1598
|
+
}
|
|
1599
|
+
return { col: q, row: r + (q - (q & 1)) / 2 };
|
|
1600
|
+
}
|
|
1601
|
+
function offsetToPixel(col, row, cellSize, orientation) {
|
|
1602
|
+
if (orientation === "pointy") {
|
|
1603
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
1604
|
+
const rowH = 1.5 * cellSize;
|
|
1605
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1606
|
+
return { x: col * hexW + offsetX, y: row * rowH };
|
|
1607
|
+
}
|
|
1608
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
1609
|
+
const colW = 1.5 * cellSize;
|
|
1610
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1611
|
+
return { x: col * colW, y: row * hexH + offsetY };
|
|
1612
|
+
}
|
|
1613
|
+
function pixelToOffset(x, y, cellSize, orientation) {
|
|
1614
|
+
if (orientation === "pointy") {
|
|
1615
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
1616
|
+
const rowH = 1.5 * cellSize;
|
|
1617
|
+
const row = Math.round(y / rowH);
|
|
1618
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1619
|
+
return { col: Math.round((x - offsetX) / hexW), row };
|
|
1620
|
+
}
|
|
1621
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
1622
|
+
const colW = 1.5 * cellSize;
|
|
1623
|
+
const col = Math.round(x / colW);
|
|
1624
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1625
|
+
return { col, row: Math.round((y - offsetY) / hexH) };
|
|
1626
|
+
}
|
|
1627
|
+
function enumerateHexRing(centerQ, centerR, n, orientation, cellSize) {
|
|
1628
|
+
const cells = [];
|
|
1629
|
+
for (let dq = -n; dq <= n; dq++) {
|
|
1630
|
+
const rMin = Math.max(-n, -dq - n);
|
|
1631
|
+
const rMax = Math.min(n, -dq + n);
|
|
1632
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
1633
|
+
const absQ = centerQ + dq;
|
|
1634
|
+
const absR = centerR + dr;
|
|
1635
|
+
const off = cubeToOffset(absQ, absR, orientation);
|
|
1636
|
+
cells.push(offsetToPixel(off.col, off.row, cellSize, orientation));
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
return cells;
|
|
1640
|
+
}
|
|
1641
|
+
function getHexDistance(a, b, cellSize, orientation) {
|
|
1642
|
+
const offA = pixelToOffset(a.x, a.y, cellSize, orientation);
|
|
1643
|
+
const offB = pixelToOffset(b.x, b.y, cellSize, orientation);
|
|
1644
|
+
const cubeA = offsetToCube(offA.col, offA.row, orientation);
|
|
1645
|
+
const cubeB = offsetToCube(offB.col, offB.row, orientation);
|
|
1646
|
+
const dq = cubeA.q - cubeB.q;
|
|
1647
|
+
const dr = cubeA.r - cubeB.r;
|
|
1648
|
+
const ds = -dq - dr;
|
|
1649
|
+
return Math.max(Math.abs(dq), Math.abs(dr), Math.abs(ds));
|
|
1650
|
+
}
|
|
1651
|
+
function getHexCellsInRadius(center, radiusCells, cellSize, orientation) {
|
|
1652
|
+
const n = Math.round(radiusCells);
|
|
1653
|
+
const off = pixelToOffset(center.x, center.y, cellSize, orientation);
|
|
1654
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
1655
|
+
if (n <= 0) {
|
|
1656
|
+
return [offsetToPixel(off.col, off.row, cellSize, orientation)];
|
|
1657
|
+
}
|
|
1658
|
+
return enumerateHexRing(cube.q, cube.r, n, orientation, cellSize);
|
|
1659
|
+
}
|
|
1660
|
+
function getHexCellsInCone(center, angle, radiusCells, cellSize, orientation) {
|
|
1661
|
+
const n = Math.round(radiusCells);
|
|
1662
|
+
const off = pixelToOffset(center.x, center.y, cellSize, orientation);
|
|
1663
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
1664
|
+
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
1665
|
+
if (n <= 0) return [centerPixel];
|
|
1666
|
+
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
1667
|
+
const step = Math.PI / 3;
|
|
1668
|
+
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
1669
|
+
const halfAngle = Math.PI / 6 + 1e-6;
|
|
1670
|
+
const cells = [centerPixel];
|
|
1671
|
+
for (let dq = -n; dq <= n; dq++) {
|
|
1672
|
+
const rMin = Math.max(-n, -dq - n);
|
|
1673
|
+
const rMax = Math.min(n, -dq + n);
|
|
1674
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
1675
|
+
if (dq === 0 && dr === 0) continue;
|
|
1676
|
+
const absQ = cube.q + dq;
|
|
1677
|
+
const absR = cube.r + dr;
|
|
1678
|
+
const pixel = offsetToPixel(
|
|
1679
|
+
cubeToOffset(absQ, absR, orientation).col,
|
|
1680
|
+
cubeToOffset(absQ, absR, orientation).row,
|
|
1681
|
+
cellSize,
|
|
1682
|
+
orientation
|
|
1683
|
+
);
|
|
1684
|
+
const dx = pixel.x - centerPixel.x;
|
|
1685
|
+
const dy = pixel.y - centerPixel.y;
|
|
1686
|
+
let diff = Math.atan2(dy, dx) - snappedAngle;
|
|
1687
|
+
if (diff > Math.PI) diff -= 2 * Math.PI;
|
|
1688
|
+
if (diff < -Math.PI) diff += 2 * Math.PI;
|
|
1689
|
+
if (Math.abs(diff) <= halfAngle) {
|
|
1690
|
+
cells.push(pixel);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
return cells;
|
|
1695
|
+
}
|
|
1696
|
+
function getHexCellsInLine(center, angle, radiusCells, cellSize, orientation) {
|
|
1697
|
+
const n = Math.round(radiusCells);
|
|
1698
|
+
const off = pixelToOffset(center.x, center.y, cellSize, orientation);
|
|
1699
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
1700
|
+
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
1701
|
+
if (n <= 0) return [centerPixel];
|
|
1702
|
+
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
1703
|
+
const step = Math.PI / 3;
|
|
1704
|
+
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
1705
|
+
const cos = Math.cos(snappedAngle);
|
|
1706
|
+
const sin = Math.sin(snappedAngle);
|
|
1707
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
1708
|
+
const lineLength = n * snapUnit;
|
|
1709
|
+
const halfWidth = snapUnit * 0.5 + 1e-6;
|
|
1710
|
+
const cells = [];
|
|
1711
|
+
for (let dq = -n; dq <= n; dq++) {
|
|
1712
|
+
const rMin = Math.max(-n, -dq - n);
|
|
1713
|
+
const rMax = Math.min(n, -dq + n);
|
|
1714
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
1715
|
+
const absQ = cube.q + dq;
|
|
1716
|
+
const absR = cube.r + dr;
|
|
1717
|
+
const pixel = offsetToPixel(
|
|
1718
|
+
cubeToOffset(absQ, absR, orientation).col,
|
|
1719
|
+
cubeToOffset(absQ, absR, orientation).row,
|
|
1720
|
+
cellSize,
|
|
1721
|
+
orientation
|
|
1722
|
+
);
|
|
1723
|
+
const dx = pixel.x - centerPixel.x;
|
|
1724
|
+
const dy = pixel.y - centerPixel.y;
|
|
1725
|
+
const along = dx * cos + dy * sin;
|
|
1726
|
+
const perp = Math.abs(-dx * sin + dy * cos);
|
|
1727
|
+
if (along >= -snapUnit * 0.1 && along <= lineLength + snapUnit * 0.1 && perp <= halfWidth) {
|
|
1728
|
+
cells.push(pixel);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
return cells;
|
|
1733
|
+
}
|
|
1734
|
+
function getHexCellsInSquare(center, radiusCells, cellSize, orientation) {
|
|
1735
|
+
const n = Math.round(radiusCells);
|
|
1736
|
+
const off = pixelToOffset(center.x, center.y, cellSize, orientation);
|
|
1737
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
1738
|
+
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
1739
|
+
if (n <= 0) return [centerPixel];
|
|
1740
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
1741
|
+
const halfSide = n * snapUnit / 2;
|
|
1742
|
+
const cells = [];
|
|
1743
|
+
for (let dq = -n; dq <= n; dq++) {
|
|
1744
|
+
const rMin = Math.max(-n, -dq - n);
|
|
1745
|
+
const rMax = Math.min(n, -dq + n);
|
|
1746
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
1747
|
+
const absQ = cube.q + dq;
|
|
1748
|
+
const absR = cube.r + dr;
|
|
1749
|
+
const pixel = offsetToPixel(
|
|
1750
|
+
cubeToOffset(absQ, absR, orientation).col,
|
|
1751
|
+
cubeToOffset(absQ, absR, orientation).row,
|
|
1752
|
+
cellSize,
|
|
1753
|
+
orientation
|
|
1754
|
+
);
|
|
1755
|
+
if (Math.abs(pixel.x - centerPixel.x) <= halfSide && Math.abs(pixel.y - centerPixel.y) <= halfSide) {
|
|
1756
|
+
cells.push(pixel);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
return cells;
|
|
1761
|
+
}
|
|
1762
|
+
function drawHexPath(ctx, cx, cy, cellSize, orientation) {
|
|
1763
|
+
const angleOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
1764
|
+
ctx.moveTo(cx + cellSize * Math.cos(angleOffset), cy + cellSize * Math.sin(angleOffset));
|
|
1765
|
+
for (let i = 1; i < 6; i++) {
|
|
1766
|
+
const a = angleOffset + Math.PI / 3 * i;
|
|
1767
|
+
ctx.lineTo(cx + cellSize * Math.cos(a), cy + cellSize * Math.sin(a));
|
|
1768
|
+
}
|
|
1769
|
+
ctx.closePath();
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1505
1772
|
// src/elements/element-renderer.ts
|
|
1506
1773
|
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
|
|
1507
1774
|
var ARROWHEAD_LENGTH = 12;
|
|
@@ -1546,6 +1813,9 @@ var ElementRenderer = class {
|
|
|
1546
1813
|
case "grid":
|
|
1547
1814
|
this.renderGrid(ctx, element);
|
|
1548
1815
|
break;
|
|
1816
|
+
case "template":
|
|
1817
|
+
this.renderTemplate(ctx, element);
|
|
1818
|
+
break;
|
|
1549
1819
|
}
|
|
1550
1820
|
}
|
|
1551
1821
|
renderStroke(ctx, stroke) {
|
|
@@ -1733,6 +2003,147 @@ var ElementRenderer = class {
|
|
|
1733
2003
|
);
|
|
1734
2004
|
}
|
|
1735
2005
|
}
|
|
2006
|
+
renderTemplate(ctx, template) {
|
|
2007
|
+
const grid = this.store?.getElementsByType("grid")[0];
|
|
2008
|
+
if (grid && grid.gridType === "hex") {
|
|
2009
|
+
this.renderHexTemplate(ctx, template, grid.cellSize, grid.hexOrientation);
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
this.renderGeometricTemplate(ctx, template);
|
|
2013
|
+
}
|
|
2014
|
+
renderGeometricTemplate(ctx, template) {
|
|
2015
|
+
const { x: cx, y: cy } = template.position;
|
|
2016
|
+
const r = template.radius;
|
|
2017
|
+
ctx.save();
|
|
2018
|
+
ctx.globalAlpha = template.opacity;
|
|
2019
|
+
ctx.fillStyle = template.fillColor;
|
|
2020
|
+
ctx.strokeStyle = template.strokeColor;
|
|
2021
|
+
ctx.lineWidth = template.strokeWidth;
|
|
2022
|
+
switch (template.templateShape) {
|
|
2023
|
+
case "circle":
|
|
2024
|
+
ctx.beginPath();
|
|
2025
|
+
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
2026
|
+
ctx.fill();
|
|
2027
|
+
ctx.stroke();
|
|
2028
|
+
if (template.radiusFeet != null && template.radiusFeet > 0) {
|
|
2029
|
+
this.renderRadiusMarker(ctx, cx, cy, r, template.radiusFeet);
|
|
2030
|
+
}
|
|
2031
|
+
break;
|
|
2032
|
+
case "square":
|
|
2033
|
+
ctx.fillRect(cx - r / 2, cy - r / 2, r, r);
|
|
2034
|
+
ctx.strokeRect(cx - r / 2, cy - r / 2, r, r);
|
|
2035
|
+
break;
|
|
2036
|
+
case "cone": {
|
|
2037
|
+
const halfAngle = Math.atan(0.5);
|
|
2038
|
+
ctx.beginPath();
|
|
2039
|
+
ctx.moveTo(cx, cy);
|
|
2040
|
+
ctx.arc(cx, cy, r, template.angle - halfAngle, template.angle + halfAngle);
|
|
2041
|
+
ctx.closePath();
|
|
2042
|
+
ctx.fill();
|
|
2043
|
+
ctx.stroke();
|
|
2044
|
+
break;
|
|
2045
|
+
}
|
|
2046
|
+
case "line": {
|
|
2047
|
+
const halfW = r / 12;
|
|
2048
|
+
const cos = Math.cos(template.angle);
|
|
2049
|
+
const sin = Math.sin(template.angle);
|
|
2050
|
+
const perpX = -sin * halfW;
|
|
2051
|
+
const perpY = cos * halfW;
|
|
2052
|
+
ctx.beginPath();
|
|
2053
|
+
ctx.moveTo(cx + perpX, cy + perpY);
|
|
2054
|
+
ctx.lineTo(cx + r * cos + perpX, cy + r * sin + perpY);
|
|
2055
|
+
ctx.lineTo(cx + r * cos - perpX, cy + r * sin - perpY);
|
|
2056
|
+
ctx.lineTo(cx - perpX, cy - perpY);
|
|
2057
|
+
ctx.closePath();
|
|
2058
|
+
ctx.fill();
|
|
2059
|
+
ctx.stroke();
|
|
2060
|
+
break;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
ctx.restore();
|
|
2064
|
+
}
|
|
2065
|
+
renderHexTemplate(ctx, template, cellSize, orientation) {
|
|
2066
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
2067
|
+
const radiusCells = template.radius / snapUnit;
|
|
2068
|
+
const center = template.position;
|
|
2069
|
+
let cells;
|
|
2070
|
+
switch (template.templateShape) {
|
|
2071
|
+
case "circle":
|
|
2072
|
+
cells = getHexCellsInRadius(center, radiusCells, cellSize, orientation);
|
|
2073
|
+
break;
|
|
2074
|
+
case "cone":
|
|
2075
|
+
cells = getHexCellsInCone(center, template.angle, radiusCells, cellSize, orientation);
|
|
2076
|
+
break;
|
|
2077
|
+
case "line":
|
|
2078
|
+
cells = getHexCellsInLine(center, template.angle, radiusCells, cellSize, orientation);
|
|
2079
|
+
break;
|
|
2080
|
+
case "square":
|
|
2081
|
+
cells = getHexCellsInSquare(center, radiusCells, cellSize, orientation);
|
|
2082
|
+
break;
|
|
2083
|
+
}
|
|
2084
|
+
ctx.save();
|
|
2085
|
+
ctx.globalAlpha = template.opacity;
|
|
2086
|
+
ctx.beginPath();
|
|
2087
|
+
for (const cell of cells) {
|
|
2088
|
+
drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
|
|
2089
|
+
}
|
|
2090
|
+
ctx.fillStyle = template.fillColor;
|
|
2091
|
+
ctx.fill();
|
|
2092
|
+
ctx.beginPath();
|
|
2093
|
+
for (const cell of cells) {
|
|
2094
|
+
drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
|
|
2095
|
+
}
|
|
2096
|
+
ctx.strokeStyle = template.strokeColor;
|
|
2097
|
+
ctx.lineWidth = template.strokeWidth;
|
|
2098
|
+
ctx.stroke();
|
|
2099
|
+
{
|
|
2100
|
+
ctx.globalAlpha = Math.min(template.opacity + 0.1, 1);
|
|
2101
|
+
ctx.beginPath();
|
|
2102
|
+
drawHexPath(ctx, center.x, center.y, cellSize, orientation);
|
|
2103
|
+
ctx.fillStyle = template.strokeColor;
|
|
2104
|
+
ctx.fill();
|
|
2105
|
+
ctx.strokeStyle = template.strokeColor;
|
|
2106
|
+
ctx.lineWidth = template.strokeWidth;
|
|
2107
|
+
ctx.stroke();
|
|
2108
|
+
}
|
|
2109
|
+
if (template.templateShape === "circle" && template.radiusFeet != null && template.radiusFeet > 0) {
|
|
2110
|
+
const r = template.radius;
|
|
2111
|
+
this.renderRadiusMarker(ctx, center.x, center.y, r, template.radiusFeet);
|
|
2112
|
+
}
|
|
2113
|
+
ctx.restore();
|
|
2114
|
+
}
|
|
2115
|
+
renderRadiusMarker(ctx, cx, cy, r, feet) {
|
|
2116
|
+
const markerColor = ctx.strokeStyle;
|
|
2117
|
+
ctx.save();
|
|
2118
|
+
ctx.globalAlpha = 1;
|
|
2119
|
+
ctx.beginPath();
|
|
2120
|
+
ctx.setLineDash([4, 4]);
|
|
2121
|
+
ctx.strokeStyle = markerColor;
|
|
2122
|
+
ctx.lineWidth = 1.5;
|
|
2123
|
+
ctx.moveTo(cx, cy);
|
|
2124
|
+
ctx.lineTo(cx + r, cy);
|
|
2125
|
+
ctx.stroke();
|
|
2126
|
+
ctx.setLineDash([]);
|
|
2127
|
+
const label = `${Math.round(feet)} ft`;
|
|
2128
|
+
const fontSize = Math.max(10, Math.min(14, r * 0.15));
|
|
2129
|
+
ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
|
|
2130
|
+
ctx.textAlign = "center";
|
|
2131
|
+
ctx.textBaseline = "bottom";
|
|
2132
|
+
const textX = cx + r / 2;
|
|
2133
|
+
const textY = cy - 4;
|
|
2134
|
+
const metrics = ctx.measureText(label);
|
|
2135
|
+
const padX = 4;
|
|
2136
|
+
const padY = 2;
|
|
2137
|
+
const textW = metrics.width + padX * 2;
|
|
2138
|
+
const textH = fontSize + padY * 2;
|
|
2139
|
+
ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
|
|
2140
|
+
ctx.beginPath();
|
|
2141
|
+
ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
|
|
2142
|
+
ctx.fill();
|
|
2143
|
+
ctx.fillStyle = markerColor;
|
|
2144
|
+
ctx.fillText(label, textX, textY - padY);
|
|
2145
|
+
ctx.restore();
|
|
2146
|
+
}
|
|
1736
2147
|
renderImage(ctx, image) {
|
|
1737
2148
|
const img = this.getImage(image.src);
|
|
1738
2149
|
if (!img) return;
|
|
@@ -2240,6 +2651,25 @@ function createText(input) {
|
|
|
2240
2651
|
textAlign: input.textAlign ?? "left"
|
|
2241
2652
|
};
|
|
2242
2653
|
}
|
|
2654
|
+
function createTemplate(input) {
|
|
2655
|
+
return {
|
|
2656
|
+
id: createId("template"),
|
|
2657
|
+
type: "template",
|
|
2658
|
+
position: input.position,
|
|
2659
|
+
zIndex: input.zIndex ?? 0,
|
|
2660
|
+
locked: input.locked ?? false,
|
|
2661
|
+
layerId: input.layerId ?? "",
|
|
2662
|
+
templateShape: input.templateShape,
|
|
2663
|
+
radius: input.radius,
|
|
2664
|
+
angle: input.angle ?? 0,
|
|
2665
|
+
fillColor: input.fillColor ?? "rgba(255, 87, 34, 0.2)",
|
|
2666
|
+
strokeColor: input.strokeColor ?? "#FF5722",
|
|
2667
|
+
strokeWidth: input.strokeWidth ?? 2,
|
|
2668
|
+
opacity: input.opacity ?? 0.6,
|
|
2669
|
+
feetPerCell: input.feetPerCell,
|
|
2670
|
+
radiusFeet: input.radiusFeet
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2243
2673
|
|
|
2244
2674
|
// src/canvas/export-image.ts
|
|
2245
2675
|
function getStrokeBounds(el) {
|
|
@@ -2276,6 +2706,11 @@ function getElementRect(el) {
|
|
|
2276
2706
|
}
|
|
2277
2707
|
case "grid":
|
|
2278
2708
|
return null;
|
|
2709
|
+
case "template": {
|
|
2710
|
+
const bounds = getElementBounds(el);
|
|
2711
|
+
if (!bounds) return null;
|
|
2712
|
+
return bounds;
|
|
2713
|
+
}
|
|
2279
2714
|
case "note":
|
|
2280
2715
|
case "image":
|
|
2281
2716
|
case "html":
|
|
@@ -3020,7 +3455,15 @@ var RenderLoop = class {
|
|
|
3020
3455
|
ctx.save();
|
|
3021
3456
|
ctx.scale(dpr, dpr);
|
|
3022
3457
|
this.renderer.setCanvasSize(cssWidth, cssHeight);
|
|
3023
|
-
this.
|
|
3458
|
+
const hasGridElement = this.store.getElementsByType("grid").length > 0;
|
|
3459
|
+
if (hasGridElement) {
|
|
3460
|
+
ctx.save();
|
|
3461
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
3462
|
+
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
|
3463
|
+
ctx.restore();
|
|
3464
|
+
} else {
|
|
3465
|
+
this.background.render(ctx, this.camera);
|
|
3466
|
+
}
|
|
3024
3467
|
ctx.save();
|
|
3025
3468
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
3026
3469
|
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
@@ -3281,16 +3724,19 @@ var Viewport = class {
|
|
|
3281
3724
|
});
|
|
3282
3725
|
this.unsubStore = [
|
|
3283
3726
|
this.store.on("add", (el) => {
|
|
3727
|
+
if (el.type === "grid") this.syncGridContext();
|
|
3284
3728
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
3285
3729
|
this.requestRender();
|
|
3286
3730
|
}),
|
|
3287
3731
|
this.store.on("remove", (el) => {
|
|
3732
|
+
if (el.type === "grid") this.syncGridContext();
|
|
3288
3733
|
this.unbindArrowsFrom(el);
|
|
3289
3734
|
this.domNodeManager.removeDomNode(el.id);
|
|
3290
3735
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
3291
3736
|
this.requestRender();
|
|
3292
3737
|
}),
|
|
3293
3738
|
this.store.on("update", ({ previous, current }) => {
|
|
3739
|
+
if (current.type === "grid") this.syncGridContext();
|
|
3294
3740
|
this.renderLoop.markLayerDirty(current.layerId);
|
|
3295
3741
|
if (previous.layerId !== current.layerId) {
|
|
3296
3742
|
this.renderLoop.markLayerDirty(previous.layerId);
|
|
@@ -3300,6 +3746,7 @@ var Viewport = class {
|
|
|
3300
3746
|
this.store.on("clear", () => {
|
|
3301
3747
|
this.domNodeManager.clearDomNodes();
|
|
3302
3748
|
this.renderLoop.markAllLayersDirty();
|
|
3749
|
+
this.syncGridContext();
|
|
3303
3750
|
this.requestRender();
|
|
3304
3751
|
})
|
|
3305
3752
|
];
|
|
@@ -3313,6 +3760,7 @@ var Viewport = class {
|
|
|
3313
3760
|
this.observeResize();
|
|
3314
3761
|
this.syncCanvasSize();
|
|
3315
3762
|
this.renderLoop.start();
|
|
3763
|
+
this.syncGridContext();
|
|
3316
3764
|
}
|
|
3317
3765
|
camera;
|
|
3318
3766
|
store;
|
|
@@ -3629,6 +4077,18 @@ var Viewport = class {
|
|
|
3629
4077
|
this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
|
|
3630
4078
|
this.requestRender();
|
|
3631
4079
|
}
|
|
4080
|
+
syncGridContext() {
|
|
4081
|
+
const grid = this.store.getElementsByType("grid")[0];
|
|
4082
|
+
if (grid) {
|
|
4083
|
+
this.toolContext.gridSize = grid.cellSize;
|
|
4084
|
+
this.toolContext.gridType = grid.gridType;
|
|
4085
|
+
this.toolContext.hexOrientation = grid.hexOrientation;
|
|
4086
|
+
} else {
|
|
4087
|
+
this.toolContext.gridSize = this._gridSize;
|
|
4088
|
+
this.toolContext.gridType = void 0;
|
|
4089
|
+
this.toolContext.hexOrientation = void 0;
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
3632
4092
|
observeResize() {
|
|
3633
4093
|
if (typeof ResizeObserver === "undefined") return;
|
|
3634
4094
|
this.resizeObserver = new ResizeObserver(() => this.syncCanvasSize());
|
|
@@ -4003,7 +4463,7 @@ var SelectTool = class {
|
|
|
4003
4463
|
ctx.setCursor?.("default");
|
|
4004
4464
|
}
|
|
4005
4465
|
snap(point, ctx) {
|
|
4006
|
-
return
|
|
4466
|
+
return smartSnap(point, ctx);
|
|
4007
4467
|
}
|
|
4008
4468
|
onPointerDown(state, ctx) {
|
|
4009
4469
|
this.ctx = ctx;
|
|
@@ -4020,6 +4480,12 @@ var SelectTool = class {
|
|
|
4020
4480
|
ctx.requestRender();
|
|
4021
4481
|
return;
|
|
4022
4482
|
}
|
|
4483
|
+
const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
|
|
4484
|
+
if (templateResizeHit) {
|
|
4485
|
+
this.mode = { type: "resizing-template", elementId: templateResizeHit };
|
|
4486
|
+
ctx.requestRender();
|
|
4487
|
+
return;
|
|
4488
|
+
}
|
|
4023
4489
|
const resizeHit = this.hitTestResizeHandle(world, ctx);
|
|
4024
4490
|
if (resizeHit) {
|
|
4025
4491
|
const el = ctx.store.getById(resizeHit.elementId);
|
|
@@ -4054,6 +4520,11 @@ var SelectTool = class {
|
|
|
4054
4520
|
applyArrowHandleDrag(this.mode.handle, this.mode.elementId, world, ctx);
|
|
4055
4521
|
return;
|
|
4056
4522
|
}
|
|
4523
|
+
if (this.mode.type === "resizing-template") {
|
|
4524
|
+
ctx.setCursor?.("nwse-resize");
|
|
4525
|
+
this.handleTemplateResize(world, ctx);
|
|
4526
|
+
return;
|
|
4527
|
+
}
|
|
4057
4528
|
if (this.mode.type === "resizing") {
|
|
4058
4529
|
ctx.setCursor?.(HANDLE_CURSORS[this.mode.handle]);
|
|
4059
4530
|
this.handleResize(world, ctx);
|
|
@@ -4077,6 +4548,16 @@ var SelectTool = class {
|
|
|
4077
4548
|
from: { x: el.from.x + dx, y: el.from.y + dy },
|
|
4078
4549
|
to: { x: el.to.x + dx, y: el.to.y + dy }
|
|
4079
4550
|
});
|
|
4551
|
+
} else if (ctx.gridType && "size" in el) {
|
|
4552
|
+
const centerX = el.position.x + el.size.w / 2 + dx;
|
|
4553
|
+
const centerY = el.position.y + el.size.h / 2 + dy;
|
|
4554
|
+
const snappedCenter = this.snap({ x: centerX, y: centerY }, ctx);
|
|
4555
|
+
ctx.store.update(id, {
|
|
4556
|
+
position: {
|
|
4557
|
+
x: snappedCenter.x - el.size.w / 2,
|
|
4558
|
+
y: snappedCenter.y - el.size.h / 2
|
|
4559
|
+
}
|
|
4560
|
+
});
|
|
4080
4561
|
} else {
|
|
4081
4562
|
ctx.store.update(id, {
|
|
4082
4563
|
position: { x: el.position.x + dx, y: el.position.y + dy }
|
|
@@ -4151,6 +4632,11 @@ var SelectTool = class {
|
|
|
4151
4632
|
ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
|
|
4152
4633
|
return;
|
|
4153
4634
|
}
|
|
4635
|
+
const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
|
|
4636
|
+
if (templateResizeHit) {
|
|
4637
|
+
ctx.setCursor?.("nwse-resize");
|
|
4638
|
+
return;
|
|
4639
|
+
}
|
|
4154
4640
|
const resizeHit = this.hitTestResizeHandle(world, ctx);
|
|
4155
4641
|
if (resizeHit) {
|
|
4156
4642
|
ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
|
|
@@ -4287,6 +4773,24 @@ var SelectTool = class {
|
|
|
4287
4773
|
);
|
|
4288
4774
|
}
|
|
4289
4775
|
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
4776
|
+
} else if (el.type === "template") {
|
|
4777
|
+
canvasCtx.setLineDash([]);
|
|
4778
|
+
canvasCtx.fillStyle = "#ffffff";
|
|
4779
|
+
const hx = bounds.x + bounds.w;
|
|
4780
|
+
const hy = bounds.y + bounds.h;
|
|
4781
|
+
canvasCtx.fillRect(
|
|
4782
|
+
hx - handleWorldSize / 2,
|
|
4783
|
+
hy - handleWorldSize / 2,
|
|
4784
|
+
handleWorldSize,
|
|
4785
|
+
handleWorldSize
|
|
4786
|
+
);
|
|
4787
|
+
canvasCtx.strokeRect(
|
|
4788
|
+
hx - handleWorldSize / 2,
|
|
4789
|
+
hy - handleWorldSize / 2,
|
|
4790
|
+
handleWorldSize,
|
|
4791
|
+
handleWorldSize
|
|
4792
|
+
);
|
|
4793
|
+
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
4290
4794
|
}
|
|
4291
4795
|
}
|
|
4292
4796
|
canvasCtx.restore();
|
|
@@ -4311,6 +4815,43 @@ var SelectTool = class {
|
|
|
4311
4815
|
}
|
|
4312
4816
|
canvasCtx.restore();
|
|
4313
4817
|
}
|
|
4818
|
+
hitTestTemplateResizeHandle(world, ctx) {
|
|
4819
|
+
if (this._selectedIds.length === 0) return null;
|
|
4820
|
+
const zoom = ctx.camera.zoom;
|
|
4821
|
+
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
4822
|
+
for (const id of this._selectedIds) {
|
|
4823
|
+
const el = ctx.store.getById(id);
|
|
4824
|
+
if (!el || el.type !== "template") continue;
|
|
4825
|
+
const bounds = getElementBounds(el);
|
|
4826
|
+
if (!bounds) continue;
|
|
4827
|
+
const hx = bounds.x + bounds.w;
|
|
4828
|
+
const hy = bounds.y + bounds.h;
|
|
4829
|
+
if (Math.abs(world.x - hx) <= handleHalf && Math.abs(world.y - hy) <= handleHalf) {
|
|
4830
|
+
return id;
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
return null;
|
|
4834
|
+
}
|
|
4835
|
+
handleTemplateResize(world, ctx) {
|
|
4836
|
+
if (this.mode.type !== "resizing-template") return;
|
|
4837
|
+
const el = ctx.store.getById(this.mode.elementId);
|
|
4838
|
+
if (!el || el.type !== "template" || el.locked) return;
|
|
4839
|
+
const dx = world.x - el.position.x;
|
|
4840
|
+
const dy = world.y - el.position.y;
|
|
4841
|
+
let newRadius = Math.sqrt(dx * dx + dy * dy);
|
|
4842
|
+
if (ctx.snapToGrid && ctx.gridSize && ctx.gridSize > 0) {
|
|
4843
|
+
const snapUnit = ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize;
|
|
4844
|
+
newRadius = Math.max(snapUnit, Math.round(newRadius / snapUnit) * snapUnit);
|
|
4845
|
+
}
|
|
4846
|
+
newRadius = Math.max(MIN_ELEMENT_SIZE, newRadius);
|
|
4847
|
+
const updates = { radius: newRadius };
|
|
4848
|
+
if (el.feetPerCell != null && ctx.gridSize && ctx.gridSize > 0) {
|
|
4849
|
+
const snapUnit = ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize;
|
|
4850
|
+
updates.radiusFeet = newRadius / snapUnit * el.feetPerCell;
|
|
4851
|
+
}
|
|
4852
|
+
ctx.store.update(this.mode.elementId, updates);
|
|
4853
|
+
ctx.requestRender();
|
|
4854
|
+
}
|
|
4314
4855
|
getMarqueeRect() {
|
|
4315
4856
|
if (this.mode.type !== "marquee") return null;
|
|
4316
4857
|
const { start } = this.mode;
|
|
@@ -4367,6 +4908,11 @@ var SelectTool = class {
|
|
|
4367
4908
|
if (el.type === "arrow") {
|
|
4368
4909
|
return isNearBezier(point, el.from, el.to, el.bend, 10);
|
|
4369
4910
|
}
|
|
4911
|
+
if (el.type === "template") {
|
|
4912
|
+
const bounds = getElementBounds(el);
|
|
4913
|
+
if (!bounds) return false;
|
|
4914
|
+
return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
|
|
4915
|
+
}
|
|
4370
4916
|
return false;
|
|
4371
4917
|
}
|
|
4372
4918
|
};
|
|
@@ -4424,7 +4970,7 @@ var ArrowTool = class {
|
|
|
4424
4970
|
this.fromBinding = { elementId: target.id };
|
|
4425
4971
|
this.fromTarget = target;
|
|
4426
4972
|
} else {
|
|
4427
|
-
this.start =
|
|
4973
|
+
this.start = smartSnap(world, ctx);
|
|
4428
4974
|
this.fromBinding = void 0;
|
|
4429
4975
|
this.fromTarget = null;
|
|
4430
4976
|
}
|
|
@@ -4442,7 +4988,7 @@ var ArrowTool = class {
|
|
|
4442
4988
|
this.end = getElementCenter(target);
|
|
4443
4989
|
this.toTarget = target;
|
|
4444
4990
|
} else {
|
|
4445
|
-
this.end =
|
|
4991
|
+
this.end = smartSnap(world, ctx);
|
|
4446
4992
|
this.toTarget = null;
|
|
4447
4993
|
}
|
|
4448
4994
|
ctx.requestRender();
|
|
@@ -4555,9 +5101,7 @@ var NoteTool = class {
|
|
|
4555
5101
|
}
|
|
4556
5102
|
onPointerUp(state, ctx) {
|
|
4557
5103
|
let world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
4558
|
-
|
|
4559
|
-
world = snapPoint(world, ctx.gridSize);
|
|
4560
|
-
}
|
|
5104
|
+
world = smartSnap(world, ctx);
|
|
4561
5105
|
const note = createNote({
|
|
4562
5106
|
position: world,
|
|
4563
5107
|
size: { ...this.size },
|
|
@@ -4612,9 +5156,7 @@ var TextTool = class {
|
|
|
4612
5156
|
}
|
|
4613
5157
|
onPointerUp(state, ctx) {
|
|
4614
5158
|
let world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
4615
|
-
|
|
4616
|
-
world = snapPoint(world, ctx.gridSize);
|
|
4617
|
-
}
|
|
5159
|
+
world = smartSnap(world, ctx);
|
|
4618
5160
|
const textEl = createText({
|
|
4619
5161
|
position: world,
|
|
4620
5162
|
fontSize: this.fontSize,
|
|
@@ -4647,8 +5189,12 @@ var ImageTool = class {
|
|
|
4647
5189
|
onPointerUp(state, ctx) {
|
|
4648
5190
|
if (!this.src) return;
|
|
4649
5191
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5192
|
+
const snapped = smartSnap(world, ctx);
|
|
4650
5193
|
const image = createImage({
|
|
4651
|
-
position:
|
|
5194
|
+
position: {
|
|
5195
|
+
x: snapped.x - this.size.w / 2,
|
|
5196
|
+
y: snapped.y - this.size.h / 2
|
|
5197
|
+
},
|
|
4652
5198
|
size: { ...this.size },
|
|
4653
5199
|
src: this.src
|
|
4654
5200
|
});
|
|
@@ -4785,7 +5331,7 @@ var ShapeTool = class {
|
|
|
4785
5331
|
for (const listener of this.optionListeners) listener();
|
|
4786
5332
|
}
|
|
4787
5333
|
snap(point, ctx) {
|
|
4788
|
-
return
|
|
5334
|
+
return smartSnap(point, ctx);
|
|
4789
5335
|
}
|
|
4790
5336
|
onKeyDown = (e) => {
|
|
4791
5337
|
if (e.key === "Shift") this.shiftHeld = true;
|
|
@@ -4795,6 +5341,398 @@ var ShapeTool = class {
|
|
|
4795
5341
|
};
|
|
4796
5342
|
};
|
|
4797
5343
|
|
|
5344
|
+
// src/tools/measure-tool.ts
|
|
5345
|
+
var MeasureTool = class {
|
|
5346
|
+
name = "measure";
|
|
5347
|
+
start = null;
|
|
5348
|
+
end = null;
|
|
5349
|
+
gridSize = 1;
|
|
5350
|
+
gridType;
|
|
5351
|
+
hexOrientation;
|
|
5352
|
+
feetPerCell;
|
|
5353
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
5354
|
+
constructor(options = {}) {
|
|
5355
|
+
this.feetPerCell = options.feetPerCell ?? 5;
|
|
5356
|
+
}
|
|
5357
|
+
getOptions() {
|
|
5358
|
+
return { feetPerCell: this.feetPerCell };
|
|
5359
|
+
}
|
|
5360
|
+
setOptions(options) {
|
|
5361
|
+
if (options.feetPerCell !== void 0) this.feetPerCell = options.feetPerCell;
|
|
5362
|
+
this.notifyOptionsChange();
|
|
5363
|
+
}
|
|
5364
|
+
onOptionsChange(listener) {
|
|
5365
|
+
this.optionListeners.add(listener);
|
|
5366
|
+
return () => this.optionListeners.delete(listener);
|
|
5367
|
+
}
|
|
5368
|
+
onPointerDown(state, ctx) {
|
|
5369
|
+
this.gridSize = ctx.gridSize ?? 1;
|
|
5370
|
+
this.gridType = ctx.gridType;
|
|
5371
|
+
this.hexOrientation = ctx.hexOrientation;
|
|
5372
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5373
|
+
this.start = this.snapToGrid(world, ctx);
|
|
5374
|
+
this.end = { ...this.start };
|
|
5375
|
+
}
|
|
5376
|
+
onPointerMove(state, ctx) {
|
|
5377
|
+
if (!this.start) return;
|
|
5378
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5379
|
+
this.end = this.snapToGrid(world, ctx);
|
|
5380
|
+
ctx.requestRender();
|
|
5381
|
+
}
|
|
5382
|
+
onPointerUp(_state, ctx) {
|
|
5383
|
+
if (!this.start) return;
|
|
5384
|
+
this.start = null;
|
|
5385
|
+
this.end = null;
|
|
5386
|
+
ctx.requestRender();
|
|
5387
|
+
}
|
|
5388
|
+
onDeactivate(_ctx) {
|
|
5389
|
+
this.start = null;
|
|
5390
|
+
this.end = null;
|
|
5391
|
+
}
|
|
5392
|
+
getMeasurement() {
|
|
5393
|
+
if (!this.start || !this.end) return null;
|
|
5394
|
+
const dx = this.end.x - this.start.x;
|
|
5395
|
+
const dy = this.end.y - this.start.y;
|
|
5396
|
+
const worldDistance = Math.sqrt(dx * dx + dy * dy);
|
|
5397
|
+
let cells;
|
|
5398
|
+
if (this.gridType === "hex" && this.hexOrientation) {
|
|
5399
|
+
cells = getHexDistance(this.start, this.end, this.gridSize, this.hexOrientation);
|
|
5400
|
+
} else {
|
|
5401
|
+
const snapUnit = this.gridSize;
|
|
5402
|
+
cells = worldDistance / snapUnit;
|
|
5403
|
+
}
|
|
5404
|
+
const feet = cells * this.feetPerCell;
|
|
5405
|
+
return {
|
|
5406
|
+
start: { ...this.start },
|
|
5407
|
+
end: { ...this.end },
|
|
5408
|
+
worldDistance,
|
|
5409
|
+
cells,
|
|
5410
|
+
feet
|
|
5411
|
+
};
|
|
5412
|
+
}
|
|
5413
|
+
renderOverlay(ctx) {
|
|
5414
|
+
const m = this.getMeasurement();
|
|
5415
|
+
if (!m) return;
|
|
5416
|
+
ctx.save();
|
|
5417
|
+
ctx.strokeStyle = "#FF5722";
|
|
5418
|
+
ctx.setLineDash([8, 4]);
|
|
5419
|
+
ctx.lineWidth = 2;
|
|
5420
|
+
ctx.beginPath();
|
|
5421
|
+
ctx.moveTo(m.start.x, m.start.y);
|
|
5422
|
+
ctx.lineTo(m.end.x, m.end.y);
|
|
5423
|
+
ctx.stroke();
|
|
5424
|
+
ctx.setLineDash([]);
|
|
5425
|
+
ctx.fillStyle = "#FF5722";
|
|
5426
|
+
const dotRadius = 4;
|
|
5427
|
+
ctx.beginPath();
|
|
5428
|
+
ctx.arc(m.start.x, m.start.y, dotRadius, 0, Math.PI * 2);
|
|
5429
|
+
ctx.fill();
|
|
5430
|
+
ctx.beginPath();
|
|
5431
|
+
ctx.arc(m.end.x, m.end.y, dotRadius, 0, Math.PI * 2);
|
|
5432
|
+
ctx.fill();
|
|
5433
|
+
const label = `${Math.round(m.feet)} ft`;
|
|
5434
|
+
const midX = (m.start.x + m.end.x) / 2;
|
|
5435
|
+
const midY = (m.start.y + m.end.y) / 2;
|
|
5436
|
+
ctx.font = "14px sans-serif";
|
|
5437
|
+
const metrics = ctx.measureText(label);
|
|
5438
|
+
const padX = 6;
|
|
5439
|
+
const padY = 4;
|
|
5440
|
+
const textH = 14;
|
|
5441
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
|
|
5442
|
+
ctx.beginPath();
|
|
5443
|
+
ctx.roundRect(
|
|
5444
|
+
midX - metrics.width / 2 - padX,
|
|
5445
|
+
midY - textH / 2 - padY,
|
|
5446
|
+
metrics.width + padX * 2,
|
|
5447
|
+
textH + padY * 2,
|
|
5448
|
+
4
|
|
5449
|
+
);
|
|
5450
|
+
ctx.fill();
|
|
5451
|
+
ctx.fillStyle = "#FFFFFF";
|
|
5452
|
+
ctx.textAlign = "center";
|
|
5453
|
+
ctx.textBaseline = "middle";
|
|
5454
|
+
ctx.fillText(label, midX, midY);
|
|
5455
|
+
ctx.restore();
|
|
5456
|
+
}
|
|
5457
|
+
snapToGrid(point, ctx) {
|
|
5458
|
+
if (!ctx.gridSize) return point;
|
|
5459
|
+
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
5460
|
+
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
5461
|
+
}
|
|
5462
|
+
if (ctx.gridType === "square") {
|
|
5463
|
+
return snapPoint(point, ctx.gridSize);
|
|
5464
|
+
}
|
|
5465
|
+
if (ctx.snapToGrid) {
|
|
5466
|
+
return snapPoint(point, ctx.gridSize);
|
|
5467
|
+
}
|
|
5468
|
+
return point;
|
|
5469
|
+
}
|
|
5470
|
+
notifyOptionsChange() {
|
|
5471
|
+
for (const listener of this.optionListeners) listener();
|
|
5472
|
+
}
|
|
5473
|
+
};
|
|
5474
|
+
|
|
5475
|
+
// src/tools/template-tool.ts
|
|
5476
|
+
var TemplateTool = class {
|
|
5477
|
+
name = "template";
|
|
5478
|
+
drawing = false;
|
|
5479
|
+
origin = { x: 0, y: 0 };
|
|
5480
|
+
current = { x: 0, y: 0 };
|
|
5481
|
+
gridSize = 1;
|
|
5482
|
+
gridType;
|
|
5483
|
+
hexOrientation;
|
|
5484
|
+
snapEnabled = false;
|
|
5485
|
+
templateShape;
|
|
5486
|
+
fillColor;
|
|
5487
|
+
strokeColor;
|
|
5488
|
+
strokeWidth;
|
|
5489
|
+
opacity;
|
|
5490
|
+
feetPerCell;
|
|
5491
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
5492
|
+
constructor(options = {}) {
|
|
5493
|
+
this.templateShape = options.templateShape ?? "circle";
|
|
5494
|
+
this.fillColor = options.fillColor ?? "rgba(255, 87, 34, 0.2)";
|
|
5495
|
+
this.strokeColor = options.strokeColor ?? "#FF5722";
|
|
5496
|
+
this.strokeWidth = options.strokeWidth ?? 2;
|
|
5497
|
+
this.opacity = options.opacity ?? 0.6;
|
|
5498
|
+
this.feetPerCell = options.feetPerCell ?? 5;
|
|
5499
|
+
}
|
|
5500
|
+
getOptions() {
|
|
5501
|
+
return {
|
|
5502
|
+
templateShape: this.templateShape,
|
|
5503
|
+
fillColor: this.fillColor,
|
|
5504
|
+
strokeColor: this.strokeColor,
|
|
5505
|
+
strokeWidth: this.strokeWidth,
|
|
5506
|
+
opacity: this.opacity,
|
|
5507
|
+
feetPerCell: this.feetPerCell
|
|
5508
|
+
};
|
|
5509
|
+
}
|
|
5510
|
+
setOptions(options) {
|
|
5511
|
+
if (options.templateShape !== void 0) this.templateShape = options.templateShape;
|
|
5512
|
+
if (options.fillColor !== void 0) this.fillColor = options.fillColor;
|
|
5513
|
+
if (options.strokeColor !== void 0) this.strokeColor = options.strokeColor;
|
|
5514
|
+
if (options.strokeWidth !== void 0) this.strokeWidth = options.strokeWidth;
|
|
5515
|
+
if (options.opacity !== void 0) this.opacity = options.opacity;
|
|
5516
|
+
if (options.feetPerCell !== void 0) this.feetPerCell = options.feetPerCell;
|
|
5517
|
+
this.notifyOptionsChange();
|
|
5518
|
+
}
|
|
5519
|
+
onOptionsChange(listener) {
|
|
5520
|
+
this.optionListeners.add(listener);
|
|
5521
|
+
return () => this.optionListeners.delete(listener);
|
|
5522
|
+
}
|
|
5523
|
+
onPointerDown(state, ctx) {
|
|
5524
|
+
this.drawing = true;
|
|
5525
|
+
this.gridSize = ctx.gridSize ?? 1;
|
|
5526
|
+
this.gridType = ctx.gridType;
|
|
5527
|
+
this.hexOrientation = ctx.hexOrientation;
|
|
5528
|
+
this.snapEnabled = !!ctx.gridType || (ctx.snapToGrid ?? false);
|
|
5529
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5530
|
+
this.origin = this.snapToGrid(world, ctx);
|
|
5531
|
+
this.current = { ...this.origin };
|
|
5532
|
+
}
|
|
5533
|
+
onPointerMove(state, ctx) {
|
|
5534
|
+
if (!this.drawing) return;
|
|
5535
|
+
this.current = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5536
|
+
ctx.requestRender();
|
|
5537
|
+
}
|
|
5538
|
+
onPointerUp(_state, ctx) {
|
|
5539
|
+
if (!this.drawing) return;
|
|
5540
|
+
this.drawing = false;
|
|
5541
|
+
const radius = this.computeRadius();
|
|
5542
|
+
if (radius <= 0) return;
|
|
5543
|
+
const angle = this.computeAngle();
|
|
5544
|
+
const gridSize = ctx.gridSize;
|
|
5545
|
+
const snapUnit = gridSize && gridSize > 0 ? ctx.gridType === "hex" ? Math.sqrt(3) * gridSize : gridSize : 0;
|
|
5546
|
+
const cells = snapUnit > 0 ? radius / snapUnit : 0;
|
|
5547
|
+
const radiusFeet = cells * this.feetPerCell;
|
|
5548
|
+
const element = createTemplate({
|
|
5549
|
+
position: { ...this.origin },
|
|
5550
|
+
templateShape: this.templateShape,
|
|
5551
|
+
radius,
|
|
5552
|
+
angle,
|
|
5553
|
+
fillColor: this.fillColor,
|
|
5554
|
+
strokeColor: this.strokeColor,
|
|
5555
|
+
strokeWidth: this.strokeWidth,
|
|
5556
|
+
opacity: this.opacity,
|
|
5557
|
+
feetPerCell: this.feetPerCell,
|
|
5558
|
+
radiusFeet: radiusFeet > 0 ? radiusFeet : void 0,
|
|
5559
|
+
layerId: ctx.activeLayerId ?? ""
|
|
5560
|
+
});
|
|
5561
|
+
ctx.store.add(element);
|
|
5562
|
+
ctx.requestRender();
|
|
5563
|
+
ctx.switchTool?.("select");
|
|
5564
|
+
}
|
|
5565
|
+
onDeactivate(_ctx) {
|
|
5566
|
+
this.drawing = false;
|
|
5567
|
+
this.origin = { x: 0, y: 0 };
|
|
5568
|
+
this.current = { x: 0, y: 0 };
|
|
5569
|
+
}
|
|
5570
|
+
renderOverlay(ctx) {
|
|
5571
|
+
if (!this.drawing) return;
|
|
5572
|
+
const radius = this.computeRadius();
|
|
5573
|
+
if (radius <= 0) return;
|
|
5574
|
+
if (this.gridType === "hex" && this.hexOrientation) {
|
|
5575
|
+
this.renderHexOverlay(ctx, radius);
|
|
5576
|
+
return;
|
|
5577
|
+
}
|
|
5578
|
+
this.renderGeometricOverlay(ctx, radius);
|
|
5579
|
+
}
|
|
5580
|
+
renderGeometricOverlay(ctx, radius) {
|
|
5581
|
+
const cx = this.origin.x;
|
|
5582
|
+
const cy = this.origin.y;
|
|
5583
|
+
const angle = this.computeAngle();
|
|
5584
|
+
ctx.save();
|
|
5585
|
+
ctx.globalAlpha = 0.4;
|
|
5586
|
+
ctx.fillStyle = this.fillColor;
|
|
5587
|
+
ctx.strokeStyle = this.strokeColor;
|
|
5588
|
+
ctx.lineWidth = this.strokeWidth;
|
|
5589
|
+
switch (this.templateShape) {
|
|
5590
|
+
case "circle":
|
|
5591
|
+
ctx.beginPath();
|
|
5592
|
+
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
|
|
5593
|
+
ctx.fill();
|
|
5594
|
+
ctx.stroke();
|
|
5595
|
+
break;
|
|
5596
|
+
case "square":
|
|
5597
|
+
ctx.fillRect(cx - radius / 2, cy - radius / 2, radius, radius);
|
|
5598
|
+
ctx.strokeRect(cx - radius / 2, cy - radius / 2, radius, radius);
|
|
5599
|
+
break;
|
|
5600
|
+
case "cone": {
|
|
5601
|
+
const halfAngle = Math.atan(0.5);
|
|
5602
|
+
ctx.beginPath();
|
|
5603
|
+
ctx.moveTo(cx, cy);
|
|
5604
|
+
ctx.arc(cx, cy, radius, angle - halfAngle, angle + halfAngle);
|
|
5605
|
+
ctx.closePath();
|
|
5606
|
+
ctx.fill();
|
|
5607
|
+
ctx.stroke();
|
|
5608
|
+
break;
|
|
5609
|
+
}
|
|
5610
|
+
case "line": {
|
|
5611
|
+
const halfW = radius / 12;
|
|
5612
|
+
const cos = Math.cos(angle);
|
|
5613
|
+
const sin = Math.sin(angle);
|
|
5614
|
+
const perpX = -sin * halfW;
|
|
5615
|
+
const perpY = cos * halfW;
|
|
5616
|
+
ctx.beginPath();
|
|
5617
|
+
ctx.moveTo(cx + perpX, cy + perpY);
|
|
5618
|
+
ctx.lineTo(cx + radius * cos + perpX, cy + radius * sin + perpY);
|
|
5619
|
+
ctx.lineTo(cx + radius * cos - perpX, cy + radius * sin - perpY);
|
|
5620
|
+
ctx.lineTo(cx - perpX, cy - perpY);
|
|
5621
|
+
ctx.closePath();
|
|
5622
|
+
ctx.fill();
|
|
5623
|
+
ctx.stroke();
|
|
5624
|
+
break;
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
ctx.restore();
|
|
5628
|
+
}
|
|
5629
|
+
renderHexOverlay(ctx, radius) {
|
|
5630
|
+
const orientation = this.hexOrientation;
|
|
5631
|
+
if (!orientation) return;
|
|
5632
|
+
const cellSize = this.gridSize;
|
|
5633
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
5634
|
+
const radiusCells = radius / snapUnit;
|
|
5635
|
+
const angle = this.computeAngle();
|
|
5636
|
+
const center = this.origin;
|
|
5637
|
+
let hexCells;
|
|
5638
|
+
switch (this.templateShape) {
|
|
5639
|
+
case "circle":
|
|
5640
|
+
hexCells = getHexCellsInRadius(center, radiusCells, cellSize, orientation);
|
|
5641
|
+
break;
|
|
5642
|
+
case "cone":
|
|
5643
|
+
hexCells = getHexCellsInCone(center, angle, radiusCells, cellSize, orientation);
|
|
5644
|
+
break;
|
|
5645
|
+
case "line":
|
|
5646
|
+
hexCells = getHexCellsInLine(center, angle, radiusCells, cellSize, orientation);
|
|
5647
|
+
break;
|
|
5648
|
+
case "square":
|
|
5649
|
+
hexCells = getHexCellsInSquare(center, radiusCells, cellSize, orientation);
|
|
5650
|
+
break;
|
|
5651
|
+
}
|
|
5652
|
+
ctx.save();
|
|
5653
|
+
ctx.globalAlpha = 0.4;
|
|
5654
|
+
ctx.beginPath();
|
|
5655
|
+
for (const cell of hexCells) {
|
|
5656
|
+
drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
|
|
5657
|
+
}
|
|
5658
|
+
ctx.fillStyle = this.fillColor;
|
|
5659
|
+
ctx.fill();
|
|
5660
|
+
ctx.beginPath();
|
|
5661
|
+
for (const cell of hexCells) {
|
|
5662
|
+
drawHexPath(ctx, cell.x, cell.y, cellSize, orientation);
|
|
5663
|
+
}
|
|
5664
|
+
ctx.strokeStyle = this.strokeColor;
|
|
5665
|
+
ctx.lineWidth = this.strokeWidth;
|
|
5666
|
+
ctx.stroke();
|
|
5667
|
+
if (this.templateShape === "cone" || this.templateShape === "line" || this.templateShape === "circle" || this.templateShape === "square") {
|
|
5668
|
+
ctx.globalAlpha = 0.5;
|
|
5669
|
+
ctx.beginPath();
|
|
5670
|
+
drawHexPath(ctx, center.x, center.y, cellSize, orientation);
|
|
5671
|
+
ctx.fillStyle = this.strokeColor;
|
|
5672
|
+
ctx.fill();
|
|
5673
|
+
ctx.strokeStyle = this.strokeColor;
|
|
5674
|
+
ctx.lineWidth = this.strokeWidth;
|
|
5675
|
+
ctx.stroke();
|
|
5676
|
+
}
|
|
5677
|
+
if (this.templateShape === "circle") {
|
|
5678
|
+
const feet = radiusCells * this.feetPerCell;
|
|
5679
|
+
if (feet > 0) {
|
|
5680
|
+
ctx.globalAlpha = 1;
|
|
5681
|
+
const label = `${Math.round(feet)} ft`;
|
|
5682
|
+
const fontSize = Math.max(10, Math.min(14, radius * 0.15));
|
|
5683
|
+
ctx.font = `bold ${fontSize}px system-ui, sans-serif`;
|
|
5684
|
+
ctx.textAlign = "center";
|
|
5685
|
+
ctx.textBaseline = "bottom";
|
|
5686
|
+
const textX = center.x;
|
|
5687
|
+
const textY = center.y - 4;
|
|
5688
|
+
const metrics = ctx.measureText(label);
|
|
5689
|
+
const padX = 4;
|
|
5690
|
+
const padY = 2;
|
|
5691
|
+
const textW = metrics.width + padX * 2;
|
|
5692
|
+
const textH = fontSize + padY * 2;
|
|
5693
|
+
ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
|
|
5694
|
+
ctx.beginPath();
|
|
5695
|
+
ctx.roundRect(textX - textW / 2, textY - textH, textW, textH, 3);
|
|
5696
|
+
ctx.fill();
|
|
5697
|
+
ctx.fillStyle = this.strokeColor;
|
|
5698
|
+
ctx.fillText(label, textX, textY - padY);
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
ctx.restore();
|
|
5702
|
+
}
|
|
5703
|
+
computeRadius() {
|
|
5704
|
+
const dx = this.current.x - this.origin.x;
|
|
5705
|
+
const dy = this.current.y - this.origin.y;
|
|
5706
|
+
const raw = Math.sqrt(dx * dx + dy * dy);
|
|
5707
|
+
if (this.snapEnabled && this.gridSize > 0) {
|
|
5708
|
+
const snapUnit = this.gridType === "hex" ? Math.sqrt(3) * this.gridSize : this.gridSize;
|
|
5709
|
+
return Math.max(snapUnit, Math.round(raw / snapUnit) * snapUnit);
|
|
5710
|
+
}
|
|
5711
|
+
return raw;
|
|
5712
|
+
}
|
|
5713
|
+
computeAngle() {
|
|
5714
|
+
const dx = this.current.x - this.origin.x;
|
|
5715
|
+
const dy = this.current.y - this.origin.y;
|
|
5716
|
+
return Math.atan2(dy, dx);
|
|
5717
|
+
}
|
|
5718
|
+
snapToGrid(point, ctx) {
|
|
5719
|
+
if (!ctx.gridSize) return point;
|
|
5720
|
+
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
5721
|
+
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
5722
|
+
}
|
|
5723
|
+
if (ctx.gridType === "square") {
|
|
5724
|
+
return snapPoint(point, ctx.gridSize);
|
|
5725
|
+
}
|
|
5726
|
+
if (ctx.snapToGrid) {
|
|
5727
|
+
return snapPoint(point, ctx.gridSize);
|
|
5728
|
+
}
|
|
5729
|
+
return point;
|
|
5730
|
+
}
|
|
5731
|
+
notifyOptionsChange() {
|
|
5732
|
+
for (const listener of this.optionListeners) listener();
|
|
5733
|
+
}
|
|
5734
|
+
};
|
|
5735
|
+
|
|
4798
5736
|
// src/history/layer-commands.ts
|
|
4799
5737
|
var CreateLayerCommand = class {
|
|
4800
5738
|
constructor(manager, layer) {
|
|
@@ -4836,7 +5774,7 @@ var UpdateLayerCommand = class {
|
|
|
4836
5774
|
};
|
|
4837
5775
|
|
|
4838
5776
|
// src/index.ts
|
|
4839
|
-
var VERSION = "0.
|
|
5777
|
+
var VERSION = "0.9.0";
|
|
4840
5778
|
export {
|
|
4841
5779
|
AddElementCommand,
|
|
4842
5780
|
ArrowTool,
|
|
@@ -4855,6 +5793,7 @@ export {
|
|
|
4855
5793
|
ImageTool,
|
|
4856
5794
|
InputHandler,
|
|
4857
5795
|
LayerManager,
|
|
5796
|
+
MeasureTool,
|
|
4858
5797
|
NoteEditor,
|
|
4859
5798
|
NoteTool,
|
|
4860
5799
|
PencilTool,
|
|
@@ -4863,6 +5802,7 @@ export {
|
|
|
4863
5802
|
RemoveLayerCommand,
|
|
4864
5803
|
SelectTool,
|
|
4865
5804
|
ShapeTool,
|
|
5805
|
+
TemplateTool,
|
|
4866
5806
|
TextTool,
|
|
4867
5807
|
ToolManager,
|
|
4868
5808
|
UpdateElementCommand,
|
|
@@ -4879,7 +5819,9 @@ export {
|
|
|
4879
5819
|
createNote,
|
|
4880
5820
|
createShape,
|
|
4881
5821
|
createStroke,
|
|
5822
|
+
createTemplate,
|
|
4882
5823
|
createText,
|
|
5824
|
+
drawHexPath,
|
|
4883
5825
|
exportImage,
|
|
4884
5826
|
exportState,
|
|
4885
5827
|
findBindTarget,
|
|
@@ -4892,10 +5834,17 @@ export {
|
|
|
4892
5834
|
getEdgeIntersection,
|
|
4893
5835
|
getElementBounds,
|
|
4894
5836
|
getElementCenter,
|
|
5837
|
+
getHexCellsInCone,
|
|
5838
|
+
getHexCellsInLine,
|
|
5839
|
+
getHexCellsInRadius,
|
|
5840
|
+
getHexCellsInSquare,
|
|
5841
|
+
getHexDistance,
|
|
4895
5842
|
isBindable,
|
|
4896
5843
|
isNearBezier,
|
|
4897
5844
|
parseState,
|
|
5845
|
+
smartSnap,
|
|
4898
5846
|
snapPoint,
|
|
5847
|
+
snapToHexCenter,
|
|
4899
5848
|
unbindArrow,
|
|
4900
5849
|
updateBoundArrow
|
|
4901
5850
|
};
|