@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.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,8 +203,13 @@ var AutoSave = class {
|
|
|
181
203
|
}
|
|
182
204
|
save() {
|
|
183
205
|
if (typeof localStorage === "undefined") return;
|
|
184
|
-
const
|
|
185
|
-
|
|
206
|
+
const layers = this.layerManager?.snapshot() ?? [];
|
|
207
|
+
const state = exportState(this.store.snapshot(), this.camera, layers);
|
|
208
|
+
try {
|
|
209
|
+
localStorage.setItem(this.key, JSON.stringify(state));
|
|
210
|
+
} catch {
|
|
211
|
+
console.warn("Auto-save failed: storage quota exceeded. State too large for localStorage.");
|
|
212
|
+
}
|
|
186
213
|
}
|
|
187
214
|
};
|
|
188
215
|
|
|
@@ -558,11 +585,20 @@ var InputHandler = class {
|
|
|
558
585
|
var ElementStore = class {
|
|
559
586
|
elements = /* @__PURE__ */ new Map();
|
|
560
587
|
bus = new EventBus();
|
|
588
|
+
layerOrderMap = /* @__PURE__ */ new Map();
|
|
561
589
|
get count() {
|
|
562
590
|
return this.elements.size;
|
|
563
591
|
}
|
|
592
|
+
setLayerOrder(order) {
|
|
593
|
+
this.layerOrderMap = new Map(order);
|
|
594
|
+
}
|
|
564
595
|
getAll() {
|
|
565
|
-
return [...this.elements.values()].sort((a, b) =>
|
|
596
|
+
return [...this.elements.values()].sort((a, b) => {
|
|
597
|
+
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
598
|
+
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
599
|
+
if (layerA !== layerB) return layerA - layerB;
|
|
600
|
+
return a.zIndex - b.zIndex;
|
|
601
|
+
});
|
|
566
602
|
}
|
|
567
603
|
getById(id) {
|
|
568
604
|
return this.elements.get(id);
|
|
@@ -746,12 +782,13 @@ function getEdgeIntersection(bounds, outsidePoint) {
|
|
|
746
782
|
y: cy + dy * scale
|
|
747
783
|
};
|
|
748
784
|
}
|
|
749
|
-
function findBindTarget(point, store, threshold, excludeId) {
|
|
785
|
+
function findBindTarget(point, store, threshold, excludeId, filter) {
|
|
750
786
|
let closest = null;
|
|
751
787
|
let closestDist = Infinity;
|
|
752
788
|
for (const el of store.getAll()) {
|
|
753
789
|
if (!isBindable(el)) continue;
|
|
754
790
|
if (excludeId && el.id === excludeId) continue;
|
|
791
|
+
if (filter && !filter(el)) continue;
|
|
755
792
|
const bounds = getElementBounds(el);
|
|
756
793
|
if (!bounds) continue;
|
|
757
794
|
const dist = distToBounds(point, bounds);
|
|
@@ -909,14 +946,19 @@ function smoothToSegments(points) {
|
|
|
909
946
|
}
|
|
910
947
|
|
|
911
948
|
// src/elements/element-renderer.ts
|
|
912
|
-
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "
|
|
949
|
+
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
|
|
913
950
|
var ARROWHEAD_LENGTH = 12;
|
|
914
951
|
var ARROWHEAD_ANGLE = Math.PI / 6;
|
|
915
952
|
var ElementRenderer = class {
|
|
916
953
|
store = null;
|
|
954
|
+
imageCache = /* @__PURE__ */ new Map();
|
|
955
|
+
onImageLoad = null;
|
|
917
956
|
setStore(store) {
|
|
918
957
|
this.store = store;
|
|
919
958
|
}
|
|
959
|
+
setOnImageLoad(callback) {
|
|
960
|
+
this.onImageLoad = callback;
|
|
961
|
+
}
|
|
920
962
|
isDomElement(element) {
|
|
921
963
|
return DOM_ELEMENT_TYPES.has(element.type);
|
|
922
964
|
}
|
|
@@ -931,6 +973,9 @@ var ElementRenderer = class {
|
|
|
931
973
|
case "shape":
|
|
932
974
|
this.renderShape(ctx, element);
|
|
933
975
|
break;
|
|
976
|
+
case "image":
|
|
977
|
+
this.renderImage(ctx, element);
|
|
978
|
+
break;
|
|
934
979
|
}
|
|
935
980
|
}
|
|
936
981
|
renderStroke(ctx, stroke) {
|
|
@@ -1066,6 +1111,20 @@ var ElementRenderer = class {
|
|
|
1066
1111
|
}
|
|
1067
1112
|
}
|
|
1068
1113
|
}
|
|
1114
|
+
renderImage(ctx, image) {
|
|
1115
|
+
const img = this.getImage(image.src);
|
|
1116
|
+
if (!img) return;
|
|
1117
|
+
ctx.drawImage(img, image.position.x, image.position.y, image.size.w, image.size.h);
|
|
1118
|
+
}
|
|
1119
|
+
getImage(src) {
|
|
1120
|
+
const cached = this.imageCache.get(src);
|
|
1121
|
+
if (cached) return cached.complete ? cached : null;
|
|
1122
|
+
const img = new Image();
|
|
1123
|
+
img.src = src;
|
|
1124
|
+
this.imageCache.set(src, img);
|
|
1125
|
+
img.onload = () => this.onImageLoad?.();
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1069
1128
|
};
|
|
1070
1129
|
|
|
1071
1130
|
// src/elements/note-editor.ts
|
|
@@ -1417,6 +1476,7 @@ function createStroke(input) {
|
|
|
1417
1476
|
position: input.position ?? { x: 0, y: 0 },
|
|
1418
1477
|
zIndex: input.zIndex ?? 0,
|
|
1419
1478
|
locked: input.locked ?? false,
|
|
1479
|
+
layerId: input.layerId ?? "",
|
|
1420
1480
|
points: input.points,
|
|
1421
1481
|
color: input.color ?? "#000000",
|
|
1422
1482
|
width: input.width ?? 2,
|
|
@@ -1430,6 +1490,7 @@ function createNote(input) {
|
|
|
1430
1490
|
position: input.position,
|
|
1431
1491
|
zIndex: input.zIndex ?? 0,
|
|
1432
1492
|
locked: input.locked ?? false,
|
|
1493
|
+
layerId: input.layerId ?? "",
|
|
1433
1494
|
size: input.size ?? { w: 200, h: 100 },
|
|
1434
1495
|
text: input.text ?? "",
|
|
1435
1496
|
backgroundColor: input.backgroundColor ?? "#ffeb3b",
|
|
@@ -1443,6 +1504,7 @@ function createArrow(input) {
|
|
|
1443
1504
|
position: input.position ?? { x: 0, y: 0 },
|
|
1444
1505
|
zIndex: input.zIndex ?? 0,
|
|
1445
1506
|
locked: input.locked ?? false,
|
|
1507
|
+
layerId: input.layerId ?? "",
|
|
1446
1508
|
from: input.from,
|
|
1447
1509
|
to: input.to,
|
|
1448
1510
|
bend: input.bend ?? 0,
|
|
@@ -1460,6 +1522,7 @@ function createImage(input) {
|
|
|
1460
1522
|
position: input.position,
|
|
1461
1523
|
zIndex: input.zIndex ?? 0,
|
|
1462
1524
|
locked: input.locked ?? false,
|
|
1525
|
+
layerId: input.layerId ?? "",
|
|
1463
1526
|
size: input.size,
|
|
1464
1527
|
src: input.src
|
|
1465
1528
|
};
|
|
@@ -1471,6 +1534,7 @@ function createHtmlElement(input) {
|
|
|
1471
1534
|
position: input.position,
|
|
1472
1535
|
zIndex: input.zIndex ?? 0,
|
|
1473
1536
|
locked: input.locked ?? false,
|
|
1537
|
+
layerId: input.layerId ?? "",
|
|
1474
1538
|
size: input.size
|
|
1475
1539
|
};
|
|
1476
1540
|
}
|
|
@@ -1481,6 +1545,7 @@ function createShape(input) {
|
|
|
1481
1545
|
position: input.position,
|
|
1482
1546
|
zIndex: input.zIndex ?? 0,
|
|
1483
1547
|
locked: input.locked ?? false,
|
|
1548
|
+
layerId: input.layerId ?? "",
|
|
1484
1549
|
shape: input.shape ?? "rectangle",
|
|
1485
1550
|
size: input.size,
|
|
1486
1551
|
strokeColor: input.strokeColor ?? "#000000",
|
|
@@ -1495,6 +1560,7 @@ function createText(input) {
|
|
|
1495
1560
|
position: input.position,
|
|
1496
1561
|
zIndex: input.zIndex ?? 0,
|
|
1497
1562
|
locked: input.locked ?? false,
|
|
1563
|
+
layerId: input.layerId ?? "",
|
|
1498
1564
|
size: input.size ?? { w: 200, h: 28 },
|
|
1499
1565
|
text: input.text ?? "",
|
|
1500
1566
|
fontSize: input.fontSize ?? 16,
|
|
@@ -1503,6 +1569,155 @@ function createText(input) {
|
|
|
1503
1569
|
};
|
|
1504
1570
|
}
|
|
1505
1571
|
|
|
1572
|
+
// src/layers/layer-manager.ts
|
|
1573
|
+
var LayerManager = class {
|
|
1574
|
+
constructor(store) {
|
|
1575
|
+
this.store = store;
|
|
1576
|
+
const defaultLayer = {
|
|
1577
|
+
id: createId("layer"),
|
|
1578
|
+
name: "Layer 1",
|
|
1579
|
+
visible: true,
|
|
1580
|
+
locked: false,
|
|
1581
|
+
order: 0,
|
|
1582
|
+
opacity: 1
|
|
1583
|
+
};
|
|
1584
|
+
this.layers.set(defaultLayer.id, defaultLayer);
|
|
1585
|
+
this._activeLayerId = defaultLayer.id;
|
|
1586
|
+
this.syncLayerOrder();
|
|
1587
|
+
}
|
|
1588
|
+
layers = /* @__PURE__ */ new Map();
|
|
1589
|
+
_activeLayerId;
|
|
1590
|
+
bus = new EventBus();
|
|
1591
|
+
get activeLayer() {
|
|
1592
|
+
const layer = this.layers.get(this._activeLayerId);
|
|
1593
|
+
if (!layer) throw new Error("Active layer not found");
|
|
1594
|
+
return { ...layer };
|
|
1595
|
+
}
|
|
1596
|
+
get activeLayerId() {
|
|
1597
|
+
return this._activeLayerId;
|
|
1598
|
+
}
|
|
1599
|
+
getLayers() {
|
|
1600
|
+
return [...this.layers.values()].sort((a, b) => a.order - b.order).map((l) => ({ ...l }));
|
|
1601
|
+
}
|
|
1602
|
+
getLayer(id) {
|
|
1603
|
+
const layer = this.layers.get(id);
|
|
1604
|
+
return layer ? { ...layer } : void 0;
|
|
1605
|
+
}
|
|
1606
|
+
isLayerVisible(id) {
|
|
1607
|
+
return this.layers.get(id)?.visible ?? true;
|
|
1608
|
+
}
|
|
1609
|
+
isLayerLocked(id) {
|
|
1610
|
+
return this.layers.get(id)?.locked ?? false;
|
|
1611
|
+
}
|
|
1612
|
+
createLayer(name) {
|
|
1613
|
+
const maxOrder = Math.max(...[...this.layers.values()].map((l) => l.order), -1);
|
|
1614
|
+
const autoName = name ?? `Layer ${this.layers.size + 1}`;
|
|
1615
|
+
const layer = {
|
|
1616
|
+
id: createId("layer"),
|
|
1617
|
+
name: autoName,
|
|
1618
|
+
visible: true,
|
|
1619
|
+
locked: false,
|
|
1620
|
+
order: maxOrder + 1,
|
|
1621
|
+
opacity: 1
|
|
1622
|
+
};
|
|
1623
|
+
this.addLayerDirect(layer);
|
|
1624
|
+
return { ...layer };
|
|
1625
|
+
}
|
|
1626
|
+
removeLayer(id) {
|
|
1627
|
+
if (this.layers.size <= 1) {
|
|
1628
|
+
throw new Error("Cannot remove the last layer");
|
|
1629
|
+
}
|
|
1630
|
+
if (this._activeLayerId === id) {
|
|
1631
|
+
const remaining = [...this.layers.values()].filter((l) => l.id !== id).sort((a, b) => b.order - a.order);
|
|
1632
|
+
const fallback = remaining[0];
|
|
1633
|
+
if (fallback) this._activeLayerId = fallback.id;
|
|
1634
|
+
}
|
|
1635
|
+
const elements = this.store.getAll().filter((el) => el.layerId === id);
|
|
1636
|
+
for (const el of elements) {
|
|
1637
|
+
this.store.update(el.id, { layerId: this._activeLayerId });
|
|
1638
|
+
}
|
|
1639
|
+
this.removeLayerDirect(id);
|
|
1640
|
+
}
|
|
1641
|
+
renameLayer(id, name) {
|
|
1642
|
+
this.updateLayerDirect(id, { name });
|
|
1643
|
+
}
|
|
1644
|
+
reorderLayer(id, newOrder) {
|
|
1645
|
+
if (!this.layers.has(id)) return;
|
|
1646
|
+
this.updateLayerDirect(id, { order: newOrder });
|
|
1647
|
+
}
|
|
1648
|
+
setLayerVisible(id, visible) {
|
|
1649
|
+
if (!visible && id === this._activeLayerId) {
|
|
1650
|
+
const fallback = this.findFallbackLayer(id);
|
|
1651
|
+
if (!fallback) return false;
|
|
1652
|
+
this._activeLayerId = fallback.id;
|
|
1653
|
+
}
|
|
1654
|
+
this.updateLayerDirect(id, { visible });
|
|
1655
|
+
return true;
|
|
1656
|
+
}
|
|
1657
|
+
setLayerLocked(id, locked) {
|
|
1658
|
+
if (locked && id === this._activeLayerId) {
|
|
1659
|
+
const fallback = this.findFallbackLayer(id);
|
|
1660
|
+
if (!fallback) return false;
|
|
1661
|
+
this._activeLayerId = fallback.id;
|
|
1662
|
+
}
|
|
1663
|
+
this.updateLayerDirect(id, { locked });
|
|
1664
|
+
return true;
|
|
1665
|
+
}
|
|
1666
|
+
setActiveLayer(id) {
|
|
1667
|
+
if (!this.layers.has(id)) return;
|
|
1668
|
+
this._activeLayerId = id;
|
|
1669
|
+
this.bus.emit("change", null);
|
|
1670
|
+
}
|
|
1671
|
+
moveElementToLayer(elementId, layerId) {
|
|
1672
|
+
if (!this.layers.has(layerId)) return;
|
|
1673
|
+
this.store.update(elementId, { layerId });
|
|
1674
|
+
this.bus.emit("change", null);
|
|
1675
|
+
}
|
|
1676
|
+
snapshot() {
|
|
1677
|
+
return this.getLayers();
|
|
1678
|
+
}
|
|
1679
|
+
loadSnapshot(layers) {
|
|
1680
|
+
this.layers.clear();
|
|
1681
|
+
for (const layer of layers) {
|
|
1682
|
+
this.layers.set(layer.id, { ...layer });
|
|
1683
|
+
}
|
|
1684
|
+
const first = this.getLayers()[0];
|
|
1685
|
+
if (first) this._activeLayerId = first.id;
|
|
1686
|
+
this.syncLayerOrder();
|
|
1687
|
+
this.bus.emit("change", null);
|
|
1688
|
+
}
|
|
1689
|
+
on(event, callback) {
|
|
1690
|
+
return this.bus.on(event, callback);
|
|
1691
|
+
}
|
|
1692
|
+
addLayerDirect(layer) {
|
|
1693
|
+
this.layers.set(layer.id, { ...layer });
|
|
1694
|
+
this.syncLayerOrder();
|
|
1695
|
+
this.bus.emit("change", null);
|
|
1696
|
+
}
|
|
1697
|
+
removeLayerDirect(id) {
|
|
1698
|
+
this.layers.delete(id);
|
|
1699
|
+
this.syncLayerOrder();
|
|
1700
|
+
this.bus.emit("change", null);
|
|
1701
|
+
}
|
|
1702
|
+
updateLayerDirect(id, props) {
|
|
1703
|
+
const layer = this.layers.get(id);
|
|
1704
|
+
if (!layer) return;
|
|
1705
|
+
Object.assign(layer, props);
|
|
1706
|
+
if ("order" in props) this.syncLayerOrder();
|
|
1707
|
+
this.bus.emit("change", null);
|
|
1708
|
+
}
|
|
1709
|
+
syncLayerOrder() {
|
|
1710
|
+
const order = /* @__PURE__ */ new Map();
|
|
1711
|
+
for (const layer of this.layers.values()) {
|
|
1712
|
+
order.set(layer.id, layer.order);
|
|
1713
|
+
}
|
|
1714
|
+
this.store.setLayerOrder(order);
|
|
1715
|
+
}
|
|
1716
|
+
findFallbackLayer(excludeId) {
|
|
1717
|
+
return [...this.layers.values()].filter((l) => l.id !== excludeId && l.visible && !l.locked).sort((a, b) => b.order - a.order)[0];
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
|
|
1506
1721
|
// src/canvas/viewport.ts
|
|
1507
1722
|
var Viewport = class {
|
|
1508
1723
|
constructor(container, options = {}) {
|
|
@@ -1511,9 +1726,11 @@ var Viewport = class {
|
|
|
1511
1726
|
this.background = new Background(options.background);
|
|
1512
1727
|
this._gridSize = options.background?.spacing ?? 24;
|
|
1513
1728
|
this.store = new ElementStore();
|
|
1729
|
+
this.layerManager = new LayerManager(this.store);
|
|
1514
1730
|
this.toolManager = new ToolManager();
|
|
1515
1731
|
this.renderer = new ElementRenderer();
|
|
1516
1732
|
this.renderer.setStore(this.store);
|
|
1733
|
+
this.renderer.setOnImageLoad(() => this.requestRender());
|
|
1517
1734
|
this.noteEditor = new NoteEditor();
|
|
1518
1735
|
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
1519
1736
|
this.history = new HistoryStack();
|
|
@@ -1534,7 +1751,10 @@ var Viewport = class {
|
|
|
1534
1751
|
this.wrapper.style.cursor = cursor;
|
|
1535
1752
|
},
|
|
1536
1753
|
snapToGrid: false,
|
|
1537
|
-
gridSize: this._gridSize
|
|
1754
|
+
gridSize: this._gridSize,
|
|
1755
|
+
activeLayerId: this.layerManager.activeLayerId,
|
|
1756
|
+
isLayerVisible: (id) => this.layerManager.isLayerVisible(id),
|
|
1757
|
+
isLayerLocked: (id) => this.layerManager.isLayerLocked(id)
|
|
1538
1758
|
};
|
|
1539
1759
|
this.inputHandler = new InputHandler(this.wrapper, this.camera, {
|
|
1540
1760
|
toolManager: this.toolManager,
|
|
@@ -1555,6 +1775,10 @@ var Viewport = class {
|
|
|
1555
1775
|
this.store.on("update", () => this.requestRender()),
|
|
1556
1776
|
this.store.on("clear", () => this.clearDomNodes())
|
|
1557
1777
|
];
|
|
1778
|
+
this.layerManager.on("change", () => {
|
|
1779
|
+
this.toolContext.activeLayerId = this.layerManager.activeLayerId;
|
|
1780
|
+
this.requestRender();
|
|
1781
|
+
});
|
|
1558
1782
|
this.wrapper.addEventListener("dblclick", this.onDblClick);
|
|
1559
1783
|
this.wrapper.addEventListener("dragover", this.onDragOver);
|
|
1560
1784
|
this.wrapper.addEventListener("drop", this.onDrop);
|
|
@@ -1564,6 +1788,7 @@ var Viewport = class {
|
|
|
1564
1788
|
}
|
|
1565
1789
|
camera;
|
|
1566
1790
|
store;
|
|
1791
|
+
layerManager;
|
|
1567
1792
|
toolManager;
|
|
1568
1793
|
history;
|
|
1569
1794
|
domLayer;
|
|
@@ -1599,7 +1824,7 @@ var Viewport = class {
|
|
|
1599
1824
|
this.needsRender = true;
|
|
1600
1825
|
}
|
|
1601
1826
|
exportState() {
|
|
1602
|
-
return exportState(this.store.snapshot(), this.camera);
|
|
1827
|
+
return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
|
|
1603
1828
|
}
|
|
1604
1829
|
exportJSON() {
|
|
1605
1830
|
return JSON.stringify(this.exportState());
|
|
@@ -1609,6 +1834,9 @@ var Viewport = class {
|
|
|
1609
1834
|
this.noteEditor.destroy(this.store);
|
|
1610
1835
|
this.clearDomNodes();
|
|
1611
1836
|
this.store.loadSnapshot(state.elements);
|
|
1837
|
+
if (state.layers && state.layers.length > 0) {
|
|
1838
|
+
this.layerManager.loadSnapshot(state.layers);
|
|
1839
|
+
}
|
|
1612
1840
|
this.history.clear();
|
|
1613
1841
|
this.historyRecorder.resume();
|
|
1614
1842
|
this.camera.moveTo(state.camera.position.x, state.camera.position.y);
|
|
@@ -1632,7 +1860,7 @@ var Viewport = class {
|
|
|
1632
1860
|
return result;
|
|
1633
1861
|
}
|
|
1634
1862
|
addImage(src, position, size = { w: 300, h: 200 }) {
|
|
1635
|
-
const image = createImage({ position, size, src });
|
|
1863
|
+
const image = createImage({ position, size, src, layerId: this.layerManager.activeLayerId });
|
|
1636
1864
|
this.historyRecorder.begin();
|
|
1637
1865
|
this.store.add(image);
|
|
1638
1866
|
this.historyRecorder.commit();
|
|
@@ -1640,7 +1868,7 @@ var Viewport = class {
|
|
|
1640
1868
|
return image.id;
|
|
1641
1869
|
}
|
|
1642
1870
|
addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
|
|
1643
|
-
const el = createHtmlElement({ position, size });
|
|
1871
|
+
const el = createHtmlElement({ position, size, layerId: this.layerManager.activeLayerId });
|
|
1644
1872
|
this.htmlContent.set(el.id, dom);
|
|
1645
1873
|
this.historyRecorder.begin();
|
|
1646
1874
|
this.store.add(el);
|
|
@@ -1683,9 +1911,17 @@ var Viewport = class {
|
|
|
1683
1911
|
ctx.save();
|
|
1684
1912
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
1685
1913
|
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
1686
|
-
|
|
1914
|
+
const allElements = this.store.getAll();
|
|
1915
|
+
let domZIndex = 0;
|
|
1916
|
+
for (const element of allElements) {
|
|
1917
|
+
if (!this.layerManager.isLayerVisible(element.layerId)) {
|
|
1918
|
+
if (this.renderer.isDomElement(element)) {
|
|
1919
|
+
this.hideDomNode(element.id);
|
|
1920
|
+
}
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1687
1923
|
if (this.renderer.isDomElement(element)) {
|
|
1688
|
-
this.syncDomNode(element);
|
|
1924
|
+
this.syncDomNode(element, domZIndex++);
|
|
1689
1925
|
} else {
|
|
1690
1926
|
this.renderer.renderCanvasElement(ctx, element);
|
|
1691
1927
|
}
|
|
@@ -1817,7 +2053,7 @@ var Viewport = class {
|
|
|
1817
2053
|
reader.readAsDataURL(file);
|
|
1818
2054
|
}
|
|
1819
2055
|
};
|
|
1820
|
-
syncDomNode(element) {
|
|
2056
|
+
syncDomNode(element, zIndex = 0) {
|
|
1821
2057
|
let node = this.domNodes.get(element.id);
|
|
1822
2058
|
if (!node) {
|
|
1823
2059
|
node = document.createElement("div");
|
|
@@ -1831,10 +2067,12 @@ var Viewport = class {
|
|
|
1831
2067
|
}
|
|
1832
2068
|
const size = "size" in element ? element.size : null;
|
|
1833
2069
|
Object.assign(node.style, {
|
|
2070
|
+
display: "block",
|
|
1834
2071
|
left: `${element.position.x}px`,
|
|
1835
2072
|
top: `${element.position.y}px`,
|
|
1836
2073
|
width: size ? `${size.w}px` : "auto",
|
|
1837
|
-
height: size ? `${size.h}px` : "auto"
|
|
2074
|
+
height: size ? `${size.h}px` : "auto",
|
|
2075
|
+
zIndex: String(zIndex)
|
|
1838
2076
|
});
|
|
1839
2077
|
this.renderDomContent(node, element);
|
|
1840
2078
|
}
|
|
@@ -1869,26 +2107,6 @@ var Viewport = class {
|
|
|
1869
2107
|
node.style.color = element.textColor;
|
|
1870
2108
|
}
|
|
1871
2109
|
}
|
|
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
2110
|
if (element.type === "html" && !node.dataset["initialized"]) {
|
|
1893
2111
|
const content = this.htmlContent.get(element.id);
|
|
1894
2112
|
if (content) {
|
|
@@ -1971,6 +2189,10 @@ var Viewport = class {
|
|
|
1971
2189
|
}
|
|
1972
2190
|
}
|
|
1973
2191
|
}
|
|
2192
|
+
hideDomNode(id) {
|
|
2193
|
+
const node = this.domNodes.get(id);
|
|
2194
|
+
if (node) node.style.display = "none";
|
|
2195
|
+
}
|
|
1974
2196
|
removeDomNode(id) {
|
|
1975
2197
|
this.htmlContent.delete(id);
|
|
1976
2198
|
const node = this.domNodes.get(id);
|
|
@@ -2117,7 +2339,8 @@ var PencilTool = class {
|
|
|
2117
2339
|
const stroke = createStroke({
|
|
2118
2340
|
points: simplified,
|
|
2119
2341
|
color: this.color,
|
|
2120
|
-
width: this.width
|
|
2342
|
+
width: this.width,
|
|
2343
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2121
2344
|
});
|
|
2122
2345
|
ctx.store.add(stroke);
|
|
2123
2346
|
this.points = [];
|
|
@@ -2181,6 +2404,8 @@ var EraserTool = class {
|
|
|
2181
2404
|
const strokes = ctx.store.getElementsByType("stroke");
|
|
2182
2405
|
let erased = false;
|
|
2183
2406
|
for (const stroke of strokes) {
|
|
2407
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(stroke.layerId)) continue;
|
|
2408
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(stroke.layerId)) continue;
|
|
2184
2409
|
if (this.strokeIntersects(stroke, world)) {
|
|
2185
2410
|
ctx.store.remove(stroke.id);
|
|
2186
2411
|
erased = true;
|
|
@@ -2666,6 +2891,8 @@ var SelectTool = class {
|
|
|
2666
2891
|
findElementsInRect(marquee, ctx) {
|
|
2667
2892
|
const ids = [];
|
|
2668
2893
|
for (const el of ctx.store.getAll()) {
|
|
2894
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
2895
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
2669
2896
|
const bounds = this.getElementBounds(el);
|
|
2670
2897
|
if (bounds && this.rectsOverlap(marquee, bounds)) {
|
|
2671
2898
|
ids.push(el.id);
|
|
@@ -2700,6 +2927,8 @@ var SelectTool = class {
|
|
|
2700
2927
|
hitTest(world, ctx) {
|
|
2701
2928
|
const elements = ctx.store.getAll().reverse();
|
|
2702
2929
|
for (const el of elements) {
|
|
2930
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
2931
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
2703
2932
|
if (this.isInsideBounds(world, el)) return el;
|
|
2704
2933
|
}
|
|
2705
2934
|
return null;
|
|
@@ -2744,11 +2973,20 @@ var ArrowTool = class {
|
|
|
2744
2973
|
if (options.color !== void 0) this.color = options.color;
|
|
2745
2974
|
if (options.width !== void 0) this.width = options.width;
|
|
2746
2975
|
}
|
|
2976
|
+
layerFilter(ctx) {
|
|
2977
|
+
if (!ctx.isLayerVisible && !ctx.isLayerLocked) return void 0;
|
|
2978
|
+
return (el) => {
|
|
2979
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) return false;
|
|
2980
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) return false;
|
|
2981
|
+
return true;
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2747
2984
|
onPointerDown(state, ctx) {
|
|
2748
2985
|
this.drawing = true;
|
|
2749
2986
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2750
2987
|
const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
|
|
2751
|
-
const
|
|
2988
|
+
const filter = this.layerFilter(ctx);
|
|
2989
|
+
const target = findBindTarget(world, ctx.store, threshold, void 0, filter);
|
|
2752
2990
|
if (target) {
|
|
2753
2991
|
this.start = getElementCenter(target);
|
|
2754
2992
|
this.fromBinding = { elementId: target.id };
|
|
@@ -2766,7 +3004,8 @@ var ArrowTool = class {
|
|
|
2766
3004
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2767
3005
|
const threshold = BIND_THRESHOLD2 / ctx.camera.zoom;
|
|
2768
3006
|
const excludeId = this.fromBinding?.elementId;
|
|
2769
|
-
const
|
|
3007
|
+
const filter = this.layerFilter(ctx);
|
|
3008
|
+
const target = findBindTarget(world, ctx.store, threshold, excludeId, filter);
|
|
2770
3009
|
if (target) {
|
|
2771
3010
|
this.end = getElementCenter(target);
|
|
2772
3011
|
this.toTarget = target;
|
|
@@ -2787,7 +3026,8 @@ var ArrowTool = class {
|
|
|
2787
3026
|
color: this.color,
|
|
2788
3027
|
width: this.width,
|
|
2789
3028
|
fromBinding: this.fromBinding,
|
|
2790
|
-
toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0
|
|
3029
|
+
toBinding: this.toTarget ? { elementId: this.toTarget.id } : void 0,
|
|
3030
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2791
3031
|
});
|
|
2792
3032
|
ctx.store.add(arrow);
|
|
2793
3033
|
this.fromTarget = null;
|
|
@@ -2874,7 +3114,8 @@ var NoteTool = class {
|
|
|
2874
3114
|
position: world,
|
|
2875
3115
|
size: { ...this.size },
|
|
2876
3116
|
backgroundColor: this.backgroundColor,
|
|
2877
|
-
textColor: this.textColor
|
|
3117
|
+
textColor: this.textColor,
|
|
3118
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2878
3119
|
});
|
|
2879
3120
|
ctx.store.add(note);
|
|
2880
3121
|
ctx.requestRender();
|
|
@@ -2918,7 +3159,8 @@ var TextTool = class {
|
|
|
2918
3159
|
position: world,
|
|
2919
3160
|
fontSize: this.fontSize,
|
|
2920
3161
|
color: this.color,
|
|
2921
|
-
textAlign: this.textAlign
|
|
3162
|
+
textAlign: this.textAlign,
|
|
3163
|
+
layerId: ctx.activeLayerId ?? ""
|
|
2922
3164
|
});
|
|
2923
3165
|
ctx.store.add(textEl);
|
|
2924
3166
|
ctx.requestRender();
|
|
@@ -3014,7 +3256,8 @@ var ShapeTool = class {
|
|
|
3014
3256
|
shape: this.shape,
|
|
3015
3257
|
strokeColor: this.strokeColor,
|
|
3016
3258
|
strokeWidth: this.strokeWidth,
|
|
3017
|
-
fillColor: this.fillColor
|
|
3259
|
+
fillColor: this.fillColor,
|
|
3260
|
+
layerId: ctx.activeLayerId ?? ""
|
|
3018
3261
|
});
|
|
3019
3262
|
ctx.store.add(shape);
|
|
3020
3263
|
ctx.requestRender();
|
|
@@ -3075,8 +3318,48 @@ var ShapeTool = class {
|
|
|
3075
3318
|
};
|
|
3076
3319
|
};
|
|
3077
3320
|
|
|
3321
|
+
// src/history/layer-commands.ts
|
|
3322
|
+
var CreateLayerCommand = class {
|
|
3323
|
+
constructor(manager, layer) {
|
|
3324
|
+
this.manager = manager;
|
|
3325
|
+
this.layer = layer;
|
|
3326
|
+
}
|
|
3327
|
+
execute(_store) {
|
|
3328
|
+
this.manager.addLayerDirect(this.layer);
|
|
3329
|
+
}
|
|
3330
|
+
undo(_store) {
|
|
3331
|
+
this.manager.removeLayerDirect(this.layer.id);
|
|
3332
|
+
}
|
|
3333
|
+
};
|
|
3334
|
+
var RemoveLayerCommand = class {
|
|
3335
|
+
constructor(manager, layer) {
|
|
3336
|
+
this.manager = manager;
|
|
3337
|
+
this.layer = layer;
|
|
3338
|
+
}
|
|
3339
|
+
execute(_store) {
|
|
3340
|
+
this.manager.removeLayerDirect(this.layer.id);
|
|
3341
|
+
}
|
|
3342
|
+
undo(_store) {
|
|
3343
|
+
this.manager.addLayerDirect(this.layer);
|
|
3344
|
+
}
|
|
3345
|
+
};
|
|
3346
|
+
var UpdateLayerCommand = class {
|
|
3347
|
+
constructor(manager, layerId, previous, current) {
|
|
3348
|
+
this.manager = manager;
|
|
3349
|
+
this.layerId = layerId;
|
|
3350
|
+
this.previous = previous;
|
|
3351
|
+
this.current = current;
|
|
3352
|
+
}
|
|
3353
|
+
execute(_store) {
|
|
3354
|
+
this.manager.updateLayerDirect(this.layerId, { ...this.current });
|
|
3355
|
+
}
|
|
3356
|
+
undo(_store) {
|
|
3357
|
+
this.manager.updateLayerDirect(this.layerId, { ...this.previous });
|
|
3358
|
+
}
|
|
3359
|
+
};
|
|
3360
|
+
|
|
3078
3361
|
// src/index.ts
|
|
3079
|
-
var VERSION = "0.
|
|
3362
|
+
var VERSION = "0.6.1";
|
|
3080
3363
|
export {
|
|
3081
3364
|
AddElementCommand,
|
|
3082
3365
|
ArrowTool,
|
|
@@ -3084,6 +3367,7 @@ export {
|
|
|
3084
3367
|
Background,
|
|
3085
3368
|
BatchCommand,
|
|
3086
3369
|
Camera,
|
|
3370
|
+
CreateLayerCommand,
|
|
3087
3371
|
ElementRenderer,
|
|
3088
3372
|
ElementStore,
|
|
3089
3373
|
EraserTool,
|
|
@@ -3093,15 +3377,18 @@ export {
|
|
|
3093
3377
|
HistoryStack,
|
|
3094
3378
|
ImageTool,
|
|
3095
3379
|
InputHandler,
|
|
3380
|
+
LayerManager,
|
|
3096
3381
|
NoteEditor,
|
|
3097
3382
|
NoteTool,
|
|
3098
3383
|
PencilTool,
|
|
3099
3384
|
RemoveElementCommand,
|
|
3385
|
+
RemoveLayerCommand,
|
|
3100
3386
|
SelectTool,
|
|
3101
3387
|
ShapeTool,
|
|
3102
3388
|
TextTool,
|
|
3103
3389
|
ToolManager,
|
|
3104
3390
|
UpdateElementCommand,
|
|
3391
|
+
UpdateLayerCommand,
|
|
3105
3392
|
VERSION,
|
|
3106
3393
|
Viewport,
|
|
3107
3394
|
clearStaleBindings,
|