@fieldnotes/core 0.8.5 → 0.8.7
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 +1058 -375
- package/dist/index.d.cts +75 -31
- package/dist/index.d.ts +75 -31
- package/dist/index.js +1056 -375
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,6 +22,153 @@ var EventBus = class {
|
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
// src/core/quadtree.ts
|
|
26
|
+
var MAX_ITEMS = 8;
|
|
27
|
+
var MAX_DEPTH = 8;
|
|
28
|
+
function intersects(a, b) {
|
|
29
|
+
return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
|
|
30
|
+
}
|
|
31
|
+
var QuadNode = class _QuadNode {
|
|
32
|
+
constructor(bounds, depth) {
|
|
33
|
+
this.bounds = bounds;
|
|
34
|
+
this.depth = depth;
|
|
35
|
+
}
|
|
36
|
+
items = [];
|
|
37
|
+
children = null;
|
|
38
|
+
insert(entry) {
|
|
39
|
+
if (this.children) {
|
|
40
|
+
const idx = this.getChildIndex(entry.bounds);
|
|
41
|
+
if (idx !== -1) {
|
|
42
|
+
const child = this.children[idx];
|
|
43
|
+
if (child) child.insert(entry);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.items.push(entry);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.items.push(entry);
|
|
50
|
+
if (this.items.length > MAX_ITEMS && this.depth < MAX_DEPTH) {
|
|
51
|
+
this.split();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
remove(id) {
|
|
55
|
+
const idx = this.items.findIndex((e) => e.id === id);
|
|
56
|
+
if (idx !== -1) {
|
|
57
|
+
this.items.splice(idx, 1);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (this.children) {
|
|
61
|
+
for (const child of this.children) {
|
|
62
|
+
if (child.remove(id)) {
|
|
63
|
+
this.collapseIfEmpty();
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
query(rect, result) {
|
|
71
|
+
if (!intersects(this.bounds, rect)) return;
|
|
72
|
+
for (const item of this.items) {
|
|
73
|
+
if (intersects(item.bounds, rect)) {
|
|
74
|
+
result.push(item.id);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (this.children) {
|
|
78
|
+
for (const child of this.children) {
|
|
79
|
+
child.query(rect, result);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
getChildIndex(itemBounds) {
|
|
84
|
+
const midX = this.bounds.x + this.bounds.w / 2;
|
|
85
|
+
const midY = this.bounds.y + this.bounds.h / 2;
|
|
86
|
+
const left = itemBounds.x >= this.bounds.x && itemBounds.x + itemBounds.w <= midX;
|
|
87
|
+
const right = itemBounds.x >= midX && itemBounds.x + itemBounds.w <= this.bounds.x + this.bounds.w;
|
|
88
|
+
const top = itemBounds.y >= this.bounds.y && itemBounds.y + itemBounds.h <= midY;
|
|
89
|
+
const bottom = itemBounds.y >= midY && itemBounds.y + itemBounds.h <= this.bounds.y + this.bounds.h;
|
|
90
|
+
if (left && top) return 0;
|
|
91
|
+
if (right && top) return 1;
|
|
92
|
+
if (left && bottom) return 2;
|
|
93
|
+
if (right && bottom) return 3;
|
|
94
|
+
return -1;
|
|
95
|
+
}
|
|
96
|
+
split() {
|
|
97
|
+
const { x, y, w, h } = this.bounds;
|
|
98
|
+
const halfW = w / 2;
|
|
99
|
+
const halfH = h / 2;
|
|
100
|
+
const d = this.depth + 1;
|
|
101
|
+
this.children = [
|
|
102
|
+
new _QuadNode({ x, y, w: halfW, h: halfH }, d),
|
|
103
|
+
new _QuadNode({ x: x + halfW, y, w: halfW, h: halfH }, d),
|
|
104
|
+
new _QuadNode({ x, y: y + halfH, w: halfW, h: halfH }, d),
|
|
105
|
+
new _QuadNode({ x: x + halfW, y: y + halfH, w: halfW, h: halfH }, d)
|
|
106
|
+
];
|
|
107
|
+
const remaining = [];
|
|
108
|
+
for (const item of this.items) {
|
|
109
|
+
const idx = this.getChildIndex(item.bounds);
|
|
110
|
+
if (idx !== -1) {
|
|
111
|
+
const target = this.children[idx];
|
|
112
|
+
if (target) target.insert(item);
|
|
113
|
+
} else {
|
|
114
|
+
remaining.push(item);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.items = remaining;
|
|
118
|
+
}
|
|
119
|
+
collapseIfEmpty() {
|
|
120
|
+
if (!this.children) return;
|
|
121
|
+
let totalItems = this.items.length;
|
|
122
|
+
for (const child of this.children) {
|
|
123
|
+
if (child.children) return;
|
|
124
|
+
totalItems += child.items.length;
|
|
125
|
+
}
|
|
126
|
+
if (totalItems <= MAX_ITEMS) {
|
|
127
|
+
for (const child of this.children) {
|
|
128
|
+
this.items.push(...child.items);
|
|
129
|
+
}
|
|
130
|
+
this.children = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var Quadtree = class {
|
|
135
|
+
root;
|
|
136
|
+
_size = 0;
|
|
137
|
+
worldBounds;
|
|
138
|
+
constructor(worldBounds) {
|
|
139
|
+
this.worldBounds = worldBounds;
|
|
140
|
+
this.root = new QuadNode(worldBounds, 0);
|
|
141
|
+
}
|
|
142
|
+
get size() {
|
|
143
|
+
return this._size;
|
|
144
|
+
}
|
|
145
|
+
insert(id, bounds) {
|
|
146
|
+
this.root.insert({ id, bounds });
|
|
147
|
+
this._size++;
|
|
148
|
+
}
|
|
149
|
+
remove(id) {
|
|
150
|
+
if (this.root.remove(id)) {
|
|
151
|
+
this._size--;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
update(id, newBounds) {
|
|
155
|
+
this.remove(id);
|
|
156
|
+
this.insert(id, newBounds);
|
|
157
|
+
}
|
|
158
|
+
query(rect) {
|
|
159
|
+
const result = [];
|
|
160
|
+
this.root.query(rect, result);
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
queryPoint(point) {
|
|
164
|
+
return this.query({ x: point.x, y: point.y, w: 0, h: 0 });
|
|
165
|
+
}
|
|
166
|
+
clear() {
|
|
167
|
+
this.root = new QuadNode(this.worldBounds, 0);
|
|
168
|
+
this._size = 0;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
25
172
|
// src/core/state-serializer.ts
|
|
26
173
|
var CURRENT_VERSION = 2;
|
|
27
174
|
function exportState(elements, camera, layers = []) {
|
|
@@ -236,16 +383,16 @@ var Camera = class {
|
|
|
236
383
|
pan(dx, dy) {
|
|
237
384
|
this.x += dx;
|
|
238
385
|
this.y += dy;
|
|
239
|
-
this.
|
|
386
|
+
this.notifyPan();
|
|
240
387
|
}
|
|
241
388
|
moveTo(x, y) {
|
|
242
389
|
this.x = x;
|
|
243
390
|
this.y = y;
|
|
244
|
-
this.
|
|
391
|
+
this.notifyPan();
|
|
245
392
|
}
|
|
246
393
|
setZoom(level) {
|
|
247
394
|
this.z = Math.min(this.maxZoom, Math.max(this.minZoom, level));
|
|
248
|
-
this.
|
|
395
|
+
this.notifyZoom();
|
|
249
396
|
}
|
|
250
397
|
zoomAt(level, screenPoint) {
|
|
251
398
|
const before = this.screenToWorld(screenPoint);
|
|
@@ -253,7 +400,7 @@ var Camera = class {
|
|
|
253
400
|
const after = this.screenToWorld(screenPoint);
|
|
254
401
|
this.x += (after.x - before.x) * this.z;
|
|
255
402
|
this.y += (after.y - before.y) * this.z;
|
|
256
|
-
this.
|
|
403
|
+
this.notifyPanAndZoom();
|
|
257
404
|
}
|
|
258
405
|
screenToWorld(screen) {
|
|
259
406
|
return {
|
|
@@ -267,6 +414,16 @@ var Camera = class {
|
|
|
267
414
|
y: world.y * this.z + this.y
|
|
268
415
|
};
|
|
269
416
|
}
|
|
417
|
+
getVisibleRect(canvasWidth, canvasHeight) {
|
|
418
|
+
const topLeft = this.screenToWorld({ x: 0, y: 0 });
|
|
419
|
+
const bottomRight = this.screenToWorld({ x: canvasWidth, y: canvasHeight });
|
|
420
|
+
return {
|
|
421
|
+
x: topLeft.x,
|
|
422
|
+
y: topLeft.y,
|
|
423
|
+
w: bottomRight.x - topLeft.x,
|
|
424
|
+
h: bottomRight.y - topLeft.y
|
|
425
|
+
};
|
|
426
|
+
}
|
|
270
427
|
toCSSTransform() {
|
|
271
428
|
return `translate3d(${this.x}px, ${this.y}px, 0) scale(${this.z})`;
|
|
272
429
|
}
|
|
@@ -274,8 +431,14 @@ var Camera = class {
|
|
|
274
431
|
this.changeListeners.add(listener);
|
|
275
432
|
return () => this.changeListeners.delete(listener);
|
|
276
433
|
}
|
|
277
|
-
|
|
278
|
-
this.changeListeners.forEach((fn) => fn());
|
|
434
|
+
notifyPan() {
|
|
435
|
+
this.changeListeners.forEach((fn) => fn({ panned: true, zoomed: false }));
|
|
436
|
+
}
|
|
437
|
+
notifyZoom() {
|
|
438
|
+
this.changeListeners.forEach((fn) => fn({ panned: false, zoomed: true }));
|
|
439
|
+
}
|
|
440
|
+
notifyPanAndZoom() {
|
|
441
|
+
this.changeListeners.forEach((fn) => fn({ panned: true, zoomed: true }));
|
|
279
442
|
}
|
|
280
443
|
};
|
|
281
444
|
|
|
@@ -581,68 +744,6 @@ var InputHandler = class {
|
|
|
581
744
|
}
|
|
582
745
|
};
|
|
583
746
|
|
|
584
|
-
// src/elements/element-store.ts
|
|
585
|
-
var ElementStore = class {
|
|
586
|
-
elements = /* @__PURE__ */ new Map();
|
|
587
|
-
bus = new EventBus();
|
|
588
|
-
layerOrderMap = /* @__PURE__ */ new Map();
|
|
589
|
-
get count() {
|
|
590
|
-
return this.elements.size;
|
|
591
|
-
}
|
|
592
|
-
setLayerOrder(order) {
|
|
593
|
-
this.layerOrderMap = new Map(order);
|
|
594
|
-
}
|
|
595
|
-
getAll() {
|
|
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
|
-
});
|
|
602
|
-
}
|
|
603
|
-
getById(id) {
|
|
604
|
-
return this.elements.get(id);
|
|
605
|
-
}
|
|
606
|
-
getElementsByType(type) {
|
|
607
|
-
return this.getAll().filter(
|
|
608
|
-
(el) => el.type === type
|
|
609
|
-
);
|
|
610
|
-
}
|
|
611
|
-
add(element) {
|
|
612
|
-
this.elements.set(element.id, element);
|
|
613
|
-
this.bus.emit("add", element);
|
|
614
|
-
}
|
|
615
|
-
update(id, partial) {
|
|
616
|
-
const existing = this.elements.get(id);
|
|
617
|
-
if (!existing) return;
|
|
618
|
-
const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
|
|
619
|
-
this.elements.set(id, updated);
|
|
620
|
-
this.bus.emit("update", { previous: existing, current: updated });
|
|
621
|
-
}
|
|
622
|
-
remove(id) {
|
|
623
|
-
const element = this.elements.get(id);
|
|
624
|
-
if (!element) return;
|
|
625
|
-
this.elements.delete(id);
|
|
626
|
-
this.bus.emit("remove", element);
|
|
627
|
-
}
|
|
628
|
-
clear() {
|
|
629
|
-
this.elements.clear();
|
|
630
|
-
this.bus.emit("clear", null);
|
|
631
|
-
}
|
|
632
|
-
snapshot() {
|
|
633
|
-
return this.getAll().map((el) => ({ ...el }));
|
|
634
|
-
}
|
|
635
|
-
loadSnapshot(elements) {
|
|
636
|
-
this.elements.clear();
|
|
637
|
-
for (const el of elements) {
|
|
638
|
-
this.elements.set(el.id, el);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
on(event, listener) {
|
|
642
|
-
return this.bus.on(event, listener);
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
|
|
646
747
|
// src/elements/arrow-geometry.ts
|
|
647
748
|
function getArrowControlPoint(from, to, bend) {
|
|
648
749
|
const midX = (from.x + to.x) / 2;
|
|
@@ -743,6 +844,185 @@ function isNearLine(point, a, b, threshold) {
|
|
|
743
844
|
return Math.hypot(point.x - projX, point.y - projY) <= threshold;
|
|
744
845
|
}
|
|
745
846
|
|
|
847
|
+
// src/elements/element-bounds.ts
|
|
848
|
+
var strokeBoundsCache = /* @__PURE__ */ new WeakMap();
|
|
849
|
+
function getElementBounds(element) {
|
|
850
|
+
if (element.type === "grid") return null;
|
|
851
|
+
if ("size" in element) {
|
|
852
|
+
return {
|
|
853
|
+
x: element.position.x,
|
|
854
|
+
y: element.position.y,
|
|
855
|
+
w: element.size.w,
|
|
856
|
+
h: element.size.h
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
if (element.type === "stroke") {
|
|
860
|
+
if (element.points.length === 0) return null;
|
|
861
|
+
const cached = strokeBoundsCache.get(element);
|
|
862
|
+
if (cached) return cached;
|
|
863
|
+
let minX = Infinity;
|
|
864
|
+
let minY = Infinity;
|
|
865
|
+
let maxX = -Infinity;
|
|
866
|
+
let maxY = -Infinity;
|
|
867
|
+
for (const p of element.points) {
|
|
868
|
+
const px = p.x + element.position.x;
|
|
869
|
+
const py = p.y + element.position.y;
|
|
870
|
+
if (px < minX) minX = px;
|
|
871
|
+
if (py < minY) minY = py;
|
|
872
|
+
if (px > maxX) maxX = px;
|
|
873
|
+
if (py > maxY) maxY = py;
|
|
874
|
+
}
|
|
875
|
+
const bounds = { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
876
|
+
strokeBoundsCache.set(element, bounds);
|
|
877
|
+
return bounds;
|
|
878
|
+
}
|
|
879
|
+
if (element.type === "arrow") {
|
|
880
|
+
return getArrowBoundsAnalytical(element.from, element.to, element.bend);
|
|
881
|
+
}
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
function getArrowBoundsAnalytical(from, to, bend) {
|
|
885
|
+
if (bend === 0) {
|
|
886
|
+
const minX2 = Math.min(from.x, to.x);
|
|
887
|
+
const minY2 = Math.min(from.y, to.y);
|
|
888
|
+
return {
|
|
889
|
+
x: minX2,
|
|
890
|
+
y: minY2,
|
|
891
|
+
w: Math.abs(to.x - from.x),
|
|
892
|
+
h: Math.abs(to.y - from.y)
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
896
|
+
let minX = Math.min(from.x, to.x);
|
|
897
|
+
let maxX = Math.max(from.x, to.x);
|
|
898
|
+
let minY = Math.min(from.y, to.y);
|
|
899
|
+
let maxY = Math.max(from.y, to.y);
|
|
900
|
+
const tx = from.x - 2 * cp.x + to.x;
|
|
901
|
+
if (tx !== 0) {
|
|
902
|
+
const t = (from.x - cp.x) / tx;
|
|
903
|
+
if (t > 0 && t < 1) {
|
|
904
|
+
const mt = 1 - t;
|
|
905
|
+
const x = mt * mt * from.x + 2 * mt * t * cp.x + t * t * to.x;
|
|
906
|
+
if (x < minX) minX = x;
|
|
907
|
+
if (x > maxX) maxX = x;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const ty = from.y - 2 * cp.y + to.y;
|
|
911
|
+
if (ty !== 0) {
|
|
912
|
+
const t = (from.y - cp.y) / ty;
|
|
913
|
+
if (t > 0 && t < 1) {
|
|
914
|
+
const mt = 1 - t;
|
|
915
|
+
const y = mt * mt * from.y + 2 * mt * t * cp.y + t * t * to.y;
|
|
916
|
+
if (y < minY) minY = y;
|
|
917
|
+
if (y > maxY) maxY = y;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
921
|
+
}
|
|
922
|
+
function boundsIntersect(a, b) {
|
|
923
|
+
return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/elements/element-store.ts
|
|
927
|
+
var ElementStore = class {
|
|
928
|
+
elements = /* @__PURE__ */ new Map();
|
|
929
|
+
bus = new EventBus();
|
|
930
|
+
layerOrderMap = /* @__PURE__ */ new Map();
|
|
931
|
+
spatialIndex = new Quadtree({ x: -1e5, y: -1e5, w: 2e5, h: 2e5 });
|
|
932
|
+
get count() {
|
|
933
|
+
return this.elements.size;
|
|
934
|
+
}
|
|
935
|
+
setLayerOrder(order) {
|
|
936
|
+
this.layerOrderMap = new Map(order);
|
|
937
|
+
}
|
|
938
|
+
getAll() {
|
|
939
|
+
return [...this.elements.values()].sort((a, b) => {
|
|
940
|
+
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
941
|
+
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
942
|
+
if (layerA !== layerB) return layerA - layerB;
|
|
943
|
+
return a.zIndex - b.zIndex;
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
getById(id) {
|
|
947
|
+
return this.elements.get(id);
|
|
948
|
+
}
|
|
949
|
+
getElementsByType(type) {
|
|
950
|
+
return this.getAll().filter(
|
|
951
|
+
(el) => el.type === type
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
add(element) {
|
|
955
|
+
this.elements.set(element.id, element);
|
|
956
|
+
const bounds = getElementBounds(element);
|
|
957
|
+
if (bounds) this.spatialIndex.insert(element.id, bounds);
|
|
958
|
+
this.bus.emit("add", element);
|
|
959
|
+
}
|
|
960
|
+
update(id, partial) {
|
|
961
|
+
const existing = this.elements.get(id);
|
|
962
|
+
if (!existing) return;
|
|
963
|
+
const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
|
|
964
|
+
this.elements.set(id, updated);
|
|
965
|
+
const newBounds = getElementBounds(updated);
|
|
966
|
+
if (newBounds) {
|
|
967
|
+
this.spatialIndex.update(id, newBounds);
|
|
968
|
+
}
|
|
969
|
+
this.bus.emit("update", { previous: existing, current: updated });
|
|
970
|
+
}
|
|
971
|
+
remove(id) {
|
|
972
|
+
const element = this.elements.get(id);
|
|
973
|
+
if (!element) return;
|
|
974
|
+
this.elements.delete(id);
|
|
975
|
+
this.spatialIndex.remove(id);
|
|
976
|
+
this.bus.emit("remove", element);
|
|
977
|
+
}
|
|
978
|
+
clear() {
|
|
979
|
+
this.elements.clear();
|
|
980
|
+
this.spatialIndex.clear();
|
|
981
|
+
this.bus.emit("clear", null);
|
|
982
|
+
}
|
|
983
|
+
snapshot() {
|
|
984
|
+
return this.getAll().map((el) => ({ ...el }));
|
|
985
|
+
}
|
|
986
|
+
loadSnapshot(elements) {
|
|
987
|
+
this.elements.clear();
|
|
988
|
+
this.spatialIndex.clear();
|
|
989
|
+
for (const el of elements) {
|
|
990
|
+
this.elements.set(el.id, el);
|
|
991
|
+
const bounds = getElementBounds(el);
|
|
992
|
+
if (bounds) this.spatialIndex.insert(el.id, bounds);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
queryRect(rect) {
|
|
996
|
+
const ids = this.spatialIndex.query(rect);
|
|
997
|
+
const elements = [];
|
|
998
|
+
for (const id of ids) {
|
|
999
|
+
const el = this.elements.get(id);
|
|
1000
|
+
if (el) elements.push(el);
|
|
1001
|
+
}
|
|
1002
|
+
return elements.sort((a, b) => {
|
|
1003
|
+
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
1004
|
+
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
1005
|
+
if (layerA !== layerB) return layerA - layerB;
|
|
1006
|
+
return a.zIndex - b.zIndex;
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
queryPoint(point) {
|
|
1010
|
+
return this.queryRect({ x: point.x, y: point.y, w: 0, h: 0 });
|
|
1011
|
+
}
|
|
1012
|
+
on(event, listener) {
|
|
1013
|
+
return this.bus.on(event, listener);
|
|
1014
|
+
}
|
|
1015
|
+
onChange(listener) {
|
|
1016
|
+
const unsubs = [
|
|
1017
|
+
this.bus.on("add", listener),
|
|
1018
|
+
this.bus.on("remove", listener),
|
|
1019
|
+
this.bus.on("update", listener),
|
|
1020
|
+
this.bus.on("clear", listener)
|
|
1021
|
+
];
|
|
1022
|
+
return () => unsubs.forEach((fn) => fn());
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
|
|
746
1026
|
// src/elements/arrow-binding.ts
|
|
747
1027
|
var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
|
|
748
1028
|
function isBindable(element) {
|
|
@@ -757,15 +1037,6 @@ function getElementCenter(element) {
|
|
|
757
1037
|
y: element.position.y + element.size.h / 2
|
|
758
1038
|
};
|
|
759
1039
|
}
|
|
760
|
-
function getElementBounds(element) {
|
|
761
|
-
if (!("size" in element)) return null;
|
|
762
|
-
return {
|
|
763
|
-
x: element.position.x,
|
|
764
|
-
y: element.position.y,
|
|
765
|
-
w: element.size.w,
|
|
766
|
-
h: element.size.h
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
1040
|
function getEdgeIntersection(bounds, outsidePoint) {
|
|
770
1041
|
const cx = bounds.x + bounds.w / 2;
|
|
771
1042
|
const cy = bounds.y + bounds.h / 2;
|
|
@@ -2092,6 +2363,10 @@ var LayerManager = class {
|
|
|
2092
2363
|
this.updateLayerDirect(id, { locked });
|
|
2093
2364
|
return true;
|
|
2094
2365
|
}
|
|
2366
|
+
setLayerOpacity(id, opacity) {
|
|
2367
|
+
if (!this.layers.has(id)) return;
|
|
2368
|
+
this.updateLayerDirect(id, { opacity: Math.max(0, Math.min(1, opacity)) });
|
|
2369
|
+
}
|
|
2095
2370
|
setActiveLayer(id) {
|
|
2096
2371
|
if (!this.layers.has(id)) return;
|
|
2097
2372
|
this._activeLayerId = id;
|
|
@@ -2147,36 +2422,522 @@ var LayerManager = class {
|
|
|
2147
2422
|
}
|
|
2148
2423
|
};
|
|
2149
2424
|
|
|
2150
|
-
// src/canvas/
|
|
2151
|
-
var
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
this.
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
this.
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
this.
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
this.
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
this.
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2425
|
+
// src/canvas/interact-mode.ts
|
|
2426
|
+
var InteractMode = class {
|
|
2427
|
+
interactingElementId = null;
|
|
2428
|
+
getNode;
|
|
2429
|
+
constructor(deps) {
|
|
2430
|
+
this.getNode = deps.getNode;
|
|
2431
|
+
}
|
|
2432
|
+
startInteracting(id) {
|
|
2433
|
+
this.stopInteracting();
|
|
2434
|
+
const node = this.getNode(id);
|
|
2435
|
+
if (!node) return;
|
|
2436
|
+
this.interactingElementId = id;
|
|
2437
|
+
node.style.pointerEvents = "auto";
|
|
2438
|
+
node.addEventListener("pointerdown", this.onNodePointerDown);
|
|
2439
|
+
window.addEventListener("keydown", this.onKeyDown);
|
|
2440
|
+
window.addEventListener("pointerdown", this.onPointerDown);
|
|
2441
|
+
}
|
|
2442
|
+
stopInteracting() {
|
|
2443
|
+
if (!this.interactingElementId) return;
|
|
2444
|
+
const node = this.getNode(this.interactingElementId);
|
|
2445
|
+
if (node) {
|
|
2446
|
+
node.style.pointerEvents = "none";
|
|
2447
|
+
node.removeEventListener("pointerdown", this.onNodePointerDown);
|
|
2448
|
+
}
|
|
2449
|
+
this.interactingElementId = null;
|
|
2450
|
+
window.removeEventListener("keydown", this.onKeyDown);
|
|
2451
|
+
window.removeEventListener("pointerdown", this.onPointerDown);
|
|
2452
|
+
}
|
|
2453
|
+
isInteracting() {
|
|
2454
|
+
return this.interactingElementId !== null;
|
|
2455
|
+
}
|
|
2456
|
+
destroy() {
|
|
2457
|
+
this.stopInteracting();
|
|
2458
|
+
}
|
|
2459
|
+
onNodePointerDown = (e) => {
|
|
2460
|
+
e.stopPropagation();
|
|
2461
|
+
};
|
|
2462
|
+
onKeyDown = (e) => {
|
|
2463
|
+
if (e.key === "Escape") {
|
|
2464
|
+
this.stopInteracting();
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2467
|
+
onPointerDown = (e) => {
|
|
2468
|
+
if (!this.interactingElementId) return;
|
|
2469
|
+
const target = e.target;
|
|
2470
|
+
if (!(target instanceof Element)) {
|
|
2471
|
+
this.stopInteracting();
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
const node = this.getNode(this.interactingElementId);
|
|
2475
|
+
if (node && !node.contains(target)) {
|
|
2476
|
+
this.stopInteracting();
|
|
2477
|
+
}
|
|
2478
|
+
};
|
|
2479
|
+
};
|
|
2480
|
+
|
|
2481
|
+
// src/canvas/dom-node-manager.ts
|
|
2482
|
+
var DomNodeManager = class {
|
|
2483
|
+
domNodes = /* @__PURE__ */ new Map();
|
|
2484
|
+
htmlContent = /* @__PURE__ */ new Map();
|
|
2485
|
+
domLayer;
|
|
2486
|
+
onEditRequest;
|
|
2487
|
+
isEditingElement;
|
|
2488
|
+
constructor(deps) {
|
|
2489
|
+
this.domLayer = deps.domLayer;
|
|
2490
|
+
this.onEditRequest = deps.onEditRequest;
|
|
2491
|
+
this.isEditingElement = deps.isEditingElement;
|
|
2492
|
+
}
|
|
2493
|
+
getNode(id) {
|
|
2494
|
+
return this.domNodes.get(id);
|
|
2495
|
+
}
|
|
2496
|
+
storeHtmlContent(elementId, dom) {
|
|
2497
|
+
this.htmlContent.set(elementId, dom);
|
|
2498
|
+
}
|
|
2499
|
+
syncDomNode(element, zIndex = 0) {
|
|
2500
|
+
let node = this.domNodes.get(element.id);
|
|
2501
|
+
if (!node) {
|
|
2502
|
+
node = document.createElement("div");
|
|
2503
|
+
node.dataset["elementId"] = element.id;
|
|
2504
|
+
Object.assign(node.style, {
|
|
2505
|
+
position: "absolute",
|
|
2506
|
+
pointerEvents: "auto"
|
|
2507
|
+
});
|
|
2508
|
+
this.domLayer.appendChild(node);
|
|
2509
|
+
this.domNodes.set(element.id, node);
|
|
2510
|
+
}
|
|
2511
|
+
const size = "size" in element ? element.size : null;
|
|
2512
|
+
Object.assign(node.style, {
|
|
2513
|
+
display: "block",
|
|
2514
|
+
left: `${element.position.x}px`,
|
|
2515
|
+
top: `${element.position.y}px`,
|
|
2516
|
+
width: size ? `${size.w}px` : "auto",
|
|
2517
|
+
height: size ? `${size.h}px` : "auto",
|
|
2518
|
+
zIndex: String(zIndex)
|
|
2519
|
+
});
|
|
2520
|
+
this.renderDomContent(node, element);
|
|
2521
|
+
}
|
|
2522
|
+
hideDomNode(id) {
|
|
2523
|
+
const node = this.domNodes.get(id);
|
|
2524
|
+
if (node) node.style.display = "none";
|
|
2525
|
+
}
|
|
2526
|
+
removeDomNode(id) {
|
|
2527
|
+
this.htmlContent.delete(id);
|
|
2528
|
+
const node = this.domNodes.get(id);
|
|
2529
|
+
if (node) {
|
|
2530
|
+
node.remove();
|
|
2531
|
+
this.domNodes.delete(id);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
clearDomNodes() {
|
|
2535
|
+
this.domNodes.forEach((node) => node.remove());
|
|
2536
|
+
this.domNodes.clear();
|
|
2537
|
+
this.htmlContent.clear();
|
|
2538
|
+
}
|
|
2539
|
+
reattachHtmlContent(store) {
|
|
2540
|
+
for (const el of store.getElementsByType("html")) {
|
|
2541
|
+
if (el.domId) {
|
|
2542
|
+
const dom = document.getElementById(el.domId);
|
|
2543
|
+
if (dom) {
|
|
2544
|
+
this.htmlContent.set(el.id, dom);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
renderDomContent(node, element) {
|
|
2550
|
+
if (element.type === "note") {
|
|
2551
|
+
if (!node.dataset["initialized"]) {
|
|
2552
|
+
node.dataset["initialized"] = "true";
|
|
2553
|
+
Object.assign(node.style, {
|
|
2554
|
+
backgroundColor: element.backgroundColor,
|
|
2555
|
+
color: element.textColor,
|
|
2556
|
+
padding: "8px",
|
|
2557
|
+
borderRadius: "4px",
|
|
2558
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
|
|
2559
|
+
fontSize: "14px",
|
|
2560
|
+
overflow: "hidden",
|
|
2561
|
+
cursor: "default",
|
|
2562
|
+
userSelect: "none",
|
|
2563
|
+
wordWrap: "break-word"
|
|
2564
|
+
});
|
|
2565
|
+
node.textContent = element.text || "";
|
|
2566
|
+
node.addEventListener("dblclick", (e) => {
|
|
2567
|
+
e.stopPropagation();
|
|
2568
|
+
const id = node.dataset["elementId"];
|
|
2569
|
+
if (id) this.onEditRequest(id);
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
if (!this.isEditingElement(element.id)) {
|
|
2573
|
+
if (node.textContent !== element.text) {
|
|
2574
|
+
node.textContent = element.text || "";
|
|
2575
|
+
}
|
|
2576
|
+
node.style.backgroundColor = element.backgroundColor;
|
|
2577
|
+
node.style.color = element.textColor;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
if (element.type === "html" && !node.dataset["initialized"]) {
|
|
2581
|
+
const content = this.htmlContent.get(element.id);
|
|
2582
|
+
if (content) {
|
|
2583
|
+
node.dataset["initialized"] = "true";
|
|
2584
|
+
Object.assign(node.style, {
|
|
2585
|
+
overflow: "hidden",
|
|
2586
|
+
pointerEvents: "none"
|
|
2587
|
+
});
|
|
2588
|
+
node.appendChild(content);
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
if (element.type === "text") {
|
|
2592
|
+
if (!node.dataset["initialized"]) {
|
|
2593
|
+
node.dataset["initialized"] = "true";
|
|
2594
|
+
Object.assign(node.style, {
|
|
2595
|
+
padding: "2px",
|
|
2596
|
+
fontSize: `${element.fontSize}px`,
|
|
2597
|
+
color: element.color,
|
|
2598
|
+
textAlign: element.textAlign,
|
|
2599
|
+
background: "none",
|
|
2600
|
+
border: "none",
|
|
2601
|
+
boxShadow: "none",
|
|
2602
|
+
overflow: "visible",
|
|
2603
|
+
cursor: "default",
|
|
2604
|
+
userSelect: "none",
|
|
2605
|
+
wordWrap: "break-word",
|
|
2606
|
+
whiteSpace: "pre-wrap",
|
|
2607
|
+
lineHeight: "1.4"
|
|
2608
|
+
});
|
|
2609
|
+
node.textContent = element.text || "";
|
|
2610
|
+
node.addEventListener("dblclick", (e) => {
|
|
2611
|
+
e.stopPropagation();
|
|
2612
|
+
const id = node.dataset["elementId"];
|
|
2613
|
+
if (id) this.onEditRequest(id);
|
|
2614
|
+
});
|
|
2615
|
+
}
|
|
2616
|
+
if (!this.isEditingElement(element.id)) {
|
|
2617
|
+
if (node.textContent !== element.text) {
|
|
2618
|
+
node.textContent = element.text || "";
|
|
2619
|
+
}
|
|
2620
|
+
Object.assign(node.style, {
|
|
2621
|
+
fontSize: `${element.fontSize}px`,
|
|
2622
|
+
color: element.color,
|
|
2623
|
+
textAlign: element.textAlign
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
};
|
|
2629
|
+
|
|
2630
|
+
// src/canvas/render-stats.ts
|
|
2631
|
+
var SAMPLE_SIZE = 60;
|
|
2632
|
+
var RenderStats = class {
|
|
2633
|
+
frameTimes = [];
|
|
2634
|
+
frameCount = 0;
|
|
2635
|
+
recordFrame(durationMs) {
|
|
2636
|
+
this.frameCount++;
|
|
2637
|
+
this.frameTimes.push(durationMs);
|
|
2638
|
+
if (this.frameTimes.length > SAMPLE_SIZE) {
|
|
2639
|
+
this.frameTimes.shift();
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
getSnapshot() {
|
|
2643
|
+
const times = this.frameTimes;
|
|
2644
|
+
if (times.length === 0) {
|
|
2645
|
+
return { fps: 0, avgFrameMs: 0, p95FrameMs: 0, lastFrameMs: 0, frameCount: 0 };
|
|
2646
|
+
}
|
|
2647
|
+
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
2648
|
+
const sorted = [...times].sort((a, b) => a - b);
|
|
2649
|
+
const p95Index = Math.min(Math.floor(sorted.length * 0.95), sorted.length - 1);
|
|
2650
|
+
const lastFrame = times[times.length - 1] ?? 0;
|
|
2651
|
+
return {
|
|
2652
|
+
fps: avg > 0 ? Math.round(1e3 / avg) : 0,
|
|
2653
|
+
avgFrameMs: Math.round(avg * 100) / 100,
|
|
2654
|
+
p95FrameMs: Math.round((sorted[p95Index] ?? 0) * 100) / 100,
|
|
2655
|
+
lastFrameMs: Math.round(lastFrame * 100) / 100,
|
|
2656
|
+
frameCount: this.frameCount
|
|
2657
|
+
};
|
|
2658
|
+
}
|
|
2659
|
+
reset() {
|
|
2660
|
+
this.frameTimes = [];
|
|
2661
|
+
this.frameCount = 0;
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
|
|
2665
|
+
// src/canvas/render-loop.ts
|
|
2666
|
+
var RenderLoop = class {
|
|
2667
|
+
needsRender = false;
|
|
2668
|
+
animFrameId = 0;
|
|
2669
|
+
canvasEl;
|
|
2670
|
+
camera;
|
|
2671
|
+
background;
|
|
2672
|
+
store;
|
|
2673
|
+
renderer;
|
|
2674
|
+
toolManager;
|
|
2675
|
+
layerManager;
|
|
2676
|
+
domNodeManager;
|
|
2677
|
+
layerCache;
|
|
2678
|
+
activeDrawingLayerId = null;
|
|
2679
|
+
lastZoom;
|
|
2680
|
+
lastCamX;
|
|
2681
|
+
lastCamY;
|
|
2682
|
+
stats = new RenderStats();
|
|
2683
|
+
constructor(deps) {
|
|
2684
|
+
this.canvasEl = deps.canvasEl;
|
|
2685
|
+
this.camera = deps.camera;
|
|
2686
|
+
this.background = deps.background;
|
|
2687
|
+
this.store = deps.store;
|
|
2688
|
+
this.renderer = deps.renderer;
|
|
2689
|
+
this.toolManager = deps.toolManager;
|
|
2690
|
+
this.layerManager = deps.layerManager;
|
|
2691
|
+
this.domNodeManager = deps.domNodeManager;
|
|
2692
|
+
this.layerCache = deps.layerCache;
|
|
2693
|
+
this.lastZoom = deps.camera.zoom;
|
|
2694
|
+
this.lastCamX = deps.camera.position.x;
|
|
2695
|
+
this.lastCamY = deps.camera.position.y;
|
|
2696
|
+
}
|
|
2697
|
+
requestRender() {
|
|
2698
|
+
this.needsRender = true;
|
|
2699
|
+
}
|
|
2700
|
+
flush() {
|
|
2701
|
+
if (this.needsRender) {
|
|
2702
|
+
this.render();
|
|
2703
|
+
this.needsRender = false;
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
start() {
|
|
2707
|
+
const loop = () => {
|
|
2708
|
+
if (this.needsRender) {
|
|
2709
|
+
this.render();
|
|
2710
|
+
this.needsRender = false;
|
|
2711
|
+
}
|
|
2712
|
+
this.animFrameId = requestAnimationFrame(loop);
|
|
2713
|
+
};
|
|
2714
|
+
this.animFrameId = requestAnimationFrame(loop);
|
|
2715
|
+
}
|
|
2716
|
+
stop() {
|
|
2717
|
+
cancelAnimationFrame(this.animFrameId);
|
|
2718
|
+
}
|
|
2719
|
+
setCanvasSize(width, height) {
|
|
2720
|
+
this.canvasEl.width = width;
|
|
2721
|
+
this.canvasEl.height = height;
|
|
2722
|
+
this.layerCache.resize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
|
|
2723
|
+
}
|
|
2724
|
+
setActiveDrawingLayer(layerId) {
|
|
2725
|
+
this.activeDrawingLayerId = layerId;
|
|
2726
|
+
}
|
|
2727
|
+
markLayerDirty(layerId) {
|
|
2728
|
+
this.layerCache.markDirty(layerId);
|
|
2729
|
+
}
|
|
2730
|
+
markAllLayersDirty() {
|
|
2731
|
+
this.layerCache.markAllDirty();
|
|
2732
|
+
}
|
|
2733
|
+
getStats() {
|
|
2734
|
+
return this.stats.getSnapshot();
|
|
2735
|
+
}
|
|
2736
|
+
compositeLayerCache(ctx, layerId, dpr) {
|
|
2737
|
+
const cached = this.layerCache.getCanvas(layerId);
|
|
2738
|
+
ctx.save();
|
|
2739
|
+
ctx.scale(1 / this.camera.zoom, 1 / this.camera.zoom);
|
|
2740
|
+
ctx.translate(-this.camera.position.x, -this.camera.position.y);
|
|
2741
|
+
ctx.scale(1 / dpr, 1 / dpr);
|
|
2742
|
+
ctx.drawImage(cached, 0, 0);
|
|
2743
|
+
ctx.restore();
|
|
2744
|
+
}
|
|
2745
|
+
render() {
|
|
2746
|
+
const t0 = performance.now();
|
|
2747
|
+
const ctx = this.canvasEl.getContext("2d");
|
|
2748
|
+
if (!ctx) return;
|
|
2749
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2750
|
+
const cssWidth = this.canvasEl.clientWidth;
|
|
2751
|
+
const cssHeight = this.canvasEl.clientHeight;
|
|
2752
|
+
const currentZoom = this.camera.zoom;
|
|
2753
|
+
const currentCamX = this.camera.position.x;
|
|
2754
|
+
const currentCamY = this.camera.position.y;
|
|
2755
|
+
if (currentZoom !== this.lastZoom || currentCamX !== this.lastCamX || currentCamY !== this.lastCamY) {
|
|
2756
|
+
this.layerCache.markAllDirty();
|
|
2757
|
+
this.lastZoom = currentZoom;
|
|
2758
|
+
this.lastCamX = currentCamX;
|
|
2759
|
+
this.lastCamY = currentCamY;
|
|
2760
|
+
}
|
|
2761
|
+
ctx.save();
|
|
2762
|
+
ctx.scale(dpr, dpr);
|
|
2763
|
+
this.renderer.setCanvasSize(cssWidth, cssHeight);
|
|
2764
|
+
this.background.render(ctx, this.camera);
|
|
2765
|
+
ctx.save();
|
|
2766
|
+
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
2767
|
+
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
2768
|
+
const visibleRect = this.camera.getVisibleRect(cssWidth, cssHeight);
|
|
2769
|
+
const margin = Math.max(visibleRect.w, visibleRect.h) * 0.1;
|
|
2770
|
+
const cullingRect = {
|
|
2771
|
+
x: visibleRect.x - margin,
|
|
2772
|
+
y: visibleRect.y - margin,
|
|
2773
|
+
w: visibleRect.w + margin * 2,
|
|
2774
|
+
h: visibleRect.h + margin * 2
|
|
2775
|
+
};
|
|
2776
|
+
const allElements = this.store.getAll();
|
|
2777
|
+
const layerElements = /* @__PURE__ */ new Map();
|
|
2778
|
+
const gridElements = [];
|
|
2779
|
+
let domZIndex = 0;
|
|
2780
|
+
for (const element of allElements) {
|
|
2781
|
+
if (!this.layerManager.isLayerVisible(element.layerId)) {
|
|
2782
|
+
if (this.renderer.isDomElement(element)) {
|
|
2783
|
+
this.domNodeManager.hideDomNode(element.id);
|
|
2784
|
+
}
|
|
2785
|
+
continue;
|
|
2786
|
+
}
|
|
2787
|
+
if (this.renderer.isDomElement(element)) {
|
|
2788
|
+
const elBounds = getElementBounds(element);
|
|
2789
|
+
if (elBounds && !boundsIntersect(elBounds, cullingRect)) {
|
|
2790
|
+
this.domNodeManager.hideDomNode(element.id);
|
|
2791
|
+
} else {
|
|
2792
|
+
this.domNodeManager.syncDomNode(element, domZIndex++);
|
|
2793
|
+
}
|
|
2794
|
+
continue;
|
|
2795
|
+
}
|
|
2796
|
+
if (element.type === "grid") {
|
|
2797
|
+
gridElements.push(element);
|
|
2798
|
+
continue;
|
|
2799
|
+
}
|
|
2800
|
+
let group = layerElements.get(element.layerId);
|
|
2801
|
+
if (!group) {
|
|
2802
|
+
group = [];
|
|
2803
|
+
layerElements.set(element.layerId, group);
|
|
2804
|
+
}
|
|
2805
|
+
group.push(element);
|
|
2806
|
+
}
|
|
2807
|
+
for (const grid of gridElements) {
|
|
2808
|
+
this.renderer.renderCanvasElement(ctx, grid);
|
|
2809
|
+
}
|
|
2810
|
+
for (const [layerId, elements] of layerElements) {
|
|
2811
|
+
const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
|
|
2812
|
+
if (!this.layerCache.isDirty(layerId)) {
|
|
2813
|
+
this.compositeLayerCache(ctx, layerId, dpr);
|
|
2814
|
+
continue;
|
|
2815
|
+
}
|
|
2816
|
+
if (isActiveDrawingLayer) {
|
|
2817
|
+
this.compositeLayerCache(ctx, layerId, dpr);
|
|
2818
|
+
continue;
|
|
2819
|
+
}
|
|
2820
|
+
const offCtx = this.layerCache.getContext(layerId);
|
|
2821
|
+
if (offCtx) {
|
|
2822
|
+
const offCanvas = this.layerCache.getCanvas(layerId);
|
|
2823
|
+
offCtx.clearRect(0, 0, offCanvas.width, offCanvas.height);
|
|
2824
|
+
offCtx.save();
|
|
2825
|
+
offCtx.scale(dpr, dpr);
|
|
2826
|
+
offCtx.translate(this.camera.position.x, this.camera.position.y);
|
|
2827
|
+
offCtx.scale(this.camera.zoom, this.camera.zoom);
|
|
2828
|
+
for (const element of elements) {
|
|
2829
|
+
const elBounds = getElementBounds(element);
|
|
2830
|
+
if (elBounds && !boundsIntersect(elBounds, cullingRect)) continue;
|
|
2831
|
+
this.renderer.renderCanvasElement(offCtx, element);
|
|
2832
|
+
}
|
|
2833
|
+
offCtx.restore();
|
|
2834
|
+
this.layerCache.markClean(layerId);
|
|
2835
|
+
this.compositeLayerCache(ctx, layerId, dpr);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
const activeTool = this.toolManager.activeTool;
|
|
2839
|
+
if (activeTool?.renderOverlay) {
|
|
2840
|
+
activeTool.renderOverlay(ctx);
|
|
2841
|
+
}
|
|
2842
|
+
ctx.restore();
|
|
2843
|
+
ctx.restore();
|
|
2844
|
+
this.stats.recordFrame(performance.now() - t0);
|
|
2845
|
+
}
|
|
2846
|
+
};
|
|
2847
|
+
|
|
2848
|
+
// src/canvas/layer-cache.ts
|
|
2849
|
+
function createOffscreenCanvas(width, height) {
|
|
2850
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
2851
|
+
return new OffscreenCanvas(width, height);
|
|
2852
|
+
}
|
|
2853
|
+
const canvas = document.createElement("canvas");
|
|
2854
|
+
canvas.width = width;
|
|
2855
|
+
canvas.height = height;
|
|
2856
|
+
return canvas;
|
|
2857
|
+
}
|
|
2858
|
+
var LayerCache = class {
|
|
2859
|
+
canvases = /* @__PURE__ */ new Map();
|
|
2860
|
+
dirtyFlags = /* @__PURE__ */ new Map();
|
|
2861
|
+
width;
|
|
2862
|
+
height;
|
|
2863
|
+
constructor(width, height) {
|
|
2864
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2865
|
+
this.width = Math.round(width * dpr);
|
|
2866
|
+
this.height = Math.round(height * dpr);
|
|
2867
|
+
}
|
|
2868
|
+
isDirty(layerId) {
|
|
2869
|
+
return this.dirtyFlags.get(layerId) !== false;
|
|
2870
|
+
}
|
|
2871
|
+
markDirty(layerId) {
|
|
2872
|
+
this.dirtyFlags.set(layerId, true);
|
|
2873
|
+
}
|
|
2874
|
+
markClean(layerId) {
|
|
2875
|
+
this.dirtyFlags.set(layerId, false);
|
|
2876
|
+
}
|
|
2877
|
+
markAllDirty() {
|
|
2878
|
+
for (const [id] of this.dirtyFlags) {
|
|
2879
|
+
this.dirtyFlags.set(id, true);
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
getCanvas(layerId) {
|
|
2883
|
+
let canvas = this.canvases.get(layerId);
|
|
2884
|
+
if (!canvas) {
|
|
2885
|
+
canvas = createOffscreenCanvas(this.width, this.height);
|
|
2886
|
+
this.canvases.set(layerId, canvas);
|
|
2887
|
+
this.dirtyFlags.set(layerId, true);
|
|
2888
|
+
}
|
|
2889
|
+
return canvas;
|
|
2890
|
+
}
|
|
2891
|
+
getContext(layerId) {
|
|
2892
|
+
const canvas = this.getCanvas(layerId);
|
|
2893
|
+
return canvas.getContext("2d");
|
|
2894
|
+
}
|
|
2895
|
+
resize(width, height) {
|
|
2896
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2897
|
+
this.width = Math.round(width * dpr);
|
|
2898
|
+
this.height = Math.round(height * dpr);
|
|
2899
|
+
for (const [id, canvas] of this.canvases) {
|
|
2900
|
+
canvas.width = this.width;
|
|
2901
|
+
canvas.height = this.height;
|
|
2902
|
+
this.dirtyFlags.set(id, true);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
clear() {
|
|
2906
|
+
this.canvases.clear();
|
|
2907
|
+
this.dirtyFlags.clear();
|
|
2908
|
+
}
|
|
2909
|
+
};
|
|
2910
|
+
|
|
2911
|
+
// src/canvas/viewport.ts
|
|
2912
|
+
var Viewport = class {
|
|
2913
|
+
constructor(container, options = {}) {
|
|
2914
|
+
this.container = container;
|
|
2915
|
+
this.camera = new Camera(options.camera);
|
|
2916
|
+
this.background = new Background(options.background);
|
|
2917
|
+
this._gridSize = options.background?.spacing ?? 24;
|
|
2918
|
+
this.store = new ElementStore();
|
|
2919
|
+
this.layerManager = new LayerManager(this.store);
|
|
2920
|
+
this.toolManager = new ToolManager();
|
|
2921
|
+
this.renderer = new ElementRenderer();
|
|
2922
|
+
this.renderer.setStore(this.store);
|
|
2923
|
+
this.renderer.setCamera(this.camera);
|
|
2924
|
+
this.renderer.setOnImageLoad(() => this.requestRender());
|
|
2925
|
+
this.noteEditor = new NoteEditor();
|
|
2926
|
+
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
2927
|
+
this.history = new HistoryStack();
|
|
2928
|
+
this.historyRecorder = new HistoryRecorder(this.store, this.history);
|
|
2929
|
+
this.wrapper = this.createWrapper();
|
|
2930
|
+
this.canvasEl = this.createCanvas();
|
|
2931
|
+
this.domLayer = this.createDomLayer();
|
|
2932
|
+
this.wrapper.appendChild(this.canvasEl);
|
|
2933
|
+
this.wrapper.appendChild(this.domLayer);
|
|
2934
|
+
this.container.appendChild(this.wrapper);
|
|
2935
|
+
this.toolContext = {
|
|
2936
|
+
camera: this.camera,
|
|
2937
|
+
store: this.store,
|
|
2938
|
+
requestRender: () => this.requestRender(),
|
|
2939
|
+
switchTool: (name) => this.toolManager.setTool(name, this.toolContext),
|
|
2940
|
+
editElement: (id) => this.startEditingElement(id),
|
|
2180
2941
|
setCursor: (cursor) => {
|
|
2181
2942
|
this.wrapper.style.cursor = cursor;
|
|
2182
2943
|
},
|
|
@@ -2192,18 +2953,56 @@ var Viewport = class {
|
|
|
2192
2953
|
historyRecorder: this.historyRecorder,
|
|
2193
2954
|
historyStack: this.history
|
|
2194
2955
|
});
|
|
2956
|
+
this.domNodeManager = new DomNodeManager({
|
|
2957
|
+
domLayer: this.domLayer,
|
|
2958
|
+
onEditRequest: (id) => this.startEditingElement(id),
|
|
2959
|
+
isEditingElement: (id) => this.noteEditor.isEditing && this.noteEditor.editingElementId === id
|
|
2960
|
+
});
|
|
2961
|
+
this.interactMode = new InteractMode({
|
|
2962
|
+
getNode: (id) => this.domNodeManager.getNode(id)
|
|
2963
|
+
});
|
|
2964
|
+
const layerCache = new LayerCache(
|
|
2965
|
+
this.canvasEl.clientWidth || 800,
|
|
2966
|
+
this.canvasEl.clientHeight || 600
|
|
2967
|
+
);
|
|
2968
|
+
this.renderLoop = new RenderLoop({
|
|
2969
|
+
canvasEl: this.canvasEl,
|
|
2970
|
+
camera: this.camera,
|
|
2971
|
+
background: this.background,
|
|
2972
|
+
store: this.store,
|
|
2973
|
+
renderer: this.renderer,
|
|
2974
|
+
toolManager: this.toolManager,
|
|
2975
|
+
layerManager: this.layerManager,
|
|
2976
|
+
domNodeManager: this.domNodeManager,
|
|
2977
|
+
layerCache
|
|
2978
|
+
});
|
|
2195
2979
|
this.unsubCamera = this.camera.onChange(() => {
|
|
2196
2980
|
this.applyCameraTransform();
|
|
2197
2981
|
this.requestRender();
|
|
2198
2982
|
});
|
|
2199
2983
|
this.unsubStore = [
|
|
2200
|
-
this.store.on("add", () =>
|
|
2984
|
+
this.store.on("add", (el) => {
|
|
2985
|
+
this.renderLoop.markLayerDirty(el.layerId);
|
|
2986
|
+
this.requestRender();
|
|
2987
|
+
}),
|
|
2201
2988
|
this.store.on("remove", (el) => {
|
|
2202
2989
|
this.unbindArrowsFrom(el);
|
|
2203
|
-
this.removeDomNode(el.id);
|
|
2990
|
+
this.domNodeManager.removeDomNode(el.id);
|
|
2991
|
+
this.renderLoop.markLayerDirty(el.layerId);
|
|
2992
|
+
this.requestRender();
|
|
2993
|
+
}),
|
|
2994
|
+
this.store.on("update", ({ previous, current }) => {
|
|
2995
|
+
this.renderLoop.markLayerDirty(current.layerId);
|
|
2996
|
+
if (previous.layerId !== current.layerId) {
|
|
2997
|
+
this.renderLoop.markLayerDirty(previous.layerId);
|
|
2998
|
+
}
|
|
2999
|
+
this.requestRender();
|
|
2204
3000
|
}),
|
|
2205
|
-
this.store.on("
|
|
2206
|
-
|
|
3001
|
+
this.store.on("clear", () => {
|
|
3002
|
+
this.domNodeManager.clearDomNodes();
|
|
3003
|
+
this.renderLoop.markAllLayersDirty();
|
|
3004
|
+
this.requestRender();
|
|
3005
|
+
})
|
|
2207
3006
|
];
|
|
2208
3007
|
this.layerManager.on("change", () => {
|
|
2209
3008
|
this.toolContext.activeLayerId = this.layerManager.activeLayerId;
|
|
@@ -2214,7 +3013,7 @@ var Viewport = class {
|
|
|
2214
3013
|
this.wrapper.addEventListener("drop", this.onDrop);
|
|
2215
3014
|
this.observeResize();
|
|
2216
3015
|
this.syncCanvasSize();
|
|
2217
|
-
this.
|
|
3016
|
+
this.renderLoop.start();
|
|
2218
3017
|
}
|
|
2219
3018
|
camera;
|
|
2220
3019
|
store;
|
|
@@ -2233,13 +3032,11 @@ var Viewport = class {
|
|
|
2233
3032
|
historyRecorder;
|
|
2234
3033
|
toolContext;
|
|
2235
3034
|
resizeObserver = null;
|
|
2236
|
-
animFrameId = 0;
|
|
2237
3035
|
_snapToGrid = false;
|
|
2238
3036
|
_gridSize;
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
interactingElementId = null;
|
|
3037
|
+
renderLoop;
|
|
3038
|
+
domNodeManager;
|
|
3039
|
+
interactMode;
|
|
2243
3040
|
get ctx() {
|
|
2244
3041
|
return this.canvasEl.getContext("2d");
|
|
2245
3042
|
}
|
|
@@ -2251,7 +3048,7 @@ var Viewport = class {
|
|
|
2251
3048
|
this.toolContext.snapToGrid = enabled;
|
|
2252
3049
|
}
|
|
2253
3050
|
requestRender() {
|
|
2254
|
-
this.
|
|
3051
|
+
this.renderLoop.requestRender();
|
|
2255
3052
|
}
|
|
2256
3053
|
exportState() {
|
|
2257
3054
|
return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
|
|
@@ -2265,12 +3062,12 @@ var Viewport = class {
|
|
|
2265
3062
|
loadState(state) {
|
|
2266
3063
|
this.historyRecorder.pause();
|
|
2267
3064
|
this.noteEditor.destroy(this.store);
|
|
2268
|
-
this.clearDomNodes();
|
|
3065
|
+
this.domNodeManager.clearDomNodes();
|
|
2269
3066
|
this.store.loadSnapshot(state.elements);
|
|
2270
3067
|
if (state.layers && state.layers.length > 0) {
|
|
2271
3068
|
this.layerManager.loadSnapshot(state.layers);
|
|
2272
3069
|
}
|
|
2273
|
-
this.reattachHtmlContent();
|
|
3070
|
+
this.domNodeManager.reattachHtmlContent(this.store);
|
|
2274
3071
|
this.history.clear();
|
|
2275
3072
|
this.historyRecorder.resume();
|
|
2276
3073
|
this.camera.moveTo(state.camera.position.x, state.camera.position.y);
|
|
@@ -2309,7 +3106,7 @@ var Viewport = class {
|
|
|
2309
3106
|
domId,
|
|
2310
3107
|
layerId: this.layerManager.activeLayerId
|
|
2311
3108
|
});
|
|
2312
|
-
this.
|
|
3109
|
+
this.domNodeManager.storeHtmlContent(el.id, dom);
|
|
2313
3110
|
this.historyRecorder.begin();
|
|
2314
3111
|
this.store.add(el);
|
|
2315
3112
|
this.historyRecorder.commit();
|
|
@@ -2345,8 +3142,8 @@ var Viewport = class {
|
|
|
2345
3142
|
this.requestRender();
|
|
2346
3143
|
}
|
|
2347
3144
|
destroy() {
|
|
2348
|
-
|
|
2349
|
-
this.
|
|
3145
|
+
this.renderLoop.stop();
|
|
3146
|
+
this.interactMode.destroy();
|
|
2350
3147
|
this.noteEditor.destroy(this.store);
|
|
2351
3148
|
this.historyRecorder.destroy();
|
|
2352
3149
|
this.wrapper.removeEventListener("dblclick", this.onDblClick);
|
|
@@ -2359,54 +3156,11 @@ var Viewport = class {
|
|
|
2359
3156
|
this.resizeObserver = null;
|
|
2360
3157
|
this.wrapper.remove();
|
|
2361
3158
|
}
|
|
2362
|
-
startRenderLoop() {
|
|
2363
|
-
const loop = () => {
|
|
2364
|
-
if (this.needsRender) {
|
|
2365
|
-
this.render();
|
|
2366
|
-
this.needsRender = false;
|
|
2367
|
-
}
|
|
2368
|
-
this.animFrameId = requestAnimationFrame(loop);
|
|
2369
|
-
};
|
|
2370
|
-
this.animFrameId = requestAnimationFrame(loop);
|
|
2371
|
-
}
|
|
2372
|
-
render() {
|
|
2373
|
-
const ctx = this.ctx;
|
|
2374
|
-
if (!ctx) return;
|
|
2375
|
-
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2376
|
-
ctx.save();
|
|
2377
|
-
ctx.scale(dpr, dpr);
|
|
2378
|
-
this.renderer.setCanvasSize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
|
|
2379
|
-
this.background.render(ctx, this.camera);
|
|
2380
|
-
ctx.save();
|
|
2381
|
-
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
2382
|
-
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
2383
|
-
const allElements = this.store.getAll();
|
|
2384
|
-
let domZIndex = 0;
|
|
2385
|
-
for (const element of allElements) {
|
|
2386
|
-
if (!this.layerManager.isLayerVisible(element.layerId)) {
|
|
2387
|
-
if (this.renderer.isDomElement(element)) {
|
|
2388
|
-
this.hideDomNode(element.id);
|
|
2389
|
-
}
|
|
2390
|
-
continue;
|
|
2391
|
-
}
|
|
2392
|
-
if (this.renderer.isDomElement(element)) {
|
|
2393
|
-
this.syncDomNode(element, domZIndex++);
|
|
2394
|
-
} else {
|
|
2395
|
-
this.renderer.renderCanvasElement(ctx, element);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
const activeTool = this.toolManager.activeTool;
|
|
2399
|
-
if (activeTool?.renderOverlay) {
|
|
2400
|
-
activeTool.renderOverlay(ctx);
|
|
2401
|
-
}
|
|
2402
|
-
ctx.restore();
|
|
2403
|
-
ctx.restore();
|
|
2404
|
-
}
|
|
2405
3159
|
startEditingElement(id) {
|
|
2406
3160
|
const element = this.store.getById(id);
|
|
2407
3161
|
if (!element || element.type !== "note" && element.type !== "text") return;
|
|
2408
|
-
this.
|
|
2409
|
-
const node = this.
|
|
3162
|
+
this.renderLoop.flush();
|
|
3163
|
+
const node = this.domNodeManager.getNode(id);
|
|
2410
3164
|
if (node) {
|
|
2411
3165
|
this.noteEditor.startEditing(node, id, this.store);
|
|
2412
3166
|
}
|
|
@@ -2420,7 +3174,7 @@ var Viewport = class {
|
|
|
2420
3174
|
this.historyRecorder.commit();
|
|
2421
3175
|
return;
|
|
2422
3176
|
}
|
|
2423
|
-
const node = this.
|
|
3177
|
+
const node = this.domNodeManager.getNode(elementId);
|
|
2424
3178
|
if (node && "size" in element) {
|
|
2425
3179
|
const measuredHeight = node.scrollHeight;
|
|
2426
3180
|
if (measuredHeight !== element.size.h) {
|
|
@@ -2448,12 +3202,12 @@ var Viewport = class {
|
|
|
2448
3202
|
const world = this.camera.screenToWorld(screen);
|
|
2449
3203
|
const hit = this.hitTestWorld(world);
|
|
2450
3204
|
if (hit?.type === "html") {
|
|
2451
|
-
this.startInteracting(hit.id);
|
|
3205
|
+
this.interactMode.startInteracting(hit.id);
|
|
2452
3206
|
}
|
|
2453
3207
|
};
|
|
2454
3208
|
hitTestWorld(world) {
|
|
2455
|
-
const
|
|
2456
|
-
for (const el of
|
|
3209
|
+
const candidates = this.store.queryPoint(world).reverse();
|
|
3210
|
+
for (const el of candidates) {
|
|
2457
3211
|
if (!("size" in el)) continue;
|
|
2458
3212
|
const { x, y } = el.position;
|
|
2459
3213
|
const { w, h } = el.size;
|
|
@@ -2463,44 +3217,9 @@ var Viewport = class {
|
|
|
2463
3217
|
}
|
|
2464
3218
|
return null;
|
|
2465
3219
|
}
|
|
2466
|
-
startInteracting(id) {
|
|
2467
|
-
this.stopInteracting();
|
|
2468
|
-
const node = this.domNodes.get(id);
|
|
2469
|
-
if (!node) return;
|
|
2470
|
-
this.interactingElementId = id;
|
|
2471
|
-
node.style.pointerEvents = "auto";
|
|
2472
|
-
node.addEventListener("pointerdown", this.onInteractNodePointerDown);
|
|
2473
|
-
window.addEventListener("keydown", this.onInteractKeyDown);
|
|
2474
|
-
window.addEventListener("pointerdown", this.onInteractPointerDown);
|
|
2475
|
-
}
|
|
2476
3220
|
stopInteracting() {
|
|
2477
|
-
|
|
2478
|
-
const node = this.domNodes.get(this.interactingElementId);
|
|
2479
|
-
if (node) {
|
|
2480
|
-
node.style.pointerEvents = "none";
|
|
2481
|
-
node.removeEventListener("pointerdown", this.onInteractNodePointerDown);
|
|
2482
|
-
}
|
|
2483
|
-
this.interactingElementId = null;
|
|
2484
|
-
window.removeEventListener("keydown", this.onInteractKeyDown);
|
|
2485
|
-
window.removeEventListener("pointerdown", this.onInteractPointerDown);
|
|
3221
|
+
this.interactMode.stopInteracting();
|
|
2486
3222
|
}
|
|
2487
|
-
onInteractNodePointerDown = (e) => {
|
|
2488
|
-
e.stopPropagation();
|
|
2489
|
-
};
|
|
2490
|
-
onInteractKeyDown = (e) => {
|
|
2491
|
-
if (e.key === "Escape") {
|
|
2492
|
-
this.stopInteracting();
|
|
2493
|
-
}
|
|
2494
|
-
};
|
|
2495
|
-
onInteractPointerDown = (e) => {
|
|
2496
|
-
if (!this.interactingElementId) return;
|
|
2497
|
-
const target = e.target;
|
|
2498
|
-
if (!target) return;
|
|
2499
|
-
const node = this.domNodes.get(this.interactingElementId);
|
|
2500
|
-
if (node && !node.contains(target)) {
|
|
2501
|
-
this.stopInteracting();
|
|
2502
|
-
}
|
|
2503
|
-
};
|
|
2504
3223
|
onDragOver = (e) => {
|
|
2505
3224
|
e.preventDefault();
|
|
2506
3225
|
};
|
|
@@ -2522,108 +3241,6 @@ var Viewport = class {
|
|
|
2522
3241
|
reader.readAsDataURL(file);
|
|
2523
3242
|
}
|
|
2524
3243
|
};
|
|
2525
|
-
syncDomNode(element, zIndex = 0) {
|
|
2526
|
-
let node = this.domNodes.get(element.id);
|
|
2527
|
-
if (!node) {
|
|
2528
|
-
node = document.createElement("div");
|
|
2529
|
-
node.dataset["elementId"] = element.id;
|
|
2530
|
-
Object.assign(node.style, {
|
|
2531
|
-
position: "absolute",
|
|
2532
|
-
pointerEvents: "auto"
|
|
2533
|
-
});
|
|
2534
|
-
this.domLayer.appendChild(node);
|
|
2535
|
-
this.domNodes.set(element.id, node);
|
|
2536
|
-
}
|
|
2537
|
-
const size = "size" in element ? element.size : null;
|
|
2538
|
-
Object.assign(node.style, {
|
|
2539
|
-
display: "block",
|
|
2540
|
-
left: `${element.position.x}px`,
|
|
2541
|
-
top: `${element.position.y}px`,
|
|
2542
|
-
width: size ? `${size.w}px` : "auto",
|
|
2543
|
-
height: size ? `${size.h}px` : "auto",
|
|
2544
|
-
zIndex: String(zIndex)
|
|
2545
|
-
});
|
|
2546
|
-
this.renderDomContent(node, element);
|
|
2547
|
-
}
|
|
2548
|
-
renderDomContent(node, element) {
|
|
2549
|
-
if (element.type === "note") {
|
|
2550
|
-
if (!node.dataset["initialized"]) {
|
|
2551
|
-
node.dataset["initialized"] = "true";
|
|
2552
|
-
Object.assign(node.style, {
|
|
2553
|
-
backgroundColor: element.backgroundColor,
|
|
2554
|
-
color: element.textColor,
|
|
2555
|
-
padding: "8px",
|
|
2556
|
-
borderRadius: "4px",
|
|
2557
|
-
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
|
|
2558
|
-
fontSize: "14px",
|
|
2559
|
-
overflow: "hidden",
|
|
2560
|
-
cursor: "default",
|
|
2561
|
-
userSelect: "none",
|
|
2562
|
-
wordWrap: "break-word"
|
|
2563
|
-
});
|
|
2564
|
-
node.textContent = element.text || "";
|
|
2565
|
-
node.addEventListener("dblclick", (e) => {
|
|
2566
|
-
e.stopPropagation();
|
|
2567
|
-
const id = node.dataset["elementId"];
|
|
2568
|
-
if (id) this.startEditingElement(id);
|
|
2569
|
-
});
|
|
2570
|
-
}
|
|
2571
|
-
if (!this.noteEditor.isEditing || this.noteEditor.editingElementId !== element.id) {
|
|
2572
|
-
if (node.textContent !== element.text) {
|
|
2573
|
-
node.textContent = element.text || "";
|
|
2574
|
-
}
|
|
2575
|
-
node.style.backgroundColor = element.backgroundColor;
|
|
2576
|
-
node.style.color = element.textColor;
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
if (element.type === "html" && !node.dataset["initialized"]) {
|
|
2580
|
-
const content = this.htmlContent.get(element.id);
|
|
2581
|
-
if (content) {
|
|
2582
|
-
node.dataset["initialized"] = "true";
|
|
2583
|
-
Object.assign(node.style, {
|
|
2584
|
-
overflow: "hidden",
|
|
2585
|
-
pointerEvents: "none"
|
|
2586
|
-
});
|
|
2587
|
-
node.appendChild(content);
|
|
2588
|
-
}
|
|
2589
|
-
}
|
|
2590
|
-
if (element.type === "text") {
|
|
2591
|
-
if (!node.dataset["initialized"]) {
|
|
2592
|
-
node.dataset["initialized"] = "true";
|
|
2593
|
-
Object.assign(node.style, {
|
|
2594
|
-
padding: "2px",
|
|
2595
|
-
fontSize: `${element.fontSize}px`,
|
|
2596
|
-
color: element.color,
|
|
2597
|
-
textAlign: element.textAlign,
|
|
2598
|
-
background: "none",
|
|
2599
|
-
border: "none",
|
|
2600
|
-
boxShadow: "none",
|
|
2601
|
-
overflow: "visible",
|
|
2602
|
-
cursor: "default",
|
|
2603
|
-
userSelect: "none",
|
|
2604
|
-
wordWrap: "break-word",
|
|
2605
|
-
whiteSpace: "pre-wrap",
|
|
2606
|
-
lineHeight: "1.4"
|
|
2607
|
-
});
|
|
2608
|
-
node.textContent = element.text || "";
|
|
2609
|
-
node.addEventListener("dblclick", (e) => {
|
|
2610
|
-
e.stopPropagation();
|
|
2611
|
-
const id = node.dataset["elementId"];
|
|
2612
|
-
if (id) this.startEditingElement(id);
|
|
2613
|
-
});
|
|
2614
|
-
}
|
|
2615
|
-
if (!this.noteEditor.isEditing || this.noteEditor.editingElementId !== element.id) {
|
|
2616
|
-
if (node.textContent !== element.text) {
|
|
2617
|
-
node.textContent = element.text || "";
|
|
2618
|
-
}
|
|
2619
|
-
Object.assign(node.style, {
|
|
2620
|
-
fontSize: `${element.fontSize}px`,
|
|
2621
|
-
color: element.color,
|
|
2622
|
-
textAlign: element.textAlign
|
|
2623
|
-
});
|
|
2624
|
-
}
|
|
2625
|
-
}
|
|
2626
|
-
}
|
|
2627
3244
|
unbindArrowsFrom(removedElement) {
|
|
2628
3245
|
const boundArrows = findBoundArrows(removedElement.id, this.store);
|
|
2629
3246
|
const bounds = getElementBounds(removedElement);
|
|
@@ -2658,35 +3275,6 @@ var Viewport = class {
|
|
|
2658
3275
|
}
|
|
2659
3276
|
}
|
|
2660
3277
|
}
|
|
2661
|
-
hideDomNode(id) {
|
|
2662
|
-
const node = this.domNodes.get(id);
|
|
2663
|
-
if (node) node.style.display = "none";
|
|
2664
|
-
}
|
|
2665
|
-
removeDomNode(id) {
|
|
2666
|
-
this.htmlContent.delete(id);
|
|
2667
|
-
const node = this.domNodes.get(id);
|
|
2668
|
-
if (node) {
|
|
2669
|
-
node.remove();
|
|
2670
|
-
this.domNodes.delete(id);
|
|
2671
|
-
}
|
|
2672
|
-
this.requestRender();
|
|
2673
|
-
}
|
|
2674
|
-
clearDomNodes() {
|
|
2675
|
-
this.domNodes.forEach((node) => node.remove());
|
|
2676
|
-
this.domNodes.clear();
|
|
2677
|
-
this.htmlContent.clear();
|
|
2678
|
-
this.requestRender();
|
|
2679
|
-
}
|
|
2680
|
-
reattachHtmlContent() {
|
|
2681
|
-
for (const el of this.store.getElementsByType("html")) {
|
|
2682
|
-
if (el.domId) {
|
|
2683
|
-
const dom = document.getElementById(el.domId);
|
|
2684
|
-
if (dom) {
|
|
2685
|
-
this.htmlContent.set(el.id, dom);
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
3278
|
createWrapper() {
|
|
2691
3279
|
const el = document.createElement("div");
|
|
2692
3280
|
Object.assign(el.style, {
|
|
@@ -2727,8 +3315,7 @@ var Viewport = class {
|
|
|
2727
3315
|
syncCanvasSize() {
|
|
2728
3316
|
const rect = this.container.getBoundingClientRect();
|
|
2729
3317
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2730
|
-
this.
|
|
2731
|
-
this.canvasEl.height = rect.height * dpr;
|
|
3318
|
+
this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
|
|
2732
3319
|
this.requestRender();
|
|
2733
3320
|
}
|
|
2734
3321
|
observeResize() {
|
|
@@ -2771,6 +3358,9 @@ var HandTool = class {
|
|
|
2771
3358
|
var MIN_POINTS_FOR_STROKE = 2;
|
|
2772
3359
|
var DEFAULT_SMOOTHING = 1.5;
|
|
2773
3360
|
var DEFAULT_PRESSURE = 0.5;
|
|
3361
|
+
var DEFAULT_MIN_POINT_DISTANCE = 3;
|
|
3362
|
+
var DEFAULT_PROGRESSIVE_THRESHOLD = 200;
|
|
3363
|
+
var PROGRESSIVE_HOT_ZONE = 30;
|
|
2774
3364
|
var PencilTool = class {
|
|
2775
3365
|
name = "pencil";
|
|
2776
3366
|
drawing = false;
|
|
@@ -2778,10 +3368,17 @@ var PencilTool = class {
|
|
|
2778
3368
|
color;
|
|
2779
3369
|
width;
|
|
2780
3370
|
smoothing;
|
|
3371
|
+
minPointDistance;
|
|
3372
|
+
progressiveThreshold;
|
|
3373
|
+
nextSimplifyAt;
|
|
3374
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
2781
3375
|
constructor(options = {}) {
|
|
2782
3376
|
this.color = options.color ?? "#000000";
|
|
2783
3377
|
this.width = options.width ?? 2;
|
|
2784
3378
|
this.smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
|
|
3379
|
+
this.minPointDistance = options.minPointDistance ?? DEFAULT_MIN_POINT_DISTANCE;
|
|
3380
|
+
this.progressiveThreshold = options.progressiveSimplifyThreshold ?? DEFAULT_PROGRESSIVE_THRESHOLD;
|
|
3381
|
+
this.nextSimplifyAt = this.progressiveThreshold;
|
|
2785
3382
|
}
|
|
2786
3383
|
onActivate(ctx) {
|
|
2787
3384
|
ctx.setCursor?.("crosshair");
|
|
@@ -2789,22 +3386,53 @@ var PencilTool = class {
|
|
|
2789
3386
|
onDeactivate(ctx) {
|
|
2790
3387
|
ctx.setCursor?.("default");
|
|
2791
3388
|
}
|
|
3389
|
+
getOptions() {
|
|
3390
|
+
return {
|
|
3391
|
+
color: this.color,
|
|
3392
|
+
width: this.width,
|
|
3393
|
+
smoothing: this.smoothing,
|
|
3394
|
+
minPointDistance: this.minPointDistance,
|
|
3395
|
+
progressiveSimplifyThreshold: this.progressiveThreshold
|
|
3396
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
onOptionsChange(listener) {
|
|
3399
|
+
this.optionListeners.add(listener);
|
|
3400
|
+
return () => this.optionListeners.delete(listener);
|
|
3401
|
+
}
|
|
2792
3402
|
setOptions(options) {
|
|
2793
3403
|
if (options.color !== void 0) this.color = options.color;
|
|
2794
3404
|
if (options.width !== void 0) this.width = options.width;
|
|
2795
3405
|
if (options.smoothing !== void 0) this.smoothing = options.smoothing;
|
|
3406
|
+
if (options.minPointDistance !== void 0) this.minPointDistance = options.minPointDistance;
|
|
3407
|
+
if (options.progressiveSimplifyThreshold !== void 0)
|
|
3408
|
+
this.progressiveThreshold = options.progressiveSimplifyThreshold;
|
|
3409
|
+
this.notifyOptionsChange();
|
|
2796
3410
|
}
|
|
2797
3411
|
onPointerDown(state, ctx) {
|
|
2798
3412
|
this.drawing = true;
|
|
2799
3413
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2800
3414
|
const pressure = state.pressure === 0 ? DEFAULT_PRESSURE : state.pressure;
|
|
2801
3415
|
this.points = [{ x: world.x, y: world.y, pressure }];
|
|
3416
|
+
this.nextSimplifyAt = this.progressiveThreshold;
|
|
2802
3417
|
}
|
|
2803
3418
|
onPointerMove(state, ctx) {
|
|
2804
3419
|
if (!this.drawing) return;
|
|
2805
3420
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2806
3421
|
const pressure = state.pressure === 0 ? DEFAULT_PRESSURE : state.pressure;
|
|
3422
|
+
const last = this.points[this.points.length - 1];
|
|
3423
|
+
if (last) {
|
|
3424
|
+
const dx = world.x - last.x;
|
|
3425
|
+
const dy = world.y - last.y;
|
|
3426
|
+
if (dx * dx + dy * dy < this.minPointDistance * this.minPointDistance) return;
|
|
3427
|
+
}
|
|
2807
3428
|
this.points.push({ x: world.x, y: world.y, pressure });
|
|
3429
|
+
if (this.points.length > this.nextSimplifyAt) {
|
|
3430
|
+
const hotZone = this.points.slice(-PROGRESSIVE_HOT_ZONE);
|
|
3431
|
+
const coldZone = this.points.slice(0, -PROGRESSIVE_HOT_ZONE);
|
|
3432
|
+
const simplified = simplifyPoints(coldZone, this.smoothing * 2);
|
|
3433
|
+
this.points = [...simplified, ...hotZone];
|
|
3434
|
+
this.nextSimplifyAt = this.points.length + this.progressiveThreshold;
|
|
3435
|
+
}
|
|
2808
3436
|
ctx.requestRender();
|
|
2809
3437
|
}
|
|
2810
3438
|
onPointerUp(_state, ctx) {
|
|
@@ -2825,6 +3453,9 @@ var PencilTool = class {
|
|
|
2825
3453
|
this.points = [];
|
|
2826
3454
|
ctx.requestRender();
|
|
2827
3455
|
}
|
|
3456
|
+
notifyOptionsChange() {
|
|
3457
|
+
for (const listener of this.optionListeners) listener();
|
|
3458
|
+
}
|
|
2828
3459
|
renderOverlay(ctx) {
|
|
2829
3460
|
if (!this.drawing || this.points.length < 2) return;
|
|
2830
3461
|
ctx.save();
|
|
@@ -2861,6 +3492,9 @@ var EraserTool = class {
|
|
|
2861
3492
|
this.radius = options.radius ?? DEFAULT_RADIUS;
|
|
2862
3493
|
this.cursor = makeEraserCursor(this.radius);
|
|
2863
3494
|
}
|
|
3495
|
+
getOptions() {
|
|
3496
|
+
return { radius: this.radius };
|
|
3497
|
+
}
|
|
2864
3498
|
onActivate(ctx) {
|
|
2865
3499
|
ctx.setCursor?.(this.cursor);
|
|
2866
3500
|
}
|
|
@@ -2880,13 +3514,20 @@ var EraserTool = class {
|
|
|
2880
3514
|
}
|
|
2881
3515
|
eraseAt(state, ctx) {
|
|
2882
3516
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2883
|
-
const
|
|
3517
|
+
const queryBounds = {
|
|
3518
|
+
x: world.x - this.radius,
|
|
3519
|
+
y: world.y - this.radius,
|
|
3520
|
+
w: this.radius * 2,
|
|
3521
|
+
h: this.radius * 2
|
|
3522
|
+
};
|
|
3523
|
+
const candidates = ctx.store.queryRect(queryBounds);
|
|
2884
3524
|
let erased = false;
|
|
2885
|
-
for (const
|
|
2886
|
-
if (
|
|
2887
|
-
if (ctx.
|
|
2888
|
-
if (
|
|
2889
|
-
|
|
3525
|
+
for (const el of candidates) {
|
|
3526
|
+
if (el.type !== "stroke") continue;
|
|
3527
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
3528
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
3529
|
+
if (this.strokeIntersects(el, world)) {
|
|
3530
|
+
ctx.store.remove(el.id);
|
|
2890
3531
|
erased = true;
|
|
2891
3532
|
}
|
|
2892
3533
|
}
|
|
@@ -3263,7 +3904,7 @@ var SelectTool = class {
|
|
|
3263
3904
|
for (const id of this._selectedIds) {
|
|
3264
3905
|
const el = ctx.store.getById(id);
|
|
3265
3906
|
if (!el || !("size" in el)) continue;
|
|
3266
|
-
const bounds =
|
|
3907
|
+
const bounds = getElementBounds(el);
|
|
3267
3908
|
if (!bounds) continue;
|
|
3268
3909
|
const corners = this.getHandlePositions(bounds);
|
|
3269
3910
|
for (const [handle, pos] of corners) {
|
|
@@ -3311,7 +3952,7 @@ var SelectTool = class {
|
|
|
3311
3952
|
this.renderBindingHighlights(canvasCtx, el, zoom);
|
|
3312
3953
|
continue;
|
|
3313
3954
|
}
|
|
3314
|
-
const bounds =
|
|
3955
|
+
const bounds = getElementBounds(el);
|
|
3315
3956
|
if (!bounds) continue;
|
|
3316
3957
|
const pad = SELECTION_PAD / zoom;
|
|
3317
3958
|
canvasCtx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
@@ -3370,12 +4011,13 @@ var SelectTool = class {
|
|
|
3370
4011
|
return { x, y, w, h };
|
|
3371
4012
|
}
|
|
3372
4013
|
findElementsInRect(marquee, ctx) {
|
|
4014
|
+
const candidates = ctx.store.queryRect(marquee);
|
|
3373
4015
|
const ids = [];
|
|
3374
|
-
for (const el of
|
|
4016
|
+
for (const el of candidates) {
|
|
3375
4017
|
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
3376
4018
|
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
3377
4019
|
if (el.type === "grid") continue;
|
|
3378
|
-
const bounds =
|
|
4020
|
+
const bounds = getElementBounds(el);
|
|
3379
4021
|
if (bounds && this.rectsOverlap(marquee, bounds)) {
|
|
3380
4022
|
ids.push(el.id);
|
|
3381
4023
|
}
|
|
@@ -3385,30 +4027,10 @@ var SelectTool = class {
|
|
|
3385
4027
|
rectsOverlap(a, b) {
|
|
3386
4028
|
return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
|
|
3387
4029
|
}
|
|
3388
|
-
getElementBounds(el) {
|
|
3389
|
-
if ("size" in el) {
|
|
3390
|
-
return { x: el.position.x, y: el.position.y, w: el.size.w, h: el.size.h };
|
|
3391
|
-
}
|
|
3392
|
-
if (el.type === "stroke" && el.points.length > 0) {
|
|
3393
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3394
|
-
for (const p of el.points) {
|
|
3395
|
-
const px = p.x + el.position.x;
|
|
3396
|
-
const py = p.y + el.position.y;
|
|
3397
|
-
if (px < minX) minX = px;
|
|
3398
|
-
if (py < minY) minY = py;
|
|
3399
|
-
if (px > maxX) maxX = px;
|
|
3400
|
-
if (py > maxY) maxY = py;
|
|
3401
|
-
}
|
|
3402
|
-
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
3403
|
-
}
|
|
3404
|
-
if (el.type === "arrow") {
|
|
3405
|
-
return getArrowBounds(el.from, el.to, el.bend);
|
|
3406
|
-
}
|
|
3407
|
-
return null;
|
|
3408
|
-
}
|
|
3409
4030
|
hitTest(world, ctx) {
|
|
3410
|
-
const
|
|
3411
|
-
|
|
4031
|
+
const r = 10;
|
|
4032
|
+
const candidates = ctx.store.queryRect({ x: world.x - r, y: world.y - r, w: r * 2, h: r * 2 }).reverse();
|
|
4033
|
+
for (const el of candidates) {
|
|
3412
4034
|
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
3413
4035
|
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
3414
4036
|
if (el.type === "grid") continue;
|
|
@@ -3449,13 +4071,25 @@ var ArrowTool = class {
|
|
|
3449
4071
|
fromBinding;
|
|
3450
4072
|
fromTarget = null;
|
|
3451
4073
|
toTarget = null;
|
|
4074
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
3452
4075
|
constructor(options = {}) {
|
|
3453
4076
|
this.color = options.color ?? "#000000";
|
|
3454
4077
|
this.width = options.width ?? 2;
|
|
3455
4078
|
}
|
|
4079
|
+
getOptions() {
|
|
4080
|
+
return { color: this.color, width: this.width };
|
|
4081
|
+
}
|
|
4082
|
+
onOptionsChange(listener) {
|
|
4083
|
+
this.optionListeners.add(listener);
|
|
4084
|
+
return () => this.optionListeners.delete(listener);
|
|
4085
|
+
}
|
|
3456
4086
|
setOptions(options) {
|
|
3457
4087
|
if (options.color !== void 0) this.color = options.color;
|
|
3458
4088
|
if (options.width !== void 0) this.width = options.width;
|
|
4089
|
+
this.notifyOptionsChange();
|
|
4090
|
+
}
|
|
4091
|
+
notifyOptionsChange() {
|
|
4092
|
+
for (const listener of this.optionListeners) listener();
|
|
3459
4093
|
}
|
|
3460
4094
|
layerFilter(ctx) {
|
|
3461
4095
|
const activeLayerId = ctx.activeLayerId;
|
|
@@ -3577,15 +4211,31 @@ var NoteTool = class {
|
|
|
3577
4211
|
backgroundColor;
|
|
3578
4212
|
textColor;
|
|
3579
4213
|
size;
|
|
4214
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
3580
4215
|
constructor(options = {}) {
|
|
3581
4216
|
this.backgroundColor = options.backgroundColor ?? "#ffeb3b";
|
|
3582
4217
|
this.textColor = options.textColor ?? "#000000";
|
|
3583
4218
|
this.size = options.size ?? { w: 200, h: 100 };
|
|
3584
4219
|
}
|
|
4220
|
+
getOptions() {
|
|
4221
|
+
return {
|
|
4222
|
+
backgroundColor: this.backgroundColor,
|
|
4223
|
+
textColor: this.textColor,
|
|
4224
|
+
size: { ...this.size }
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
onOptionsChange(listener) {
|
|
4228
|
+
this.optionListeners.add(listener);
|
|
4229
|
+
return () => this.optionListeners.delete(listener);
|
|
4230
|
+
}
|
|
3585
4231
|
setOptions(options) {
|
|
3586
4232
|
if (options.backgroundColor !== void 0) this.backgroundColor = options.backgroundColor;
|
|
3587
4233
|
if (options.textColor !== void 0) this.textColor = options.textColor;
|
|
3588
4234
|
if (options.size !== void 0) this.size = options.size;
|
|
4235
|
+
this.notifyOptionsChange();
|
|
4236
|
+
}
|
|
4237
|
+
notifyOptionsChange() {
|
|
4238
|
+
for (const listener of this.optionListeners) listener();
|
|
3589
4239
|
}
|
|
3590
4240
|
onPointerDown(_state, _ctx) {
|
|
3591
4241
|
}
|
|
@@ -3616,15 +4266,27 @@ var TextTool = class {
|
|
|
3616
4266
|
fontSize;
|
|
3617
4267
|
color;
|
|
3618
4268
|
textAlign;
|
|
4269
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
3619
4270
|
constructor(options = {}) {
|
|
3620
4271
|
this.fontSize = options.fontSize ?? 16;
|
|
3621
4272
|
this.color = options.color ?? "#1a1a1a";
|
|
3622
4273
|
this.textAlign = options.textAlign ?? "left";
|
|
3623
4274
|
}
|
|
4275
|
+
getOptions() {
|
|
4276
|
+
return { fontSize: this.fontSize, color: this.color, textAlign: this.textAlign };
|
|
4277
|
+
}
|
|
4278
|
+
onOptionsChange(listener) {
|
|
4279
|
+
this.optionListeners.add(listener);
|
|
4280
|
+
return () => this.optionListeners.delete(listener);
|
|
4281
|
+
}
|
|
3624
4282
|
setOptions(options) {
|
|
3625
4283
|
if (options.fontSize !== void 0) this.fontSize = options.fontSize;
|
|
3626
4284
|
if (options.color !== void 0) this.color = options.color;
|
|
3627
4285
|
if (options.textAlign !== void 0) this.textAlign = options.textAlign;
|
|
4286
|
+
this.notifyOptionsChange();
|
|
4287
|
+
}
|
|
4288
|
+
notifyOptionsChange() {
|
|
4289
|
+
for (const listener of this.optionListeners) listener();
|
|
3628
4290
|
}
|
|
3629
4291
|
onActivate(ctx) {
|
|
3630
4292
|
ctx.setCursor?.("text");
|
|
@@ -3696,17 +4358,31 @@ var ShapeTool = class {
|
|
|
3696
4358
|
strokeColor;
|
|
3697
4359
|
strokeWidth;
|
|
3698
4360
|
fillColor;
|
|
4361
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
3699
4362
|
constructor(options = {}) {
|
|
3700
4363
|
this.shape = options.shape ?? "rectangle";
|
|
3701
4364
|
this.strokeColor = options.strokeColor ?? "#000000";
|
|
3702
4365
|
this.strokeWidth = options.strokeWidth ?? 2;
|
|
3703
4366
|
this.fillColor = options.fillColor ?? "none";
|
|
3704
4367
|
}
|
|
4368
|
+
getOptions() {
|
|
4369
|
+
return {
|
|
4370
|
+
shape: this.shape,
|
|
4371
|
+
strokeColor: this.strokeColor,
|
|
4372
|
+
strokeWidth: this.strokeWidth,
|
|
4373
|
+
fillColor: this.fillColor
|
|
4374
|
+
};
|
|
4375
|
+
}
|
|
4376
|
+
onOptionsChange(listener) {
|
|
4377
|
+
this.optionListeners.add(listener);
|
|
4378
|
+
return () => this.optionListeners.delete(listener);
|
|
4379
|
+
}
|
|
3705
4380
|
setOptions(options) {
|
|
3706
4381
|
if (options.shape !== void 0) this.shape = options.shape;
|
|
3707
4382
|
if (options.strokeColor !== void 0) this.strokeColor = options.strokeColor;
|
|
3708
4383
|
if (options.strokeWidth !== void 0) this.strokeWidth = options.strokeWidth;
|
|
3709
4384
|
if (options.fillColor !== void 0) this.fillColor = options.fillColor;
|
|
4385
|
+
this.notifyOptionsChange();
|
|
3710
4386
|
}
|
|
3711
4387
|
onActivate(_ctx) {
|
|
3712
4388
|
if (typeof window !== "undefined") {
|
|
@@ -3793,6 +4469,9 @@ var ShapeTool = class {
|
|
|
3793
4469
|
}
|
|
3794
4470
|
return { position: { x, y }, size: { w, h } };
|
|
3795
4471
|
}
|
|
4472
|
+
notifyOptionsChange() {
|
|
4473
|
+
for (const listener of this.optionListeners) listener();
|
|
4474
|
+
}
|
|
3796
4475
|
snap(point, ctx) {
|
|
3797
4476
|
return ctx.snapToGrid && ctx.gridSize ? snapPoint(point, ctx.gridSize) : point;
|
|
3798
4477
|
}
|
|
@@ -3845,7 +4524,7 @@ var UpdateLayerCommand = class {
|
|
|
3845
4524
|
};
|
|
3846
4525
|
|
|
3847
4526
|
// src/index.ts
|
|
3848
|
-
var VERSION = "0.8.
|
|
4527
|
+
var VERSION = "0.8.7";
|
|
3849
4528
|
export {
|
|
3850
4529
|
AddElementCommand,
|
|
3851
4530
|
ArrowTool,
|
|
@@ -3867,6 +4546,7 @@ export {
|
|
|
3867
4546
|
NoteEditor,
|
|
3868
4547
|
NoteTool,
|
|
3869
4548
|
PencilTool,
|
|
4549
|
+
Quadtree,
|
|
3870
4550
|
RemoveElementCommand,
|
|
3871
4551
|
RemoveLayerCommand,
|
|
3872
4552
|
SelectTool,
|
|
@@ -3877,6 +4557,7 @@ export {
|
|
|
3877
4557
|
UpdateLayerCommand,
|
|
3878
4558
|
VERSION,
|
|
3879
4559
|
Viewport,
|
|
4560
|
+
boundsIntersect,
|
|
3880
4561
|
clearStaleBindings,
|
|
3881
4562
|
createArrow,
|
|
3882
4563
|
createGrid,
|