@fieldnotes/core 0.57.0 → 0.59.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 +348 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -2
- package/dist/index.d.ts +107 -2
- package/dist/index.js +347 -97
- 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
|
|
@@ -5555,6 +5753,19 @@ function computeBounds(elements, padding) {
|
|
|
5555
5753
|
h: maxY - minY + padding * 2
|
|
5556
5754
|
};
|
|
5557
5755
|
}
|
|
5756
|
+
function resolveExportBounds(region, elements, padding) {
|
|
5757
|
+
if (!region) return computeBounds(elements, padding);
|
|
5758
|
+
const finite = Number.isFinite(region.x) && Number.isFinite(region.y) && Number.isFinite(region.w) && Number.isFinite(region.h);
|
|
5759
|
+
if (!finite || region.w <= 0 || region.h <= 0) {
|
|
5760
|
+
throw new RangeError("region must have finite coordinates and positive w/h");
|
|
5761
|
+
}
|
|
5762
|
+
return {
|
|
5763
|
+
x: region.x - padding,
|
|
5764
|
+
y: region.y - padding,
|
|
5765
|
+
w: region.w + padding * 2,
|
|
5766
|
+
h: region.h + padding * 2
|
|
5767
|
+
};
|
|
5768
|
+
}
|
|
5558
5769
|
function renderGridForBounds(ctx, grid, bounds) {
|
|
5559
5770
|
const visibleBounds = {
|
|
5560
5771
|
minX: bounds.x,
|
|
@@ -5597,6 +5808,29 @@ function nonNegativeOption(value, fallback, name) {
|
|
|
5597
5808
|
}
|
|
5598
5809
|
return resolved;
|
|
5599
5810
|
}
|
|
5811
|
+
function fitExportScale(bounds, requestedScale, options) {
|
|
5812
|
+
const maxDimension = positiveOption(options.maxDimension, DEFAULT_MAX_DIMENSION, "maxDimension");
|
|
5813
|
+
const maxPixels = positiveOption(options.maxPixels, DEFAULT_MAX_PIXELS, "maxPixels");
|
|
5814
|
+
const fits = (scale) => {
|
|
5815
|
+
const w = Math.ceil(bounds.w * scale);
|
|
5816
|
+
const h = Math.ceil(bounds.h * scale);
|
|
5817
|
+
return w <= maxDimension && h <= maxDimension && w * h <= maxPixels;
|
|
5818
|
+
};
|
|
5819
|
+
if (fits(requestedScale)) return requestedScale;
|
|
5820
|
+
const analytical = Math.min(
|
|
5821
|
+
maxDimension / Math.max(bounds.w, bounds.h),
|
|
5822
|
+
Math.sqrt(maxPixels) / Math.sqrt(bounds.w) / Math.sqrt(bounds.h)
|
|
5823
|
+
);
|
|
5824
|
+
let hi = Math.min(requestedScale, analytical);
|
|
5825
|
+
if (fits(hi)) return hi;
|
|
5826
|
+
let lo = 0;
|
|
5827
|
+
for (let i = 0; i < 40; i++) {
|
|
5828
|
+
const mid = (lo + hi) / 2;
|
|
5829
|
+
if (fits(mid)) lo = mid;
|
|
5830
|
+
else hi = mid;
|
|
5831
|
+
}
|
|
5832
|
+
return lo;
|
|
5833
|
+
}
|
|
5600
5834
|
function assertExportSize(width, height, options) {
|
|
5601
5835
|
const maxDimension = positiveOption(options.maxDimension, DEFAULT_MAX_DIMENSION, "maxDimension");
|
|
5602
5836
|
const maxPixels = positiveOption(options.maxPixels, DEFAULT_MAX_PIXELS, "maxPixels");
|
|
@@ -5671,10 +5905,23 @@ function loadImages(elements, options = {}) {
|
|
|
5671
5905
|
});
|
|
5672
5906
|
}
|
|
5673
5907
|
async function exportImage(store, options = {}, layerManager) {
|
|
5674
|
-
const
|
|
5908
|
+
const requestedScale = positiveOption(options.scale, 2, "scale");
|
|
5909
|
+
const scaleMode = options.scaleMode ?? "exact";
|
|
5910
|
+
if (scaleMode !== "exact" && scaleMode !== "fit") {
|
|
5911
|
+
throw new RangeError(`scaleMode must be 'exact' or 'fit'`);
|
|
5912
|
+
}
|
|
5675
5913
|
const padding = nonNegativeOption(options.padding, 0, "padding");
|
|
5676
5914
|
validateExportResourceOptions(options);
|
|
5677
5915
|
validateHtmlExportOptions(options);
|
|
5916
|
+
const format = options.format ?? "png";
|
|
5917
|
+
if (format !== "png" && format !== "jpeg") {
|
|
5918
|
+
throw new RangeError(`format must be 'png' or 'jpeg'`);
|
|
5919
|
+
}
|
|
5920
|
+
if (options.quality !== void 0) {
|
|
5921
|
+
if (!Number.isFinite(options.quality) || options.quality <= 0 || options.quality > 1) {
|
|
5922
|
+
throw new RangeError("quality must be a finite number in (0, 1]");
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5678
5925
|
const background = options.background ?? "#ffffff";
|
|
5679
5926
|
const filter = options.filter;
|
|
5680
5927
|
const allElements = store.getAll();
|
|
@@ -5682,8 +5929,9 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
5682
5929
|
if (filter) {
|
|
5683
5930
|
visibleElements = visibleElements.filter(filter);
|
|
5684
5931
|
}
|
|
5685
|
-
const bounds =
|
|
5932
|
+
const bounds = resolveExportBounds(options.region, visibleElements, padding);
|
|
5686
5933
|
if (!bounds) return null;
|
|
5934
|
+
const scale = scaleMode === "fit" ? fitExportScale(bounds, requestedScale, options) : requestedScale;
|
|
5687
5935
|
const width = Math.ceil(bounds.w * scale);
|
|
5688
5936
|
const height = Math.ceil(bounds.h * scale);
|
|
5689
5937
|
assertExportSize(width, height, options);
|
|
@@ -5785,8 +6033,9 @@ async function exportImage(store, options = {}, layerManager) {
|
|
|
5785
6033
|
renderGridForBounds(ctx, grid, bounds);
|
|
5786
6034
|
ctx.restore();
|
|
5787
6035
|
}
|
|
6036
|
+
const mimeType = format === "jpeg" ? "image/jpeg" : "image/png";
|
|
5788
6037
|
return new Promise((resolve) => {
|
|
5789
|
-
canvas.toBlob((blob) => resolve(blob),
|
|
6038
|
+
canvas.toBlob((blob) => resolve(blob), mimeType, options.quality);
|
|
5790
6039
|
});
|
|
5791
6040
|
}
|
|
5792
6041
|
|
|
@@ -7978,25 +8227,7 @@ var Viewport = class {
|
|
|
7978
8227
|
}
|
|
7979
8228
|
this.unsubToolChange = this.toolManager.onChange(() => this.contextMenu?.close());
|
|
7980
8229
|
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();
|
|
8230
|
+
this.minimap = new Minimap(this.wrapper, this);
|
|
8000
8231
|
}
|
|
8001
8232
|
this.domNodeManager = new DomNodeManager({
|
|
8002
8233
|
domLayer: this.paintStack,
|
|
@@ -8032,7 +8263,6 @@ var Viewport = class {
|
|
|
8032
8263
|
this.applyCameraTransform();
|
|
8033
8264
|
this.noteEditor.updateToolbarPosition();
|
|
8034
8265
|
this.contextMenu?.close();
|
|
8035
|
-
this.minimap?.scheduleDraw();
|
|
8036
8266
|
this.requestRender();
|
|
8037
8267
|
});
|
|
8038
8268
|
this.gridController = new GridController({
|
|
@@ -8047,7 +8277,6 @@ var Viewport = class {
|
|
|
8047
8277
|
this.store.on("add", (el) => {
|
|
8048
8278
|
if (el.type === "grid") this.gridController.syncContext();
|
|
8049
8279
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
8050
|
-
this.minimap?.scheduleDraw();
|
|
8051
8280
|
this.requestRender();
|
|
8052
8281
|
}),
|
|
8053
8282
|
this.store.on("remove", (el) => {
|
|
@@ -8055,7 +8284,6 @@ var Viewport = class {
|
|
|
8055
8284
|
this.unbindArrowsFrom(el);
|
|
8056
8285
|
this.domNodeManager.removeDomNode(el.id);
|
|
8057
8286
|
this.renderLoop.markLayerDirty(el.layerId);
|
|
8058
|
-
this.minimap?.scheduleDraw();
|
|
8059
8287
|
this.requestRender();
|
|
8060
8288
|
}),
|
|
8061
8289
|
this.store.on("update", ({ previous, current }) => {
|
|
@@ -8064,21 +8292,18 @@ var Viewport = class {
|
|
|
8064
8292
|
if (previous.layerId !== current.layerId) {
|
|
8065
8293
|
this.renderLoop.markLayerDirty(previous.layerId);
|
|
8066
8294
|
}
|
|
8067
|
-
this.minimap?.scheduleDraw();
|
|
8068
8295
|
this.requestRender();
|
|
8069
8296
|
}),
|
|
8070
8297
|
this.store.on("clear", () => {
|
|
8071
8298
|
this.domNodeManager.clearDomNodes();
|
|
8072
8299
|
this.renderLoop.markAllLayersDirty();
|
|
8073
8300
|
this.gridController.syncContext();
|
|
8074
|
-
this.minimap?.scheduleDraw();
|
|
8075
8301
|
this.requestRender();
|
|
8076
8302
|
})
|
|
8077
8303
|
];
|
|
8078
8304
|
this.layerManager.on("change", () => {
|
|
8079
8305
|
this.toolContext.activeLayerId = this.layerManager.activeLayerId;
|
|
8080
8306
|
this.renderLoop.markAllLayersDirty();
|
|
8081
|
-
this.minimap?.scheduleDraw();
|
|
8082
8307
|
this.requestRender();
|
|
8083
8308
|
});
|
|
8084
8309
|
this.interactions = new ViewportInteractions({
|
|
@@ -8142,6 +8367,7 @@ var Viewport = class {
|
|
|
8142
8367
|
contextMenu = null;
|
|
8143
8368
|
minimap = null;
|
|
8144
8369
|
htmlRenderers = /* @__PURE__ */ new Map();
|
|
8370
|
+
resizeListeners = /* @__PURE__ */ new Set();
|
|
8145
8371
|
get ctx() {
|
|
8146
8372
|
return this.canvasEl.getContext("2d");
|
|
8147
8373
|
}
|
|
@@ -8166,6 +8392,27 @@ var Viewport = class {
|
|
|
8166
8392
|
if (!bbox) return;
|
|
8167
8393
|
this.camera.fitToContent(bbox, this.wrapper.clientWidth, this.wrapper.clientHeight, padding);
|
|
8168
8394
|
}
|
|
8395
|
+
/** World-space rectangle currently visible through the canvas. */
|
|
8396
|
+
getVisibleRect() {
|
|
8397
|
+
return this.camera.getVisibleRect(this.canvasEl.clientWidth, this.canvasEl.clientHeight);
|
|
8398
|
+
}
|
|
8399
|
+
/** Centers the camera on a world point without changing zoom. */
|
|
8400
|
+
centerCameraAt(world) {
|
|
8401
|
+
const z = this.camera.zoom;
|
|
8402
|
+
this.camera.moveTo(
|
|
8403
|
+
this.canvasEl.clientWidth / 2 - world.x * z,
|
|
8404
|
+
this.canvasEl.clientHeight / 2 - world.y * z
|
|
8405
|
+
);
|
|
8406
|
+
}
|
|
8407
|
+
/**
|
|
8408
|
+
* Notifies after the host container resizes (ResizeObserver-driven). A resize
|
|
8409
|
+
* changes the visible world rect without a camera event; overlays such as the
|
|
8410
|
+
* minimap subscribe to stay current. Returns an idempotent unsubscribe.
|
|
8411
|
+
*/
|
|
8412
|
+
onResize(listener) {
|
|
8413
|
+
this.resizeListeners.add(listener);
|
|
8414
|
+
return () => this.resizeListeners.delete(listener);
|
|
8415
|
+
}
|
|
8169
8416
|
requestRender() {
|
|
8170
8417
|
this.renderLoop.requestRender();
|
|
8171
8418
|
}
|
|
@@ -8470,6 +8717,7 @@ var Viewport = class {
|
|
|
8470
8717
|
this.unsubStore.forEach((fn) => fn());
|
|
8471
8718
|
this.resizeObserver?.disconnect();
|
|
8472
8719
|
this.resizeObserver = null;
|
|
8720
|
+
this.resizeListeners.clear();
|
|
8473
8721
|
this.wrapper.remove();
|
|
8474
8722
|
}
|
|
8475
8723
|
stopInteracting() {
|
|
@@ -8519,6 +8767,7 @@ var Viewport = class {
|
|
|
8519
8767
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
8520
8768
|
this.renderLoop.setCanvasSize(rect.width * dpr, rect.height * dpr);
|
|
8521
8769
|
this.requestRender();
|
|
8770
|
+
this.resizeListeners.forEach((fn) => fn());
|
|
8522
8771
|
}
|
|
8523
8772
|
observeResize() {
|
|
8524
8773
|
if (typeof ResizeObserver === "undefined") return;
|
|
@@ -8558,7 +8807,7 @@ function toLaserTrailPresence(emission) {
|
|
|
8558
8807
|
};
|
|
8559
8808
|
}
|
|
8560
8809
|
var DEFAULT_COLOR = "#ff3b30";
|
|
8561
|
-
var
|
|
8810
|
+
var DEFAULT_WIDTH2 = 4;
|
|
8562
8811
|
var DEFAULT_FADE_MS = 1200;
|
|
8563
8812
|
var DEFAULT_MAX_POINTS = 512;
|
|
8564
8813
|
var RemoteLaserOverlay = class {
|
|
@@ -8574,7 +8823,7 @@ var RemoteLaserOverlay = class {
|
|
|
8574
8823
|
constructor(host, options = {}) {
|
|
8575
8824
|
this.host = host;
|
|
8576
8825
|
this.color = options.color ?? DEFAULT_COLOR;
|
|
8577
|
-
this.width = options.width ??
|
|
8826
|
+
this.width = options.width ?? DEFAULT_WIDTH2;
|
|
8578
8827
|
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS;
|
|
8579
8828
|
this.maxPointsPerSender = options.maxPointsPerSender ?? DEFAULT_MAX_POINTS;
|
|
8580
8829
|
this.unregister = host.registerOverlay((ctx) => this.renderTrails(ctx));
|
|
@@ -11803,7 +12052,7 @@ var TemplateTool = class {
|
|
|
11803
12052
|
|
|
11804
12053
|
// src/tools/laser-tool.ts
|
|
11805
12054
|
var DEFAULT_COLOR5 = "#ff3b30";
|
|
11806
|
-
var
|
|
12055
|
+
var DEFAULT_WIDTH3 = 4;
|
|
11807
12056
|
var DEFAULT_FADE_MS3 = 1200;
|
|
11808
12057
|
var LaserTool = class {
|
|
11809
12058
|
name;
|
|
@@ -11819,7 +12068,7 @@ var LaserTool = class {
|
|
|
11819
12068
|
constructor(options = {}) {
|
|
11820
12069
|
this.name = options.name ?? "laser";
|
|
11821
12070
|
this.color = options.color ?? DEFAULT_COLOR5;
|
|
11822
|
-
this.width = options.width ??
|
|
12071
|
+
this.width = options.width ?? DEFAULT_WIDTH3;
|
|
11823
12072
|
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS3;
|
|
11824
12073
|
}
|
|
11825
12074
|
now() {
|
|
@@ -12066,7 +12315,7 @@ var PingTool = class {
|
|
|
12066
12315
|
};
|
|
12067
12316
|
|
|
12068
12317
|
// src/index.ts
|
|
12069
|
-
var VERSION = "0.
|
|
12318
|
+
var VERSION = "0.59.0";
|
|
12070
12319
|
export {
|
|
12071
12320
|
ArrowTool,
|
|
12072
12321
|
AutoSave,
|
|
@@ -12085,6 +12334,7 @@ export {
|
|
|
12085
12334
|
MEASURE_PRESENCE_KIND,
|
|
12086
12335
|
MeasureTool,
|
|
12087
12336
|
MemoryAdapter,
|
|
12337
|
+
MinimapController,
|
|
12088
12338
|
NoteTool,
|
|
12089
12339
|
PING_PRESENCE_KIND,
|
|
12090
12340
|
PencilTool,
|