@tsdraw/core 0.4.0 → 0.5.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 +194 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -1
- package/dist/index.d.ts +64 -1
- package/dist/index.js +193 -103
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -39,9 +39,53 @@ declare const DRAG_DISTANCE_SQUARED = 36;
|
|
|
39
39
|
declare const DEFAULT_COLORS: Record<string, string>;
|
|
40
40
|
declare const MAX_POINTS_PER_SHAPE = 200;
|
|
41
41
|
|
|
42
|
+
interface TsdrawPageRecord {
|
|
43
|
+
id: string;
|
|
44
|
+
typeName: 'page';
|
|
45
|
+
pageId: string;
|
|
46
|
+
shapeIds: ShapeId[];
|
|
47
|
+
erasingShapeIds: ShapeId[];
|
|
48
|
+
}
|
|
49
|
+
interface TsdrawShapeRecord {
|
|
50
|
+
id: ShapeId;
|
|
51
|
+
typeName: 'shape';
|
|
52
|
+
shape: Shape;
|
|
53
|
+
}
|
|
54
|
+
type TsdrawPersistedRecord = TsdrawPageRecord | TsdrawShapeRecord;
|
|
55
|
+
interface TsdrawDocumentSnapshot {
|
|
56
|
+
records: TsdrawPersistedRecord[];
|
|
57
|
+
}
|
|
58
|
+
interface TsdrawSessionStateSnapshot {
|
|
59
|
+
version: 1;
|
|
60
|
+
viewport: {
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
zoom: number;
|
|
64
|
+
};
|
|
65
|
+
currentToolId: string;
|
|
66
|
+
drawStyle: {
|
|
67
|
+
color: ColorStyle;
|
|
68
|
+
dash: DashStyle;
|
|
69
|
+
size: SizeStyle;
|
|
70
|
+
};
|
|
71
|
+
selectedShapeIds: ShapeId[];
|
|
72
|
+
}
|
|
73
|
+
interface TsdrawEditorSnapshot {
|
|
74
|
+
document: TsdrawDocumentSnapshot;
|
|
75
|
+
state: TsdrawSessionStateSnapshot;
|
|
76
|
+
}
|
|
77
|
+
interface DocumentStoreSnapshot {
|
|
78
|
+
page: PageState;
|
|
79
|
+
order: ShapeId[];
|
|
80
|
+
}
|
|
81
|
+
declare function documentSnapshotToRecords(snapshot: DocumentStoreSnapshot): TsdrawPersistedRecord[];
|
|
82
|
+
declare function recordsToDocumentSnapshot(records: TsdrawPersistedRecord[]): DocumentStoreSnapshot | null;
|
|
83
|
+
|
|
84
|
+
type DocumentStoreListener = () => void;
|
|
42
85
|
declare class DocumentStore {
|
|
43
86
|
private state;
|
|
44
87
|
private order;
|
|
88
|
+
private readonly listeners;
|
|
45
89
|
getPage(): PageState;
|
|
46
90
|
getShape(id: ShapeId): Shape | undefined;
|
|
47
91
|
getCurrentPageShapesSorted(): Shape[];
|
|
@@ -58,6 +102,10 @@ declare class DocumentStore {
|
|
|
58
102
|
maxX: number;
|
|
59
103
|
maxY: number;
|
|
60
104
|
}): Set<ShapeId>;
|
|
105
|
+
getSnapshot(): DocumentStoreSnapshot;
|
|
106
|
+
loadSnapshot(snapshot: DocumentStoreSnapshot): void;
|
|
107
|
+
listen(listener: DocumentStoreListener): () => void;
|
|
108
|
+
private emitChange;
|
|
61
109
|
}
|
|
62
110
|
|
|
63
111
|
interface PointerInput {
|
|
@@ -228,6 +276,7 @@ interface EditorOptions {
|
|
|
228
276
|
toolDefinitions?: ToolDefinition[];
|
|
229
277
|
initialToolId?: ToolId;
|
|
230
278
|
}
|
|
279
|
+
type EditorListener = () => void;
|
|
231
280
|
declare class Editor {
|
|
232
281
|
readonly store: DocumentStore;
|
|
233
282
|
readonly input: InputManager;
|
|
@@ -239,6 +288,7 @@ declare class Editor {
|
|
|
239
288
|
};
|
|
240
289
|
private drawStyle;
|
|
241
290
|
private readonly toolStateContext;
|
|
291
|
+
private readonly listeners;
|
|
242
292
|
constructor(opts?: EditorOptions);
|
|
243
293
|
registerToolDefinition(toolDefinition: ToolDefinition): void;
|
|
244
294
|
private getDefaultToolDefinitions;
|
|
@@ -269,12 +319,25 @@ declare class Editor {
|
|
|
269
319
|
dash: DashStyle;
|
|
270
320
|
size: SizeStyle;
|
|
271
321
|
}>): void;
|
|
322
|
+
setViewport(partial: Partial<Viewport>): void;
|
|
272
323
|
panBy(dx: number, dy: number): void;
|
|
324
|
+
getDocumentSnapshot(): TsdrawDocumentSnapshot;
|
|
325
|
+
loadDocumentSnapshot(snapshot: TsdrawDocumentSnapshot): void;
|
|
326
|
+
getSessionStateSnapshot(args?: {
|
|
327
|
+
selectedShapeIds?: ShapeId[];
|
|
328
|
+
}): TsdrawSessionStateSnapshot;
|
|
329
|
+
loadSessionStateSnapshot(snapshot: TsdrawSessionStateSnapshot): ShapeId[];
|
|
330
|
+
getPersistenceSnapshot(args?: {
|
|
331
|
+
selectedShapeIds?: ShapeId[];
|
|
332
|
+
}): TsdrawEditorSnapshot;
|
|
333
|
+
loadPersistenceSnapshot(snapshot: Partial<TsdrawEditorSnapshot>): ShapeId[];
|
|
334
|
+
listen(listener: EditorListener): () => void;
|
|
273
335
|
screenToPage(screenX: number, screenY: number): {
|
|
274
336
|
x: number;
|
|
275
337
|
y: number;
|
|
276
338
|
};
|
|
277
339
|
render(ctx: CanvasRenderingContext2D): void;
|
|
340
|
+
private emitChange;
|
|
278
341
|
}
|
|
279
342
|
|
|
280
343
|
declare class PenIdleState extends StateNode {
|
|
@@ -461,4 +524,4 @@ declare function decodePathToPoints(segments: {
|
|
|
461
524
|
y: number;
|
|
462
525
|
}[];
|
|
463
526
|
|
|
464
|
-
export { type Bounds, CanvasRenderer, type ColorStyle, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, type DashStyle, type DefaultToolId, DocumentStore, type DrawSegment, type DrawShape, ERASER_MARGIN, Editor, type EditorOptions, EraserErasingState, EraserIdleState, EraserPointingState, HandDraggingState, HandIdleState, type ICanvasRenderer, type IEditor, InputManager, MAX_POINTS_PER_SHAPE, type PageState, PenDrawingState, PenIdleState, type PointerInput, type ResizeHandle, STROKE_WIDTHS, type SegmentType, SelectIdleState, type SelectionBounds, type Shape, type ShapeId, type SizeStyle, StateNode, type StateNodeConstructor, type ToolDefinition, type ToolId, type ToolKeyInfo, ToolManager, type ToolPointerDownInfo, type ToolPointerMoveInfo, type ToolStateContext, type ToolStateTransitionInfo, type TransformSnapshot, type TsdrawRenderTheme, type Vec3, type Viewport, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, encodePoints, getSelectionBoundsPage, getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
|
|
527
|
+
export { type Bounds, CanvasRenderer, type ColorStyle, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, type DashStyle, type DefaultToolId, DocumentStore, type DocumentStoreSnapshot, type DrawSegment, type DrawShape, ERASER_MARGIN, Editor, type EditorOptions, EraserErasingState, EraserIdleState, EraserPointingState, HandDraggingState, HandIdleState, type ICanvasRenderer, type IEditor, InputManager, MAX_POINTS_PER_SHAPE, type PageState, PenDrawingState, PenIdleState, type PointerInput, type ResizeHandle, STROKE_WIDTHS, type SegmentType, SelectIdleState, type SelectionBounds, type Shape, type ShapeId, type SizeStyle, StateNode, type StateNodeConstructor, type ToolDefinition, type ToolId, type ToolKeyInfo, ToolManager, type ToolPointerDownInfo, type ToolPointerMoveInfo, type ToolStateContext, type ToolStateTransitionInfo, type TransformSnapshot, type TsdrawDocumentSnapshot, type TsdrawEditorSnapshot, type TsdrawPageRecord, type TsdrawPersistedRecord, type TsdrawRenderTheme, type TsdrawSessionStateSnapshot, type TsdrawShapeRecord, type Vec3, type Viewport, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, documentSnapshotToRecords, encodePoints, getSelectionBoundsPage, getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, recordsToDocumentSnapshot, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
|
package/dist/index.d.ts
CHANGED
|
@@ -39,9 +39,53 @@ declare const DRAG_DISTANCE_SQUARED = 36;
|
|
|
39
39
|
declare const DEFAULT_COLORS: Record<string, string>;
|
|
40
40
|
declare const MAX_POINTS_PER_SHAPE = 200;
|
|
41
41
|
|
|
42
|
+
interface TsdrawPageRecord {
|
|
43
|
+
id: string;
|
|
44
|
+
typeName: 'page';
|
|
45
|
+
pageId: string;
|
|
46
|
+
shapeIds: ShapeId[];
|
|
47
|
+
erasingShapeIds: ShapeId[];
|
|
48
|
+
}
|
|
49
|
+
interface TsdrawShapeRecord {
|
|
50
|
+
id: ShapeId;
|
|
51
|
+
typeName: 'shape';
|
|
52
|
+
shape: Shape;
|
|
53
|
+
}
|
|
54
|
+
type TsdrawPersistedRecord = TsdrawPageRecord | TsdrawShapeRecord;
|
|
55
|
+
interface TsdrawDocumentSnapshot {
|
|
56
|
+
records: TsdrawPersistedRecord[];
|
|
57
|
+
}
|
|
58
|
+
interface TsdrawSessionStateSnapshot {
|
|
59
|
+
version: 1;
|
|
60
|
+
viewport: {
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
zoom: number;
|
|
64
|
+
};
|
|
65
|
+
currentToolId: string;
|
|
66
|
+
drawStyle: {
|
|
67
|
+
color: ColorStyle;
|
|
68
|
+
dash: DashStyle;
|
|
69
|
+
size: SizeStyle;
|
|
70
|
+
};
|
|
71
|
+
selectedShapeIds: ShapeId[];
|
|
72
|
+
}
|
|
73
|
+
interface TsdrawEditorSnapshot {
|
|
74
|
+
document: TsdrawDocumentSnapshot;
|
|
75
|
+
state: TsdrawSessionStateSnapshot;
|
|
76
|
+
}
|
|
77
|
+
interface DocumentStoreSnapshot {
|
|
78
|
+
page: PageState;
|
|
79
|
+
order: ShapeId[];
|
|
80
|
+
}
|
|
81
|
+
declare function documentSnapshotToRecords(snapshot: DocumentStoreSnapshot): TsdrawPersistedRecord[];
|
|
82
|
+
declare function recordsToDocumentSnapshot(records: TsdrawPersistedRecord[]): DocumentStoreSnapshot | null;
|
|
83
|
+
|
|
84
|
+
type DocumentStoreListener = () => void;
|
|
42
85
|
declare class DocumentStore {
|
|
43
86
|
private state;
|
|
44
87
|
private order;
|
|
88
|
+
private readonly listeners;
|
|
45
89
|
getPage(): PageState;
|
|
46
90
|
getShape(id: ShapeId): Shape | undefined;
|
|
47
91
|
getCurrentPageShapesSorted(): Shape[];
|
|
@@ -58,6 +102,10 @@ declare class DocumentStore {
|
|
|
58
102
|
maxX: number;
|
|
59
103
|
maxY: number;
|
|
60
104
|
}): Set<ShapeId>;
|
|
105
|
+
getSnapshot(): DocumentStoreSnapshot;
|
|
106
|
+
loadSnapshot(snapshot: DocumentStoreSnapshot): void;
|
|
107
|
+
listen(listener: DocumentStoreListener): () => void;
|
|
108
|
+
private emitChange;
|
|
61
109
|
}
|
|
62
110
|
|
|
63
111
|
interface PointerInput {
|
|
@@ -228,6 +276,7 @@ interface EditorOptions {
|
|
|
228
276
|
toolDefinitions?: ToolDefinition[];
|
|
229
277
|
initialToolId?: ToolId;
|
|
230
278
|
}
|
|
279
|
+
type EditorListener = () => void;
|
|
231
280
|
declare class Editor {
|
|
232
281
|
readonly store: DocumentStore;
|
|
233
282
|
readonly input: InputManager;
|
|
@@ -239,6 +288,7 @@ declare class Editor {
|
|
|
239
288
|
};
|
|
240
289
|
private drawStyle;
|
|
241
290
|
private readonly toolStateContext;
|
|
291
|
+
private readonly listeners;
|
|
242
292
|
constructor(opts?: EditorOptions);
|
|
243
293
|
registerToolDefinition(toolDefinition: ToolDefinition): void;
|
|
244
294
|
private getDefaultToolDefinitions;
|
|
@@ -269,12 +319,25 @@ declare class Editor {
|
|
|
269
319
|
dash: DashStyle;
|
|
270
320
|
size: SizeStyle;
|
|
271
321
|
}>): void;
|
|
322
|
+
setViewport(partial: Partial<Viewport>): void;
|
|
272
323
|
panBy(dx: number, dy: number): void;
|
|
324
|
+
getDocumentSnapshot(): TsdrawDocumentSnapshot;
|
|
325
|
+
loadDocumentSnapshot(snapshot: TsdrawDocumentSnapshot): void;
|
|
326
|
+
getSessionStateSnapshot(args?: {
|
|
327
|
+
selectedShapeIds?: ShapeId[];
|
|
328
|
+
}): TsdrawSessionStateSnapshot;
|
|
329
|
+
loadSessionStateSnapshot(snapshot: TsdrawSessionStateSnapshot): ShapeId[];
|
|
330
|
+
getPersistenceSnapshot(args?: {
|
|
331
|
+
selectedShapeIds?: ShapeId[];
|
|
332
|
+
}): TsdrawEditorSnapshot;
|
|
333
|
+
loadPersistenceSnapshot(snapshot: Partial<TsdrawEditorSnapshot>): ShapeId[];
|
|
334
|
+
listen(listener: EditorListener): () => void;
|
|
273
335
|
screenToPage(screenX: number, screenY: number): {
|
|
274
336
|
x: number;
|
|
275
337
|
y: number;
|
|
276
338
|
};
|
|
277
339
|
render(ctx: CanvasRenderingContext2D): void;
|
|
340
|
+
private emitChange;
|
|
278
341
|
}
|
|
279
342
|
|
|
280
343
|
declare class PenIdleState extends StateNode {
|
|
@@ -461,4 +524,4 @@ declare function decodePathToPoints(segments: {
|
|
|
461
524
|
y: number;
|
|
462
525
|
}[];
|
|
463
526
|
|
|
464
|
-
export { type Bounds, CanvasRenderer, type ColorStyle, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, type DashStyle, type DefaultToolId, DocumentStore, type DrawSegment, type DrawShape, ERASER_MARGIN, Editor, type EditorOptions, EraserErasingState, EraserIdleState, EraserPointingState, HandDraggingState, HandIdleState, type ICanvasRenderer, type IEditor, InputManager, MAX_POINTS_PER_SHAPE, type PageState, PenDrawingState, PenIdleState, type PointerInput, type ResizeHandle, STROKE_WIDTHS, type SegmentType, SelectIdleState, type SelectionBounds, type Shape, type ShapeId, type SizeStyle, StateNode, type StateNodeConstructor, type ToolDefinition, type ToolId, type ToolKeyInfo, ToolManager, type ToolPointerDownInfo, type ToolPointerMoveInfo, type ToolStateContext, type ToolStateTransitionInfo, type TransformSnapshot, type TsdrawRenderTheme, type Vec3, type Viewport, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, encodePoints, getSelectionBoundsPage, getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
|
|
527
|
+
export { type Bounds, CanvasRenderer, type ColorStyle, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, type DashStyle, type DefaultToolId, DocumentStore, type DocumentStoreSnapshot, type DrawSegment, type DrawShape, ERASER_MARGIN, Editor, type EditorOptions, EraserErasingState, EraserIdleState, EraserPointingState, HandDraggingState, HandIdleState, type ICanvasRenderer, type IEditor, InputManager, MAX_POINTS_PER_SHAPE, type PageState, PenDrawingState, PenIdleState, type PointerInput, type ResizeHandle, STROKE_WIDTHS, type SegmentType, SelectIdleState, type SelectionBounds, type Shape, type ShapeId, type SizeStyle, StateNode, type StateNodeConstructor, type ToolDefinition, type ToolId, type ToolKeyInfo, ToolManager, type ToolPointerDownInfo, type ToolPointerMoveInfo, type ToolStateContext, type ToolStateTransitionInfo, type TransformSnapshot, type TsdrawDocumentSnapshot, type TsdrawEditorSnapshot, type TsdrawPageRecord, type TsdrawPersistedRecord, type TsdrawRenderTheme, type TsdrawSessionStateSnapshot, type TsdrawShapeRecord, type Vec3, type Viewport, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, documentSnapshotToRecords, encodePoints, getSelectionBoundsPage, getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, recordsToDocumentSnapshot, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
|
package/dist/index.js
CHANGED
|
@@ -69,6 +69,12 @@ function decodePathToPoints(segments, ox, oy) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// src/store/documentStore.ts
|
|
72
|
+
function cloneValue(value) {
|
|
73
|
+
if (typeof structuredClone === "function") {
|
|
74
|
+
return structuredClone(value);
|
|
75
|
+
}
|
|
76
|
+
return JSON.parse(JSON.stringify(value));
|
|
77
|
+
}
|
|
72
78
|
var DocumentStore = class {
|
|
73
79
|
state = {
|
|
74
80
|
id: "page-1",
|
|
@@ -76,6 +82,7 @@ var DocumentStore = class {
|
|
|
76
82
|
erasingShapeIds: []
|
|
77
83
|
};
|
|
78
84
|
order = [];
|
|
85
|
+
listeners = /* @__PURE__ */ new Set();
|
|
79
86
|
getPage() {
|
|
80
87
|
return this.state;
|
|
81
88
|
}
|
|
@@ -96,15 +103,18 @@ var DocumentStore = class {
|
|
|
96
103
|
}
|
|
97
104
|
setErasingShapes(ids) {
|
|
98
105
|
this.state.erasingShapeIds = ids;
|
|
106
|
+
this.emitChange();
|
|
99
107
|
}
|
|
100
108
|
createShape(shape) {
|
|
101
109
|
this.state.shapes[shape.id] = shape;
|
|
102
110
|
this.order.push(shape.id);
|
|
111
|
+
this.emitChange();
|
|
103
112
|
}
|
|
104
113
|
updateShape(id, partial) {
|
|
105
114
|
const existing = this.state.shapes[id];
|
|
106
115
|
if (!existing) return;
|
|
107
116
|
this.state.shapes[id] = { ...existing, ...partial, id };
|
|
117
|
+
this.emitChange();
|
|
108
118
|
}
|
|
109
119
|
deleteShapes(ids) {
|
|
110
120
|
for (const id of ids) {
|
|
@@ -112,6 +122,7 @@ var DocumentStore = class {
|
|
|
112
122
|
this.order = this.order.filter((i) => i !== id);
|
|
113
123
|
}
|
|
114
124
|
this.state.erasingShapeIds = this.state.erasingShapeIds.filter((i) => !ids.includes(i));
|
|
125
|
+
this.emitChange();
|
|
115
126
|
}
|
|
116
127
|
getCurrentPageShapes() {
|
|
117
128
|
return Object.values(this.state.shapes);
|
|
@@ -127,6 +138,35 @@ var DocumentStore = class {
|
|
|
127
138
|
}
|
|
128
139
|
return ids;
|
|
129
140
|
}
|
|
141
|
+
getSnapshot() {
|
|
142
|
+
return {
|
|
143
|
+
page: cloneValue(this.state),
|
|
144
|
+
order: [...this.order]
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// Load snapshot into the document when loading a persistence snapshot (so on page reload)
|
|
148
|
+
loadSnapshot(snapshot) {
|
|
149
|
+
const pageState = cloneValue(snapshot.page);
|
|
150
|
+
const normalizedOrder = [...snapshot.order].filter((shapeId) => pageState.shapes[shapeId] != null);
|
|
151
|
+
this.state = {
|
|
152
|
+
id: pageState.id,
|
|
153
|
+
shapes: pageState.shapes,
|
|
154
|
+
erasingShapeIds: pageState.erasingShapeIds.filter((shapeId) => pageState.shapes[shapeId] != null)
|
|
155
|
+
};
|
|
156
|
+
this.order = normalizedOrder;
|
|
157
|
+
this.emitChange();
|
|
158
|
+
}
|
|
159
|
+
listen(listener) {
|
|
160
|
+
this.listeners.add(listener);
|
|
161
|
+
return () => {
|
|
162
|
+
this.listeners.delete(listener);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
emitChange() {
|
|
166
|
+
for (const listener of this.listeners) {
|
|
167
|
+
listener();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
130
170
|
};
|
|
131
171
|
function getShapeBounds(shape) {
|
|
132
172
|
if (shape.type !== "draw") {
|
|
@@ -213,105 +253,26 @@ function zoomViewport(viewport, factor, centerX, centerY) {
|
|
|
213
253
|
}
|
|
214
254
|
|
|
215
255
|
// src/utils/colors.ts
|
|
256
|
+
var DARK_COLORS = {
|
|
257
|
+
black: "#f0f0f0",
|
|
258
|
+
grey: "#aeb8c2",
|
|
259
|
+
"light-violet": "#cf6ef5",
|
|
260
|
+
violet: "#a83ce0",
|
|
261
|
+
blue: "#5b7dff",
|
|
262
|
+
"light-blue": "#4fb3ff",
|
|
263
|
+
yellow: "#f4b13a",
|
|
264
|
+
orange: "#ef7a24",
|
|
265
|
+
green: "#1fb27a",
|
|
266
|
+
"light-green": "#4ecb66",
|
|
267
|
+
"light-red": "#ff6f78",
|
|
268
|
+
red: "#f24343",
|
|
269
|
+
white: "#ffffff"
|
|
270
|
+
};
|
|
216
271
|
function resolveThemeColor(colorStyle, theme) {
|
|
217
|
-
const
|
|
218
|
-
if (!
|
|
219
|
-
if (theme === "light") return
|
|
220
|
-
return
|
|
221
|
-
}
|
|
222
|
-
function invertAndHueRotate180(color) {
|
|
223
|
-
const rgb = parseHexColor(color);
|
|
224
|
-
if (!rgb) return color;
|
|
225
|
-
const inverted = {
|
|
226
|
-
r: 255 - rgb.r,
|
|
227
|
-
g: 255 - rgb.g,
|
|
228
|
-
b: 255 - rgb.b
|
|
229
|
-
};
|
|
230
|
-
const hsl = rgbToHsl(inverted.r, inverted.g, inverted.b);
|
|
231
|
-
const rotated = hslToRgb((hsl.h + 180) % 360, hsl.s, hsl.l);
|
|
232
|
-
return rgbToHex(rotated.r, rotated.g, rotated.b);
|
|
233
|
-
}
|
|
234
|
-
function parseHexColor(color) {
|
|
235
|
-
const normalized = color.trim().toLowerCase();
|
|
236
|
-
if (!normalized.startsWith("#")) return null;
|
|
237
|
-
if (normalized.length === 4) {
|
|
238
|
-
return {
|
|
239
|
-
r: parseInt(normalized[1] + normalized[1], 16),
|
|
240
|
-
g: parseInt(normalized[2] + normalized[2], 16),
|
|
241
|
-
b: parseInt(normalized[3] + normalized[3], 16)
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
if (normalized.length !== 7) return null;
|
|
245
|
-
return {
|
|
246
|
-
r: parseInt(normalized.slice(1, 3), 16),
|
|
247
|
-
g: parseInt(normalized.slice(3, 5), 16),
|
|
248
|
-
b: parseInt(normalized.slice(5, 7), 16)
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
function rgbToHex(r, g, b) {
|
|
252
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
253
|
-
}
|
|
254
|
-
function toHex(value) {
|
|
255
|
-
return Math.round(Math.max(0, Math.min(255, value))).toString(16).padStart(2, "0");
|
|
256
|
-
}
|
|
257
|
-
function rgbToHsl(r, g, b) {
|
|
258
|
-
const red = r / 255;
|
|
259
|
-
const green = g / 255;
|
|
260
|
-
const blue = b / 255;
|
|
261
|
-
const maxChannel = Math.max(red, green, blue);
|
|
262
|
-
const minChannel = Math.min(red, green, blue);
|
|
263
|
-
const delta = maxChannel - minChannel;
|
|
264
|
-
const lightness = (maxChannel + minChannel) / 2;
|
|
265
|
-
if (delta === 0) {
|
|
266
|
-
return { h: 0, s: 0, l: lightness };
|
|
267
|
-
}
|
|
268
|
-
const saturation = lightness > 0.5 ? delta / (2 - maxChannel - minChannel) : delta / (maxChannel + minChannel);
|
|
269
|
-
let hue = 0;
|
|
270
|
-
if (maxChannel === red) {
|
|
271
|
-
hue = ((green - blue) / delta + (green < blue ? 6 : 0)) * 60;
|
|
272
|
-
} else if (maxChannel === green) {
|
|
273
|
-
hue = ((blue - red) / delta + 2) * 60;
|
|
274
|
-
} else {
|
|
275
|
-
hue = ((red - green) / delta + 4) * 60;
|
|
276
|
-
}
|
|
277
|
-
return { h: hue, s: saturation, l: lightness };
|
|
278
|
-
}
|
|
279
|
-
function hslToRgb(h, s, l) {
|
|
280
|
-
if (s === 0) {
|
|
281
|
-
const channel = l * 255;
|
|
282
|
-
return { r: channel, g: channel, b: channel };
|
|
283
|
-
}
|
|
284
|
-
const chroma = (1 - Math.abs(2 * l - 1)) * s;
|
|
285
|
-
const hueSegment = h / 60;
|
|
286
|
-
const x = chroma * (1 - Math.abs(hueSegment % 2 - 1));
|
|
287
|
-
let red = 0;
|
|
288
|
-
let green = 0;
|
|
289
|
-
let blue = 0;
|
|
290
|
-
if (hueSegment >= 0 && hueSegment < 1) {
|
|
291
|
-
red = chroma;
|
|
292
|
-
green = x;
|
|
293
|
-
} else if (hueSegment < 2) {
|
|
294
|
-
red = x;
|
|
295
|
-
green = chroma;
|
|
296
|
-
} else if (hueSegment < 3) {
|
|
297
|
-
green = chroma;
|
|
298
|
-
blue = x;
|
|
299
|
-
} else if (hueSegment < 4) {
|
|
300
|
-
green = x;
|
|
301
|
-
blue = chroma;
|
|
302
|
-
} else if (hueSegment < 5) {
|
|
303
|
-
red = x;
|
|
304
|
-
blue = chroma;
|
|
305
|
-
} else {
|
|
306
|
-
red = chroma;
|
|
307
|
-
blue = x;
|
|
308
|
-
}
|
|
309
|
-
const match = l - chroma / 2;
|
|
310
|
-
return {
|
|
311
|
-
r: (red + match) * 255,
|
|
312
|
-
g: (green + match) * 255,
|
|
313
|
-
b: (blue + match) * 255
|
|
314
|
-
};
|
|
272
|
+
const lightThemeColor = DEFAULT_COLORS[colorStyle];
|
|
273
|
+
if (!lightThemeColor) return colorStyle;
|
|
274
|
+
if (theme === "light") return lightThemeColor;
|
|
275
|
+
return DARK_COLORS[colorStyle] ?? lightThemeColor;
|
|
315
276
|
}
|
|
316
277
|
var CanvasRenderer = class {
|
|
317
278
|
theme = "light";
|
|
@@ -1288,10 +1249,68 @@ var HandDraggingState = class extends StateNode {
|
|
|
1288
1249
|
}
|
|
1289
1250
|
};
|
|
1290
1251
|
|
|
1252
|
+
// src/persistence/snapshots.ts
|
|
1253
|
+
var PAGE_RECORD_ID = "page:current";
|
|
1254
|
+
function cloneValue2(value) {
|
|
1255
|
+
if (typeof structuredClone === "function") {
|
|
1256
|
+
return structuredClone(value);
|
|
1257
|
+
}
|
|
1258
|
+
return JSON.parse(JSON.stringify(value));
|
|
1259
|
+
}
|
|
1260
|
+
function asDrawShape(value) {
|
|
1261
|
+
return cloneValue2(value);
|
|
1262
|
+
}
|
|
1263
|
+
function documentSnapshotToRecords(snapshot) {
|
|
1264
|
+
const shapeIds = [...snapshot.order].filter((id) => snapshot.page.shapes[id] != null);
|
|
1265
|
+
const pageRecord = {
|
|
1266
|
+
id: PAGE_RECORD_ID,
|
|
1267
|
+
typeName: "page",
|
|
1268
|
+
pageId: snapshot.page.id,
|
|
1269
|
+
shapeIds,
|
|
1270
|
+
erasingShapeIds: [...snapshot.page.erasingShapeIds]
|
|
1271
|
+
};
|
|
1272
|
+
const shapeRecords = shapeIds.map((shapeId) => snapshot.page.shapes[shapeId]).filter((shape) => shape != null).map((shape) => ({
|
|
1273
|
+
id: shape.id,
|
|
1274
|
+
typeName: "shape",
|
|
1275
|
+
shape: asDrawShape(shape)
|
|
1276
|
+
}));
|
|
1277
|
+
return [pageRecord, ...shapeRecords];
|
|
1278
|
+
}
|
|
1279
|
+
function recordsToDocumentSnapshot(records) {
|
|
1280
|
+
const pageRecord = records.find((record) => record.typeName === "page");
|
|
1281
|
+
if (!pageRecord) {
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
const shapeRecordMap = /* @__PURE__ */ new Map();
|
|
1285
|
+
for (const record of records) {
|
|
1286
|
+
if (record.typeName === "shape") {
|
|
1287
|
+
shapeRecordMap.set(record.id, record);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
const shapes = {};
|
|
1291
|
+
const order = [];
|
|
1292
|
+
for (const shapeId of pageRecord.shapeIds) {
|
|
1293
|
+
const shapeRecord = shapeRecordMap.get(shapeId);
|
|
1294
|
+
if (!shapeRecord) continue;
|
|
1295
|
+
shapes[shapeId] = asDrawShape(shapeRecord.shape);
|
|
1296
|
+
order.push(shapeId);
|
|
1297
|
+
}
|
|
1298
|
+
return {
|
|
1299
|
+
page: {
|
|
1300
|
+
id: pageRecord.pageId,
|
|
1301
|
+
shapes,
|
|
1302
|
+
erasingShapeIds: [...pageRecord.erasingShapeIds].filter((shapeId) => shapes[shapeId] != null)
|
|
1303
|
+
},
|
|
1304
|
+
order
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1291
1308
|
// src/editor/Editor.ts
|
|
1292
1309
|
var shapeIdCounter = 0;
|
|
1310
|
+
var shapeIdRuntimeSeed = Math.random().toString(36).slice(2, 8);
|
|
1293
1311
|
function createShapeId() {
|
|
1294
|
-
|
|
1312
|
+
shapeIdCounter += 1;
|
|
1313
|
+
return `shape:${Date.now().toString(36)}-${shapeIdRuntimeSeed}-${shapeIdCounter.toString(36)}`;
|
|
1295
1314
|
}
|
|
1296
1315
|
var Editor = class {
|
|
1297
1316
|
store = new DocumentStore();
|
|
@@ -1307,9 +1326,11 @@ var Editor = class {
|
|
|
1307
1326
|
size: "m"
|
|
1308
1327
|
};
|
|
1309
1328
|
toolStateContext;
|
|
1329
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1310
1330
|
// Creates a new editor instance with the given options (with defaults if not provided)
|
|
1311
1331
|
constructor(opts = {}) {
|
|
1312
1332
|
this.options = { dragDistanceSquared: opts.dragDistanceSquared ?? DRAG_DISTANCE_SQUARED };
|
|
1333
|
+
this.store.listen(() => this.emitChange());
|
|
1313
1334
|
this.toolStateContext = {
|
|
1314
1335
|
transition: (id, info) => this.tools.transition(id, info)
|
|
1315
1336
|
};
|
|
@@ -1319,7 +1340,7 @@ var Editor = class {
|
|
|
1319
1340
|
for (const customTool of opts.toolDefinitions ?? []) {
|
|
1320
1341
|
this.registerToolDefinition(customTool);
|
|
1321
1342
|
}
|
|
1322
|
-
this.
|
|
1343
|
+
this.setCurrentTool(opts.initialToolId ?? "pen");
|
|
1323
1344
|
}
|
|
1324
1345
|
registerToolDefinition(toolDefinition) {
|
|
1325
1346
|
for (const stateConstructor of toolDefinition.stateConstructors) {
|
|
@@ -1380,6 +1401,7 @@ var Editor = class {
|
|
|
1380
1401
|
}
|
|
1381
1402
|
setCurrentTool(id) {
|
|
1382
1403
|
this.tools.setCurrentTool(id);
|
|
1404
|
+
this.emitChange();
|
|
1383
1405
|
}
|
|
1384
1406
|
getCurrentToolId() {
|
|
1385
1407
|
return this.tools.getCurrentToolId();
|
|
@@ -1389,10 +1411,73 @@ var Editor = class {
|
|
|
1389
1411
|
}
|
|
1390
1412
|
setCurrentDrawStyle(partial) {
|
|
1391
1413
|
this.drawStyle = { ...this.drawStyle, ...partial };
|
|
1414
|
+
this.emitChange();
|
|
1415
|
+
}
|
|
1416
|
+
setViewport(partial) {
|
|
1417
|
+
this.viewport = {
|
|
1418
|
+
x: partial.x ?? this.viewport.x,
|
|
1419
|
+
y: partial.y ?? this.viewport.y,
|
|
1420
|
+
zoom: partial.zoom ?? this.viewport.zoom
|
|
1421
|
+
};
|
|
1422
|
+
this.emitChange();
|
|
1392
1423
|
}
|
|
1393
1424
|
panBy(dx, dy) {
|
|
1394
|
-
this.
|
|
1395
|
-
|
|
1425
|
+
this.setViewport({
|
|
1426
|
+
x: this.viewport.x + dx,
|
|
1427
|
+
y: this.viewport.y + dy
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
getDocumentSnapshot() {
|
|
1431
|
+
return {
|
|
1432
|
+
records: documentSnapshotToRecords(this.store.getSnapshot())
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
loadDocumentSnapshot(snapshot) {
|
|
1436
|
+
const documentSnapshot = recordsToDocumentSnapshot(snapshot.records);
|
|
1437
|
+
if (!documentSnapshot) return;
|
|
1438
|
+
this.store.loadSnapshot(documentSnapshot);
|
|
1439
|
+
}
|
|
1440
|
+
getSessionStateSnapshot(args) {
|
|
1441
|
+
return {
|
|
1442
|
+
version: 1,
|
|
1443
|
+
viewport: {
|
|
1444
|
+
x: this.viewport.x,
|
|
1445
|
+
y: this.viewport.y,
|
|
1446
|
+
zoom: this.viewport.zoom
|
|
1447
|
+
},
|
|
1448
|
+
currentToolId: this.getCurrentToolId(),
|
|
1449
|
+
drawStyle: this.getCurrentDrawStyle(),
|
|
1450
|
+
selectedShapeIds: [...args?.selectedShapeIds ?? []]
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
loadSessionStateSnapshot(snapshot) {
|
|
1454
|
+
this.setViewport(snapshot.viewport);
|
|
1455
|
+
this.setCurrentDrawStyle(snapshot.drawStyle);
|
|
1456
|
+
if (this.tools.hasTool(snapshot.currentToolId)) {
|
|
1457
|
+
this.setCurrentTool(snapshot.currentToolId);
|
|
1458
|
+
}
|
|
1459
|
+
return [...snapshot.selectedShapeIds];
|
|
1460
|
+
}
|
|
1461
|
+
getPersistenceSnapshot(args) {
|
|
1462
|
+
return {
|
|
1463
|
+
document: this.getDocumentSnapshot(),
|
|
1464
|
+
state: this.getSessionStateSnapshot(args)
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
loadPersistenceSnapshot(snapshot) {
|
|
1468
|
+
if (snapshot.document) {
|
|
1469
|
+
this.loadDocumentSnapshot(snapshot.document);
|
|
1470
|
+
}
|
|
1471
|
+
if (snapshot.state) {
|
|
1472
|
+
return this.loadSessionStateSnapshot(snapshot.state);
|
|
1473
|
+
}
|
|
1474
|
+
return [];
|
|
1475
|
+
}
|
|
1476
|
+
listen(listener) {
|
|
1477
|
+
this.listeners.add(listener);
|
|
1478
|
+
return () => {
|
|
1479
|
+
this.listeners.delete(listener);
|
|
1480
|
+
};
|
|
1396
1481
|
}
|
|
1397
1482
|
// Convert screen coords to page coords
|
|
1398
1483
|
screenToPage(screenX, screenY) {
|
|
@@ -1405,6 +1490,11 @@ var Editor = class {
|
|
|
1405
1490
|
const visible = shapes.filter((s) => !erasingIds.has(s.id));
|
|
1406
1491
|
this.renderer.render(ctx, this.viewport, visible);
|
|
1407
1492
|
}
|
|
1493
|
+
emitChange() {
|
|
1494
|
+
for (const listener of this.listeners) {
|
|
1495
|
+
listener();
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1408
1498
|
};
|
|
1409
1499
|
|
|
1410
1500
|
// src/tools/select/selectHelpers.ts
|
|
@@ -1648,6 +1738,6 @@ function applyResize(editor, handle, startBounds, startShapes, pointer, lockAspe
|
|
|
1648
1738
|
}
|
|
1649
1739
|
}
|
|
1650
1740
|
|
|
1651
|
-
export { CanvasRenderer, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, DocumentStore, ERASER_MARGIN, Editor, EraserErasingState, EraserIdleState, EraserPointingState, HandDraggingState, HandIdleState, InputManager, MAX_POINTS_PER_SHAPE, PenDrawingState, PenIdleState, STROKE_WIDTHS, SelectIdleState, StateNode, ToolManager, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, encodePoints, getSelectionBoundsPage, getShapeBounds2 as getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
|
|
1741
|
+
export { CanvasRenderer, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, DocumentStore, ERASER_MARGIN, Editor, EraserErasingState, EraserIdleState, EraserPointingState, HandDraggingState, HandIdleState, InputManager, MAX_POINTS_PER_SHAPE, PenDrawingState, PenIdleState, STROKE_WIDTHS, SelectIdleState, StateNode, ToolManager, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, documentSnapshotToRecords, encodePoints, getSelectionBoundsPage, getShapeBounds2 as getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, recordsToDocumentSnapshot, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
|
|
1652
1742
|
//# sourceMappingURL=index.js.map
|
|
1653
1743
|
//# sourceMappingURL=index.js.map
|