@fieldnotes/core 0.8.10 → 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 +977 -14
- package/dist/index.d.cts +183 -65
- package/dist/index.d.ts +183 -65
- package/dist/index.js +966 -14
- 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);
|
|
@@ -3217,7 +3660,10 @@ var Viewport = class {
|
|
|
3217
3660
|
this.renderer = new ElementRenderer();
|
|
3218
3661
|
this.renderer.setStore(this.store);
|
|
3219
3662
|
this.renderer.setCamera(this.camera);
|
|
3220
|
-
this.renderer.setOnImageLoad(() =>
|
|
3663
|
+
this.renderer.setOnImageLoad(() => {
|
|
3664
|
+
this.renderLoop.markAllLayersDirty();
|
|
3665
|
+
this.requestRender();
|
|
3666
|
+
});
|
|
3221
3667
|
this.noteEditor = new NoteEditor();
|
|
3222
3668
|
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
3223
3669
|
this.history = new HistoryStack();
|
|
@@ -3278,16 +3724,19 @@ var Viewport = class {
|
|
|
3278
3724
|
});
|
|
3279
3725
|
this.unsubStore = [
|
|
3280
3726
|
this.store.on("add", (el) => {
|
|
3727
|
+
if (el.type === "grid") this.syncGridContext();
|
|
3281
3728
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
3282
3729
|
this.requestRender();
|
|
3283
3730
|
}),
|
|
3284
3731
|
this.store.on("remove", (el) => {
|
|
3732
|
+
if (el.type === "grid") this.syncGridContext();
|
|
3285
3733
|
this.unbindArrowsFrom(el);
|
|
3286
3734
|
this.domNodeManager.removeDomNode(el.id);
|
|
3287
3735
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
3288
3736
|
this.requestRender();
|
|
3289
3737
|
}),
|
|
3290
3738
|
this.store.on("update", ({ previous, current }) => {
|
|
3739
|
+
if (current.type === "grid") this.syncGridContext();
|
|
3291
3740
|
this.renderLoop.markLayerDirty(current.layerId);
|
|
3292
3741
|
if (previous.layerId !== current.layerId) {
|
|
3293
3742
|
this.renderLoop.markLayerDirty(previous.layerId);
|
|
@@ -3297,6 +3746,7 @@ var Viewport = class {
|
|
|
3297
3746
|
this.store.on("clear", () => {
|
|
3298
3747
|
this.domNodeManager.clearDomNodes();
|
|
3299
3748
|
this.renderLoop.markAllLayersDirty();
|
|
3749
|
+
this.syncGridContext();
|
|
3300
3750
|
this.requestRender();
|
|
3301
3751
|
})
|
|
3302
3752
|
];
|
|
@@ -3310,6 +3760,7 @@ var Viewport = class {
|
|
|
3310
3760
|
this.observeResize();
|
|
3311
3761
|
this.syncCanvasSize();
|
|
3312
3762
|
this.renderLoop.start();
|
|
3763
|
+
this.syncGridContext();
|
|
3313
3764
|
}
|
|
3314
3765
|
camera;
|
|
3315
3766
|
store;
|
|
@@ -3626,6 +4077,18 @@ var Viewport = class {
|
|
|
3626
4077
|
this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
|
|
3627
4078
|
this.requestRender();
|
|
3628
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
|
+
}
|
|
3629
4092
|
observeResize() {
|
|
3630
4093
|
if (typeof ResizeObserver === "undefined") return;
|
|
3631
4094
|
this.resizeObserver = new ResizeObserver(() => this.syncCanvasSize());
|
|
@@ -4000,7 +4463,7 @@ var SelectTool = class {
|
|
|
4000
4463
|
ctx.setCursor?.("default");
|
|
4001
4464
|
}
|
|
4002
4465
|
snap(point, ctx) {
|
|
4003
|
-
return
|
|
4466
|
+
return smartSnap(point, ctx);
|
|
4004
4467
|
}
|
|
4005
4468
|
onPointerDown(state, ctx) {
|
|
4006
4469
|
this.ctx = ctx;
|
|
@@ -4017,6 +4480,12 @@ var SelectTool = class {
|
|
|
4017
4480
|
ctx.requestRender();
|
|
4018
4481
|
return;
|
|
4019
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
|
+
}
|
|
4020
4489
|
const resizeHit = this.hitTestResizeHandle(world, ctx);
|
|
4021
4490
|
if (resizeHit) {
|
|
4022
4491
|
const el = ctx.store.getById(resizeHit.elementId);
|
|
@@ -4051,6 +4520,11 @@ var SelectTool = class {
|
|
|
4051
4520
|
applyArrowHandleDrag(this.mode.handle, this.mode.elementId, world, ctx);
|
|
4052
4521
|
return;
|
|
4053
4522
|
}
|
|
4523
|
+
if (this.mode.type === "resizing-template") {
|
|
4524
|
+
ctx.setCursor?.("nwse-resize");
|
|
4525
|
+
this.handleTemplateResize(world, ctx);
|
|
4526
|
+
return;
|
|
4527
|
+
}
|
|
4054
4528
|
if (this.mode.type === "resizing") {
|
|
4055
4529
|
ctx.setCursor?.(HANDLE_CURSORS[this.mode.handle]);
|
|
4056
4530
|
this.handleResize(world, ctx);
|
|
@@ -4074,6 +4548,16 @@ var SelectTool = class {
|
|
|
4074
4548
|
from: { x: el.from.x + dx, y: el.from.y + dy },
|
|
4075
4549
|
to: { x: el.to.x + dx, y: el.to.y + dy }
|
|
4076
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
|
+
});
|
|
4077
4561
|
} else {
|
|
4078
4562
|
ctx.store.update(id, {
|
|
4079
4563
|
position: { x: el.position.x + dx, y: el.position.y + dy }
|
|
@@ -4148,6 +4632,11 @@ var SelectTool = class {
|
|
|
4148
4632
|
ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
|
|
4149
4633
|
return;
|
|
4150
4634
|
}
|
|
4635
|
+
const templateResizeHit = this.hitTestTemplateResizeHandle(world, ctx);
|
|
4636
|
+
if (templateResizeHit) {
|
|
4637
|
+
ctx.setCursor?.("nwse-resize");
|
|
4638
|
+
return;
|
|
4639
|
+
}
|
|
4151
4640
|
const resizeHit = this.hitTestResizeHandle(world, ctx);
|
|
4152
4641
|
if (resizeHit) {
|
|
4153
4642
|
ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
|
|
@@ -4284,6 +4773,24 @@ var SelectTool = class {
|
|
|
4284
4773
|
);
|
|
4285
4774
|
}
|
|
4286
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]);
|
|
4287
4794
|
}
|
|
4288
4795
|
}
|
|
4289
4796
|
canvasCtx.restore();
|
|
@@ -4308,6 +4815,43 @@ var SelectTool = class {
|
|
|
4308
4815
|
}
|
|
4309
4816
|
canvasCtx.restore();
|
|
4310
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
|
+
}
|
|
4311
4855
|
getMarqueeRect() {
|
|
4312
4856
|
if (this.mode.type !== "marquee") return null;
|
|
4313
4857
|
const { start } = this.mode;
|
|
@@ -4364,6 +4908,11 @@ var SelectTool = class {
|
|
|
4364
4908
|
if (el.type === "arrow") {
|
|
4365
4909
|
return isNearBezier(point, el.from, el.to, el.bend, 10);
|
|
4366
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
|
+
}
|
|
4367
4916
|
return false;
|
|
4368
4917
|
}
|
|
4369
4918
|
};
|
|
@@ -4421,7 +4970,7 @@ var ArrowTool = class {
|
|
|
4421
4970
|
this.fromBinding = { elementId: target.id };
|
|
4422
4971
|
this.fromTarget = target;
|
|
4423
4972
|
} else {
|
|
4424
|
-
this.start =
|
|
4973
|
+
this.start = smartSnap(world, ctx);
|
|
4425
4974
|
this.fromBinding = void 0;
|
|
4426
4975
|
this.fromTarget = null;
|
|
4427
4976
|
}
|
|
@@ -4439,7 +4988,7 @@ var ArrowTool = class {
|
|
|
4439
4988
|
this.end = getElementCenter(target);
|
|
4440
4989
|
this.toTarget = target;
|
|
4441
4990
|
} else {
|
|
4442
|
-
this.end =
|
|
4991
|
+
this.end = smartSnap(world, ctx);
|
|
4443
4992
|
this.toTarget = null;
|
|
4444
4993
|
}
|
|
4445
4994
|
ctx.requestRender();
|
|
@@ -4552,9 +5101,7 @@ var NoteTool = class {
|
|
|
4552
5101
|
}
|
|
4553
5102
|
onPointerUp(state, ctx) {
|
|
4554
5103
|
let world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
4555
|
-
|
|
4556
|
-
world = snapPoint(world, ctx.gridSize);
|
|
4557
|
-
}
|
|
5104
|
+
world = smartSnap(world, ctx);
|
|
4558
5105
|
const note = createNote({
|
|
4559
5106
|
position: world,
|
|
4560
5107
|
size: { ...this.size },
|
|
@@ -4609,9 +5156,7 @@ var TextTool = class {
|
|
|
4609
5156
|
}
|
|
4610
5157
|
onPointerUp(state, ctx) {
|
|
4611
5158
|
let world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
4612
|
-
|
|
4613
|
-
world = snapPoint(world, ctx.gridSize);
|
|
4614
|
-
}
|
|
5159
|
+
world = smartSnap(world, ctx);
|
|
4615
5160
|
const textEl = createText({
|
|
4616
5161
|
position: world,
|
|
4617
5162
|
fontSize: this.fontSize,
|
|
@@ -4644,8 +5189,12 @@ var ImageTool = class {
|
|
|
4644
5189
|
onPointerUp(state, ctx) {
|
|
4645
5190
|
if (!this.src) return;
|
|
4646
5191
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
5192
|
+
const snapped = smartSnap(world, ctx);
|
|
4647
5193
|
const image = createImage({
|
|
4648
|
-
position:
|
|
5194
|
+
position: {
|
|
5195
|
+
x: snapped.x - this.size.w / 2,
|
|
5196
|
+
y: snapped.y - this.size.h / 2
|
|
5197
|
+
},
|
|
4649
5198
|
size: { ...this.size },
|
|
4650
5199
|
src: this.src
|
|
4651
5200
|
});
|
|
@@ -4782,7 +5331,7 @@ var ShapeTool = class {
|
|
|
4782
5331
|
for (const listener of this.optionListeners) listener();
|
|
4783
5332
|
}
|
|
4784
5333
|
snap(point, ctx) {
|
|
4785
|
-
return
|
|
5334
|
+
return smartSnap(point, ctx);
|
|
4786
5335
|
}
|
|
4787
5336
|
onKeyDown = (e) => {
|
|
4788
5337
|
if (e.key === "Shift") this.shiftHeld = true;
|
|
@@ -4792,6 +5341,398 @@ var ShapeTool = class {
|
|
|
4792
5341
|
};
|
|
4793
5342
|
};
|
|
4794
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
|
+
|
|
4795
5736
|
// src/history/layer-commands.ts
|
|
4796
5737
|
var CreateLayerCommand = class {
|
|
4797
5738
|
constructor(manager, layer) {
|
|
@@ -4833,7 +5774,7 @@ var UpdateLayerCommand = class {
|
|
|
4833
5774
|
};
|
|
4834
5775
|
|
|
4835
5776
|
// src/index.ts
|
|
4836
|
-
var VERSION = "0.
|
|
5777
|
+
var VERSION = "0.9.0";
|
|
4837
5778
|
export {
|
|
4838
5779
|
AddElementCommand,
|
|
4839
5780
|
ArrowTool,
|
|
@@ -4852,6 +5793,7 @@ export {
|
|
|
4852
5793
|
ImageTool,
|
|
4853
5794
|
InputHandler,
|
|
4854
5795
|
LayerManager,
|
|
5796
|
+
MeasureTool,
|
|
4855
5797
|
NoteEditor,
|
|
4856
5798
|
NoteTool,
|
|
4857
5799
|
PencilTool,
|
|
@@ -4860,6 +5802,7 @@ export {
|
|
|
4860
5802
|
RemoveLayerCommand,
|
|
4861
5803
|
SelectTool,
|
|
4862
5804
|
ShapeTool,
|
|
5805
|
+
TemplateTool,
|
|
4863
5806
|
TextTool,
|
|
4864
5807
|
ToolManager,
|
|
4865
5808
|
UpdateElementCommand,
|
|
@@ -4876,7 +5819,9 @@ export {
|
|
|
4876
5819
|
createNote,
|
|
4877
5820
|
createShape,
|
|
4878
5821
|
createStroke,
|
|
5822
|
+
createTemplate,
|
|
4879
5823
|
createText,
|
|
5824
|
+
drawHexPath,
|
|
4880
5825
|
exportImage,
|
|
4881
5826
|
exportState,
|
|
4882
5827
|
findBindTarget,
|
|
@@ -4889,10 +5834,17 @@ export {
|
|
|
4889
5834
|
getEdgeIntersection,
|
|
4890
5835
|
getElementBounds,
|
|
4891
5836
|
getElementCenter,
|
|
5837
|
+
getHexCellsInCone,
|
|
5838
|
+
getHexCellsInLine,
|
|
5839
|
+
getHexCellsInRadius,
|
|
5840
|
+
getHexCellsInSquare,
|
|
5841
|
+
getHexDistance,
|
|
4892
5842
|
isBindable,
|
|
4893
5843
|
isNearBezier,
|
|
4894
5844
|
parseState,
|
|
5845
|
+
smartSnap,
|
|
4895
5846
|
snapPoint,
|
|
5847
|
+
snapToHexCenter,
|
|
4896
5848
|
unbindArrow,
|
|
4897
5849
|
updateBoundArrow
|
|
4898
5850
|
};
|