@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.js
CHANGED
|
@@ -23,15 +23,16 @@ var EventBus = class {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
// src/core/state-serializer.ts
|
|
26
|
-
var CURRENT_VERSION =
|
|
27
|
-
function exportState(elements, camera) {
|
|
26
|
+
var CURRENT_VERSION = 2;
|
|
27
|
+
function exportState(elements, camera, layers = []) {
|
|
28
28
|
return {
|
|
29
29
|
version: CURRENT_VERSION,
|
|
30
30
|
camera: {
|
|
31
31
|
position: { ...camera.position },
|
|
32
32
|
zoom: camera.zoom
|
|
33
33
|
},
|
|
34
|
-
elements: elements.map((el) => structuredClone(el))
|
|
34
|
+
elements: elements.map((el) => structuredClone(el)),
|
|
35
|
+
layers: layers.map((l) => ({ ...l }))
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
function parseState(json) {
|
|
@@ -69,6 +70,19 @@ function validateState(data) {
|
|
|
69
70
|
migrateElement(el);
|
|
70
71
|
}
|
|
71
72
|
cleanBindings(obj["elements"]);
|
|
73
|
+
const layers = obj["layers"];
|
|
74
|
+
if (!Array.isArray(layers) || layers.length === 0) {
|
|
75
|
+
obj["layers"] = [
|
|
76
|
+
{
|
|
77
|
+
id: "default-layer",
|
|
78
|
+
name: "Layer 1",
|
|
79
|
+
visible: true,
|
|
80
|
+
locked: false,
|
|
81
|
+
order: 0,
|
|
82
|
+
opacity: 1
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
}
|
|
72
86
|
}
|
|
73
87
|
var VALID_TYPES = /* @__PURE__ */ new Set(["stroke", "note", "arrow", "image", "html", "text", "shape"]);
|
|
74
88
|
function validateElement(el) {
|
|
@@ -101,6 +115,9 @@ function cleanBindings(elements) {
|
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
117
|
function migrateElement(obj) {
|
|
118
|
+
if (typeof obj["layerId"] !== "string") {
|
|
119
|
+
obj["layerId"] = "default-layer";
|
|
120
|
+
}
|
|
104
121
|
if (obj["type"] === "arrow" && typeof obj["bend"] !== "number") {
|
|
105
122
|
obj["bend"] = 0;
|
|
106
123
|
}
|
|
@@ -136,9 +153,11 @@ var AutoSave = class {
|
|
|
136
153
|
this.camera = camera;
|
|
137
154
|
this.key = options.key ?? DEFAULT_KEY;
|
|
138
155
|
this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
156
|
+
this.layerManager = options.layerManager;
|
|
139
157
|
}
|
|
140
158
|
key;
|
|
141
159
|
debounceMs;
|
|
160
|
+
layerManager;
|
|
142
161
|
timerId = null;
|
|
143
162
|
unsubscribers = [];
|
|
144
163
|
start() {
|
|
@@ -149,6 +168,9 @@ var AutoSave = class {
|
|
|
149
168
|
this.store.on("update", schedule),
|
|
150
169
|
this.camera.onChange(schedule)
|
|
151
170
|
];
|
|
171
|
+
if (this.layerManager) {
|
|
172
|
+
this.unsubscribers.push(this.layerManager.on("change", schedule));
|
|
173
|
+
}
|
|
152
174
|
}
|
|
153
175
|
stop() {
|
|
154
176
|
this.cancelPending();
|
|
@@ -181,7 +203,8 @@ var AutoSave = class {
|
|
|
181
203
|
}
|
|
182
204
|
save() {
|
|
183
205
|
if (typeof localStorage === "undefined") return;
|
|
184
|
-
const
|
|
206
|
+
const layers = this.layerManager?.snapshot() ?? [];
|
|
207
|
+
const state = exportState(this.store.snapshot(), this.camera, layers);
|
|
185
208
|
localStorage.setItem(this.key, JSON.stringify(state));
|
|
186
209
|
}
|
|
187
210
|
};
|
|
@@ -558,11 +581,20 @@ var InputHandler = class {
|
|
|
558
581
|
var ElementStore = class {
|
|
559
582
|
elements = /* @__PURE__ */ new Map();
|
|
560
583
|
bus = new EventBus();
|
|
584
|
+
layerOrderMap = /* @__PURE__ */ new Map();
|
|
561
585
|
get count() {
|
|
562
586
|
return this.elements.size;
|
|
563
587
|
}
|
|
588
|
+
setLayerOrder(order) {
|
|
589
|
+
this.layerOrderMap = new Map(order);
|
|
590
|
+
}
|
|
564
591
|
getAll() {
|
|
565
|
-
return [...this.elements.values()].sort((a, b) =>
|
|
592
|
+
return [...this.elements.values()].sort((a, b) => {
|
|
593
|
+
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
594
|
+
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
595
|
+
if (layerA !== layerB) return layerA - layerB;
|
|
596
|
+
return a.zIndex - b.zIndex;
|
|
597
|
+
});
|
|
566
598
|
}
|
|
567
599
|
getById(id) {
|
|
568
600
|
return this.elements.get(id);
|
|
@@ -746,12 +778,13 @@ function getEdgeIntersection(bounds, outsidePoint) {
|
|
|
746
778
|
y: cy + dy * scale
|
|
747
779
|
};
|
|
748
780
|
}
|
|
749
|
-
function findBindTarget(point, store, threshold, excludeId) {
|
|
781
|
+
function findBindTarget(point, store, threshold, excludeId, filter) {
|
|
750
782
|
let closest = null;
|
|
751
783
|
let closestDist = Infinity;
|
|
752
784
|
for (const el of store.getAll()) {
|
|
753
785
|
if (!isBindable(el)) continue;
|
|
754
786
|
if (excludeId && el.id === excludeId) continue;
|
|
787
|
+
if (filter && !filter(el)) continue;
|
|
755
788
|
const bounds = getElementBounds(el);
|
|
756
789
|
if (!bounds) continue;
|
|
757
790
|
const dist = distToBounds(point, bounds);
|
|
@@ -909,14 +942,19 @@ function smoothToSegments(points) {
|
|
|
909
942
|
}
|
|
910
943
|
|
|
911
944
|
// src/elements/element-renderer.ts
|
|
912
|
-
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "
|
|
945
|
+
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
|
|
913
946
|
var ARROWHEAD_LENGTH = 12;
|
|
914
947
|
var ARROWHEAD_ANGLE = Math.PI / 6;
|
|
915
948
|
var ElementRenderer = class {
|
|
916
949
|
store = null;
|
|
950
|
+
imageCache = /* @__PURE__ */ new Map();
|
|
951
|
+
onImageLoad = null;
|
|
917
952
|
setStore(store) {
|
|
918
953
|
this.store = store;
|
|
919
954
|
}
|
|
955
|
+
setOnImageLoad(callback) {
|
|
956
|
+
this.onImageLoad = callback;
|
|
957
|
+
}
|
|
920
958
|
isDomElement(element) {
|
|
921
959
|
return DOM_ELEMENT_TYPES.has(element.type);
|
|
922
960
|
}
|
|
@@ -931,6 +969,9 @@ var ElementRenderer = class {
|
|
|
931
969
|
case "shape":
|
|
932
970
|
this.renderShape(ctx, element);
|
|
933
971
|
break;
|
|
972
|
+
case "image":
|
|
973
|
+
this.renderImage(ctx, element);
|
|
974
|
+
break;
|
|
934
975
|
}
|
|
935
976
|
}
|
|
936
977
|
renderStroke(ctx, stroke) {
|
|
@@ -1066,6 +1107,20 @@ var ElementRenderer = class {
|
|
|
1066
1107
|
}
|
|
1067
1108
|
}
|
|
1068
1109
|
}
|
|
1110
|
+
renderImage(ctx, image) {
|
|
1111
|
+
const img = this.getImage(image.src);
|
|
1112
|
+
if (!img) return;
|
|
1113
|
+
ctx.drawImage(img, image.position.x, image.position.y, image.size.w, image.size.h);
|
|
1114
|
+
}
|
|
1115
|
+
getImage(src) {
|
|
1116
|
+
const cached = this.imageCache.get(src);
|
|
1117
|
+
if (cached) return cached.complete ? cached : null;
|
|
1118
|
+
const img = new Image();
|
|
1119
|
+
img.src = src;
|
|
1120
|
+
this.imageCache.set(src, img);
|
|
1121
|
+
img.onload = () => this.onImageLoad?.();
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1069
1124
|
};
|
|
1070
1125
|
|
|
1071
1126
|
// src/elements/note-editor.ts
|
|
@@ -1417,6 +1472,7 @@ function createStroke(input) {
|
|
|
1417
1472
|
position: input.position ?? { x: 0, y: 0 },
|
|
1418
1473
|
zIndex: input.zIndex ?? 0,
|
|
1419
1474
|
locked: input.locked ?? false,
|
|
1475
|
+
layerId: input.layerId ?? "",
|
|
1420
1476
|
points: input.points,
|
|
1421
1477
|
color: input.color ?? "#000000",
|
|
1422
1478
|
width: input.width ?? 2,
|
|
@@ -1430,6 +1486,7 @@ function createNote(input) {
|
|
|
1430
1486
|
position: input.position,
|
|
1431
1487
|
zIndex: input.zIndex ?? 0,
|
|
1432
1488
|
locked: input.locked ?? false,
|
|
1489
|
+
layerId: input.layerId ?? "",
|
|
1433
1490
|
size: input.size ?? { w: 200, h: 100 },
|
|
1434
1491
|
text: input.text ?? "",
|
|
1435
1492
|
backgroundColor: input.backgroundColor ?? "#ffeb3b",
|
|
@@ -1443,6 +1500,7 @@ function createArrow(input) {
|
|
|
1443
1500
|
position: input.position ?? { x: 0, y: 0 },
|
|
1444
1501
|
zIndex: input.zIndex ?? 0,
|
|
1445
1502
|
locked: input.locked ?? false,
|
|
1503
|
+
layerId: input.layerId ?? "",
|
|
1446
1504
|
from: input.from,
|
|
1447
1505
|
to: input.to,
|
|
1448
1506
|
bend: input.bend ?? 0,
|
|
@@ -1460,6 +1518,7 @@ function createImage(input) {
|
|
|
1460
1518
|
position: input.position,
|
|
1461
1519
|
zIndex: input.zIndex ?? 0,
|
|
1462
1520
|
locked: input.locked ?? false,
|
|
1521
|
+
layerId: input.layerId ?? "",
|
|
1463
1522
|
size: input.size,
|
|
1464
1523
|
src: input.src
|
|
1465
1524
|
};
|
|
@@ -1471,6 +1530,7 @@ function createHtmlElement(input) {
|
|
|
1471
1530
|
position: input.position,
|
|
1472
1531
|
zIndex: input.zIndex ?? 0,
|
|
1473
1532
|
locked: input.locked ?? false,
|
|
1533
|
+
layerId: input.layerId ?? "",
|
|
1474
1534
|
size: input.size
|
|
1475
1535
|
};
|
|
1476
1536
|
}
|
|
@@ -1481,6 +1541,7 @@ function createShape(input) {
|
|
|
1481
1541
|
position: input.position,
|
|
1482
1542
|
zIndex: input.zIndex ?? 0,
|
|
1483
1543
|
locked: input.locked ?? false,
|
|
1544
|
+
layerId: input.layerId ?? "",
|
|
1484
1545
|
shape: input.shape ?? "rectangle",
|
|
1485
1546
|
size: input.size,
|
|
1486
1547
|
strokeColor: input.strokeColor ?? "#000000",
|
|
@@ -1495,6 +1556,7 @@ function createText(input) {
|
|
|
1495
1556
|
position: input.position,
|
|
1496
1557
|
zIndex: input.zIndex ?? 0,
|
|
1497
1558
|
locked: input.locked ?? false,
|
|
1559
|
+
layerId: input.layerId ?? "",
|
|
1498
1560
|
size: input.size ?? { w: 200, h: 28 },
|
|
1499
1561
|
text: input.text ?? "",
|
|
1500
1562
|
fontSize: input.fontSize ?? 16,
|
|
@@ -1503,6 +1565,155 @@ function createText(input) {
|
|
|
1503
1565
|
};
|
|
1504
1566
|
}
|
|
1505
1567
|
|
|
1568
|
+
// src/layers/layer-manager.ts
|
|
1569
|
+
var LayerManager = class {
|
|
1570
|
+
constructor(store) {
|
|
1571
|
+
this.store = store;
|
|
1572
|
+
const defaultLayer = {
|
|
1573
|
+
id: createId("layer"),
|
|
1574
|
+
name: "Layer 1",
|
|
1575
|
+
visible: true,
|
|
1576
|
+
locked: false,
|
|
1577
|
+
order: 0,
|
|
1578
|
+
opacity: 1
|
|
1579
|
+
};
|
|
1580
|
+
this.layers.set(defaultLayer.id, defaultLayer);
|
|
1581
|
+
this._activeLayerId = defaultLayer.id;
|
|
1582
|
+
this.syncLayerOrder();
|
|
1583
|
+
}
|
|
1584
|
+
layers = /* @__PURE__ */ new Map();
|
|
1585
|
+
_activeLayerId;
|
|
1586
|
+
bus = new EventBus();
|
|
1587
|
+
get activeLayer() {
|
|
1588
|
+
const layer = this.layers.get(this._activeLayerId);
|
|
1589
|
+
if (!layer) throw new Error("Active layer not found");
|
|
1590
|
+
return { ...layer };
|
|
1591
|
+
}
|
|
1592
|
+
get activeLayerId() {
|
|
1593
|
+
return this._activeLayerId;
|
|
1594
|
+
}
|
|
1595
|
+
getLayers() {
|
|
1596
|
+
return [...this.layers.values()].sort((a, b) => a.order - b.order).map((l) => ({ ...l }));
|
|
1597
|
+
}
|
|
1598
|
+
getLayer(id) {
|
|
1599
|
+
const layer = this.layers.get(id);
|
|
1600
|
+
return layer ? { ...layer } : void 0;
|
|
1601
|
+
}
|
|
1602
|
+
isLayerVisible(id) {
|
|
1603
|
+
return this.layers.get(id)?.visible ?? true;
|
|
1604
|
+
}
|
|
1605
|
+
isLayerLocked(id) {
|
|
1606
|
+
return this.layers.get(id)?.locked ?? false;
|
|
1607
|
+
}
|
|
1608
|
+
createLayer(name) {
|
|
1609
|
+
const maxOrder = Math.max(...[...this.layers.values()].map((l) => l.order), -1);
|
|
1610
|
+
const autoName = name ?? `Layer ${this.layers.size + 1}`;
|
|
1611
|
+
const layer = {
|
|
1612
|
+
id: createId("layer"),
|
|
1613
|
+
name: autoName,
|
|
1614
|
+
visible: true,
|
|
1615
|
+
locked: false,
|
|
1616
|
+
order: maxOrder + 1,
|
|
1617
|
+
opacity: 1
|
|
1618
|
+
};
|
|
1619
|
+
this.addLayerDirect(layer);
|
|
1620
|
+
return { ...layer };
|
|
1621
|
+
}
|
|
1622
|
+
removeLayer(id) {
|
|
1623
|
+
if (this.layers.size <= 1) {
|
|
1624
|
+
throw new Error("Cannot remove the last layer");
|
|
1625
|
+
}
|
|
1626
|
+
if (this._activeLayerId === id) {
|
|
1627
|
+
const remaining = [...this.layers.values()].filter((l) => l.id !== id).sort((a, b) => b.order - a.order);
|
|
1628
|
+
const fallback = remaining[0];
|
|
1629
|
+
if (fallback) this._activeLayerId = fallback.id;
|
|
1630
|
+
}
|
|
1631
|
+
const elements = this.store.getAll().filter((el) => el.layerId === id);
|
|
1632
|
+
for (const el of elements) {
|
|
1633
|
+
this.store.update(el.id, { layerId: this._activeLayerId });
|
|
1634
|
+
}
|
|
1635
|
+
this.removeLayerDirect(id);
|
|
1636
|
+
}
|
|
1637
|
+
renameLayer(id, name) {
|
|
1638
|
+
this.updateLayerDirect(id, { name });
|
|
1639
|
+
}
|
|
1640
|
+
reorderLayer(id, newOrder) {
|
|
1641
|
+
if (!this.layers.has(id)) return;
|
|
1642
|
+
this.updateLayerDirect(id, { order: newOrder });
|
|
1643
|
+
}
|
|
1644
|
+
setLayerVisible(id, visible) {
|
|
1645
|
+
if (!visible && id === this._activeLayerId) {
|
|
1646
|
+
const fallback = this.findFallbackLayer(id);
|
|
1647
|
+
if (!fallback) return false;
|
|
1648
|
+
this._activeLayerId = fallback.id;
|
|
1649
|
+
}
|
|
1650
|
+
this.updateLayerDirect(id, { visible });
|
|
1651
|
+
return true;
|
|
1652
|
+
}
|
|
1653
|
+
setLayerLocked(id, locked) {
|
|
1654
|
+
if (locked && id === this._activeLayerId) {
|
|
1655
|
+
const fallback = this.findFallbackLayer(id);
|
|
1656
|
+
if (!fallback) return false;
|
|
1657
|
+
this._activeLayerId = fallback.id;
|
|
1658
|
+
}
|
|
1659
|
+
this.updateLayerDirect(id, { locked });
|
|
1660
|
+
return true;
|
|
1661
|
+
}
|
|
1662
|
+
setActiveLayer(id) {
|
|
1663
|
+
if (!this.layers.has(id)) return;
|
|
1664
|
+
this._activeLayerId = id;
|
|
1665
|
+
this.bus.emit("change", null);
|
|
1666
|
+
}
|
|
1667
|
+
moveElementToLayer(elementId, layerId) {
|
|
1668
|
+
if (!this.layers.has(layerId)) return;
|
|
1669
|
+
this.store.update(elementId, { layerId });
|
|
1670
|
+
this.bus.emit("change", null);
|
|
1671
|
+
}
|
|
1672
|
+
snapshot() {
|
|
1673
|
+
return this.getLayers();
|
|
1674
|
+
}
|
|
1675
|
+
loadSnapshot(layers) {
|
|
1676
|
+
this.layers.clear();
|
|
1677
|
+
for (const layer of layers) {
|
|
1678
|
+
this.layers.set(layer.id, { ...layer });
|
|
1679
|
+
}
|
|
1680
|
+
const first = this.getLayers()[0];
|
|
1681
|
+
if (first) this._activeLayerId = first.id;
|
|
1682
|
+
this.syncLayerOrder();
|
|
1683
|
+
this.bus.emit("change", null);
|
|
1684
|
+
}
|
|
1685
|
+
on(event, callback) {
|
|
1686
|
+
return this.bus.on(event, callback);
|
|
1687
|
+
}
|
|
1688
|
+
addLayerDirect(layer) {
|
|
1689
|
+
this.layers.set(layer.id, { ...layer });
|
|
1690
|
+
this.syncLayerOrder();
|
|
1691
|
+
this.bus.emit("change", null);
|
|
1692
|
+
}
|
|
1693
|
+
removeLayerDirect(id) {
|
|
1694
|
+
this.layers.delete(id);
|
|
1695
|
+
this.syncLayerOrder();
|
|
1696
|
+
this.bus.emit("change", null);
|
|
1697
|
+
}
|
|
1698
|
+
updateLayerDirect(id, props) {
|
|
1699
|
+
const layer = this.layers.get(id);
|
|
1700
|
+
if (!layer) return;
|
|
1701
|
+
Object.assign(layer, props);
|
|
1702
|
+
if ("order" in props) this.syncLayerOrder();
|
|
1703
|
+
this.bus.emit("change", null);
|
|
1704
|
+
}
|
|
1705
|
+
syncLayerOrder() {
|
|
1706
|
+
const order = /* @__PURE__ */ new Map();
|
|
1707
|
+
for (const layer of this.layers.values()) {
|
|
1708
|
+
order.set(layer.id, layer.order);
|
|
1709
|
+
}
|
|
1710
|
+
this.store.setLayerOrder(order);
|
|
1711
|
+
}
|
|
1712
|
+
findFallbackLayer(excludeId) {
|
|
1713
|
+
return [...this.layers.values()].filter((l) => l.id !== excludeId && l.visible && !l.locked).sort((a, b) => b.order - a.order)[0];
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
|
|
1506
1717
|
// src/canvas/viewport.ts
|
|
1507
1718
|
var Viewport = class {
|
|
1508
1719
|
constructor(container, options = {}) {
|
|
@@ -1511,9 +1722,11 @@ var Viewport = class {
|
|
|
1511
1722
|
this.background = new Background(options.background);
|
|
1512
1723
|
this._gridSize = options.background?.spacing ?? 24;
|
|
1513
1724
|
this.store = new ElementStore();
|
|
1725
|
+
this.layerManager = new LayerManager(this.store);
|
|
1514
1726
|
this.toolManager = new ToolManager();
|
|
1515
1727
|
this.renderer = new ElementRenderer();
|
|
1516
1728
|
this.renderer.setStore(this.store);
|
|
1729
|
+
this.renderer.setOnImageLoad(() => this.requestRender());
|
|
1517
1730
|
this.noteEditor = new NoteEditor();
|
|
1518
1731
|
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
1519
1732
|
this.history = new HistoryStack();
|
|
@@ -1534,7 +1747,10 @@ var Viewport = class {
|
|
|
1534
1747
|
this.wrapper.style.cursor = cursor;
|
|
1535
1748
|
},
|
|
1536
1749
|
snapToGrid: false,
|
|
1537
|
-
gridSize: this._gridSize
|
|
1750
|
+
gridSize: this._gridSize,
|
|
1751
|
+
activeLayerId: this.layerManager.activeLayerId,
|
|
1752
|
+
isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
|
|
1753
|
+
isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
|
|
1538
1754
|
};
|
|
1539
1755
|
this.inputHandler = new InputHandler(this.wrapper, this.camera, {
|
|
1540
1756
|
toolManager: this.toolManager,
|
|
@@ -1555,6 +1771,10 @@ var Viewport = class {
|
|
|
1555
1771
|
this.store.on("update", () => this.requestRender()),
|
|
1556
1772
|
this.store.on("clear", () => this.clearDomNodes())
|
|
1557
1773
|
];
|
|
1774
|
+
this.layerManager.on("change", () => {
|
|
1775
|
+
this.toolContext.activeLayerId = this.layerManager.activeLayerId;
|
|
1776
|
+
this.requestRender();
|
|
1777
|
+
});
|
|
1558
1778
|
this.wrapper.addEventListener("dblclick", this.onDblClick);
|
|
1559
1779
|
this.wrapper.addEventListener("dragover", this.onDragOver);
|
|
1560
1780
|
this.wrapper.addEventListener("drop", this.onDrop);
|
|
@@ -1564,6 +1784,7 @@ var Viewport = class {
|
|
|
1564
1784
|
}
|
|
1565
1785
|
camera;
|
|
1566
1786
|
store;
|
|
1787
|
+
layerManager;
|
|
1567
1788
|
toolManager;
|
|
1568
1789
|
history;
|
|
1569
1790
|
domLayer;
|
|
@@ -1599,7 +1820,7 @@ var Viewport = class {
|
|
|
1599
1820
|
this.needsRender = true;
|
|
1600
1821
|
}
|
|
1601
1822
|
exportState() {
|
|
1602
|
-
return exportState(this.store.snapshot(), this.camera);
|
|
1823
|
+
return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
|
|
1603
1824
|
}
|
|
1604
1825
|
exportJSON() {
|
|
1605
1826
|
return JSON.stringify(this.exportState());
|
|
@@ -1609,6 +1830,9 @@ var Viewport = class {
|
|
|
1609
1830
|
this.noteEditor.destroy(this.store);
|
|
1610
1831
|
this.clearDomNodes();
|
|
1611
1832
|
this.store.loadSnapshot(state.elements);
|
|
1833
|
+
if (state.layers && state.layers.length > 0) {
|
|
1834
|
+
this.layerManager.loadSnapshot(state.layers);
|
|
1835
|
+
}
|
|
1612
1836
|
this.history.clear();
|
|
1613
1837
|
this.historyRecorder.resume();
|
|
1614
1838
|
this.camera.moveTo(state.camera.position.x, state.camera.position.y);
|
|
@@ -1632,7 +1856,7 @@ var Viewport = class {
|
|
|
1632
1856
|
return result;
|
|
1633
1857
|
}
|
|
1634
1858
|
addImage(src, position, size = { w: 300, h: 200 }) {
|
|
1635
|
-
const image = createImage({ position, size, src });
|
|
1859
|
+
const image = createImage({ position, size, src, layerId: this.layerManager.activeLayerId });
|
|
1636
1860
|
this.historyRecorder.begin();
|
|
1637
1861
|
this.store.add(image);
|
|
1638
1862
|
this.historyRecorder.commit();
|
|
@@ -1640,7 +1864,7 @@ var Viewport = class {
|
|
|
1640
1864
|
return image.id;
|
|
1641
1865
|
}
|
|
1642
1866
|
addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
|
|
1643
|
-
const el = createHtmlElement({ position, size });
|
|
1867
|
+
const el = createHtmlElement({ position, size, layerId: this.layerManager.activeLayerId });
|
|
1644
1868
|
this.htmlContent.set(el.id, dom);
|
|
1645
1869
|
this.historyRecorder.begin();
|
|
1646
1870
|
this.store.add(el);
|
|
@@ -1683,9 +1907,17 @@ var Viewport = class {
|
|
|
1683
1907
|
ctx.save();
|
|
1684
1908
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
1685
1909
|
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
1686
|
-
|
|
1910
|
+
const allElements = this.store.getAll();
|
|
1911
|
+
let domZIndex = 0;
|
|
1912
|
+
for (const element of allElements) {
|
|
1913
|
+
if (!this.layerManager.isLayerVisible(element.layerId)) {
|
|
1914
|
+
if (this.renderer.isDomElement(element)) {
|
|
1915
|
+
this.hideDomNode(element.id);
|
|
1916
|
+
}
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1687
1919
|
if (this.renderer.isDomElement(element)) {
|
|
1688
|
-
this.syncDomNode(element);
|
|
1920
|
+
this.syncDomNode(element, domZIndex++);
|
|
1689
1921
|
} else {
|
|
1690
1922
|
this.renderer.renderCanvasElement(ctx, element);
|
|
1691
1923
|
}
|
|
@@ -1817,7 +2049,7 @@ var Viewport = class {
|
|
|
1817
2049
|
reader.readAsDataURL(file);
|
|
1818
2050
|
}
|
|
1819
2051
|
};
|
|
1820
|
-
syncDomNode(element) {
|
|
2052
|
+
syncDomNode(element, zIndex = 0) {
|
|
1821
2053
|
let node = this.domNodes.get(element.id);
|
|
1822
2054
|
if (!node) {
|
|
1823
2055
|
node = document.createElement("div");
|
|
@@ -1831,10 +2063,12 @@ var Viewport = class {
|
|
|
1831
2063
|
}
|
|
1832
2064
|
const size = "size" in element ? element.size : null;
|
|
1833
2065
|
Object.assign(node.style, {
|
|
2066
|
+
display: "block",
|
|
1834
2067
|
left: `${element.position.x}px`,
|
|
1835
2068
|
top: `${element.position.y}px`,
|
|
1836
2069
|
width: size ? `${size.w}px` : "auto",
|
|
1837
|
-
height: size ? `${size.h}px` : "auto"
|
|
2070
|
+
height: size ? `${size.h}px` : "auto",
|
|
2071
|
+
zIndex: String(zIndex)
|
|
1838
2072
|
});
|
|
1839
2073
|
this.renderDomContent(node, element);
|
|
1840
2074
|
}
|
|
@@ -1869,26 +2103,6 @@ var Viewport = class {
|
|
|
1869
2103
|
node.style.color = element.textColor;
|
|
1870
2104
|
}
|
|
1871
2105
|
}
|
|
1872
|
-
if (element.type === "image") {
|
|
1873
|
-
if (!node.dataset["initialized"]) {
|
|
1874
|
-
node.dataset["initialized"] = "true";
|
|
1875
|
-
const img = document.createElement("img");
|
|
1876
|
-
img.src = element.src;
|
|
1877
|
-
Object.assign(img.style, {
|
|
1878
|
-
width: "100%",
|
|
1879
|
-
height: "100%",
|
|
1880
|
-
objectFit: "contain",
|
|
1881
|
-
pointerEvents: "none"
|
|
1882
|
-
});
|
|
1883
|
-
img.draggable = false;
|
|
1884
|
-
node.appendChild(img);
|
|
1885
|
-
} else {
|
|
1886
|
-
const img = node.querySelector("img");
|
|
1887
|
-
if (img && img.src !== element.src) {
|
|
1888
|
-
img.src = element.src;
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
2106
|
if (element.type === "html" && !node.dataset["initialized"]) {
|
|
1893
2107
|
const content = this.htmlContent.get(element.id);
|
|
1894
2108
|
if (content) {
|
|
@@ -1971,6 +2185,10 @@ var Viewport = class {
|
|
|
1971
2185
|
}
|
|
1972
2186
|
}
|
|
1973
2187
|
}
|
|
2188
|
+
hideDomNode(id) {
|
|
2189
|
+
const node = this.domNodes.get(id);
|
|
2190
|
+
if (node) node.style.display = "none";
|
|
2191
|
+
}
|
|
1974
2192
|
removeDomNode(id) {
|
|
1975
2193
|
this.htmlContent.delete(id);
|
|
1976
2194
|
const node = this.domNodes.get(id);
|
|
@@ -2117,7 +2335,8 @@ var PencilTool = class {
|
|
|
2117
2335
|
const stroke = createStroke({
|
|
2118
2336
|
points: simplified,
|
|
2119
2337
|
color: this.color,
|
|
2120
|
-
width: this.width
|
|
2338
|
+
width: this.width,
|
|
2339
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2121
2340
|
});
|
|
2122
2341
|
ctx.store.add(stroke);
|
|
2123
2342
|
this.points = [];
|
|
@@ -2181,6 +2400,8 @@ var EraserTool = class {
|
|
|
2181
2400
|
const strokes = ctx.store.getElementsByType("stroke");
|
|
2182
2401
|
let erased = false;
|
|
2183
2402
|
for (const stroke of strokes) {
|
|
2403
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(stroke.layerId)) continue;
|
|
2404
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(stroke.layerId)) continue;
|
|
2184
2405
|
if (this.strokeIntersects(stroke, world)) {
|
|
2185
2406
|
ctx.store.remove(stroke.id);
|
|
2186
2407
|
erased = true;
|
|
@@ -2666,6 +2887,8 @@ var SelectTool = class {
|
|
|
2666
2887
|
findElementsInRect(marquee, ctx) {
|
|
2667
2888
|
const ids = [];
|
|
2668
2889
|
for (const el of ctx.store.getAll()) {
|
|
2890
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
2891
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
2669
2892
|
const bounds = this.getElementBounds(el);
|
|
2670
2893
|
if (bounds && this.rectsOverlap(marquee, bounds)) {
|
|
2671
2894
|
ids.push(el.id);
|
|
@@ -2700,6 +2923,8 @@ var SelectTool = class {
|
|
|
2700
2923
|
hitTest(world, ctx) {
|
|
2701
2924
|
const elements = ctx.store.getAll().reverse();
|
|
2702
2925
|
for (const el of elements) {
|
|
2926
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
2927
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
2703
2928
|
if (this.isInsideBounds(world, el)) return el;
|
|
2704
2929
|
}
|
|
2705
2930
|
return null;
|
|
@@ -2744,11 +2969,20 @@ var ArrowTool = class {
|
|
|
2744
2969
|
if (options.color !== void 0) this.color = options.color;
|
|
2745
2970
|
if (options.width !== void 0) this.width = options.width;
|
|
2746
2971
|
}
|
|
2972
|
+
layerFilter(ctx) {
|
|
2973
|
+
if (!ctx.isLayerVisible && !ctx.isLayerLocked) return void 0;
|
|
2974
|
+
return (el) => {
|
|
2975
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) return false;
|
|
2976
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) return false;
|
|
2977
|
+
return true;
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2747
2980
|
onPointerDown(state, ctx) {
|
|
2748
2981
|
this.drawing = true;
|
|
2749
2982
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2750
2983
|
const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
|
|
2751
|
-
const
|
|
2984
|
+
const filter = this.layerFilter(ctx);
|
|
2985
|
+
const target = findBindTarget(world, ctx.store, threshold, void 0, filter);
|
|
2752
2986
|
if (target) {
|
|
2753
2987
|
this.start = getElementCenter(target);
|
|
2754
2988
|
this.fromBinding = { elementId: target.id };
|
|
@@ -2766,7 +3000,8 @@ var ArrowTool = class {
|
|
|
2766
3000
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2767
3001
|
const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
|
|
2768
3002
|
const excludeId = this.fromBinding?.elementId;
|
|
2769
|
-
const
|
|
3003
|
+
const filter = this.layerFilter(ctx);
|
|
3004
|
+
const target = findBindTarget(world, ctx.store, threshold, excludeId, filter);
|
|
2770
3005
|
if (target) {
|
|
2771
3006
|
this.end = getElementCenter(target);
|
|
2772
3007
|
this.toTarget = target;
|
|
@@ -2787,7 +3022,8 @@ var ArrowTool = class {
|
|
|
2787
3022
|
color: this.color,
|
|
2788
3023
|
width: this.width,
|
|
2789
3024
|
fromBinding: this.fromBinding,
|
|
2790
|
-
toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0
|
|
3025
|
+
toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0,
|
|
3026
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2791
3027
|
});
|
|
2792
3028
|
ctx.store.add(arrow);
|
|
2793
3029
|
this.fromTarget = null;
|
|
@@ -2874,7 +3110,8 @@ var NoteTool = class {
|
|
|
2874
3110
|
position: world,
|
|
2875
3111
|
size: { ...this.size },
|
|
2876
3112
|
backgroundColor: this.backgroundColor,
|
|
2877
|
-
textColor: this.textColor
|
|
3113
|
+
textColor: this.textColor,
|
|
3114
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2878
3115
|
});
|
|
2879
3116
|
ctx.store.add(note);
|
|
2880
3117
|
ctx.requestRender();
|
|
@@ -2918,7 +3155,8 @@ var TextTool = class {
|
|
|
2918
3155
|
position: world,
|
|
2919
3156
|
fontSize: this.fontSize,
|
|
2920
3157
|
color: this.color,
|
|
2921
|
-
textAlign: this.textAlign
|
|
3158
|
+
textAlign: this.textAlign,
|
|
3159
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2922
3160
|
});
|
|
2923
3161
|
ctx.store.add(textEl);
|
|
2924
3162
|
ctx.requestRender();
|
|
@@ -3014,7 +3252,8 @@ var ShapeTool = class {
|
|
|
3014
3252
|
shape: this.shape,
|
|
3015
3253
|
strokeColor: this.strokeColor,
|
|
3016
3254
|
strokeWidth: this.strokeWidth,
|
|
3017
|
-
fillColor: this.fillColor
|
|
3255
|
+
fillColor: this.fillColor,
|
|
3256
|
+
layerId: ctx.activeLayerId ?? ""
|
|
3018
3257
|
});
|
|
3019
3258
|
ctx.store.add(shape);
|
|
3020
3259
|
ctx.requestRender();
|
|
@@ -3075,8 +3314,48 @@ var ShapeTool = class {
|
|
|
3075
3314
|
};
|
|
3076
3315
|
};
|
|
3077
3316
|
|
|
3317
|
+
// src/history/layer-commands.ts
|
|
3318
|
+
var CreateLayerCommand = class {
|
|
3319
|
+
constructor(manager, layer) {
|
|
3320
|
+
this.manager = manager;
|
|
3321
|
+
this.layer = layer;
|
|
3322
|
+
}
|
|
3323
|
+
execute(_store) {
|
|
3324
|
+
this.manager.addLayerDirect(this.layer);
|
|
3325
|
+
}
|
|
3326
|
+
undo(_store) {
|
|
3327
|
+
this.manager.removeLayerDirect(this.layer.id);
|
|
3328
|
+
}
|
|
3329
|
+
};
|
|
3330
|
+
var RemoveLayerCommand = class {
|
|
3331
|
+
constructor(manager, layer) {
|
|
3332
|
+
this.manager = manager;
|
|
3333
|
+
this.layer = layer;
|
|
3334
|
+
}
|
|
3335
|
+
execute(_store) {
|
|
3336
|
+
this.manager.removeLayerDirect(this.layer.id);
|
|
3337
|
+
}
|
|
3338
|
+
undo(_store) {
|
|
3339
|
+
this.manager.addLayerDirect(this.layer);
|
|
3340
|
+
}
|
|
3341
|
+
};
|
|
3342
|
+
var UpdateLayerCommand = class {
|
|
3343
|
+
constructor(manager, layerId, previous, current) {
|
|
3344
|
+
this.manager = manager;
|
|
3345
|
+
this.layerId = layerId;
|
|
3346
|
+
this.previous = previous;
|
|
3347
|
+
this.current = current;
|
|
3348
|
+
}
|
|
3349
|
+
execute(_store) {
|
|
3350
|
+
this.manager.updateLayerDirect(this.layerId, { ...this.current });
|
|
3351
|
+
}
|
|
3352
|
+
undo(_store) {
|
|
3353
|
+
this.manager.updateLayerDirect(this.layerId, { ...this.previous });
|
|
3354
|
+
}
|
|
3355
|
+
};
|
|
3356
|
+
|
|
3078
3357
|
// src/index.ts
|
|
3079
|
-
var VERSION = "0.
|
|
3358
|
+
var VERSION = "0.6.0";
|
|
3080
3359
|
export {
|
|
3081
3360
|
AddElementCommand,
|
|
3082
3361
|
ArrowTool,
|
|
@@ -3084,6 +3363,7 @@ export {
|
|
|
3084
3363
|
Background,
|
|
3085
3364
|
BatchCommand,
|
|
3086
3365
|
Camera,
|
|
3366
|
+
CreateLayerCommand,
|
|
3087
3367
|
ElementRenderer,
|
|
3088
3368
|
ElementStore,
|
|
3089
3369
|
EraserTool,
|
|
@@ -3093,15 +3373,18 @@ export {
|
|
|
3093
3373
|
HistoryStack,
|
|
3094
3374
|
ImageTool,
|
|
3095
3375
|
InputHandler,
|
|
3376
|
+
LayerManager,
|
|
3096
3377
|
NoteEditor,
|
|
3097
3378
|
NoteTool,
|
|
3098
3379
|
PencilTool,
|
|
3099
3380
|
RemoveElementCommand,
|
|
3381
|
+
RemoveLayerCommand,
|
|
3100
3382
|
SelectTool,
|
|
3101
3383
|
ShapeTool,
|
|
3102
3384
|
TextTool,
|
|
3103
3385
|
ToolManager,
|
|
3104
3386
|
UpdateElementCommand,
|
|
3387
|
+
UpdateLayerCommand,
|
|
3105
3388
|
VERSION,
|
|
3106
3389
|
Viewport,
|
|
3107
3390
|
clearStaleBindings,
|