@fieldnotes/core 0.5.0 → 0.6.1
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 +61 -11
- package/dist/index.cjs +335 -44
- 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 +331 -44
- 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,8 +284,13 @@ var AutoSave = class {
|
|
|
258
284
|
}
|
|
259
285
|
save() {
|
|
260
286
|
if (typeof localStorage === "undefined") return;
|
|
261
|
-
const
|
|
262
|
-
|
|
287
|
+
const layers = this.layerManager?.snapshot() ?? [];
|
|
288
|
+
const state = exportState(this.store.snapshot(), this.camera, layers);
|
|
289
|
+
try {
|
|
290
|
+
localStorage.setItem(this.key, JSON.stringify(state));
|
|
291
|
+
} catch {
|
|
292
|
+
console.warn("Auto-save failed: storage quota exceeded. State too large for localStorage.");
|
|
293
|
+
}
|
|
263
294
|
}
|
|
264
295
|
};
|
|
265
296
|
|
|
@@ -635,11 +666,20 @@ var InputHandler = class {
|
|
|
635
666
|
var ElementStore = class {
|
|
636
667
|
elements = /* @__PURE__ */ new Map();
|
|
637
668
|
bus = new EventBus();
|
|
669
|
+
layerOrderMap = /* @__PURE__ */ new Map();
|
|
638
670
|
get count() {
|
|
639
671
|
return this.elements.size;
|
|
640
672
|
}
|
|
673
|
+
setLayerOrder(order) {
|
|
674
|
+
this.layerOrderMap = new Map(order);
|
|
675
|
+
}
|
|
641
676
|
getAll() {
|
|
642
|
-
return [...this.elements.values()].sort((a, b) =>
|
|
677
|
+
return [...this.elements.values()].sort((a, b) => {
|
|
678
|
+
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
679
|
+
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
680
|
+
if (layerA !== layerB) return layerA - layerB;
|
|
681
|
+
return a.zIndex - b.zIndex;
|
|
682
|
+
});
|
|
643
683
|
}
|
|
644
684
|
getById(id) {
|
|
645
685
|
return this.elements.get(id);
|
|
@@ -823,12 +863,13 @@ function getEdgeIntersection(bounds, outsidePoint) {
|
|
|
823
863
|
y: cy + dy * scale
|
|
824
864
|
};
|
|
825
865
|
}
|
|
826
|
-
function findBindTarget(point, store, threshold, excludeId) {
|
|
866
|
+
function findBindTarget(point, store, threshold, excludeId, filter) {
|
|
827
867
|
let closest = null;
|
|
828
868
|
let closestDist = Infinity;
|
|
829
869
|
for (const el of store.getAll()) {
|
|
830
870
|
if (!isBindable(el)) continue;
|
|
831
871
|
if (excludeId && el.id === excludeId) continue;
|
|
872
|
+
if (filter && !filter(el)) continue;
|
|
832
873
|
const bounds = getElementBounds(el);
|
|
833
874
|
if (!bounds) continue;
|
|
834
875
|
const dist = distToBounds(point, bounds);
|
|
@@ -986,14 +1027,19 @@ function smoothToSegments(points) {
|
|
|
986
1027
|
}
|
|
987
1028
|
|
|
988
1029
|
// src/elements/element-renderer.ts
|
|
989
|
-
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "
|
|
1030
|
+
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
|
|
990
1031
|
var ARROWHEAD_LENGTH = 12;
|
|
991
1032
|
var ARROWHEAD_ANGLE = Math.PI / 6;
|
|
992
1033
|
var ElementRenderer = class {
|
|
993
1034
|
store = null;
|
|
1035
|
+
imageCache = /* @__PURE__ */ new Map();
|
|
1036
|
+
onImageLoad = null;
|
|
994
1037
|
setStore(store) {
|
|
995
1038
|
this.store = store;
|
|
996
1039
|
}
|
|
1040
|
+
setOnImageLoad(callback) {
|
|
1041
|
+
this.onImageLoad = callback;
|
|
1042
|
+
}
|
|
997
1043
|
isDomElement(element) {
|
|
998
1044
|
return DOM_ELEMENT_TYPES.has(element.type);
|
|
999
1045
|
}
|
|
@@ -1008,6 +1054,9 @@ var ElementRenderer = class {
|
|
|
1008
1054
|
case "shape":
|
|
1009
1055
|
this.renderShape(ctx, element);
|
|
1010
1056
|
break;
|
|
1057
|
+
case "image":
|
|
1058
|
+
this.renderImage(ctx, element);
|
|
1059
|
+
break;
|
|
1011
1060
|
}
|
|
1012
1061
|
}
|
|
1013
1062
|
renderStroke(ctx, stroke) {
|
|
@@ -1143,6 +1192,20 @@ var ElementRenderer = class {
|
|
|
1143
1192
|
}
|
|
1144
1193
|
}
|
|
1145
1194
|
}
|
|
1195
|
+
renderImage(ctx, image) {
|
|
1196
|
+
const img = this.getImage(image.src);
|
|
1197
|
+
if (!img) return;
|
|
1198
|
+
ctx.drawImage(img, image.position.x, image.position.y, image.size.w, image.size.h);
|
|
1199
|
+
}
|
|
1200
|
+
getImage(src) {
|
|
1201
|
+
const cached = this.imageCache.get(src);
|
|
1202
|
+
if (cached) return cached.complete ? cached : null;
|
|
1203
|
+
const img = new Image();
|
|
1204
|
+
img.src = src;
|
|
1205
|
+
this.imageCache.set(src, img);
|
|
1206
|
+
img.onload = () => this.onImageLoad?.();
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1146
1209
|
};
|
|
1147
1210
|
|
|
1148
1211
|
// src/elements/note-editor.ts
|
|
@@ -1494,6 +1557,7 @@ function createStroke(input) {
|
|
|
1494
1557
|
position: input.position ?? { x: 0, y: 0 },
|
|
1495
1558
|
zIndex: input.zIndex ?? 0,
|
|
1496
1559
|
locked: input.locked ?? false,
|
|
1560
|
+
layerId: input.layerId ?? "",
|
|
1497
1561
|
points: input.points,
|
|
1498
1562
|
color: input.color ?? "#000000",
|
|
1499
1563
|
width: input.width ?? 2,
|
|
@@ -1507,6 +1571,7 @@ function createNote(input) {
|
|
|
1507
1571
|
position: input.position,
|
|
1508
1572
|
zIndex: input.zIndex ?? 0,
|
|
1509
1573
|
locked: input.locked ?? false,
|
|
1574
|
+
layerId: input.layerId ?? "",
|
|
1510
1575
|
size: input.size ?? { w: 200, h: 100 },
|
|
1511
1576
|
text: input.text ?? "",
|
|
1512
1577
|
backgroundColor: input.backgroundColor ?? "#ffeb3b",
|
|
@@ -1520,6 +1585,7 @@ function createArrow(input) {
|
|
|
1520
1585
|
position: input.position ?? { x: 0, y: 0 },
|
|
1521
1586
|
zIndex: input.zIndex ?? 0,
|
|
1522
1587
|
locked: input.locked ?? false,
|
|
1588
|
+
layerId: input.layerId ?? "",
|
|
1523
1589
|
from: input.from,
|
|
1524
1590
|
to: input.to,
|
|
1525
1591
|
bend: input.bend ?? 0,
|
|
@@ -1537,6 +1603,7 @@ function createImage(input) {
|
|
|
1537
1603
|
position: input.position,
|
|
1538
1604
|
zIndex: input.zIndex ?? 0,
|
|
1539
1605
|
locked: input.locked ?? false,
|
|
1606
|
+
layerId: input.layerId ?? "",
|
|
1540
1607
|
size: input.size,
|
|
1541
1608
|
src: input.src
|
|
1542
1609
|
};
|
|
@@ -1548,6 +1615,7 @@ function createHtmlElement(input) {
|
|
|
1548
1615
|
position: input.position,
|
|
1549
1616
|
zIndex: input.zIndex ?? 0,
|
|
1550
1617
|
locked: input.locked ?? false,
|
|
1618
|
+
layerId: input.layerId ?? "",
|
|
1551
1619
|
size: input.size
|
|
1552
1620
|
};
|
|
1553
1621
|
}
|
|
@@ -1558,6 +1626,7 @@ function createShape(input) {
|
|
|
1558
1626
|
position: input.position,
|
|
1559
1627
|
zIndex: input.zIndex ?? 0,
|
|
1560
1628
|
locked: input.locked ?? false,
|
|
1629
|
+
layerId: input.layerId ?? "",
|
|
1561
1630
|
shape: input.shape ?? "rectangle",
|
|
1562
1631
|
size: input.size,
|
|
1563
1632
|
strokeColor: input.strokeColor ?? "#000000",
|
|
@@ -1572,6 +1641,7 @@ function createText(input) {
|
|
|
1572
1641
|
position: input.position,
|
|
1573
1642
|
zIndex: input.zIndex ?? 0,
|
|
1574
1643
|
locked: input.locked ?? false,
|
|
1644
|
+
layerId: input.layerId ?? "",
|
|
1575
1645
|
size: input.size ?? { w: 200, h: 28 },
|
|
1576
1646
|
text: input.text ?? "",
|
|
1577
1647
|
fontSize: input.fontSize ?? 16,
|
|
@@ -1580,6 +1650,155 @@ function createText(input) {
|
|
|
1580
1650
|
};
|
|
1581
1651
|
}
|
|
1582
1652
|
|
|
1653
|
+
// src/layers/layer-manager.ts
|
|
1654
|
+
var LayerManager = class {
|
|
1655
|
+
constructor(store) {
|
|
1656
|
+
this.store = store;
|
|
1657
|
+
const defaultLayer = {
|
|
1658
|
+
id: createId("layer"),
|
|
1659
|
+
name: "Layer 1",
|
|
1660
|
+
visible: true,
|
|
1661
|
+
locked: false,
|
|
1662
|
+
order: 0,
|
|
1663
|
+
opacity: 1
|
|
1664
|
+
};
|
|
1665
|
+
this.layers.set(defaultLayer.id, defaultLayer);
|
|
1666
|
+
this._activeLayerId = defaultLayer.id;
|
|
1667
|
+
this.syncLayerOrder();
|
|
1668
|
+
}
|
|
1669
|
+
layers = /* @__PURE__ */ new Map();
|
|
1670
|
+
_activeLayerId;
|
|
1671
|
+
bus = new EventBus();
|
|
1672
|
+
get activeLayer() {
|
|
1673
|
+
const layer = this.layers.get(this._activeLayerId);
|
|
1674
|
+
if (!layer) throw new Error("Active layer not found");
|
|
1675
|
+
return { ...layer };
|
|
1676
|
+
}
|
|
1677
|
+
get activeLayerId() {
|
|
1678
|
+
return this._activeLayerId;
|
|
1679
|
+
}
|
|
1680
|
+
getLayers() {
|
|
1681
|
+
return [...this.layers.values()].sort((a, b) => a.order - b.order).map((l) => ({ ...l }));
|
|
1682
|
+
}
|
|
1683
|
+
getLayer(id) {
|
|
1684
|
+
const layer = this.layers.get(id);
|
|
1685
|
+
return layer ? { ...layer } : void 0;
|
|
1686
|
+
}
|
|
1687
|
+
isLayerVisible(id) {
|
|
1688
|
+
return this.layers.get(id)?.visible ?? true;
|
|
1689
|
+
}
|
|
1690
|
+
isLayerLocked(id) {
|
|
1691
|
+
return this.layers.get(id)?.locked ?? false;
|
|
1692
|
+
}
|
|
1693
|
+
createLayer(name) {
|
|
1694
|
+
const maxOrder = Math.max(...[...this.layers.values()].map((l) => l.order), -1);
|
|
1695
|
+
const autoName = name ?? `Layer ${this.layers.size + 1}`;
|
|
1696
|
+
const layer = {
|
|
1697
|
+
id: createId("layer"),
|
|
1698
|
+
name: autoName,
|
|
1699
|
+
visible: true,
|
|
1700
|
+
locked: false,
|
|
1701
|
+
order: maxOrder + 1,
|
|
1702
|
+
opacity: 1
|
|
1703
|
+
};
|
|
1704
|
+
this.addLayerDirect(layer);
|
|
1705
|
+
return { ...layer };
|
|
1706
|
+
}
|
|
1707
|
+
removeLayer(id) {
|
|
1708
|
+
if (this.layers.size <= 1) {
|
|
1709
|
+
throw new Error("Cannot remove the last layer");
|
|
1710
|
+
}
|
|
1711
|
+
if (this._activeLayerId === id) {
|
|
1712
|
+
const remaining = [...this.layers.values()].filter((l) => l.id !== id).sort((a, b) => b.order - a.order);
|
|
1713
|
+
const fallback = remaining[0];
|
|
1714
|
+
if (fallback) this._activeLayerId = fallback.id;
|
|
1715
|
+
}
|
|
1716
|
+
const elements = this.store.getAll().filter((el) => el.layerId === id);
|
|
1717
|
+
for (const el of elements) {
|
|
1718
|
+
this.store.update(el.id, { layerId: this._activeLayerId });
|
|
1719
|
+
}
|
|
1720
|
+
this.removeLayerDirect(id);
|
|
1721
|
+
}
|
|
1722
|
+
renameLayer(id, name) {
|
|
1723
|
+
this.updateLayerDirect(id, { name });
|
|
1724
|
+
}
|
|
1725
|
+
reorderLayer(id, newOrder) {
|
|
1726
|
+
if (!this.layers.has(id)) return;
|
|
1727
|
+
this.updateLayerDirect(id, { order: newOrder });
|
|
1728
|
+
}
|
|
1729
|
+
setLayerVisible(id, visible) {
|
|
1730
|
+
if (!visible && id === this._activeLayerId) {
|
|
1731
|
+
const fallback = this.findFallbackLayer(id);
|
|
1732
|
+
if (!fallback) return false;
|
|
1733
|
+
this._activeLayerId = fallback.id;
|
|
1734
|
+
}
|
|
1735
|
+
this.updateLayerDirect(id, { visible });
|
|
1736
|
+
return true;
|
|
1737
|
+
}
|
|
1738
|
+
setLayerLocked(id, locked) {
|
|
1739
|
+
if (locked && id === this._activeLayerId) {
|
|
1740
|
+
const fallback = this.findFallbackLayer(id);
|
|
1741
|
+
if (!fallback) return false;
|
|
1742
|
+
this._activeLayerId = fallback.id;
|
|
1743
|
+
}
|
|
1744
|
+
this.updateLayerDirect(id, { locked });
|
|
1745
|
+
return true;
|
|
1746
|
+
}
|
|
1747
|
+
setActiveLayer(id) {
|
|
1748
|
+
if (!this.layers.has(id)) return;
|
|
1749
|
+
this._activeLayerId = id;
|
|
1750
|
+
this.bus.emit("change", null);
|
|
1751
|
+
}
|
|
1752
|
+
moveElementToLayer(elementId, layerId) {
|
|
1753
|
+
if (!this.layers.has(layerId)) return;
|
|
1754
|
+
this.store.update(elementId, { layerId });
|
|
1755
|
+
this.bus.emit("change", null);
|
|
1756
|
+
}
|
|
1757
|
+
snapshot() {
|
|
1758
|
+
return this.getLayers();
|
|
1759
|
+
}
|
|
1760
|
+
loadSnapshot(layers) {
|
|
1761
|
+
this.layers.clear();
|
|
1762
|
+
for (const layer of layers) {
|
|
1763
|
+
this.layers.set(layer.id, { ...layer });
|
|
1764
|
+
}
|
|
1765
|
+
const first = this.getLayers()[0];
|
|
1766
|
+
if (first) this._activeLayerId = first.id;
|
|
1767
|
+
this.syncLayerOrder();
|
|
1768
|
+
this.bus.emit("change", null);
|
|
1769
|
+
}
|
|
1770
|
+
on(event, callback) {
|
|
1771
|
+
return this.bus.on(event, callback);
|
|
1772
|
+
}
|
|
1773
|
+
addLayerDirect(layer) {
|
|
1774
|
+
this.layers.set(layer.id, { ...layer });
|
|
1775
|
+
this.syncLayerOrder();
|
|
1776
|
+
this.bus.emit("change", null);
|
|
1777
|
+
}
|
|
1778
|
+
removeLayerDirect(id) {
|
|
1779
|
+
this.layers.delete(id);
|
|
1780
|
+
this.syncLayerOrder();
|
|
1781
|
+
this.bus.emit("change", null);
|
|
1782
|
+
}
|
|
1783
|
+
updateLayerDirect(id, props) {
|
|
1784
|
+
const layer = this.layers.get(id);
|
|
1785
|
+
if (!layer) return;
|
|
1786
|
+
Object.assign(layer, props);
|
|
1787
|
+
if ("order" in props) this.syncLayerOrder();
|
|
1788
|
+
this.bus.emit("change", null);
|
|
1789
|
+
}
|
|
1790
|
+
syncLayerOrder() {
|
|
1791
|
+
const order = /* @__PURE__ */ new Map();
|
|
1792
|
+
for (const layer of this.layers.values()) {
|
|
1793
|
+
order.set(layer.id, layer.order);
|
|
1794
|
+
}
|
|
1795
|
+
this.store.setLayerOrder(order);
|
|
1796
|
+
}
|
|
1797
|
+
findFallbackLayer(excludeId) {
|
|
1798
|
+
return [...this.layers.values()].filter((l) => l.id !== excludeId && l.visible && !l.locked).sort((a, b) => b.order - a.order)[0];
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1583
1802
|
// src/canvas/viewport.ts
|
|
1584
1803
|
var Viewport = class {
|
|
1585
1804
|
constructor(container, options = {}) {
|
|
@@ -1588,9 +1807,11 @@ var Viewport = class {
|
|
|
1588
1807
|
this.background = new Background(options.background);
|
|
1589
1808
|
this._gridSize = options.background?.spacing ?? 24;
|
|
1590
1809
|
this.store = new ElementStore();
|
|
1810
|
+
this.layerManager = new LayerManager(this.store);
|
|
1591
1811
|
this.toolManager = new ToolManager();
|
|
1592
1812
|
this.renderer = new ElementRenderer();
|
|
1593
1813
|
this.renderer.setStore(this.store);
|
|
1814
|
+
this.renderer.setOnImageLoad(() => this.requestRender());
|
|
1594
1815
|
this.noteEditor = new NoteEditor();
|
|
1595
1816
|
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
1596
1817
|
this.history = new HistoryStack();
|
|
@@ -1611,7 +1832,10 @@ var Viewport = class {
|
|
|
1611
1832
|
this.wrapper.style.cursor = cursor;
|
|
1612
1833
|
},
|
|
1613
1834
|
snapToGrid: false,
|
|
1614
|
-
gridSize: this._gridSize
|
|
1835
|
+
gridSize: this._gridSize,
|
|
1836
|
+
activeLayerId: this.layerManager.activeLayerId,
|
|
1837
|
+
isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
|
|
1838
|
+
isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
|
|
1615
1839
|
};
|
|
1616
1840
|
this.inputHandler = new InputHandler(this.wrapper, this.camera, {
|
|
1617
1841
|
toolManager: this.toolManager,
|
|
@@ -1632,6 +1856,10 @@ var Viewport = class {
|
|
|
1632
1856
|
this.store.on("update", () => this.requestRender()),
|
|
1633
1857
|
this.store.on("clear", () => this.clearDomNodes())
|
|
1634
1858
|
];
|
|
1859
|
+
this.layerManager.on("change", () => {
|
|
1860
|
+
this.toolContext.activeLayerId = this.layerManager.activeLayerId;
|
|
1861
|
+
this.requestRender();
|
|
1862
|
+
});
|
|
1635
1863
|
this.wrapper.addEventListener("dblclick", this.onDblClick);
|
|
1636
1864
|
this.wrapper.addEventListener("dragover", this.onDragOver);
|
|
1637
1865
|
this.wrapper.addEventListener("drop", this.onDrop);
|
|
@@ -1641,6 +1869,7 @@ var Viewport = class {
|
|
|
1641
1869
|
}
|
|
1642
1870
|
camera;
|
|
1643
1871
|
store;
|
|
1872
|
+
layerManager;
|
|
1644
1873
|
toolManager;
|
|
1645
1874
|
history;
|
|
1646
1875
|
domLayer;
|
|
@@ -1676,7 +1905,7 @@ var Viewport = class {
|
|
|
1676
1905
|
this.needsRender = true;
|
|
1677
1906
|
}
|
|
1678
1907
|
exportState() {
|
|
1679
|
-
return exportState(this.store.snapshot(), this.camera);
|
|
1908
|
+
return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
|
|
1680
1909
|
}
|
|
1681
1910
|
exportJSON() {
|
|
1682
1911
|
return JSON.stringify(this.exportState());
|
|
@@ -1686,6 +1915,9 @@ var Viewport = class {
|
|
|
1686
1915
|
this.noteEditor.destroy(this.store);
|
|
1687
1916
|
this.clearDomNodes();
|
|
1688
1917
|
this.store.loadSnapshot(state.elements);
|
|
1918
|
+
if (state.layers && state.layers.length > 0) {
|
|
1919
|
+
this.layerManager.loadSnapshot(state.layers);
|
|
1920
|
+
}
|
|
1689
1921
|
this.history.clear();
|
|
1690
1922
|
this.historyRecorder.resume();
|
|
1691
1923
|
this.camera.moveTo(state.camera.position.x, state.camera.position.y);
|
|
@@ -1709,7 +1941,7 @@ var Viewport = class {
|
|
|
1709
1941
|
return result;
|
|
1710
1942
|
}
|
|
1711
1943
|
addImage(src, position, size = { w: 300, h: 200 }) {
|
|
1712
|
-
const image = createImage({ position, size, src });
|
|
1944
|
+
const image = createImage({ position, size, src, layerId: this.layerManager.activeLayerId });
|
|
1713
1945
|
this.historyRecorder.begin();
|
|
1714
1946
|
this.store.add(image);
|
|
1715
1947
|
this.historyRecorder.commit();
|
|
@@ -1717,7 +1949,7 @@ var Viewport = class {
|
|
|
1717
1949
|
return image.id;
|
|
1718
1950
|
}
|
|
1719
1951
|
addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
|
|
1720
|
-
const el = createHtmlElement({ position, size });
|
|
1952
|
+
const el = createHtmlElement({ position, size, layerId: this.layerManager.activeLayerId });
|
|
1721
1953
|
this.htmlContent.set(el.id, dom);
|
|
1722
1954
|
this.historyRecorder.begin();
|
|
1723
1955
|
this.store.add(el);
|
|
@@ -1760,9 +1992,17 @@ var Viewport = class {
|
|
|
1760
1992
|
ctx.save();
|
|
1761
1993
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
1762
1994
|
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
1763
|
-
|
|
1995
|
+
const allElements = this.store.getAll();
|
|
1996
|
+
let domZIndex = 0;
|
|
1997
|
+
for (const element of allElements) {
|
|
1998
|
+
if (!this.layerManager.isLayerVisible(element.layerId)) {
|
|
1999
|
+
if (this.renderer.isDomElement(element)) {
|
|
2000
|
+
this.hideDomNode(element.id);
|
|
2001
|
+
}
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
1764
2004
|
if (this.renderer.isDomElement(element)) {
|
|
1765
|
-
this.syncDomNode(element);
|
|
2005
|
+
this.syncDomNode(element, domZIndex++);
|
|
1766
2006
|
} else {
|
|
1767
2007
|
this.renderer.renderCanvasElement(ctx, element);
|
|
1768
2008
|
}
|
|
@@ -1894,7 +2134,7 @@ var Viewport = class {
|
|
|
1894
2134
|
reader.readAsDataURL(file);
|
|
1895
2135
|
}
|
|
1896
2136
|
};
|
|
1897
|
-
syncDomNode(element) {
|
|
2137
|
+
syncDomNode(element, zIndex = 0) {
|
|
1898
2138
|
let node = this.domNodes.get(element.id);
|
|
1899
2139
|
if (!node) {
|
|
1900
2140
|
node = document.createElement("div");
|
|
@@ -1908,10 +2148,12 @@ var Viewport = class {
|
|
|
1908
2148
|
}
|
|
1909
2149
|
const size = "size" in element ? element.size : null;
|
|
1910
2150
|
Object.assign(node.style, {
|
|
2151
|
+
display: "block",
|
|
1911
2152
|
left: `${element.position.x}px`,
|
|
1912
2153
|
top: `${element.position.y}px`,
|
|
1913
2154
|
width: size ? `${size.w}px` : "auto",
|
|
1914
|
-
height: size ? `${size.h}px` : "auto"
|
|
2155
|
+
height: size ? `${size.h}px` : "auto",
|
|
2156
|
+
zIndex: String(zIndex)
|
|
1915
2157
|
});
|
|
1916
2158
|
this.renderDomContent(node, element);
|
|
1917
2159
|
}
|
|
@@ -1946,26 +2188,6 @@ var Viewport = class {
|
|
|
1946
2188
|
node.style.color = element.textColor;
|
|
1947
2189
|
}
|
|
1948
2190
|
}
|
|
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
2191
|
if (element.type === "html" && !node.dataset["initialized"]) {
|
|
1970
2192
|
const content = this.htmlContent.get(element.id);
|
|
1971
2193
|
if (content) {
|
|
@@ -2048,6 +2270,10 @@ var Viewport = class {
|
|
|
2048
2270
|
}
|
|
2049
2271
|
}
|
|
2050
2272
|
}
|
|
2273
|
+
hideDomNode(id) {
|
|
2274
|
+
const node = this.domNodes.get(id);
|
|
2275
|
+
if (node) node.style.display = "none";
|
|
2276
|
+
}
|
|
2051
2277
|
removeDomNode(id) {
|
|
2052
2278
|
this.htmlContent.delete(id);
|
|
2053
2279
|
const node = this.domNodes.get(id);
|
|
@@ -2194,7 +2420,8 @@ var PencilTool = class {
|
|
|
2194
2420
|
const stroke = createStroke({
|
|
2195
2421
|
points: simplified,
|
|
2196
2422
|
color: this.color,
|
|
2197
|
-
width: this.width
|
|
2423
|
+
width: this.width,
|
|
2424
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2198
2425
|
});
|
|
2199
2426
|
ctx.store.add(stroke);
|
|
2200
2427
|
this.points = [];
|
|
@@ -2258,6 +2485,8 @@ var EraserTool = class {
|
|
|
2258
2485
|
const strokes = ctx.store.getElementsByType("stroke");
|
|
2259
2486
|
let erased = false;
|
|
2260
2487
|
for (const stroke of strokes) {
|
|
2488
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(stroke.layerId)) continue;
|
|
2489
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(stroke.layerId)) continue;
|
|
2261
2490
|
if (this.strokeIntersects(stroke, world)) {
|
|
2262
2491
|
ctx.store.remove(stroke.id);
|
|
2263
2492
|
erased = true;
|
|
@@ -2743,6 +2972,8 @@ var SelectTool = class {
|
|
|
2743
2972
|
findElementsInRect(marquee, ctx) {
|
|
2744
2973
|
const ids = [];
|
|
2745
2974
|
for (const el of ctx.store.getAll()) {
|
|
2975
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
2976
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
2746
2977
|
const bounds = this.getElementBounds(el);
|
|
2747
2978
|
if (bounds && this.rectsOverlap(marquee, bounds)) {
|
|
2748
2979
|
ids.push(el.id);
|
|
@@ -2777,6 +3008,8 @@ var SelectTool = class {
|
|
|
2777
3008
|
hitTest(world, ctx) {
|
|
2778
3009
|
const elements = ctx.store.getAll().reverse();
|
|
2779
3010
|
for (const el of elements) {
|
|
3011
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
3012
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
2780
3013
|
if (this.isInsideBounds(world, el)) return el;
|
|
2781
3014
|
}
|
|
2782
3015
|
return null;
|
|
@@ -2821,11 +3054,20 @@ var ArrowTool = class {
|
|
|
2821
3054
|
if (options.color !== void 0) this.color = options.color;
|
|
2822
3055
|
if (options.width !== void 0) this.width = options.width;
|
|
2823
3056
|
}
|
|
3057
|
+
layerFilter(ctx) {
|
|
3058
|
+
if (!ctx.isLayerVisible && !ctx.isLayerLocked) return void 0;
|
|
3059
|
+
return (el) => {
|
|
3060
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) return false;
|
|
3061
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) return false;
|
|
3062
|
+
return true;
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
2824
3065
|
onPointerDown(state, ctx) {
|
|
2825
3066
|
this.drawing = true;
|
|
2826
3067
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2827
3068
|
const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
|
|
2828
|
-
const
|
|
3069
|
+
const filter = this.layerFilter(ctx);
|
|
3070
|
+
const target = findBindTarget(world, ctx.store, threshold, void 0, filter);
|
|
2829
3071
|
if (target) {
|
|
2830
3072
|
this.start = getElementCenter(target);
|
|
2831
3073
|
this.fromBinding = { elementId: target.id };
|
|
@@ -2843,7 +3085,8 @@ var ArrowTool = class {
|
|
|
2843
3085
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2844
3086
|
const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
|
|
2845
3087
|
const excludeId = this.fromBinding?.elementId;
|
|
2846
|
-
const
|
|
3088
|
+
const filter = this.layerFilter(ctx);
|
|
3089
|
+
const target = findBindTarget(world, ctx.store, threshold, excludeId, filter);
|
|
2847
3090
|
if (target) {
|
|
2848
3091
|
this.end = getElementCenter(target);
|
|
2849
3092
|
this.toTarget = target;
|
|
@@ -2864,7 +3107,8 @@ var ArrowTool = class {
|
|
|
2864
3107
|
color: this.color,
|
|
2865
3108
|
width: this.width,
|
|
2866
3109
|
fromBinding: this.fromBinding,
|
|
2867
|
-
toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0
|
|
3110
|
+
toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0,
|
|
3111
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2868
3112
|
});
|
|
2869
3113
|
ctx.store.add(arrow);
|
|
2870
3114
|
this.fromTarget = null;
|
|
@@ -2951,7 +3195,8 @@ var NoteTool = class {
|
|
|
2951
3195
|
position: world,
|
|
2952
3196
|
size: { ...this.size },
|
|
2953
3197
|
backgroundColor: this.backgroundColor,
|
|
2954
|
-
textColor: this.textColor
|
|
3198
|
+
textColor: this.textColor,
|
|
3199
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2955
3200
|
});
|
|
2956
3201
|
ctx.store.add(note);
|
|
2957
3202
|
ctx.requestRender();
|
|
@@ -2995,7 +3240,8 @@ var TextTool = class {
|
|
|
2995
3240
|
position: world,
|
|
2996
3241
|
fontSize: this.fontSize,
|
|
2997
3242
|
color: this.color,
|
|
2998
|
-
textAlign: this.textAlign
|
|
3243
|
+
textAlign: this.textAlign,
|
|
3244
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2999
3245
|
});
|
|
3000
3246
|
ctx.store.add(textEl);
|
|
3001
3247
|
ctx.requestRender();
|
|
@@ -3091,7 +3337,8 @@ var ShapeTool = class {
|
|
|
3091
3337
|
shape: this.shape,
|
|
3092
3338
|
strokeColor: this.strokeColor,
|
|
3093
3339
|
strokeWidth: this.strokeWidth,
|
|
3094
|
-
fillColor: this.fillColor
|
|
3340
|
+
fillColor: this.fillColor,
|
|
3341
|
+
layerId: ctx.activeLayerId ?? ""
|
|
3095
3342
|
});
|
|
3096
3343
|
ctx.store.add(shape);
|
|
3097
3344
|
ctx.requestRender();
|
|
@@ -3152,8 +3399,48 @@ var ShapeTool = class {
|
|
|
3152
3399
|
};
|
|
3153
3400
|
};
|
|
3154
3401
|
|
|
3402
|
+
// src/history/layer-commands.ts
|
|
3403
|
+
var CreateLayerCommand = class {
|
|
3404
|
+
constructor(manager, layer) {
|
|
3405
|
+
this.manager = manager;
|
|
3406
|
+
this.layer = layer;
|
|
3407
|
+
}
|
|
3408
|
+
execute(_store) {
|
|
3409
|
+
this.manager.addLayerDirect(this.layer);
|
|
3410
|
+
}
|
|
3411
|
+
undo(_store) {
|
|
3412
|
+
this.manager.removeLayerDirect(this.layer.id);
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
var RemoveLayerCommand = class {
|
|
3416
|
+
constructor(manager, layer) {
|
|
3417
|
+
this.manager = manager;
|
|
3418
|
+
this.layer = layer;
|
|
3419
|
+
}
|
|
3420
|
+
execute(_store) {
|
|
3421
|
+
this.manager.removeLayerDirect(this.layer.id);
|
|
3422
|
+
}
|
|
3423
|
+
undo(_store) {
|
|
3424
|
+
this.manager.addLayerDirect(this.layer);
|
|
3425
|
+
}
|
|
3426
|
+
};
|
|
3427
|
+
var UpdateLayerCommand = class {
|
|
3428
|
+
constructor(manager, layerId, previous, current) {
|
|
3429
|
+
this.manager = manager;
|
|
3430
|
+
this.layerId = layerId;
|
|
3431
|
+
this.previous = previous;
|
|
3432
|
+
this.current = current;
|
|
3433
|
+
}
|
|
3434
|
+
execute(_store) {
|
|
3435
|
+
this.manager.updateLayerDirect(this.layerId, { ...this.current });
|
|
3436
|
+
}
|
|
3437
|
+
undo(_store) {
|
|
3438
|
+
this.manager.updateLayerDirect(this.layerId, { ...this.previous });
|
|
3439
|
+
}
|
|
3440
|
+
};
|
|
3441
|
+
|
|
3155
3442
|
// src/index.ts
|
|
3156
|
-
var VERSION = "0.
|
|
3443
|
+
var VERSION = "0.6.1";
|
|
3157
3444
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3158
3445
|
0 && (module.exports = {
|
|
3159
3446
|
AddElementCommand,
|
|
@@ -3162,6 +3449,7 @@ var VERSION = "0.5.0";
|
|
|
3162
3449
|
Background,
|
|
3163
3450
|
BatchCommand,
|
|
3164
3451
|
Camera,
|
|
3452
|
+
CreateLayerCommand,
|
|
3165
3453
|
ElementRenderer,
|
|
3166
3454
|
ElementStore,
|
|
3167
3455
|
EraserTool,
|
|
@@ -3171,15 +3459,18 @@ var VERSION = "0.5.0";
|
|
|
3171
3459
|
HistoryStack,
|
|
3172
3460
|
ImageTool,
|
|
3173
3461
|
InputHandler,
|
|
3462
|
+
LayerManager,
|
|
3174
3463
|
NoteEditor,
|
|
3175
3464
|
NoteTool,
|
|
3176
3465
|
PencilTool,
|
|
3177
3466
|
RemoveElementCommand,
|
|
3467
|
+
RemoveLayerCommand,
|
|
3178
3468
|
SelectTool,
|
|
3179
3469
|
ShapeTool,
|
|
3180
3470
|
TextTool,
|
|
3181
3471
|
ToolManager,
|
|
3182
3472
|
UpdateElementCommand,
|
|
3473
|
+
UpdateLayerCommand,
|
|
3183
3474
|
VERSION,
|
|
3184
3475
|
Viewport,
|
|
3185
3476
|
clearStaleBindings,
|