@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.js
CHANGED
|
@@ -4695,88 +4695,247 @@ function miniToWorld(t, p) {
|
|
|
4695
4695
|
return { x: (p.x - t.offsetX) / t.scale, y: (p.y - t.offsetY) / t.scale };
|
|
4696
4696
|
}
|
|
4697
4697
|
|
|
4698
|
-
// src/canvas/minimap.ts
|
|
4699
|
-
var
|
|
4700
|
-
var
|
|
4701
|
-
var
|
|
4702
|
-
var
|
|
4698
|
+
// src/canvas/minimap-controller.ts
|
|
4699
|
+
var DEFAULT_WIDTH = 200;
|
|
4700
|
+
var DEFAULT_HEIGHT = 140;
|
|
4701
|
+
var DEFAULT_PADDING = 8;
|
|
4702
|
+
var DEFAULT_DEBOUNCE_MS2 = 200;
|
|
4703
|
+
var DEFAULT_VIEWPORT_STROKE = "#3b82f6";
|
|
4703
4704
|
var NEUTRAL = "rgba(100,116,139,0.6)";
|
|
4704
|
-
var
|
|
4705
|
+
var DOM_FALLBACK_TYPES = /* @__PURE__ */ new Set(["note", "text", "html"]);
|
|
4705
4706
|
function elementColor(el) {
|
|
4706
4707
|
return "color" in el && typeof el.color === "string" ? el.color : NEUTRAL;
|
|
4707
4708
|
}
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
Object.assign(canvas.style, {
|
|
4715
|
-
position: "absolute",
|
|
4716
|
-
right: `${MARGIN}px`,
|
|
4717
|
-
bottom: `${MARGIN}px`,
|
|
4718
|
-
width: `${WIDTH}px`,
|
|
4719
|
-
height: `${HEIGHT}px`,
|
|
4720
|
-
background: "rgba(255,255,255,0.85)",
|
|
4721
|
-
border: "1px solid rgba(0,0,0,0.15)",
|
|
4722
|
-
borderRadius: "4px",
|
|
4723
|
-
touchAction: "none",
|
|
4724
|
-
cursor: "pointer",
|
|
4725
|
-
zIndex: "10"
|
|
4726
|
-
});
|
|
4727
|
-
canvas.addEventListener("pointerdown", this.onPointerDown);
|
|
4728
|
-
canvas.addEventListener("pointermove", this.onPointerMove);
|
|
4729
|
-
canvas.addEventListener("pointerup", this.onPointerUp);
|
|
4730
|
-
this.deps.container.appendChild(canvas);
|
|
4709
|
+
function sameBounds(a, b) {
|
|
4710
|
+
return a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;
|
|
4711
|
+
}
|
|
4712
|
+
var MinimapController = class {
|
|
4713
|
+
constructor(viewport, canvas, options = {}) {
|
|
4714
|
+
this.viewport = viewport;
|
|
4731
4715
|
this.canvas = canvas;
|
|
4716
|
+
this.width = options.width ?? DEFAULT_WIDTH;
|
|
4717
|
+
this.height = options.height ?? DEFAULT_HEIGHT;
|
|
4718
|
+
this.padding = options.padding ?? DEFAULT_PADDING;
|
|
4719
|
+
this.background = options.background ?? null;
|
|
4720
|
+
this.viewportStroke = options.viewportStroke ?? DEFAULT_VIEWPORT_STROKE;
|
|
4721
|
+
this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS2;
|
|
4722
|
+
this.interactive = options.interactive !== false;
|
|
4723
|
+
this.requestFrame = options.requestFrame ?? ((cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : 0);
|
|
4724
|
+
this.cancelFrame = options.cancelFrame ?? ((id) => {
|
|
4725
|
+
if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(id);
|
|
4726
|
+
});
|
|
4727
|
+
this.renderer.setStore(viewport.store);
|
|
4728
|
+
this.renderer.setOnImageLoad(() => this.markSceneDirty());
|
|
4729
|
+
this.applyCanvasSize();
|
|
4730
|
+
const onScene = () => this.markSceneDirty();
|
|
4731
|
+
this.unsubs.push(
|
|
4732
|
+
viewport.store.on("add", onScene),
|
|
4733
|
+
viewport.store.on("remove", onScene),
|
|
4734
|
+
viewport.store.on("update", onScene),
|
|
4735
|
+
viewport.store.on("clear", onScene),
|
|
4736
|
+
viewport.layerManager.on("change", onScene),
|
|
4737
|
+
viewport.camera.onChange(() => this.onViewChanged()),
|
|
4738
|
+
viewport.onResize(() => this.onViewChanged())
|
|
4739
|
+
);
|
|
4740
|
+
if (this.interactive) {
|
|
4741
|
+
canvas.style.touchAction = "none";
|
|
4742
|
+
canvas.style.cursor = "pointer";
|
|
4743
|
+
canvas.addEventListener("pointerdown", this.onPointerDown);
|
|
4744
|
+
canvas.addEventListener("pointermove", this.onPointerMove);
|
|
4745
|
+
canvas.addEventListener("pointerup", this.onPointerUp);
|
|
4746
|
+
canvas.addEventListener("pointercancel", this.onPointerEnd);
|
|
4747
|
+
canvas.addEventListener("lostpointercapture", this.onPointerEnd);
|
|
4748
|
+
}
|
|
4749
|
+
this.renderScene();
|
|
4750
|
+
this.requestDraw();
|
|
4732
4751
|
}
|
|
4733
|
-
|
|
4734
|
-
|
|
4752
|
+
width;
|
|
4753
|
+
height;
|
|
4754
|
+
padding;
|
|
4755
|
+
background;
|
|
4756
|
+
viewportStroke;
|
|
4757
|
+
debounceMs;
|
|
4758
|
+
interactive;
|
|
4759
|
+
requestFrame;
|
|
4760
|
+
cancelFrame;
|
|
4761
|
+
renderer = new ElementRenderer();
|
|
4762
|
+
scene = null;
|
|
4763
|
+
frameId = null;
|
|
4764
|
+
debounceTimer = null;
|
|
4735
4765
|
dragging = false;
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4766
|
+
disposed = false;
|
|
4767
|
+
unsubs = [];
|
|
4768
|
+
setSize(width, height) {
|
|
4769
|
+
if (this.disposed) return;
|
|
4770
|
+
this.width = width;
|
|
4771
|
+
this.height = height;
|
|
4772
|
+
this.applyCanvasSize();
|
|
4773
|
+
this.clearDebounce();
|
|
4774
|
+
this.renderScene();
|
|
4775
|
+
this.requestDraw();
|
|
4739
4776
|
}
|
|
4740
|
-
|
|
4741
|
-
if (this.
|
|
4742
|
-
|
|
4743
|
-
|
|
4777
|
+
requestDraw() {
|
|
4778
|
+
if (this.disposed || this.frameId !== null) return;
|
|
4779
|
+
this.frameId = this.requestFrame(this.draw);
|
|
4780
|
+
}
|
|
4781
|
+
dispose() {
|
|
4782
|
+
if (this.disposed) return;
|
|
4783
|
+
this.disposed = true;
|
|
4784
|
+
this.clearDebounce();
|
|
4785
|
+
if (this.frameId !== null) {
|
|
4786
|
+
this.cancelFrame(this.frameId);
|
|
4787
|
+
this.frameId = null;
|
|
4788
|
+
}
|
|
4789
|
+
for (const unsub of this.unsubs.splice(0)) unsub();
|
|
4790
|
+
if (this.interactive) {
|
|
4791
|
+
this.canvas.removeEventListener("pointerdown", this.onPointerDown);
|
|
4792
|
+
this.canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
4793
|
+
this.canvas.removeEventListener("pointerup", this.onPointerUp);
|
|
4794
|
+
this.canvas.removeEventListener("pointercancel", this.onPointerEnd);
|
|
4795
|
+
this.canvas.removeEventListener("lostpointercapture", this.onPointerEnd);
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
clearDebounce() {
|
|
4799
|
+
if (this.debounceTimer !== null) {
|
|
4800
|
+
clearTimeout(this.debounceTimer);
|
|
4801
|
+
this.debounceTimer = null;
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4804
|
+
dpr() {
|
|
4805
|
+
return typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
4806
|
+
}
|
|
4807
|
+
applyCanvasSize() {
|
|
4808
|
+
const dpr = this.dpr();
|
|
4809
|
+
this.canvas.width = Math.max(1, Math.round(this.width * dpr));
|
|
4810
|
+
this.canvas.height = Math.max(1, Math.round(this.height * dpr));
|
|
4811
|
+
}
|
|
4812
|
+
// Single source for both mapping bounds and rendering: layer-visible,
|
|
4813
|
+
// grids excluded. (getElementBounds already returns null for grids, so they
|
|
4814
|
+
// cannot extend the bounding box today — this filter makes the invariant
|
|
4815
|
+
// structural instead of relying on that special case.)
|
|
4816
|
+
sceneElements() {
|
|
4817
|
+
return this.viewport.store.getAll().filter((el) => el.type !== "grid" && this.viewport.layerManager.isLayerVisible(el.layerId));
|
|
4818
|
+
}
|
|
4819
|
+
currentMapping() {
|
|
4820
|
+
const viewportRect = this.viewport.getVisibleRect();
|
|
4821
|
+
const content = getElementsBoundingBox(this.sceneElements());
|
|
4822
|
+
return content ? unionBounds(content, viewportRect) : viewportRect;
|
|
4823
|
+
}
|
|
4824
|
+
onViewChanged() {
|
|
4825
|
+
if (this.disposed) return;
|
|
4826
|
+
const mapping = this.currentMapping();
|
|
4827
|
+
if (!this.scene || !sameBounds(mapping, this.scene.mapping)) {
|
|
4828
|
+
this.markSceneDirty();
|
|
4744
4829
|
}
|
|
4745
|
-
this.
|
|
4746
|
-
this.canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
4747
|
-
this.canvas.removeEventListener("pointerup", this.onPointerUp);
|
|
4748
|
-
this.canvas.remove();
|
|
4830
|
+
this.requestDraw();
|
|
4749
4831
|
}
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4832
|
+
markSceneDirty() {
|
|
4833
|
+
if (this.disposed) return;
|
|
4834
|
+
this.clearDebounce();
|
|
4835
|
+
this.debounceTimer = setTimeout(() => {
|
|
4836
|
+
this.debounceTimer = null;
|
|
4837
|
+
this.renderScene();
|
|
4838
|
+
this.requestDraw();
|
|
4839
|
+
}, this.debounceMs);
|
|
4840
|
+
}
|
|
4841
|
+
renderScene() {
|
|
4842
|
+
if (this.disposed) return;
|
|
4843
|
+
const dpr = this.dpr();
|
|
4844
|
+
const mapping = this.currentMapping();
|
|
4845
|
+
const transform = computeMinimapTransform(mapping, this.width, this.height, this.padding);
|
|
4846
|
+
const sceneCanvas = document.createElement("canvas");
|
|
4847
|
+
sceneCanvas.width = Math.max(1, Math.round(this.width * dpr));
|
|
4848
|
+
sceneCanvas.height = Math.max(1, Math.round(this.height * dpr));
|
|
4849
|
+
const ctx = sceneCanvas.getContext("2d");
|
|
4850
|
+
if (!ctx) return;
|
|
4851
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
4852
|
+
for (const el of this.sceneElements()) {
|
|
4853
|
+
let list = byLayer.get(el.layerId);
|
|
4854
|
+
if (!list) {
|
|
4855
|
+
list = [];
|
|
4856
|
+
byLayer.set(el.layerId, list);
|
|
4857
|
+
}
|
|
4858
|
+
list.push(el);
|
|
4859
|
+
}
|
|
4860
|
+
for (const layer of this.viewport.layerManager.getLayers()) {
|
|
4861
|
+
const els = byLayer.get(layer.id);
|
|
4862
|
+
if (!els || els.length === 0) continue;
|
|
4863
|
+
const opacity = layer.opacity;
|
|
4864
|
+
if (opacity >= 1) {
|
|
4865
|
+
this.renderLayerElements(ctx, els, transform, dpr);
|
|
4866
|
+
continue;
|
|
4867
|
+
}
|
|
4868
|
+
const layerCanvas = document.createElement("canvas");
|
|
4869
|
+
layerCanvas.width = sceneCanvas.width;
|
|
4870
|
+
layerCanvas.height = sceneCanvas.height;
|
|
4871
|
+
const layerCtx = layerCanvas.getContext("2d");
|
|
4872
|
+
if (!layerCtx) continue;
|
|
4873
|
+
this.renderLayerElements(layerCtx, els, transform, dpr);
|
|
4874
|
+
ctx.save();
|
|
4875
|
+
ctx.globalAlpha = opacity;
|
|
4876
|
+
ctx.drawImage(layerCanvas, 0, 0);
|
|
4877
|
+
ctx.restore();
|
|
4878
|
+
}
|
|
4879
|
+
this.scene = { canvas: sceneCanvas, transform, mapping };
|
|
4880
|
+
}
|
|
4881
|
+
renderLayerElements(ctx, elements, t, dpr) {
|
|
4882
|
+
for (const el of elements) {
|
|
4883
|
+
if (DOM_FALLBACK_TYPES.has(el.type)) {
|
|
4884
|
+
const b = getElementBounds(el);
|
|
4885
|
+
if (!b) continue;
|
|
4886
|
+
const tl = worldToMini(t, { x: b.x, y: b.y });
|
|
4887
|
+
ctx.save();
|
|
4888
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
4889
|
+
ctx.fillStyle = elementColor(el);
|
|
4890
|
+
ctx.fillRect(tl.x, tl.y, Math.max(1, b.w * t.scale), Math.max(1, b.h * t.scale));
|
|
4891
|
+
ctx.restore();
|
|
4892
|
+
continue;
|
|
4893
|
+
}
|
|
4894
|
+
ctx.save();
|
|
4895
|
+
ctx.setTransform(dpr * t.scale, 0, 0, dpr * t.scale, dpr * t.offsetX, dpr * t.offsetY);
|
|
4896
|
+
this.renderer.renderCanvasElement(ctx, el);
|
|
4897
|
+
ctx.restore();
|
|
4898
|
+
}
|
|
4755
4899
|
}
|
|
4756
4900
|
draw = () => {
|
|
4757
|
-
this.
|
|
4901
|
+
this.frameId = null;
|
|
4902
|
+
if (this.disposed) return;
|
|
4758
4903
|
const ctx = this.canvas.getContext("2d");
|
|
4759
4904
|
if (!ctx) return;
|
|
4760
|
-
const
|
|
4761
|
-
|
|
4762
|
-
ctx.clearRect(0, 0,
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
const
|
|
4771
|
-
|
|
4905
|
+
const dpr = this.dpr();
|
|
4906
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
4907
|
+
ctx.clearRect(0, 0, this.width, this.height);
|
|
4908
|
+
if (this.background) {
|
|
4909
|
+
ctx.fillStyle = this.background;
|
|
4910
|
+
ctx.fillRect(0, 0, this.width, this.height);
|
|
4911
|
+
}
|
|
4912
|
+
const scene = this.scene;
|
|
4913
|
+
if (!scene) return;
|
|
4914
|
+
ctx.drawImage(scene.canvas, 0, 0, this.width, this.height);
|
|
4915
|
+
const viewportRect = this.viewport.getVisibleRect();
|
|
4916
|
+
const tl = worldToMini(scene.transform, { x: viewportRect.x, y: viewportRect.y });
|
|
4917
|
+
ctx.strokeStyle = this.viewportStroke;
|
|
4772
4918
|
ctx.lineWidth = 1.5;
|
|
4773
|
-
ctx.strokeRect(
|
|
4919
|
+
ctx.strokeRect(
|
|
4920
|
+
tl.x,
|
|
4921
|
+
tl.y,
|
|
4922
|
+
viewportRect.w * scene.transform.scale,
|
|
4923
|
+
viewportRect.h * scene.transform.scale
|
|
4924
|
+
);
|
|
4774
4925
|
};
|
|
4926
|
+
navTransform() {
|
|
4927
|
+
return this.scene?.transform ?? computeMinimapTransform(this.currentMapping(), this.width, this.height, this.padding);
|
|
4928
|
+
}
|
|
4775
4929
|
navigateFromEvent(e) {
|
|
4776
4930
|
const rect = this.canvas.getBoundingClientRect();
|
|
4777
|
-
const
|
|
4778
|
-
const
|
|
4779
|
-
|
|
4931
|
+
const scaleX = rect.width > 0 ? this.width / rect.width : 1;
|
|
4932
|
+
const scaleY = rect.height > 0 ? this.height / rect.height : 1;
|
|
4933
|
+
const point = {
|
|
4934
|
+
x: (e.clientX - rect.left) * scaleX,
|
|
4935
|
+
y: (e.clientY - rect.top) * scaleY
|
|
4936
|
+
};
|
|
4937
|
+
const world = miniToWorld(this.navTransform(), point);
|
|
4938
|
+
this.viewport.centerCameraAt(world);
|
|
4780
4939
|
}
|
|
4781
4940
|
onPointerDown = (e) => {
|
|
4782
4941
|
e.stopPropagation();
|
|
@@ -4797,6 +4956,45 @@ var Minimap = class {
|
|
|
4797
4956
|
} catch {
|
|
4798
4957
|
}
|
|
4799
4958
|
};
|
|
4959
|
+
// pointercancel / lostpointercapture: a browser gesture or interrupted touch
|
|
4960
|
+
// can end the interaction without a pointerup — the drag must not stick.
|
|
4961
|
+
onPointerEnd = () => {
|
|
4962
|
+
this.dragging = false;
|
|
4963
|
+
};
|
|
4964
|
+
};
|
|
4965
|
+
|
|
4966
|
+
// src/canvas/minimap.ts
|
|
4967
|
+
var WIDTH = 200;
|
|
4968
|
+
var HEIGHT = 140;
|
|
4969
|
+
var MARGIN = 16;
|
|
4970
|
+
var Minimap = class {
|
|
4971
|
+
canvas;
|
|
4972
|
+
controller;
|
|
4973
|
+
constructor(container, viewport) {
|
|
4974
|
+
const canvas = document.createElement("canvas");
|
|
4975
|
+
Object.assign(canvas.style, {
|
|
4976
|
+
position: "absolute",
|
|
4977
|
+
right: `${MARGIN}px`,
|
|
4978
|
+
bottom: `${MARGIN}px`,
|
|
4979
|
+
width: `${WIDTH}px`,
|
|
4980
|
+
height: `${HEIGHT}px`,
|
|
4981
|
+
background: "rgba(255,255,255,0.85)",
|
|
4982
|
+
border: "1px solid rgba(0,0,0,0.15)",
|
|
4983
|
+
borderRadius: "4px",
|
|
4984
|
+
zIndex: "10"
|
|
4985
|
+
});
|
|
4986
|
+
canvas.dataset.fieldnotesMinimap = "true";
|
|
4987
|
+
container.appendChild(canvas);
|
|
4988
|
+
this.canvas = canvas;
|
|
4989
|
+
this.controller = new MinimapController(viewport, canvas, { width: WIDTH, height: HEIGHT });
|
|
4990
|
+
}
|
|
4991
|
+
scheduleDraw() {
|
|
4992
|
+
this.controller.requestDraw();
|
|
4993
|
+
}
|
|
4994
|
+
destroy() {
|
|
4995
|
+
this.controller.dispose();
|
|
4996
|
+
this.canvas.remove();
|
|
4997
|
+
}
|
|
4800
4998
|
};
|
|
4801
4999
|
|
|
4802
5000
|
// src/canvas/viewport-dom.ts
|
|
@@ -7978,25 +8176,7 @@ var Viewport = class {
|
|
|
7978
8176
|
}
|
|
7979
8177
|
this.unsubToolChange = this.toolManager.onChange(() => this.contextMenu?.close());
|
|
7980
8178
|
if (options.minimap) {
|
|
7981
|
-
|
|
7982
|
-
this.minimap = new Minimap({
|
|
7983
|
-
container: this.wrapper,
|
|
7984
|
-
getElements: visibleEls,
|
|
7985
|
-
getContentBounds: () => getElementsBoundingBox(visibleEls()),
|
|
7986
|
-
getViewportRect: () => this.camera.getVisibleRect(this.canvasEl.clientWidth, this.canvasEl.clientHeight),
|
|
7987
|
-
navigateTo: (w) => {
|
|
7988
|
-
const z = this.camera.zoom;
|
|
7989
|
-
this.camera.moveTo(
|
|
7990
|
-
this.canvasEl.clientWidth / 2 - w.x * z,
|
|
7991
|
-
this.canvasEl.clientHeight / 2 - w.y * z
|
|
7992
|
-
);
|
|
7993
|
-
},
|
|
7994
|
-
requestFrame: (cb) => typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(cb) : 0,
|
|
7995
|
-
cancelFrame: (id) => {
|
|
7996
|
-
if (typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(id);
|
|
7997
|
-
}
|
|
7998
|
-
});
|
|
7999
|
-
this.minimap.scheduleDraw();
|
|
8179
|
+
this.minimap = new Minimap(this.wrapper, this);
|
|
8000
8180
|
}
|
|
8001
8181
|
this.domNodeManager = new DomNodeManager({
|
|
8002
8182
|
domLayer: this.paintStack,
|
|
@@ -8032,7 +8212,6 @@ var Viewport = class {
|
|
|
8032
8212
|
this.applyCameraTransform();
|
|
8033
8213
|
this.noteEditor.updateToolbarPosition();
|
|
8034
8214
|
this.contextMenu?.close();
|
|
8035
|
-
this.minimap?.scheduleDraw();
|
|
8036
8215
|
this.requestRender();
|
|
8037
8216
|
});
|
|
8038
8217
|
this.gridController = new GridController({
|
|
@@ -8047,7 +8226,6 @@ var Viewport = class {
|
|
|
8047
8226
|
this.store.on("add", (el) => {
|
|
8048
8227
|
if (el.type === "grid") this.gridController.syncContext();
|
|
8049
8228
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
8050
|
-
this.minimap?.scheduleDraw();
|
|
8051
8229
|
this.requestRender();
|
|
8052
8230
|
}),
|
|
8053
8231
|
this.store.on("remove", (el) => {
|
|
@@ -8055,7 +8233,6 @@ var Viewport = class {
|
|
|
8055
8233
|
this.unbindArrowsFrom(el);
|
|
8056
8234
|
this.domNodeManager.removeDomNode(el.id);
|
|
8057
8235
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
8058
|
-
this.minimap?.scheduleDraw();
|
|
8059
8236
|
this.requestRender();
|
|
8060
8237
|
}),
|
|
8061
8238
|
this.store.on("update", ({ previous, current }) => {
|
|
@@ -8064,21 +8241,18 @@ var Viewport = class {
|
|
|
8064
8241
|
if (previous.layerId !== current.layerId) {
|
|
8065
8242
|
this.renderLoop.markLayerDirty(previous.layerId);
|
|
8066
8243
|
}
|
|
8067
|
-
this.minimap?.scheduleDraw();
|
|
8068
8244
|
this.requestRender();
|
|
8069
8245
|
}),
|
|
8070
8246
|
this.store.on("clear", () => {
|
|
8071
8247
|
this.domNodeManager.clearDomNodes();
|
|
8072
8248
|
this.renderLoop.markAllLayersDirty();
|
|
8073
8249
|
this.gridController.syncContext();
|
|
8074
|
-
this.minimap?.scheduleDraw();
|
|
8075
8250
|
this.requestRender();
|
|
8076
8251
|
})
|
|
8077
8252
|
];
|
|
8078
8253
|
this.layerManager.on("change", () => {
|
|
8079
8254
|
this.toolContext.activeLayerId = this.layerManager.activeLayerId;
|
|
8080
8255
|
this.renderLoop.markAllLayersDirty();
|
|
8081
|
-
this.minimap?.scheduleDraw();
|
|
8082
8256
|
this.requestRender();
|
|
8083
8257
|
});
|
|
8084
8258
|
this.interactions = new ViewportInteractions({
|
|
@@ -8142,6 +8316,7 @@ var Viewport = class {
|
|
|
8142
8316
|
contextMenu = null;
|
|
8143
8317
|
minimap = null;
|
|
8144
8318
|
htmlRenderers = /* @__PURE__ */ new Map();
|
|
8319
|
+
resizeListeners = /* @__PURE__ */ new Set();
|
|
8145
8320
|
get ctx() {
|
|
8146
8321
|
return this.canvasEl.getContext("2d");
|
|
8147
8322
|
}
|
|
@@ -8166,6 +8341,27 @@ var Viewport = class {
|
|
|
8166
8341
|
if (!bbox) return;
|
|
8167
8342
|
this.camera.fitToContent(bbox, this.wrapper.clientWidth, this.wrapper.clientHeight, padding);
|
|
8168
8343
|
}
|
|
8344
|
+
/** World-space rectangle currently visible through the canvas. */
|
|
8345
|
+
getVisibleRect() {
|
|
8346
|
+
return this.camera.getVisibleRect(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
|
|
8347
|
+
}
|
|
8348
|
+
/** Centers the camera on a world point without changing zoom. */
|
|
8349
|
+
centerCameraAt(world) {
|
|
8350
|
+
const z = this.camera.zoom;
|
|
8351
|
+
this.camera.moveTo(
|
|
8352
|
+
this.canvasEl.clientWidth / 2 - world.x * z,
|
|
8353
|
+
this.canvasEl.clientHeight / 2 - world.y * z
|
|
8354
|
+
);
|
|
8355
|
+
}
|
|
8356
|
+
/**
|
|
8357
|
+
* Notifies after the host container resizes (ResizeObserver-driven). A resize
|
|
8358
|
+
* changes the visible world rect without a camera event; overlays such as the
|
|
8359
|
+
* minimap subscribe to stay current. Returns an idempotent unsubscribe.
|
|
8360
|
+
*/
|
|
8361
|
+
onResize(listener) {
|
|
8362
|
+
this.resizeListeners.add(listener);
|
|
8363
|
+
return () => this.resizeListeners.delete(listener);
|
|
8364
|
+
}
|
|
8169
8365
|
requestRender() {
|
|
8170
8366
|
this.renderLoop.requestRender();
|
|
8171
8367
|
}
|
|
@@ -8470,6 +8666,7 @@ var Viewport = class {
|
|
|
8470
8666
|
this.unsubStore.forEach((fn) => fn());
|
|
8471
8667
|
this.resizeObserver?.disconnect();
|
|
8472
8668
|
this.resizeObserver = null;
|
|
8669
|
+
this.resizeListeners.clear();
|
|
8473
8670
|
this.wrapper.remove();
|
|
8474
8671
|
}
|
|
8475
8672
|
stopInteracting() {
|
|
@@ -8519,6 +8716,7 @@ var Viewport = class {
|
|
|
8519
8716
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
8520
8717
|
this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
|
|
8521
8718
|
this.requestRender();
|
|
8719
|
+
this.resizeListeners.forEach((fn) => fn());
|
|
8522
8720
|
}
|
|
8523
8721
|
observeResize() {
|
|
8524
8722
|
if (typeof ResizeObserver === "undefined") return;
|
|
@@ -8558,7 +8756,7 @@ function toLaserTrailPresence(emission) {
|
|
|
8558
8756
|
};
|
|
8559
8757
|
}
|
|
8560
8758
|
var DEFAULT_COLOR = "#ff3b30";
|
|
8561
|
-
var
|
|
8759
|
+
var DEFAULT_WIDTH2 = 4;
|
|
8562
8760
|
var DEFAULT_FADE_MS = 1200;
|
|
8563
8761
|
var DEFAULT_MAX_POINTS = 512;
|
|
8564
8762
|
var RemoteLaserOverlay = class {
|
|
@@ -8574,7 +8772,7 @@ var RemoteLaserOverlay = class {
|
|
|
8574
8772
|
constructor(host, options = {}) {
|
|
8575
8773
|
this.host = host;
|
|
8576
8774
|
this.color = options.color ?? DEFAULT_COLOR;
|
|
8577
|
-
this.width = options.width ??
|
|
8775
|
+
this.width = options.width ?? DEFAULT_WIDTH2;
|
|
8578
8776
|
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS;
|
|
8579
8777
|
this.maxPointsPerSender = options.maxPointsPerSender ?? DEFAULT_MAX_POINTS;
|
|
8580
8778
|
this.unregister = host.registerOverlay((ctx) => this.renderTrails(ctx));
|
|
@@ -8845,10 +9043,218 @@ var RemotePingOverlay = class {
|
|
|
8845
9043
|
}
|
|
8846
9044
|
};
|
|
8847
9045
|
|
|
9046
|
+
// src/canvas/measure-render.ts
|
|
9047
|
+
function formatMeasureLabel(feet) {
|
|
9048
|
+
return `${Math.round(feet)} ft`;
|
|
9049
|
+
}
|
|
9050
|
+
function drawMeasurement(ctx, m, opts = {}) {
|
|
9051
|
+
ctx.save();
|
|
9052
|
+
if (opts.alpha !== void 0) ctx.globalAlpha = opts.alpha;
|
|
9053
|
+
ctx.strokeStyle = m.color;
|
|
9054
|
+
ctx.setLineDash([8, 4]);
|
|
9055
|
+
ctx.lineWidth = 2;
|
|
9056
|
+
ctx.beginPath();
|
|
9057
|
+
ctx.moveTo(m.start.x, m.start.y);
|
|
9058
|
+
ctx.lineTo(m.end.x, m.end.y);
|
|
9059
|
+
ctx.stroke();
|
|
9060
|
+
ctx.setLineDash([]);
|
|
9061
|
+
ctx.fillStyle = m.color;
|
|
9062
|
+
const dotRadius = 4;
|
|
9063
|
+
ctx.beginPath();
|
|
9064
|
+
ctx.arc(m.start.x, m.start.y, dotRadius, 0, Math.PI * 2);
|
|
9065
|
+
ctx.fill();
|
|
9066
|
+
ctx.beginPath();
|
|
9067
|
+
ctx.arc(m.end.x, m.end.y, dotRadius, 0, Math.PI * 2);
|
|
9068
|
+
ctx.fill();
|
|
9069
|
+
const label = formatMeasureLabel(m.feet);
|
|
9070
|
+
const midX = (m.start.x + m.end.x) / 2;
|
|
9071
|
+
const midY = (m.start.y + m.end.y) / 2;
|
|
9072
|
+
ctx.font = "14px sans-serif";
|
|
9073
|
+
const metrics = ctx.measureText(label);
|
|
9074
|
+
const padX = 6;
|
|
9075
|
+
const padY = 4;
|
|
9076
|
+
const textH = 14;
|
|
9077
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
|
|
9078
|
+
ctx.beginPath();
|
|
9079
|
+
ctx.roundRect(
|
|
9080
|
+
midX - metrics.width / 2 - padX,
|
|
9081
|
+
midY - textH / 2 - padY,
|
|
9082
|
+
metrics.width + padX * 2,
|
|
9083
|
+
textH + padY * 2,
|
|
9084
|
+
4
|
|
9085
|
+
);
|
|
9086
|
+
ctx.fill();
|
|
9087
|
+
ctx.fillStyle = "#FFFFFF";
|
|
9088
|
+
ctx.textAlign = "center";
|
|
9089
|
+
ctx.textBaseline = "middle";
|
|
9090
|
+
ctx.fillText(label, midX, midY);
|
|
9091
|
+
ctx.restore();
|
|
9092
|
+
}
|
|
9093
|
+
|
|
9094
|
+
// src/canvas/remote-measure-overlay.ts
|
|
9095
|
+
var MEASURE_PRESENCE_KIND = "measure";
|
|
9096
|
+
function isFinitePoint2(value) {
|
|
9097
|
+
if (typeof value !== "object" || value === null) return false;
|
|
9098
|
+
const point = value;
|
|
9099
|
+
return typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y);
|
|
9100
|
+
}
|
|
9101
|
+
function isMeasurePresence(data) {
|
|
9102
|
+
if (typeof data !== "object" || data === null) return false;
|
|
9103
|
+
const payload = data;
|
|
9104
|
+
if (payload.kind !== MEASURE_PRESENCE_KIND) return false;
|
|
9105
|
+
if ("cleared" in payload) return payload.cleared === true;
|
|
9106
|
+
if (!isFinitePoint2(payload.start) || !isFinitePoint2(payload.end)) return false;
|
|
9107
|
+
if (typeof payload.cells !== "number" || !Number.isFinite(payload.cells)) return false;
|
|
9108
|
+
if (typeof payload.feet !== "number" || !Number.isFinite(payload.feet)) return false;
|
|
9109
|
+
if (payload.color !== void 0 && typeof payload.color !== "string") return false;
|
|
9110
|
+
return true;
|
|
9111
|
+
}
|
|
9112
|
+
function toMeasurePresence(emission) {
|
|
9113
|
+
if (emission === null) return { kind: MEASURE_PRESENCE_KIND, cleared: true };
|
|
9114
|
+
return {
|
|
9115
|
+
kind: MEASURE_PRESENCE_KIND,
|
|
9116
|
+
start: emission.start,
|
|
9117
|
+
end: emission.end,
|
|
9118
|
+
cells: emission.cells,
|
|
9119
|
+
feet: emission.feet,
|
|
9120
|
+
color: emission.color
|
|
9121
|
+
};
|
|
9122
|
+
}
|
|
9123
|
+
var DEFAULT_COLOR3 = "#FF5722";
|
|
9124
|
+
var DEFAULT_HOLD_MS = 1500;
|
|
9125
|
+
var DEFAULT_FADE_MS2 = 400;
|
|
9126
|
+
var DEFAULT_MAX_AGE_MS = 3e4;
|
|
9127
|
+
var RemoteMeasureOverlay = class {
|
|
9128
|
+
host;
|
|
9129
|
+
color;
|
|
9130
|
+
holdMs;
|
|
9131
|
+
fadeMs;
|
|
9132
|
+
maxAgeMs;
|
|
9133
|
+
measurements = /* @__PURE__ */ new Map();
|
|
9134
|
+
unregister;
|
|
9135
|
+
rafId = null;
|
|
9136
|
+
disposed = false;
|
|
9137
|
+
constructor(host, options = {}) {
|
|
9138
|
+
this.host = host;
|
|
9139
|
+
this.color = options.color ?? DEFAULT_COLOR3;
|
|
9140
|
+
this.holdMs = options.holdMs ?? DEFAULT_HOLD_MS;
|
|
9141
|
+
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS2;
|
|
9142
|
+
this.maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
9143
|
+
this.unregister = host.registerOverlay((ctx) => this.renderMeasurements(ctx));
|
|
9144
|
+
}
|
|
9145
|
+
now() {
|
|
9146
|
+
return performance.now();
|
|
9147
|
+
}
|
|
9148
|
+
/**
|
|
9149
|
+
* Applies a presence payload from `sender` (any opaque per-sender key, e.g.
|
|
9150
|
+
* the envelope `from`). Non-measure or malformed payloads are ignored and
|
|
9151
|
+
* reported as `false`, so hosts can feed every presence frame through.
|
|
9152
|
+
*/
|
|
9153
|
+
apply(sender, data) {
|
|
9154
|
+
if (this.disposed || !isMeasurePresence(data)) return false;
|
|
9155
|
+
if ("cleared" in data) {
|
|
9156
|
+
this.beginLinger(sender);
|
|
9157
|
+
return true;
|
|
9158
|
+
}
|
|
9159
|
+
const existing = this.measurements.get(sender);
|
|
9160
|
+
if (existing?.expiryTimer != null) clearTimeout(existing.expiryTimer);
|
|
9161
|
+
this.measurements.set(sender, {
|
|
9162
|
+
start: data.start,
|
|
9163
|
+
end: data.end,
|
|
9164
|
+
feet: data.feet,
|
|
9165
|
+
color: data.color ?? this.color,
|
|
9166
|
+
clearedAt: null,
|
|
9167
|
+
expiryTimer: setTimeout(() => this.beginLinger(sender), this.maxAgeMs)
|
|
9168
|
+
});
|
|
9169
|
+
this.host.requestRender();
|
|
9170
|
+
return true;
|
|
9171
|
+
}
|
|
9172
|
+
/** Removes a sender's ruler immediately (presence-leave/disconnect). */
|
|
9173
|
+
remove(sender) {
|
|
9174
|
+
const entry = this.measurements.get(sender);
|
|
9175
|
+
if (!entry) return;
|
|
9176
|
+
if (entry.expiryTimer != null) clearTimeout(entry.expiryTimer);
|
|
9177
|
+
this.measurements.delete(sender);
|
|
9178
|
+
this.host.requestRender();
|
|
9179
|
+
}
|
|
9180
|
+
/** Removes every ruler immediately. */
|
|
9181
|
+
clear() {
|
|
9182
|
+
if (this.measurements.size === 0) return;
|
|
9183
|
+
for (const entry of this.measurements.values()) {
|
|
9184
|
+
if (entry.expiryTimer != null) clearTimeout(entry.expiryTimer);
|
|
9185
|
+
}
|
|
9186
|
+
this.measurements.clear();
|
|
9187
|
+
this.host.requestRender();
|
|
9188
|
+
}
|
|
9189
|
+
/** Number of senders with a visible (active or lingering) ruler. */
|
|
9190
|
+
get activeSenderCount() {
|
|
9191
|
+
return this.measurements.size;
|
|
9192
|
+
}
|
|
9193
|
+
/** Unregisters the overlay, cancels timers, stops animating. Idempotent. */
|
|
9194
|
+
dispose() {
|
|
9195
|
+
if (this.disposed) return;
|
|
9196
|
+
this.disposed = true;
|
|
9197
|
+
if (this.rafId !== null) {
|
|
9198
|
+
cancelAnimationFrame(this.rafId);
|
|
9199
|
+
this.rafId = null;
|
|
9200
|
+
}
|
|
9201
|
+
for (const entry of this.measurements.values()) {
|
|
9202
|
+
if (entry.expiryTimer != null) clearTimeout(entry.expiryTimer);
|
|
9203
|
+
}
|
|
9204
|
+
this.measurements.clear();
|
|
9205
|
+
this.unregister?.();
|
|
9206
|
+
this.unregister = null;
|
|
9207
|
+
this.host.requestRender();
|
|
9208
|
+
}
|
|
9209
|
+
beginLinger(sender) {
|
|
9210
|
+
const entry = this.measurements.get(sender);
|
|
9211
|
+
if (!entry || entry.clearedAt !== null) return;
|
|
9212
|
+
if (entry.expiryTimer != null) {
|
|
9213
|
+
clearTimeout(entry.expiryTimer);
|
|
9214
|
+
entry.expiryTimer = null;
|
|
9215
|
+
}
|
|
9216
|
+
entry.clearedAt = this.now();
|
|
9217
|
+
this.ensureAnimating();
|
|
9218
|
+
this.host.requestRender();
|
|
9219
|
+
}
|
|
9220
|
+
ensureAnimating() {
|
|
9221
|
+
if (this.rafId === null) {
|
|
9222
|
+
this.rafId = requestAnimationFrame(() => this.tick());
|
|
9223
|
+
}
|
|
9224
|
+
}
|
|
9225
|
+
tick() {
|
|
9226
|
+
if (this.disposed) return;
|
|
9227
|
+
const now = this.now();
|
|
9228
|
+
let lingering = 0;
|
|
9229
|
+
for (const [sender, entry] of this.measurements) {
|
|
9230
|
+
if (entry.clearedAt === null) continue;
|
|
9231
|
+
if (now - entry.clearedAt >= this.holdMs + this.fadeMs) {
|
|
9232
|
+
this.measurements.delete(sender);
|
|
9233
|
+
} else {
|
|
9234
|
+
lingering += 1;
|
|
9235
|
+
}
|
|
9236
|
+
}
|
|
9237
|
+
this.host.requestRender();
|
|
9238
|
+
this.rafId = lingering > 0 ? requestAnimationFrame(() => this.tick()) : null;
|
|
9239
|
+
}
|
|
9240
|
+
renderMeasurements(ctx) {
|
|
9241
|
+
if (this.measurements.size === 0) return;
|
|
9242
|
+
const now = this.now();
|
|
9243
|
+
for (const entry of this.measurements.values()) {
|
|
9244
|
+
let alpha = 1;
|
|
9245
|
+
if (entry.clearedAt !== null) {
|
|
9246
|
+
const fadeAge = now - entry.clearedAt - this.holdMs;
|
|
9247
|
+
if (fadeAge > 0) alpha = Math.max(0, 1 - fadeAge / this.fadeMs);
|
|
9248
|
+
}
|
|
9249
|
+
drawMeasurement(ctx, entry, { alpha });
|
|
9250
|
+
}
|
|
9251
|
+
}
|
|
9252
|
+
};
|
|
9253
|
+
|
|
8848
9254
|
// src/canvas/ping-input.ts
|
|
8849
9255
|
var DEFAULT_LONG_PRESS_MS = 600;
|
|
8850
9256
|
var DEFAULT_SLOP_PX = 8;
|
|
8851
|
-
var
|
|
9257
|
+
var DEFAULT_COLOR4 = "#ff3b30";
|
|
8852
9258
|
var DEFAULT_DURATION_MS2 = 1800;
|
|
8853
9259
|
var DEFAULT_RADIUS2 = 48;
|
|
8854
9260
|
var DEFAULT_MIN_INTERVAL_MS = 300;
|
|
@@ -8881,7 +9287,7 @@ var PingInput = class {
|
|
|
8881
9287
|
this.longPressEnabled = options.longPressEnabled ?? false;
|
|
8882
9288
|
this.longPressMs = options.longPressMs ?? DEFAULT_LONG_PRESS_MS;
|
|
8883
9289
|
this.slopPx = options.slopPx ?? DEFAULT_SLOP_PX;
|
|
8884
|
-
this.color = options.color ??
|
|
9290
|
+
this.color = options.color ?? DEFAULT_COLOR4;
|
|
8885
9291
|
this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS2;
|
|
8886
9292
|
this.radius = options.radius ?? DEFAULT_RADIUS2;
|
|
8887
9293
|
this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
|
|
@@ -11171,21 +11577,37 @@ var MeasureTool = class {
|
|
|
11171
11577
|
gridType;
|
|
11172
11578
|
hexOrientation;
|
|
11173
11579
|
feetPerCell;
|
|
11580
|
+
color;
|
|
11174
11581
|
optionListeners = /* @__PURE__ */ new Set();
|
|
11582
|
+
measurementListeners = /* @__PURE__ */ new Set();
|
|
11583
|
+
emissionRafId = null;
|
|
11175
11584
|
constructor(options = {}) {
|
|
11176
11585
|
this.feetPerCell = options.feetPerCell ?? 5;
|
|
11586
|
+
this.color = options.color ?? "#FF5722";
|
|
11177
11587
|
}
|
|
11178
11588
|
getOptions() {
|
|
11179
|
-
return { feetPerCell: this.feetPerCell };
|
|
11589
|
+
return { feetPerCell: this.feetPerCell, color: this.color };
|
|
11180
11590
|
}
|
|
11181
11591
|
setOptions(options) {
|
|
11182
11592
|
if (options.feetPerCell !== void 0) this.feetPerCell = options.feetPerCell;
|
|
11593
|
+
if (options.color !== void 0) this.color = options.color;
|
|
11183
11594
|
this.notifyOptionsChange();
|
|
11184
11595
|
}
|
|
11185
11596
|
onOptionsChange(listener) {
|
|
11186
11597
|
this.optionListeners.add(listener);
|
|
11187
11598
|
return () => this.optionListeners.delete(listener);
|
|
11188
11599
|
}
|
|
11600
|
+
/**
|
|
11601
|
+
* Subscribes to raf-coalesced measurement snapshots. While a measurement is
|
|
11602
|
+
* in progress, listeners receive at most one snapshot per animation frame
|
|
11603
|
+
* carrying the latest state; `null` is delivered synchronously when the
|
|
11604
|
+
* measurement clears (pointer-up or deactivate). Emissions are ephemeral by
|
|
11605
|
+
* contract: presence only — never elements, history, or persisted state.
|
|
11606
|
+
*/
|
|
11607
|
+
onMeasurement(listener) {
|
|
11608
|
+
this.measurementListeners.add(listener);
|
|
11609
|
+
return () => this.measurementListeners.delete(listener);
|
|
11610
|
+
}
|
|
11189
11611
|
onPointerDown(state, ctx) {
|
|
11190
11612
|
this.gridSize = ctx.gridSize ?? 1;
|
|
11191
11613
|
this.gridType = ctx.gridType;
|
|
@@ -11193,22 +11615,27 @@ var MeasureTool = class {
|
|
|
11193
11615
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
11194
11616
|
this.start = this.snapToGrid(world, ctx);
|
|
11195
11617
|
this.end = { ...this.start };
|
|
11618
|
+
this.scheduleEmission();
|
|
11196
11619
|
}
|
|
11197
11620
|
onPointerMove(state, ctx) {
|
|
11198
11621
|
if (!this.start) return;
|
|
11199
11622
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
11200
11623
|
this.end = this.snapToGrid(world, ctx);
|
|
11201
11624
|
ctx.requestRender();
|
|
11625
|
+
this.scheduleEmission();
|
|
11202
11626
|
}
|
|
11203
11627
|
onPointerUp(_state, ctx) {
|
|
11204
11628
|
if (!this.start) return;
|
|
11205
11629
|
this.start = null;
|
|
11206
11630
|
this.end = null;
|
|
11207
11631
|
ctx.requestRender();
|
|
11632
|
+
this.emitClear();
|
|
11208
11633
|
}
|
|
11209
11634
|
onDeactivate(_ctx) {
|
|
11635
|
+
const wasActive = this.start !== null;
|
|
11210
11636
|
this.start = null;
|
|
11211
11637
|
this.end = null;
|
|
11638
|
+
if (wasActive) this.emitClear();
|
|
11212
11639
|
}
|
|
11213
11640
|
getMeasurement() {
|
|
11214
11641
|
if (!this.start || !this.end) return null;
|
|
@@ -11234,46 +11661,7 @@ var MeasureTool = class {
|
|
|
11234
11661
|
renderOverlay(ctx) {
|
|
11235
11662
|
const m = this.getMeasurement();
|
|
11236
11663
|
if (!m) return;
|
|
11237
|
-
ctx.
|
|
11238
|
-
ctx.strokeStyle = "#FF5722";
|
|
11239
|
-
ctx.setLineDash([8, 4]);
|
|
11240
|
-
ctx.lineWidth = 2;
|
|
11241
|
-
ctx.beginPath();
|
|
11242
|
-
ctx.moveTo(m.start.x, m.start.y);
|
|
11243
|
-
ctx.lineTo(m.end.x, m.end.y);
|
|
11244
|
-
ctx.stroke();
|
|
11245
|
-
ctx.setLineDash([]);
|
|
11246
|
-
ctx.fillStyle = "#FF5722";
|
|
11247
|
-
const dotRadius = 4;
|
|
11248
|
-
ctx.beginPath();
|
|
11249
|
-
ctx.arc(m.start.x, m.start.y, dotRadius, 0, Math.PI * 2);
|
|
11250
|
-
ctx.fill();
|
|
11251
|
-
ctx.beginPath();
|
|
11252
|
-
ctx.arc(m.end.x, m.end.y, dotRadius, 0, Math.PI * 2);
|
|
11253
|
-
ctx.fill();
|
|
11254
|
-
const label = `${Math.round(m.feet)} ft`;
|
|
11255
|
-
const midX = (m.start.x + m.end.x) / 2;
|
|
11256
|
-
const midY = (m.start.y + m.end.y) / 2;
|
|
11257
|
-
ctx.font = "14px sans-serif";
|
|
11258
|
-
const metrics = ctx.measureText(label);
|
|
11259
|
-
const padX = 6;
|
|
11260
|
-
const padY = 4;
|
|
11261
|
-
const textH = 14;
|
|
11262
|
-
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
|
|
11263
|
-
ctx.beginPath();
|
|
11264
|
-
ctx.roundRect(
|
|
11265
|
-
midX - metrics.width / 2 - padX,
|
|
11266
|
-
midY - textH / 2 - padY,
|
|
11267
|
-
metrics.width + padX * 2,
|
|
11268
|
-
textH + padY * 2,
|
|
11269
|
-
4
|
|
11270
|
-
);
|
|
11271
|
-
ctx.fill();
|
|
11272
|
-
ctx.fillStyle = "#FFFFFF";
|
|
11273
|
-
ctx.textAlign = "center";
|
|
11274
|
-
ctx.textBaseline = "middle";
|
|
11275
|
-
ctx.fillText(label, midX, midY);
|
|
11276
|
-
ctx.restore();
|
|
11664
|
+
drawMeasurement(ctx, { start: m.start, end: m.end, feet: m.feet, color: this.color });
|
|
11277
11665
|
}
|
|
11278
11666
|
snapToGrid(point, ctx) {
|
|
11279
11667
|
if (!ctx.gridSize) return point;
|
|
@@ -11291,6 +11679,32 @@ var MeasureTool = class {
|
|
|
11291
11679
|
notifyOptionsChange() {
|
|
11292
11680
|
for (const listener of this.optionListeners) listener();
|
|
11293
11681
|
}
|
|
11682
|
+
scheduleEmission() {
|
|
11683
|
+
if (this.measurementListeners.size === 0) return;
|
|
11684
|
+
if (this.emissionRafId !== null) return;
|
|
11685
|
+
this.emissionRafId = requestAnimationFrame(() => {
|
|
11686
|
+
this.emissionRafId = null;
|
|
11687
|
+
const m = this.getMeasurement();
|
|
11688
|
+
if (!m) return;
|
|
11689
|
+
this.emit({ ...m, color: this.color });
|
|
11690
|
+
});
|
|
11691
|
+
}
|
|
11692
|
+
emitClear() {
|
|
11693
|
+
if (this.emissionRafId !== null) {
|
|
11694
|
+
cancelAnimationFrame(this.emissionRafId);
|
|
11695
|
+
this.emissionRafId = null;
|
|
11696
|
+
}
|
|
11697
|
+
if (this.measurementListeners.size === 0) return;
|
|
11698
|
+
this.emit(null);
|
|
11699
|
+
}
|
|
11700
|
+
emit(emission) {
|
|
11701
|
+
for (const listener of this.measurementListeners) {
|
|
11702
|
+
try {
|
|
11703
|
+
listener(emission);
|
|
11704
|
+
} catch {
|
|
11705
|
+
}
|
|
11706
|
+
}
|
|
11707
|
+
}
|
|
11294
11708
|
};
|
|
11295
11709
|
|
|
11296
11710
|
// src/tools/template-tool.ts
|
|
@@ -11586,9 +12000,9 @@ var TemplateTool = class {
|
|
|
11586
12000
|
};
|
|
11587
12001
|
|
|
11588
12002
|
// src/tools/laser-tool.ts
|
|
11589
|
-
var
|
|
11590
|
-
var
|
|
11591
|
-
var
|
|
12003
|
+
var DEFAULT_COLOR5 = "#ff3b30";
|
|
12004
|
+
var DEFAULT_WIDTH3 = 4;
|
|
12005
|
+
var DEFAULT_FADE_MS3 = 1200;
|
|
11592
12006
|
var LaserTool = class {
|
|
11593
12007
|
name;
|
|
11594
12008
|
color;
|
|
@@ -11602,9 +12016,9 @@ var LaserTool = class {
|
|
|
11602
12016
|
pendingEmission = [];
|
|
11603
12017
|
constructor(options = {}) {
|
|
11604
12018
|
this.name = options.name ?? "laser";
|
|
11605
|
-
this.color = options.color ??
|
|
11606
|
-
this.width = options.width ??
|
|
11607
|
-
this.fadeMs = options.fadeMs ??
|
|
12019
|
+
this.color = options.color ?? DEFAULT_COLOR5;
|
|
12020
|
+
this.width = options.width ?? DEFAULT_WIDTH3;
|
|
12021
|
+
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS3;
|
|
11608
12022
|
}
|
|
11609
12023
|
now() {
|
|
11610
12024
|
return performance.now();
|
|
@@ -11730,7 +12144,7 @@ var LaserTool = class {
|
|
|
11730
12144
|
};
|
|
11731
12145
|
|
|
11732
12146
|
// src/tools/ping-tool.ts
|
|
11733
|
-
var
|
|
12147
|
+
var DEFAULT_COLOR6 = "#ff3b30";
|
|
11734
12148
|
var DEFAULT_DURATION_MS3 = 1800;
|
|
11735
12149
|
var DEFAULT_RADIUS4 = 48;
|
|
11736
12150
|
var DEFAULT_MIN_INTERVAL_MS2 = 300;
|
|
@@ -11747,7 +12161,7 @@ var PingTool = class {
|
|
|
11747
12161
|
pingListeners = /* @__PURE__ */ new Set();
|
|
11748
12162
|
constructor(options = {}) {
|
|
11749
12163
|
this.name = options.name ?? "ping";
|
|
11750
|
-
this.color = options.color ??
|
|
12164
|
+
this.color = options.color ?? DEFAULT_COLOR6;
|
|
11751
12165
|
this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS3;
|
|
11752
12166
|
this.radius = options.radius ?? DEFAULT_RADIUS4;
|
|
11753
12167
|
this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS2;
|
|
@@ -11850,7 +12264,7 @@ var PingTool = class {
|
|
|
11850
12264
|
};
|
|
11851
12265
|
|
|
11852
12266
|
// src/index.ts
|
|
11853
|
-
var VERSION = "0.
|
|
12267
|
+
var VERSION = "0.58.0";
|
|
11854
12268
|
export {
|
|
11855
12269
|
ArrowTool,
|
|
11856
12270
|
AutoSave,
|
|
@@ -11866,14 +12280,17 @@ export {
|
|
|
11866
12280
|
LaserTool,
|
|
11867
12281
|
LayerManager,
|
|
11868
12282
|
LocalStorageAdapter,
|
|
12283
|
+
MEASURE_PRESENCE_KIND,
|
|
11869
12284
|
MeasureTool,
|
|
11870
12285
|
MemoryAdapter,
|
|
12286
|
+
MinimapController,
|
|
11871
12287
|
NoteTool,
|
|
11872
12288
|
PING_PRESENCE_KIND,
|
|
11873
12289
|
PencilTool,
|
|
11874
12290
|
PingInput,
|
|
11875
12291
|
PingTool,
|
|
11876
12292
|
RemoteLaserOverlay,
|
|
12293
|
+
RemoteMeasureOverlay,
|
|
11877
12294
|
RemotePingOverlay,
|
|
11878
12295
|
SelectTool,
|
|
11879
12296
|
ShapeTool,
|
|
@@ -11911,6 +12328,7 @@ export {
|
|
|
11911
12328
|
getHexCellsInSquare,
|
|
11912
12329
|
getHexDistance,
|
|
11913
12330
|
isLaserTrailPresence,
|
|
12331
|
+
isMeasurePresence,
|
|
11914
12332
|
isNearBezier,
|
|
11915
12333
|
isPingPresence,
|
|
11916
12334
|
setFontSize,
|
|
@@ -11919,6 +12337,7 @@ export {
|
|
|
11919
12337
|
snapToHexCenter,
|
|
11920
12338
|
styleToPatch,
|
|
11921
12339
|
toLaserTrailPresence,
|
|
12340
|
+
toMeasurePresence,
|
|
11922
12341
|
toPingPresence,
|
|
11923
12342
|
toggleBold,
|
|
11924
12343
|
toggleItalic,
|