@fieldnotes/core 0.38.0 → 0.38.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +675 -675
- package/dist/index.cjs +857 -760
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -26
- package/dist/index.d.ts +5 -26
- package/dist/index.js +857 -760
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4055,17 +4055,43 @@ var ContextMenu = class {
|
|
|
4055
4055
|
}
|
|
4056
4056
|
};
|
|
4057
4057
|
|
|
4058
|
-
// src/
|
|
4059
|
-
function
|
|
4060
|
-
const
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4058
|
+
// src/canvas/viewport-dom.ts
|
|
4059
|
+
function createWrapper() {
|
|
4060
|
+
const el = document.createElement("div");
|
|
4061
|
+
Object.assign(el.style, {
|
|
4062
|
+
position: "relative",
|
|
4063
|
+
width: "100%",
|
|
4064
|
+
height: "100%",
|
|
4065
|
+
overflow: "hidden",
|
|
4066
|
+
overscrollBehavior: "none",
|
|
4067
|
+
userSelect: "none",
|
|
4068
|
+
webkitUserSelect: "none"
|
|
4069
|
+
});
|
|
4070
|
+
return el;
|
|
4071
|
+
}
|
|
4072
|
+
function createCanvas() {
|
|
4073
|
+
const el = document.createElement("canvas");
|
|
4074
|
+
Object.assign(el.style, {
|
|
4075
|
+
position: "absolute",
|
|
4076
|
+
top: "0",
|
|
4077
|
+
left: "0",
|
|
4078
|
+
width: "100%",
|
|
4079
|
+
height: "100%"
|
|
4080
|
+
});
|
|
4081
|
+
return el;
|
|
4082
|
+
}
|
|
4083
|
+
function createDomLayer() {
|
|
4084
|
+
const el = document.createElement("div");
|
|
4085
|
+
Object.assign(el.style, {
|
|
4086
|
+
position: "absolute",
|
|
4087
|
+
top: "0",
|
|
4088
|
+
left: "0",
|
|
4089
|
+
width: "100%",
|
|
4090
|
+
height: "100%",
|
|
4091
|
+
pointerEvents: "none",
|
|
4092
|
+
transformOrigin: "0 0"
|
|
4093
|
+
});
|
|
4094
|
+
return el;
|
|
4069
4095
|
}
|
|
4070
4096
|
|
|
4071
4097
|
// src/elements/arrow-label-editor.ts
|
|
@@ -5689,6 +5715,19 @@ var MarginViewport = class {
|
|
|
5689
5715
|
}
|
|
5690
5716
|
};
|
|
5691
5717
|
|
|
5718
|
+
// src/elements/translate.ts
|
|
5719
|
+
function translateElementPatch(el, dx, dy) {
|
|
5720
|
+
const position = { x: el.position.x + dx, y: el.position.y + dy };
|
|
5721
|
+
if (el.type === "arrow") {
|
|
5722
|
+
return {
|
|
5723
|
+
position,
|
|
5724
|
+
from: { x: el.from.x + dx, y: el.from.y + dy },
|
|
5725
|
+
to: { x: el.to.x + dx, y: el.to.y + dy }
|
|
5726
|
+
};
|
|
5727
|
+
}
|
|
5728
|
+
return { position };
|
|
5729
|
+
}
|
|
5730
|
+
|
|
5692
5731
|
// src/elements/element-style.ts
|
|
5693
5732
|
function styleToPatch(element, style) {
|
|
5694
5733
|
const { color, fillColor, strokeWidth, opacity, fontSize } = style;
|
|
@@ -5776,7 +5815,7 @@ function getElementStyle(element) {
|
|
|
5776
5815
|
}
|
|
5777
5816
|
}
|
|
5778
5817
|
|
|
5779
|
-
// src/canvas/
|
|
5818
|
+
// src/canvas/selection-ops.ts
|
|
5780
5819
|
function unionBounds(list) {
|
|
5781
5820
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
5782
5821
|
for (const b of list) {
|
|
@@ -5787,16 +5826,244 @@ function unionBounds(list) {
|
|
|
5787
5826
|
}
|
|
5788
5827
|
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
5789
5828
|
}
|
|
5790
|
-
var EMPTY_IDS = [];
|
|
5791
|
-
var ARROW_HIT_THRESHOLD = 10;
|
|
5792
|
-
function noop() {
|
|
5793
|
-
}
|
|
5794
5829
|
function sharedValue(values) {
|
|
5795
5830
|
const present = values.filter((v) => v !== void 0);
|
|
5796
5831
|
if (present.length === 0) return void 0;
|
|
5797
5832
|
const first = present[0];
|
|
5798
5833
|
return present.every((v) => v === first) ? first : void 0;
|
|
5799
5834
|
}
|
|
5835
|
+
var SelectionOps = class {
|
|
5836
|
+
constructor(deps) {
|
|
5837
|
+
this.deps = deps;
|
|
5838
|
+
}
|
|
5839
|
+
getStyle() {
|
|
5840
|
+
const ids = this.deps.getSelectedIds();
|
|
5841
|
+
if (ids.length === 0) return null;
|
|
5842
|
+
const styles = [];
|
|
5843
|
+
for (const id of ids) {
|
|
5844
|
+
const el = this.deps.store.getById(id);
|
|
5845
|
+
if (el) styles.push(getElementStyle(el));
|
|
5846
|
+
}
|
|
5847
|
+
if (styles.length === 0) return null;
|
|
5848
|
+
const result = {};
|
|
5849
|
+
const color = sharedValue(styles.map((s) => s.color));
|
|
5850
|
+
if (color !== void 0) result.color = color;
|
|
5851
|
+
const fillColor = sharedValue(styles.map((s) => s.fillColor));
|
|
5852
|
+
if (fillColor !== void 0) result.fillColor = fillColor;
|
|
5853
|
+
const strokeWidth = sharedValue(styles.map((s) => s.strokeWidth));
|
|
5854
|
+
if (strokeWidth !== void 0) result.strokeWidth = strokeWidth;
|
|
5855
|
+
const opacity = sharedValue(styles.map((s) => s.opacity));
|
|
5856
|
+
if (opacity !== void 0) result.opacity = opacity;
|
|
5857
|
+
const fontSize = sharedValue(styles.map((s) => s.fontSize));
|
|
5858
|
+
if (fontSize !== void 0) result.fontSize = fontSize;
|
|
5859
|
+
return result;
|
|
5860
|
+
}
|
|
5861
|
+
applyStyle(style) {
|
|
5862
|
+
const ids = this.deps.getSelectedIds();
|
|
5863
|
+
if (ids.length === 0) return;
|
|
5864
|
+
this.deps.recorder.begin();
|
|
5865
|
+
for (const id of ids) {
|
|
5866
|
+
const el = this.deps.store.getById(id);
|
|
5867
|
+
if (!el) continue;
|
|
5868
|
+
const patch = styleToPatch(el, style);
|
|
5869
|
+
if (Object.keys(patch).length > 0) {
|
|
5870
|
+
this.deps.store.update(id, patch);
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
this.deps.recorder.commit();
|
|
5874
|
+
}
|
|
5875
|
+
group() {
|
|
5876
|
+
const ids = this.deps.getSelectedIds();
|
|
5877
|
+
if (ids.length < 2) return;
|
|
5878
|
+
const groupId = createId("group");
|
|
5879
|
+
this.deps.recorder.begin();
|
|
5880
|
+
for (const id of ids) {
|
|
5881
|
+
if (this.deps.store.getById(id)) this.deps.store.update(id, { groupId });
|
|
5882
|
+
}
|
|
5883
|
+
this.deps.recorder.commit();
|
|
5884
|
+
}
|
|
5885
|
+
ungroup() {
|
|
5886
|
+
const ids = this.deps.getSelectedIds();
|
|
5887
|
+
if (ids.length === 0) return;
|
|
5888
|
+
this.deps.recorder.begin();
|
|
5889
|
+
for (const id of ids) {
|
|
5890
|
+
const el = this.deps.store.getById(id);
|
|
5891
|
+
if (el && el.groupId !== void 0) this.deps.store.update(id, { groupId: void 0 });
|
|
5892
|
+
}
|
|
5893
|
+
this.deps.recorder.commit();
|
|
5894
|
+
}
|
|
5895
|
+
toggleLock() {
|
|
5896
|
+
const ids = this.deps.getSelectedIds();
|
|
5897
|
+
if (ids.length === 0) return;
|
|
5898
|
+
const anyUnlocked = ids.some((id) => {
|
|
5899
|
+
const el = this.deps.store.getById(id);
|
|
5900
|
+
return el ? !el.locked : false;
|
|
5901
|
+
});
|
|
5902
|
+
this.deps.recorder.begin();
|
|
5903
|
+
for (const id of ids) {
|
|
5904
|
+
const el = this.deps.store.getById(id);
|
|
5905
|
+
if (el && el.locked !== anyUnlocked) this.deps.store.update(id, { locked: anyUnlocked });
|
|
5906
|
+
}
|
|
5907
|
+
this.deps.recorder.commit();
|
|
5908
|
+
}
|
|
5909
|
+
align(edge) {
|
|
5910
|
+
const bounded = this.boundedSelection();
|
|
5911
|
+
if (bounded.length < 2) return;
|
|
5912
|
+
const B = unionBounds(bounded.map((e) => e.bounds));
|
|
5913
|
+
this.deps.recorder.begin();
|
|
5914
|
+
const moved = [];
|
|
5915
|
+
for (const { id, el, bounds: b } of bounded) {
|
|
5916
|
+
if (!this.isMovable(el)) continue;
|
|
5917
|
+
let dx = 0;
|
|
5918
|
+
let dy = 0;
|
|
5919
|
+
switch (edge) {
|
|
5920
|
+
case "left":
|
|
5921
|
+
dx = B.x - b.x;
|
|
5922
|
+
break;
|
|
5923
|
+
case "right":
|
|
5924
|
+
dx = B.x + B.w - (b.x + b.w);
|
|
5925
|
+
break;
|
|
5926
|
+
case "center-x":
|
|
5927
|
+
dx = B.x + B.w / 2 - (b.x + b.w / 2);
|
|
5928
|
+
break;
|
|
5929
|
+
case "top":
|
|
5930
|
+
dy = B.y - b.y;
|
|
5931
|
+
break;
|
|
5932
|
+
case "bottom":
|
|
5933
|
+
dy = B.y + B.h - (b.y + b.h);
|
|
5934
|
+
break;
|
|
5935
|
+
case "middle":
|
|
5936
|
+
dy = B.y + B.h / 2 - (b.y + b.h / 2);
|
|
5937
|
+
break;
|
|
5938
|
+
}
|
|
5939
|
+
if (dx === 0 && dy === 0) continue;
|
|
5940
|
+
this.deps.store.update(id, translateElementPatch(el, dx, dy));
|
|
5941
|
+
moved.push(id);
|
|
5942
|
+
}
|
|
5943
|
+
updateArrowsBoundToElements(moved, this.deps.store);
|
|
5944
|
+
this.deps.recorder.commit();
|
|
5945
|
+
this.deps.requestRender();
|
|
5946
|
+
}
|
|
5947
|
+
distribute(axis) {
|
|
5948
|
+
const bounded = this.boundedSelection();
|
|
5949
|
+
if (bounded.length < 3) return;
|
|
5950
|
+
const center2 = (b) => axis === "horizontal" ? b.x + b.w / 2 : b.y + b.h / 2;
|
|
5951
|
+
const sorted = [...bounded].sort((p, q) => center2(p.bounds) - center2(q.bounds));
|
|
5952
|
+
const first = sorted[0];
|
|
5953
|
+
const last = sorted[sorted.length - 1];
|
|
5954
|
+
if (!first || !last) return;
|
|
5955
|
+
const c0 = center2(first.bounds);
|
|
5956
|
+
const cN = center2(last.bounds);
|
|
5957
|
+
const n = sorted.length;
|
|
5958
|
+
this.deps.recorder.begin();
|
|
5959
|
+
const moved = [];
|
|
5960
|
+
for (let i = 1; i < n - 1; i++) {
|
|
5961
|
+
const item = sorted[i];
|
|
5962
|
+
if (!item || !this.isMovable(item.el)) continue;
|
|
5963
|
+
const target = c0 + i * (cN - c0) / (n - 1);
|
|
5964
|
+
const delta = target - center2(item.bounds);
|
|
5965
|
+
if (delta === 0) continue;
|
|
5966
|
+
const [dx, dy] = axis === "horizontal" ? [delta, 0] : [0, delta];
|
|
5967
|
+
this.deps.store.update(item.id, translateElementPatch(item.el, dx, dy));
|
|
5968
|
+
moved.push(item.id);
|
|
5969
|
+
}
|
|
5970
|
+
updateArrowsBoundToElements(moved, this.deps.store);
|
|
5971
|
+
this.deps.recorder.commit();
|
|
5972
|
+
this.deps.requestRender();
|
|
5973
|
+
}
|
|
5974
|
+
boundedSelection() {
|
|
5975
|
+
const out = [];
|
|
5976
|
+
for (const id of this.deps.getSelectedIds()) {
|
|
5977
|
+
const el = this.deps.store.getById(id);
|
|
5978
|
+
if (!el) continue;
|
|
5979
|
+
const bounds = getElementBounds(el);
|
|
5980
|
+
if (bounds) out.push({ id, el, bounds });
|
|
5981
|
+
}
|
|
5982
|
+
return out;
|
|
5983
|
+
}
|
|
5984
|
+
isMovable(el) {
|
|
5985
|
+
if (el.locked) return false;
|
|
5986
|
+
if (el.type === "arrow" && (el.fromBinding ?? el.toBinding)) return false;
|
|
5987
|
+
return true;
|
|
5988
|
+
}
|
|
5989
|
+
};
|
|
5990
|
+
|
|
5991
|
+
// src/canvas/grid-controller.ts
|
|
5992
|
+
var GridController = class {
|
|
5993
|
+
constructor(deps) {
|
|
5994
|
+
this.deps = deps;
|
|
5995
|
+
}
|
|
5996
|
+
listeners = /* @__PURE__ */ new Set();
|
|
5997
|
+
add(input) {
|
|
5998
|
+
const existing = this.deps.store.getElementsByType("grid")[0];
|
|
5999
|
+
this.deps.recorder.begin();
|
|
6000
|
+
if (existing) {
|
|
6001
|
+
this.deps.store.remove(existing.id);
|
|
6002
|
+
}
|
|
6003
|
+
const grid = createGrid({ ...input, layerId: this.deps.getActiveLayerId() });
|
|
6004
|
+
this.deps.store.add(grid);
|
|
6005
|
+
this.deps.recorder.commit();
|
|
6006
|
+
this.deps.requestRender();
|
|
6007
|
+
return grid.id;
|
|
6008
|
+
}
|
|
6009
|
+
update(updates) {
|
|
6010
|
+
const grid = this.deps.store.getElementsByType("grid")[0];
|
|
6011
|
+
if (!grid) return;
|
|
6012
|
+
this.deps.recorder.begin();
|
|
6013
|
+
this.deps.store.update(grid.id, updates);
|
|
6014
|
+
this.deps.recorder.commit();
|
|
6015
|
+
this.deps.requestRender();
|
|
6016
|
+
}
|
|
6017
|
+
remove() {
|
|
6018
|
+
const grid = this.deps.store.getElementsByType("grid")[0];
|
|
6019
|
+
if (!grid) return;
|
|
6020
|
+
this.deps.recorder.begin();
|
|
6021
|
+
this.deps.store.remove(grid.id);
|
|
6022
|
+
this.deps.recorder.commit();
|
|
6023
|
+
this.deps.requestRender();
|
|
6024
|
+
}
|
|
6025
|
+
getInfo() {
|
|
6026
|
+
const grid = this.deps.store.getElementsByType("grid")[0];
|
|
6027
|
+
if (!grid) return null;
|
|
6028
|
+
return {
|
|
6029
|
+
gridType: grid.gridType,
|
|
6030
|
+
hexOrientation: grid.hexOrientation,
|
|
6031
|
+
cellSize: grid.cellSize,
|
|
6032
|
+
cellRadius: grid.gridType === "hex" ? grid.cellSize : grid.cellSize / 2
|
|
6033
|
+
};
|
|
6034
|
+
}
|
|
6035
|
+
onChange(listener) {
|
|
6036
|
+
this.listeners.add(listener);
|
|
6037
|
+
return () => {
|
|
6038
|
+
this.listeners.delete(listener);
|
|
6039
|
+
};
|
|
6040
|
+
}
|
|
6041
|
+
syncContext() {
|
|
6042
|
+
const grid = this.deps.store.getElementsByType("grid")[0];
|
|
6043
|
+
if (grid) {
|
|
6044
|
+
this.deps.toolContext.gridSize = grid.cellSize;
|
|
6045
|
+
this.deps.toolContext.gridType = grid.gridType;
|
|
6046
|
+
this.deps.toolContext.hexOrientation = grid.hexOrientation;
|
|
6047
|
+
} else {
|
|
6048
|
+
this.deps.toolContext.gridSize = this.deps.defaultGridSize;
|
|
6049
|
+
this.deps.toolContext.gridType = void 0;
|
|
6050
|
+
this.deps.toolContext.hexOrientation = void 0;
|
|
6051
|
+
}
|
|
6052
|
+
this.notify();
|
|
6053
|
+
}
|
|
6054
|
+
notify() {
|
|
6055
|
+
const info = this.getInfo();
|
|
6056
|
+
for (const listener of this.listeners) {
|
|
6057
|
+
listener(info);
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6060
|
+
};
|
|
6061
|
+
|
|
6062
|
+
// src/canvas/viewport.ts
|
|
6063
|
+
var EMPTY_IDS = [];
|
|
6064
|
+
var ARROW_HIT_THRESHOLD = 10;
|
|
6065
|
+
function noop() {
|
|
6066
|
+
}
|
|
5800
6067
|
var Viewport = class {
|
|
5801
6068
|
constructor(container, options = {}) {
|
|
5802
6069
|
this.container = container;
|
|
@@ -5839,9 +6106,15 @@ var Viewport = class {
|
|
|
5839
6106
|
this.dropHandler = options.onDrop;
|
|
5840
6107
|
this.history = new HistoryStack();
|
|
5841
6108
|
this.historyRecorder = new HistoryRecorder(this.store, this.history, this.layerManager);
|
|
5842
|
-
this.
|
|
5843
|
-
|
|
5844
|
-
|
|
6109
|
+
this.selectionOps = new SelectionOps({
|
|
6110
|
+
store: this.store,
|
|
6111
|
+
recorder: this.historyRecorder,
|
|
6112
|
+
getSelectedIds: () => this.getSelectedIds(),
|
|
6113
|
+
requestRender: () => this.requestRender()
|
|
6114
|
+
});
|
|
6115
|
+
this.wrapper = createWrapper();
|
|
6116
|
+
this.canvasEl = createCanvas();
|
|
6117
|
+
this.domLayer = createDomLayer();
|
|
5845
6118
|
this.wrapper.appendChild(this.canvasEl);
|
|
5846
6119
|
this.wrapper.appendChild(this.domLayer);
|
|
5847
6120
|
this.container.appendChild(this.wrapper);
|
|
@@ -5919,21 +6192,29 @@ var Viewport = class {
|
|
|
5919
6192
|
this.contextMenu?.close();
|
|
5920
6193
|
this.requestRender();
|
|
5921
6194
|
});
|
|
6195
|
+
this.gridController = new GridController({
|
|
6196
|
+
store: this.store,
|
|
6197
|
+
recorder: this.historyRecorder,
|
|
6198
|
+
requestRender: () => this.requestRender(),
|
|
6199
|
+
getActiveLayerId: () => this.layerManager.activeLayerId,
|
|
6200
|
+
toolContext: this.toolContext,
|
|
6201
|
+
defaultGridSize: this._gridSize
|
|
6202
|
+
});
|
|
5922
6203
|
this.unsubStore = [
|
|
5923
6204
|
this.store.on("add", (el) => {
|
|
5924
|
-
if (el.type === "grid") this.
|
|
6205
|
+
if (el.type === "grid") this.gridController.syncContext();
|
|
5925
6206
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
5926
6207
|
this.requestRender();
|
|
5927
6208
|
}),
|
|
5928
6209
|
this.store.on("remove", (el) => {
|
|
5929
|
-
if (el.type === "grid") this.
|
|
6210
|
+
if (el.type === "grid") this.gridController.syncContext();
|
|
5930
6211
|
this.unbindArrowsFrom(el);
|
|
5931
6212
|
this.domNodeManager.removeDomNode(el.id);
|
|
5932
6213
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
5933
6214
|
this.requestRender();
|
|
5934
6215
|
}),
|
|
5935
6216
|
this.store.on("update", ({ previous, current }) => {
|
|
5936
|
-
if (current.type === "grid") this.
|
|
6217
|
+
if (current.type === "grid") this.gridController.syncContext();
|
|
5937
6218
|
this.renderLoop.markLayerDirty(current.layerId);
|
|
5938
6219
|
if (previous.layerId !== current.layerId) {
|
|
5939
6220
|
this.renderLoop.markLayerDirty(previous.layerId);
|
|
@@ -5943,7 +6224,7 @@ var Viewport = class {
|
|
|
5943
6224
|
this.store.on("clear", () => {
|
|
5944
6225
|
this.domNodeManager.clearDomNodes();
|
|
5945
6226
|
this.renderLoop.markAllLayersDirty();
|
|
5946
|
-
this.
|
|
6227
|
+
this.gridController.syncContext();
|
|
5947
6228
|
this.requestRender();
|
|
5948
6229
|
})
|
|
5949
6230
|
];
|
|
@@ -5958,7 +6239,7 @@ var Viewport = class {
|
|
|
5958
6239
|
this.observeResize();
|
|
5959
6240
|
this.syncCanvasSize();
|
|
5960
6241
|
this.renderLoop.start();
|
|
5961
|
-
this.
|
|
6242
|
+
this.gridController.syncContext();
|
|
5962
6243
|
}
|
|
5963
6244
|
camera;
|
|
5964
6245
|
store;
|
|
@@ -5977,6 +6258,7 @@ var Viewport = class {
|
|
|
5977
6258
|
noteEditor;
|
|
5978
6259
|
arrowLabelEditor;
|
|
5979
6260
|
historyRecorder;
|
|
6261
|
+
selectionOps;
|
|
5980
6262
|
toolContext;
|
|
5981
6263
|
marginViewport;
|
|
5982
6264
|
resizeObserver = null;
|
|
@@ -5988,7 +6270,7 @@ var Viewport = class {
|
|
|
5988
6270
|
interactMode;
|
|
5989
6271
|
onHtmlElementMount;
|
|
5990
6272
|
dropHandler;
|
|
5991
|
-
|
|
6273
|
+
gridController;
|
|
5992
6274
|
doubleTapDetector = new DoubleTapDetector();
|
|
5993
6275
|
tapDownX = 0;
|
|
5994
6276
|
tapDownY = 0;
|
|
@@ -6160,48 +6442,19 @@ var Viewport = class {
|
|
|
6160
6442
|
this.requestRender();
|
|
6161
6443
|
}
|
|
6162
6444
|
addGrid(input) {
|
|
6163
|
-
|
|
6164
|
-
this.historyRecorder.begin();
|
|
6165
|
-
if (existing) {
|
|
6166
|
-
this.store.remove(existing.id);
|
|
6167
|
-
}
|
|
6168
|
-
const grid = createGrid({ ...input, layerId: this.layerManager.activeLayerId });
|
|
6169
|
-
this.store.add(grid);
|
|
6170
|
-
this.historyRecorder.commit();
|
|
6171
|
-
this.requestRender();
|
|
6172
|
-
return grid.id;
|
|
6445
|
+
return this.gridController.add(input);
|
|
6173
6446
|
}
|
|
6174
6447
|
updateGrid(updates) {
|
|
6175
|
-
|
|
6176
|
-
if (!grid) return;
|
|
6177
|
-
this.historyRecorder.begin();
|
|
6178
|
-
this.store.update(grid.id, updates);
|
|
6179
|
-
this.historyRecorder.commit();
|
|
6180
|
-
this.requestRender();
|
|
6448
|
+
this.gridController.update(updates);
|
|
6181
6449
|
}
|
|
6182
6450
|
removeGrid() {
|
|
6183
|
-
|
|
6184
|
-
if (!grid) return;
|
|
6185
|
-
this.historyRecorder.begin();
|
|
6186
|
-
this.store.remove(grid.id);
|
|
6187
|
-
this.historyRecorder.commit();
|
|
6188
|
-
this.requestRender();
|
|
6451
|
+
this.gridController.remove();
|
|
6189
6452
|
}
|
|
6190
6453
|
getGridInfo() {
|
|
6191
|
-
|
|
6192
|
-
if (!grid) return null;
|
|
6193
|
-
return {
|
|
6194
|
-
gridType: grid.gridType,
|
|
6195
|
-
hexOrientation: grid.hexOrientation,
|
|
6196
|
-
cellSize: grid.cellSize,
|
|
6197
|
-
cellRadius: grid.gridType === "hex" ? grid.cellSize : grid.cellSize / 2
|
|
6198
|
-
};
|
|
6454
|
+
return this.gridController.getInfo();
|
|
6199
6455
|
}
|
|
6200
6456
|
onGridChange(listener) {
|
|
6201
|
-
this.
|
|
6202
|
-
return () => {
|
|
6203
|
-
this.gridChangeListeners.delete(listener);
|
|
6204
|
-
};
|
|
6457
|
+
return this.gridController.onChange(listener);
|
|
6205
6458
|
}
|
|
6206
6459
|
getSelectTool() {
|
|
6207
6460
|
return this.toolManager.getTool("select");
|
|
@@ -6242,154 +6495,25 @@ var Viewport = class {
|
|
|
6242
6495
|
return tool ? tool.onSelectionChange(listener) : noop;
|
|
6243
6496
|
}
|
|
6244
6497
|
getSelectionStyle() {
|
|
6245
|
-
|
|
6246
|
-
if (ids.length === 0) return null;
|
|
6247
|
-
const styles = [];
|
|
6248
|
-
for (const id of ids) {
|
|
6249
|
-
const el = this.store.getById(id);
|
|
6250
|
-
if (el) styles.push(getElementStyle(el));
|
|
6251
|
-
}
|
|
6252
|
-
if (styles.length === 0) return null;
|
|
6253
|
-
const result = {};
|
|
6254
|
-
const color = sharedValue(styles.map((s) => s.color));
|
|
6255
|
-
if (color !== void 0) result.color = color;
|
|
6256
|
-
const fillColor = sharedValue(styles.map((s) => s.fillColor));
|
|
6257
|
-
if (fillColor !== void 0) result.fillColor = fillColor;
|
|
6258
|
-
const strokeWidth = sharedValue(styles.map((s) => s.strokeWidth));
|
|
6259
|
-
if (strokeWidth !== void 0) result.strokeWidth = strokeWidth;
|
|
6260
|
-
const opacity = sharedValue(styles.map((s) => s.opacity));
|
|
6261
|
-
if (opacity !== void 0) result.opacity = opacity;
|
|
6262
|
-
const fontSize = sharedValue(styles.map((s) => s.fontSize));
|
|
6263
|
-
if (fontSize !== void 0) result.fontSize = fontSize;
|
|
6264
|
-
return result;
|
|
6498
|
+
return this.selectionOps.getStyle();
|
|
6265
6499
|
}
|
|
6266
6500
|
applyStyleToSelection(style) {
|
|
6267
|
-
|
|
6268
|
-
if (ids.length === 0) return;
|
|
6269
|
-
this.historyRecorder.begin();
|
|
6270
|
-
for (const id of ids) {
|
|
6271
|
-
const el = this.store.getById(id);
|
|
6272
|
-
if (!el) continue;
|
|
6273
|
-
const patch = styleToPatch(el, style);
|
|
6274
|
-
if (Object.keys(patch).length > 0) {
|
|
6275
|
-
this.store.update(id, patch);
|
|
6276
|
-
}
|
|
6277
|
-
}
|
|
6278
|
-
this.historyRecorder.commit();
|
|
6501
|
+
this.selectionOps.applyStyle(style);
|
|
6279
6502
|
}
|
|
6280
6503
|
groupSelection() {
|
|
6281
|
-
|
|
6282
|
-
if (ids.length < 2) return;
|
|
6283
|
-
const groupId = createId("group");
|
|
6284
|
-
this.historyRecorder.begin();
|
|
6285
|
-
for (const id of ids) {
|
|
6286
|
-
if (this.store.getById(id)) this.store.update(id, { groupId });
|
|
6287
|
-
}
|
|
6288
|
-
this.historyRecorder.commit();
|
|
6504
|
+
this.selectionOps.group();
|
|
6289
6505
|
}
|
|
6290
6506
|
ungroupSelection() {
|
|
6291
|
-
|
|
6292
|
-
if (ids.length === 0) return;
|
|
6293
|
-
this.historyRecorder.begin();
|
|
6294
|
-
for (const id of ids) {
|
|
6295
|
-
const el = this.store.getById(id);
|
|
6296
|
-
if (el && el.groupId !== void 0) this.store.update(id, { groupId: void 0 });
|
|
6297
|
-
}
|
|
6298
|
-
this.historyRecorder.commit();
|
|
6507
|
+
this.selectionOps.ungroup();
|
|
6299
6508
|
}
|
|
6300
6509
|
toggleLockSelection() {
|
|
6301
|
-
|
|
6302
|
-
if (ids.length === 0) return;
|
|
6303
|
-
const anyUnlocked = ids.some((id) => {
|
|
6304
|
-
const el = this.store.getById(id);
|
|
6305
|
-
return el ? !el.locked : false;
|
|
6306
|
-
});
|
|
6307
|
-
this.historyRecorder.begin();
|
|
6308
|
-
for (const id of ids) {
|
|
6309
|
-
const el = this.store.getById(id);
|
|
6310
|
-
if (el && el.locked !== anyUnlocked) this.store.update(id, { locked: anyUnlocked });
|
|
6311
|
-
}
|
|
6312
|
-
this.historyRecorder.commit();
|
|
6510
|
+
this.selectionOps.toggleLock();
|
|
6313
6511
|
}
|
|
6314
6512
|
alignSelection(edge) {
|
|
6315
|
-
|
|
6316
|
-
if (bounded.length < 2) return;
|
|
6317
|
-
const B = unionBounds(bounded.map((e) => e.bounds));
|
|
6318
|
-
this.historyRecorder.begin();
|
|
6319
|
-
const moved = [];
|
|
6320
|
-
for (const { id, el, bounds: b } of bounded) {
|
|
6321
|
-
if (!this.isMovable(el)) continue;
|
|
6322
|
-
let dx = 0;
|
|
6323
|
-
let dy = 0;
|
|
6324
|
-
switch (edge) {
|
|
6325
|
-
case "left":
|
|
6326
|
-
dx = B.x - b.x;
|
|
6327
|
-
break;
|
|
6328
|
-
case "right":
|
|
6329
|
-
dx = B.x + B.w - (b.x + b.w);
|
|
6330
|
-
break;
|
|
6331
|
-
case "center-x":
|
|
6332
|
-
dx = B.x + B.w / 2 - (b.x + b.w / 2);
|
|
6333
|
-
break;
|
|
6334
|
-
case "top":
|
|
6335
|
-
dy = B.y - b.y;
|
|
6336
|
-
break;
|
|
6337
|
-
case "bottom":
|
|
6338
|
-
dy = B.y + B.h - (b.y + b.h);
|
|
6339
|
-
break;
|
|
6340
|
-
case "middle":
|
|
6341
|
-
dy = B.y + B.h / 2 - (b.y + b.h / 2);
|
|
6342
|
-
break;
|
|
6343
|
-
}
|
|
6344
|
-
if (dx === 0 && dy === 0) continue;
|
|
6345
|
-
this.store.update(id, translateElementPatch(el, dx, dy));
|
|
6346
|
-
moved.push(id);
|
|
6347
|
-
}
|
|
6348
|
-
updateArrowsBoundToElements(moved, this.store);
|
|
6349
|
-
this.historyRecorder.commit();
|
|
6350
|
-
this.requestRender();
|
|
6513
|
+
this.selectionOps.align(edge);
|
|
6351
6514
|
}
|
|
6352
6515
|
distributeSelection(axis) {
|
|
6353
|
-
|
|
6354
|
-
if (bounded.length < 3) return;
|
|
6355
|
-
const center2 = (b) => axis === "horizontal" ? b.x + b.w / 2 : b.y + b.h / 2;
|
|
6356
|
-
const sorted = [...bounded].sort((p, q) => center2(p.bounds) - center2(q.bounds));
|
|
6357
|
-
const first = sorted[0];
|
|
6358
|
-
const last = sorted[sorted.length - 1];
|
|
6359
|
-
if (!first || !last) return;
|
|
6360
|
-
const c0 = center2(first.bounds);
|
|
6361
|
-
const cN = center2(last.bounds);
|
|
6362
|
-
const n = sorted.length;
|
|
6363
|
-
this.historyRecorder.begin();
|
|
6364
|
-
const moved = [];
|
|
6365
|
-
for (let i = 1; i < n - 1; i++) {
|
|
6366
|
-
const item = sorted[i];
|
|
6367
|
-
if (!item || !this.isMovable(item.el)) continue;
|
|
6368
|
-
const target = c0 + i * (cN - c0) / (n - 1);
|
|
6369
|
-
const delta = target - center2(item.bounds);
|
|
6370
|
-
if (delta === 0) continue;
|
|
6371
|
-
const [dx, dy] = axis === "horizontal" ? [delta, 0] : [0, delta];
|
|
6372
|
-
this.store.update(item.id, translateElementPatch(item.el, dx, dy));
|
|
6373
|
-
moved.push(item.id);
|
|
6374
|
-
}
|
|
6375
|
-
updateArrowsBoundToElements(moved, this.store);
|
|
6376
|
-
this.historyRecorder.commit();
|
|
6377
|
-
this.requestRender();
|
|
6378
|
-
}
|
|
6379
|
-
boundedSelection() {
|
|
6380
|
-
const out = [];
|
|
6381
|
-
for (const id of this.getSelectedIds()) {
|
|
6382
|
-
const el = this.store.getById(id);
|
|
6383
|
-
if (!el) continue;
|
|
6384
|
-
const bounds = getElementBounds(el);
|
|
6385
|
-
if (bounds) out.push({ id, el, bounds });
|
|
6386
|
-
}
|
|
6387
|
-
return out;
|
|
6388
|
-
}
|
|
6389
|
-
isMovable(el) {
|
|
6390
|
-
if (el.locked) return false;
|
|
6391
|
-
if (el.type === "arrow" && (el.fromBinding ?? el.toBinding)) return false;
|
|
6392
|
-
return true;
|
|
6516
|
+
this.selectionOps.distribute(axis);
|
|
6393
6517
|
}
|
|
6394
6518
|
getRenderStats() {
|
|
6395
6519
|
return this.renderLoop.getStats();
|
|
@@ -6598,43 +6722,6 @@ var Viewport = class {
|
|
|
6598
6722
|
}
|
|
6599
6723
|
}
|
|
6600
6724
|
}
|
|
6601
|
-
createWrapper() {
|
|
6602
|
-
const el = document.createElement("div");
|
|
6603
|
-
Object.assign(el.style, {
|
|
6604
|
-
position: "relative",
|
|
6605
|
-
width: "100%",
|
|
6606
|
-
height: "100%",
|
|
6607
|
-
overflow: "hidden",
|
|
6608
|
-
overscrollBehavior: "none",
|
|
6609
|
-
userSelect: "none",
|
|
6610
|
-
webkitUserSelect: "none"
|
|
6611
|
-
});
|
|
6612
|
-
return el;
|
|
6613
|
-
}
|
|
6614
|
-
createCanvas() {
|
|
6615
|
-
const el = document.createElement("canvas");
|
|
6616
|
-
Object.assign(el.style, {
|
|
6617
|
-
position: "absolute",
|
|
6618
|
-
top: "0",
|
|
6619
|
-
left: "0",
|
|
6620
|
-
width: "100%",
|
|
6621
|
-
height: "100%"
|
|
6622
|
-
});
|
|
6623
|
-
return el;
|
|
6624
|
-
}
|
|
6625
|
-
createDomLayer() {
|
|
6626
|
-
const el = document.createElement("div");
|
|
6627
|
-
Object.assign(el.style, {
|
|
6628
|
-
position: "absolute",
|
|
6629
|
-
top: "0",
|
|
6630
|
-
left: "0",
|
|
6631
|
-
width: "100%",
|
|
6632
|
-
height: "100%",
|
|
6633
|
-
pointerEvents: "none",
|
|
6634
|
-
transformOrigin: "0 0"
|
|
6635
|
-
});
|
|
6636
|
-
return el;
|
|
6637
|
-
}
|
|
6638
6725
|
applyCameraTransform() {
|
|
6639
6726
|
this.domLayer.style.transform = this.camera.toCSSTransform();
|
|
6640
6727
|
}
|
|
@@ -6644,25 +6731,6 @@ var Viewport = class {
|
|
|
6644
6731
|
this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
|
|
6645
6732
|
this.requestRender();
|
|
6646
6733
|
}
|
|
6647
|
-
syncGridContext() {
|
|
6648
|
-
const grid = this.store.getElementsByType("grid")[0];
|
|
6649
|
-
if (grid) {
|
|
6650
|
-
this.toolContext.gridSize = grid.cellSize;
|
|
6651
|
-
this.toolContext.gridType = grid.gridType;
|
|
6652
|
-
this.toolContext.hexOrientation = grid.hexOrientation;
|
|
6653
|
-
} else {
|
|
6654
|
-
this.toolContext.gridSize = this._gridSize;
|
|
6655
|
-
this.toolContext.gridType = void 0;
|
|
6656
|
-
this.toolContext.hexOrientation = void 0;
|
|
6657
|
-
}
|
|
6658
|
-
this.notifyGridChangeListeners();
|
|
6659
|
-
}
|
|
6660
|
-
notifyGridChangeListeners() {
|
|
6661
|
-
const info = this.getGridInfo();
|
|
6662
|
-
for (const listener of this.gridChangeListeners) {
|
|
6663
|
-
listener(info);
|
|
6664
|
-
}
|
|
6665
|
-
}
|
|
6666
6734
|
observeResize() {
|
|
6667
6735
|
if (typeof ResizeObserver === "undefined") return;
|
|
6668
6736
|
this.resizeObserver = new ResizeObserver(() => this.syncCanvasSize());
|
|
@@ -7195,14 +7263,11 @@ function computeSnapGuides(moving, targets, threshold) {
|
|
|
7195
7263
|
return { dx: xSnap?.delta ?? 0, dy: ySnap?.delta ?? 0, guides };
|
|
7196
7264
|
}
|
|
7197
7265
|
|
|
7198
|
-
// src/tools/select-
|
|
7266
|
+
// src/tools/select-overlay.ts
|
|
7199
7267
|
var HANDLE_SIZE = 8;
|
|
7200
|
-
var SNAP_PX = 6;
|
|
7201
7268
|
var HANDLE_HIT_PADDING2 = 4;
|
|
7202
7269
|
var SELECTION_PAD = 4;
|
|
7203
|
-
var MIN_ELEMENT_SIZE = 20;
|
|
7204
7270
|
var ROTATE_HANDLE_OFFSET = 24;
|
|
7205
|
-
var ROTATE_SNAP = Math.PI / 12;
|
|
7206
7271
|
var ROTATABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape", "stroke"]);
|
|
7207
7272
|
var HANDLE_CURSORS = {
|
|
7208
7273
|
nw: "nwse-resize",
|
|
@@ -7210,6 +7275,486 @@ var HANDLE_CURSORS = {
|
|
|
7210
7275
|
ne: "nesw-resize",
|
|
7211
7276
|
sw: "nesw-resize"
|
|
7212
7277
|
};
|
|
7278
|
+
function getOverlayLayout(el, zoom) {
|
|
7279
|
+
const bounds = getElementBounds(el);
|
|
7280
|
+
if (!bounds) return null;
|
|
7281
|
+
const angle = el.rotation ?? 0;
|
|
7282
|
+
const pad = SELECTION_PAD / zoom;
|
|
7283
|
+
const center2 = { x: bounds.x + bounds.w / 2, y: bounds.y + bounds.h / 2 };
|
|
7284
|
+
const raw = [
|
|
7285
|
+
["nw", { x: bounds.x - pad, y: bounds.y - pad }],
|
|
7286
|
+
["ne", { x: bounds.x + bounds.w + pad, y: bounds.y - pad }],
|
|
7287
|
+
["sw", { x: bounds.x - pad, y: bounds.y + bounds.h + pad }],
|
|
7288
|
+
["se", { x: bounds.x + bounds.w + pad, y: bounds.y + bounds.h + pad }]
|
|
7289
|
+
];
|
|
7290
|
+
const corners = raw.map(
|
|
7291
|
+
([h, p]) => [h, rotatePoint(p, center2, angle)]
|
|
7292
|
+
);
|
|
7293
|
+
const topMid = { x: center2.x, y: bounds.y - pad - ROTATE_HANDLE_OFFSET / zoom };
|
|
7294
|
+
const rotateHandle = rotatePoint(topMid, center2, angle);
|
|
7295
|
+
return { center: center2, corners, rotateHandle, angle };
|
|
7296
|
+
}
|
|
7297
|
+
function getHandlePositions(bounds) {
|
|
7298
|
+
return [
|
|
7299
|
+
["nw", { x: bounds.x, y: bounds.y }],
|
|
7300
|
+
["ne", { x: bounds.x + bounds.w, y: bounds.y }],
|
|
7301
|
+
["sw", { x: bounds.x, y: bounds.y + bounds.h }],
|
|
7302
|
+
["se", { x: bounds.x + bounds.w, y: bounds.y + bounds.h }]
|
|
7303
|
+
];
|
|
7304
|
+
}
|
|
7305
|
+
function topMidpoint(layout) {
|
|
7306
|
+
const nw = layout.corners.find(([h]) => h === "nw")?.[1] ?? { x: 0, y: 0 };
|
|
7307
|
+
const ne = layout.corners.find(([h]) => h === "ne")?.[1] ?? { x: 0, y: 0 };
|
|
7308
|
+
return { x: (nw.x + ne.x) / 2, y: (nw.y + ne.y) / 2 };
|
|
7309
|
+
}
|
|
7310
|
+
function drawLockBadge(ctx, at, zoom) {
|
|
7311
|
+
const r = 9 / zoom;
|
|
7312
|
+
ctx.save();
|
|
7313
|
+
ctx.setLineDash([]);
|
|
7314
|
+
ctx.beginPath();
|
|
7315
|
+
ctx.arc(at.x, at.y, r, 0, Math.PI * 2);
|
|
7316
|
+
ctx.fillStyle = "#ffffff";
|
|
7317
|
+
ctx.fill();
|
|
7318
|
+
ctx.strokeStyle = "#2196F3";
|
|
7319
|
+
ctx.lineWidth = 1.5 / zoom;
|
|
7320
|
+
ctx.stroke();
|
|
7321
|
+
const bw = 8 / zoom;
|
|
7322
|
+
const bh = 6 / zoom;
|
|
7323
|
+
ctx.fillStyle = "#2196F3";
|
|
7324
|
+
ctx.fillRect(at.x - bw / 2, at.y - bh / 2 + 1 / zoom, bw, bh);
|
|
7325
|
+
ctx.beginPath();
|
|
7326
|
+
ctx.arc(at.x, at.y - bh / 2 + 1 / zoom, 2.5 / zoom, Math.PI, 0);
|
|
7327
|
+
ctx.lineWidth = 1.4 / zoom;
|
|
7328
|
+
ctx.stroke();
|
|
7329
|
+
ctx.restore();
|
|
7330
|
+
}
|
|
7331
|
+
function renderMarquee(ctx, rect) {
|
|
7332
|
+
ctx.save();
|
|
7333
|
+
ctx.strokeStyle = "#2196F3";
|
|
7334
|
+
ctx.fillStyle = "rgba(33, 150, 243, 0.08)";
|
|
7335
|
+
ctx.lineWidth = 1;
|
|
7336
|
+
ctx.setLineDash([4, 4]);
|
|
7337
|
+
ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
|
|
7338
|
+
ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
|
|
7339
|
+
ctx.restore();
|
|
7340
|
+
}
|
|
7341
|
+
function renderBindingHighlights(ctx, arrow, zoom, store) {
|
|
7342
|
+
if (!arrow.fromBinding && !arrow.toBinding) return;
|
|
7343
|
+
const pad = SELECTION_PAD / zoom;
|
|
7344
|
+
ctx.save();
|
|
7345
|
+
ctx.strokeStyle = "#2196F3";
|
|
7346
|
+
ctx.lineWidth = 2 / zoom;
|
|
7347
|
+
ctx.setLineDash([]);
|
|
7348
|
+
const drawn = /* @__PURE__ */ new Set();
|
|
7349
|
+
for (const binding of [arrow.fromBinding, arrow.toBinding]) {
|
|
7350
|
+
if (!binding || drawn.has(binding.elementId)) continue;
|
|
7351
|
+
drawn.add(binding.elementId);
|
|
7352
|
+
const target = store.getById(binding.elementId);
|
|
7353
|
+
if (!target) continue;
|
|
7354
|
+
const bounds = getElementBounds(target);
|
|
7355
|
+
if (!bounds) continue;
|
|
7356
|
+
ctx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
7357
|
+
}
|
|
7358
|
+
ctx.restore();
|
|
7359
|
+
}
|
|
7360
|
+
function renderSelectionBoxes(ctx, p) {
|
|
7361
|
+
if (p.selectedIds.length === 0) return;
|
|
7362
|
+
const zoom = p.zoom;
|
|
7363
|
+
const handleWorldSize = HANDLE_SIZE / zoom;
|
|
7364
|
+
ctx.save();
|
|
7365
|
+
ctx.strokeStyle = "#2196F3";
|
|
7366
|
+
ctx.lineWidth = 1.5 / zoom;
|
|
7367
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7368
|
+
for (const id of p.selectedIds) {
|
|
7369
|
+
const el = p.store.getById(id);
|
|
7370
|
+
if (!el) continue;
|
|
7371
|
+
if (el.type === "arrow") {
|
|
7372
|
+
renderArrowHandles(ctx, el, zoom);
|
|
7373
|
+
renderBindingHighlights(ctx, el, zoom, p.store);
|
|
7374
|
+
continue;
|
|
7375
|
+
}
|
|
7376
|
+
if (el.type === "shape" && el.shape === "line") {
|
|
7377
|
+
ctx.setLineDash([]);
|
|
7378
|
+
ctx.fillStyle = "#ffffff";
|
|
7379
|
+
const r = handleWorldSize / 2;
|
|
7380
|
+
for (const pt of lineEndpoints(el)) {
|
|
7381
|
+
ctx.beginPath();
|
|
7382
|
+
ctx.arc(pt.x, pt.y, r, 0, Math.PI * 2);
|
|
7383
|
+
ctx.fill();
|
|
7384
|
+
ctx.stroke();
|
|
7385
|
+
}
|
|
7386
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7387
|
+
continue;
|
|
7388
|
+
}
|
|
7389
|
+
const bounds = getElementBounds(el);
|
|
7390
|
+
if (!bounds) continue;
|
|
7391
|
+
const layout = getOverlayLayout(el, zoom);
|
|
7392
|
+
if (!layout) continue;
|
|
7393
|
+
const pad = SELECTION_PAD / zoom;
|
|
7394
|
+
if (layout.angle === 0) {
|
|
7395
|
+
ctx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
7396
|
+
} else {
|
|
7397
|
+
const ordered = ["nw", "ne", "se", "sw"].map((h) => layout.corners.find(([c]) => c === h)?.[1]).filter((pp) => !!pp);
|
|
7398
|
+
const [p0, ...others] = ordered;
|
|
7399
|
+
if (p0) {
|
|
7400
|
+
ctx.beginPath();
|
|
7401
|
+
ctx.moveTo(p0.x, p0.y);
|
|
7402
|
+
for (const pp of others) ctx.lineTo(pp.x, pp.y);
|
|
7403
|
+
ctx.closePath();
|
|
7404
|
+
ctx.stroke();
|
|
7405
|
+
}
|
|
7406
|
+
}
|
|
7407
|
+
if (!el.locked) {
|
|
7408
|
+
if ("size" in el) {
|
|
7409
|
+
ctx.setLineDash([]);
|
|
7410
|
+
ctx.fillStyle = "#ffffff";
|
|
7411
|
+
const corners = layout.angle === 0 ? getHandlePositions(bounds) : layout.corners;
|
|
7412
|
+
for (const [, pos] of corners) {
|
|
7413
|
+
ctx.fillRect(
|
|
7414
|
+
pos.x - handleWorldSize / 2,
|
|
7415
|
+
pos.y - handleWorldSize / 2,
|
|
7416
|
+
handleWorldSize,
|
|
7417
|
+
handleWorldSize
|
|
7418
|
+
);
|
|
7419
|
+
ctx.strokeRect(
|
|
7420
|
+
pos.x - handleWorldSize / 2,
|
|
7421
|
+
pos.y - handleWorldSize / 2,
|
|
7422
|
+
handleWorldSize,
|
|
7423
|
+
handleWorldSize
|
|
7424
|
+
);
|
|
7425
|
+
}
|
|
7426
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7427
|
+
} else if (el.type === "template") {
|
|
7428
|
+
ctx.setLineDash([]);
|
|
7429
|
+
ctx.fillStyle = "#ffffff";
|
|
7430
|
+
const hx = bounds.x + bounds.w;
|
|
7431
|
+
const hy = bounds.y + bounds.h;
|
|
7432
|
+
ctx.fillRect(
|
|
7433
|
+
hx - handleWorldSize / 2,
|
|
7434
|
+
hy - handleWorldSize / 2,
|
|
7435
|
+
handleWorldSize,
|
|
7436
|
+
handleWorldSize
|
|
7437
|
+
);
|
|
7438
|
+
ctx.strokeRect(
|
|
7439
|
+
hx - handleWorldSize / 2,
|
|
7440
|
+
hy - handleWorldSize / 2,
|
|
7441
|
+
handleWorldSize,
|
|
7442
|
+
handleWorldSize
|
|
7443
|
+
);
|
|
7444
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7445
|
+
}
|
|
7446
|
+
if (p.selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
|
|
7447
|
+
const stemStart = topMidpoint(layout);
|
|
7448
|
+
const stemEnd = layout.rotateHandle;
|
|
7449
|
+
ctx.beginPath();
|
|
7450
|
+
ctx.moveTo(stemStart.x, stemStart.y);
|
|
7451
|
+
ctx.lineTo(stemEnd.x, stemEnd.y);
|
|
7452
|
+
ctx.stroke();
|
|
7453
|
+
ctx.setLineDash([]);
|
|
7454
|
+
ctx.fillStyle = "#ffffff";
|
|
7455
|
+
ctx.beginPath();
|
|
7456
|
+
ctx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7457
|
+
ctx.fill();
|
|
7458
|
+
ctx.stroke();
|
|
7459
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7460
|
+
}
|
|
7461
|
+
}
|
|
7462
|
+
if (el.locked) {
|
|
7463
|
+
const ne = layout.corners.find(([h]) => h === "ne")?.[1];
|
|
7464
|
+
if (ne) drawLockBadge(ctx, ne, zoom);
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
ctx.restore();
|
|
7468
|
+
}
|
|
7469
|
+
function renderGuideLines(ctx, p) {
|
|
7470
|
+
const zoom = p.zoom;
|
|
7471
|
+
const rect = p.rect;
|
|
7472
|
+
ctx.save();
|
|
7473
|
+
ctx.strokeStyle = "#FF4081";
|
|
7474
|
+
ctx.lineWidth = 1 / zoom;
|
|
7475
|
+
ctx.setLineDash([]);
|
|
7476
|
+
for (const g of p.guides) {
|
|
7477
|
+
ctx.beginPath();
|
|
7478
|
+
if (g.axis === "x") {
|
|
7479
|
+
const y0 = rect ? rect.y : p.currentWorld.y - 1e5;
|
|
7480
|
+
const y1 = rect ? rect.y + rect.h : p.currentWorld.y + 1e5;
|
|
7481
|
+
ctx.moveTo(g.position, y0);
|
|
7482
|
+
ctx.lineTo(g.position, y1);
|
|
7483
|
+
} else {
|
|
7484
|
+
const x0 = rect ? rect.x : p.currentWorld.x - 1e5;
|
|
7485
|
+
const x1 = rect ? rect.x + rect.w : p.currentWorld.x + 1e5;
|
|
7486
|
+
ctx.moveTo(x0, g.position);
|
|
7487
|
+
ctx.lineTo(x1, g.position);
|
|
7488
|
+
}
|
|
7489
|
+
ctx.stroke();
|
|
7490
|
+
}
|
|
7491
|
+
ctx.restore();
|
|
7492
|
+
}
|
|
7493
|
+
|
|
7494
|
+
// src/tools/select-hit.ts
|
|
7495
|
+
function hitTest(world, ctx) {
|
|
7496
|
+
const r = 10;
|
|
7497
|
+
const candidates = ctx.store.queryRect({ x: world.x - r, y: world.y - r, w: r * 2, h: r * 2 }).reverse();
|
|
7498
|
+
for (const el of candidates) {
|
|
7499
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
7500
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
7501
|
+
if (el.type === "grid") continue;
|
|
7502
|
+
if (isInsideBounds(world, el)) return el;
|
|
7503
|
+
}
|
|
7504
|
+
return null;
|
|
7505
|
+
}
|
|
7506
|
+
function isInsideBounds(point, el) {
|
|
7507
|
+
if (el.type === "grid") return false;
|
|
7508
|
+
const angle = el.rotation ?? 0;
|
|
7509
|
+
if (angle !== 0) {
|
|
7510
|
+
const b = getElementBounds(el);
|
|
7511
|
+
if (b) {
|
|
7512
|
+
point = rotatePoint(point, { x: b.x + b.w / 2, y: b.y + b.h / 2 }, -angle);
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
if (el.type === "shape" && el.shape === "line") {
|
|
7516
|
+
const [a, b] = lineEndpoints(el);
|
|
7517
|
+
const threshold = Math.max(el.strokeWidth / 2, 6);
|
|
7518
|
+
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
7519
|
+
}
|
|
7520
|
+
if ("size" in el) {
|
|
7521
|
+
const s = el.size;
|
|
7522
|
+
return point.x >= el.position.x && point.x <= el.position.x + s.w && point.y >= el.position.y && point.y <= el.position.y + s.h;
|
|
7523
|
+
}
|
|
7524
|
+
if (el.type === "stroke") {
|
|
7525
|
+
return hitTestStroke(el, point, 10);
|
|
7526
|
+
}
|
|
7527
|
+
if (el.type === "arrow") {
|
|
7528
|
+
return isNearBezier(point, el.from, el.to, el.bend, 10);
|
|
7529
|
+
}
|
|
7530
|
+
if (el.type === "template") {
|
|
7531
|
+
const bounds = getElementBounds(el);
|
|
7532
|
+
if (!bounds) return false;
|
|
7533
|
+
return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
|
|
7534
|
+
}
|
|
7535
|
+
return false;
|
|
7536
|
+
}
|
|
7537
|
+
function hitTestResizeHandle(world, ctx, selectedIds) {
|
|
7538
|
+
if (selectedIds.length === 0) return null;
|
|
7539
|
+
const zoom = ctx.camera.zoom;
|
|
7540
|
+
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7541
|
+
for (const id of selectedIds) {
|
|
7542
|
+
const el = ctx.store.getById(id);
|
|
7543
|
+
if (!el || !("size" in el)) continue;
|
|
7544
|
+
if (el.locked) continue;
|
|
7545
|
+
if (el.type === "shape" && el.shape === "line") continue;
|
|
7546
|
+
const layout = getOverlayLayout(el, zoom);
|
|
7547
|
+
if (!layout) continue;
|
|
7548
|
+
for (const [handle, pos] of layout.corners) {
|
|
7549
|
+
if (Math.abs(world.x - pos.x) <= handleHalf && Math.abs(world.y - pos.y) <= handleHalf) {
|
|
7550
|
+
return { elementId: id, handle };
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7553
|
+
}
|
|
7554
|
+
return null;
|
|
7555
|
+
}
|
|
7556
|
+
function hitTestRotateHandle(world, ctx, selectedIds) {
|
|
7557
|
+
if (selectedIds.length !== 1) return null;
|
|
7558
|
+
const id = selectedIds[0];
|
|
7559
|
+
if (!id) return null;
|
|
7560
|
+
const el = ctx.store.getById(id);
|
|
7561
|
+
if (!el || el.locked || !ROTATABLE_TYPES.has(el.type)) return null;
|
|
7562
|
+
const layout = getOverlayLayout(el, ctx.camera.zoom);
|
|
7563
|
+
if (!layout) return null;
|
|
7564
|
+
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / ctx.camera.zoom;
|
|
7565
|
+
const dx = world.x - layout.rotateHandle.x;
|
|
7566
|
+
const dy = world.y - layout.rotateHandle.y;
|
|
7567
|
+
return dx * dx + dy * dy <= r * r ? { elementId: id } : null;
|
|
7568
|
+
}
|
|
7569
|
+
function hitTestLineHandles(world, ctx, selectedIds) {
|
|
7570
|
+
if (selectedIds.length === 0) return null;
|
|
7571
|
+
const zoom = ctx.camera.zoom;
|
|
7572
|
+
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7573
|
+
const r2 = r * r;
|
|
7574
|
+
for (const id of selectedIds) {
|
|
7575
|
+
const el = ctx.store.getById(id);
|
|
7576
|
+
if (!el || el.type !== "shape" || el.shape !== "line") continue;
|
|
7577
|
+
const [a, b] = lineEndpoints(el);
|
|
7578
|
+
if ((world.x - a.x) ** 2 + (world.y - a.y) ** 2 <= r2) return { elementId: id, fixed: b };
|
|
7579
|
+
if ((world.x - b.x) ** 2 + (world.y - b.y) ** 2 <= r2) return { elementId: id, fixed: a };
|
|
7580
|
+
}
|
|
7581
|
+
return null;
|
|
7582
|
+
}
|
|
7583
|
+
function hitTestTemplateResizeHandle(world, ctx, selectedIds) {
|
|
7584
|
+
if (selectedIds.length === 0) return null;
|
|
7585
|
+
const zoom = ctx.camera.zoom;
|
|
7586
|
+
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7587
|
+
for (const id of selectedIds) {
|
|
7588
|
+
const el = ctx.store.getById(id);
|
|
7589
|
+
if (!el || el.type !== "template") continue;
|
|
7590
|
+
const bounds = getElementBounds(el);
|
|
7591
|
+
if (!bounds) continue;
|
|
7592
|
+
const hx = bounds.x + bounds.w;
|
|
7593
|
+
const hy = bounds.y + bounds.h;
|
|
7594
|
+
if (Math.abs(world.x - hx) <= handleHalf && Math.abs(world.y - hy) <= handleHalf) {
|
|
7595
|
+
return id;
|
|
7596
|
+
}
|
|
7597
|
+
}
|
|
7598
|
+
return null;
|
|
7599
|
+
}
|
|
7600
|
+
function findElementsInRect(marquee, ctx) {
|
|
7601
|
+
const candidates = ctx.store.queryRect(marquee);
|
|
7602
|
+
const ids = [];
|
|
7603
|
+
for (const el of candidates) {
|
|
7604
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
7605
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
7606
|
+
if (el.type === "grid") continue;
|
|
7607
|
+
const bounds = getElementBounds(el);
|
|
7608
|
+
if (bounds && rectsOverlap(marquee, rotatedAABB(bounds, el.rotation ?? 0))) {
|
|
7609
|
+
ids.push(el.id);
|
|
7610
|
+
}
|
|
7611
|
+
}
|
|
7612
|
+
return ids;
|
|
7613
|
+
}
|
|
7614
|
+
function rectsOverlap(a, b) {
|
|
7615
|
+
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;
|
|
7616
|
+
}
|
|
7617
|
+
|
|
7618
|
+
// src/tools/select-resize.ts
|
|
7619
|
+
var MIN_ELEMENT_SIZE = 20;
|
|
7620
|
+
function anchorOffset(handle, w, h) {
|
|
7621
|
+
switch (handle) {
|
|
7622
|
+
case "se":
|
|
7623
|
+
return { x: -w / 2, y: -h / 2 };
|
|
7624
|
+
case "sw":
|
|
7625
|
+
return { x: w / 2, y: -h / 2 };
|
|
7626
|
+
case "ne":
|
|
7627
|
+
return { x: -w / 2, y: h / 2 };
|
|
7628
|
+
case "nw":
|
|
7629
|
+
return { x: w / 2, y: h / 2 };
|
|
7630
|
+
default:
|
|
7631
|
+
return { x: 0, y: 0 };
|
|
7632
|
+
}
|
|
7633
|
+
}
|
|
7634
|
+
function computeResize(el, handle, world, lastWorld, aspectRatio, shiftKey) {
|
|
7635
|
+
const dx = world.x - lastWorld.x;
|
|
7636
|
+
const dy = world.y - lastWorld.y;
|
|
7637
|
+
let { x, y, w, h } = { x: el.position.x, y: el.position.y, w: el.size.w, h: el.size.h };
|
|
7638
|
+
switch (handle) {
|
|
7639
|
+
case "se":
|
|
7640
|
+
w += dx;
|
|
7641
|
+
h += dy;
|
|
7642
|
+
break;
|
|
7643
|
+
case "sw":
|
|
7644
|
+
x += dx;
|
|
7645
|
+
w -= dx;
|
|
7646
|
+
h += dy;
|
|
7647
|
+
break;
|
|
7648
|
+
case "ne":
|
|
7649
|
+
y += dy;
|
|
7650
|
+
w += dx;
|
|
7651
|
+
h -= dy;
|
|
7652
|
+
break;
|
|
7653
|
+
case "nw":
|
|
7654
|
+
x += dx;
|
|
7655
|
+
y += dy;
|
|
7656
|
+
w -= dx;
|
|
7657
|
+
h -= dy;
|
|
7658
|
+
break;
|
|
7659
|
+
}
|
|
7660
|
+
if (shiftKey && aspectRatio > 0) {
|
|
7661
|
+
const absDw = Math.abs(w - el.size.w);
|
|
7662
|
+
const absDh = Math.abs(h - el.size.h);
|
|
7663
|
+
if (absDw >= absDh) {
|
|
7664
|
+
h = w / aspectRatio;
|
|
7665
|
+
} else {
|
|
7666
|
+
w = h * aspectRatio;
|
|
7667
|
+
}
|
|
7668
|
+
if (handle === "nw" || handle === "sw") {
|
|
7669
|
+
x = el.position.x + el.size.w - w;
|
|
7670
|
+
}
|
|
7671
|
+
if (handle === "nw" || handle === "ne") {
|
|
7672
|
+
y = el.position.y + el.size.h - h;
|
|
7673
|
+
}
|
|
7674
|
+
}
|
|
7675
|
+
if (w < MIN_ELEMENT_SIZE) {
|
|
7676
|
+
if (handle === "nw" || handle === "sw") x = el.position.x + el.size.w - MIN_ELEMENT_SIZE;
|
|
7677
|
+
w = MIN_ELEMENT_SIZE;
|
|
7678
|
+
}
|
|
7679
|
+
if (h < MIN_ELEMENT_SIZE) {
|
|
7680
|
+
if (handle === "nw" || handle === "ne") y = el.position.y + el.size.h - MIN_ELEMENT_SIZE;
|
|
7681
|
+
h = MIN_ELEMENT_SIZE;
|
|
7682
|
+
}
|
|
7683
|
+
return { position: { x, y }, size: { w, h } };
|
|
7684
|
+
}
|
|
7685
|
+
function computeRotatedResize(el, handle, angle, world, lastWorld, aspectRatio, shiftKey) {
|
|
7686
|
+
const wdx = world.x - lastWorld.x;
|
|
7687
|
+
const wdy = world.y - lastWorld.y;
|
|
7688
|
+
const cosN = Math.cos(-angle);
|
|
7689
|
+
const sinN = Math.sin(-angle);
|
|
7690
|
+
const ldx = wdx * cosN - wdy * sinN;
|
|
7691
|
+
const ldy = wdx * sinN + wdy * cosN;
|
|
7692
|
+
let w = el.size.w;
|
|
7693
|
+
let h = el.size.h;
|
|
7694
|
+
switch (handle) {
|
|
7695
|
+
case "se":
|
|
7696
|
+
w += ldx;
|
|
7697
|
+
h += ldy;
|
|
7698
|
+
break;
|
|
7699
|
+
case "sw":
|
|
7700
|
+
w -= ldx;
|
|
7701
|
+
h += ldy;
|
|
7702
|
+
break;
|
|
7703
|
+
case "ne":
|
|
7704
|
+
w += ldx;
|
|
7705
|
+
h -= ldy;
|
|
7706
|
+
break;
|
|
7707
|
+
case "nw":
|
|
7708
|
+
w -= ldx;
|
|
7709
|
+
h -= ldy;
|
|
7710
|
+
break;
|
|
7711
|
+
}
|
|
7712
|
+
if (shiftKey && aspectRatio > 0) {
|
|
7713
|
+
const absDw = Math.abs(w - el.size.w);
|
|
7714
|
+
const absDh = Math.abs(h - el.size.h);
|
|
7715
|
+
if (absDw >= absDh) h = w / aspectRatio;
|
|
7716
|
+
else w = h * aspectRatio;
|
|
7717
|
+
}
|
|
7718
|
+
w = Math.max(w, MIN_ELEMENT_SIZE);
|
|
7719
|
+
h = Math.max(h, MIN_ELEMENT_SIZE);
|
|
7720
|
+
const oldCenter = { x: el.position.x + el.size.w / 2, y: el.position.y + el.size.h / 2 };
|
|
7721
|
+
const oldAnchorLocal = anchorOffset(handle, el.size.w, el.size.h);
|
|
7722
|
+
const anchorWorld = rotatePoint(
|
|
7723
|
+
{ x: oldCenter.x + oldAnchorLocal.x, y: oldCenter.y + oldAnchorLocal.y },
|
|
7724
|
+
oldCenter,
|
|
7725
|
+
angle
|
|
7726
|
+
);
|
|
7727
|
+
const newAnchorLocal = anchorOffset(handle, w, h);
|
|
7728
|
+
const cos = Math.cos(angle);
|
|
7729
|
+
const sin = Math.sin(angle);
|
|
7730
|
+
const rotatedAnchor = {
|
|
7731
|
+
x: newAnchorLocal.x * cos - newAnchorLocal.y * sin,
|
|
7732
|
+
y: newAnchorLocal.x * sin + newAnchorLocal.y * cos
|
|
7733
|
+
};
|
|
7734
|
+
const newCenter = { x: anchorWorld.x - rotatedAnchor.x, y: anchorWorld.y - rotatedAnchor.y };
|
|
7735
|
+
const position = { x: newCenter.x - w / 2, y: newCenter.y - h / 2 };
|
|
7736
|
+
return { position, size: { w, h } };
|
|
7737
|
+
}
|
|
7738
|
+
function computeTemplateResize(el, world, opts) {
|
|
7739
|
+
const dx = world.x - el.position.x;
|
|
7740
|
+
const dy = world.y - el.position.y;
|
|
7741
|
+
let newRadius = Math.sqrt(dx * dx + dy * dy);
|
|
7742
|
+
if (opts.snapToGrid && opts.gridSize && opts.gridSize > 0) {
|
|
7743
|
+
const snapUnit = opts.gridType === "hex" ? Math.sqrt(3) * opts.gridSize : opts.gridSize;
|
|
7744
|
+
newRadius = Math.max(snapUnit, Math.round(newRadius / snapUnit) * snapUnit);
|
|
7745
|
+
}
|
|
7746
|
+
newRadius = Math.max(MIN_ELEMENT_SIZE, newRadius);
|
|
7747
|
+
const updates = { radius: newRadius };
|
|
7748
|
+
if (el.feetPerCell != null && opts.gridSize && opts.gridSize > 0) {
|
|
7749
|
+
const snapUnit = opts.gridType === "hex" ? Math.sqrt(3) * opts.gridSize : opts.gridSize;
|
|
7750
|
+
updates.radiusFeet = newRadius / snapUnit * el.feetPerCell;
|
|
7751
|
+
}
|
|
7752
|
+
return updates;
|
|
7753
|
+
}
|
|
7754
|
+
|
|
7755
|
+
// src/tools/select-tool.ts
|
|
7756
|
+
var SNAP_PX = 6;
|
|
7757
|
+
var ROTATE_SNAP = Math.PI / 12;
|
|
7213
7758
|
var SelectTool = class {
|
|
7214
7759
|
name = "select";
|
|
7215
7760
|
_selectedIds = [];
|
|
@@ -7245,7 +7790,7 @@ var SelectTool = class {
|
|
|
7245
7790
|
this.ctx?.requestRender();
|
|
7246
7791
|
}
|
|
7247
7792
|
selectAtPoint(world, ctx) {
|
|
7248
|
-
const hit =
|
|
7793
|
+
const hit = hitTest(world, ctx);
|
|
7249
7794
|
if (!hit) {
|
|
7250
7795
|
this.setSelectedIds([]);
|
|
7251
7796
|
return;
|
|
@@ -7289,19 +7834,19 @@ var SelectTool = class {
|
|
|
7289
7834
|
ctx.requestRender();
|
|
7290
7835
|
return;
|
|
7291
7836
|
}
|
|
7292
|
-
const lineHit =
|
|
7837
|
+
const lineHit = hitTestLineHandles(world, ctx, this._selectedIds);
|
|
7293
7838
|
if (lineHit) {
|
|
7294
7839
|
this.mode = { type: "line-handle", elementId: lineHit.elementId, fixed: lineHit.fixed };
|
|
7295
7840
|
ctx.requestRender();
|
|
7296
7841
|
return;
|
|
7297
7842
|
}
|
|
7298
|
-
const templateResizeHit =
|
|
7843
|
+
const templateResizeHit = hitTestTemplateResizeHandle(world, ctx, this._selectedIds);
|
|
7299
7844
|
if (templateResizeHit) {
|
|
7300
7845
|
this.mode = { type: "resizing-template", elementId: templateResizeHit };
|
|
7301
7846
|
ctx.requestRender();
|
|
7302
7847
|
return;
|
|
7303
7848
|
}
|
|
7304
|
-
const rotateHit =
|
|
7849
|
+
const rotateHit = hitTestRotateHandle(world, ctx, this._selectedIds);
|
|
7305
7850
|
if (rotateHit) {
|
|
7306
7851
|
const el = ctx.store.getById(rotateHit.elementId);
|
|
7307
7852
|
const layout = el ? this.getOverlayLayout(el, ctx.camera.zoom) : null;
|
|
@@ -7317,7 +7862,7 @@ var SelectTool = class {
|
|
|
7317
7862
|
return;
|
|
7318
7863
|
}
|
|
7319
7864
|
}
|
|
7320
|
-
const resizeHit =
|
|
7865
|
+
const resizeHit = hitTestResizeHandle(world, ctx, this._selectedIds);
|
|
7321
7866
|
if (resizeHit) {
|
|
7322
7867
|
const el = ctx.store.getById(resizeHit.elementId);
|
|
7323
7868
|
if (el && "size" in el) {
|
|
@@ -7333,7 +7878,7 @@ var SelectTool = class {
|
|
|
7333
7878
|
}
|
|
7334
7879
|
this.pendingSingleSelectId = null;
|
|
7335
7880
|
this.hasDragged = false;
|
|
7336
|
-
const hit =
|
|
7881
|
+
const hit = hitTest(world, ctx);
|
|
7337
7882
|
if (hit) {
|
|
7338
7883
|
const all = ctx.store.getAll();
|
|
7339
7884
|
const alreadySelected = this._selectedIds.includes(hit.id);
|
|
@@ -7471,7 +8016,7 @@ var SelectTool = class {
|
|
|
7471
8016
|
if (this.mode.type === "marquee") {
|
|
7472
8017
|
const rect = this.getMarqueeRect();
|
|
7473
8018
|
if (rect) {
|
|
7474
|
-
this.setSelectedIds(expandToGroups(
|
|
8019
|
+
this.setSelectedIds(expandToGroups(findElementsInRect(rect, ctx), ctx.store.getAll()));
|
|
7475
8020
|
}
|
|
7476
8021
|
ctx.requestRender();
|
|
7477
8022
|
}
|
|
@@ -7498,8 +8043,16 @@ var SelectTool = class {
|
|
|
7498
8043
|
this.setHovered(hoverId, ctx);
|
|
7499
8044
|
}
|
|
7500
8045
|
renderOverlay(canvasCtx) {
|
|
7501
|
-
this.
|
|
7502
|
-
|
|
8046
|
+
if (this.mode.type === "marquee") {
|
|
8047
|
+
const rect = this.getMarqueeRect();
|
|
8048
|
+
if (rect) renderMarquee(canvasCtx, rect);
|
|
8049
|
+
}
|
|
8050
|
+
if (this.ctx)
|
|
8051
|
+
renderSelectionBoxes(canvasCtx, {
|
|
8052
|
+
selectedIds: this._selectedIds,
|
|
8053
|
+
store: this.ctx.store,
|
|
8054
|
+
zoom: this.ctx.camera.zoom
|
|
8055
|
+
});
|
|
7503
8056
|
if (this.mode.type === "arrow-handle" && this.ctx) {
|
|
7504
8057
|
const target = getArrowHandleDragTarget(
|
|
7505
8058
|
this.mode.handle,
|
|
@@ -7533,32 +8086,13 @@ var SelectTool = class {
|
|
|
7533
8086
|
}
|
|
7534
8087
|
}
|
|
7535
8088
|
}
|
|
7536
|
-
this.
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
canvasCtx.strokeStyle = "#FF4081";
|
|
7544
|
-
canvasCtx.lineWidth = 1 / zoom;
|
|
7545
|
-
canvasCtx.setLineDash([]);
|
|
7546
|
-
for (const g of this.activeGuides) {
|
|
7547
|
-
canvasCtx.beginPath();
|
|
7548
|
-
if (g.axis === "x") {
|
|
7549
|
-
const y0 = rect ? rect.y : this.currentWorld.y - 1e5;
|
|
7550
|
-
const y1 = rect ? rect.y + rect.h : this.currentWorld.y + 1e5;
|
|
7551
|
-
canvasCtx.moveTo(g.position, y0);
|
|
7552
|
-
canvasCtx.lineTo(g.position, y1);
|
|
7553
|
-
} else {
|
|
7554
|
-
const x0 = rect ? rect.x : this.currentWorld.x - 1e5;
|
|
7555
|
-
const x1 = rect ? rect.x + rect.w : this.currentWorld.x + 1e5;
|
|
7556
|
-
canvasCtx.moveTo(x0, g.position);
|
|
7557
|
-
canvasCtx.lineTo(x1, g.position);
|
|
7558
|
-
}
|
|
7559
|
-
canvasCtx.stroke();
|
|
7560
|
-
}
|
|
7561
|
-
canvasCtx.restore();
|
|
8089
|
+
if (this.mode.type === "dragging" && this.ctx && this.activeGuides.length)
|
|
8090
|
+
renderGuideLines(canvasCtx, {
|
|
8091
|
+
guides: this.activeGuides,
|
|
8092
|
+
rect: this.dragVisibleRect,
|
|
8093
|
+
currentWorld: this.currentWorld,
|
|
8094
|
+
zoom: this.ctx.camera.zoom
|
|
8095
|
+
});
|
|
7562
8096
|
}
|
|
7563
8097
|
updateArrowsBoundTo(ids, ctx) {
|
|
7564
8098
|
updateArrowsBoundToElements(ids, ctx.store);
|
|
@@ -7584,25 +8118,25 @@ var SelectTool = class {
|
|
|
7584
8118
|
ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
|
|
7585
8119
|
return null;
|
|
7586
8120
|
}
|
|
7587
|
-
if (
|
|
8121
|
+
if (hitTestLineHandles(world, ctx, this._selectedIds)) {
|
|
7588
8122
|
ctx.setCursor?.("grab");
|
|
7589
8123
|
return null;
|
|
7590
8124
|
}
|
|
7591
|
-
const templateResizeHit =
|
|
8125
|
+
const templateResizeHit = hitTestTemplateResizeHandle(world, ctx, this._selectedIds);
|
|
7592
8126
|
if (templateResizeHit) {
|
|
7593
8127
|
ctx.setCursor?.("nwse-resize");
|
|
7594
8128
|
return null;
|
|
7595
8129
|
}
|
|
7596
|
-
if (
|
|
8130
|
+
if (hitTestRotateHandle(world, ctx, this._selectedIds)) {
|
|
7597
8131
|
ctx.setCursor?.("grab");
|
|
7598
8132
|
return null;
|
|
7599
8133
|
}
|
|
7600
|
-
const resizeHit =
|
|
8134
|
+
const resizeHit = hitTestResizeHandle(world, ctx, this._selectedIds);
|
|
7601
8135
|
if (resizeHit) {
|
|
7602
8136
|
ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
|
|
7603
8137
|
return null;
|
|
7604
8138
|
}
|
|
7605
|
-
const hit =
|
|
8139
|
+
const hit = hitTest(world, ctx);
|
|
7606
8140
|
ctx.setCursor?.(hit ? "move" : "default");
|
|
7607
8141
|
return hit ? hit.id : null;
|
|
7608
8142
|
}
|
|
@@ -7616,421 +8150,43 @@ var SelectTool = class {
|
|
|
7616
8150
|
const el = ctx.store.getById(this.mode.elementId);
|
|
7617
8151
|
if (!el || !("size" in el) || el.locked) return;
|
|
7618
8152
|
const angle = el.rotation ?? 0;
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
x += dx;
|
|
7635
|
-
w -= dx;
|
|
7636
|
-
h += dy;
|
|
7637
|
-
break;
|
|
7638
|
-
case "ne":
|
|
7639
|
-
y += dy;
|
|
7640
|
-
w += dx;
|
|
7641
|
-
h -= dy;
|
|
7642
|
-
break;
|
|
7643
|
-
case "nw":
|
|
7644
|
-
x += dx;
|
|
7645
|
-
y += dy;
|
|
7646
|
-
w -= dx;
|
|
7647
|
-
h -= dy;
|
|
7648
|
-
break;
|
|
7649
|
-
}
|
|
7650
|
-
if (shiftKey && this.resizeAspectRatio > 0) {
|
|
7651
|
-
const absDw = Math.abs(w - el.size.w);
|
|
7652
|
-
const absDh = Math.abs(h - el.size.h);
|
|
7653
|
-
if (absDw >= absDh) {
|
|
7654
|
-
h = w / this.resizeAspectRatio;
|
|
7655
|
-
} else {
|
|
7656
|
-
w = h * this.resizeAspectRatio;
|
|
7657
|
-
}
|
|
7658
|
-
if (handle === "nw" || handle === "sw") {
|
|
7659
|
-
x = el.position.x + el.size.w - w;
|
|
7660
|
-
}
|
|
7661
|
-
if (handle === "nw" || handle === "ne") {
|
|
7662
|
-
y = el.position.y + el.size.h - h;
|
|
7663
|
-
}
|
|
7664
|
-
}
|
|
7665
|
-
if (w < MIN_ELEMENT_SIZE) {
|
|
7666
|
-
if (handle === "nw" || handle === "sw") x = el.position.x + el.size.w - MIN_ELEMENT_SIZE;
|
|
7667
|
-
w = MIN_ELEMENT_SIZE;
|
|
7668
|
-
}
|
|
7669
|
-
if (h < MIN_ELEMENT_SIZE) {
|
|
7670
|
-
if (handle === "nw" || handle === "ne") y = el.position.y + el.size.h - MIN_ELEMENT_SIZE;
|
|
7671
|
-
h = MIN_ELEMENT_SIZE;
|
|
7672
|
-
}
|
|
7673
|
-
ctx.store.update(this.mode.elementId, {
|
|
7674
|
-
position: { x, y },
|
|
7675
|
-
size: { w, h }
|
|
7676
|
-
});
|
|
7677
|
-
this.updateArrowsBoundTo([this.mode.elementId], ctx);
|
|
7678
|
-
ctx.requestRender();
|
|
7679
|
-
}
|
|
7680
|
-
anchorOffset(handle, w, h) {
|
|
7681
|
-
switch (handle) {
|
|
7682
|
-
case "se":
|
|
7683
|
-
return { x: -w / 2, y: -h / 2 };
|
|
7684
|
-
case "sw":
|
|
7685
|
-
return { x: w / 2, y: -h / 2 };
|
|
7686
|
-
case "ne":
|
|
7687
|
-
return { x: -w / 2, y: h / 2 };
|
|
7688
|
-
case "nw":
|
|
7689
|
-
return { x: w / 2, y: h / 2 };
|
|
7690
|
-
default:
|
|
7691
|
-
return { x: 0, y: 0 };
|
|
7692
|
-
}
|
|
7693
|
-
}
|
|
7694
|
-
handleRotatedResize(world, el, angle, ctx, shiftKey) {
|
|
7695
|
-
if (this.mode.type !== "resizing") return;
|
|
7696
|
-
const { handle } = this.mode;
|
|
7697
|
-
const wdx = world.x - this.lastWorld.x;
|
|
7698
|
-
const wdy = world.y - this.lastWorld.y;
|
|
7699
|
-
this.lastWorld = world;
|
|
7700
|
-
const cosN = Math.cos(-angle);
|
|
7701
|
-
const sinN = Math.sin(-angle);
|
|
7702
|
-
const ldx = wdx * cosN - wdy * sinN;
|
|
7703
|
-
const ldy = wdx * sinN + wdy * cosN;
|
|
7704
|
-
let w = el.size.w;
|
|
7705
|
-
let h = el.size.h;
|
|
7706
|
-
switch (handle) {
|
|
7707
|
-
case "se":
|
|
7708
|
-
w += ldx;
|
|
7709
|
-
h += ldy;
|
|
7710
|
-
break;
|
|
7711
|
-
case "sw":
|
|
7712
|
-
w -= ldx;
|
|
7713
|
-
h += ldy;
|
|
7714
|
-
break;
|
|
7715
|
-
case "ne":
|
|
7716
|
-
w += ldx;
|
|
7717
|
-
h -= ldy;
|
|
7718
|
-
break;
|
|
7719
|
-
case "nw":
|
|
7720
|
-
w -= ldx;
|
|
7721
|
-
h -= ldy;
|
|
7722
|
-
break;
|
|
7723
|
-
}
|
|
7724
|
-
if (shiftKey && this.resizeAspectRatio > 0) {
|
|
7725
|
-
const absDw = Math.abs(w - el.size.w);
|
|
7726
|
-
const absDh = Math.abs(h - el.size.h);
|
|
7727
|
-
if (absDw >= absDh) h = w / this.resizeAspectRatio;
|
|
7728
|
-
else w = h * this.resizeAspectRatio;
|
|
7729
|
-
}
|
|
7730
|
-
w = Math.max(w, MIN_ELEMENT_SIZE);
|
|
7731
|
-
h = Math.max(h, MIN_ELEMENT_SIZE);
|
|
7732
|
-
const oldCenter = { x: el.position.x + el.size.w / 2, y: el.position.y + el.size.h / 2 };
|
|
7733
|
-
const oldAnchorLocal = this.anchorOffset(handle, el.size.w, el.size.h);
|
|
7734
|
-
const anchorWorld = rotatePoint(
|
|
7735
|
-
{ x: oldCenter.x + oldAnchorLocal.x, y: oldCenter.y + oldAnchorLocal.y },
|
|
7736
|
-
oldCenter,
|
|
7737
|
-
angle
|
|
8153
|
+
const patch = angle !== 0 ? computeRotatedResize(
|
|
8154
|
+
el,
|
|
8155
|
+
this.mode.handle,
|
|
8156
|
+
angle,
|
|
8157
|
+
world,
|
|
8158
|
+
this.lastWorld,
|
|
8159
|
+
this.resizeAspectRatio,
|
|
8160
|
+
shiftKey
|
|
8161
|
+
) : computeResize(
|
|
8162
|
+
el,
|
|
8163
|
+
this.mode.handle,
|
|
8164
|
+
world,
|
|
8165
|
+
this.lastWorld,
|
|
8166
|
+
this.resizeAspectRatio,
|
|
8167
|
+
shiftKey
|
|
7738
8168
|
);
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
const sin = Math.sin(angle);
|
|
7742
|
-
const rotatedAnchor = {
|
|
7743
|
-
x: newAnchorLocal.x * cos - newAnchorLocal.y * sin,
|
|
7744
|
-
y: newAnchorLocal.x * sin + newAnchorLocal.y * cos
|
|
7745
|
-
};
|
|
7746
|
-
const newCenter = { x: anchorWorld.x - rotatedAnchor.x, y: anchorWorld.y - rotatedAnchor.y };
|
|
7747
|
-
const position = { x: newCenter.x - w / 2, y: newCenter.y - h / 2 };
|
|
7748
|
-
ctx.store.update(this.mode.elementId, { position, size: { w, h } });
|
|
8169
|
+
this.lastWorld = world;
|
|
8170
|
+
ctx.store.update(this.mode.elementId, patch);
|
|
7749
8171
|
this.updateArrowsBoundTo([this.mode.elementId], ctx);
|
|
7750
8172
|
ctx.requestRender();
|
|
7751
8173
|
}
|
|
7752
|
-
hitTestResizeHandle(world, ctx) {
|
|
7753
|
-
if (this._selectedIds.length === 0) return null;
|
|
7754
|
-
const zoom = ctx.camera.zoom;
|
|
7755
|
-
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7756
|
-
for (const id of this._selectedIds) {
|
|
7757
|
-
const el = ctx.store.getById(id);
|
|
7758
|
-
if (!el || !("size" in el)) continue;
|
|
7759
|
-
if (el.locked) continue;
|
|
7760
|
-
if (el.type === "shape" && el.shape === "line") continue;
|
|
7761
|
-
const layout = this.getOverlayLayout(el, zoom);
|
|
7762
|
-
if (!layout) continue;
|
|
7763
|
-
for (const [handle, pos] of layout.corners) {
|
|
7764
|
-
if (Math.abs(world.x - pos.x) <= handleHalf && Math.abs(world.y - pos.y) <= handleHalf) {
|
|
7765
|
-
return { elementId: id, handle };
|
|
7766
|
-
}
|
|
7767
|
-
}
|
|
7768
|
-
}
|
|
7769
|
-
return null;
|
|
7770
|
-
}
|
|
7771
|
-
hitTestRotateHandle(world, ctx) {
|
|
7772
|
-
if (this._selectedIds.length !== 1) return null;
|
|
7773
|
-
const id = this._selectedIds[0];
|
|
7774
|
-
if (!id) return null;
|
|
7775
|
-
const el = ctx.store.getById(id);
|
|
7776
|
-
if (!el || el.locked || !ROTATABLE_TYPES.has(el.type)) return null;
|
|
7777
|
-
const layout = this.getOverlayLayout(el, ctx.camera.zoom);
|
|
7778
|
-
if (!layout) return null;
|
|
7779
|
-
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / ctx.camera.zoom;
|
|
7780
|
-
const dx = world.x - layout.rotateHandle.x;
|
|
7781
|
-
const dy = world.y - layout.rotateHandle.y;
|
|
7782
|
-
return dx * dx + dy * dy <= r * r ? { elementId: id } : null;
|
|
7783
|
-
}
|
|
7784
|
-
hitTestLineHandles(world, ctx) {
|
|
7785
|
-
if (this._selectedIds.length === 0) return null;
|
|
7786
|
-
const zoom = ctx.camera.zoom;
|
|
7787
|
-
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7788
|
-
const r2 = r * r;
|
|
7789
|
-
for (const id of this._selectedIds) {
|
|
7790
|
-
const el = ctx.store.getById(id);
|
|
7791
|
-
if (!el || el.type !== "shape" || el.shape !== "line") continue;
|
|
7792
|
-
const [a, b] = lineEndpoints(el);
|
|
7793
|
-
if ((world.x - a.x) ** 2 + (world.y - a.y) ** 2 <= r2) return { elementId: id, fixed: b };
|
|
7794
|
-
if ((world.x - b.x) ** 2 + (world.y - b.y) ** 2 <= r2) return { elementId: id, fixed: a };
|
|
7795
|
-
}
|
|
7796
|
-
return null;
|
|
7797
|
-
}
|
|
7798
|
-
getHandlePositions(bounds) {
|
|
7799
|
-
return [
|
|
7800
|
-
["nw", { x: bounds.x, y: bounds.y }],
|
|
7801
|
-
["ne", { x: bounds.x + bounds.w, y: bounds.y }],
|
|
7802
|
-
["sw", { x: bounds.x, y: bounds.y + bounds.h }],
|
|
7803
|
-
["se", { x: bounds.x + bounds.w, y: bounds.y + bounds.h }]
|
|
7804
|
-
];
|
|
7805
|
-
}
|
|
7806
8174
|
getOverlayLayout(el, zoom) {
|
|
7807
|
-
|
|
7808
|
-
if (!bounds) return null;
|
|
7809
|
-
const angle = el.rotation ?? 0;
|
|
7810
|
-
const pad = SELECTION_PAD / zoom;
|
|
7811
|
-
const center2 = { x: bounds.x + bounds.w / 2, y: bounds.y + bounds.h / 2 };
|
|
7812
|
-
const raw = [
|
|
7813
|
-
["nw", { x: bounds.x - pad, y: bounds.y - pad }],
|
|
7814
|
-
["ne", { x: bounds.x + bounds.w + pad, y: bounds.y - pad }],
|
|
7815
|
-
["sw", { x: bounds.x - pad, y: bounds.y + bounds.h + pad }],
|
|
7816
|
-
["se", { x: bounds.x + bounds.w + pad, y: bounds.y + bounds.h + pad }]
|
|
7817
|
-
];
|
|
7818
|
-
const corners = raw.map(
|
|
7819
|
-
([h, p]) => [h, rotatePoint(p, center2, angle)]
|
|
7820
|
-
);
|
|
7821
|
-
const topMid = { x: center2.x, y: bounds.y - pad - ROTATE_HANDLE_OFFSET / zoom };
|
|
7822
|
-
const rotateHandle = rotatePoint(topMid, center2, angle);
|
|
7823
|
-
return { center: center2, corners, rotateHandle, angle };
|
|
7824
|
-
}
|
|
7825
|
-
topMidpoint(layout) {
|
|
7826
|
-
const nw = layout.corners.find(([h]) => h === "nw")?.[1] ?? { x: 0, y: 0 };
|
|
7827
|
-
const ne = layout.corners.find(([h]) => h === "ne")?.[1] ?? { x: 0, y: 0 };
|
|
7828
|
-
return { x: (nw.x + ne.x) / 2, y: (nw.y + ne.y) / 2 };
|
|
7829
|
-
}
|
|
7830
|
-
renderMarquee(canvasCtx) {
|
|
7831
|
-
if (this.mode.type !== "marquee") return;
|
|
7832
|
-
const rect = this.getMarqueeRect();
|
|
7833
|
-
if (!rect) return;
|
|
7834
|
-
canvasCtx.save();
|
|
7835
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7836
|
-
canvasCtx.fillStyle = "rgba(33, 150, 243, 0.08)";
|
|
7837
|
-
canvasCtx.lineWidth = 1;
|
|
7838
|
-
canvasCtx.setLineDash([4, 4]);
|
|
7839
|
-
canvasCtx.strokeRect(rect.x, rect.y, rect.w, rect.h);
|
|
7840
|
-
canvasCtx.fillRect(rect.x, rect.y, rect.w, rect.h);
|
|
7841
|
-
canvasCtx.restore();
|
|
7842
|
-
}
|
|
7843
|
-
renderSelectionBoxes(canvasCtx) {
|
|
7844
|
-
if (this._selectedIds.length === 0 || !this.ctx) return;
|
|
7845
|
-
const zoom = this.ctx.camera.zoom;
|
|
7846
|
-
const handleWorldSize = HANDLE_SIZE / zoom;
|
|
7847
|
-
canvasCtx.save();
|
|
7848
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7849
|
-
canvasCtx.lineWidth = 1.5 / zoom;
|
|
7850
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7851
|
-
for (const id of this._selectedIds) {
|
|
7852
|
-
const el = this.ctx.store.getById(id);
|
|
7853
|
-
if (!el) continue;
|
|
7854
|
-
if (el.type === "arrow") {
|
|
7855
|
-
renderArrowHandles(canvasCtx, el, zoom);
|
|
7856
|
-
this.renderBindingHighlights(canvasCtx, el, zoom);
|
|
7857
|
-
continue;
|
|
7858
|
-
}
|
|
7859
|
-
if (el.type === "shape" && el.shape === "line") {
|
|
7860
|
-
canvasCtx.setLineDash([]);
|
|
7861
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7862
|
-
const r = handleWorldSize / 2;
|
|
7863
|
-
for (const p of lineEndpoints(el)) {
|
|
7864
|
-
canvasCtx.beginPath();
|
|
7865
|
-
canvasCtx.arc(p.x, p.y, r, 0, Math.PI * 2);
|
|
7866
|
-
canvasCtx.fill();
|
|
7867
|
-
canvasCtx.stroke();
|
|
7868
|
-
}
|
|
7869
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7870
|
-
continue;
|
|
7871
|
-
}
|
|
7872
|
-
const bounds = getElementBounds(el);
|
|
7873
|
-
if (!bounds) continue;
|
|
7874
|
-
const layout = this.getOverlayLayout(el, zoom);
|
|
7875
|
-
if (!layout) continue;
|
|
7876
|
-
const pad = SELECTION_PAD / zoom;
|
|
7877
|
-
if (layout.angle === 0) {
|
|
7878
|
-
canvasCtx.strokeRect(
|
|
7879
|
-
bounds.x - pad,
|
|
7880
|
-
bounds.y - pad,
|
|
7881
|
-
bounds.w + pad * 2,
|
|
7882
|
-
bounds.h + pad * 2
|
|
7883
|
-
);
|
|
7884
|
-
} else {
|
|
7885
|
-
const ordered = ["nw", "ne", "se", "sw"].map((h) => layout.corners.find(([c]) => c === h)?.[1]).filter((p) => !!p);
|
|
7886
|
-
const [p0, ...others] = ordered;
|
|
7887
|
-
if (p0) {
|
|
7888
|
-
canvasCtx.beginPath();
|
|
7889
|
-
canvasCtx.moveTo(p0.x, p0.y);
|
|
7890
|
-
for (const p of others) canvasCtx.lineTo(p.x, p.y);
|
|
7891
|
-
canvasCtx.closePath();
|
|
7892
|
-
canvasCtx.stroke();
|
|
7893
|
-
}
|
|
7894
|
-
}
|
|
7895
|
-
if (!el.locked) {
|
|
7896
|
-
if ("size" in el) {
|
|
7897
|
-
canvasCtx.setLineDash([]);
|
|
7898
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7899
|
-
const corners = layout.angle === 0 ? this.getHandlePositions(bounds) : layout.corners;
|
|
7900
|
-
for (const [, pos] of corners) {
|
|
7901
|
-
canvasCtx.fillRect(
|
|
7902
|
-
pos.x - handleWorldSize / 2,
|
|
7903
|
-
pos.y - handleWorldSize / 2,
|
|
7904
|
-
handleWorldSize,
|
|
7905
|
-
handleWorldSize
|
|
7906
|
-
);
|
|
7907
|
-
canvasCtx.strokeRect(
|
|
7908
|
-
pos.x - handleWorldSize / 2,
|
|
7909
|
-
pos.y - handleWorldSize / 2,
|
|
7910
|
-
handleWorldSize,
|
|
7911
|
-
handleWorldSize
|
|
7912
|
-
);
|
|
7913
|
-
}
|
|
7914
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7915
|
-
} else if (el.type === "template") {
|
|
7916
|
-
canvasCtx.setLineDash([]);
|
|
7917
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7918
|
-
const hx = bounds.x + bounds.w;
|
|
7919
|
-
const hy = bounds.y + bounds.h;
|
|
7920
|
-
canvasCtx.fillRect(
|
|
7921
|
-
hx - handleWorldSize / 2,
|
|
7922
|
-
hy - handleWorldSize / 2,
|
|
7923
|
-
handleWorldSize,
|
|
7924
|
-
handleWorldSize
|
|
7925
|
-
);
|
|
7926
|
-
canvasCtx.strokeRect(
|
|
7927
|
-
hx - handleWorldSize / 2,
|
|
7928
|
-
hy - handleWorldSize / 2,
|
|
7929
|
-
handleWorldSize,
|
|
7930
|
-
handleWorldSize
|
|
7931
|
-
);
|
|
7932
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7933
|
-
}
|
|
7934
|
-
if (this._selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
|
|
7935
|
-
const stemStart = this.topMidpoint(layout);
|
|
7936
|
-
const stemEnd = layout.rotateHandle;
|
|
7937
|
-
canvasCtx.beginPath();
|
|
7938
|
-
canvasCtx.moveTo(stemStart.x, stemStart.y);
|
|
7939
|
-
canvasCtx.lineTo(stemEnd.x, stemEnd.y);
|
|
7940
|
-
canvasCtx.stroke();
|
|
7941
|
-
canvasCtx.setLineDash([]);
|
|
7942
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7943
|
-
canvasCtx.beginPath();
|
|
7944
|
-
canvasCtx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7945
|
-
canvasCtx.fill();
|
|
7946
|
-
canvasCtx.stroke();
|
|
7947
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7948
|
-
}
|
|
7949
|
-
}
|
|
7950
|
-
if (el.locked) {
|
|
7951
|
-
const ne = layout.corners.find(([h]) => h === "ne")?.[1];
|
|
7952
|
-
if (ne) this.drawLockBadge(canvasCtx, ne, zoom);
|
|
7953
|
-
}
|
|
7954
|
-
}
|
|
7955
|
-
canvasCtx.restore();
|
|
7956
|
-
}
|
|
7957
|
-
drawLockBadge(ctx, at, zoom) {
|
|
7958
|
-
const r = 9 / zoom;
|
|
7959
|
-
ctx.save();
|
|
7960
|
-
ctx.setLineDash([]);
|
|
7961
|
-
ctx.beginPath();
|
|
7962
|
-
ctx.arc(at.x, at.y, r, 0, Math.PI * 2);
|
|
7963
|
-
ctx.fillStyle = "#ffffff";
|
|
7964
|
-
ctx.fill();
|
|
7965
|
-
ctx.strokeStyle = "#2196F3";
|
|
7966
|
-
ctx.lineWidth = 1.5 / zoom;
|
|
7967
|
-
ctx.stroke();
|
|
7968
|
-
const bw = 8 / zoom;
|
|
7969
|
-
const bh = 6 / zoom;
|
|
7970
|
-
ctx.fillStyle = "#2196F3";
|
|
7971
|
-
ctx.fillRect(at.x - bw / 2, at.y - bh / 2 + 1 / zoom, bw, bh);
|
|
7972
|
-
ctx.beginPath();
|
|
7973
|
-
ctx.arc(at.x, at.y - bh / 2 + 1 / zoom, 2.5 / zoom, Math.PI, 0);
|
|
7974
|
-
ctx.lineWidth = 1.4 / zoom;
|
|
7975
|
-
ctx.stroke();
|
|
7976
|
-
ctx.restore();
|
|
7977
|
-
}
|
|
7978
|
-
renderBindingHighlights(canvasCtx, arrow, zoom) {
|
|
7979
|
-
if (!this.ctx) return;
|
|
7980
|
-
if (!arrow.fromBinding && !arrow.toBinding) return;
|
|
7981
|
-
const pad = SELECTION_PAD / zoom;
|
|
7982
|
-
canvasCtx.save();
|
|
7983
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7984
|
-
canvasCtx.lineWidth = 2 / zoom;
|
|
7985
|
-
canvasCtx.setLineDash([]);
|
|
7986
|
-
const drawn = /* @__PURE__ */ new Set();
|
|
7987
|
-
for (const binding of [arrow.fromBinding, arrow.toBinding]) {
|
|
7988
|
-
if (!binding || drawn.has(binding.elementId)) continue;
|
|
7989
|
-
drawn.add(binding.elementId);
|
|
7990
|
-
const target = this.ctx.store.getById(binding.elementId);
|
|
7991
|
-
if (!target) continue;
|
|
7992
|
-
const bounds = getElementBounds(target);
|
|
7993
|
-
if (!bounds) continue;
|
|
7994
|
-
canvasCtx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
7995
|
-
}
|
|
7996
|
-
canvasCtx.restore();
|
|
7997
|
-
}
|
|
7998
|
-
hitTestTemplateResizeHandle(world, ctx) {
|
|
7999
|
-
if (this._selectedIds.length === 0) return null;
|
|
8000
|
-
const zoom = ctx.camera.zoom;
|
|
8001
|
-
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
8002
|
-
for (const id of this._selectedIds) {
|
|
8003
|
-
const el = ctx.store.getById(id);
|
|
8004
|
-
if (!el || el.type !== "template") continue;
|
|
8005
|
-
const bounds = getElementBounds(el);
|
|
8006
|
-
if (!bounds) continue;
|
|
8007
|
-
const hx = bounds.x + bounds.w;
|
|
8008
|
-
const hy = bounds.y + bounds.h;
|
|
8009
|
-
if (Math.abs(world.x - hx) <= handleHalf && Math.abs(world.y - hy) <= handleHalf) {
|
|
8010
|
-
return id;
|
|
8011
|
-
}
|
|
8012
|
-
}
|
|
8013
|
-
return null;
|
|
8175
|
+
return getOverlayLayout(el, zoom);
|
|
8014
8176
|
}
|
|
8015
8177
|
handleTemplateResize(world, ctx) {
|
|
8016
8178
|
if (this.mode.type !== "resizing-template") return;
|
|
8017
8179
|
const el = ctx.store.getById(this.mode.elementId);
|
|
8018
8180
|
if (!el || el.type !== "template" || el.locked) return;
|
|
8019
|
-
const
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
if (el.feetPerCell != null && ctx.gridSize && ctx.gridSize > 0) {
|
|
8029
|
-
const snapUnit = ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize;
|
|
8030
|
-
updates.radiusFeet = newRadius / snapUnit * el.feetPerCell;
|
|
8031
|
-
}
|
|
8032
|
-
ctx.store.update(this.mode.elementId, updates);
|
|
8033
|
-
ctx.requestRender();
|
|
8181
|
+
const patch = computeTemplateResize(el, world, {
|
|
8182
|
+
snapToGrid: ctx.snapToGrid,
|
|
8183
|
+
gridSize: ctx.gridSize,
|
|
8184
|
+
gridType: ctx.gridType
|
|
8185
|
+
});
|
|
8186
|
+
if (patch) {
|
|
8187
|
+
ctx.store.update(this.mode.elementId, patch);
|
|
8188
|
+
ctx.requestRender();
|
|
8189
|
+
}
|
|
8034
8190
|
}
|
|
8035
8191
|
getMarqueeRect() {
|
|
8036
8192
|
if (this.mode.type !== "marquee") return null;
|
|
@@ -8043,65 +8199,6 @@ var SelectTool = class {
|
|
|
8043
8199
|
if (w === 0 && h === 0) return null;
|
|
8044
8200
|
return { x, y, w, h };
|
|
8045
8201
|
}
|
|
8046
|
-
findElementsInRect(marquee, ctx) {
|
|
8047
|
-
const candidates = ctx.store.queryRect(marquee);
|
|
8048
|
-
const ids = [];
|
|
8049
|
-
for (const el of candidates) {
|
|
8050
|
-
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
8051
|
-
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
8052
|
-
if (el.type === "grid") continue;
|
|
8053
|
-
const bounds = getElementBounds(el);
|
|
8054
|
-
if (bounds && this.rectsOverlap(marquee, rotatedAABB(bounds, el.rotation ?? 0))) {
|
|
8055
|
-
ids.push(el.id);
|
|
8056
|
-
}
|
|
8057
|
-
}
|
|
8058
|
-
return ids;
|
|
8059
|
-
}
|
|
8060
|
-
rectsOverlap(a, b) {
|
|
8061
|
-
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;
|
|
8062
|
-
}
|
|
8063
|
-
hitTest(world, ctx) {
|
|
8064
|
-
const r = 10;
|
|
8065
|
-
const candidates = ctx.store.queryRect({ x: world.x - r, y: world.y - r, w: r * 2, h: r * 2 }).reverse();
|
|
8066
|
-
for (const el of candidates) {
|
|
8067
|
-
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
8068
|
-
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
8069
|
-
if (el.type === "grid") continue;
|
|
8070
|
-
if (this.isInsideBounds(world, el)) return el;
|
|
8071
|
-
}
|
|
8072
|
-
return null;
|
|
8073
|
-
}
|
|
8074
|
-
isInsideBounds(point, el) {
|
|
8075
|
-
if (el.type === "grid") return false;
|
|
8076
|
-
const angle = el.rotation ?? 0;
|
|
8077
|
-
if (angle !== 0) {
|
|
8078
|
-
const b = getElementBounds(el);
|
|
8079
|
-
if (b) {
|
|
8080
|
-
point = rotatePoint(point, { x: b.x + b.w / 2, y: b.y + b.h / 2 }, -angle);
|
|
8081
|
-
}
|
|
8082
|
-
}
|
|
8083
|
-
if (el.type === "shape" && el.shape === "line") {
|
|
8084
|
-
const [a, b] = lineEndpoints(el);
|
|
8085
|
-
const threshold = Math.max(el.strokeWidth / 2, 6);
|
|
8086
|
-
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
8087
|
-
}
|
|
8088
|
-
if ("size" in el) {
|
|
8089
|
-
const s = el.size;
|
|
8090
|
-
return point.x >= el.position.x && point.x <= el.position.x + s.w && point.y >= el.position.y && point.y <= el.position.y + s.h;
|
|
8091
|
-
}
|
|
8092
|
-
if (el.type === "stroke") {
|
|
8093
|
-
return hitTestStroke(el, point, 10);
|
|
8094
|
-
}
|
|
8095
|
-
if (el.type === "arrow") {
|
|
8096
|
-
return isNearBezier(point, el.from, el.to, el.bend, 10);
|
|
8097
|
-
}
|
|
8098
|
-
if (el.type === "template") {
|
|
8099
|
-
const bounds = getElementBounds(el);
|
|
8100
|
-
if (!bounds) return false;
|
|
8101
|
-
return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
|
|
8102
|
-
}
|
|
8103
|
-
return false;
|
|
8104
|
-
}
|
|
8105
8202
|
};
|
|
8106
8203
|
|
|
8107
8204
|
// src/tools/arrow-tool.ts
|
|
@@ -8947,7 +9044,7 @@ var TemplateTool = class {
|
|
|
8947
9044
|
};
|
|
8948
9045
|
|
|
8949
9046
|
// src/index.ts
|
|
8950
|
-
var VERSION = "0.38.
|
|
9047
|
+
var VERSION = "0.38.2";
|
|
8951
9048
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8952
9049
|
0 && (module.exports = {
|
|
8953
9050
|
ArrowTool,
|