@fieldnotes/core 0.56.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +567 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +226 -35
- package/dist/index.d.ts +226 -35
- package/dist/index.js +562 -143
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -34,14 +34,17 @@ __export(index_exports, {
|
|
|
34
34
|
LaserTool: () => LaserTool,
|
|
35
35
|
LayerManager: () => LayerManager,
|
|
36
36
|
LocalStorageAdapter: () => LocalStorageAdapter,
|
|
37
|
+
MEASURE_PRESENCE_KIND: () => MEASURE_PRESENCE_KIND,
|
|
37
38
|
MeasureTool: () => MeasureTool,
|
|
38
39
|
MemoryAdapter: () => MemoryAdapter,
|
|
40
|
+
MinimapController: () => MinimapController,
|
|
39
41
|
NoteTool: () => NoteTool,
|
|
40
42
|
PING_PRESENCE_KIND: () => PING_PRESENCE_KIND,
|
|
41
43
|
PencilTool: () => PencilTool,
|
|
42
44
|
PingInput: () => PingInput,
|
|
43
45
|
PingTool: () => PingTool,
|
|
44
46
|
RemoteLaserOverlay: () => RemoteLaserOverlay,
|
|
47
|
+
RemoteMeasureOverlay: () => RemoteMeasureOverlay,
|
|
45
48
|
RemotePingOverlay: () => RemotePingOverlay,
|
|
46
49
|
SelectTool: () => SelectTool,
|
|
47
50
|
ShapeTool: () => ShapeTool,
|
|
@@ -79,6 +82,7 @@ __export(index_exports, {
|
|
|
79
82
|
getHexCellsInSquare: () => getHexCellsInSquare,
|
|
80
83
|
getHexDistance: () => getHexDistance,
|
|
81
84
|
isLaserTrailPresence: () => isLaserTrailPresence,
|
|
85
|
+
isMeasurePresence: () => isMeasurePresence,
|
|
82
86
|
isNearBezier: () => isNearBezier,
|
|
83
87
|
isPingPresence: () => isPingPresence,
|
|
84
88
|
setFontSize: () => setFontSize,
|
|
@@ -87,6 +91,7 @@ __export(index_exports, {
|
|
|
87
91
|
snapToHexCenter: () => snapToHexCenter,
|
|
88
92
|
styleToPatch: () => styleToPatch,
|
|
89
93
|
toLaserTrailPresence: () => toLaserTrailPresence,
|
|
94
|
+
toMeasurePresence: () => toMeasurePresence,
|
|
90
95
|
toPingPresence: () => toPingPresence,
|
|
91
96
|
toggleBold: () => toggleBold,
|
|
92
97
|
toggleItalic: () => toggleItalic,
|
|
@@ -4792,88 +4797,247 @@ function miniToWorld(t, p) {
|
|
|
4792
4797
|
return { x: (p.x - t.offsetX) / t.scale, y: (p.y - t.offsetY) / t.scale };
|
|
4793
4798
|
}
|
|
4794
4799
|
|
|
4795
|
-
// src/canvas/minimap.ts
|
|
4796
|
-
var
|
|
4797
|
-
var
|
|
4798
|
-
var
|
|
4799
|
-
var
|
|
4800
|
+
// src/canvas/minimap-controller.ts
|
|
4801
|
+
var DEFAULT_WIDTH = 200;
|
|
4802
|
+
var DEFAULT_HEIGHT = 140;
|
|
4803
|
+
var DEFAULT_PADDING = 8;
|
|
4804
|
+
var DEFAULT_DEBOUNCE_MS2 = 200;
|
|
4805
|
+
var DEFAULT_VIEWPORT_STROKE = "#3b82f6";
|
|
4800
4806
|
var NEUTRAL = "rgba(100,116,139,0.6)";
|
|
4801
|
-
var
|
|
4807
|
+
var DOM_FALLBACK_TYPES = /* @__PURE__ */ new Set(["note", "text", "html"]);
|
|
4802
4808
|
function elementColor(el) {
|
|
4803
4809
|
return "color" in el && typeof el.color === "string" ? el.color : NEUTRAL;
|
|
4804
4810
|
}
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
Object.assign(canvas.style, {
|
|
4812
|
-
position: "absolute",
|
|
4813
|
-
right: `${MARGIN}px`,
|
|
4814
|
-
bottom: `${MARGIN}px`,
|
|
4815
|
-
width: `${WIDTH}px`,
|
|
4816
|
-
height: `${HEIGHT}px`,
|
|
4817
|
-
background: "rgba(255,255,255,0.85)",
|
|
4818
|
-
border: "1px solid rgba(0,0,0,0.15)",
|
|
4819
|
-
borderRadius: "4px",
|
|
4820
|
-
touchAction: "none",
|
|
4821
|
-
cursor: "pointer",
|
|
4822
|
-
zIndex: "10"
|
|
4823
|
-
});
|
|
4824
|
-
canvas.addEventListener("pointerdown", this.onPointerDown);
|
|
4825
|
-
canvas.addEventListener("pointermove", this.onPointerMove);
|
|
4826
|
-
canvas.addEventListener("pointerup", this.onPointerUp);
|
|
4827
|
-
this.deps.container.appendChild(canvas);
|
|
4811
|
+
function sameBounds(a, b) {
|
|
4812
|
+
return a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;
|
|
4813
|
+
}
|
|
4814
|
+
var MinimapController = class {
|
|
4815
|
+
constructor(viewport, canvas, options = {}) {
|
|
4816
|
+
this.viewport = viewport;
|
|
4828
4817
|
this.canvas = canvas;
|
|
4818
|
+
this.width = options.width ?? DEFAULT_WIDTH;
|
|
4819
|
+
this.height = options.height ?? DEFAULT_HEIGHT;
|
|
4820
|
+
this.padding = options.padding ?? DEFAULT_PADDING;
|
|
4821
|
+
this.background = options.background ?? null;
|
|
4822
|
+
this.viewportStroke = options.viewportStroke ?? DEFAULT_VIEWPORT_STROKE;
|
|
4823
|
+
this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS2;
|
|
4824
|
+
this.interactive = options.interactive !== false;
|
|
4825
|
+
this.requestFrame = options.requestFrame ?? ((cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : 0);
|
|
4826
|
+
this.cancelFrame = options.cancelFrame ?? ((id) => {
|
|
4827
|
+
if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(id);
|
|
4828
|
+
});
|
|
4829
|
+
this.renderer.setStore(viewport.store);
|
|
4830
|
+
this.renderer.setOnImageLoad(() => this.markSceneDirty());
|
|
4831
|
+
this.applyCanvasSize();
|
|
4832
|
+
const onScene = () => this.markSceneDirty();
|
|
4833
|
+
this.unsubs.push(
|
|
4834
|
+
viewport.store.on("add", onScene),
|
|
4835
|
+
viewport.store.on("remove", onScene),
|
|
4836
|
+
viewport.store.on("update", onScene),
|
|
4837
|
+
viewport.store.on("clear", onScene),
|
|
4838
|
+
viewport.layerManager.on("change", onScene),
|
|
4839
|
+
viewport.camera.onChange(() => this.onViewChanged()),
|
|
4840
|
+
viewport.onResize(() => this.onViewChanged())
|
|
4841
|
+
);
|
|
4842
|
+
if (this.interactive) {
|
|
4843
|
+
canvas.style.touchAction = "none";
|
|
4844
|
+
canvas.style.cursor = "pointer";
|
|
4845
|
+
canvas.addEventListener("pointerdown", this.onPointerDown);
|
|
4846
|
+
canvas.addEventListener("pointermove", this.onPointerMove);
|
|
4847
|
+
canvas.addEventListener("pointerup", this.onPointerUp);
|
|
4848
|
+
canvas.addEventListener("pointercancel", this.onPointerEnd);
|
|
4849
|
+
canvas.addEventListener("lostpointercapture", this.onPointerEnd);
|
|
4850
|
+
}
|
|
4851
|
+
this.renderScene();
|
|
4852
|
+
this.requestDraw();
|
|
4829
4853
|
}
|
|
4830
|
-
|
|
4831
|
-
|
|
4854
|
+
width;
|
|
4855
|
+
height;
|
|
4856
|
+
padding;
|
|
4857
|
+
background;
|
|
4858
|
+
viewportStroke;
|
|
4859
|
+
debounceMs;
|
|
4860
|
+
interactive;
|
|
4861
|
+
requestFrame;
|
|
4862
|
+
cancelFrame;
|
|
4863
|
+
renderer = new ElementRenderer();
|
|
4864
|
+
scene = null;
|
|
4865
|
+
frameId = null;
|
|
4866
|
+
debounceTimer = null;
|
|
4832
4867
|
dragging = false;
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4868
|
+
disposed = false;
|
|
4869
|
+
unsubs = [];
|
|
4870
|
+
setSize(width, height) {
|
|
4871
|
+
if (this.disposed) return;
|
|
4872
|
+
this.width = width;
|
|
4873
|
+
this.height = height;
|
|
4874
|
+
this.applyCanvasSize();
|
|
4875
|
+
this.clearDebounce();
|
|
4876
|
+
this.renderScene();
|
|
4877
|
+
this.requestDraw();
|
|
4836
4878
|
}
|
|
4837
|
-
|
|
4838
|
-
if (this.
|
|
4839
|
-
|
|
4840
|
-
|
|
4879
|
+
requestDraw() {
|
|
4880
|
+
if (this.disposed || this.frameId !== null) return;
|
|
4881
|
+
this.frameId = this.requestFrame(this.draw);
|
|
4882
|
+
}
|
|
4883
|
+
dispose() {
|
|
4884
|
+
if (this.disposed) return;
|
|
4885
|
+
this.disposed = true;
|
|
4886
|
+
this.clearDebounce();
|
|
4887
|
+
if (this.frameId !== null) {
|
|
4888
|
+
this.cancelFrame(this.frameId);
|
|
4889
|
+
this.frameId = null;
|
|
4890
|
+
}
|
|
4891
|
+
for (const unsub of this.unsubs.splice(0)) unsub();
|
|
4892
|
+
if (this.interactive) {
|
|
4893
|
+
this.canvas.removeEventListener("pointerdown", this.onPointerDown);
|
|
4894
|
+
this.canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
4895
|
+
this.canvas.removeEventListener("pointerup", this.onPointerUp);
|
|
4896
|
+
this.canvas.removeEventListener("pointercancel", this.onPointerEnd);
|
|
4897
|
+
this.canvas.removeEventListener("lostpointercapture", this.onPointerEnd);
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
clearDebounce() {
|
|
4901
|
+
if (this.debounceTimer !== null) {
|
|
4902
|
+
clearTimeout(this.debounceTimer);
|
|
4903
|
+
this.debounceTimer = null;
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
dpr() {
|
|
4907
|
+
return typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
4908
|
+
}
|
|
4909
|
+
applyCanvasSize() {
|
|
4910
|
+
const dpr = this.dpr();
|
|
4911
|
+
this.canvas.width = Math.max(1, Math.round(this.width * dpr));
|
|
4912
|
+
this.canvas.height = Math.max(1, Math.round(this.height * dpr));
|
|
4913
|
+
}
|
|
4914
|
+
// Single source for both mapping bounds and rendering: layer-visible,
|
|
4915
|
+
// grids excluded. (getElementBounds already returns null for grids, so they
|
|
4916
|
+
// cannot extend the bounding box today — this filter makes the invariant
|
|
4917
|
+
// structural instead of relying on that special case.)
|
|
4918
|
+
sceneElements() {
|
|
4919
|
+
return this.viewport.store.getAll().filter((el) => el.type !== "grid" && this.viewport.layerManager.isLayerVisible(el.layerId));
|
|
4920
|
+
}
|
|
4921
|
+
currentMapping() {
|
|
4922
|
+
const viewportRect = this.viewport.getVisibleRect();
|
|
4923
|
+
const content = getElementsBoundingBox(this.sceneElements());
|
|
4924
|
+
return content ? unionBounds(content, viewportRect) : viewportRect;
|
|
4925
|
+
}
|
|
4926
|
+
onViewChanged() {
|
|
4927
|
+
if (this.disposed) return;
|
|
4928
|
+
const mapping = this.currentMapping();
|
|
4929
|
+
if (!this.scene || !sameBounds(mapping, this.scene.mapping)) {
|
|
4930
|
+
this.markSceneDirty();
|
|
4841
4931
|
}
|
|
4842
|
-
this.
|
|
4843
|
-
this.canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
4844
|
-
this.canvas.removeEventListener("pointerup", this.onPointerUp);
|
|
4845
|
-
this.canvas.remove();
|
|
4932
|
+
this.requestDraw();
|
|
4846
4933
|
}
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4934
|
+
markSceneDirty() {
|
|
4935
|
+
if (this.disposed) return;
|
|
4936
|
+
this.clearDebounce();
|
|
4937
|
+
this.debounceTimer = setTimeout(() => {
|
|
4938
|
+
this.debounceTimer = null;
|
|
4939
|
+
this.renderScene();
|
|
4940
|
+
this.requestDraw();
|
|
4941
|
+
}, this.debounceMs);
|
|
4942
|
+
}
|
|
4943
|
+
renderScene() {
|
|
4944
|
+
if (this.disposed) return;
|
|
4945
|
+
const dpr = this.dpr();
|
|
4946
|
+
const mapping = this.currentMapping();
|
|
4947
|
+
const transform = computeMinimapTransform(mapping, this.width, this.height, this.padding);
|
|
4948
|
+
const sceneCanvas = document.createElement("canvas");
|
|
4949
|
+
sceneCanvas.width = Math.max(1, Math.round(this.width * dpr));
|
|
4950
|
+
sceneCanvas.height = Math.max(1, Math.round(this.height * dpr));
|
|
4951
|
+
const ctx = sceneCanvas.getContext("2d");
|
|
4952
|
+
if (!ctx) return;
|
|
4953
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
4954
|
+
for (const el of this.sceneElements()) {
|
|
4955
|
+
let list = byLayer.get(el.layerId);
|
|
4956
|
+
if (!list) {
|
|
4957
|
+
list = [];
|
|
4958
|
+
byLayer.set(el.layerId, list);
|
|
4959
|
+
}
|
|
4960
|
+
list.push(el);
|
|
4961
|
+
}
|
|
4962
|
+
for (const layer of this.viewport.layerManager.getLayers()) {
|
|
4963
|
+
const els = byLayer.get(layer.id);
|
|
4964
|
+
if (!els || els.length === 0) continue;
|
|
4965
|
+
const opacity = layer.opacity;
|
|
4966
|
+
if (opacity >= 1) {
|
|
4967
|
+
this.renderLayerElements(ctx, els, transform, dpr);
|
|
4968
|
+
continue;
|
|
4969
|
+
}
|
|
4970
|
+
const layerCanvas = document.createElement("canvas");
|
|
4971
|
+
layerCanvas.width = sceneCanvas.width;
|
|
4972
|
+
layerCanvas.height = sceneCanvas.height;
|
|
4973
|
+
const layerCtx = layerCanvas.getContext("2d");
|
|
4974
|
+
if (!layerCtx) continue;
|
|
4975
|
+
this.renderLayerElements(layerCtx, els, transform, dpr);
|
|
4976
|
+
ctx.save();
|
|
4977
|
+
ctx.globalAlpha = opacity;
|
|
4978
|
+
ctx.drawImage(layerCanvas, 0, 0);
|
|
4979
|
+
ctx.restore();
|
|
4980
|
+
}
|
|
4981
|
+
this.scene = { canvas: sceneCanvas, transform, mapping };
|
|
4982
|
+
}
|
|
4983
|
+
renderLayerElements(ctx, elements, t, dpr) {
|
|
4984
|
+
for (const el of elements) {
|
|
4985
|
+
if (DOM_FALLBACK_TYPES.has(el.type)) {
|
|
4986
|
+
const b = getElementBounds(el);
|
|
4987
|
+
if (!b) continue;
|
|
4988
|
+
const tl = worldToMini(t, { x: b.x, y: b.y });
|
|
4989
|
+
ctx.save();
|
|
4990
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
4991
|
+
ctx.fillStyle = elementColor(el);
|
|
4992
|
+
ctx.fillRect(tl.x, tl.y, Math.max(1, b.w * t.scale), Math.max(1, b.h * t.scale));
|
|
4993
|
+
ctx.restore();
|
|
4994
|
+
continue;
|
|
4995
|
+
}
|
|
4996
|
+
ctx.save();
|
|
4997
|
+
ctx.setTransform(dpr * t.scale, 0, 0, dpr * t.scale, dpr * t.offsetX, dpr * t.offsetY);
|
|
4998
|
+
this.renderer.renderCanvasElement(ctx, el);
|
|
4999
|
+
ctx.restore();
|
|
5000
|
+
}
|
|
4852
5001
|
}
|
|
4853
5002
|
draw = () => {
|
|
4854
|
-
this.
|
|
5003
|
+
this.frameId = null;
|
|
5004
|
+
if (this.disposed) return;
|
|
4855
5005
|
const ctx = this.canvas.getContext("2d");
|
|
4856
5006
|
if (!ctx) return;
|
|
4857
|
-
const
|
|
4858
|
-
|
|
4859
|
-
ctx.clearRect(0, 0,
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
const
|
|
4868
|
-
|
|
5007
|
+
const dpr = this.dpr();
|
|
5008
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
5009
|
+
ctx.clearRect(0, 0, this.width, this.height);
|
|
5010
|
+
if (this.background) {
|
|
5011
|
+
ctx.fillStyle = this.background;
|
|
5012
|
+
ctx.fillRect(0, 0, this.width, this.height);
|
|
5013
|
+
}
|
|
5014
|
+
const scene = this.scene;
|
|
5015
|
+
if (!scene) return;
|
|
5016
|
+
ctx.drawImage(scene.canvas, 0, 0, this.width, this.height);
|
|
5017
|
+
const viewportRect = this.viewport.getVisibleRect();
|
|
5018
|
+
const tl = worldToMini(scene.transform, { x: viewportRect.x, y: viewportRect.y });
|
|
5019
|
+
ctx.strokeStyle = this.viewportStroke;
|
|
4869
5020
|
ctx.lineWidth = 1.5;
|
|
4870
|
-
ctx.strokeRect(
|
|
5021
|
+
ctx.strokeRect(
|
|
5022
|
+
tl.x,
|
|
5023
|
+
tl.y,
|
|
5024
|
+
viewportRect.w * scene.transform.scale,
|
|
5025
|
+
viewportRect.h * scene.transform.scale
|
|
5026
|
+
);
|
|
4871
5027
|
};
|
|
5028
|
+
navTransform() {
|
|
5029
|
+
return this.scene?.transform ?? computeMinimapTransform(this.currentMapping(), this.width, this.height, this.padding);
|
|
5030
|
+
}
|
|
4872
5031
|
navigateFromEvent(e) {
|
|
4873
5032
|
const rect = this.canvas.getBoundingClientRect();
|
|
4874
|
-
const
|
|
4875
|
-
const
|
|
4876
|
-
|
|
5033
|
+
const scaleX = rect.width > 0 ? this.width / rect.width : 1;
|
|
5034
|
+
const scaleY = rect.height > 0 ? this.height / rect.height : 1;
|
|
5035
|
+
const point = {
|
|
5036
|
+
x: (e.clientX - rect.left) * scaleX,
|
|
5037
|
+
y: (e.clientY - rect.top) * scaleY
|
|
5038
|
+
};
|
|
5039
|
+
const world = miniToWorld(this.navTransform(), point);
|
|
5040
|
+
this.viewport.centerCameraAt(world);
|
|
4877
5041
|
}
|
|
4878
5042
|
onPointerDown = (e) => {
|
|
4879
5043
|
e.stopPropagation();
|
|
@@ -4894,6 +5058,45 @@ var Minimap = class {
|
|
|
4894
5058
|
} catch {
|
|
4895
5059
|
}
|
|
4896
5060
|
};
|
|
5061
|
+
// pointercancel / lostpointercapture: a browser gesture or interrupted touch
|
|
5062
|
+
// can end the interaction without a pointerup — the drag must not stick.
|
|
5063
|
+
onPointerEnd = () => {
|
|
5064
|
+
this.dragging = false;
|
|
5065
|
+
};
|
|
5066
|
+
};
|
|
5067
|
+
|
|
5068
|
+
// src/canvas/minimap.ts
|
|
5069
|
+
var WIDTH = 200;
|
|
5070
|
+
var HEIGHT = 140;
|
|
5071
|
+
var MARGIN = 16;
|
|
5072
|
+
var Minimap = class {
|
|
5073
|
+
canvas;
|
|
5074
|
+
controller;
|
|
5075
|
+
constructor(container, viewport) {
|
|
5076
|
+
const canvas = document.createElement("canvas");
|
|
5077
|
+
Object.assign(canvas.style, {
|
|
5078
|
+
position: "absolute",
|
|
5079
|
+
right: `${MARGIN}px`,
|
|
5080
|
+
bottom: `${MARGIN}px`,
|
|
5081
|
+
width: `${WIDTH}px`,
|
|
5082
|
+
height: `${HEIGHT}px`,
|
|
5083
|
+
background: "rgba(255,255,255,0.85)",
|
|
5084
|
+
border: "1px solid rgba(0,0,0,0.15)",
|
|
5085
|
+
borderRadius: "4px",
|
|
5086
|
+
zIndex: "10"
|
|
5087
|
+
});
|
|
5088
|
+
canvas.dataset.fieldnotesMinimap = "true";
|
|
5089
|
+
container.appendChild(canvas);
|
|
5090
|
+
this.canvas = canvas;
|
|
5091
|
+
this.controller = new MinimapController(viewport, canvas, { width: WIDTH, height: HEIGHT });
|
|
5092
|
+
}
|
|
5093
|
+
scheduleDraw() {
|
|
5094
|
+
this.controller.requestDraw();
|
|
5095
|
+
}
|
|
5096
|
+
destroy() {
|
|
5097
|
+
this.controller.dispose();
|
|
5098
|
+
this.canvas.remove();
|
|
5099
|
+
}
|
|
4897
5100
|
};
|
|
4898
5101
|
|
|
4899
5102
|
// src/canvas/viewport-dom.ts
|
|
@@ -8075,25 +8278,7 @@ var Viewport = class {
|
|
|
8075
8278
|
}
|
|
8076
8279
|
this.unsubToolChange = this.toolManager.onChange(() => this.contextMenu?.close());
|
|
8077
8280
|
if (options.minimap) {
|
|
8078
|
-
|
|
8079
|
-
this.minimap = new Minimap({
|
|
8080
|
-
container: this.wrapper,
|
|
8081
|
-
getElements: visibleEls,
|
|
8082
|
-
getContentBounds: () => getElementsBoundingBox(visibleEls()),
|
|
8083
|
-
getViewportRect: () => this.camera.getVisibleRect(this.canvasEl.clientWidth, this.canvasEl.clientHeight),
|
|
8084
|
-
navigateTo: (w) => {
|
|
8085
|
-
const z = this.camera.zoom;
|
|
8086
|
-
this.camera.moveTo(
|
|
8087
|
-
this.canvasEl.clientWidth / 2 - w.x * z,
|
|
8088
|
-
this.canvasEl.clientHeight / 2 - w.y * z
|
|
8089
|
-
);
|
|
8090
|
-
},
|
|
8091
|
-
requestFrame: (cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : 0,
|
|
8092
|
-
cancelFrame: (id) => {
|
|
8093
|
-
if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(id);
|
|
8094
|
-
}
|
|
8095
|
-
});
|
|
8096
|
-
this.minimap.scheduleDraw();
|
|
8281
|
+
this.minimap = new Minimap(this.wrapper, this);
|
|
8097
8282
|
}
|
|
8098
8283
|
this.domNodeManager = new DomNodeManager({
|
|
8099
8284
|
domLayer: this.paintStack,
|
|
@@ -8129,7 +8314,6 @@ var Viewport = class {
|
|
|
8129
8314
|
this.applyCameraTransform();
|
|
8130
8315
|
this.noteEditor.updateToolbarPosition();
|
|
8131
8316
|
this.contextMenu?.close();
|
|
8132
|
-
this.minimap?.scheduleDraw();
|
|
8133
8317
|
this.requestRender();
|
|
8134
8318
|
});
|
|
8135
8319
|
this.gridController = new GridController({
|
|
@@ -8144,7 +8328,6 @@ var Viewport = class {
|
|
|
8144
8328
|
this.store.on("add", (el) => {
|
|
8145
8329
|
if (el.type === "grid") this.gridController.syncContext();
|
|
8146
8330
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
8147
|
-
this.minimap?.scheduleDraw();
|
|
8148
8331
|
this.requestRender();
|
|
8149
8332
|
}),
|
|
8150
8333
|
this.store.on("remove", (el) => {
|
|
@@ -8152,7 +8335,6 @@ var Viewport = class {
|
|
|
8152
8335
|
this.unbindArrowsFrom(el);
|
|
8153
8336
|
this.domNodeManager.removeDomNode(el.id);
|
|
8154
8337
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
8155
|
-
this.minimap?.scheduleDraw();
|
|
8156
8338
|
this.requestRender();
|
|
8157
8339
|
}),
|
|
8158
8340
|
this.store.on("update", ({ previous, current }) => {
|
|
@@ -8161,21 +8343,18 @@ var Viewport = class {
|
|
|
8161
8343
|
if (previous.layerId !== current.layerId) {
|
|
8162
8344
|
this.renderLoop.markLayerDirty(previous.layerId);
|
|
8163
8345
|
}
|
|
8164
|
-
this.minimap?.scheduleDraw();
|
|
8165
8346
|
this.requestRender();
|
|
8166
8347
|
}),
|
|
8167
8348
|
this.store.on("clear", () => {
|
|
8168
8349
|
this.domNodeManager.clearDomNodes();
|
|
8169
8350
|
this.renderLoop.markAllLayersDirty();
|
|
8170
8351
|
this.gridController.syncContext();
|
|
8171
|
-
this.minimap?.scheduleDraw();
|
|
8172
8352
|
this.requestRender();
|
|
8173
8353
|
})
|
|
8174
8354
|
];
|
|
8175
8355
|
this.layerManager.on("change", () => {
|
|
8176
8356
|
this.toolContext.activeLayerId = this.layerManager.activeLayerId;
|
|
8177
8357
|
this.renderLoop.markAllLayersDirty();
|
|
8178
|
-
this.minimap?.scheduleDraw();
|
|
8179
8358
|
this.requestRender();
|
|
8180
8359
|
});
|
|
8181
8360
|
this.interactions = new ViewportInteractions({
|
|
@@ -8239,6 +8418,7 @@ var Viewport = class {
|
|
|
8239
8418
|
contextMenu = null;
|
|
8240
8419
|
minimap = null;
|
|
8241
8420
|
htmlRenderers = /* @__PURE__ */ new Map();
|
|
8421
|
+
resizeListeners = /* @__PURE__ */ new Set();
|
|
8242
8422
|
get ctx() {
|
|
8243
8423
|
return this.canvasEl.getContext("2d");
|
|
8244
8424
|
}
|
|
@@ -8263,6 +8443,27 @@ var Viewport = class {
|
|
|
8263
8443
|
if (!bbox) return;
|
|
8264
8444
|
this.camera.fitToContent(bbox, this.wrapper.clientWidth, this.wrapper.clientHeight, padding);
|
|
8265
8445
|
}
|
|
8446
|
+
/** World-space rectangle currently visible through the canvas. */
|
|
8447
|
+
getVisibleRect() {
|
|
8448
|
+
return this.camera.getVisibleRect(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
|
|
8449
|
+
}
|
|
8450
|
+
/** Centers the camera on a world point without changing zoom. */
|
|
8451
|
+
centerCameraAt(world) {
|
|
8452
|
+
const z = this.camera.zoom;
|
|
8453
|
+
this.camera.moveTo(
|
|
8454
|
+
this.canvasEl.clientWidth / 2 - world.x * z,
|
|
8455
|
+
this.canvasEl.clientHeight / 2 - world.y * z
|
|
8456
|
+
);
|
|
8457
|
+
}
|
|
8458
|
+
/**
|
|
8459
|
+
* Notifies after the host container resizes (ResizeObserver-driven). A resize
|
|
8460
|
+
* changes the visible world rect without a camera event; overlays such as the
|
|
8461
|
+
* minimap subscribe to stay current. Returns an idempotent unsubscribe.
|
|
8462
|
+
*/
|
|
8463
|
+
onResize(listener) {
|
|
8464
|
+
this.resizeListeners.add(listener);
|
|
8465
|
+
return () => this.resizeListeners.delete(listener);
|
|
8466
|
+
}
|
|
8266
8467
|
requestRender() {
|
|
8267
8468
|
this.renderLoop.requestRender();
|
|
8268
8469
|
}
|
|
@@ -8567,6 +8768,7 @@ var Viewport = class {
|
|
|
8567
8768
|
this.unsubStore.forEach((fn) => fn());
|
|
8568
8769
|
this.resizeObserver?.disconnect();
|
|
8569
8770
|
this.resizeObserver = null;
|
|
8771
|
+
this.resizeListeners.clear();
|
|
8570
8772
|
this.wrapper.remove();
|
|
8571
8773
|
}
|
|
8572
8774
|
stopInteracting() {
|
|
@@ -8616,6 +8818,7 @@ var Viewport = class {
|
|
|
8616
8818
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
8617
8819
|
this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
|
|
8618
8820
|
this.requestRender();
|
|
8821
|
+
this.resizeListeners.forEach((fn) => fn());
|
|
8619
8822
|
}
|
|
8620
8823
|
observeResize() {
|
|
8621
8824
|
if (typeof ResizeObserver === "undefined") return;
|
|
@@ -8655,7 +8858,7 @@ function toLaserTrailPresence(emission) {
|
|
|
8655
8858
|
};
|
|
8656
8859
|
}
|
|
8657
8860
|
var DEFAULT_COLOR = "#ff3b30";
|
|
8658
|
-
var
|
|
8861
|
+
var DEFAULT_WIDTH2 = 4;
|
|
8659
8862
|
var DEFAULT_FADE_MS = 1200;
|
|
8660
8863
|
var DEFAULT_MAX_POINTS = 512;
|
|
8661
8864
|
var RemoteLaserOverlay = class {
|
|
@@ -8671,7 +8874,7 @@ var RemoteLaserOverlay = class {
|
|
|
8671
8874
|
constructor(host, options = {}) {
|
|
8672
8875
|
this.host = host;
|
|
8673
8876
|
this.color = options.color ?? DEFAULT_COLOR;
|
|
8674
|
-
this.width = options.width ??
|
|
8877
|
+
this.width = options.width ?? DEFAULT_WIDTH2;
|
|
8675
8878
|
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS;
|
|
8676
8879
|
this.maxPointsPerSender = options.maxPointsPerSender ?? DEFAULT_MAX_POINTS;
|
|
8677
8880
|
this.unregister = host.registerOverlay((ctx) => this.renderTrails(ctx));
|
|
@@ -8942,10 +9145,218 @@ var RemotePingOverlay = class {
|
|
|
8942
9145
|
}
|
|
8943
9146
|
};
|
|
8944
9147
|
|
|
9148
|
+
// src/canvas/measure-render.ts
|
|
9149
|
+
function formatMeasureLabel(feet) {
|
|
9150
|
+
return `${Math.round(feet)} ft`;
|
|
9151
|
+
}
|
|
9152
|
+
function drawMeasurement(ctx, m, opts = {}) {
|
|
9153
|
+
ctx.save();
|
|
9154
|
+
if (opts.alpha !== void 0) ctx.globalAlpha = opts.alpha;
|
|
9155
|
+
ctx.strokeStyle = m.color;
|
|
9156
|
+
ctx.setLineDash([8, 4]);
|
|
9157
|
+
ctx.lineWidth = 2;
|
|
9158
|
+
ctx.beginPath();
|
|
9159
|
+
ctx.moveTo(m.start.x, m.start.y);
|
|
9160
|
+
ctx.lineTo(m.end.x, m.end.y);
|
|
9161
|
+
ctx.stroke();
|
|
9162
|
+
ctx.setLineDash([]);
|
|
9163
|
+
ctx.fillStyle = m.color;
|
|
9164
|
+
const dotRadius = 4;
|
|
9165
|
+
ctx.beginPath();
|
|
9166
|
+
ctx.arc(m.start.x, m.start.y, dotRadius, 0, Math.PI * 2);
|
|
9167
|
+
ctx.fill();
|
|
9168
|
+
ctx.beginPath();
|
|
9169
|
+
ctx.arc(m.end.x, m.end.y, dotRadius, 0, Math.PI * 2);
|
|
9170
|
+
ctx.fill();
|
|
9171
|
+
const label = formatMeasureLabel(m.feet);
|
|
9172
|
+
const midX = (m.start.x + m.end.x) / 2;
|
|
9173
|
+
const midY = (m.start.y + m.end.y) / 2;
|
|
9174
|
+
ctx.font = "14px sans-serif";
|
|
9175
|
+
const metrics = ctx.measureText(label);
|
|
9176
|
+
const padX = 6;
|
|
9177
|
+
const padY = 4;
|
|
9178
|
+
const textH = 14;
|
|
9179
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
|
|
9180
|
+
ctx.beginPath();
|
|
9181
|
+
ctx.roundRect(
|
|
9182
|
+
midX - metrics.width / 2 - padX,
|
|
9183
|
+
midY - textH / 2 - padY,
|
|
9184
|
+
metrics.width + padX * 2,
|
|
9185
|
+
textH + padY * 2,
|
|
9186
|
+
4
|
|
9187
|
+
);
|
|
9188
|
+
ctx.fill();
|
|
9189
|
+
ctx.fillStyle = "#FFFFFF";
|
|
9190
|
+
ctx.textAlign = "center";
|
|
9191
|
+
ctx.textBaseline = "middle";
|
|
9192
|
+
ctx.fillText(label, midX, midY);
|
|
9193
|
+
ctx.restore();
|
|
9194
|
+
}
|
|
9195
|
+
|
|
9196
|
+
// src/canvas/remote-measure-overlay.ts
|
|
9197
|
+
var MEASURE_PRESENCE_KIND = "measure";
|
|
9198
|
+
function isFinitePoint2(value) {
|
|
9199
|
+
if (typeof value !== "object" || value === null) return false;
|
|
9200
|
+
const point = value;
|
|
9201
|
+
return typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y);
|
|
9202
|
+
}
|
|
9203
|
+
function isMeasurePresence(data) {
|
|
9204
|
+
if (typeof data !== "object" || data === null) return false;
|
|
9205
|
+
const payload = data;
|
|
9206
|
+
if (payload.kind !== MEASURE_PRESENCE_KIND) return false;
|
|
9207
|
+
if ("cleared" in payload) return payload.cleared === true;
|
|
9208
|
+
if (!isFinitePoint2(payload.start) || !isFinitePoint2(payload.end)) return false;
|
|
9209
|
+
if (typeof payload.cells !== "number" || !Number.isFinite(payload.cells)) return false;
|
|
9210
|
+
if (typeof payload.feet !== "number" || !Number.isFinite(payload.feet)) return false;
|
|
9211
|
+
if (payload.color !== void 0 && typeof payload.color !== "string") return false;
|
|
9212
|
+
return true;
|
|
9213
|
+
}
|
|
9214
|
+
function toMeasurePresence(emission) {
|
|
9215
|
+
if (emission === null) return { kind: MEASURE_PRESENCE_KIND, cleared: true };
|
|
9216
|
+
return {
|
|
9217
|
+
kind: MEASURE_PRESENCE_KIND,
|
|
9218
|
+
start: emission.start,
|
|
9219
|
+
end: emission.end,
|
|
9220
|
+
cells: emission.cells,
|
|
9221
|
+
feet: emission.feet,
|
|
9222
|
+
color: emission.color
|
|
9223
|
+
};
|
|
9224
|
+
}
|
|
9225
|
+
var DEFAULT_COLOR3 = "#FF5722";
|
|
9226
|
+
var DEFAULT_HOLD_MS = 1500;
|
|
9227
|
+
var DEFAULT_FADE_MS2 = 400;
|
|
9228
|
+
var DEFAULT_MAX_AGE_MS = 3e4;
|
|
9229
|
+
var RemoteMeasureOverlay = class {
|
|
9230
|
+
host;
|
|
9231
|
+
color;
|
|
9232
|
+
holdMs;
|
|
9233
|
+
fadeMs;
|
|
9234
|
+
maxAgeMs;
|
|
9235
|
+
measurements = /* @__PURE__ */ new Map();
|
|
9236
|
+
unregister;
|
|
9237
|
+
rafId = null;
|
|
9238
|
+
disposed = false;
|
|
9239
|
+
constructor(host, options = {}) {
|
|
9240
|
+
this.host = host;
|
|
9241
|
+
this.color = options.color ?? DEFAULT_COLOR3;
|
|
9242
|
+
this.holdMs = options.holdMs ?? DEFAULT_HOLD_MS;
|
|
9243
|
+
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS2;
|
|
9244
|
+
this.maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
9245
|
+
this.unregister = host.registerOverlay((ctx) => this.renderMeasurements(ctx));
|
|
9246
|
+
}
|
|
9247
|
+
now() {
|
|
9248
|
+
return performance.now();
|
|
9249
|
+
}
|
|
9250
|
+
/**
|
|
9251
|
+
* Applies a presence payload from `sender` (any opaque per-sender key, e.g.
|
|
9252
|
+
* the envelope `from`). Non-measure or malformed payloads are ignored and
|
|
9253
|
+
* reported as `false`, so hosts can feed every presence frame through.
|
|
9254
|
+
*/
|
|
9255
|
+
apply(sender, data) {
|
|
9256
|
+
if (this.disposed || !isMeasurePresence(data)) return false;
|
|
9257
|
+
if ("cleared" in data) {
|
|
9258
|
+
this.beginLinger(sender);
|
|
9259
|
+
return true;
|
|
9260
|
+
}
|
|
9261
|
+
const existing = this.measurements.get(sender);
|
|
9262
|
+
if (existing?.expiryTimer != null) clearTimeout(existing.expiryTimer);
|
|
9263
|
+
this.measurements.set(sender, {
|
|
9264
|
+
start: data.start,
|
|
9265
|
+
end: data.end,
|
|
9266
|
+
feet: data.feet,
|
|
9267
|
+
color: data.color ?? this.color,
|
|
9268
|
+
clearedAt: null,
|
|
9269
|
+
expiryTimer: setTimeout(() => this.beginLinger(sender), this.maxAgeMs)
|
|
9270
|
+
});
|
|
9271
|
+
this.host.requestRender();
|
|
9272
|
+
return true;
|
|
9273
|
+
}
|
|
9274
|
+
/** Removes a sender's ruler immediately (presence-leave/disconnect). */
|
|
9275
|
+
remove(sender) {
|
|
9276
|
+
const entry = this.measurements.get(sender);
|
|
9277
|
+
if (!entry) return;
|
|
9278
|
+
if (entry.expiryTimer != null) clearTimeout(entry.expiryTimer);
|
|
9279
|
+
this.measurements.delete(sender);
|
|
9280
|
+
this.host.requestRender();
|
|
9281
|
+
}
|
|
9282
|
+
/** Removes every ruler immediately. */
|
|
9283
|
+
clear() {
|
|
9284
|
+
if (this.measurements.size === 0) return;
|
|
9285
|
+
for (const entry of this.measurements.values()) {
|
|
9286
|
+
if (entry.expiryTimer != null) clearTimeout(entry.expiryTimer);
|
|
9287
|
+
}
|
|
9288
|
+
this.measurements.clear();
|
|
9289
|
+
this.host.requestRender();
|
|
9290
|
+
}
|
|
9291
|
+
/** Number of senders with a visible (active or lingering) ruler. */
|
|
9292
|
+
get activeSenderCount() {
|
|
9293
|
+
return this.measurements.size;
|
|
9294
|
+
}
|
|
9295
|
+
/** Unregisters the overlay, cancels timers, stops animating. Idempotent. */
|
|
9296
|
+
dispose() {
|
|
9297
|
+
if (this.disposed) return;
|
|
9298
|
+
this.disposed = true;
|
|
9299
|
+
if (this.rafId !== null) {
|
|
9300
|
+
cancelAnimationFrame(this.rafId);
|
|
9301
|
+
this.rafId = null;
|
|
9302
|
+
}
|
|
9303
|
+
for (const entry of this.measurements.values()) {
|
|
9304
|
+
if (entry.expiryTimer != null) clearTimeout(entry.expiryTimer);
|
|
9305
|
+
}
|
|
9306
|
+
this.measurements.clear();
|
|
9307
|
+
this.unregister?.();
|
|
9308
|
+
this.unregister = null;
|
|
9309
|
+
this.host.requestRender();
|
|
9310
|
+
}
|
|
9311
|
+
beginLinger(sender) {
|
|
9312
|
+
const entry = this.measurements.get(sender);
|
|
9313
|
+
if (!entry || entry.clearedAt !== null) return;
|
|
9314
|
+
if (entry.expiryTimer != null) {
|
|
9315
|
+
clearTimeout(entry.expiryTimer);
|
|
9316
|
+
entry.expiryTimer = null;
|
|
9317
|
+
}
|
|
9318
|
+
entry.clearedAt = this.now();
|
|
9319
|
+
this.ensureAnimating();
|
|
9320
|
+
this.host.requestRender();
|
|
9321
|
+
}
|
|
9322
|
+
ensureAnimating() {
|
|
9323
|
+
if (this.rafId === null) {
|
|
9324
|
+
this.rafId = requestAnimationFrame(() => this.tick());
|
|
9325
|
+
}
|
|
9326
|
+
}
|
|
9327
|
+
tick() {
|
|
9328
|
+
if (this.disposed) return;
|
|
9329
|
+
const now = this.now();
|
|
9330
|
+
let lingering = 0;
|
|
9331
|
+
for (const [sender, entry] of this.measurements) {
|
|
9332
|
+
if (entry.clearedAt === null) continue;
|
|
9333
|
+
if (now - entry.clearedAt >= this.holdMs + this.fadeMs) {
|
|
9334
|
+
this.measurements.delete(sender);
|
|
9335
|
+
} else {
|
|
9336
|
+
lingering += 1;
|
|
9337
|
+
}
|
|
9338
|
+
}
|
|
9339
|
+
this.host.requestRender();
|
|
9340
|
+
this.rafId = lingering > 0 ? requestAnimationFrame(() => this.tick()) : null;
|
|
9341
|
+
}
|
|
9342
|
+
renderMeasurements(ctx) {
|
|
9343
|
+
if (this.measurements.size === 0) return;
|
|
9344
|
+
const now = this.now();
|
|
9345
|
+
for (const entry of this.measurements.values()) {
|
|
9346
|
+
let alpha = 1;
|
|
9347
|
+
if (entry.clearedAt !== null) {
|
|
9348
|
+
const fadeAge = now - entry.clearedAt - this.holdMs;
|
|
9349
|
+
if (fadeAge > 0) alpha = Math.max(0, 1 - fadeAge / this.fadeMs);
|
|
9350
|
+
}
|
|
9351
|
+
drawMeasurement(ctx, entry, { alpha });
|
|
9352
|
+
}
|
|
9353
|
+
}
|
|
9354
|
+
};
|
|
9355
|
+
|
|
8945
9356
|
// src/canvas/ping-input.ts
|
|
8946
9357
|
var DEFAULT_LONG_PRESS_MS = 600;
|
|
8947
9358
|
var DEFAULT_SLOP_PX = 8;
|
|
8948
|
-
var
|
|
9359
|
+
var DEFAULT_COLOR4 = "#ff3b30";
|
|
8949
9360
|
var DEFAULT_DURATION_MS2 = 1800;
|
|
8950
9361
|
var DEFAULT_RADIUS2 = 48;
|
|
8951
9362
|
var DEFAULT_MIN_INTERVAL_MS = 300;
|
|
@@ -8978,7 +9389,7 @@ var PingInput = class {
|
|
|
8978
9389
|
this.longPressEnabled = options.longPressEnabled ?? false;
|
|
8979
9390
|
this.longPressMs = options.longPressMs ?? DEFAULT_LONG_PRESS_MS;
|
|
8980
9391
|
this.slopPx = options.slopPx ?? DEFAULT_SLOP_PX;
|
|
8981
|
-
this.color = options.color ??
|
|
9392
|
+
this.color = options.color ?? DEFAULT_COLOR4;
|
|
8982
9393
|
this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS2;
|
|
8983
9394
|
this.radius = options.radius ?? DEFAULT_RADIUS2;
|
|
8984
9395
|
this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
|
|
@@ -11268,21 +11679,37 @@ var MeasureTool = class {
|
|
|
11268
11679
|
gridType;
|
|
11269
11680
|
hexOrientation;
|
|
11270
11681
|
feetPerCell;
|
|
11682
|
+
color;
|
|
11271
11683
|
optionListeners = /* @__PURE__ */ new Set();
|
|
11684
|
+
measurementListeners = /* @__PURE__ */ new Set();
|
|
11685
|
+
emissionRafId = null;
|
|
11272
11686
|
constructor(options = {}) {
|
|
11273
11687
|
this.feetPerCell = options.feetPerCell ?? 5;
|
|
11688
|
+
this.color = options.color ?? "#FF5722";
|
|
11274
11689
|
}
|
|
11275
11690
|
getOptions() {
|
|
11276
|
-
return { feetPerCell: this.feetPerCell };
|
|
11691
|
+
return { feetPerCell: this.feetPerCell, color: this.color };
|
|
11277
11692
|
}
|
|
11278
11693
|
setOptions(options) {
|
|
11279
11694
|
if (options.feetPerCell !== void 0) this.feetPerCell = options.feetPerCell;
|
|
11695
|
+
if (options.color !== void 0) this.color = options.color;
|
|
11280
11696
|
this.notifyOptionsChange();
|
|
11281
11697
|
}
|
|
11282
11698
|
onOptionsChange(listener) {
|
|
11283
11699
|
this.optionListeners.add(listener);
|
|
11284
11700
|
return () => this.optionListeners.delete(listener);
|
|
11285
11701
|
}
|
|
11702
|
+
/**
|
|
11703
|
+
* Subscribes to raf-coalesced measurement snapshots. While a measurement is
|
|
11704
|
+
* in progress, listeners receive at most one snapshot per animation frame
|
|
11705
|
+
* carrying the latest state; `null` is delivered synchronously when the
|
|
11706
|
+
* measurement clears (pointer-up or deactivate). Emissions are ephemeral by
|
|
11707
|
+
* contract: presence only — never elements, history, or persisted state.
|
|
11708
|
+
*/
|
|
11709
|
+
onMeasurement(listener) {
|
|
11710
|
+
this.measurementListeners.add(listener);
|
|
11711
|
+
return () => this.measurementListeners.delete(listener);
|
|
11712
|
+
}
|
|
11286
11713
|
onPointerDown(state, ctx) {
|
|
11287
11714
|
this.gridSize = ctx.gridSize ?? 1;
|
|
11288
11715
|
this.gridType = ctx.gridType;
|
|
@@ -11290,22 +11717,27 @@ var MeasureTool = class {
|
|
|
11290
11717
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
11291
11718
|
this.start = this.snapToGrid(world, ctx);
|
|
11292
11719
|
this.end = { ...this.start };
|
|
11720
|
+
this.scheduleEmission();
|
|
11293
11721
|
}
|
|
11294
11722
|
onPointerMove(state, ctx) {
|
|
11295
11723
|
if (!this.start) return;
|
|
11296
11724
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
11297
11725
|
this.end = this.snapToGrid(world, ctx);
|
|
11298
11726
|
ctx.requestRender();
|
|
11727
|
+
this.scheduleEmission();
|
|
11299
11728
|
}
|
|
11300
11729
|
onPointerUp(_state, ctx) {
|
|
11301
11730
|
if (!this.start) return;
|
|
11302
11731
|
this.start = null;
|
|
11303
11732
|
this.end = null;
|
|
11304
11733
|
ctx.requestRender();
|
|
11734
|
+
this.emitClear();
|
|
11305
11735
|
}
|
|
11306
11736
|
onDeactivate(_ctx) {
|
|
11737
|
+
const wasActive = this.start !== null;
|
|
11307
11738
|
this.start = null;
|
|
11308
11739
|
this.end = null;
|
|
11740
|
+
if (wasActive) this.emitClear();
|
|
11309
11741
|
}
|
|
11310
11742
|
getMeasurement() {
|
|
11311
11743
|
if (!this.start || !this.end) return null;
|
|
@@ -11331,46 +11763,7 @@ var MeasureTool = class {
|
|
|
11331
11763
|
renderOverlay(ctx) {
|
|
11332
11764
|
const m = this.getMeasurement();
|
|
11333
11765
|
if (!m) return;
|
|
11334
|
-
ctx.
|
|
11335
|
-
ctx.strokeStyle = "#FF5722";
|
|
11336
|
-
ctx.setLineDash([8, 4]);
|
|
11337
|
-
ctx.lineWidth = 2;
|
|
11338
|
-
ctx.beginPath();
|
|
11339
|
-
ctx.moveTo(m.start.x, m.start.y);
|
|
11340
|
-
ctx.lineTo(m.end.x, m.end.y);
|
|
11341
|
-
ctx.stroke();
|
|
11342
|
-
ctx.setLineDash([]);
|
|
11343
|
-
ctx.fillStyle = "#FF5722";
|
|
11344
|
-
const dotRadius = 4;
|
|
11345
|
-
ctx.beginPath();
|
|
11346
|
-
ctx.arc(m.start.x, m.start.y, dotRadius, 0, Math.PI * 2);
|
|
11347
|
-
ctx.fill();
|
|
11348
|
-
ctx.beginPath();
|
|
11349
|
-
ctx.arc(m.end.x, m.end.y, dotRadius, 0, Math.PI * 2);
|
|
11350
|
-
ctx.fill();
|
|
11351
|
-
const label = `${Math.round(m.feet)} ft`;
|
|
11352
|
-
const midX = (m.start.x + m.end.x) / 2;
|
|
11353
|
-
const midY = (m.start.y + m.end.y) / 2;
|
|
11354
|
-
ctx.font = "14px sans-serif";
|
|
11355
|
-
const metrics = ctx.measureText(label);
|
|
11356
|
-
const padX = 6;
|
|
11357
|
-
const padY = 4;
|
|
11358
|
-
const textH = 14;
|
|
11359
|
-
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
|
|
11360
|
-
ctx.beginPath();
|
|
11361
|
-
ctx.roundRect(
|
|
11362
|
-
midX - metrics.width / 2 - padX,
|
|
11363
|
-
midY - textH / 2 - padY,
|
|
11364
|
-
metrics.width + padX * 2,
|
|
11365
|
-
textH + padY * 2,
|
|
11366
|
-
4
|
|
11367
|
-
);
|
|
11368
|
-
ctx.fill();
|
|
11369
|
-
ctx.fillStyle = "#FFFFFF";
|
|
11370
|
-
ctx.textAlign = "center";
|
|
11371
|
-
ctx.textBaseline = "middle";
|
|
11372
|
-
ctx.fillText(label, midX, midY);
|
|
11373
|
-
ctx.restore();
|
|
11766
|
+
drawMeasurement(ctx, { start: m.start, end: m.end, feet: m.feet, color: this.color });
|
|
11374
11767
|
}
|
|
11375
11768
|
snapToGrid(point, ctx) {
|
|
11376
11769
|
if (!ctx.gridSize) return point;
|
|
@@ -11388,6 +11781,32 @@ var MeasureTool = class {
|
|
|
11388
11781
|
notifyOptionsChange() {
|
|
11389
11782
|
for (const listener of this.optionListeners) listener();
|
|
11390
11783
|
}
|
|
11784
|
+
scheduleEmission() {
|
|
11785
|
+
if (this.measurementListeners.size === 0) return;
|
|
11786
|
+
if (this.emissionRafId !== null) return;
|
|
11787
|
+
this.emissionRafId = requestAnimationFrame(() => {
|
|
11788
|
+
this.emissionRafId = null;
|
|
11789
|
+
const m = this.getMeasurement();
|
|
11790
|
+
if (!m) return;
|
|
11791
|
+
this.emit({ ...m, color: this.color });
|
|
11792
|
+
});
|
|
11793
|
+
}
|
|
11794
|
+
emitClear() {
|
|
11795
|
+
if (this.emissionRafId !== null) {
|
|
11796
|
+
cancelAnimationFrame(this.emissionRafId);
|
|
11797
|
+
this.emissionRafId = null;
|
|
11798
|
+
}
|
|
11799
|
+
if (this.measurementListeners.size === 0) return;
|
|
11800
|
+
this.emit(null);
|
|
11801
|
+
}
|
|
11802
|
+
emit(emission) {
|
|
11803
|
+
for (const listener of this.measurementListeners) {
|
|
11804
|
+
try {
|
|
11805
|
+
listener(emission);
|
|
11806
|
+
} catch {
|
|
11807
|
+
}
|
|
11808
|
+
}
|
|
11809
|
+
}
|
|
11391
11810
|
};
|
|
11392
11811
|
|
|
11393
11812
|
// src/tools/template-tool.ts
|
|
@@ -11683,9 +12102,9 @@ var TemplateTool = class {
|
|
|
11683
12102
|
};
|
|
11684
12103
|
|
|
11685
12104
|
// src/tools/laser-tool.ts
|
|
11686
|
-
var
|
|
11687
|
-
var
|
|
11688
|
-
var
|
|
12105
|
+
var DEFAULT_COLOR5 = "#ff3b30";
|
|
12106
|
+
var DEFAULT_WIDTH3 = 4;
|
|
12107
|
+
var DEFAULT_FADE_MS3 = 1200;
|
|
11689
12108
|
var LaserTool = class {
|
|
11690
12109
|
name;
|
|
11691
12110
|
color;
|
|
@@ -11699,9 +12118,9 @@ var LaserTool = class {
|
|
|
11699
12118
|
pendingEmission = [];
|
|
11700
12119
|
constructor(options = {}) {
|
|
11701
12120
|
this.name = options.name ?? "laser";
|
|
11702
|
-
this.color = options.color ??
|
|
11703
|
-
this.width = options.width ??
|
|
11704
|
-
this.fadeMs = options.fadeMs ??
|
|
12121
|
+
this.color = options.color ?? DEFAULT_COLOR5;
|
|
12122
|
+
this.width = options.width ?? DEFAULT_WIDTH3;
|
|
12123
|
+
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS3;
|
|
11705
12124
|
}
|
|
11706
12125
|
now() {
|
|
11707
12126
|
return performance.now();
|
|
@@ -11827,7 +12246,7 @@ var LaserTool = class {
|
|
|
11827
12246
|
};
|
|
11828
12247
|
|
|
11829
12248
|
// src/tools/ping-tool.ts
|
|
11830
|
-
var
|
|
12249
|
+
var DEFAULT_COLOR6 = "#ff3b30";
|
|
11831
12250
|
var DEFAULT_DURATION_MS3 = 1800;
|
|
11832
12251
|
var DEFAULT_RADIUS4 = 48;
|
|
11833
12252
|
var DEFAULT_MIN_INTERVAL_MS2 = 300;
|
|
@@ -11844,7 +12263,7 @@ var PingTool = class {
|
|
|
11844
12263
|
pingListeners = /* @__PURE__ */ new Set();
|
|
11845
12264
|
constructor(options = {}) {
|
|
11846
12265
|
this.name = options.name ?? "ping";
|
|
11847
|
-
this.color = options.color ??
|
|
12266
|
+
this.color = options.color ?? DEFAULT_COLOR6;
|
|
11848
12267
|
this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS3;
|
|
11849
12268
|
this.radius = options.radius ?? DEFAULT_RADIUS4;
|
|
11850
12269
|
this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS2;
|
|
@@ -11947,7 +12366,7 @@ var PingTool = class {
|
|
|
11947
12366
|
};
|
|
11948
12367
|
|
|
11949
12368
|
// src/index.ts
|
|
11950
|
-
var VERSION = "0.
|
|
12369
|
+
var VERSION = "0.58.0";
|
|
11951
12370
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11952
12371
|
0 && (module.exports = {
|
|
11953
12372
|
ArrowTool,
|
|
@@ -11964,14 +12383,17 @@ var VERSION = "0.56.0";
|
|
|
11964
12383
|
LaserTool,
|
|
11965
12384
|
LayerManager,
|
|
11966
12385
|
LocalStorageAdapter,
|
|
12386
|
+
MEASURE_PRESENCE_KIND,
|
|
11967
12387
|
MeasureTool,
|
|
11968
12388
|
MemoryAdapter,
|
|
12389
|
+
MinimapController,
|
|
11969
12390
|
NoteTool,
|
|
11970
12391
|
PING_PRESENCE_KIND,
|
|
11971
12392
|
PencilTool,
|
|
11972
12393
|
PingInput,
|
|
11973
12394
|
PingTool,
|
|
11974
12395
|
RemoteLaserOverlay,
|
|
12396
|
+
RemoteMeasureOverlay,
|
|
11975
12397
|
RemotePingOverlay,
|
|
11976
12398
|
SelectTool,
|
|
11977
12399
|
ShapeTool,
|
|
@@ -12009,6 +12431,7 @@ var VERSION = "0.56.0";
|
|
|
12009
12431
|
getHexCellsInSquare,
|
|
12010
12432
|
getHexDistance,
|
|
12011
12433
|
isLaserTrailPresence,
|
|
12434
|
+
isMeasurePresence,
|
|
12012
12435
|
isNearBezier,
|
|
12013
12436
|
isPingPresence,
|
|
12014
12437
|
setFontSize,
|
|
@@ -12017,6 +12440,7 @@ var VERSION = "0.56.0";
|
|
|
12017
12440
|
snapToHexCenter,
|
|
12018
12441
|
styleToPatch,
|
|
12019
12442
|
toLaserTrailPresence,
|
|
12443
|
+
toMeasurePresence,
|
|
12020
12444
|
toPingPresence,
|
|
12021
12445
|
toggleBold,
|
|
12022
12446
|
toggleItalic,
|