@fieldnotes/core 0.5.0 → 0.6.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 +330 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -4
- package/dist/index.d.ts +85 -4
- package/dist/index.js +326 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
Background: () => Background,
|
|
27
27
|
BatchCommand: () => BatchCommand,
|
|
28
28
|
Camera: () => Camera,
|
|
29
|
+
CreateLayerCommand: () => CreateLayerCommand,
|
|
29
30
|
ElementRenderer: () => ElementRenderer,
|
|
30
31
|
ElementStore: () => ElementStore,
|
|
31
32
|
EraserTool: () => EraserTool,
|
|
@@ -35,15 +36,18 @@ __export(index_exports, {
|
|
|
35
36
|
HistoryStack: () => HistoryStack,
|
|
36
37
|
ImageTool: () => ImageTool,
|
|
37
38
|
InputHandler: () => InputHandler,
|
|
39
|
+
LayerManager: () => LayerManager,
|
|
38
40
|
NoteEditor: () => NoteEditor,
|
|
39
41
|
NoteTool: () => NoteTool,
|
|
40
42
|
PencilTool: () => PencilTool,
|
|
41
43
|
RemoveElementCommand: () => RemoveElementCommand,
|
|
44
|
+
RemoveLayerCommand: () => RemoveLayerCommand,
|
|
42
45
|
SelectTool: () => SelectTool,
|
|
43
46
|
ShapeTool: () => ShapeTool,
|
|
44
47
|
TextTool: () => TextTool,
|
|
45
48
|
ToolManager: () => ToolManager,
|
|
46
49
|
UpdateElementCommand: () => UpdateElementCommand,
|
|
50
|
+
UpdateLayerCommand: () => UpdateLayerCommand,
|
|
47
51
|
VERSION: () => VERSION,
|
|
48
52
|
Viewport: () => Viewport,
|
|
49
53
|
clearStaleBindings: () => clearStaleBindings,
|
|
@@ -100,15 +104,16 @@ var EventBus = class {
|
|
|
100
104
|
};
|
|
101
105
|
|
|
102
106
|
// src/core/state-serializer.ts
|
|
103
|
-
var CURRENT_VERSION =
|
|
104
|
-
function exportState(elements, camera) {
|
|
107
|
+
var CURRENT_VERSION = 2;
|
|
108
|
+
function exportState(elements, camera, layers = []) {
|
|
105
109
|
return {
|
|
106
110
|
version: CURRENT_VERSION,
|
|
107
111
|
camera: {
|
|
108
112
|
position: { ...camera.position },
|
|
109
113
|
zoom: camera.zoom
|
|
110
114
|
},
|
|
111
|
-
elements: elements.map((el) => structuredClone(el))
|
|
115
|
+
elements: elements.map((el) => structuredClone(el)),
|
|
116
|
+
layers: layers.map((l) => ({ ...l }))
|
|
112
117
|
};
|
|
113
118
|
}
|
|
114
119
|
function parseState(json) {
|
|
@@ -146,6 +151,19 @@ function validateState(data) {
|
|
|
146
151
|
migrateElement(el);
|
|
147
152
|
}
|
|
148
153
|
cleanBindings(obj["elements"]);
|
|
154
|
+
const layers = obj["layers"];
|
|
155
|
+
if (!Array.isArray(layers) || layers.length === 0) {
|
|
156
|
+
obj["layers"] = [
|
|
157
|
+
{
|
|
158
|
+
id: "default-layer",
|
|
159
|
+
name: "Layer 1",
|
|
160
|
+
visible: true,
|
|
161
|
+
locked: false,
|
|
162
|
+
order: 0,
|
|
163
|
+
opacity: 1
|
|
164
|
+
}
|
|
165
|
+
];
|
|
166
|
+
}
|
|
149
167
|
}
|
|
150
168
|
var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text", "shape"]);
|
|
151
169
|
function validateElement(el) {
|
|
@@ -178,6 +196,9 @@ function cleanBindings(elements) {
|
|
|
178
196
|
}
|
|
179
197
|
}
|
|
180
198
|
function migrateElement(obj) {
|
|
199
|
+
if (typeof obj["layerId"] !== "string") {
|
|
200
|
+
obj["layerId"] = "default-layer";
|
|
201
|
+
}
|
|
181
202
|
if (obj["type"] === "arrow" && typeof obj["bend"] !== "number") {
|
|
182
203
|
obj["bend"] = 0;
|
|
183
204
|
}
|
|
@@ -213,9 +234,11 @@ var AutoSave = class {
|
|
|
213
234
|
this.camera = camera;
|
|
214
235
|
this.key = options.key ?? DEFAULT_KEY;
|
|
215
236
|
this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
237
|
+
this.layerManager = options.layerManager;
|
|
216
238
|
}
|
|
217
239
|
key;
|
|
218
240
|
debounceMs;
|
|
241
|
+
layerManager;
|
|
219
242
|
timerId = null;
|
|
220
243
|
unsubscribers = [];
|
|
221
244
|
start() {
|
|
@@ -226,6 +249,9 @@ var AutoSave = class {
|
|
|
226
249
|
this.store.on("update", schedule),
|
|
227
250
|
this.camera.onChange(schedule)
|
|
228
251
|
];
|
|
252
|
+
if (this.layerManager) {
|
|
253
|
+
this.unsubscribers.push(this.layerManager.on("change", schedule));
|
|
254
|
+
}
|
|
229
255
|
}
|
|
230
256
|
stop() {
|
|
231
257
|
this.cancelPending();
|
|
@@ -258,7 +284,8 @@ var AutoSave = class {
|
|
|
258
284
|
}
|
|
259
285
|
save() {
|
|
260
286
|
if (typeof localStorage === "undefined") return;
|
|
261
|
-
const
|
|
287
|
+
const layers = this.layerManager?.snapshot() ?? [];
|
|
288
|
+
const state = exportState(this.store.snapshot(), this.camera, layers);
|
|
262
289
|
localStorage.setItem(this.key, JSON.stringify(state));
|
|
263
290
|
}
|
|
264
291
|
};
|
|
@@ -635,11 +662,20 @@ var InputHandler = class {
|
|
|
635
662
|
var ElementStore = class {
|
|
636
663
|
elements = /* @__PURE__ */ new Map();
|
|
637
664
|
bus = new EventBus();
|
|
665
|
+
layerOrderMap = /* @__PURE__ */ new Map();
|
|
638
666
|
get count() {
|
|
639
667
|
return this.elements.size;
|
|
640
668
|
}
|
|
669
|
+
setLayerOrder(order) {
|
|
670
|
+
this.layerOrderMap = new Map(order);
|
|
671
|
+
}
|
|
641
672
|
getAll() {
|
|
642
|
-
return [...this.elements.values()].sort((a, b) =>
|
|
673
|
+
return [...this.elements.values()].sort((a, b) => {
|
|
674
|
+
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
675
|
+
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
676
|
+
if (layerA !== layerB) return layerA - layerB;
|
|
677
|
+
return a.zIndex - b.zIndex;
|
|
678
|
+
});
|
|
643
679
|
}
|
|
644
680
|
getById(id) {
|
|
645
681
|
return this.elements.get(id);
|
|
@@ -823,12 +859,13 @@ function getEdgeIntersection(bounds, outsidePoint) {
|
|
|
823
859
|
y: cy + dy * scale
|
|
824
860
|
};
|
|
825
861
|
}
|
|
826
|
-
function findBindTarget(point, store, threshold, excludeId) {
|
|
862
|
+
function findBindTarget(point, store, threshold, excludeId, filter) {
|
|
827
863
|
let closest = null;
|
|
828
864
|
let closestDist = Infinity;
|
|
829
865
|
for (const el of store.getAll()) {
|
|
830
866
|
if (!isBindable(el)) continue;
|
|
831
867
|
if (excludeId && el.id === excludeId) continue;
|
|
868
|
+
if (filter && !filter(el)) continue;
|
|
832
869
|
const bounds = getElementBounds(el);
|
|
833
870
|
if (!bounds) continue;
|
|
834
871
|
const dist = distToBounds(point, bounds);
|
|
@@ -986,14 +1023,19 @@ function smoothToSegments(points) {
|
|
|
986
1023
|
}
|
|
987
1024
|
|
|
988
1025
|
// src/elements/element-renderer.ts
|
|
989
|
-
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "
|
|
1026
|
+
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
|
|
990
1027
|
var ARROWHEAD_LENGTH = 12;
|
|
991
1028
|
var ARROWHEAD_ANGLE = Math.PI / 6;
|
|
992
1029
|
var ElementRenderer = class {
|
|
993
1030
|
store = null;
|
|
1031
|
+
imageCache = /* @__PURE__ */ new Map();
|
|
1032
|
+
onImageLoad = null;
|
|
994
1033
|
setStore(store) {
|
|
995
1034
|
this.store = store;
|
|
996
1035
|
}
|
|
1036
|
+
setOnImageLoad(callback) {
|
|
1037
|
+
this.onImageLoad = callback;
|
|
1038
|
+
}
|
|
997
1039
|
isDomElement(element) {
|
|
998
1040
|
return DOM_ELEMENT_TYPES.has(element.type);
|
|
999
1041
|
}
|
|
@@ -1008,6 +1050,9 @@ var ElementRenderer = class {
|
|
|
1008
1050
|
case "shape":
|
|
1009
1051
|
this.renderShape(ctx, element);
|
|
1010
1052
|
break;
|
|
1053
|
+
case "image":
|
|
1054
|
+
this.renderImage(ctx, element);
|
|
1055
|
+
break;
|
|
1011
1056
|
}
|
|
1012
1057
|
}
|
|
1013
1058
|
renderStroke(ctx, stroke) {
|
|
@@ -1143,6 +1188,20 @@ var ElementRenderer = class {
|
|
|
1143
1188
|
}
|
|
1144
1189
|
}
|
|
1145
1190
|
}
|
|
1191
|
+
renderImage(ctx, image) {
|
|
1192
|
+
const img = this.getImage(image.src);
|
|
1193
|
+
if (!img) return;
|
|
1194
|
+
ctx.drawImage(img, image.position.x, image.position.y, image.size.w, image.size.h);
|
|
1195
|
+
}
|
|
1196
|
+
getImage(src) {
|
|
1197
|
+
const cached = this.imageCache.get(src);
|
|
1198
|
+
if (cached) return cached.complete ? cached : null;
|
|
1199
|
+
const img = new Image();
|
|
1200
|
+
img.src = src;
|
|
1201
|
+
this.imageCache.set(src, img);
|
|
1202
|
+
img.onload = () => this.onImageLoad?.();
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1146
1205
|
};
|
|
1147
1206
|
|
|
1148
1207
|
// src/elements/note-editor.ts
|
|
@@ -1494,6 +1553,7 @@ function createStroke(input) {
|
|
|
1494
1553
|
position: input.position ?? { x: 0, y: 0 },
|
|
1495
1554
|
zIndex: input.zIndex ?? 0,
|
|
1496
1555
|
locked: input.locked ?? false,
|
|
1556
|
+
layerId: input.layerId ?? "",
|
|
1497
1557
|
points: input.points,
|
|
1498
1558
|
color: input.color ?? "#000000",
|
|
1499
1559
|
width: input.width ?? 2,
|
|
@@ -1507,6 +1567,7 @@ function createNote(input) {
|
|
|
1507
1567
|
position: input.position,
|
|
1508
1568
|
zIndex: input.zIndex ?? 0,
|
|
1509
1569
|
locked: input.locked ?? false,
|
|
1570
|
+
layerId: input.layerId ?? "",
|
|
1510
1571
|
size: input.size ?? { w: 200, h: 100 },
|
|
1511
1572
|
text: input.text ?? "",
|
|
1512
1573
|
backgroundColor: input.backgroundColor ?? "#ffeb3b",
|
|
@@ -1520,6 +1581,7 @@ function createArrow(input) {
|
|
|
1520
1581
|
position: input.position ?? { x: 0, y: 0 },
|
|
1521
1582
|
zIndex: input.zIndex ?? 0,
|
|
1522
1583
|
locked: input.locked ?? false,
|
|
1584
|
+
layerId: input.layerId ?? "",
|
|
1523
1585
|
from: input.from,
|
|
1524
1586
|
to: input.to,
|
|
1525
1587
|
bend: input.bend ?? 0,
|
|
@@ -1537,6 +1599,7 @@ function createImage(input) {
|
|
|
1537
1599
|
position: input.position,
|
|
1538
1600
|
zIndex: input.zIndex ?? 0,
|
|
1539
1601
|
locked: input.locked ?? false,
|
|
1602
|
+
layerId: input.layerId ?? "",
|
|
1540
1603
|
size: input.size,
|
|
1541
1604
|
src: input.src
|
|
1542
1605
|
};
|
|
@@ -1548,6 +1611,7 @@ function createHtmlElement(input) {
|
|
|
1548
1611
|
position: input.position,
|
|
1549
1612
|
zIndex: input.zIndex ?? 0,
|
|
1550
1613
|
locked: input.locked ?? false,
|
|
1614
|
+
layerId: input.layerId ?? "",
|
|
1551
1615
|
size: input.size
|
|
1552
1616
|
};
|
|
1553
1617
|
}
|
|
@@ -1558,6 +1622,7 @@ function createShape(input) {
|
|
|
1558
1622
|
position: input.position,
|
|
1559
1623
|
zIndex: input.zIndex ?? 0,
|
|
1560
1624
|
locked: input.locked ?? false,
|
|
1625
|
+
layerId: input.layerId ?? "",
|
|
1561
1626
|
shape: input.shape ?? "rectangle",
|
|
1562
1627
|
size: input.size,
|
|
1563
1628
|
strokeColor: input.strokeColor ?? "#000000",
|
|
@@ -1572,6 +1637,7 @@ function createText(input) {
|
|
|
1572
1637
|
position: input.position,
|
|
1573
1638
|
zIndex: input.zIndex ?? 0,
|
|
1574
1639
|
locked: input.locked ?? false,
|
|
1640
|
+
layerId: input.layerId ?? "",
|
|
1575
1641
|
size: input.size ?? { w: 200, h: 28 },
|
|
1576
1642
|
text: input.text ?? "",
|
|
1577
1643
|
fontSize: input.fontSize ?? 16,
|
|
@@ -1580,6 +1646,155 @@ function createText(input) {
|
|
|
1580
1646
|
};
|
|
1581
1647
|
}
|
|
1582
1648
|
|
|
1649
|
+
// src/layers/layer-manager.ts
|
|
1650
|
+
var LayerManager = class {
|
|
1651
|
+
constructor(store) {
|
|
1652
|
+
this.store = store;
|
|
1653
|
+
const defaultLayer = {
|
|
1654
|
+
id: createId("layer"),
|
|
1655
|
+
name: "Layer 1",
|
|
1656
|
+
visible: true,
|
|
1657
|
+
locked: false,
|
|
1658
|
+
order: 0,
|
|
1659
|
+
opacity: 1
|
|
1660
|
+
};
|
|
1661
|
+
this.layers.set(defaultLayer.id, defaultLayer);
|
|
1662
|
+
this._activeLayerId = defaultLayer.id;
|
|
1663
|
+
this.syncLayerOrder();
|
|
1664
|
+
}
|
|
1665
|
+
layers = /* @__PURE__ */ new Map();
|
|
1666
|
+
_activeLayerId;
|
|
1667
|
+
bus = new EventBus();
|
|
1668
|
+
get activeLayer() {
|
|
1669
|
+
const layer = this.layers.get(this._activeLayerId);
|
|
1670
|
+
if (!layer) throw new Error("Active layer not found");
|
|
1671
|
+
return { ...layer };
|
|
1672
|
+
}
|
|
1673
|
+
get activeLayerId() {
|
|
1674
|
+
return this._activeLayerId;
|
|
1675
|
+
}
|
|
1676
|
+
getLayers() {
|
|
1677
|
+
return [...this.layers.values()].sort((a, b) => a.order - b.order).map((l) => ({ ...l }));
|
|
1678
|
+
}
|
|
1679
|
+
getLayer(id) {
|
|
1680
|
+
const layer = this.layers.get(id);
|
|
1681
|
+
return layer ? { ...layer } : void 0;
|
|
1682
|
+
}
|
|
1683
|
+
isLayerVisible(id) {
|
|
1684
|
+
return this.layers.get(id)?.visible ?? true;
|
|
1685
|
+
}
|
|
1686
|
+
isLayerLocked(id) {
|
|
1687
|
+
return this.layers.get(id)?.locked ?? false;
|
|
1688
|
+
}
|
|
1689
|
+
createLayer(name) {
|
|
1690
|
+
const maxOrder = Math.max(...[...this.layers.values()].map((l) => l.order), -1);
|
|
1691
|
+
const autoName = name ?? `Layer ${this.layers.size + 1}`;
|
|
1692
|
+
const layer = {
|
|
1693
|
+
id: createId("layer"),
|
|
1694
|
+
name: autoName,
|
|
1695
|
+
visible: true,
|
|
1696
|
+
locked: false,
|
|
1697
|
+
order: maxOrder + 1,
|
|
1698
|
+
opacity: 1
|
|
1699
|
+
};
|
|
1700
|
+
this.addLayerDirect(layer);
|
|
1701
|
+
return { ...layer };
|
|
1702
|
+
}
|
|
1703
|
+
removeLayer(id) {
|
|
1704
|
+
if (this.layers.size <= 1) {
|
|
1705
|
+
throw new Error("Cannot remove the last layer");
|
|
1706
|
+
}
|
|
1707
|
+
if (this._activeLayerId === id) {
|
|
1708
|
+
const remaining = [...this.layers.values()].filter((l) => l.id !== id).sort((a, b) => b.order - a.order);
|
|
1709
|
+
const fallback = remaining[0];
|
|
1710
|
+
if (fallback) this._activeLayerId = fallback.id;
|
|
1711
|
+
}
|
|
1712
|
+
const elements = this.store.getAll().filter((el) => el.layerId === id);
|
|
1713
|
+
for (const el of elements) {
|
|
1714
|
+
this.store.update(el.id, { layerId: this._activeLayerId });
|
|
1715
|
+
}
|
|
1716
|
+
this.removeLayerDirect(id);
|
|
1717
|
+
}
|
|
1718
|
+
renameLayer(id, name) {
|
|
1719
|
+
this.updateLayerDirect(id, { name });
|
|
1720
|
+
}
|
|
1721
|
+
reorderLayer(id, newOrder) {
|
|
1722
|
+
if (!this.layers.has(id)) return;
|
|
1723
|
+
this.updateLayerDirect(id, { order: newOrder });
|
|
1724
|
+
}
|
|
1725
|
+
setLayerVisible(id, visible) {
|
|
1726
|
+
if (!visible && id === this._activeLayerId) {
|
|
1727
|
+
const fallback = this.findFallbackLayer(id);
|
|
1728
|
+
if (!fallback) return false;
|
|
1729
|
+
this._activeLayerId = fallback.id;
|
|
1730
|
+
}
|
|
1731
|
+
this.updateLayerDirect(id, { visible });
|
|
1732
|
+
return true;
|
|
1733
|
+
}
|
|
1734
|
+
setLayerLocked(id, locked) {
|
|
1735
|
+
if (locked && id === this._activeLayerId) {
|
|
1736
|
+
const fallback = this.findFallbackLayer(id);
|
|
1737
|
+
if (!fallback) return false;
|
|
1738
|
+
this._activeLayerId = fallback.id;
|
|
1739
|
+
}
|
|
1740
|
+
this.updateLayerDirect(id, { locked });
|
|
1741
|
+
return true;
|
|
1742
|
+
}
|
|
1743
|
+
setActiveLayer(id) {
|
|
1744
|
+
if (!this.layers.has(id)) return;
|
|
1745
|
+
this._activeLayerId = id;
|
|
1746
|
+
this.bus.emit("change", null);
|
|
1747
|
+
}
|
|
1748
|
+
moveElementToLayer(elementId, layerId) {
|
|
1749
|
+
if (!this.layers.has(layerId)) return;
|
|
1750
|
+
this.store.update(elementId, { layerId });
|
|
1751
|
+
this.bus.emit("change", null);
|
|
1752
|
+
}
|
|
1753
|
+
snapshot() {
|
|
1754
|
+
return this.getLayers();
|
|
1755
|
+
}
|
|
1756
|
+
loadSnapshot(layers) {
|
|
1757
|
+
this.layers.clear();
|
|
1758
|
+
for (const layer of layers) {
|
|
1759
|
+
this.layers.set(layer.id, { ...layer });
|
|
1760
|
+
}
|
|
1761
|
+
const first = this.getLayers()[0];
|
|
1762
|
+
if (first) this._activeLayerId = first.id;
|
|
1763
|
+
this.syncLayerOrder();
|
|
1764
|
+
this.bus.emit("change", null);
|
|
1765
|
+
}
|
|
1766
|
+
on(event, callback) {
|
|
1767
|
+
return this.bus.on(event, callback);
|
|
1768
|
+
}
|
|
1769
|
+
addLayerDirect(layer) {
|
|
1770
|
+
this.layers.set(layer.id, { ...layer });
|
|
1771
|
+
this.syncLayerOrder();
|
|
1772
|
+
this.bus.emit("change", null);
|
|
1773
|
+
}
|
|
1774
|
+
removeLayerDirect(id) {
|
|
1775
|
+
this.layers.delete(id);
|
|
1776
|
+
this.syncLayerOrder();
|
|
1777
|
+
this.bus.emit("change", null);
|
|
1778
|
+
}
|
|
1779
|
+
updateLayerDirect(id, props) {
|
|
1780
|
+
const layer = this.layers.get(id);
|
|
1781
|
+
if (!layer) return;
|
|
1782
|
+
Object.assign(layer, props);
|
|
1783
|
+
if ("order" in props) this.syncLayerOrder();
|
|
1784
|
+
this.bus.emit("change", null);
|
|
1785
|
+
}
|
|
1786
|
+
syncLayerOrder() {
|
|
1787
|
+
const order = /* @__PURE__ */ new Map();
|
|
1788
|
+
for (const layer of this.layers.values()) {
|
|
1789
|
+
order.set(layer.id, layer.order);
|
|
1790
|
+
}
|
|
1791
|
+
this.store.setLayerOrder(order);
|
|
1792
|
+
}
|
|
1793
|
+
findFallbackLayer(excludeId) {
|
|
1794
|
+
return [...this.layers.values()].filter((l) => l.id !== excludeId && l.visible && !l.locked).sort((a, b) => b.order - a.order)[0];
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
|
|
1583
1798
|
// src/canvas/viewport.ts
|
|
1584
1799
|
var Viewport = class {
|
|
1585
1800
|
constructor(container, options = {}) {
|
|
@@ -1588,9 +1803,11 @@ var Viewport = class {
|
|
|
1588
1803
|
this.background = new Background(options.background);
|
|
1589
1804
|
this._gridSize = options.background?.spacing ?? 24;
|
|
1590
1805
|
this.store = new ElementStore();
|
|
1806
|
+
this.layerManager = new LayerManager(this.store);
|
|
1591
1807
|
this.toolManager = new ToolManager();
|
|
1592
1808
|
this.renderer = new ElementRenderer();
|
|
1593
1809
|
this.renderer.setStore(this.store);
|
|
1810
|
+
this.renderer.setOnImageLoad(() => this.requestRender());
|
|
1594
1811
|
this.noteEditor = new NoteEditor();
|
|
1595
1812
|
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
1596
1813
|
this.history = new HistoryStack();
|
|
@@ -1611,7 +1828,10 @@ var Viewport = class {
|
|
|
1611
1828
|
this.wrapper.style.cursor = cursor;
|
|
1612
1829
|
},
|
|
1613
1830
|
snapToGrid: false,
|
|
1614
|
-
gridSize: this._gridSize
|
|
1831
|
+
gridSize: this._gridSize,
|
|
1832
|
+
activeLayerId: this.layerManager.activeLayerId,
|
|
1833
|
+
isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
|
|
1834
|
+
isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
|
|
1615
1835
|
};
|
|
1616
1836
|
this.inputHandler = new InputHandler(this.wrapper, this.camera, {
|
|
1617
1837
|
toolManager: this.toolManager,
|
|
@@ -1632,6 +1852,10 @@ var Viewport = class {
|
|
|
1632
1852
|
this.store.on("update", () => this.requestRender()),
|
|
1633
1853
|
this.store.on("clear", () => this.clearDomNodes())
|
|
1634
1854
|
];
|
|
1855
|
+
this.layerManager.on("change", () => {
|
|
1856
|
+
this.toolContext.activeLayerId = this.layerManager.activeLayerId;
|
|
1857
|
+
this.requestRender();
|
|
1858
|
+
});
|
|
1635
1859
|
this.wrapper.addEventListener("dblclick", this.onDblClick);
|
|
1636
1860
|
this.wrapper.addEventListener("dragover", this.onDragOver);
|
|
1637
1861
|
this.wrapper.addEventListener("drop", this.onDrop);
|
|
@@ -1641,6 +1865,7 @@ var Viewport = class {
|
|
|
1641
1865
|
}
|
|
1642
1866
|
camera;
|
|
1643
1867
|
store;
|
|
1868
|
+
layerManager;
|
|
1644
1869
|
toolManager;
|
|
1645
1870
|
history;
|
|
1646
1871
|
domLayer;
|
|
@@ -1676,7 +1901,7 @@ var Viewport = class {
|
|
|
1676
1901
|
this.needsRender = true;
|
|
1677
1902
|
}
|
|
1678
1903
|
exportState() {
|
|
1679
|
-
return exportState(this.store.snapshot(), this.camera);
|
|
1904
|
+
return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
|
|
1680
1905
|
}
|
|
1681
1906
|
exportJSON() {
|
|
1682
1907
|
return JSON.stringify(this.exportState());
|
|
@@ -1686,6 +1911,9 @@ var Viewport = class {
|
|
|
1686
1911
|
this.noteEditor.destroy(this.store);
|
|
1687
1912
|
this.clearDomNodes();
|
|
1688
1913
|
this.store.loadSnapshot(state.elements);
|
|
1914
|
+
if (state.layers && state.layers.length > 0) {
|
|
1915
|
+
this.layerManager.loadSnapshot(state.layers);
|
|
1916
|
+
}
|
|
1689
1917
|
this.history.clear();
|
|
1690
1918
|
this.historyRecorder.resume();
|
|
1691
1919
|
this.camera.moveTo(state.camera.position.x, state.camera.position.y);
|
|
@@ -1709,7 +1937,7 @@ var Viewport = class {
|
|
|
1709
1937
|
return result;
|
|
1710
1938
|
}
|
|
1711
1939
|
addImage(src, position, size = { w: 300, h: 200 }) {
|
|
1712
|
-
const image = createImage({ position, size, src });
|
|
1940
|
+
const image = createImage({ position, size, src, layerId: this.layerManager.activeLayerId });
|
|
1713
1941
|
this.historyRecorder.begin();
|
|
1714
1942
|
this.store.add(image);
|
|
1715
1943
|
this.historyRecorder.commit();
|
|
@@ -1717,7 +1945,7 @@ var Viewport = class {
|
|
|
1717
1945
|
return image.id;
|
|
1718
1946
|
}
|
|
1719
1947
|
addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
|
|
1720
|
-
const el = createHtmlElement({ position, size });
|
|
1948
|
+
const el = createHtmlElement({ position, size, layerId: this.layerManager.activeLayerId });
|
|
1721
1949
|
this.htmlContent.set(el.id, dom);
|
|
1722
1950
|
this.historyRecorder.begin();
|
|
1723
1951
|
this.store.add(el);
|
|
@@ -1760,9 +1988,17 @@ var Viewport = class {
|
|
|
1760
1988
|
ctx.save();
|
|
1761
1989
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
1762
1990
|
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
1763
|
-
|
|
1991
|
+
const allElements = this.store.getAll();
|
|
1992
|
+
let domZIndex = 0;
|
|
1993
|
+
for (const element of allElements) {
|
|
1994
|
+
if (!this.layerManager.isLayerVisible(element.layerId)) {
|
|
1995
|
+
if (this.renderer.isDomElement(element)) {
|
|
1996
|
+
this.hideDomNode(element.id);
|
|
1997
|
+
}
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
1764
2000
|
if (this.renderer.isDomElement(element)) {
|
|
1765
|
-
this.syncDomNode(element);
|
|
2001
|
+
this.syncDomNode(element, domZIndex++);
|
|
1766
2002
|
} else {
|
|
1767
2003
|
this.renderer.renderCanvasElement(ctx, element);
|
|
1768
2004
|
}
|
|
@@ -1894,7 +2130,7 @@ var Viewport = class {
|
|
|
1894
2130
|
reader.readAsDataURL(file);
|
|
1895
2131
|
}
|
|
1896
2132
|
};
|
|
1897
|
-
syncDomNode(element) {
|
|
2133
|
+
syncDomNode(element, zIndex = 0) {
|
|
1898
2134
|
let node = this.domNodes.get(element.id);
|
|
1899
2135
|
if (!node) {
|
|
1900
2136
|
node = document.createElement("div");
|
|
@@ -1908,10 +2144,12 @@ var Viewport = class {
|
|
|
1908
2144
|
}
|
|
1909
2145
|
const size = "size" in element ? element.size : null;
|
|
1910
2146
|
Object.assign(node.style, {
|
|
2147
|
+
display: "block",
|
|
1911
2148
|
left: `${element.position.x}px`,
|
|
1912
2149
|
top: `${element.position.y}px`,
|
|
1913
2150
|
width: size ? `${size.w}px` : "auto",
|
|
1914
|
-
height: size ? `${size.h}px` : "auto"
|
|
2151
|
+
height: size ? `${size.h}px` : "auto",
|
|
2152
|
+
zIndex: String(zIndex)
|
|
1915
2153
|
});
|
|
1916
2154
|
this.renderDomContent(node, element);
|
|
1917
2155
|
}
|
|
@@ -1946,26 +2184,6 @@ var Viewport = class {
|
|
|
1946
2184
|
node.style.color = element.textColor;
|
|
1947
2185
|
}
|
|
1948
2186
|
}
|
|
1949
|
-
if (element.type === "image") {
|
|
1950
|
-
if (!node.dataset["initialized"]) {
|
|
1951
|
-
node.dataset["initialized"] = "true";
|
|
1952
|
-
const img = document.createElement("img");
|
|
1953
|
-
img.src = element.src;
|
|
1954
|
-
Object.assign(img.style, {
|
|
1955
|
-
width: "100%",
|
|
1956
|
-
height: "100%",
|
|
1957
|
-
objectFit: "contain",
|
|
1958
|
-
pointerEvents: "none"
|
|
1959
|
-
});
|
|
1960
|
-
img.draggable = false;
|
|
1961
|
-
node.appendChild(img);
|
|
1962
|
-
} else {
|
|
1963
|
-
const img = node.querySelector("img");
|
|
1964
|
-
if (img && img.src !== element.src) {
|
|
1965
|
-
img.src = element.src;
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
2187
|
if (element.type === "html" && !node.dataset["initialized"]) {
|
|
1970
2188
|
const content = this.htmlContent.get(element.id);
|
|
1971
2189
|
if (content) {
|
|
@@ -2048,6 +2266,10 @@ var Viewport = class {
|
|
|
2048
2266
|
}
|
|
2049
2267
|
}
|
|
2050
2268
|
}
|
|
2269
|
+
hideDomNode(id) {
|
|
2270
|
+
const node = this.domNodes.get(id);
|
|
2271
|
+
if (node) node.style.display = "none";
|
|
2272
|
+
}
|
|
2051
2273
|
removeDomNode(id) {
|
|
2052
2274
|
this.htmlContent.delete(id);
|
|
2053
2275
|
const node = this.domNodes.get(id);
|
|
@@ -2194,7 +2416,8 @@ var PencilTool = class {
|
|
|
2194
2416
|
const stroke = createStroke({
|
|
2195
2417
|
points: simplified,
|
|
2196
2418
|
color: this.color,
|
|
2197
|
-
width: this.width
|
|
2419
|
+
width: this.width,
|
|
2420
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2198
2421
|
});
|
|
2199
2422
|
ctx.store.add(stroke);
|
|
2200
2423
|
this.points = [];
|
|
@@ -2258,6 +2481,8 @@ var EraserTool = class {
|
|
|
2258
2481
|
const strokes = ctx.store.getElementsByType("stroke");
|
|
2259
2482
|
let erased = false;
|
|
2260
2483
|
for (const stroke of strokes) {
|
|
2484
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(stroke.layerId)) continue;
|
|
2485
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(stroke.layerId)) continue;
|
|
2261
2486
|
if (this.strokeIntersects(stroke, world)) {
|
|
2262
2487
|
ctx.store.remove(stroke.id);
|
|
2263
2488
|
erased = true;
|
|
@@ -2743,6 +2968,8 @@ var SelectTool = class {
|
|
|
2743
2968
|
findElementsInRect(marquee, ctx) {
|
|
2744
2969
|
const ids = [];
|
|
2745
2970
|
for (const el of ctx.store.getAll()) {
|
|
2971
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
2972
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
2746
2973
|
const bounds = this.getElementBounds(el);
|
|
2747
2974
|
if (bounds && this.rectsOverlap(marquee, bounds)) {
|
|
2748
2975
|
ids.push(el.id);
|
|
@@ -2777,6 +3004,8 @@ var SelectTool = class {
|
|
|
2777
3004
|
hitTest(world, ctx) {
|
|
2778
3005
|
const elements = ctx.store.getAll().reverse();
|
|
2779
3006
|
for (const el of elements) {
|
|
3007
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
3008
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
2780
3009
|
if (this.isInsideBounds(world, el)) return el;
|
|
2781
3010
|
}
|
|
2782
3011
|
return null;
|
|
@@ -2821,11 +3050,20 @@ var ArrowTool = class {
|
|
|
2821
3050
|
if (options.color !== void 0) this.color = options.color;
|
|
2822
3051
|
if (options.width !== void 0) this.width = options.width;
|
|
2823
3052
|
}
|
|
3053
|
+
layerFilter(ctx) {
|
|
3054
|
+
if (!ctx.isLayerVisible && !ctx.isLayerLocked) return void 0;
|
|
3055
|
+
return (el) => {
|
|
3056
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) return false;
|
|
3057
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) return false;
|
|
3058
|
+
return true;
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
2824
3061
|
onPointerDown(state, ctx) {
|
|
2825
3062
|
this.drawing = true;
|
|
2826
3063
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2827
3064
|
const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
|
|
2828
|
-
const
|
|
3065
|
+
const filter = this.layerFilter(ctx);
|
|
3066
|
+
const target = findBindTarget(world, ctx.store, threshold, void 0, filter);
|
|
2829
3067
|
if (target) {
|
|
2830
3068
|
this.start = getElementCenter(target);
|
|
2831
3069
|
this.fromBinding = { elementId: target.id };
|
|
@@ -2843,7 +3081,8 @@ var ArrowTool = class {
|
|
|
2843
3081
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2844
3082
|
const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
|
|
2845
3083
|
const excludeId = this.fromBinding?.elementId;
|
|
2846
|
-
const
|
|
3084
|
+
const filter = this.layerFilter(ctx);
|
|
3085
|
+
const target = findBindTarget(world, ctx.store, threshold, excludeId, filter);
|
|
2847
3086
|
if (target) {
|
|
2848
3087
|
this.end = getElementCenter(target);
|
|
2849
3088
|
this.toTarget = target;
|
|
@@ -2864,7 +3103,8 @@ var ArrowTool = class {
|
|
|
2864
3103
|
color: this.color,
|
|
2865
3104
|
width: this.width,
|
|
2866
3105
|
fromBinding: this.fromBinding,
|
|
2867
|
-
toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0
|
|
3106
|
+
toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0,
|
|
3107
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2868
3108
|
});
|
|
2869
3109
|
ctx.store.add(arrow);
|
|
2870
3110
|
this.fromTarget = null;
|
|
@@ -2951,7 +3191,8 @@ var NoteTool = class {
|
|
|
2951
3191
|
position: world,
|
|
2952
3192
|
size: { ...this.size },
|
|
2953
3193
|
backgroundColor: this.backgroundColor,
|
|
2954
|
-
textColor: this.textColor
|
|
3194
|
+
textColor: this.textColor,
|
|
3195
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2955
3196
|
});
|
|
2956
3197
|
ctx.store.add(note);
|
|
2957
3198
|
ctx.requestRender();
|
|
@@ -2995,7 +3236,8 @@ var TextTool = class {
|
|
|
2995
3236
|
position: world,
|
|
2996
3237
|
fontSize: this.fontSize,
|
|
2997
3238
|
color: this.color,
|
|
2998
|
-
textAlign: this.textAlign
|
|
3239
|
+
textAlign: this.textAlign,
|
|
3240
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2999
3241
|
});
|
|
3000
3242
|
ctx.store.add(textEl);
|
|
3001
3243
|
ctx.requestRender();
|
|
@@ -3091,7 +3333,8 @@ var ShapeTool = class {
|
|
|
3091
3333
|
shape: this.shape,
|
|
3092
3334
|
strokeColor: this.strokeColor,
|
|
3093
3335
|
strokeWidth: this.strokeWidth,
|
|
3094
|
-
fillColor: this.fillColor
|
|
3336
|
+
fillColor: this.fillColor,
|
|
3337
|
+
layerId: ctx.activeLayerId ?? ""
|
|
3095
3338
|
});
|
|
3096
3339
|
ctx.store.add(shape);
|
|
3097
3340
|
ctx.requestRender();
|
|
@@ -3152,8 +3395,48 @@ var ShapeTool = class {
|
|
|
3152
3395
|
};
|
|
3153
3396
|
};
|
|
3154
3397
|
|
|
3398
|
+
// src/history/layer-commands.ts
|
|
3399
|
+
var CreateLayerCommand = class {
|
|
3400
|
+
constructor(manager, layer) {
|
|
3401
|
+
this.manager = manager;
|
|
3402
|
+
this.layer = layer;
|
|
3403
|
+
}
|
|
3404
|
+
execute(_store) {
|
|
3405
|
+
this.manager.addLayerDirect(this.layer);
|
|
3406
|
+
}
|
|
3407
|
+
undo(_store) {
|
|
3408
|
+
this.manager.removeLayerDirect(this.layer.id);
|
|
3409
|
+
}
|
|
3410
|
+
};
|
|
3411
|
+
var RemoveLayerCommand = class {
|
|
3412
|
+
constructor(manager, layer) {
|
|
3413
|
+
this.manager = manager;
|
|
3414
|
+
this.layer = layer;
|
|
3415
|
+
}
|
|
3416
|
+
execute(_store) {
|
|
3417
|
+
this.manager.removeLayerDirect(this.layer.id);
|
|
3418
|
+
}
|
|
3419
|
+
undo(_store) {
|
|
3420
|
+
this.manager.addLayerDirect(this.layer);
|
|
3421
|
+
}
|
|
3422
|
+
};
|
|
3423
|
+
var UpdateLayerCommand = class {
|
|
3424
|
+
constructor(manager, layerId, previous, current) {
|
|
3425
|
+
this.manager = manager;
|
|
3426
|
+
this.layerId = layerId;
|
|
3427
|
+
this.previous = previous;
|
|
3428
|
+
this.current = current;
|
|
3429
|
+
}
|
|
3430
|
+
execute(_store) {
|
|
3431
|
+
this.manager.updateLayerDirect(this.layerId, { ...this.current });
|
|
3432
|
+
}
|
|
3433
|
+
undo(_store) {
|
|
3434
|
+
this.manager.updateLayerDirect(this.layerId, { ...this.previous });
|
|
3435
|
+
}
|
|
3436
|
+
};
|
|
3437
|
+
|
|
3155
3438
|
// src/index.ts
|
|
3156
|
-
var VERSION = "0.
|
|
3439
|
+
var VERSION = "0.6.0";
|
|
3157
3440
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3158
3441
|
0 && (module.exports = {
|
|
3159
3442
|
AddElementCommand,
|
|
@@ -3162,6 +3445,7 @@ var VERSION = "0.5.0";
|
|
|
3162
3445
|
Background,
|
|
3163
3446
|
BatchCommand,
|
|
3164
3447
|
Camera,
|
|
3448
|
+
CreateLayerCommand,
|
|
3165
3449
|
ElementRenderer,
|
|
3166
3450
|
ElementStore,
|
|
3167
3451
|
EraserTool,
|
|
@@ -3171,15 +3455,18 @@ var VERSION = "0.5.0";
|
|
|
3171
3455
|
HistoryStack,
|
|
3172
3456
|
ImageTool,
|
|
3173
3457
|
InputHandler,
|
|
3458
|
+
LayerManager,
|
|
3174
3459
|
NoteEditor,
|
|
3175
3460
|
NoteTool,
|
|
3176
3461
|
PencilTool,
|
|
3177
3462
|
RemoveElementCommand,
|
|
3463
|
+
RemoveLayerCommand,
|
|
3178
3464
|
SelectTool,
|
|
3179
3465
|
ShapeTool,
|
|
3180
3466
|
TextTool,
|
|
3181
3467
|
ToolManager,
|
|
3182
3468
|
UpdateElementCommand,
|
|
3469
|
+
UpdateLayerCommand,
|
|
3183
3470
|
VERSION,
|
|
3184
3471
|
Viewport,
|
|
3185
3472
|
clearStaleBindings,
|