@fieldnotes/core 0.39.0 → 0.40.1
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 +199 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +198 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -233,7 +233,7 @@ interface Tool {
|
|
|
233
233
|
setOptions?(options: object): void;
|
|
234
234
|
onOptionsChange?(listener: () => void): () => void;
|
|
235
235
|
}
|
|
236
|
-
type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'template';
|
|
236
|
+
type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'template' | 'laser';
|
|
237
237
|
|
|
238
238
|
declare function snapPoint(point: Point, gridSize: number): Point;
|
|
239
239
|
declare function snapToHexCenter(point: Point, cellSize: number, orientation: HexOrientation): Point;
|
|
@@ -1017,6 +1017,37 @@ declare class TemplateTool implements Tool {
|
|
|
1017
1017
|
private notifyOptionsChange;
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
1020
|
-
|
|
1020
|
+
interface LaserToolOptions {
|
|
1021
|
+
name?: string;
|
|
1022
|
+
color?: string;
|
|
1023
|
+
width?: number;
|
|
1024
|
+
fadeMs?: number;
|
|
1025
|
+
}
|
|
1026
|
+
declare class LaserTool implements Tool {
|
|
1027
|
+
readonly name: string;
|
|
1028
|
+
private color;
|
|
1029
|
+
private width;
|
|
1030
|
+
private fadeMs;
|
|
1031
|
+
private trail;
|
|
1032
|
+
private rafId;
|
|
1033
|
+
private drawing;
|
|
1034
|
+
private optionListeners;
|
|
1035
|
+
constructor(options?: LaserToolOptions);
|
|
1036
|
+
private now;
|
|
1037
|
+
onActivate(ctx: ToolContext): void;
|
|
1038
|
+
onDeactivate(ctx: ToolContext): void;
|
|
1039
|
+
getOptions(): LaserToolOptions;
|
|
1040
|
+
onOptionsChange(listener: () => void): () => void;
|
|
1041
|
+
setOptions(options: LaserToolOptions): void;
|
|
1042
|
+
onPointerDown(state: PointerState, ctx: ToolContext): void;
|
|
1043
|
+
onPointerMove(state: PointerState, ctx: ToolContext): void;
|
|
1044
|
+
onPointerUp(_state: PointerState, _ctx: ToolContext): void;
|
|
1045
|
+
renderOverlay(ctx: CanvasRenderingContext2D): void;
|
|
1046
|
+
private ensureAnimating;
|
|
1047
|
+
private tick;
|
|
1048
|
+
private notifyOptionsChange;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
declare const VERSION = "0.40.1";
|
|
1021
1052
|
|
|
1022
|
-
export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
|
1053
|
+
export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
package/dist/index.d.ts
CHANGED
|
@@ -233,7 +233,7 @@ interface Tool {
|
|
|
233
233
|
setOptions?(options: object): void;
|
|
234
234
|
onOptionsChange?(listener: () => void): () => void;
|
|
235
235
|
}
|
|
236
|
-
type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'template';
|
|
236
|
+
type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'template' | 'laser';
|
|
237
237
|
|
|
238
238
|
declare function snapPoint(point: Point, gridSize: number): Point;
|
|
239
239
|
declare function snapToHexCenter(point: Point, cellSize: number, orientation: HexOrientation): Point;
|
|
@@ -1017,6 +1017,37 @@ declare class TemplateTool implements Tool {
|
|
|
1017
1017
|
private notifyOptionsChange;
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
1020
|
-
|
|
1020
|
+
interface LaserToolOptions {
|
|
1021
|
+
name?: string;
|
|
1022
|
+
color?: string;
|
|
1023
|
+
width?: number;
|
|
1024
|
+
fadeMs?: number;
|
|
1025
|
+
}
|
|
1026
|
+
declare class LaserTool implements Tool {
|
|
1027
|
+
readonly name: string;
|
|
1028
|
+
private color;
|
|
1029
|
+
private width;
|
|
1030
|
+
private fadeMs;
|
|
1031
|
+
private trail;
|
|
1032
|
+
private rafId;
|
|
1033
|
+
private drawing;
|
|
1034
|
+
private optionListeners;
|
|
1035
|
+
constructor(options?: LaserToolOptions);
|
|
1036
|
+
private now;
|
|
1037
|
+
onActivate(ctx: ToolContext): void;
|
|
1038
|
+
onDeactivate(ctx: ToolContext): void;
|
|
1039
|
+
getOptions(): LaserToolOptions;
|
|
1040
|
+
onOptionsChange(listener: () => void): () => void;
|
|
1041
|
+
setOptions(options: LaserToolOptions): void;
|
|
1042
|
+
onPointerDown(state: PointerState, ctx: ToolContext): void;
|
|
1043
|
+
onPointerMove(state: PointerState, ctx: ToolContext): void;
|
|
1044
|
+
onPointerUp(_state: PointerState, _ctx: ToolContext): void;
|
|
1045
|
+
renderOverlay(ctx: CanvasRenderingContext2D): void;
|
|
1046
|
+
private ensureAnimating;
|
|
1047
|
+
private tick;
|
|
1048
|
+
private notifyOptionsChange;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
declare const VERSION = "0.40.1";
|
|
1021
1052
|
|
|
1022
|
-
export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
|
1053
|
+
export { type ActiveFormats, type AlignEdge, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DistributeAxis, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type ExportSvgOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, LaserTool, type LaserToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportSvg, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
package/dist/index.js
CHANGED
|
@@ -831,6 +831,11 @@ var KeyboardActions = class {
|
|
|
831
831
|
if (tool?.name !== "select") return null;
|
|
832
832
|
return { tool, ctx };
|
|
833
833
|
}
|
|
834
|
+
selectableElements(ctx) {
|
|
835
|
+
return ctx.store.getAll().filter(
|
|
836
|
+
(el) => !el.locked && (ctx.isLayerVisible?.(el.layerId) ?? true) && !(ctx.isLayerLocked?.(el.layerId) ?? false)
|
|
837
|
+
);
|
|
838
|
+
}
|
|
834
839
|
nudge(dx, dy, byCell) {
|
|
835
840
|
if (this.deps.isToolActive()) return false;
|
|
836
841
|
const sel = this.selectTool();
|
|
@@ -973,12 +978,28 @@ var KeyboardActions = class {
|
|
|
973
978
|
}
|
|
974
979
|
const sel = this.selectTool();
|
|
975
980
|
if (!sel) return;
|
|
976
|
-
const ids = sel.ctx.
|
|
977
|
-
(el) => !el.locked && (sel.ctx.isLayerVisible?.(el.layerId) ?? true) && !(sel.ctx.isLayerLocked?.(el.layerId) ?? false)
|
|
978
|
-
).map((el) => el.id);
|
|
981
|
+
const ids = this.selectableElements(sel.ctx).map((el) => el.id);
|
|
979
982
|
sel.tool.setSelection(ids);
|
|
980
983
|
sel.ctx.requestRender();
|
|
981
984
|
}
|
|
985
|
+
cycleSelection(direction) {
|
|
986
|
+
if (this.deps.isToolActive()) return;
|
|
987
|
+
const tm = this.deps.getToolManager();
|
|
988
|
+
const ctx = this.deps.getToolContext();
|
|
989
|
+
if (!tm || !ctx) return;
|
|
990
|
+
if (tm.activeTool?.name !== "select") ctx.switchTool?.("select");
|
|
991
|
+
const sel = this.selectTool();
|
|
992
|
+
if (!sel) return;
|
|
993
|
+
const eligible = this.selectableElements(sel.ctx).filter((el) => el.type !== "grid");
|
|
994
|
+
if (eligible.length === 0) return;
|
|
995
|
+
const idxs = sel.tool.selectedIds.map((id) => eligible.findIndex((e) => e.id === id)).filter((i) => i >= 0);
|
|
996
|
+
const anchor = idxs.length === 0 ? direction > 0 ? -1 : 0 : direction > 0 ? Math.max(...idxs) : Math.min(...idxs);
|
|
997
|
+
const next = (anchor + direction + eligible.length) % eligible.length;
|
|
998
|
+
const target = eligible[next];
|
|
999
|
+
if (!target) return;
|
|
1000
|
+
sel.tool.setSelection([target.id]);
|
|
1001
|
+
sel.ctx.requestRender();
|
|
1002
|
+
}
|
|
982
1003
|
zoomToFit() {
|
|
983
1004
|
if (this.deps.isToolActive()) return;
|
|
984
1005
|
this.deps.fitToContent?.();
|
|
@@ -1083,6 +1104,8 @@ var DEFAULT_BINDINGS = [
|
|
|
1083
1104
|
["undo", ["mod+z"]],
|
|
1084
1105
|
["redo", ["mod+y", "mod+shift+z"]],
|
|
1085
1106
|
["select-all", ["mod+a"]],
|
|
1107
|
+
["cycle-selection", ["tab"]],
|
|
1108
|
+
["cycle-selection-reverse", ["shift+tab"]],
|
|
1086
1109
|
["copy", ["mod+c"]],
|
|
1087
1110
|
["duplicate", ["mod+d"]],
|
|
1088
1111
|
["z-forward", ["]"]],
|
|
@@ -1353,6 +1376,14 @@ var KeyboardHandler = class {
|
|
|
1353
1376
|
e?.preventDefault();
|
|
1354
1377
|
this.deps.actions.selectAll();
|
|
1355
1378
|
return;
|
|
1379
|
+
case "cycle-selection":
|
|
1380
|
+
e?.preventDefault();
|
|
1381
|
+
this.deps.actions.cycleSelection(1);
|
|
1382
|
+
return;
|
|
1383
|
+
case "cycle-selection-reverse":
|
|
1384
|
+
e?.preventDefault();
|
|
1385
|
+
this.deps.actions.cycleSelection(-1);
|
|
1386
|
+
return;
|
|
1356
1387
|
case "copy":
|
|
1357
1388
|
e?.preventDefault();
|
|
1358
1389
|
this.deps.actions.copy();
|
|
@@ -4651,6 +4682,16 @@ function renderStyledRuns(ctx, runs, startX, startY, maxWidth) {
|
|
|
4651
4682
|
}
|
|
4652
4683
|
}
|
|
4653
4684
|
|
|
4685
|
+
// src/canvas/text-canvas-renderer.ts
|
|
4686
|
+
function renderTextOnCanvas(ctx, text) {
|
|
4687
|
+
const pad = 2;
|
|
4688
|
+
ctx.save();
|
|
4689
|
+
ctx.fillStyle = text.color;
|
|
4690
|
+
const runs = parseStyledRuns(text.text ?? "", text.fontSize);
|
|
4691
|
+
renderStyledRuns(ctx, runs, text.position.x + pad, text.position.y + pad, text.size.w - pad * 2);
|
|
4692
|
+
ctx.restore();
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4654
4695
|
// src/canvas/export-image.ts
|
|
4655
4696
|
var center = (b) => ({ x: b.x + b.w / 2, y: b.y + b.h / 2 });
|
|
4656
4697
|
function getStrokeBounds(el) {
|
|
@@ -4733,30 +4774,6 @@ function computeBounds(elements, padding) {
|
|
|
4733
4774
|
h: maxY - minY + padding * 2
|
|
4734
4775
|
};
|
|
4735
4776
|
}
|
|
4736
|
-
function renderTextOnCanvas(ctx, text) {
|
|
4737
|
-
if (!text.text) return;
|
|
4738
|
-
ctx.save();
|
|
4739
|
-
ctx.fillStyle = text.color;
|
|
4740
|
-
ctx.font = `${text.fontSize}px system-ui, sans-serif`;
|
|
4741
|
-
ctx.textBaseline = "top";
|
|
4742
|
-
ctx.textAlign = text.textAlign;
|
|
4743
|
-
const pad = 2;
|
|
4744
|
-
let textX = text.position.x + pad;
|
|
4745
|
-
if (text.textAlign === "center") {
|
|
4746
|
-
textX = text.position.x + text.size.w / 2;
|
|
4747
|
-
} else if (text.textAlign === "right") {
|
|
4748
|
-
textX = text.position.x + text.size.w - pad;
|
|
4749
|
-
}
|
|
4750
|
-
const lineHeight = text.fontSize * 1.4;
|
|
4751
|
-
const lines = text.text.split("\n");
|
|
4752
|
-
for (let i = 0; i < lines.length; i++) {
|
|
4753
|
-
const line = lines[i];
|
|
4754
|
-
if (line !== void 0) {
|
|
4755
|
-
ctx.fillText(line, textX, text.position.y + pad + i * lineHeight);
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
ctx.restore();
|
|
4759
|
-
}
|
|
4760
4777
|
function renderGridForBounds(ctx, grid, bounds) {
|
|
4761
4778
|
const visibleBounds = {
|
|
4762
4779
|
minX: bounds.x,
|
|
@@ -4978,28 +4995,27 @@ function emitImage(image, dataUri) {
|
|
|
4978
4995
|
const { w, h } = image.size;
|
|
4979
4996
|
return `<image href="${esc(href)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
4980
4997
|
}
|
|
4981
|
-
function emitText(text) {
|
|
4998
|
+
function emitText(text, rasterScale) {
|
|
4982
4999
|
if (!text.text) return "";
|
|
4983
|
-
const
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
const y = text.position.y + pad + i * lineHeight;
|
|
5000
|
-
out += `<text x="${n(textX)}" y="${n(y)}" font-family="system-ui, sans-serif" font-size="${n(text.fontSize)}" fill="${esc(text.color)}" text-anchor="${anchor}" dominant-baseline="text-before-edge">${esc(line)}</text>`;
|
|
5000
|
+
const { x, y } = text.position;
|
|
5001
|
+
const { w, h } = text.size;
|
|
5002
|
+
if (typeof document === "undefined") return "";
|
|
5003
|
+
const canvas = document.createElement("canvas");
|
|
5004
|
+
canvas.width = Math.max(1, Math.ceil(w * rasterScale));
|
|
5005
|
+
canvas.height = Math.max(1, Math.ceil(h * rasterScale));
|
|
5006
|
+
const ctx = canvas.getContext("2d");
|
|
5007
|
+
if (!ctx) return "";
|
|
5008
|
+
ctx.scale(rasterScale, rasterScale);
|
|
5009
|
+
ctx.translate(-x, -y);
|
|
5010
|
+
renderTextOnCanvas(ctx, text);
|
|
5011
|
+
let dataUri;
|
|
5012
|
+
try {
|
|
5013
|
+
dataUri = canvas.toDataURL();
|
|
5014
|
+
} catch {
|
|
5015
|
+
return "";
|
|
5001
5016
|
}
|
|
5002
|
-
return
|
|
5017
|
+
if (!dataUri || !dataUri.startsWith("data:")) return "";
|
|
5018
|
+
return `<image href="${esc(dataUri)}" x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" />`;
|
|
5003
5019
|
}
|
|
5004
5020
|
function emitNote(note, rasterScale) {
|
|
5005
5021
|
const { x, y } = note.position;
|
|
@@ -5182,7 +5198,7 @@ function emitElement(el, imageDataUris, rasterScale, firstGrid, store) {
|
|
|
5182
5198
|
case "image":
|
|
5183
5199
|
return withRotationSvg(el, emitImage(el, imageDataUris.get(el.id)));
|
|
5184
5200
|
case "text":
|
|
5185
|
-
return withRotationSvg(el, emitText(el));
|
|
5201
|
+
return withRotationSvg(el, emitText(el, rasterScale));
|
|
5186
5202
|
case "note":
|
|
5187
5203
|
return withRotationSvg(el, emitNote(el, rasterScale));
|
|
5188
5204
|
case "template":
|
|
@@ -5645,10 +5661,9 @@ var DomNodeManager = class {
|
|
|
5645
5661
|
cursor: "default",
|
|
5646
5662
|
userSelect: "none",
|
|
5647
5663
|
wordWrap: "break-word",
|
|
5648
|
-
whiteSpace: "pre-wrap",
|
|
5649
5664
|
lineHeight: "1.4"
|
|
5650
5665
|
});
|
|
5651
|
-
node.
|
|
5666
|
+
node.innerHTML = element.text || "";
|
|
5652
5667
|
const detector = new DoubleTapDetector();
|
|
5653
5668
|
node.addEventListener("pointerup", (e) => {
|
|
5654
5669
|
if (detector.feed(e)) {
|
|
@@ -5659,8 +5674,9 @@ var DomNodeManager = class {
|
|
|
5659
5674
|
});
|
|
5660
5675
|
}
|
|
5661
5676
|
if (!this.isEditingElement(element.id)) {
|
|
5662
|
-
|
|
5663
|
-
|
|
5677
|
+
const text = element.text || "";
|
|
5678
|
+
if (node.innerHTML !== text) {
|
|
5679
|
+
node.innerHTML = text;
|
|
5664
5680
|
}
|
|
5665
5681
|
Object.assign(node.style, {
|
|
5666
5682
|
fontSize: `${element.fontSize}px`,
|
|
@@ -7691,6 +7707,17 @@ function renderArrowHandles(canvasCtx, arrow, zoom) {
|
|
|
7691
7707
|
canvasCtx.stroke();
|
|
7692
7708
|
}
|
|
7693
7709
|
}
|
|
7710
|
+
function renderArrowHoverHandle(canvasCtx, arrow, zoom) {
|
|
7711
|
+
const mid = getArrowMidpoint(arrow.from, arrow.to, arrow.bend);
|
|
7712
|
+
const radius = HANDLE_RADIUS / zoom;
|
|
7713
|
+
canvasCtx.fillStyle = "#2196F3";
|
|
7714
|
+
canvasCtx.strokeStyle = "#2196F3";
|
|
7715
|
+
canvasCtx.lineWidth = 1.5 / zoom;
|
|
7716
|
+
canvasCtx.beginPath();
|
|
7717
|
+
canvasCtx.arc(mid.x, mid.y, radius, 0, Math.PI * 2);
|
|
7718
|
+
canvasCtx.fill();
|
|
7719
|
+
canvasCtx.stroke();
|
|
7720
|
+
}
|
|
7694
7721
|
|
|
7695
7722
|
// src/elements/snap-guides.ts
|
|
7696
7723
|
function xAnchors(b) {
|
|
@@ -8539,7 +8566,13 @@ var SelectTool = class {
|
|
|
8539
8566
|
if (this.hoveredId && this.ctx && this.mode.type === "idle") {
|
|
8540
8567
|
if (!this._selectedIds.includes(this.hoveredId)) {
|
|
8541
8568
|
const el = this.ctx.store.getById(this.hoveredId);
|
|
8542
|
-
if (el) {
|
|
8569
|
+
if (el?.type === "arrow") {
|
|
8570
|
+
canvasCtx.save();
|
|
8571
|
+
canvasCtx.globalAlpha = 0.35;
|
|
8572
|
+
canvasCtx.setLineDash([]);
|
|
8573
|
+
renderArrowHoverHandle(canvasCtx, el, this.ctx.camera.zoom);
|
|
8574
|
+
canvasCtx.restore();
|
|
8575
|
+
} else if (el) {
|
|
8543
8576
|
const b = getElementBounds(el);
|
|
8544
8577
|
if (b) {
|
|
8545
8578
|
canvasCtx.save();
|
|
@@ -9510,8 +9543,118 @@ var TemplateTool = class {
|
|
|
9510
9543
|
}
|
|
9511
9544
|
};
|
|
9512
9545
|
|
|
9546
|
+
// src/tools/laser-tool.ts
|
|
9547
|
+
var DEFAULT_COLOR = "#ff3b30";
|
|
9548
|
+
var DEFAULT_WIDTH = 4;
|
|
9549
|
+
var DEFAULT_FADE_MS = 1200;
|
|
9550
|
+
var LaserTool = class {
|
|
9551
|
+
name;
|
|
9552
|
+
color;
|
|
9553
|
+
width;
|
|
9554
|
+
fadeMs;
|
|
9555
|
+
trail = [];
|
|
9556
|
+
rafId = null;
|
|
9557
|
+
drawing = false;
|
|
9558
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
9559
|
+
constructor(options = {}) {
|
|
9560
|
+
this.name = options.name ?? "laser";
|
|
9561
|
+
this.color = options.color ?? DEFAULT_COLOR;
|
|
9562
|
+
this.width = options.width ?? DEFAULT_WIDTH;
|
|
9563
|
+
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS;
|
|
9564
|
+
}
|
|
9565
|
+
now() {
|
|
9566
|
+
return performance.now();
|
|
9567
|
+
}
|
|
9568
|
+
onActivate(ctx) {
|
|
9569
|
+
ctx.setCursor?.("crosshair");
|
|
9570
|
+
}
|
|
9571
|
+
onDeactivate(ctx) {
|
|
9572
|
+
if (this.rafId !== null) {
|
|
9573
|
+
cancelAnimationFrame(this.rafId);
|
|
9574
|
+
this.rafId = null;
|
|
9575
|
+
}
|
|
9576
|
+
this.trail = [];
|
|
9577
|
+
this.drawing = false;
|
|
9578
|
+
ctx.setCursor?.("default");
|
|
9579
|
+
ctx.requestRender();
|
|
9580
|
+
}
|
|
9581
|
+
getOptions() {
|
|
9582
|
+
return {
|
|
9583
|
+
name: this.name,
|
|
9584
|
+
color: this.color,
|
|
9585
|
+
width: this.width,
|
|
9586
|
+
fadeMs: this.fadeMs
|
|
9587
|
+
};
|
|
9588
|
+
}
|
|
9589
|
+
onOptionsChange(listener) {
|
|
9590
|
+
this.optionListeners.add(listener);
|
|
9591
|
+
return () => this.optionListeners.delete(listener);
|
|
9592
|
+
}
|
|
9593
|
+
setOptions(options) {
|
|
9594
|
+
if (options.color !== void 0) this.color = options.color;
|
|
9595
|
+
if (options.width !== void 0) this.width = options.width;
|
|
9596
|
+
if (options.fadeMs !== void 0) this.fadeMs = options.fadeMs;
|
|
9597
|
+
this.notifyOptionsChange();
|
|
9598
|
+
}
|
|
9599
|
+
onPointerDown(state, ctx) {
|
|
9600
|
+
this.drawing = true;
|
|
9601
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
9602
|
+
this.trail.push({ x: world.x, y: world.y, t: this.now() });
|
|
9603
|
+
this.ensureAnimating(ctx);
|
|
9604
|
+
}
|
|
9605
|
+
onPointerMove(state, ctx) {
|
|
9606
|
+
if (!this.drawing) return;
|
|
9607
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
9608
|
+
this.trail.push({ x: world.x, y: world.y, t: this.now() });
|
|
9609
|
+
this.ensureAnimating(ctx);
|
|
9610
|
+
}
|
|
9611
|
+
onPointerUp(_state, _ctx) {
|
|
9612
|
+
this.drawing = false;
|
|
9613
|
+
}
|
|
9614
|
+
renderOverlay(ctx) {
|
|
9615
|
+
if (this.trail.length < 2) return;
|
|
9616
|
+
ctx.save();
|
|
9617
|
+
ctx.strokeStyle = this.color;
|
|
9618
|
+
ctx.lineWidth = this.width;
|
|
9619
|
+
ctx.lineCap = "round";
|
|
9620
|
+
ctx.lineJoin = "round";
|
|
9621
|
+
const now = this.now();
|
|
9622
|
+
for (let i = 0; i < this.trail.length - 1; i++) {
|
|
9623
|
+
const a = this.trail[i];
|
|
9624
|
+
const b = this.trail[i + 1];
|
|
9625
|
+
if (!a || !b) continue;
|
|
9626
|
+
const age = now - b.t;
|
|
9627
|
+
ctx.globalAlpha = Math.max(0, 1 - age / this.fadeMs);
|
|
9628
|
+
ctx.beginPath();
|
|
9629
|
+
ctx.moveTo(a.x, a.y);
|
|
9630
|
+
ctx.lineTo(b.x, b.y);
|
|
9631
|
+
ctx.stroke();
|
|
9632
|
+
}
|
|
9633
|
+
ctx.restore();
|
|
9634
|
+
}
|
|
9635
|
+
ensureAnimating(ctx) {
|
|
9636
|
+
if (this.rafId === null) {
|
|
9637
|
+
this.rafId = requestAnimationFrame(() => this.tick(ctx));
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9640
|
+
tick(ctx) {
|
|
9641
|
+
const cutoff = this.now() - this.fadeMs;
|
|
9642
|
+
this.trail = this.trail.filter((p) => p.t >= cutoff);
|
|
9643
|
+
if (this.trail.length > 0) {
|
|
9644
|
+
ctx.requestRender();
|
|
9645
|
+
this.rafId = requestAnimationFrame(() => this.tick(ctx));
|
|
9646
|
+
} else {
|
|
9647
|
+
ctx.requestRender();
|
|
9648
|
+
this.rafId = null;
|
|
9649
|
+
}
|
|
9650
|
+
}
|
|
9651
|
+
notifyOptionsChange() {
|
|
9652
|
+
for (const listener of this.optionListeners) listener();
|
|
9653
|
+
}
|
|
9654
|
+
};
|
|
9655
|
+
|
|
9513
9656
|
// src/index.ts
|
|
9514
|
-
var VERSION = "0.
|
|
9657
|
+
var VERSION = "0.40.1";
|
|
9515
9658
|
export {
|
|
9516
9659
|
ArrowTool,
|
|
9517
9660
|
AutoSave,
|
|
@@ -9522,6 +9665,7 @@ export {
|
|
|
9522
9665
|
HandTool,
|
|
9523
9666
|
HistoryStack,
|
|
9524
9667
|
ImageTool,
|
|
9668
|
+
LaserTool,
|
|
9525
9669
|
LayerManager,
|
|
9526
9670
|
MeasureTool,
|
|
9527
9671
|
NoteTool,
|