@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.cjs
CHANGED
|
@@ -40,6 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
NoteEditor: () => NoteEditor,
|
|
41
41
|
NoteTool: () => NoteTool,
|
|
42
42
|
PencilTool: () => PencilTool,
|
|
43
|
+
Quadtree: () => Quadtree,
|
|
43
44
|
RemoveElementCommand: () => RemoveElementCommand,
|
|
44
45
|
RemoveLayerCommand: () => RemoveLayerCommand,
|
|
45
46
|
SelectTool: () => SelectTool,
|
|
@@ -50,6 +51,7 @@ __export(index_exports, {
|
|
|
50
51
|
UpdateLayerCommand: () => UpdateLayerCommand,
|
|
51
52
|
VERSION: () => VERSION,
|
|
52
53
|
Viewport: () => Viewport,
|
|
54
|
+
boundsIntersect: () => boundsIntersect,
|
|
53
55
|
clearStaleBindings: () => clearStaleBindings,
|
|
54
56
|
createArrow: () => createArrow,
|
|
55
57
|
createGrid: () => createGrid,
|
|
@@ -105,6 +107,153 @@ var EventBus = class {
|
|
|
105
107
|
}
|
|
106
108
|
};
|
|
107
109
|
|
|
110
|
+
// src/core/quadtree.ts
|
|
111
|
+
var MAX_ITEMS = 8;
|
|
112
|
+
var MAX_DEPTH = 8;
|
|
113
|
+
function intersects(a, b) {
|
|
114
|
+
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;
|
|
115
|
+
}
|
|
116
|
+
var QuadNode = class _QuadNode {
|
|
117
|
+
constructor(bounds, depth) {
|
|
118
|
+
this.bounds = bounds;
|
|
119
|
+
this.depth = depth;
|
|
120
|
+
}
|
|
121
|
+
items = [];
|
|
122
|
+
children = null;
|
|
123
|
+
insert(entry) {
|
|
124
|
+
if (this.children) {
|
|
125
|
+
const idx = this.getChildIndex(entry.bounds);
|
|
126
|
+
if (idx !== -1) {
|
|
127
|
+
const child = this.children[idx];
|
|
128
|
+
if (child) child.insert(entry);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.items.push(entry);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
this.items.push(entry);
|
|
135
|
+
if (this.items.length > MAX_ITEMS && this.depth < MAX_DEPTH) {
|
|
136
|
+
this.split();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
remove(id) {
|
|
140
|
+
const idx = this.items.findIndex((e) => e.id === id);
|
|
141
|
+
if (idx !== -1) {
|
|
142
|
+
this.items.splice(idx, 1);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
if (this.children) {
|
|
146
|
+
for (const child of this.children) {
|
|
147
|
+
if (child.remove(id)) {
|
|
148
|
+
this.collapseIfEmpty();
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
query(rect, result) {
|
|
156
|
+
if (!intersects(this.bounds, rect)) return;
|
|
157
|
+
for (const item of this.items) {
|
|
158
|
+
if (intersects(item.bounds, rect)) {
|
|
159
|
+
result.push(item.id);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (this.children) {
|
|
163
|
+
for (const child of this.children) {
|
|
164
|
+
child.query(rect, result);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
getChildIndex(itemBounds) {
|
|
169
|
+
const midX = this.bounds.x + this.bounds.w / 2;
|
|
170
|
+
const midY = this.bounds.y + this.bounds.h / 2;
|
|
171
|
+
const left = itemBounds.x >= this.bounds.x && itemBounds.x + itemBounds.w <= midX;
|
|
172
|
+
const right = itemBounds.x >= midX && itemBounds.x + itemBounds.w <= this.bounds.x + this.bounds.w;
|
|
173
|
+
const top = itemBounds.y >= this.bounds.y && itemBounds.y + itemBounds.h <= midY;
|
|
174
|
+
const bottom = itemBounds.y >= midY && itemBounds.y + itemBounds.h <= this.bounds.y + this.bounds.h;
|
|
175
|
+
if (left && top) return 0;
|
|
176
|
+
if (right && top) return 1;
|
|
177
|
+
if (left && bottom) return 2;
|
|
178
|
+
if (right && bottom) return 3;
|
|
179
|
+
return -1;
|
|
180
|
+
}
|
|
181
|
+
split() {
|
|
182
|
+
const { x, y, w, h } = this.bounds;
|
|
183
|
+
const halfW = w / 2;
|
|
184
|
+
const halfH = h / 2;
|
|
185
|
+
const d = this.depth + 1;
|
|
186
|
+
this.children = [
|
|
187
|
+
new _QuadNode({ x, y, w: halfW, h: halfH }, d),
|
|
188
|
+
new _QuadNode({ x: x + halfW, y, w: halfW, h: halfH }, d),
|
|
189
|
+
new _QuadNode({ x, y: y + halfH, w: halfW, h: halfH }, d),
|
|
190
|
+
new _QuadNode({ x: x + halfW, y: y + halfH, w: halfW, h: halfH }, d)
|
|
191
|
+
];
|
|
192
|
+
const remaining = [];
|
|
193
|
+
for (const item of this.items) {
|
|
194
|
+
const idx = this.getChildIndex(item.bounds);
|
|
195
|
+
if (idx !== -1) {
|
|
196
|
+
const target = this.children[idx];
|
|
197
|
+
if (target) target.insert(item);
|
|
198
|
+
} else {
|
|
199
|
+
remaining.push(item);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
this.items = remaining;
|
|
203
|
+
}
|
|
204
|
+
collapseIfEmpty() {
|
|
205
|
+
if (!this.children) return;
|
|
206
|
+
let totalItems = this.items.length;
|
|
207
|
+
for (const child of this.children) {
|
|
208
|
+
if (child.children) return;
|
|
209
|
+
totalItems += child.items.length;
|
|
210
|
+
}
|
|
211
|
+
if (totalItems <= MAX_ITEMS) {
|
|
212
|
+
for (const child of this.children) {
|
|
213
|
+
this.items.push(...child.items);
|
|
214
|
+
}
|
|
215
|
+
this.children = null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
var Quadtree = class {
|
|
220
|
+
root;
|
|
221
|
+
_size = 0;
|
|
222
|
+
worldBounds;
|
|
223
|
+
constructor(worldBounds) {
|
|
224
|
+
this.worldBounds = worldBounds;
|
|
225
|
+
this.root = new QuadNode(worldBounds, 0);
|
|
226
|
+
}
|
|
227
|
+
get size() {
|
|
228
|
+
return this._size;
|
|
229
|
+
}
|
|
230
|
+
insert(id, bounds) {
|
|
231
|
+
this.root.insert({ id, bounds });
|
|
232
|
+
this._size++;
|
|
233
|
+
}
|
|
234
|
+
remove(id) {
|
|
235
|
+
if (this.root.remove(id)) {
|
|
236
|
+
this._size--;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
update(id, newBounds) {
|
|
240
|
+
this.remove(id);
|
|
241
|
+
this.insert(id, newBounds);
|
|
242
|
+
}
|
|
243
|
+
query(rect) {
|
|
244
|
+
const result = [];
|
|
245
|
+
this.root.query(rect, result);
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
queryPoint(point) {
|
|
249
|
+
return this.query({ x: point.x, y: point.y, w: 0, h: 0 });
|
|
250
|
+
}
|
|
251
|
+
clear() {
|
|
252
|
+
this.root = new QuadNode(this.worldBounds, 0);
|
|
253
|
+
this._size = 0;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
108
257
|
// src/core/state-serializer.ts
|
|
109
258
|
var CURRENT_VERSION = 2;
|
|
110
259
|
function exportState(elements, camera, layers = []) {
|
|
@@ -319,16 +468,16 @@ var Camera = class {
|
|
|
319
468
|
pan(dx, dy) {
|
|
320
469
|
this.x += dx;
|
|
321
470
|
this.y += dy;
|
|
322
|
-
this.
|
|
471
|
+
this.notifyPan();
|
|
323
472
|
}
|
|
324
473
|
moveTo(x, y) {
|
|
325
474
|
this.x = x;
|
|
326
475
|
this.y = y;
|
|
327
|
-
this.
|
|
476
|
+
this.notifyPan();
|
|
328
477
|
}
|
|
329
478
|
setZoom(level) {
|
|
330
479
|
this.z = Math.min(this.maxZoom, Math.max(this.minZoom, level));
|
|
331
|
-
this.
|
|
480
|
+
this.notifyZoom();
|
|
332
481
|
}
|
|
333
482
|
zoomAt(level, screenPoint) {
|
|
334
483
|
const before = this.screenToWorld(screenPoint);
|
|
@@ -336,7 +485,7 @@ var Camera = class {
|
|
|
336
485
|
const after = this.screenToWorld(screenPoint);
|
|
337
486
|
this.x += (after.x - before.x) * this.z;
|
|
338
487
|
this.y += (after.y - before.y) * this.z;
|
|
339
|
-
this.
|
|
488
|
+
this.notifyPanAndZoom();
|
|
340
489
|
}
|
|
341
490
|
screenToWorld(screen) {
|
|
342
491
|
return {
|
|
@@ -350,6 +499,16 @@ var Camera = class {
|
|
|
350
499
|
y: world.y * this.z + this.y
|
|
351
500
|
};
|
|
352
501
|
}
|
|
502
|
+
getVisibleRect(canvasWidth, canvasHeight) {
|
|
503
|
+
const topLeft = this.screenToWorld({ x: 0, y: 0 });
|
|
504
|
+
const bottomRight = this.screenToWorld({ x: canvasWidth, y: canvasHeight });
|
|
505
|
+
return {
|
|
506
|
+
x: topLeft.x,
|
|
507
|
+
y: topLeft.y,
|
|
508
|
+
w: bottomRight.x - topLeft.x,
|
|
509
|
+
h: bottomRight.y - topLeft.y
|
|
510
|
+
};
|
|
511
|
+
}
|
|
353
512
|
toCSSTransform() {
|
|
354
513
|
return `translate3d(${this.x}px, ${this.y}px, 0) scale(${this.z})`;
|
|
355
514
|
}
|
|
@@ -357,8 +516,14 @@ var Camera = class {
|
|
|
357
516
|
this.changeListeners.add(listener);
|
|
358
517
|
return () => this.changeListeners.delete(listener);
|
|
359
518
|
}
|
|
360
|
-
|
|
361
|
-
this.changeListeners.forEach((fn) => fn());
|
|
519
|
+
notifyPan() {
|
|
520
|
+
this.changeListeners.forEach((fn) => fn({ panned: true, zoomed: false }));
|
|
521
|
+
}
|
|
522
|
+
notifyZoom() {
|
|
523
|
+
this.changeListeners.forEach((fn) => fn({ panned: false, zoomed: true }));
|
|
524
|
+
}
|
|
525
|
+
notifyPanAndZoom() {
|
|
526
|
+
this.changeListeners.forEach((fn) => fn({ panned: true, zoomed: true }));
|
|
362
527
|
}
|
|
363
528
|
};
|
|
364
529
|
|
|
@@ -664,68 +829,6 @@ var InputHandler = class {
|
|
|
664
829
|
}
|
|
665
830
|
};
|
|
666
831
|
|
|
667
|
-
// src/elements/element-store.ts
|
|
668
|
-
var ElementStore = class {
|
|
669
|
-
elements = /* @__PURE__ */ new Map();
|
|
670
|
-
bus = new EventBus();
|
|
671
|
-
layerOrderMap = /* @__PURE__ */ new Map();
|
|
672
|
-
get count() {
|
|
673
|
-
return this.elements.size;
|
|
674
|
-
}
|
|
675
|
-
setLayerOrder(order) {
|
|
676
|
-
this.layerOrderMap = new Map(order);
|
|
677
|
-
}
|
|
678
|
-
getAll() {
|
|
679
|
-
return [...this.elements.values()].sort((a, b) => {
|
|
680
|
-
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
681
|
-
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
682
|
-
if (layerA !== layerB) return layerA - layerB;
|
|
683
|
-
return a.zIndex - b.zIndex;
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
getById(id) {
|
|
687
|
-
return this.elements.get(id);
|
|
688
|
-
}
|
|
689
|
-
getElementsByType(type) {
|
|
690
|
-
return this.getAll().filter(
|
|
691
|
-
(el) => el.type === type
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
add(element) {
|
|
695
|
-
this.elements.set(element.id, element);
|
|
696
|
-
this.bus.emit("add", element);
|
|
697
|
-
}
|
|
698
|
-
update(id, partial) {
|
|
699
|
-
const existing = this.elements.get(id);
|
|
700
|
-
if (!existing) return;
|
|
701
|
-
const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
|
|
702
|
-
this.elements.set(id, updated);
|
|
703
|
-
this.bus.emit("update", { previous: existing, current: updated });
|
|
704
|
-
}
|
|
705
|
-
remove(id) {
|
|
706
|
-
const element = this.elements.get(id);
|
|
707
|
-
if (!element) return;
|
|
708
|
-
this.elements.delete(id);
|
|
709
|
-
this.bus.emit("remove", element);
|
|
710
|
-
}
|
|
711
|
-
clear() {
|
|
712
|
-
this.elements.clear();
|
|
713
|
-
this.bus.emit("clear", null);
|
|
714
|
-
}
|
|
715
|
-
snapshot() {
|
|
716
|
-
return this.getAll().map((el) => ({ ...el }));
|
|
717
|
-
}
|
|
718
|
-
loadSnapshot(elements) {
|
|
719
|
-
this.elements.clear();
|
|
720
|
-
for (const el of elements) {
|
|
721
|
-
this.elements.set(el.id, el);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
on(event, listener) {
|
|
725
|
-
return this.bus.on(event, listener);
|
|
726
|
-
}
|
|
727
|
-
};
|
|
728
|
-
|
|
729
832
|
// src/elements/arrow-geometry.ts
|
|
730
833
|
function getArrowControlPoint(from, to, bend) {
|
|
731
834
|
const midX = (from.x + to.x) / 2;
|
|
@@ -826,6 +929,185 @@ function isNearLine(point, a, b, threshold) {
|
|
|
826
929
|
return Math.hypot(point.x - projX, point.y - projY) <= threshold;
|
|
827
930
|
}
|
|
828
931
|
|
|
932
|
+
// src/elements/element-bounds.ts
|
|
933
|
+
var strokeBoundsCache = /* @__PURE__ */ new WeakMap();
|
|
934
|
+
function getElementBounds(element) {
|
|
935
|
+
if (element.type === "grid") return null;
|
|
936
|
+
if ("size" in element) {
|
|
937
|
+
return {
|
|
938
|
+
x: element.position.x,
|
|
939
|
+
y: element.position.y,
|
|
940
|
+
w: element.size.w,
|
|
941
|
+
h: element.size.h
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
if (element.type === "stroke") {
|
|
945
|
+
if (element.points.length === 0) return null;
|
|
946
|
+
const cached = strokeBoundsCache.get(element);
|
|
947
|
+
if (cached) return cached;
|
|
948
|
+
let minX = Infinity;
|
|
949
|
+
let minY = Infinity;
|
|
950
|
+
let maxX = -Infinity;
|
|
951
|
+
let maxY = -Infinity;
|
|
952
|
+
for (const p of element.points) {
|
|
953
|
+
const px = p.x + element.position.x;
|
|
954
|
+
const py = p.y + element.position.y;
|
|
955
|
+
if (px < minX) minX = px;
|
|
956
|
+
if (py < minY) minY = py;
|
|
957
|
+
if (px > maxX) maxX = px;
|
|
958
|
+
if (py > maxY) maxY = py;
|
|
959
|
+
}
|
|
960
|
+
const bounds = { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
961
|
+
strokeBoundsCache.set(element, bounds);
|
|
962
|
+
return bounds;
|
|
963
|
+
}
|
|
964
|
+
if (element.type === "arrow") {
|
|
965
|
+
return getArrowBoundsAnalytical(element.from, element.to, element.bend);
|
|
966
|
+
}
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
function getArrowBoundsAnalytical(from, to, bend) {
|
|
970
|
+
if (bend === 0) {
|
|
971
|
+
const minX2 = Math.min(from.x, to.x);
|
|
972
|
+
const minY2 = Math.min(from.y, to.y);
|
|
973
|
+
return {
|
|
974
|
+
x: minX2,
|
|
975
|
+
y: minY2,
|
|
976
|
+
w: Math.abs(to.x - from.x),
|
|
977
|
+
h: Math.abs(to.y - from.y)
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
const cp = getArrowControlPoint(from, to, bend);
|
|
981
|
+
let minX = Math.min(from.x, to.x);
|
|
982
|
+
let maxX = Math.max(from.x, to.x);
|
|
983
|
+
let minY = Math.min(from.y, to.y);
|
|
984
|
+
let maxY = Math.max(from.y, to.y);
|
|
985
|
+
const tx = from.x - 2 * cp.x + to.x;
|
|
986
|
+
if (tx !== 0) {
|
|
987
|
+
const t = (from.x - cp.x) / tx;
|
|
988
|
+
if (t > 0 && t < 1) {
|
|
989
|
+
const mt = 1 - t;
|
|
990
|
+
const x = mt * mt * from.x + 2 * mt * t * cp.x + t * t * to.x;
|
|
991
|
+
if (x < minX) minX = x;
|
|
992
|
+
if (x > maxX) maxX = x;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
const ty = from.y - 2 * cp.y + to.y;
|
|
996
|
+
if (ty !== 0) {
|
|
997
|
+
const t = (from.y - cp.y) / ty;
|
|
998
|
+
if (t > 0 && t < 1) {
|
|
999
|
+
const mt = 1 - t;
|
|
1000
|
+
const y = mt * mt * from.y + 2 * mt * t * cp.y + t * t * to.y;
|
|
1001
|
+
if (y < minY) minY = y;
|
|
1002
|
+
if (y > maxY) maxY = y;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1006
|
+
}
|
|
1007
|
+
function boundsIntersect(a, b) {
|
|
1008
|
+
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;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/elements/element-store.ts
|
|
1012
|
+
var ElementStore = class {
|
|
1013
|
+
elements = /* @__PURE__ */ new Map();
|
|
1014
|
+
bus = new EventBus();
|
|
1015
|
+
layerOrderMap = /* @__PURE__ */ new Map();
|
|
1016
|
+
spatialIndex = new Quadtree({ x: -1e5, y: -1e5, w: 2e5, h: 2e5 });
|
|
1017
|
+
get count() {
|
|
1018
|
+
return this.elements.size;
|
|
1019
|
+
}
|
|
1020
|
+
setLayerOrder(order) {
|
|
1021
|
+
this.layerOrderMap = new Map(order);
|
|
1022
|
+
}
|
|
1023
|
+
getAll() {
|
|
1024
|
+
return [...this.elements.values()].sort((a, b) => {
|
|
1025
|
+
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
1026
|
+
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
1027
|
+
if (layerA !== layerB) return layerA - layerB;
|
|
1028
|
+
return a.zIndex - b.zIndex;
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
getById(id) {
|
|
1032
|
+
return this.elements.get(id);
|
|
1033
|
+
}
|
|
1034
|
+
getElementsByType(type) {
|
|
1035
|
+
return this.getAll().filter(
|
|
1036
|
+
(el) => el.type === type
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
add(element) {
|
|
1040
|
+
this.elements.set(element.id, element);
|
|
1041
|
+
const bounds = getElementBounds(element);
|
|
1042
|
+
if (bounds) this.spatialIndex.insert(element.id, bounds);
|
|
1043
|
+
this.bus.emit("add", element);
|
|
1044
|
+
}
|
|
1045
|
+
update(id, partial) {
|
|
1046
|
+
const existing = this.elements.get(id);
|
|
1047
|
+
if (!existing) return;
|
|
1048
|
+
const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
|
|
1049
|
+
this.elements.set(id, updated);
|
|
1050
|
+
const newBounds = getElementBounds(updated);
|
|
1051
|
+
if (newBounds) {
|
|
1052
|
+
this.spatialIndex.update(id, newBounds);
|
|
1053
|
+
}
|
|
1054
|
+
this.bus.emit("update", { previous: existing, current: updated });
|
|
1055
|
+
}
|
|
1056
|
+
remove(id) {
|
|
1057
|
+
const element = this.elements.get(id);
|
|
1058
|
+
if (!element) return;
|
|
1059
|
+
this.elements.delete(id);
|
|
1060
|
+
this.spatialIndex.remove(id);
|
|
1061
|
+
this.bus.emit("remove", element);
|
|
1062
|
+
}
|
|
1063
|
+
clear() {
|
|
1064
|
+
this.elements.clear();
|
|
1065
|
+
this.spatialIndex.clear();
|
|
1066
|
+
this.bus.emit("clear", null);
|
|
1067
|
+
}
|
|
1068
|
+
snapshot() {
|
|
1069
|
+
return this.getAll().map((el) => ({ ...el }));
|
|
1070
|
+
}
|
|
1071
|
+
loadSnapshot(elements) {
|
|
1072
|
+
this.elements.clear();
|
|
1073
|
+
this.spatialIndex.clear();
|
|
1074
|
+
for (const el of elements) {
|
|
1075
|
+
this.elements.set(el.id, el);
|
|
1076
|
+
const bounds = getElementBounds(el);
|
|
1077
|
+
if (bounds) this.spatialIndex.insert(el.id, bounds);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
queryRect(rect) {
|
|
1081
|
+
const ids = this.spatialIndex.query(rect);
|
|
1082
|
+
const elements = [];
|
|
1083
|
+
for (const id of ids) {
|
|
1084
|
+
const el = this.elements.get(id);
|
|
1085
|
+
if (el) elements.push(el);
|
|
1086
|
+
}
|
|
1087
|
+
return elements.sort((a, b) => {
|
|
1088
|
+
const layerA = this.layerOrderMap.get(a.layerId) ?? 0;
|
|
1089
|
+
const layerB = this.layerOrderMap.get(b.layerId) ?? 0;
|
|
1090
|
+
if (layerA !== layerB) return layerA - layerB;
|
|
1091
|
+
return a.zIndex - b.zIndex;
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
queryPoint(point) {
|
|
1095
|
+
return this.queryRect({ x: point.x, y: point.y, w: 0, h: 0 });
|
|
1096
|
+
}
|
|
1097
|
+
on(event, listener) {
|
|
1098
|
+
return this.bus.on(event, listener);
|
|
1099
|
+
}
|
|
1100
|
+
onChange(listener) {
|
|
1101
|
+
const unsubs = [
|
|
1102
|
+
this.bus.on("add", listener),
|
|
1103
|
+
this.bus.on("remove", listener),
|
|
1104
|
+
this.bus.on("update", listener),
|
|
1105
|
+
this.bus.on("clear", listener)
|
|
1106
|
+
];
|
|
1107
|
+
return () => unsubs.forEach((fn) => fn());
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
829
1111
|
// src/elements/arrow-binding.ts
|
|
830
1112
|
var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
|
|
831
1113
|
function isBindable(element) {
|
|
@@ -840,15 +1122,6 @@ function getElementCenter(element) {
|
|
|
840
1122
|
y: element.position.y + element.size.h / 2
|
|
841
1123
|
};
|
|
842
1124
|
}
|
|
843
|
-
function getElementBounds(element) {
|
|
844
|
-
if (!("size" in element)) return null;
|
|
845
|
-
return {
|
|
846
|
-
x: element.position.x,
|
|
847
|
-
y: element.position.y,
|
|
848
|
-
w: element.size.w,
|
|
849
|
-
h: element.size.h
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
1125
|
function getEdgeIntersection(bounds, outsidePoint) {
|
|
853
1126
|
const cx = bounds.x + bounds.w / 2;
|
|
854
1127
|
const cy = bounds.y + bounds.h / 2;
|
|
@@ -2175,6 +2448,10 @@ var LayerManager = class {
|
|
|
2175
2448
|
this.updateLayerDirect(id, { locked });
|
|
2176
2449
|
return true;
|
|
2177
2450
|
}
|
|
2451
|
+
setLayerOpacity(id, opacity) {
|
|
2452
|
+
if (!this.layers.has(id)) return;
|
|
2453
|
+
this.updateLayerDirect(id, { opacity: Math.max(0, Math.min(1, opacity)) });
|
|
2454
|
+
}
|
|
2178
2455
|
setActiveLayer(id) {
|
|
2179
2456
|
if (!this.layers.has(id)) return;
|
|
2180
2457
|
this._activeLayerId = id;
|
|
@@ -2230,36 +2507,522 @@ var LayerManager = class {
|
|
|
2230
2507
|
}
|
|
2231
2508
|
};
|
|
2232
2509
|
|
|
2233
|
-
// src/canvas/
|
|
2234
|
-
var
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
this.
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
this.
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
this.
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
this.
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
this.
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2510
|
+
// src/canvas/interact-mode.ts
|
|
2511
|
+
var InteractMode = class {
|
|
2512
|
+
interactingElementId = null;
|
|
2513
|
+
getNode;
|
|
2514
|
+
constructor(deps) {
|
|
2515
|
+
this.getNode = deps.getNode;
|
|
2516
|
+
}
|
|
2517
|
+
startInteracting(id) {
|
|
2518
|
+
this.stopInteracting();
|
|
2519
|
+
const node = this.getNode(id);
|
|
2520
|
+
if (!node) return;
|
|
2521
|
+
this.interactingElementId = id;
|
|
2522
|
+
node.style.pointerEvents = "auto";
|
|
2523
|
+
node.addEventListener("pointerdown", this.onNodePointerDown);
|
|
2524
|
+
window.addEventListener("keydown", this.onKeyDown);
|
|
2525
|
+
window.addEventListener("pointerdown", this.onPointerDown);
|
|
2526
|
+
}
|
|
2527
|
+
stopInteracting() {
|
|
2528
|
+
if (!this.interactingElementId) return;
|
|
2529
|
+
const node = this.getNode(this.interactingElementId);
|
|
2530
|
+
if (node) {
|
|
2531
|
+
node.style.pointerEvents = "none";
|
|
2532
|
+
node.removeEventListener("pointerdown", this.onNodePointerDown);
|
|
2533
|
+
}
|
|
2534
|
+
this.interactingElementId = null;
|
|
2535
|
+
window.removeEventListener("keydown", this.onKeyDown);
|
|
2536
|
+
window.removeEventListener("pointerdown", this.onPointerDown);
|
|
2537
|
+
}
|
|
2538
|
+
isInteracting() {
|
|
2539
|
+
return this.interactingElementId !== null;
|
|
2540
|
+
}
|
|
2541
|
+
destroy() {
|
|
2542
|
+
this.stopInteracting();
|
|
2543
|
+
}
|
|
2544
|
+
onNodePointerDown = (e) => {
|
|
2545
|
+
e.stopPropagation();
|
|
2546
|
+
};
|
|
2547
|
+
onKeyDown = (e) => {
|
|
2548
|
+
if (e.key === "Escape") {
|
|
2549
|
+
this.stopInteracting();
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
onPointerDown = (e) => {
|
|
2553
|
+
if (!this.interactingElementId) return;
|
|
2554
|
+
const target = e.target;
|
|
2555
|
+
if (!(target instanceof Element)) {
|
|
2556
|
+
this.stopInteracting();
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
const node = this.getNode(this.interactingElementId);
|
|
2560
|
+
if (node && !node.contains(target)) {
|
|
2561
|
+
this.stopInteracting();
|
|
2562
|
+
}
|
|
2563
|
+
};
|
|
2564
|
+
};
|
|
2565
|
+
|
|
2566
|
+
// src/canvas/dom-node-manager.ts
|
|
2567
|
+
var DomNodeManager = class {
|
|
2568
|
+
domNodes = /* @__PURE__ */ new Map();
|
|
2569
|
+
htmlContent = /* @__PURE__ */ new Map();
|
|
2570
|
+
domLayer;
|
|
2571
|
+
onEditRequest;
|
|
2572
|
+
isEditingElement;
|
|
2573
|
+
constructor(deps) {
|
|
2574
|
+
this.domLayer = deps.domLayer;
|
|
2575
|
+
this.onEditRequest = deps.onEditRequest;
|
|
2576
|
+
this.isEditingElement = deps.isEditingElement;
|
|
2577
|
+
}
|
|
2578
|
+
getNode(id) {
|
|
2579
|
+
return this.domNodes.get(id);
|
|
2580
|
+
}
|
|
2581
|
+
storeHtmlContent(elementId, dom) {
|
|
2582
|
+
this.htmlContent.set(elementId, dom);
|
|
2583
|
+
}
|
|
2584
|
+
syncDomNode(element, zIndex = 0) {
|
|
2585
|
+
let node = this.domNodes.get(element.id);
|
|
2586
|
+
if (!node) {
|
|
2587
|
+
node = document.createElement("div");
|
|
2588
|
+
node.dataset["elementId"] = element.id;
|
|
2589
|
+
Object.assign(node.style, {
|
|
2590
|
+
position: "absolute",
|
|
2591
|
+
pointerEvents: "auto"
|
|
2592
|
+
});
|
|
2593
|
+
this.domLayer.appendChild(node);
|
|
2594
|
+
this.domNodes.set(element.id, node);
|
|
2595
|
+
}
|
|
2596
|
+
const size = "size" in element ? element.size : null;
|
|
2597
|
+
Object.assign(node.style, {
|
|
2598
|
+
display: "block",
|
|
2599
|
+
left: `${element.position.x}px`,
|
|
2600
|
+
top: `${element.position.y}px`,
|
|
2601
|
+
width: size ? `${size.w}px` : "auto",
|
|
2602
|
+
height: size ? `${size.h}px` : "auto",
|
|
2603
|
+
zIndex: String(zIndex)
|
|
2604
|
+
});
|
|
2605
|
+
this.renderDomContent(node, element);
|
|
2606
|
+
}
|
|
2607
|
+
hideDomNode(id) {
|
|
2608
|
+
const node = this.domNodes.get(id);
|
|
2609
|
+
if (node) node.style.display = "none";
|
|
2610
|
+
}
|
|
2611
|
+
removeDomNode(id) {
|
|
2612
|
+
this.htmlContent.delete(id);
|
|
2613
|
+
const node = this.domNodes.get(id);
|
|
2614
|
+
if (node) {
|
|
2615
|
+
node.remove();
|
|
2616
|
+
this.domNodes.delete(id);
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
clearDomNodes() {
|
|
2620
|
+
this.domNodes.forEach((node) => node.remove());
|
|
2621
|
+
this.domNodes.clear();
|
|
2622
|
+
this.htmlContent.clear();
|
|
2623
|
+
}
|
|
2624
|
+
reattachHtmlContent(store) {
|
|
2625
|
+
for (const el of store.getElementsByType("html")) {
|
|
2626
|
+
if (el.domId) {
|
|
2627
|
+
const dom = document.getElementById(el.domId);
|
|
2628
|
+
if (dom) {
|
|
2629
|
+
this.htmlContent.set(el.id, dom);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
renderDomContent(node, element) {
|
|
2635
|
+
if (element.type === "note") {
|
|
2636
|
+
if (!node.dataset["initialized"]) {
|
|
2637
|
+
node.dataset["initialized"] = "true";
|
|
2638
|
+
Object.assign(node.style, {
|
|
2639
|
+
backgroundColor: element.backgroundColor,
|
|
2640
|
+
color: element.textColor,
|
|
2641
|
+
padding: "8px",
|
|
2642
|
+
borderRadius: "4px",
|
|
2643
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
|
|
2644
|
+
fontSize: "14px",
|
|
2645
|
+
overflow: "hidden",
|
|
2646
|
+
cursor: "default",
|
|
2647
|
+
userSelect: "none",
|
|
2648
|
+
wordWrap: "break-word"
|
|
2649
|
+
});
|
|
2650
|
+
node.textContent = element.text || "";
|
|
2651
|
+
node.addEventListener("dblclick", (e) => {
|
|
2652
|
+
e.stopPropagation();
|
|
2653
|
+
const id = node.dataset["elementId"];
|
|
2654
|
+
if (id) this.onEditRequest(id);
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
if (!this.isEditingElement(element.id)) {
|
|
2658
|
+
if (node.textContent !== element.text) {
|
|
2659
|
+
node.textContent = element.text || "";
|
|
2660
|
+
}
|
|
2661
|
+
node.style.backgroundColor = element.backgroundColor;
|
|
2662
|
+
node.style.color = element.textColor;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
if (element.type === "html" && !node.dataset["initialized"]) {
|
|
2666
|
+
const content = this.htmlContent.get(element.id);
|
|
2667
|
+
if (content) {
|
|
2668
|
+
node.dataset["initialized"] = "true";
|
|
2669
|
+
Object.assign(node.style, {
|
|
2670
|
+
overflow: "hidden",
|
|
2671
|
+
pointerEvents: "none"
|
|
2672
|
+
});
|
|
2673
|
+
node.appendChild(content);
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
if (element.type === "text") {
|
|
2677
|
+
if (!node.dataset["initialized"]) {
|
|
2678
|
+
node.dataset["initialized"] = "true";
|
|
2679
|
+
Object.assign(node.style, {
|
|
2680
|
+
padding: "2px",
|
|
2681
|
+
fontSize: `${element.fontSize}px`,
|
|
2682
|
+
color: element.color,
|
|
2683
|
+
textAlign: element.textAlign,
|
|
2684
|
+
background: "none",
|
|
2685
|
+
border: "none",
|
|
2686
|
+
boxShadow: "none",
|
|
2687
|
+
overflow: "visible",
|
|
2688
|
+
cursor: "default",
|
|
2689
|
+
userSelect: "none",
|
|
2690
|
+
wordWrap: "break-word",
|
|
2691
|
+
whiteSpace: "pre-wrap",
|
|
2692
|
+
lineHeight: "1.4"
|
|
2693
|
+
});
|
|
2694
|
+
node.textContent = element.text || "";
|
|
2695
|
+
node.addEventListener("dblclick", (e) => {
|
|
2696
|
+
e.stopPropagation();
|
|
2697
|
+
const id = node.dataset["elementId"];
|
|
2698
|
+
if (id) this.onEditRequest(id);
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
if (!this.isEditingElement(element.id)) {
|
|
2702
|
+
if (node.textContent !== element.text) {
|
|
2703
|
+
node.textContent = element.text || "";
|
|
2704
|
+
}
|
|
2705
|
+
Object.assign(node.style, {
|
|
2706
|
+
fontSize: `${element.fontSize}px`,
|
|
2707
|
+
color: element.color,
|
|
2708
|
+
textAlign: element.textAlign
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
};
|
|
2714
|
+
|
|
2715
|
+
// src/canvas/render-stats.ts
|
|
2716
|
+
var SAMPLE_SIZE = 60;
|
|
2717
|
+
var RenderStats = class {
|
|
2718
|
+
frameTimes = [];
|
|
2719
|
+
frameCount = 0;
|
|
2720
|
+
recordFrame(durationMs) {
|
|
2721
|
+
this.frameCount++;
|
|
2722
|
+
this.frameTimes.push(durationMs);
|
|
2723
|
+
if (this.frameTimes.length > SAMPLE_SIZE) {
|
|
2724
|
+
this.frameTimes.shift();
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
getSnapshot() {
|
|
2728
|
+
const times = this.frameTimes;
|
|
2729
|
+
if (times.length === 0) {
|
|
2730
|
+
return { fps: 0, avgFrameMs: 0, p95FrameMs: 0, lastFrameMs: 0, frameCount: 0 };
|
|
2731
|
+
}
|
|
2732
|
+
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
2733
|
+
const sorted = [...times].sort((a, b) => a - b);
|
|
2734
|
+
const p95Index = Math.min(Math.floor(sorted.length * 0.95), sorted.length - 1);
|
|
2735
|
+
const lastFrame = times[times.length - 1] ?? 0;
|
|
2736
|
+
return {
|
|
2737
|
+
fps: avg > 0 ? Math.round(1e3 / avg) : 0,
|
|
2738
|
+
avgFrameMs: Math.round(avg * 100) / 100,
|
|
2739
|
+
p95FrameMs: Math.round((sorted[p95Index] ?? 0) * 100) / 100,
|
|
2740
|
+
lastFrameMs: Math.round(lastFrame * 100) / 100,
|
|
2741
|
+
frameCount: this.frameCount
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
reset() {
|
|
2745
|
+
this.frameTimes = [];
|
|
2746
|
+
this.frameCount = 0;
|
|
2747
|
+
}
|
|
2748
|
+
};
|
|
2749
|
+
|
|
2750
|
+
// src/canvas/render-loop.ts
|
|
2751
|
+
var RenderLoop = class {
|
|
2752
|
+
needsRender = false;
|
|
2753
|
+
animFrameId = 0;
|
|
2754
|
+
canvasEl;
|
|
2755
|
+
camera;
|
|
2756
|
+
background;
|
|
2757
|
+
store;
|
|
2758
|
+
renderer;
|
|
2759
|
+
toolManager;
|
|
2760
|
+
layerManager;
|
|
2761
|
+
domNodeManager;
|
|
2762
|
+
layerCache;
|
|
2763
|
+
activeDrawingLayerId = null;
|
|
2764
|
+
lastZoom;
|
|
2765
|
+
lastCamX;
|
|
2766
|
+
lastCamY;
|
|
2767
|
+
stats = new RenderStats();
|
|
2768
|
+
constructor(deps) {
|
|
2769
|
+
this.canvasEl = deps.canvasEl;
|
|
2770
|
+
this.camera = deps.camera;
|
|
2771
|
+
this.background = deps.background;
|
|
2772
|
+
this.store = deps.store;
|
|
2773
|
+
this.renderer = deps.renderer;
|
|
2774
|
+
this.toolManager = deps.toolManager;
|
|
2775
|
+
this.layerManager = deps.layerManager;
|
|
2776
|
+
this.domNodeManager = deps.domNodeManager;
|
|
2777
|
+
this.layerCache = deps.layerCache;
|
|
2778
|
+
this.lastZoom = deps.camera.zoom;
|
|
2779
|
+
this.lastCamX = deps.camera.position.x;
|
|
2780
|
+
this.lastCamY = deps.camera.position.y;
|
|
2781
|
+
}
|
|
2782
|
+
requestRender() {
|
|
2783
|
+
this.needsRender = true;
|
|
2784
|
+
}
|
|
2785
|
+
flush() {
|
|
2786
|
+
if (this.needsRender) {
|
|
2787
|
+
this.render();
|
|
2788
|
+
this.needsRender = false;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
start() {
|
|
2792
|
+
const loop = () => {
|
|
2793
|
+
if (this.needsRender) {
|
|
2794
|
+
this.render();
|
|
2795
|
+
this.needsRender = false;
|
|
2796
|
+
}
|
|
2797
|
+
this.animFrameId = requestAnimationFrame(loop);
|
|
2798
|
+
};
|
|
2799
|
+
this.animFrameId = requestAnimationFrame(loop);
|
|
2800
|
+
}
|
|
2801
|
+
stop() {
|
|
2802
|
+
cancelAnimationFrame(this.animFrameId);
|
|
2803
|
+
}
|
|
2804
|
+
setCanvasSize(width, height) {
|
|
2805
|
+
this.canvasEl.width = width;
|
|
2806
|
+
this.canvasEl.height = height;
|
|
2807
|
+
this.layerCache.resize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
|
|
2808
|
+
}
|
|
2809
|
+
setActiveDrawingLayer(layerId) {
|
|
2810
|
+
this.activeDrawingLayerId = layerId;
|
|
2811
|
+
}
|
|
2812
|
+
markLayerDirty(layerId) {
|
|
2813
|
+
this.layerCache.markDirty(layerId);
|
|
2814
|
+
}
|
|
2815
|
+
markAllLayersDirty() {
|
|
2816
|
+
this.layerCache.markAllDirty();
|
|
2817
|
+
}
|
|
2818
|
+
getStats() {
|
|
2819
|
+
return this.stats.getSnapshot();
|
|
2820
|
+
}
|
|
2821
|
+
compositeLayerCache(ctx, layerId, dpr) {
|
|
2822
|
+
const cached = this.layerCache.getCanvas(layerId);
|
|
2823
|
+
ctx.save();
|
|
2824
|
+
ctx.scale(1 / this.camera.zoom, 1 / this.camera.zoom);
|
|
2825
|
+
ctx.translate(-this.camera.position.x, -this.camera.position.y);
|
|
2826
|
+
ctx.scale(1 / dpr, 1 / dpr);
|
|
2827
|
+
ctx.drawImage(cached, 0, 0);
|
|
2828
|
+
ctx.restore();
|
|
2829
|
+
}
|
|
2830
|
+
render() {
|
|
2831
|
+
const t0 = performance.now();
|
|
2832
|
+
const ctx = this.canvasEl.getContext("2d");
|
|
2833
|
+
if (!ctx) return;
|
|
2834
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2835
|
+
const cssWidth = this.canvasEl.clientWidth;
|
|
2836
|
+
const cssHeight = this.canvasEl.clientHeight;
|
|
2837
|
+
const currentZoom = this.camera.zoom;
|
|
2838
|
+
const currentCamX = this.camera.position.x;
|
|
2839
|
+
const currentCamY = this.camera.position.y;
|
|
2840
|
+
if (currentZoom !== this.lastZoom || currentCamX !== this.lastCamX || currentCamY !== this.lastCamY) {
|
|
2841
|
+
this.layerCache.markAllDirty();
|
|
2842
|
+
this.lastZoom = currentZoom;
|
|
2843
|
+
this.lastCamX = currentCamX;
|
|
2844
|
+
this.lastCamY = currentCamY;
|
|
2845
|
+
}
|
|
2846
|
+
ctx.save();
|
|
2847
|
+
ctx.scale(dpr, dpr);
|
|
2848
|
+
this.renderer.setCanvasSize(cssWidth, cssHeight);
|
|
2849
|
+
this.background.render(ctx, this.camera);
|
|
2850
|
+
ctx.save();
|
|
2851
|
+
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
2852
|
+
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
2853
|
+
const visibleRect = this.camera.getVisibleRect(cssWidth, cssHeight);
|
|
2854
|
+
const margin = Math.max(visibleRect.w, visibleRect.h) * 0.1;
|
|
2855
|
+
const cullingRect = {
|
|
2856
|
+
x: visibleRect.x - margin,
|
|
2857
|
+
y: visibleRect.y - margin,
|
|
2858
|
+
w: visibleRect.w + margin * 2,
|
|
2859
|
+
h: visibleRect.h + margin * 2
|
|
2860
|
+
};
|
|
2861
|
+
const allElements = this.store.getAll();
|
|
2862
|
+
const layerElements = /* @__PURE__ */ new Map();
|
|
2863
|
+
const gridElements = [];
|
|
2864
|
+
let domZIndex = 0;
|
|
2865
|
+
for (const element of allElements) {
|
|
2866
|
+
if (!this.layerManager.isLayerVisible(element.layerId)) {
|
|
2867
|
+
if (this.renderer.isDomElement(element)) {
|
|
2868
|
+
this.domNodeManager.hideDomNode(element.id);
|
|
2869
|
+
}
|
|
2870
|
+
continue;
|
|
2871
|
+
}
|
|
2872
|
+
if (this.renderer.isDomElement(element)) {
|
|
2873
|
+
const elBounds = getElementBounds(element);
|
|
2874
|
+
if (elBounds && !boundsIntersect(elBounds, cullingRect)) {
|
|
2875
|
+
this.domNodeManager.hideDomNode(element.id);
|
|
2876
|
+
} else {
|
|
2877
|
+
this.domNodeManager.syncDomNode(element, domZIndex++);
|
|
2878
|
+
}
|
|
2879
|
+
continue;
|
|
2880
|
+
}
|
|
2881
|
+
if (element.type === "grid") {
|
|
2882
|
+
gridElements.push(element);
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
let group = layerElements.get(element.layerId);
|
|
2886
|
+
if (!group) {
|
|
2887
|
+
group = [];
|
|
2888
|
+
layerElements.set(element.layerId, group);
|
|
2889
|
+
}
|
|
2890
|
+
group.push(element);
|
|
2891
|
+
}
|
|
2892
|
+
for (const grid of gridElements) {
|
|
2893
|
+
this.renderer.renderCanvasElement(ctx, grid);
|
|
2894
|
+
}
|
|
2895
|
+
for (const [layerId, elements] of layerElements) {
|
|
2896
|
+
const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
|
|
2897
|
+
if (!this.layerCache.isDirty(layerId)) {
|
|
2898
|
+
this.compositeLayerCache(ctx, layerId, dpr);
|
|
2899
|
+
continue;
|
|
2900
|
+
}
|
|
2901
|
+
if (isActiveDrawingLayer) {
|
|
2902
|
+
this.compositeLayerCache(ctx, layerId, dpr);
|
|
2903
|
+
continue;
|
|
2904
|
+
}
|
|
2905
|
+
const offCtx = this.layerCache.getContext(layerId);
|
|
2906
|
+
if (offCtx) {
|
|
2907
|
+
const offCanvas = this.layerCache.getCanvas(layerId);
|
|
2908
|
+
offCtx.clearRect(0, 0, offCanvas.width, offCanvas.height);
|
|
2909
|
+
offCtx.save();
|
|
2910
|
+
offCtx.scale(dpr, dpr);
|
|
2911
|
+
offCtx.translate(this.camera.position.x, this.camera.position.y);
|
|
2912
|
+
offCtx.scale(this.camera.zoom, this.camera.zoom);
|
|
2913
|
+
for (const element of elements) {
|
|
2914
|
+
const elBounds = getElementBounds(element);
|
|
2915
|
+
if (elBounds && !boundsIntersect(elBounds, cullingRect)) continue;
|
|
2916
|
+
this.renderer.renderCanvasElement(offCtx, element);
|
|
2917
|
+
}
|
|
2918
|
+
offCtx.restore();
|
|
2919
|
+
this.layerCache.markClean(layerId);
|
|
2920
|
+
this.compositeLayerCache(ctx, layerId, dpr);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
const activeTool = this.toolManager.activeTool;
|
|
2924
|
+
if (activeTool?.renderOverlay) {
|
|
2925
|
+
activeTool.renderOverlay(ctx);
|
|
2926
|
+
}
|
|
2927
|
+
ctx.restore();
|
|
2928
|
+
ctx.restore();
|
|
2929
|
+
this.stats.recordFrame(performance.now() - t0);
|
|
2930
|
+
}
|
|
2931
|
+
};
|
|
2932
|
+
|
|
2933
|
+
// src/canvas/layer-cache.ts
|
|
2934
|
+
function createOffscreenCanvas(width, height) {
|
|
2935
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
2936
|
+
return new OffscreenCanvas(width, height);
|
|
2937
|
+
}
|
|
2938
|
+
const canvas = document.createElement("canvas");
|
|
2939
|
+
canvas.width = width;
|
|
2940
|
+
canvas.height = height;
|
|
2941
|
+
return canvas;
|
|
2942
|
+
}
|
|
2943
|
+
var LayerCache = class {
|
|
2944
|
+
canvases = /* @__PURE__ */ new Map();
|
|
2945
|
+
dirtyFlags = /* @__PURE__ */ new Map();
|
|
2946
|
+
width;
|
|
2947
|
+
height;
|
|
2948
|
+
constructor(width, height) {
|
|
2949
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2950
|
+
this.width = Math.round(width * dpr);
|
|
2951
|
+
this.height = Math.round(height * dpr);
|
|
2952
|
+
}
|
|
2953
|
+
isDirty(layerId) {
|
|
2954
|
+
return this.dirtyFlags.get(layerId) !== false;
|
|
2955
|
+
}
|
|
2956
|
+
markDirty(layerId) {
|
|
2957
|
+
this.dirtyFlags.set(layerId, true);
|
|
2958
|
+
}
|
|
2959
|
+
markClean(layerId) {
|
|
2960
|
+
this.dirtyFlags.set(layerId, false);
|
|
2961
|
+
}
|
|
2962
|
+
markAllDirty() {
|
|
2963
|
+
for (const [id] of this.dirtyFlags) {
|
|
2964
|
+
this.dirtyFlags.set(id, true);
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
getCanvas(layerId) {
|
|
2968
|
+
let canvas = this.canvases.get(layerId);
|
|
2969
|
+
if (!canvas) {
|
|
2970
|
+
canvas = createOffscreenCanvas(this.width, this.height);
|
|
2971
|
+
this.canvases.set(layerId, canvas);
|
|
2972
|
+
this.dirtyFlags.set(layerId, true);
|
|
2973
|
+
}
|
|
2974
|
+
return canvas;
|
|
2975
|
+
}
|
|
2976
|
+
getContext(layerId) {
|
|
2977
|
+
const canvas = this.getCanvas(layerId);
|
|
2978
|
+
return canvas.getContext("2d");
|
|
2979
|
+
}
|
|
2980
|
+
resize(width, height) {
|
|
2981
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2982
|
+
this.width = Math.round(width * dpr);
|
|
2983
|
+
this.height = Math.round(height * dpr);
|
|
2984
|
+
for (const [id, canvas] of this.canvases) {
|
|
2985
|
+
canvas.width = this.width;
|
|
2986
|
+
canvas.height = this.height;
|
|
2987
|
+
this.dirtyFlags.set(id, true);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
clear() {
|
|
2991
|
+
this.canvases.clear();
|
|
2992
|
+
this.dirtyFlags.clear();
|
|
2993
|
+
}
|
|
2994
|
+
};
|
|
2995
|
+
|
|
2996
|
+
// src/canvas/viewport.ts
|
|
2997
|
+
var Viewport = class {
|
|
2998
|
+
constructor(container, options = {}) {
|
|
2999
|
+
this.container = container;
|
|
3000
|
+
this.camera = new Camera(options.camera);
|
|
3001
|
+
this.background = new Background(options.background);
|
|
3002
|
+
this._gridSize = options.background?.spacing ?? 24;
|
|
3003
|
+
this.store = new ElementStore();
|
|
3004
|
+
this.layerManager = new LayerManager(this.store);
|
|
3005
|
+
this.toolManager = new ToolManager();
|
|
3006
|
+
this.renderer = new ElementRenderer();
|
|
3007
|
+
this.renderer.setStore(this.store);
|
|
3008
|
+
this.renderer.setCamera(this.camera);
|
|
3009
|
+
this.renderer.setOnImageLoad(() => this.requestRender());
|
|
3010
|
+
this.noteEditor = new NoteEditor();
|
|
3011
|
+
this.noteEditor.setOnStop((id) => this.onTextEditStop(id));
|
|
3012
|
+
this.history = new HistoryStack();
|
|
3013
|
+
this.historyRecorder = new HistoryRecorder(this.store, this.history);
|
|
3014
|
+
this.wrapper = this.createWrapper();
|
|
3015
|
+
this.canvasEl = this.createCanvas();
|
|
3016
|
+
this.domLayer = this.createDomLayer();
|
|
3017
|
+
this.wrapper.appendChild(this.canvasEl);
|
|
3018
|
+
this.wrapper.appendChild(this.domLayer);
|
|
3019
|
+
this.container.appendChild(this.wrapper);
|
|
3020
|
+
this.toolContext = {
|
|
3021
|
+
camera: this.camera,
|
|
3022
|
+
store: this.store,
|
|
3023
|
+
requestRender: () => this.requestRender(),
|
|
3024
|
+
switchTool: (name) => this.toolManager.setTool(name, this.toolContext),
|
|
3025
|
+
editElement: (id) => this.startEditingElement(id),
|
|
2263
3026
|
setCursor: (cursor) => {
|
|
2264
3027
|
this.wrapper.style.cursor = cursor;
|
|
2265
3028
|
},
|
|
@@ -2275,18 +3038,56 @@ var Viewport = class {
|
|
|
2275
3038
|
historyRecorder: this.historyRecorder,
|
|
2276
3039
|
historyStack: this.history
|
|
2277
3040
|
});
|
|
3041
|
+
this.domNodeManager = new DomNodeManager({
|
|
3042
|
+
domLayer: this.domLayer,
|
|
3043
|
+
onEditRequest: (id) => this.startEditingElement(id),
|
|
3044
|
+
isEditingElement: (id) => this.noteEditor.isEditing && this.noteEditor.editingElementId === id
|
|
3045
|
+
});
|
|
3046
|
+
this.interactMode = new InteractMode({
|
|
3047
|
+
getNode: (id) => this.domNodeManager.getNode(id)
|
|
3048
|
+
});
|
|
3049
|
+
const layerCache = new LayerCache(
|
|
3050
|
+
this.canvasEl.clientWidth || 800,
|
|
3051
|
+
this.canvasEl.clientHeight || 600
|
|
3052
|
+
);
|
|
3053
|
+
this.renderLoop = new RenderLoop({
|
|
3054
|
+
canvasEl: this.canvasEl,
|
|
3055
|
+
camera: this.camera,
|
|
3056
|
+
background: this.background,
|
|
3057
|
+
store: this.store,
|
|
3058
|
+
renderer: this.renderer,
|
|
3059
|
+
toolManager: this.toolManager,
|
|
3060
|
+
layerManager: this.layerManager,
|
|
3061
|
+
domNodeManager: this.domNodeManager,
|
|
3062
|
+
layerCache
|
|
3063
|
+
});
|
|
2278
3064
|
this.unsubCamera = this.camera.onChange(() => {
|
|
2279
3065
|
this.applyCameraTransform();
|
|
2280
3066
|
this.requestRender();
|
|
2281
3067
|
});
|
|
2282
3068
|
this.unsubStore = [
|
|
2283
|
-
this.store.on("add", () =>
|
|
3069
|
+
this.store.on("add", (el) => {
|
|
3070
|
+
this.renderLoop.markLayerDirty(el.layerId);
|
|
3071
|
+
this.requestRender();
|
|
3072
|
+
}),
|
|
2284
3073
|
this.store.on("remove", (el) => {
|
|
2285
3074
|
this.unbindArrowsFrom(el);
|
|
2286
|
-
this.removeDomNode(el.id);
|
|
3075
|
+
this.domNodeManager.removeDomNode(el.id);
|
|
3076
|
+
this.renderLoop.markLayerDirty(el.layerId);
|
|
3077
|
+
this.requestRender();
|
|
3078
|
+
}),
|
|
3079
|
+
this.store.on("update", ({ previous, current }) => {
|
|
3080
|
+
this.renderLoop.markLayerDirty(current.layerId);
|
|
3081
|
+
if (previous.layerId !== current.layerId) {
|
|
3082
|
+
this.renderLoop.markLayerDirty(previous.layerId);
|
|
3083
|
+
}
|
|
3084
|
+
this.requestRender();
|
|
2287
3085
|
}),
|
|
2288
|
-
this.store.on("
|
|
2289
|
-
|
|
3086
|
+
this.store.on("clear", () => {
|
|
3087
|
+
this.domNodeManager.clearDomNodes();
|
|
3088
|
+
this.renderLoop.markAllLayersDirty();
|
|
3089
|
+
this.requestRender();
|
|
3090
|
+
})
|
|
2290
3091
|
];
|
|
2291
3092
|
this.layerManager.on("change", () => {
|
|
2292
3093
|
this.toolContext.activeLayerId = this.layerManager.activeLayerId;
|
|
@@ -2297,7 +3098,7 @@ var Viewport = class {
|
|
|
2297
3098
|
this.wrapper.addEventListener("drop", this.onDrop);
|
|
2298
3099
|
this.observeResize();
|
|
2299
3100
|
this.syncCanvasSize();
|
|
2300
|
-
this.
|
|
3101
|
+
this.renderLoop.start();
|
|
2301
3102
|
}
|
|
2302
3103
|
camera;
|
|
2303
3104
|
store;
|
|
@@ -2316,13 +3117,11 @@ var Viewport = class {
|
|
|
2316
3117
|
historyRecorder;
|
|
2317
3118
|
toolContext;
|
|
2318
3119
|
resizeObserver = null;
|
|
2319
|
-
animFrameId = 0;
|
|
2320
3120
|
_snapToGrid = false;
|
|
2321
3121
|
_gridSize;
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
interactingElementId = null;
|
|
3122
|
+
renderLoop;
|
|
3123
|
+
domNodeManager;
|
|
3124
|
+
interactMode;
|
|
2326
3125
|
get ctx() {
|
|
2327
3126
|
return this.canvasEl.getContext("2d");
|
|
2328
3127
|
}
|
|
@@ -2334,7 +3133,7 @@ var Viewport = class {
|
|
|
2334
3133
|
this.toolContext.snapToGrid = enabled;
|
|
2335
3134
|
}
|
|
2336
3135
|
requestRender() {
|
|
2337
|
-
this.
|
|
3136
|
+
this.renderLoop.requestRender();
|
|
2338
3137
|
}
|
|
2339
3138
|
exportState() {
|
|
2340
3139
|
return exportState(this.store.snapshot(), this.camera, this.layerManager.snapshot());
|
|
@@ -2348,12 +3147,12 @@ var Viewport = class {
|
|
|
2348
3147
|
loadState(state) {
|
|
2349
3148
|
this.historyRecorder.pause();
|
|
2350
3149
|
this.noteEditor.destroy(this.store);
|
|
2351
|
-
this.clearDomNodes();
|
|
3150
|
+
this.domNodeManager.clearDomNodes();
|
|
2352
3151
|
this.store.loadSnapshot(state.elements);
|
|
2353
3152
|
if (state.layers && state.layers.length > 0) {
|
|
2354
3153
|
this.layerManager.loadSnapshot(state.layers);
|
|
2355
3154
|
}
|
|
2356
|
-
this.reattachHtmlContent();
|
|
3155
|
+
this.domNodeManager.reattachHtmlContent(this.store);
|
|
2357
3156
|
this.history.clear();
|
|
2358
3157
|
this.historyRecorder.resume();
|
|
2359
3158
|
this.camera.moveTo(state.camera.position.x, state.camera.position.y);
|
|
@@ -2392,7 +3191,7 @@ var Viewport = class {
|
|
|
2392
3191
|
domId,
|
|
2393
3192
|
layerId: this.layerManager.activeLayerId
|
|
2394
3193
|
});
|
|
2395
|
-
this.
|
|
3194
|
+
this.domNodeManager.storeHtmlContent(el.id, dom);
|
|
2396
3195
|
this.historyRecorder.begin();
|
|
2397
3196
|
this.store.add(el);
|
|
2398
3197
|
this.historyRecorder.commit();
|
|
@@ -2428,8 +3227,8 @@ var Viewport = class {
|
|
|
2428
3227
|
this.requestRender();
|
|
2429
3228
|
}
|
|
2430
3229
|
destroy() {
|
|
2431
|
-
|
|
2432
|
-
this.
|
|
3230
|
+
this.renderLoop.stop();
|
|
3231
|
+
this.interactMode.destroy();
|
|
2433
3232
|
this.noteEditor.destroy(this.store);
|
|
2434
3233
|
this.historyRecorder.destroy();
|
|
2435
3234
|
this.wrapper.removeEventListener("dblclick", this.onDblClick);
|
|
@@ -2442,54 +3241,11 @@ var Viewport = class {
|
|
|
2442
3241
|
this.resizeObserver = null;
|
|
2443
3242
|
this.wrapper.remove();
|
|
2444
3243
|
}
|
|
2445
|
-
startRenderLoop() {
|
|
2446
|
-
const loop = () => {
|
|
2447
|
-
if (this.needsRender) {
|
|
2448
|
-
this.render();
|
|
2449
|
-
this.needsRender = false;
|
|
2450
|
-
}
|
|
2451
|
-
this.animFrameId = requestAnimationFrame(loop);
|
|
2452
|
-
};
|
|
2453
|
-
this.animFrameId = requestAnimationFrame(loop);
|
|
2454
|
-
}
|
|
2455
|
-
render() {
|
|
2456
|
-
const ctx = this.ctx;
|
|
2457
|
-
if (!ctx) return;
|
|
2458
|
-
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2459
|
-
ctx.save();
|
|
2460
|
-
ctx.scale(dpr, dpr);
|
|
2461
|
-
this.renderer.setCanvasSize(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
|
|
2462
|
-
this.background.render(ctx, this.camera);
|
|
2463
|
-
ctx.save();
|
|
2464
|
-
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
2465
|
-
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
2466
|
-
const allElements = this.store.getAll();
|
|
2467
|
-
let domZIndex = 0;
|
|
2468
|
-
for (const element of allElements) {
|
|
2469
|
-
if (!this.layerManager.isLayerVisible(element.layerId)) {
|
|
2470
|
-
if (this.renderer.isDomElement(element)) {
|
|
2471
|
-
this.hideDomNode(element.id);
|
|
2472
|
-
}
|
|
2473
|
-
continue;
|
|
2474
|
-
}
|
|
2475
|
-
if (this.renderer.isDomElement(element)) {
|
|
2476
|
-
this.syncDomNode(element, domZIndex++);
|
|
2477
|
-
} else {
|
|
2478
|
-
this.renderer.renderCanvasElement(ctx, element);
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
const activeTool = this.toolManager.activeTool;
|
|
2482
|
-
if (activeTool?.renderOverlay) {
|
|
2483
|
-
activeTool.renderOverlay(ctx);
|
|
2484
|
-
}
|
|
2485
|
-
ctx.restore();
|
|
2486
|
-
ctx.restore();
|
|
2487
|
-
}
|
|
2488
3244
|
startEditingElement(id) {
|
|
2489
3245
|
const element = this.store.getById(id);
|
|
2490
3246
|
if (!element || element.type !== "note" && element.type !== "text") return;
|
|
2491
|
-
this.
|
|
2492
|
-
const node = this.
|
|
3247
|
+
this.renderLoop.flush();
|
|
3248
|
+
const node = this.domNodeManager.getNode(id);
|
|
2493
3249
|
if (node) {
|
|
2494
3250
|
this.noteEditor.startEditing(node, id, this.store);
|
|
2495
3251
|
}
|
|
@@ -2503,7 +3259,7 @@ var Viewport = class {
|
|
|
2503
3259
|
this.historyRecorder.commit();
|
|
2504
3260
|
return;
|
|
2505
3261
|
}
|
|
2506
|
-
const node = this.
|
|
3262
|
+
const node = this.domNodeManager.getNode(elementId);
|
|
2507
3263
|
if (node && "size" in element) {
|
|
2508
3264
|
const measuredHeight = node.scrollHeight;
|
|
2509
3265
|
if (measuredHeight !== element.size.h) {
|
|
@@ -2531,12 +3287,12 @@ var Viewport = class {
|
|
|
2531
3287
|
const world = this.camera.screenToWorld(screen);
|
|
2532
3288
|
const hit = this.hitTestWorld(world);
|
|
2533
3289
|
if (hit?.type === "html") {
|
|
2534
|
-
this.startInteracting(hit.id);
|
|
3290
|
+
this.interactMode.startInteracting(hit.id);
|
|
2535
3291
|
}
|
|
2536
3292
|
};
|
|
2537
3293
|
hitTestWorld(world) {
|
|
2538
|
-
const
|
|
2539
|
-
for (const el of
|
|
3294
|
+
const candidates = this.store.queryPoint(world).reverse();
|
|
3295
|
+
for (const el of candidates) {
|
|
2540
3296
|
if (!("size" in el)) continue;
|
|
2541
3297
|
const { x, y } = el.position;
|
|
2542
3298
|
const { w, h } = el.size;
|
|
@@ -2546,44 +3302,9 @@ var Viewport = class {
|
|
|
2546
3302
|
}
|
|
2547
3303
|
return null;
|
|
2548
3304
|
}
|
|
2549
|
-
startInteracting(id) {
|
|
2550
|
-
this.stopInteracting();
|
|
2551
|
-
const node = this.domNodes.get(id);
|
|
2552
|
-
if (!node) return;
|
|
2553
|
-
this.interactingElementId = id;
|
|
2554
|
-
node.style.pointerEvents = "auto";
|
|
2555
|
-
node.addEventListener("pointerdown", this.onInteractNodePointerDown);
|
|
2556
|
-
window.addEventListener("keydown", this.onInteractKeyDown);
|
|
2557
|
-
window.addEventListener("pointerdown", this.onInteractPointerDown);
|
|
2558
|
-
}
|
|
2559
3305
|
stopInteracting() {
|
|
2560
|
-
|
|
2561
|
-
const node = this.domNodes.get(this.interactingElementId);
|
|
2562
|
-
if (node) {
|
|
2563
|
-
node.style.pointerEvents = "none";
|
|
2564
|
-
node.removeEventListener("pointerdown", this.onInteractNodePointerDown);
|
|
2565
|
-
}
|
|
2566
|
-
this.interactingElementId = null;
|
|
2567
|
-
window.removeEventListener("keydown", this.onInteractKeyDown);
|
|
2568
|
-
window.removeEventListener("pointerdown", this.onInteractPointerDown);
|
|
3306
|
+
this.interactMode.stopInteracting();
|
|
2569
3307
|
}
|
|
2570
|
-
onInteractNodePointerDown = (e) => {
|
|
2571
|
-
e.stopPropagation();
|
|
2572
|
-
};
|
|
2573
|
-
onInteractKeyDown = (e) => {
|
|
2574
|
-
if (e.key === "Escape") {
|
|
2575
|
-
this.stopInteracting();
|
|
2576
|
-
}
|
|
2577
|
-
};
|
|
2578
|
-
onInteractPointerDown = (e) => {
|
|
2579
|
-
if (!this.interactingElementId) return;
|
|
2580
|
-
const target = e.target;
|
|
2581
|
-
if (!target) return;
|
|
2582
|
-
const node = this.domNodes.get(this.interactingElementId);
|
|
2583
|
-
if (node && !node.contains(target)) {
|
|
2584
|
-
this.stopInteracting();
|
|
2585
|
-
}
|
|
2586
|
-
};
|
|
2587
3308
|
onDragOver = (e) => {
|
|
2588
3309
|
e.preventDefault();
|
|
2589
3310
|
};
|
|
@@ -2605,108 +3326,6 @@ var Viewport = class {
|
|
|
2605
3326
|
reader.readAsDataURL(file);
|
|
2606
3327
|
}
|
|
2607
3328
|
};
|
|
2608
|
-
syncDomNode(element, zIndex = 0) {
|
|
2609
|
-
let node = this.domNodes.get(element.id);
|
|
2610
|
-
if (!node) {
|
|
2611
|
-
node = document.createElement("div");
|
|
2612
|
-
node.dataset["elementId"] = element.id;
|
|
2613
|
-
Object.assign(node.style, {
|
|
2614
|
-
position: "absolute",
|
|
2615
|
-
pointerEvents: "auto"
|
|
2616
|
-
});
|
|
2617
|
-
this.domLayer.appendChild(node);
|
|
2618
|
-
this.domNodes.set(element.id, node);
|
|
2619
|
-
}
|
|
2620
|
-
const size = "size" in element ? element.size : null;
|
|
2621
|
-
Object.assign(node.style, {
|
|
2622
|
-
display: "block",
|
|
2623
|
-
left: `${element.position.x}px`,
|
|
2624
|
-
top: `${element.position.y}px`,
|
|
2625
|
-
width: size ? `${size.w}px` : "auto",
|
|
2626
|
-
height: size ? `${size.h}px` : "auto",
|
|
2627
|
-
zIndex: String(zIndex)
|
|
2628
|
-
});
|
|
2629
|
-
this.renderDomContent(node, element);
|
|
2630
|
-
}
|
|
2631
|
-
renderDomContent(node, element) {
|
|
2632
|
-
if (element.type === "note") {
|
|
2633
|
-
if (!node.dataset["initialized"]) {
|
|
2634
|
-
node.dataset["initialized"] = "true";
|
|
2635
|
-
Object.assign(node.style, {
|
|
2636
|
-
backgroundColor: element.backgroundColor,
|
|
2637
|
-
color: element.textColor,
|
|
2638
|
-
padding: "8px",
|
|
2639
|
-
borderRadius: "4px",
|
|
2640
|
-
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
|
|
2641
|
-
fontSize: "14px",
|
|
2642
|
-
overflow: "hidden",
|
|
2643
|
-
cursor: "default",
|
|
2644
|
-
userSelect: "none",
|
|
2645
|
-
wordWrap: "break-word"
|
|
2646
|
-
});
|
|
2647
|
-
node.textContent = element.text || "";
|
|
2648
|
-
node.addEventListener("dblclick", (e) => {
|
|
2649
|
-
e.stopPropagation();
|
|
2650
|
-
const id = node.dataset["elementId"];
|
|
2651
|
-
if (id) this.startEditingElement(id);
|
|
2652
|
-
});
|
|
2653
|
-
}
|
|
2654
|
-
if (!this.noteEditor.isEditing || this.noteEditor.editingElementId !== element.id) {
|
|
2655
|
-
if (node.textContent !== element.text) {
|
|
2656
|
-
node.textContent = element.text || "";
|
|
2657
|
-
}
|
|
2658
|
-
node.style.backgroundColor = element.backgroundColor;
|
|
2659
|
-
node.style.color = element.textColor;
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
if (element.type === "html" && !node.dataset["initialized"]) {
|
|
2663
|
-
const content = this.htmlContent.get(element.id);
|
|
2664
|
-
if (content) {
|
|
2665
|
-
node.dataset["initialized"] = "true";
|
|
2666
|
-
Object.assign(node.style, {
|
|
2667
|
-
overflow: "hidden",
|
|
2668
|
-
pointerEvents: "none"
|
|
2669
|
-
});
|
|
2670
|
-
node.appendChild(content);
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
if (element.type === "text") {
|
|
2674
|
-
if (!node.dataset["initialized"]) {
|
|
2675
|
-
node.dataset["initialized"] = "true";
|
|
2676
|
-
Object.assign(node.style, {
|
|
2677
|
-
padding: "2px",
|
|
2678
|
-
fontSize: `${element.fontSize}px`,
|
|
2679
|
-
color: element.color,
|
|
2680
|
-
textAlign: element.textAlign,
|
|
2681
|
-
background: "none",
|
|
2682
|
-
border: "none",
|
|
2683
|
-
boxShadow: "none",
|
|
2684
|
-
overflow: "visible",
|
|
2685
|
-
cursor: "default",
|
|
2686
|
-
userSelect: "none",
|
|
2687
|
-
wordWrap: "break-word",
|
|
2688
|
-
whiteSpace: "pre-wrap",
|
|
2689
|
-
lineHeight: "1.4"
|
|
2690
|
-
});
|
|
2691
|
-
node.textContent = element.text || "";
|
|
2692
|
-
node.addEventListener("dblclick", (e) => {
|
|
2693
|
-
e.stopPropagation();
|
|
2694
|
-
const id = node.dataset["elementId"];
|
|
2695
|
-
if (id) this.startEditingElement(id);
|
|
2696
|
-
});
|
|
2697
|
-
}
|
|
2698
|
-
if (!this.noteEditor.isEditing || this.noteEditor.editingElementId !== element.id) {
|
|
2699
|
-
if (node.textContent !== element.text) {
|
|
2700
|
-
node.textContent = element.text || "";
|
|
2701
|
-
}
|
|
2702
|
-
Object.assign(node.style, {
|
|
2703
|
-
fontSize: `${element.fontSize}px`,
|
|
2704
|
-
color: element.color,
|
|
2705
|
-
textAlign: element.textAlign
|
|
2706
|
-
});
|
|
2707
|
-
}
|
|
2708
|
-
}
|
|
2709
|
-
}
|
|
2710
3329
|
unbindArrowsFrom(removedElement) {
|
|
2711
3330
|
const boundArrows = findBoundArrows(removedElement.id, this.store);
|
|
2712
3331
|
const bounds = getElementBounds(removedElement);
|
|
@@ -2741,35 +3360,6 @@ var Viewport = class {
|
|
|
2741
3360
|
}
|
|
2742
3361
|
}
|
|
2743
3362
|
}
|
|
2744
|
-
hideDomNode(id) {
|
|
2745
|
-
const node = this.domNodes.get(id);
|
|
2746
|
-
if (node) node.style.display = "none";
|
|
2747
|
-
}
|
|
2748
|
-
removeDomNode(id) {
|
|
2749
|
-
this.htmlContent.delete(id);
|
|
2750
|
-
const node = this.domNodes.get(id);
|
|
2751
|
-
if (node) {
|
|
2752
|
-
node.remove();
|
|
2753
|
-
this.domNodes.delete(id);
|
|
2754
|
-
}
|
|
2755
|
-
this.requestRender();
|
|
2756
|
-
}
|
|
2757
|
-
clearDomNodes() {
|
|
2758
|
-
this.domNodes.forEach((node) => node.remove());
|
|
2759
|
-
this.domNodes.clear();
|
|
2760
|
-
this.htmlContent.clear();
|
|
2761
|
-
this.requestRender();
|
|
2762
|
-
}
|
|
2763
|
-
reattachHtmlContent() {
|
|
2764
|
-
for (const el of this.store.getElementsByType("html")) {
|
|
2765
|
-
if (el.domId) {
|
|
2766
|
-
const dom = document.getElementById(el.domId);
|
|
2767
|
-
if (dom) {
|
|
2768
|
-
this.htmlContent.set(el.id, dom);
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
3363
|
createWrapper() {
|
|
2774
3364
|
const el = document.createElement("div");
|
|
2775
3365
|
Object.assign(el.style, {
|
|
@@ -2810,8 +3400,7 @@ var Viewport = class {
|
|
|
2810
3400
|
syncCanvasSize() {
|
|
2811
3401
|
const rect = this.container.getBoundingClientRect();
|
|
2812
3402
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
2813
|
-
this.
|
|
2814
|
-
this.canvasEl.height = rect.height * dpr;
|
|
3403
|
+
this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
|
|
2815
3404
|
this.requestRender();
|
|
2816
3405
|
}
|
|
2817
3406
|
observeResize() {
|
|
@@ -2854,6 +3443,9 @@ var HandTool = class {
|
|
|
2854
3443
|
var MIN_POINTS_FOR_STROKE = 2;
|
|
2855
3444
|
var DEFAULT_SMOOTHING = 1.5;
|
|
2856
3445
|
var DEFAULT_PRESSURE = 0.5;
|
|
3446
|
+
var DEFAULT_MIN_POINT_DISTANCE = 3;
|
|
3447
|
+
var DEFAULT_PROGRESSIVE_THRESHOLD = 200;
|
|
3448
|
+
var PROGRESSIVE_HOT_ZONE = 30;
|
|
2857
3449
|
var PencilTool = class {
|
|
2858
3450
|
name = "pencil";
|
|
2859
3451
|
drawing = false;
|
|
@@ -2861,10 +3453,17 @@ var PencilTool = class {
|
|
|
2861
3453
|
color;
|
|
2862
3454
|
width;
|
|
2863
3455
|
smoothing;
|
|
3456
|
+
minPointDistance;
|
|
3457
|
+
progressiveThreshold;
|
|
3458
|
+
nextSimplifyAt;
|
|
3459
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
2864
3460
|
constructor(options = {}) {
|
|
2865
3461
|
this.color = options.color ?? "#000000";
|
|
2866
3462
|
this.width = options.width ?? 2;
|
|
2867
3463
|
this.smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
|
|
3464
|
+
this.minPointDistance = options.minPointDistance ?? DEFAULT_MIN_POINT_DISTANCE;
|
|
3465
|
+
this.progressiveThreshold = options.progressiveSimplifyThreshold ?? DEFAULT_PROGRESSIVE_THRESHOLD;
|
|
3466
|
+
this.nextSimplifyAt = this.progressiveThreshold;
|
|
2868
3467
|
}
|
|
2869
3468
|
onActivate(ctx) {
|
|
2870
3469
|
ctx.setCursor?.("crosshair");
|
|
@@ -2872,22 +3471,53 @@ var PencilTool = class {
|
|
|
2872
3471
|
onDeactivate(ctx) {
|
|
2873
3472
|
ctx.setCursor?.("default");
|
|
2874
3473
|
}
|
|
3474
|
+
getOptions() {
|
|
3475
|
+
return {
|
|
3476
|
+
color: this.color,
|
|
3477
|
+
width: this.width,
|
|
3478
|
+
smoothing: this.smoothing,
|
|
3479
|
+
minPointDistance: this.minPointDistance,
|
|
3480
|
+
progressiveSimplifyThreshold: this.progressiveThreshold
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
onOptionsChange(listener) {
|
|
3484
|
+
this.optionListeners.add(listener);
|
|
3485
|
+
return () => this.optionListeners.delete(listener);
|
|
3486
|
+
}
|
|
2875
3487
|
setOptions(options) {
|
|
2876
3488
|
if (options.color !== void 0) this.color = options.color;
|
|
2877
3489
|
if (options.width !== void 0) this.width = options.width;
|
|
2878
3490
|
if (options.smoothing !== void 0) this.smoothing = options.smoothing;
|
|
3491
|
+
if (options.minPointDistance !== void 0) this.minPointDistance = options.minPointDistance;
|
|
3492
|
+
if (options.progressiveSimplifyThreshold !== void 0)
|
|
3493
|
+
this.progressiveThreshold = options.progressiveSimplifyThreshold;
|
|
3494
|
+
this.notifyOptionsChange();
|
|
2879
3495
|
}
|
|
2880
3496
|
onPointerDown(state, ctx) {
|
|
2881
3497
|
this.drawing = true;
|
|
2882
3498
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2883
3499
|
const pressure = state.pressure === 0 ? DEFAULT_PRESSURE : state.pressure;
|
|
2884
3500
|
this.points = [{ x: world.x, y: world.y, pressure }];
|
|
3501
|
+
this.nextSimplifyAt = this.progressiveThreshold;
|
|
2885
3502
|
}
|
|
2886
3503
|
onPointerMove(state, ctx) {
|
|
2887
3504
|
if (!this.drawing) return;
|
|
2888
3505
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2889
3506
|
const pressure = state.pressure === 0 ? DEFAULT_PRESSURE : state.pressure;
|
|
3507
|
+
const last = this.points[this.points.length - 1];
|
|
3508
|
+
if (last) {
|
|
3509
|
+
const dx = world.x - last.x;
|
|
3510
|
+
const dy = world.y - last.y;
|
|
3511
|
+
if (dx * dx + dy * dy < this.minPointDistance * this.minPointDistance) return;
|
|
3512
|
+
}
|
|
2890
3513
|
this.points.push({ x: world.x, y: world.y, pressure });
|
|
3514
|
+
if (this.points.length > this.nextSimplifyAt) {
|
|
3515
|
+
const hotZone = this.points.slice(-PROGRESSIVE_HOT_ZONE);
|
|
3516
|
+
const coldZone = this.points.slice(0, -PROGRESSIVE_HOT_ZONE);
|
|
3517
|
+
const simplified = simplifyPoints(coldZone, this.smoothing * 2);
|
|
3518
|
+
this.points = [...simplified, ...hotZone];
|
|
3519
|
+
this.nextSimplifyAt = this.points.length + this.progressiveThreshold;
|
|
3520
|
+
}
|
|
2891
3521
|
ctx.requestRender();
|
|
2892
3522
|
}
|
|
2893
3523
|
onPointerUp(_state, ctx) {
|
|
@@ -2908,6 +3538,9 @@ var PencilTool = class {
|
|
|
2908
3538
|
this.points = [];
|
|
2909
3539
|
ctx.requestRender();
|
|
2910
3540
|
}
|
|
3541
|
+
notifyOptionsChange() {
|
|
3542
|
+
for (const listener of this.optionListeners) listener();
|
|
3543
|
+
}
|
|
2911
3544
|
renderOverlay(ctx) {
|
|
2912
3545
|
if (!this.drawing || this.points.length < 2) return;
|
|
2913
3546
|
ctx.save();
|
|
@@ -2944,6 +3577,9 @@ var EraserTool = class {
|
|
|
2944
3577
|
this.radius = options.radius ?? DEFAULT_RADIUS;
|
|
2945
3578
|
this.cursor = makeEraserCursor(this.radius);
|
|
2946
3579
|
}
|
|
3580
|
+
getOptions() {
|
|
3581
|
+
return { radius: this.radius };
|
|
3582
|
+
}
|
|
2947
3583
|
onActivate(ctx) {
|
|
2948
3584
|
ctx.setCursor?.(this.cursor);
|
|
2949
3585
|
}
|
|
@@ -2963,13 +3599,20 @@ var EraserTool = class {
|
|
|
2963
3599
|
}
|
|
2964
3600
|
eraseAt(state, ctx) {
|
|
2965
3601
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
2966
|
-
const
|
|
3602
|
+
const queryBounds = {
|
|
3603
|
+
x: world.x - this.radius,
|
|
3604
|
+
y: world.y - this.radius,
|
|
3605
|
+
w: this.radius * 2,
|
|
3606
|
+
h: this.radius * 2
|
|
3607
|
+
};
|
|
3608
|
+
const candidates = ctx.store.queryRect(queryBounds);
|
|
2967
3609
|
let erased = false;
|
|
2968
|
-
for (const
|
|
2969
|
-
if (
|
|
2970
|
-
if (ctx.
|
|
2971
|
-
if (
|
|
2972
|
-
|
|
3610
|
+
for (const el of candidates) {
|
|
3611
|
+
if (el.type !== "stroke") continue;
|
|
3612
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
3613
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
3614
|
+
if (this.strokeIntersects(el, world)) {
|
|
3615
|
+
ctx.store.remove(el.id);
|
|
2973
3616
|
erased = true;
|
|
2974
3617
|
}
|
|
2975
3618
|
}
|
|
@@ -3346,7 +3989,7 @@ var SelectTool = class {
|
|
|
3346
3989
|
for (const id of this._selectedIds) {
|
|
3347
3990
|
const el = ctx.store.getById(id);
|
|
3348
3991
|
if (!el || !("size" in el)) continue;
|
|
3349
|
-
const bounds =
|
|
3992
|
+
const bounds = getElementBounds(el);
|
|
3350
3993
|
if (!bounds) continue;
|
|
3351
3994
|
const corners = this.getHandlePositions(bounds);
|
|
3352
3995
|
for (const [handle, pos] of corners) {
|
|
@@ -3394,7 +4037,7 @@ var SelectTool = class {
|
|
|
3394
4037
|
this.renderBindingHighlights(canvasCtx, el, zoom);
|
|
3395
4038
|
continue;
|
|
3396
4039
|
}
|
|
3397
|
-
const bounds =
|
|
4040
|
+
const bounds = getElementBounds(el);
|
|
3398
4041
|
if (!bounds) continue;
|
|
3399
4042
|
const pad = SELECTION_PAD / zoom;
|
|
3400
4043
|
canvasCtx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
@@ -3453,12 +4096,13 @@ var SelectTool = class {
|
|
|
3453
4096
|
return { x, y, w, h };
|
|
3454
4097
|
}
|
|
3455
4098
|
findElementsInRect(marquee, ctx) {
|
|
4099
|
+
const candidates = ctx.store.queryRect(marquee);
|
|
3456
4100
|
const ids = [];
|
|
3457
|
-
for (const el of
|
|
4101
|
+
for (const el of candidates) {
|
|
3458
4102
|
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
3459
4103
|
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
3460
4104
|
if (el.type === "grid") continue;
|
|
3461
|
-
const bounds =
|
|
4105
|
+
const bounds = getElementBounds(el);
|
|
3462
4106
|
if (bounds && this.rectsOverlap(marquee, bounds)) {
|
|
3463
4107
|
ids.push(el.id);
|
|
3464
4108
|
}
|
|
@@ -3468,30 +4112,10 @@ var SelectTool = class {
|
|
|
3468
4112
|
rectsOverlap(a, b) {
|
|
3469
4113
|
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;
|
|
3470
4114
|
}
|
|
3471
|
-
getElementBounds(el) {
|
|
3472
|
-
if ("size" in el) {
|
|
3473
|
-
return { x: el.position.x, y: el.position.y, w: el.size.w, h: el.size.h };
|
|
3474
|
-
}
|
|
3475
|
-
if (el.type === "stroke" && el.points.length > 0) {
|
|
3476
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3477
|
-
for (const p of el.points) {
|
|
3478
|
-
const px = p.x + el.position.x;
|
|
3479
|
-
const py = p.y + el.position.y;
|
|
3480
|
-
if (px < minX) minX = px;
|
|
3481
|
-
if (py < minY) minY = py;
|
|
3482
|
-
if (px > maxX) maxX = px;
|
|
3483
|
-
if (py > maxY) maxY = py;
|
|
3484
|
-
}
|
|
3485
|
-
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
3486
|
-
}
|
|
3487
|
-
if (el.type === "arrow") {
|
|
3488
|
-
return getArrowBounds(el.from, el.to, el.bend);
|
|
3489
|
-
}
|
|
3490
|
-
return null;
|
|
3491
|
-
}
|
|
3492
4115
|
hitTest(world, ctx) {
|
|
3493
|
-
const
|
|
3494
|
-
|
|
4116
|
+
const r = 10;
|
|
4117
|
+
const candidates = ctx.store.queryRect({ x: world.x - r, y: world.y - r, w: r * 2, h: r * 2 }).reverse();
|
|
4118
|
+
for (const el of candidates) {
|
|
3495
4119
|
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
3496
4120
|
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
3497
4121
|
if (el.type === "grid") continue;
|
|
@@ -3532,13 +4156,25 @@ var ArrowTool = class {
|
|
|
3532
4156
|
fromBinding;
|
|
3533
4157
|
fromTarget = null;
|
|
3534
4158
|
toTarget = null;
|
|
4159
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
3535
4160
|
constructor(options = {}) {
|
|
3536
4161
|
this.color = options.color ?? "#000000";
|
|
3537
4162
|
this.width = options.width ?? 2;
|
|
3538
4163
|
}
|
|
4164
|
+
getOptions() {
|
|
4165
|
+
return { color: this.color, width: this.width };
|
|
4166
|
+
}
|
|
4167
|
+
onOptionsChange(listener) {
|
|
4168
|
+
this.optionListeners.add(listener);
|
|
4169
|
+
return () => this.optionListeners.delete(listener);
|
|
4170
|
+
}
|
|
3539
4171
|
setOptions(options) {
|
|
3540
4172
|
if (options.color !== void 0) this.color = options.color;
|
|
3541
4173
|
if (options.width !== void 0) this.width = options.width;
|
|
4174
|
+
this.notifyOptionsChange();
|
|
4175
|
+
}
|
|
4176
|
+
notifyOptionsChange() {
|
|
4177
|
+
for (const listener of this.optionListeners) listener();
|
|
3542
4178
|
}
|
|
3543
4179
|
layerFilter(ctx) {
|
|
3544
4180
|
const activeLayerId = ctx.activeLayerId;
|
|
@@ -3660,15 +4296,31 @@ var NoteTool = class {
|
|
|
3660
4296
|
backgroundColor;
|
|
3661
4297
|
textColor;
|
|
3662
4298
|
size;
|
|
4299
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
3663
4300
|
constructor(options = {}) {
|
|
3664
4301
|
this.backgroundColor = options.backgroundColor ?? "#ffeb3b";
|
|
3665
4302
|
this.textColor = options.textColor ?? "#000000";
|
|
3666
4303
|
this.size = options.size ?? { w: 200, h: 100 };
|
|
3667
4304
|
}
|
|
4305
|
+
getOptions() {
|
|
4306
|
+
return {
|
|
4307
|
+
backgroundColor: this.backgroundColor,
|
|
4308
|
+
textColor: this.textColor,
|
|
4309
|
+
size: { ...this.size }
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4312
|
+
onOptionsChange(listener) {
|
|
4313
|
+
this.optionListeners.add(listener);
|
|
4314
|
+
return () => this.optionListeners.delete(listener);
|
|
4315
|
+
}
|
|
3668
4316
|
setOptions(options) {
|
|
3669
4317
|
if (options.backgroundColor !== void 0) this.backgroundColor = options.backgroundColor;
|
|
3670
4318
|
if (options.textColor !== void 0) this.textColor = options.textColor;
|
|
3671
4319
|
if (options.size !== void 0) this.size = options.size;
|
|
4320
|
+
this.notifyOptionsChange();
|
|
4321
|
+
}
|
|
4322
|
+
notifyOptionsChange() {
|
|
4323
|
+
for (const listener of this.optionListeners) listener();
|
|
3672
4324
|
}
|
|
3673
4325
|
onPointerDown(_state, _ctx) {
|
|
3674
4326
|
}
|
|
@@ -3699,15 +4351,27 @@ var TextTool = class {
|
|
|
3699
4351
|
fontSize;
|
|
3700
4352
|
color;
|
|
3701
4353
|
textAlign;
|
|
4354
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
3702
4355
|
constructor(options = {}) {
|
|
3703
4356
|
this.fontSize = options.fontSize ?? 16;
|
|
3704
4357
|
this.color = options.color ?? "#1a1a1a";
|
|
3705
4358
|
this.textAlign = options.textAlign ?? "left";
|
|
3706
4359
|
}
|
|
4360
|
+
getOptions() {
|
|
4361
|
+
return { fontSize: this.fontSize, color: this.color, textAlign: this.textAlign };
|
|
4362
|
+
}
|
|
4363
|
+
onOptionsChange(listener) {
|
|
4364
|
+
this.optionListeners.add(listener);
|
|
4365
|
+
return () => this.optionListeners.delete(listener);
|
|
4366
|
+
}
|
|
3707
4367
|
setOptions(options) {
|
|
3708
4368
|
if (options.fontSize !== void 0) this.fontSize = options.fontSize;
|
|
3709
4369
|
if (options.color !== void 0) this.color = options.color;
|
|
3710
4370
|
if (options.textAlign !== void 0) this.textAlign = options.textAlign;
|
|
4371
|
+
this.notifyOptionsChange();
|
|
4372
|
+
}
|
|
4373
|
+
notifyOptionsChange() {
|
|
4374
|
+
for (const listener of this.optionListeners) listener();
|
|
3711
4375
|
}
|
|
3712
4376
|
onActivate(ctx) {
|
|
3713
4377
|
ctx.setCursor?.("text");
|
|
@@ -3779,17 +4443,31 @@ var ShapeTool = class {
|
|
|
3779
4443
|
strokeColor;
|
|
3780
4444
|
strokeWidth;
|
|
3781
4445
|
fillColor;
|
|
4446
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
3782
4447
|
constructor(options = {}) {
|
|
3783
4448
|
this.shape = options.shape ?? "rectangle";
|
|
3784
4449
|
this.strokeColor = options.strokeColor ?? "#000000";
|
|
3785
4450
|
this.strokeWidth = options.strokeWidth ?? 2;
|
|
3786
4451
|
this.fillColor = options.fillColor ?? "none";
|
|
3787
4452
|
}
|
|
4453
|
+
getOptions() {
|
|
4454
|
+
return {
|
|
4455
|
+
shape: this.shape,
|
|
4456
|
+
strokeColor: this.strokeColor,
|
|
4457
|
+
strokeWidth: this.strokeWidth,
|
|
4458
|
+
fillColor: this.fillColor
|
|
4459
|
+
};
|
|
4460
|
+
}
|
|
4461
|
+
onOptionsChange(listener) {
|
|
4462
|
+
this.optionListeners.add(listener);
|
|
4463
|
+
return () => this.optionListeners.delete(listener);
|
|
4464
|
+
}
|
|
3788
4465
|
setOptions(options) {
|
|
3789
4466
|
if (options.shape !== void 0) this.shape = options.shape;
|
|
3790
4467
|
if (options.strokeColor !== void 0) this.strokeColor = options.strokeColor;
|
|
3791
4468
|
if (options.strokeWidth !== void 0) this.strokeWidth = options.strokeWidth;
|
|
3792
4469
|
if (options.fillColor !== void 0) this.fillColor = options.fillColor;
|
|
4470
|
+
this.notifyOptionsChange();
|
|
3793
4471
|
}
|
|
3794
4472
|
onActivate(_ctx) {
|
|
3795
4473
|
if (typeof window !== "undefined") {
|
|
@@ -3876,6 +4554,9 @@ var ShapeTool = class {
|
|
|
3876
4554
|
}
|
|
3877
4555
|
return { position: { x, y }, size: { w, h } };
|
|
3878
4556
|
}
|
|
4557
|
+
notifyOptionsChange() {
|
|
4558
|
+
for (const listener of this.optionListeners) listener();
|
|
4559
|
+
}
|
|
3879
4560
|
snap(point, ctx) {
|
|
3880
4561
|
return ctx.snapToGrid && ctx.gridSize ? snapPoint(point, ctx.gridSize) : point;
|
|
3881
4562
|
}
|
|
@@ -3928,7 +4609,7 @@ var UpdateLayerCommand = class {
|
|
|
3928
4609
|
};
|
|
3929
4610
|
|
|
3930
4611
|
// src/index.ts
|
|
3931
|
-
var VERSION = "0.8.
|
|
4612
|
+
var VERSION = "0.8.7";
|
|
3932
4613
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3933
4614
|
0 && (module.exports = {
|
|
3934
4615
|
AddElementCommand,
|
|
@@ -3951,6 +4632,7 @@ var VERSION = "0.8.5";
|
|
|
3951
4632
|
NoteEditor,
|
|
3952
4633
|
NoteTool,
|
|
3953
4634
|
PencilTool,
|
|
4635
|
+
Quadtree,
|
|
3954
4636
|
RemoveElementCommand,
|
|
3955
4637
|
RemoveLayerCommand,
|
|
3956
4638
|
SelectTool,
|
|
@@ -3961,6 +4643,7 @@ var VERSION = "0.8.5";
|
|
|
3961
4643
|
UpdateLayerCommand,
|
|
3962
4644
|
VERSION,
|
|
3963
4645
|
Viewport,
|
|
4646
|
+
boundsIntersect,
|
|
3964
4647
|
clearStaleBindings,
|
|
3965
4648
|
createArrow,
|
|
3966
4649
|
createGrid,
|