@fieldnotes/core 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +211 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -2
- package/dist/index.d.ts +36 -2
- package/dist/index.js +210 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,6 +52,7 @@ __export(index_exports, {
|
|
|
52
52
|
Viewport: () => Viewport,
|
|
53
53
|
clearStaleBindings: () => clearStaleBindings,
|
|
54
54
|
createArrow: () => createArrow,
|
|
55
|
+
createGrid: () => createGrid,
|
|
55
56
|
createHtmlElement: () => createHtmlElement,
|
|
56
57
|
createId: () => createId,
|
|
57
58
|
createImage: () => createImage,
|
|
@@ -165,7 +166,7 @@ function validateState(data) {
|
|
|
165
166
|
];
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
|
-
var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text", "shape"]);
|
|
169
|
+
var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text", "shape", "grid"]);
|
|
169
170
|
function validateElement(el) {
|
|
170
171
|
if (!el || typeof el !== "object") {
|
|
171
172
|
throw new Error("Invalid element: expected an object");
|
|
@@ -1026,6 +1027,118 @@ function smoothToSegments(points) {
|
|
|
1026
1027
|
return segments;
|
|
1027
1028
|
}
|
|
1028
1029
|
|
|
1030
|
+
// src/elements/grid-renderer.ts
|
|
1031
|
+
function getSquareGridLines(bounds, cellSize) {
|
|
1032
|
+
if (cellSize <= 0) return { verticals: [], horizontals: [] };
|
|
1033
|
+
const verticals = [];
|
|
1034
|
+
const startX = Math.floor(bounds.minX / cellSize) * cellSize;
|
|
1035
|
+
const endX = Math.ceil(bounds.maxX / cellSize) * cellSize;
|
|
1036
|
+
for (let x = startX; x <= endX; x += cellSize) {
|
|
1037
|
+
verticals.push(x);
|
|
1038
|
+
}
|
|
1039
|
+
const horizontals = [];
|
|
1040
|
+
const startY = Math.floor(bounds.minY / cellSize) * cellSize;
|
|
1041
|
+
const endY = Math.ceil(bounds.maxY / cellSize) * cellSize;
|
|
1042
|
+
for (let y = startY; y <= endY; y += cellSize) {
|
|
1043
|
+
horizontals.push(y);
|
|
1044
|
+
}
|
|
1045
|
+
return { verticals, horizontals };
|
|
1046
|
+
}
|
|
1047
|
+
function getHexVertices(cx, cy, circumradius, orientation) {
|
|
1048
|
+
const vertices = [];
|
|
1049
|
+
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
1050
|
+
for (let i = 0; i < 6; i++) {
|
|
1051
|
+
const angle = Math.PI / 3 * i + angleOffset;
|
|
1052
|
+
vertices.push({
|
|
1053
|
+
x: cx + circumradius * Math.cos(angle),
|
|
1054
|
+
y: cy + circumradius * Math.sin(angle)
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
return vertices;
|
|
1058
|
+
}
|
|
1059
|
+
function getHexCenters(bounds, circumradius, orientation) {
|
|
1060
|
+
if (circumradius <= 0) return [];
|
|
1061
|
+
const centers = [];
|
|
1062
|
+
if (orientation === "pointy") {
|
|
1063
|
+
const hexW = Math.sqrt(3) * circumradius;
|
|
1064
|
+
const hexH = 2 * circumradius;
|
|
1065
|
+
const rowH = hexH * 0.75;
|
|
1066
|
+
const startRow = Math.floor((bounds.minY - circumradius) / rowH);
|
|
1067
|
+
const endRow = Math.ceil((bounds.maxY + circumradius) / rowH);
|
|
1068
|
+
const startCol = Math.floor((bounds.minX - hexW) / hexW);
|
|
1069
|
+
const endCol = Math.ceil((bounds.maxX + hexW) / hexW);
|
|
1070
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
1071
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1072
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1073
|
+
centers.push({
|
|
1074
|
+
x: col * hexW + offsetX,
|
|
1075
|
+
y: row * rowH
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
} else {
|
|
1080
|
+
const hexW = 2 * circumradius;
|
|
1081
|
+
const hexH = Math.sqrt(3) * circumradius;
|
|
1082
|
+
const colW = hexW * 0.75;
|
|
1083
|
+
const startCol = Math.floor((bounds.minX - circumradius) / colW);
|
|
1084
|
+
const endCol = Math.ceil((bounds.maxX + circumradius) / colW);
|
|
1085
|
+
const startRow = Math.floor((bounds.minY - hexH) / hexH);
|
|
1086
|
+
const endRow = Math.ceil((bounds.maxY + hexH) / hexH);
|
|
1087
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1088
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1089
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
1090
|
+
centers.push({
|
|
1091
|
+
x: col * colW,
|
|
1092
|
+
y: row * hexH + offsetY
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return centers;
|
|
1098
|
+
}
|
|
1099
|
+
function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opacity) {
|
|
1100
|
+
if (cellSize <= 0) return;
|
|
1101
|
+
const { verticals, horizontals } = getSquareGridLines(bounds, cellSize);
|
|
1102
|
+
ctx.save();
|
|
1103
|
+
ctx.strokeStyle = strokeColor;
|
|
1104
|
+
ctx.lineWidth = strokeWidth;
|
|
1105
|
+
ctx.globalAlpha = opacity;
|
|
1106
|
+
ctx.beginPath();
|
|
1107
|
+
for (const x of verticals) {
|
|
1108
|
+
ctx.moveTo(x, bounds.minY);
|
|
1109
|
+
ctx.lineTo(x, bounds.maxY);
|
|
1110
|
+
}
|
|
1111
|
+
for (const y of horizontals) {
|
|
1112
|
+
ctx.moveTo(bounds.minX, y);
|
|
1113
|
+
ctx.lineTo(bounds.maxX, y);
|
|
1114
|
+
}
|
|
1115
|
+
ctx.stroke();
|
|
1116
|
+
ctx.restore();
|
|
1117
|
+
}
|
|
1118
|
+
function renderHexGrid(ctx, bounds, cellSize, orientation, strokeColor, strokeWidth, opacity) {
|
|
1119
|
+
if (cellSize <= 0) return;
|
|
1120
|
+
const centers = getHexCenters(bounds, cellSize, orientation);
|
|
1121
|
+
ctx.save();
|
|
1122
|
+
ctx.strokeStyle = strokeColor;
|
|
1123
|
+
ctx.lineWidth = strokeWidth;
|
|
1124
|
+
ctx.globalAlpha = opacity;
|
|
1125
|
+
ctx.beginPath();
|
|
1126
|
+
for (const center of centers) {
|
|
1127
|
+
const verts = getHexVertices(center.x, center.y, cellSize, orientation);
|
|
1128
|
+
const first = verts[0];
|
|
1129
|
+
if (!first) continue;
|
|
1130
|
+
ctx.moveTo(first.x, first.y);
|
|
1131
|
+
for (let i = 1; i < verts.length; i++) {
|
|
1132
|
+
const v = verts[i];
|
|
1133
|
+
if (!v) continue;
|
|
1134
|
+
ctx.lineTo(v.x, v.y);
|
|
1135
|
+
}
|
|
1136
|
+
ctx.closePath();
|
|
1137
|
+
}
|
|
1138
|
+
ctx.stroke();
|
|
1139
|
+
ctx.restore();
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1029
1142
|
// src/elements/element-renderer.ts
|
|
1030
1143
|
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
|
|
1031
1144
|
var ARROWHEAD_LENGTH = 12;
|
|
@@ -1034,12 +1147,20 @@ var ElementRenderer = class {
|
|
|
1034
1147
|
store = null;
|
|
1035
1148
|
imageCache = /* @__PURE__ */ new Map();
|
|
1036
1149
|
onImageLoad = null;
|
|
1150
|
+
camera = null;
|
|
1151
|
+
canvasSize = null;
|
|
1037
1152
|
setStore(store) {
|
|
1038
1153
|
this.store = store;
|
|
1039
1154
|
}
|
|
1040
1155
|
setOnImageLoad(callback) {
|
|
1041
1156
|
this.onImageLoad = callback;
|
|
1042
1157
|
}
|
|
1158
|
+
setCamera(camera) {
|
|
1159
|
+
this.camera = camera;
|
|
1160
|
+
}
|
|
1161
|
+
setCanvasSize(w, h) {
|
|
1162
|
+
this.canvasSize = { w, h };
|
|
1163
|
+
}
|
|
1043
1164
|
isDomElement(element) {
|
|
1044
1165
|
return DOM_ELEMENT_TYPES.has(element.type);
|
|
1045
1166
|
}
|
|
@@ -1057,6 +1178,9 @@ var ElementRenderer = class {
|
|
|
1057
1178
|
case "image":
|
|
1058
1179
|
this.renderImage(ctx, element);
|
|
1059
1180
|
break;
|
|
1181
|
+
case "grid":
|
|
1182
|
+
this.renderGrid(ctx, element);
|
|
1183
|
+
break;
|
|
1060
1184
|
}
|
|
1061
1185
|
}
|
|
1062
1186
|
renderStroke(ctx, stroke) {
|
|
@@ -1192,6 +1316,42 @@ var ElementRenderer = class {
|
|
|
1192
1316
|
}
|
|
1193
1317
|
}
|
|
1194
1318
|
}
|
|
1319
|
+
renderGrid(ctx, grid) {
|
|
1320
|
+
if (!this.canvasSize) return;
|
|
1321
|
+
const cam = this.camera;
|
|
1322
|
+
if (!cam) return;
|
|
1323
|
+
const topLeft = cam.screenToWorld({ x: 0, y: 0 });
|
|
1324
|
+
const bottomRight = cam.screenToWorld({
|
|
1325
|
+
x: this.canvasSize.w,
|
|
1326
|
+
y: this.canvasSize.h
|
|
1327
|
+
});
|
|
1328
|
+
const bounds = {
|
|
1329
|
+
minX: topLeft.x,
|
|
1330
|
+
minY: topLeft.y,
|
|
1331
|
+
maxX: bottomRight.x,
|
|
1332
|
+
maxY: bottomRight.y
|
|
1333
|
+
};
|
|
1334
|
+
if (grid.gridType === "hex") {
|
|
1335
|
+
renderHexGrid(
|
|
1336
|
+
ctx,
|
|
1337
|
+
bounds,
|
|
1338
|
+
grid.cellSize,
|
|
1339
|
+
grid.hexOrientation,
|
|
1340
|
+
grid.strokeColor,
|
|
1341
|
+
grid.strokeWidth,
|
|
1342
|
+
grid.opacity
|
|
1343
|
+
);
|
|
1344
|
+
} else {
|
|
1345
|
+
renderSquareGrid(
|
|
1346
|
+
ctx,
|
|
1347
|
+
bounds,
|
|
1348
|
+
grid.cellSize,
|
|
1349
|
+
grid.strokeColor,
|
|
1350
|
+
grid.strokeWidth,
|
|
1351
|
+
grid.opacity
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1195
1355
|
renderImage(ctx, image) {
|
|
1196
1356
|
const img = this.getImage(image.src);
|
|
1197
1357
|
if (!img) return;
|
|
@@ -1634,6 +1794,22 @@ function createShape(input) {
|
|
|
1634
1794
|
fillColor: input.fillColor ?? "none"
|
|
1635
1795
|
};
|
|
1636
1796
|
}
|
|
1797
|
+
function createGrid(input) {
|
|
1798
|
+
return {
|
|
1799
|
+
id: createId("grid"),
|
|
1800
|
+
type: "grid",
|
|
1801
|
+
position: input.position ?? { x: 0, y: 0 },
|
|
1802
|
+
zIndex: input.zIndex ?? 0,
|
|
1803
|
+
locked: input.locked ?? false,
|
|
1804
|
+
layerId: input.layerId ?? "",
|
|
1805
|
+
gridType: input.gridType ?? "square",
|
|
1806
|
+
hexOrientation: input.hexOrientation ?? "pointy",
|
|
1807
|
+
cellSize: input.cellSize ?? 40,
|
|
1808
|
+
strokeColor: input.strokeColor ?? "#000000",
|
|
1809
|
+
strokeWidth: input.strokeWidth ?? 1,
|
|
1810
|
+
opacity: input.opacity ?? 1
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1637
1813
|
function createText(input) {
|
|
1638
1814
|
return {
|
|
1639
1815
|
id: createId("text"),
|
|
@@ -1811,6 +1987,7 @@ var Viewport = class {
|
|
|
1811
1987
|
this.toolManager = new ToolManager();
|
|
1812
1988
|
this.renderer = new ElementRenderer();
|
|
1813
1989
|
this.renderer.setStore(this.store);
|
|
1990
|
+
this.renderer.setCamera(this.camera);
|
|
1814
1991
|
this.renderer.setOnImageLoad(() => this.requestRender());
|
|
1815
1992
|
this.noteEditor = new NoteEditor();
|
|
1816
1993
|
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
@@ -1957,6 +2134,34 @@ var Viewport = class {
|
|
|
1957
2134
|
this.requestRender();
|
|
1958
2135
|
return el.id;
|
|
1959
2136
|
}
|
|
2137
|
+
addGrid(input) {
|
|
2138
|
+
const existing = this.store.getElementsByType("grid")[0];
|
|
2139
|
+
this.historyRecorder.begin();
|
|
2140
|
+
if (existing) {
|
|
2141
|
+
this.store.remove(existing.id);
|
|
2142
|
+
}
|
|
2143
|
+
const grid = createGrid({ ...input, layerId: this.layerManager.activeLayerId });
|
|
2144
|
+
this.store.add(grid);
|
|
2145
|
+
this.historyRecorder.commit();
|
|
2146
|
+
this.requestRender();
|
|
2147
|
+
return grid.id;
|
|
2148
|
+
}
|
|
2149
|
+
updateGrid(updates) {
|
|
2150
|
+
const grid = this.store.getElementsByType("grid")[0];
|
|
2151
|
+
if (!grid) return;
|
|
2152
|
+
this.historyRecorder.begin();
|
|
2153
|
+
this.store.update(grid.id, updates);
|
|
2154
|
+
this.historyRecorder.commit();
|
|
2155
|
+
this.requestRender();
|
|
2156
|
+
}
|
|
2157
|
+
removeGrid() {
|
|
2158
|
+
const grid = this.store.getElementsByType("grid")[0];
|
|
2159
|
+
if (!grid) return;
|
|
2160
|
+
this.historyRecorder.begin();
|
|
2161
|
+
this.store.remove(grid.id);
|
|
2162
|
+
this.historyRecorder.commit();
|
|
2163
|
+
this.requestRender();
|
|
2164
|
+
}
|
|
1960
2165
|
destroy() {
|
|
1961
2166
|
cancelAnimationFrame(this.animFrameId);
|
|
1962
2167
|
this.stopInteracting();
|
|
@@ -1988,6 +2193,7 @@ var Viewport = class {
|
|
|
1988
2193
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
1989
2194
|
ctx.save();
|
|
1990
2195
|
ctx.scale(dpr, dpr);
|
|
2196
|
+
this.renderer.setCanvasSize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
|
|
1991
2197
|
this.background.render(ctx, this.camera);
|
|
1992
2198
|
ctx.save();
|
|
1993
2199
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
@@ -2974,6 +3180,7 @@ var SelectTool = class {
|
|
|
2974
3180
|
for (const el of ctx.store.getAll()) {
|
|
2975
3181
|
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
2976
3182
|
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
3183
|
+
if (el.type === "grid") continue;
|
|
2977
3184
|
const bounds = this.getElementBounds(el);
|
|
2978
3185
|
if (bounds && this.rectsOverlap(marquee, bounds)) {
|
|
2979
3186
|
ids.push(el.id);
|
|
@@ -3010,11 +3217,13 @@ var SelectTool = class {
|
|
|
3010
3217
|
for (const el of elements) {
|
|
3011
3218
|
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
3012
3219
|
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
3220
|
+
if (el.type === "grid") continue;
|
|
3013
3221
|
if (this.isInsideBounds(world, el)) return el;
|
|
3014
3222
|
}
|
|
3015
3223
|
return null;
|
|
3016
3224
|
}
|
|
3017
3225
|
isInsideBounds(point, el) {
|
|
3226
|
+
if (el.type === "grid") return false;
|
|
3018
3227
|
if ("size" in el) {
|
|
3019
3228
|
const s = el.size;
|
|
3020
3229
|
return point.x >= el.position.x && point.x <= el.position.x + s.w && point.y >= el.position.y && point.y <= el.position.y + s.h;
|
|
@@ -3475,6 +3684,7 @@ var VERSION = "0.6.1";
|
|
|
3475
3684
|
Viewport,
|
|
3476
3685
|
clearStaleBindings,
|
|
3477
3686
|
createArrow,
|
|
3687
|
+
createGrid,
|
|
3478
3688
|
createHtmlElement,
|
|
3479
3689
|
createId,
|
|
3480
3690
|
createImage,
|