@fieldnotes/core 0.2.0 → 0.2.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/README.md +332 -273
- package/dist/index.cjs +188 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -6
- package/dist/index.d.ts +21 -6
- package/dist/index.js +188 -48
- package/dist/index.js.map +1 -1
- package/package.json +31 -31
package/dist/index.d.cts
CHANGED
|
@@ -13,6 +13,11 @@ interface Point {
|
|
|
13
13
|
x: number;
|
|
14
14
|
y: number;
|
|
15
15
|
}
|
|
16
|
+
interface StrokePoint {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
pressure: number;
|
|
20
|
+
}
|
|
16
21
|
interface Size {
|
|
17
22
|
w: number;
|
|
18
23
|
h: number;
|
|
@@ -28,7 +33,7 @@ interface BaseElement {
|
|
|
28
33
|
}
|
|
29
34
|
interface StrokeElement extends BaseElement {
|
|
30
35
|
type: 'stroke';
|
|
31
|
-
points:
|
|
36
|
+
points: StrokePoint[];
|
|
32
37
|
color: string;
|
|
33
38
|
width: number;
|
|
34
39
|
opacity: number;
|
|
@@ -200,6 +205,7 @@ declare class ToolManager {
|
|
|
200
205
|
get activeTool(): Tool | null;
|
|
201
206
|
get toolNames(): string[];
|
|
202
207
|
register(tool: Tool): void;
|
|
208
|
+
getTool<T extends Tool = Tool>(name: string): T | undefined;
|
|
203
209
|
setTool(name: string, ctx: ToolContext): void;
|
|
204
210
|
handlePointerDown(state: PointerState, ctx: ToolContext): void;
|
|
205
211
|
handlePointerMove(state: PointerState, ctx: ToolContext): void;
|
|
@@ -280,7 +286,6 @@ declare class InputHandler {
|
|
|
280
286
|
destroy(): void;
|
|
281
287
|
private bind;
|
|
282
288
|
private onWheel;
|
|
283
|
-
private isInteractiveHtmlContent;
|
|
284
289
|
private onPointerDown;
|
|
285
290
|
private onPointerMove;
|
|
286
291
|
private onPointerUp;
|
|
@@ -328,6 +333,7 @@ declare class Viewport {
|
|
|
328
333
|
private needsRender;
|
|
329
334
|
private domNodes;
|
|
330
335
|
private htmlContent;
|
|
336
|
+
private interactingElementId;
|
|
331
337
|
constructor(container: HTMLElement, options?: ViewportOptions);
|
|
332
338
|
get ctx(): CanvasRenderingContext2D | null;
|
|
333
339
|
requestRender(): void;
|
|
@@ -343,7 +349,7 @@ declare class Viewport {
|
|
|
343
349
|
}, size?: {
|
|
344
350
|
w: number;
|
|
345
351
|
h: number;
|
|
346
|
-
}):
|
|
352
|
+
}): string;
|
|
347
353
|
addHtmlElement(dom: HTMLElement, position: {
|
|
348
354
|
x: number;
|
|
349
355
|
y: number;
|
|
@@ -356,6 +362,11 @@ declare class Viewport {
|
|
|
356
362
|
private render;
|
|
357
363
|
private startEditingNote;
|
|
358
364
|
private onDblClick;
|
|
365
|
+
private hitTestWorld;
|
|
366
|
+
private startInteracting;
|
|
367
|
+
stopInteracting(): void;
|
|
368
|
+
private onInteractKeyDown;
|
|
369
|
+
private onInteractPointerDown;
|
|
359
370
|
private onDragOver;
|
|
360
371
|
private onDrop;
|
|
361
372
|
private syncDomNode;
|
|
@@ -401,7 +412,7 @@ interface BaseDefaults {
|
|
|
401
412
|
locked?: boolean;
|
|
402
413
|
}
|
|
403
414
|
interface StrokeInput extends BaseDefaults {
|
|
404
|
-
points:
|
|
415
|
+
points: StrokePoint[];
|
|
405
416
|
color?: string;
|
|
406
417
|
width?: number;
|
|
407
418
|
opacity?: number;
|
|
@@ -488,6 +499,7 @@ declare class HandTool implements Tool {
|
|
|
488
499
|
interface PencilToolOptions {
|
|
489
500
|
color?: string;
|
|
490
501
|
width?: number;
|
|
502
|
+
smoothing?: number;
|
|
491
503
|
}
|
|
492
504
|
declare class PencilTool implements Tool {
|
|
493
505
|
readonly name = "pencil";
|
|
@@ -495,6 +507,7 @@ declare class PencilTool implements Tool {
|
|
|
495
507
|
private points;
|
|
496
508
|
private color;
|
|
497
509
|
private width;
|
|
510
|
+
private smoothing;
|
|
498
511
|
constructor(options?: PencilToolOptions);
|
|
499
512
|
onActivate(ctx: ToolContext): void;
|
|
500
513
|
onDeactivate(ctx: ToolContext): void;
|
|
@@ -565,6 +578,7 @@ declare class ArrowTool implements Tool {
|
|
|
565
578
|
private color;
|
|
566
579
|
private width;
|
|
567
580
|
constructor(options?: ArrowToolOptions);
|
|
581
|
+
setOptions(options: ArrowToolOptions): void;
|
|
568
582
|
onPointerDown(state: PointerState, ctx: ToolContext): void;
|
|
569
583
|
onPointerMove(state: PointerState, ctx: ToolContext): void;
|
|
570
584
|
onPointerUp(_state: PointerState, ctx: ToolContext): void;
|
|
@@ -580,6 +594,7 @@ declare class NoteTool implements Tool {
|
|
|
580
594
|
private backgroundColor;
|
|
581
595
|
private size;
|
|
582
596
|
constructor(options?: NoteToolOptions);
|
|
597
|
+
setOptions(options: NoteToolOptions): void;
|
|
583
598
|
onPointerDown(_state: PointerState, _ctx: ToolContext): void;
|
|
584
599
|
onPointerMove(_state: PointerState, _ctx: ToolContext): void;
|
|
585
600
|
onPointerUp(state: PointerState, ctx: ToolContext): void;
|
|
@@ -599,6 +614,6 @@ declare class ImageTool implements Tool {
|
|
|
599
614
|
onPointerUp(state: PointerState, ctx: ToolContext): void;
|
|
600
615
|
}
|
|
601
616
|
|
|
602
|
-
declare const VERSION = "0.1
|
|
617
|
+
declare const VERSION = "0.2.1";
|
|
603
618
|
|
|
604
|
-
export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
|
|
619
|
+
export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type StrokePoint, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ interface Point {
|
|
|
13
13
|
x: number;
|
|
14
14
|
y: number;
|
|
15
15
|
}
|
|
16
|
+
interface StrokePoint {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
pressure: number;
|
|
20
|
+
}
|
|
16
21
|
interface Size {
|
|
17
22
|
w: number;
|
|
18
23
|
h: number;
|
|
@@ -28,7 +33,7 @@ interface BaseElement {
|
|
|
28
33
|
}
|
|
29
34
|
interface StrokeElement extends BaseElement {
|
|
30
35
|
type: 'stroke';
|
|
31
|
-
points:
|
|
36
|
+
points: StrokePoint[];
|
|
32
37
|
color: string;
|
|
33
38
|
width: number;
|
|
34
39
|
opacity: number;
|
|
@@ -200,6 +205,7 @@ declare class ToolManager {
|
|
|
200
205
|
get activeTool(): Tool | null;
|
|
201
206
|
get toolNames(): string[];
|
|
202
207
|
register(tool: Tool): void;
|
|
208
|
+
getTool<T extends Tool = Tool>(name: string): T | undefined;
|
|
203
209
|
setTool(name: string, ctx: ToolContext): void;
|
|
204
210
|
handlePointerDown(state: PointerState, ctx: ToolContext): void;
|
|
205
211
|
handlePointerMove(state: PointerState, ctx: ToolContext): void;
|
|
@@ -280,7 +286,6 @@ declare class InputHandler {
|
|
|
280
286
|
destroy(): void;
|
|
281
287
|
private bind;
|
|
282
288
|
private onWheel;
|
|
283
|
-
private isInteractiveHtmlContent;
|
|
284
289
|
private onPointerDown;
|
|
285
290
|
private onPointerMove;
|
|
286
291
|
private onPointerUp;
|
|
@@ -328,6 +333,7 @@ declare class Viewport {
|
|
|
328
333
|
private needsRender;
|
|
329
334
|
private domNodes;
|
|
330
335
|
private htmlContent;
|
|
336
|
+
private interactingElementId;
|
|
331
337
|
constructor(container: HTMLElement, options?: ViewportOptions);
|
|
332
338
|
get ctx(): CanvasRenderingContext2D | null;
|
|
333
339
|
requestRender(): void;
|
|
@@ -343,7 +349,7 @@ declare class Viewport {
|
|
|
343
349
|
}, size?: {
|
|
344
350
|
w: number;
|
|
345
351
|
h: number;
|
|
346
|
-
}):
|
|
352
|
+
}): string;
|
|
347
353
|
addHtmlElement(dom: HTMLElement, position: {
|
|
348
354
|
x: number;
|
|
349
355
|
y: number;
|
|
@@ -356,6 +362,11 @@ declare class Viewport {
|
|
|
356
362
|
private render;
|
|
357
363
|
private startEditingNote;
|
|
358
364
|
private onDblClick;
|
|
365
|
+
private hitTestWorld;
|
|
366
|
+
private startInteracting;
|
|
367
|
+
stopInteracting(): void;
|
|
368
|
+
private onInteractKeyDown;
|
|
369
|
+
private onInteractPointerDown;
|
|
359
370
|
private onDragOver;
|
|
360
371
|
private onDrop;
|
|
361
372
|
private syncDomNode;
|
|
@@ -401,7 +412,7 @@ interface BaseDefaults {
|
|
|
401
412
|
locked?: boolean;
|
|
402
413
|
}
|
|
403
414
|
interface StrokeInput extends BaseDefaults {
|
|
404
|
-
points:
|
|
415
|
+
points: StrokePoint[];
|
|
405
416
|
color?: string;
|
|
406
417
|
width?: number;
|
|
407
418
|
opacity?: number;
|
|
@@ -488,6 +499,7 @@ declare class HandTool implements Tool {
|
|
|
488
499
|
interface PencilToolOptions {
|
|
489
500
|
color?: string;
|
|
490
501
|
width?: number;
|
|
502
|
+
smoothing?: number;
|
|
491
503
|
}
|
|
492
504
|
declare class PencilTool implements Tool {
|
|
493
505
|
readonly name = "pencil";
|
|
@@ -495,6 +507,7 @@ declare class PencilTool implements Tool {
|
|
|
495
507
|
private points;
|
|
496
508
|
private color;
|
|
497
509
|
private width;
|
|
510
|
+
private smoothing;
|
|
498
511
|
constructor(options?: PencilToolOptions);
|
|
499
512
|
onActivate(ctx: ToolContext): void;
|
|
500
513
|
onDeactivate(ctx: ToolContext): void;
|
|
@@ -565,6 +578,7 @@ declare class ArrowTool implements Tool {
|
|
|
565
578
|
private color;
|
|
566
579
|
private width;
|
|
567
580
|
constructor(options?: ArrowToolOptions);
|
|
581
|
+
setOptions(options: ArrowToolOptions): void;
|
|
568
582
|
onPointerDown(state: PointerState, ctx: ToolContext): void;
|
|
569
583
|
onPointerMove(state: PointerState, ctx: ToolContext): void;
|
|
570
584
|
onPointerUp(_state: PointerState, ctx: ToolContext): void;
|
|
@@ -580,6 +594,7 @@ declare class NoteTool implements Tool {
|
|
|
580
594
|
private backgroundColor;
|
|
581
595
|
private size;
|
|
582
596
|
constructor(options?: NoteToolOptions);
|
|
597
|
+
setOptions(options: NoteToolOptions): void;
|
|
583
598
|
onPointerDown(_state: PointerState, _ctx: ToolContext): void;
|
|
584
599
|
onPointerMove(_state: PointerState, _ctx: ToolContext): void;
|
|
585
600
|
onPointerUp(state: PointerState, ctx: ToolContext): void;
|
|
@@ -599,6 +614,6 @@ declare class ImageTool implements Tool {
|
|
|
599
614
|
onPointerUp(state: PointerState, ctx: ToolContext): void;
|
|
600
615
|
}
|
|
601
616
|
|
|
602
|
-
declare const VERSION = "0.1
|
|
617
|
+
declare const VERSION = "0.2.1";
|
|
603
618
|
|
|
604
|
-
export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
|
|
619
|
+
export { AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Bounds, Camera, type CameraOptions, type CanvasElement, type CanvasState, type Command, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, HandTool, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputHandler, NoteEditor, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, RemoveElementCommand, SelectTool, type Size, type StrokeElement, type StrokePoint, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, VERSION, Viewport, type ViewportOptions, createArrow, createHtmlElement, createId, createImage, createNote, createStroke, exportState, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, isNearBezier, parseState };
|
package/dist/index.js
CHANGED
|
@@ -89,6 +89,13 @@ function migrateElement(obj) {
|
|
|
89
89
|
if (obj["type"] === "arrow" && typeof obj["bend"] !== "number") {
|
|
90
90
|
obj["bend"] = 0;
|
|
91
91
|
}
|
|
92
|
+
if (obj["type"] === "stroke" && Array.isArray(obj["points"])) {
|
|
93
|
+
for (const pt of obj["points"]) {
|
|
94
|
+
if (typeof pt["pressure"] !== "number") {
|
|
95
|
+
pt["pressure"] = 0.5;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
// src/core/auto-save.ts
|
|
@@ -344,24 +351,8 @@ var InputHandler = class {
|
|
|
344
351
|
y: e.clientY - rect.top
|
|
345
352
|
});
|
|
346
353
|
};
|
|
347
|
-
isInteractiveHtmlContent(e) {
|
|
348
|
-
const target = e.target;
|
|
349
|
-
if (!target) return false;
|
|
350
|
-
const node = target.closest("[data-element-id]");
|
|
351
|
-
if (!node) return false;
|
|
352
|
-
const elementId = node.dataset["elementId"];
|
|
353
|
-
if (!elementId) return false;
|
|
354
|
-
const store = this.toolContext?.store;
|
|
355
|
-
if (!store) return false;
|
|
356
|
-
const element = store.getById(elementId);
|
|
357
|
-
if (!element || element.type !== "html") return false;
|
|
358
|
-
return true;
|
|
359
|
-
}
|
|
360
354
|
onPointerDown = (e) => {
|
|
361
355
|
this.activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
362
|
-
if (this.isInteractiveHtmlContent(e)) {
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
356
|
this.element.setPointerCapture?.(e.pointerId);
|
|
366
357
|
if (this.activePointers.size === 2) {
|
|
367
358
|
this.startPinch();
|
|
@@ -687,6 +678,79 @@ function isNearLine(point, a, b, threshold) {
|
|
|
687
678
|
return Math.hypot(point.x - projX, point.y - projY) <= threshold;
|
|
688
679
|
}
|
|
689
680
|
|
|
681
|
+
// src/elements/stroke-smoothing.ts
|
|
682
|
+
var MIN_PRESSURE_SCALE = 0.2;
|
|
683
|
+
function pressureToWidth(pressure, baseWidth) {
|
|
684
|
+
return baseWidth * (MIN_PRESSURE_SCALE + (1 - MIN_PRESSURE_SCALE) * pressure);
|
|
685
|
+
}
|
|
686
|
+
function simplifyPoints(points, tolerance) {
|
|
687
|
+
if (points.length <= 2) return points.slice();
|
|
688
|
+
return rdp(points, 0, points.length - 1, tolerance);
|
|
689
|
+
}
|
|
690
|
+
function rdp(points, start, end, tolerance) {
|
|
691
|
+
const first = points[start];
|
|
692
|
+
const last = points[end];
|
|
693
|
+
if (!first || !last) return [];
|
|
694
|
+
if (end - start <= 1) return [first, last];
|
|
695
|
+
let maxDist = 0;
|
|
696
|
+
let maxIndex = start;
|
|
697
|
+
for (let i = start + 1; i < end; i++) {
|
|
698
|
+
const pt = points[i];
|
|
699
|
+
if (!pt) continue;
|
|
700
|
+
const dist = perpendicularDistance(pt, first, last);
|
|
701
|
+
if (dist > maxDist) {
|
|
702
|
+
maxDist = dist;
|
|
703
|
+
maxIndex = i;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (maxDist <= tolerance) return [first, last];
|
|
707
|
+
const left = rdp(points, start, maxIndex, tolerance);
|
|
708
|
+
const right = rdp(points, maxIndex, end, tolerance);
|
|
709
|
+
return left.concat(right.slice(1));
|
|
710
|
+
}
|
|
711
|
+
function perpendicularDistance(pt, lineStart, lineEnd) {
|
|
712
|
+
const dx = lineEnd.x - lineStart.x;
|
|
713
|
+
const dy = lineEnd.y - lineStart.y;
|
|
714
|
+
const lenSq = dx * dx + dy * dy;
|
|
715
|
+
if (lenSq === 0) {
|
|
716
|
+
const ex = pt.x - lineStart.x;
|
|
717
|
+
const ey = pt.y - lineStart.y;
|
|
718
|
+
return Math.sqrt(ex * ex + ey * ey);
|
|
719
|
+
}
|
|
720
|
+
const num = Math.abs(dy * pt.x - dx * pt.y + lineEnd.x * lineStart.y - lineEnd.y * lineStart.x);
|
|
721
|
+
return num / Math.sqrt(lenSq);
|
|
722
|
+
}
|
|
723
|
+
function smoothToSegments(points) {
|
|
724
|
+
if (points.length < 2) return [];
|
|
725
|
+
if (points.length === 2) {
|
|
726
|
+
const p0 = points[0];
|
|
727
|
+
const p1 = points[1];
|
|
728
|
+
if (!p0 || !p1) return [];
|
|
729
|
+
const mx = (p0.x + p1.x) / 2;
|
|
730
|
+
const my = (p0.y + p1.y) / 2;
|
|
731
|
+
return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
|
|
732
|
+
}
|
|
733
|
+
const segments = [];
|
|
734
|
+
const n = points.length;
|
|
735
|
+
for (let i = 0; i < n - 1; i++) {
|
|
736
|
+
const p0 = points[Math.max(0, i - 1)];
|
|
737
|
+
const p1 = points[i];
|
|
738
|
+
const p2 = points[i + 1];
|
|
739
|
+
const p3 = points[Math.min(n - 1, i + 2)];
|
|
740
|
+
if (!p0 || !p1 || !p2 || !p3) continue;
|
|
741
|
+
const cp1 = {
|
|
742
|
+
x: p1.x + (p2.x - p0.x) / 6,
|
|
743
|
+
y: p1.y + (p2.y - p0.y) / 6
|
|
744
|
+
};
|
|
745
|
+
const cp2 = {
|
|
746
|
+
x: p2.x - (p3.x - p1.x) / 6,
|
|
747
|
+
y: p2.y - (p3.y - p1.y) / 6
|
|
748
|
+
};
|
|
749
|
+
segments.push({ start: p1, cp1, cp2, end: p2 });
|
|
750
|
+
}
|
|
751
|
+
return segments;
|
|
752
|
+
}
|
|
753
|
+
|
|
690
754
|
// src/elements/element-renderer.ts
|
|
691
755
|
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "image", "html"]);
|
|
692
756
|
var ARROWHEAD_LENGTH = 12;
|
|
@@ -710,22 +774,18 @@ var ElementRenderer = class {
|
|
|
710
774
|
ctx.save();
|
|
711
775
|
ctx.translate(stroke.position.x, stroke.position.y);
|
|
712
776
|
ctx.strokeStyle = stroke.color;
|
|
713
|
-
ctx.lineWidth = stroke.width;
|
|
714
777
|
ctx.lineCap = "round";
|
|
715
778
|
ctx.lineJoin = "round";
|
|
716
779
|
ctx.globalAlpha = stroke.opacity;
|
|
717
|
-
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
ctx.
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
ctx.lineTo(pt.x, pt.y);
|
|
726
|
-
}
|
|
780
|
+
const segments = smoothToSegments(stroke.points);
|
|
781
|
+
for (const seg of segments) {
|
|
782
|
+
const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
|
|
783
|
+
ctx.lineWidth = w;
|
|
784
|
+
ctx.beginPath();
|
|
785
|
+
ctx.moveTo(seg.start.x, seg.start.y);
|
|
786
|
+
ctx.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
|
|
787
|
+
ctx.stroke();
|
|
727
788
|
}
|
|
728
|
-
ctx.stroke();
|
|
729
789
|
ctx.restore();
|
|
730
790
|
}
|
|
731
791
|
renderArrow(ctx, arrow) {
|
|
@@ -868,6 +928,9 @@ var ToolManager = class {
|
|
|
868
928
|
register(tool) {
|
|
869
929
|
this.tools.set(tool.name, tool);
|
|
870
930
|
}
|
|
931
|
+
getTool(name) {
|
|
932
|
+
return this.tools.get(name);
|
|
933
|
+
}
|
|
871
934
|
setTool(name, ctx) {
|
|
872
935
|
const tool = this.tools.get(name);
|
|
873
936
|
if (!tool) return;
|
|
@@ -1227,6 +1290,7 @@ var Viewport = class {
|
|
|
1227
1290
|
needsRender = true;
|
|
1228
1291
|
domNodes = /* @__PURE__ */ new Map();
|
|
1229
1292
|
htmlContent = /* @__PURE__ */ new Map();
|
|
1293
|
+
interactingElementId = null;
|
|
1230
1294
|
get ctx() {
|
|
1231
1295
|
return this.canvasEl.getContext("2d");
|
|
1232
1296
|
}
|
|
@@ -1272,6 +1336,7 @@ var Viewport = class {
|
|
|
1272
1336
|
this.store.add(image);
|
|
1273
1337
|
this.historyRecorder.commit();
|
|
1274
1338
|
this.requestRender();
|
|
1339
|
+
return image.id;
|
|
1275
1340
|
}
|
|
1276
1341
|
addHtmlElement(dom, position, size = { w: 200, h: 150 }) {
|
|
1277
1342
|
const el = createHtmlElement({ position, size });
|
|
@@ -1284,6 +1349,7 @@ var Viewport = class {
|
|
|
1284
1349
|
}
|
|
1285
1350
|
destroy() {
|
|
1286
1351
|
cancelAnimationFrame(this.animFrameId);
|
|
1352
|
+
this.stopInteracting();
|
|
1287
1353
|
this.noteEditor.destroy(this.store);
|
|
1288
1354
|
this.historyRecorder.destroy();
|
|
1289
1355
|
this.wrapper.removeEventListener("dblclick", this.onDblClick);
|
|
@@ -1341,11 +1407,69 @@ var Viewport = class {
|
|
|
1341
1407
|
}
|
|
1342
1408
|
onDblClick = (e) => {
|
|
1343
1409
|
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1410
|
+
const nodeEl = el?.closest("[data-element-id]");
|
|
1411
|
+
if (nodeEl) {
|
|
1412
|
+
const elementId = nodeEl.dataset["elementId"];
|
|
1413
|
+
if (elementId) {
|
|
1414
|
+
const element = this.store.getById(elementId);
|
|
1415
|
+
if (element?.type === "note") {
|
|
1416
|
+
this.startEditingNote(elementId);
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
const rect = this.wrapper.getBoundingClientRect();
|
|
1422
|
+
const screen = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
1423
|
+
const world = this.camera.screenToWorld(screen);
|
|
1424
|
+
const hit = this.hitTestWorld(world);
|
|
1425
|
+
if (hit?.type === "html") {
|
|
1426
|
+
this.startInteracting(hit.id);
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
hitTestWorld(world) {
|
|
1430
|
+
const elements = this.store.getAll().reverse();
|
|
1431
|
+
for (const el of elements) {
|
|
1432
|
+
if (!("size" in el)) continue;
|
|
1433
|
+
const { x, y } = el.position;
|
|
1434
|
+
const { w, h } = el.size;
|
|
1435
|
+
if (world.x >= x && world.x <= x + w && world.y >= y && world.y <= y + h) {
|
|
1436
|
+
return el;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return null;
|
|
1440
|
+
}
|
|
1441
|
+
startInteracting(id) {
|
|
1442
|
+
this.stopInteracting();
|
|
1443
|
+
const node = this.domNodes.get(id);
|
|
1444
|
+
if (!node) return;
|
|
1445
|
+
this.interactingElementId = id;
|
|
1446
|
+
node.style.pointerEvents = "auto";
|
|
1447
|
+
window.addEventListener("keydown", this.onInteractKeyDown);
|
|
1448
|
+
window.addEventListener("pointerdown", this.onInteractPointerDown);
|
|
1449
|
+
}
|
|
1450
|
+
stopInteracting() {
|
|
1451
|
+
if (!this.interactingElementId) return;
|
|
1452
|
+
const node = this.domNodes.get(this.interactingElementId);
|
|
1453
|
+
if (node) {
|
|
1454
|
+
node.style.pointerEvents = "none";
|
|
1455
|
+
}
|
|
1456
|
+
this.interactingElementId = null;
|
|
1457
|
+
window.removeEventListener("keydown", this.onInteractKeyDown);
|
|
1458
|
+
window.removeEventListener("pointerdown", this.onInteractPointerDown);
|
|
1459
|
+
}
|
|
1460
|
+
onInteractKeyDown = (e) => {
|
|
1461
|
+
if (e.key === "Escape") {
|
|
1462
|
+
this.stopInteracting();
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
onInteractPointerDown = (e) => {
|
|
1466
|
+
if (!this.interactingElementId) return;
|
|
1467
|
+
const target = e.target;
|
|
1468
|
+
if (!target) return;
|
|
1469
|
+
const node = this.domNodes.get(this.interactingElementId);
|
|
1470
|
+
if (node && !node.contains(target)) {
|
|
1471
|
+
this.stopInteracting();
|
|
1472
|
+
}
|
|
1349
1473
|
};
|
|
1350
1474
|
onDragOver = (e) => {
|
|
1351
1475
|
e.preventDefault();
|
|
@@ -1443,7 +1567,8 @@ var Viewport = class {
|
|
|
1443
1567
|
if (content) {
|
|
1444
1568
|
node.dataset["initialized"] = "true";
|
|
1445
1569
|
Object.assign(node.style, {
|
|
1446
|
-
overflow: "hidden"
|
|
1570
|
+
overflow: "hidden",
|
|
1571
|
+
pointerEvents: "none"
|
|
1447
1572
|
});
|
|
1448
1573
|
node.appendChild(content);
|
|
1449
1574
|
}
|
|
@@ -1546,15 +1671,19 @@ var HandTool = class {
|
|
|
1546
1671
|
|
|
1547
1672
|
// src/tools/pencil-tool.ts
|
|
1548
1673
|
var MIN_POINTS_FOR_STROKE = 2;
|
|
1674
|
+
var DEFAULT_SMOOTHING = 1.5;
|
|
1675
|
+
var DEFAULT_PRESSURE = 0.5;
|
|
1549
1676
|
var PencilTool = class {
|
|
1550
1677
|
name = "pencil";
|
|
1551
1678
|
drawing = false;
|
|
1552
1679
|
points = [];
|
|
1553
1680
|
color;
|
|
1554
1681
|
width;
|
|
1682
|
+
smoothing;
|
|
1555
1683
|
constructor(options = {}) {
|
|
1556
1684
|
this.color = options.color ?? "#000000";
|
|
1557
1685
|
this.width = options.width ?? 2;
|
|
1686
|
+
this.smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
|
|
1558
1687
|
}
|
|
1559
1688
|
onActivate(ctx) {
|
|
1560
1689
|
ctx.setCursor?.("crosshair");
|
|
@@ -1565,16 +1694,19 @@ var PencilTool = class {
|
|
|
1565
1694
|
setOptions(options) {
|
|
1566
1695
|
if (options.color !== void 0) this.color = options.color;
|
|
1567
1696
|
if (options.width !== void 0) this.width = options.width;
|
|
1697
|
+
if (options.smoothing !== void 0) this.smoothing = options.smoothing;
|
|
1568
1698
|
}
|
|
1569
1699
|
onPointerDown(state, ctx) {
|
|
1570
1700
|
this.drawing = true;
|
|
1571
1701
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
1572
|
-
|
|
1702
|
+
const pressure = state.pressure === 0 ? DEFAULT_PRESSURE : state.pressure;
|
|
1703
|
+
this.points = [{ x: world.x, y: world.y, pressure }];
|
|
1573
1704
|
}
|
|
1574
1705
|
onPointerMove(state, ctx) {
|
|
1575
1706
|
if (!this.drawing) return;
|
|
1576
1707
|
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
1577
|
-
|
|
1708
|
+
const pressure = state.pressure === 0 ? DEFAULT_PRESSURE : state.pressure;
|
|
1709
|
+
this.points.push({ x: world.x, y: world.y, pressure });
|
|
1578
1710
|
ctx.requestRender();
|
|
1579
1711
|
}
|
|
1580
1712
|
onPointerUp(_state, ctx) {
|
|
@@ -1584,8 +1716,9 @@ var PencilTool = class {
|
|
|
1584
1716
|
this.points = [];
|
|
1585
1717
|
return;
|
|
1586
1718
|
}
|
|
1719
|
+
const simplified = simplifyPoints(this.points, this.smoothing);
|
|
1587
1720
|
const stroke = createStroke({
|
|
1588
|
-
points:
|
|
1721
|
+
points: simplified,
|
|
1589
1722
|
color: this.color,
|
|
1590
1723
|
width: this.width
|
|
1591
1724
|
});
|
|
@@ -1597,19 +1730,18 @@ var PencilTool = class {
|
|
|
1597
1730
|
if (!this.drawing || this.points.length < 2) return;
|
|
1598
1731
|
ctx.save();
|
|
1599
1732
|
ctx.strokeStyle = this.color;
|
|
1600
|
-
ctx.lineWidth = this.width;
|
|
1601
1733
|
ctx.lineCap = "round";
|
|
1602
1734
|
ctx.lineJoin = "round";
|
|
1603
1735
|
ctx.globalAlpha = 0.8;
|
|
1604
|
-
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1736
|
+
const segments = smoothToSegments(this.points);
|
|
1737
|
+
for (const seg of segments) {
|
|
1738
|
+
const w = (pressureToWidth(seg.start.pressure, this.width) + pressureToWidth(seg.end.pressure, this.width)) / 2;
|
|
1739
|
+
ctx.lineWidth = w;
|
|
1740
|
+
ctx.beginPath();
|
|
1741
|
+
ctx.moveTo(seg.start.x, seg.start.y);
|
|
1742
|
+
ctx.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
|
|
1743
|
+
ctx.stroke();
|
|
1611
1744
|
}
|
|
1612
|
-
ctx.stroke();
|
|
1613
1745
|
ctx.restore();
|
|
1614
1746
|
}
|
|
1615
1747
|
};
|
|
@@ -1897,7 +2029,7 @@ var SelectTool = class {
|
|
|
1897
2029
|
handleResize(world, ctx) {
|
|
1898
2030
|
if (this.mode.type !== "resizing") return;
|
|
1899
2031
|
const el = ctx.store.getById(this.mode.elementId);
|
|
1900
|
-
if (!el || !("size" in el)) return;
|
|
2032
|
+
if (!el || !("size" in el) || el.locked) return;
|
|
1901
2033
|
const { handle } = this.mode;
|
|
1902
2034
|
const dx = world.x - this.lastWorld.x;
|
|
1903
2035
|
const dy = world.y - this.lastWorld.y;
|
|
@@ -2104,6 +2236,10 @@ var ArrowTool = class {
|
|
|
2104
2236
|
this.color = options.color ?? "#000000";
|
|
2105
2237
|
this.width = options.width ?? 2;
|
|
2106
2238
|
}
|
|
2239
|
+
setOptions(options) {
|
|
2240
|
+
if (options.color !== void 0) this.color = options.color;
|
|
2241
|
+
if (options.width !== void 0) this.width = options.width;
|
|
2242
|
+
}
|
|
2107
2243
|
onPointerDown(state, ctx) {
|
|
2108
2244
|
this.drawing = true;
|
|
2109
2245
|
this.start = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
@@ -2168,6 +2304,10 @@ var NoteTool = class {
|
|
|
2168
2304
|
this.backgroundColor = options.backgroundColor ?? "#ffeb3b";
|
|
2169
2305
|
this.size = options.size ?? { w: 200, h: 100 };
|
|
2170
2306
|
}
|
|
2307
|
+
setOptions(options) {
|
|
2308
|
+
if (options.backgroundColor !== void 0) this.backgroundColor = options.backgroundColor;
|
|
2309
|
+
if (options.size !== void 0) this.size = options.size;
|
|
2310
|
+
}
|
|
2171
2311
|
onPointerDown(_state, _ctx) {
|
|
2172
2312
|
}
|
|
2173
2313
|
onPointerMove(_state, _ctx) {
|
|
@@ -2217,7 +2357,7 @@ var ImageTool = class {
|
|
|
2217
2357
|
};
|
|
2218
2358
|
|
|
2219
2359
|
// src/index.ts
|
|
2220
|
-
var VERSION = "0.1
|
|
2360
|
+
var VERSION = "0.2.1";
|
|
2221
2361
|
export {
|
|
2222
2362
|
AddElementCommand,
|
|
2223
2363
|
ArrowTool,
|