@tsdraw/core 0.5.0 → 0.6.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 +165 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +165 -100
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -74,6 +74,11 @@ interface TsdrawEditorSnapshot {
|
|
|
74
74
|
document: TsdrawDocumentSnapshot;
|
|
75
75
|
state: TsdrawSessionStateSnapshot;
|
|
76
76
|
}
|
|
77
|
+
interface TsdrawHistorySnapshot {
|
|
78
|
+
version: 1;
|
|
79
|
+
undoStack: TsdrawDocumentSnapshot[];
|
|
80
|
+
redoStack: TsdrawDocumentSnapshot[];
|
|
81
|
+
}
|
|
77
82
|
interface DocumentStoreSnapshot {
|
|
78
83
|
page: PageState;
|
|
79
84
|
order: ShapeId[];
|
|
@@ -289,7 +294,16 @@ declare class Editor {
|
|
|
289
294
|
private drawStyle;
|
|
290
295
|
private readonly toolStateContext;
|
|
291
296
|
private readonly listeners;
|
|
297
|
+
private readonly historyListeners;
|
|
298
|
+
private undoStack;
|
|
299
|
+
private redoStack;
|
|
300
|
+
private lastDocumentSnapshot;
|
|
301
|
+
private suppressHistoryCapture;
|
|
302
|
+
private historyBatchDepth;
|
|
303
|
+
private historyBatchStartSnapshot;
|
|
304
|
+
private historyBatchChanged;
|
|
292
305
|
constructor(opts?: EditorOptions);
|
|
306
|
+
private captureDocumentHistory;
|
|
293
307
|
registerToolDefinition(toolDefinition: ToolDefinition): void;
|
|
294
308
|
private getDefaultToolDefinitions;
|
|
295
309
|
createShapeId(): ShapeId;
|
|
@@ -331,13 +345,25 @@ declare class Editor {
|
|
|
331
345
|
selectedShapeIds?: ShapeId[];
|
|
332
346
|
}): TsdrawEditorSnapshot;
|
|
333
347
|
loadPersistenceSnapshot(snapshot: Partial<TsdrawEditorSnapshot>): ShapeId[];
|
|
348
|
+
getHistorySnapshot(): TsdrawHistorySnapshot;
|
|
349
|
+
loadHistorySnapshot(snapshot: TsdrawHistorySnapshot | null | undefined): void;
|
|
350
|
+
clearRedoHistory(): void;
|
|
351
|
+
beginHistoryEntry(): void;
|
|
352
|
+
endHistoryEntry(): void;
|
|
353
|
+
canUndo(): boolean;
|
|
354
|
+
canRedo(): boolean;
|
|
355
|
+
undo(): boolean;
|
|
356
|
+
redo(): boolean;
|
|
334
357
|
listen(listener: EditorListener): () => void;
|
|
358
|
+
listenHistory(listener: EditorListener): () => void;
|
|
335
359
|
screenToPage(screenX: number, screenY: number): {
|
|
336
360
|
x: number;
|
|
337
361
|
y: number;
|
|
338
362
|
};
|
|
339
363
|
render(ctx: CanvasRenderingContext2D): void;
|
|
340
364
|
private emitChange;
|
|
365
|
+
private emitHistoryChange;
|
|
366
|
+
private runWithoutHistoryCapture;
|
|
341
367
|
}
|
|
342
368
|
|
|
343
369
|
declare class PenIdleState extends StateNode {
|
|
@@ -524,4 +550,4 @@ declare function decodePathToPoints(segments: {
|
|
|
524
550
|
y: number;
|
|
525
551
|
}[];
|
|
526
552
|
|
|
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 };
|
|
553
|
+
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 TsdrawHistorySnapshot, 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
|
@@ -74,6 +74,11 @@ interface TsdrawEditorSnapshot {
|
|
|
74
74
|
document: TsdrawDocumentSnapshot;
|
|
75
75
|
state: TsdrawSessionStateSnapshot;
|
|
76
76
|
}
|
|
77
|
+
interface TsdrawHistorySnapshot {
|
|
78
|
+
version: 1;
|
|
79
|
+
undoStack: TsdrawDocumentSnapshot[];
|
|
80
|
+
redoStack: TsdrawDocumentSnapshot[];
|
|
81
|
+
}
|
|
77
82
|
interface DocumentStoreSnapshot {
|
|
78
83
|
page: PageState;
|
|
79
84
|
order: ShapeId[];
|
|
@@ -289,7 +294,16 @@ declare class Editor {
|
|
|
289
294
|
private drawStyle;
|
|
290
295
|
private readonly toolStateContext;
|
|
291
296
|
private readonly listeners;
|
|
297
|
+
private readonly historyListeners;
|
|
298
|
+
private undoStack;
|
|
299
|
+
private redoStack;
|
|
300
|
+
private lastDocumentSnapshot;
|
|
301
|
+
private suppressHistoryCapture;
|
|
302
|
+
private historyBatchDepth;
|
|
303
|
+
private historyBatchStartSnapshot;
|
|
304
|
+
private historyBatchChanged;
|
|
292
305
|
constructor(opts?: EditorOptions);
|
|
306
|
+
private captureDocumentHistory;
|
|
293
307
|
registerToolDefinition(toolDefinition: ToolDefinition): void;
|
|
294
308
|
private getDefaultToolDefinitions;
|
|
295
309
|
createShapeId(): ShapeId;
|
|
@@ -331,13 +345,25 @@ declare class Editor {
|
|
|
331
345
|
selectedShapeIds?: ShapeId[];
|
|
332
346
|
}): TsdrawEditorSnapshot;
|
|
333
347
|
loadPersistenceSnapshot(snapshot: Partial<TsdrawEditorSnapshot>): ShapeId[];
|
|
348
|
+
getHistorySnapshot(): TsdrawHistorySnapshot;
|
|
349
|
+
loadHistorySnapshot(snapshot: TsdrawHistorySnapshot | null | undefined): void;
|
|
350
|
+
clearRedoHistory(): void;
|
|
351
|
+
beginHistoryEntry(): void;
|
|
352
|
+
endHistoryEntry(): void;
|
|
353
|
+
canUndo(): boolean;
|
|
354
|
+
canRedo(): boolean;
|
|
355
|
+
undo(): boolean;
|
|
356
|
+
redo(): boolean;
|
|
334
357
|
listen(listener: EditorListener): () => void;
|
|
358
|
+
listenHistory(listener: EditorListener): () => void;
|
|
335
359
|
screenToPage(screenX: number, screenY: number): {
|
|
336
360
|
x: number;
|
|
337
361
|
y: number;
|
|
338
362
|
};
|
|
339
363
|
render(ctx: CanvasRenderingContext2D): void;
|
|
340
364
|
private emitChange;
|
|
365
|
+
private emitHistoryChange;
|
|
366
|
+
private runWithoutHistoryCapture;
|
|
341
367
|
}
|
|
342
368
|
|
|
343
369
|
declare class PenIdleState extends StateNode {
|
|
@@ -524,4 +550,4 @@ declare function decodePathToPoints(segments: {
|
|
|
524
550
|
y: number;
|
|
525
551
|
}[];
|
|
526
552
|
|
|
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 };
|
|
553
|
+
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 TsdrawHistorySnapshot, 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
|
@@ -253,105 +253,26 @@ function zoomViewport(viewport, factor, centerX, centerY) {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
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
|
+
};
|
|
256
271
|
function resolveThemeColor(colorStyle, theme) {
|
|
257
|
-
const
|
|
258
|
-
if (!
|
|
259
|
-
if (theme === "light") return
|
|
260
|
-
return
|
|
261
|
-
}
|
|
262
|
-
function invertAndHueRotate180(color) {
|
|
263
|
-
const rgb = parseHexColor(color);
|
|
264
|
-
if (!rgb) return color;
|
|
265
|
-
const inverted = {
|
|
266
|
-
r: 255 - rgb.r,
|
|
267
|
-
g: 255 - rgb.g,
|
|
268
|
-
b: 255 - rgb.b
|
|
269
|
-
};
|
|
270
|
-
const hsl = rgbToHsl(inverted.r, inverted.g, inverted.b);
|
|
271
|
-
const rotated = hslToRgb((hsl.h + 180) % 360, hsl.s, hsl.l);
|
|
272
|
-
return rgbToHex(rotated.r, rotated.g, rotated.b);
|
|
273
|
-
}
|
|
274
|
-
function parseHexColor(color) {
|
|
275
|
-
const normalized = color.trim().toLowerCase();
|
|
276
|
-
if (!normalized.startsWith("#")) return null;
|
|
277
|
-
if (normalized.length === 4) {
|
|
278
|
-
return {
|
|
279
|
-
r: parseInt(normalized[1] + normalized[1], 16),
|
|
280
|
-
g: parseInt(normalized[2] + normalized[2], 16),
|
|
281
|
-
b: parseInt(normalized[3] + normalized[3], 16)
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
if (normalized.length !== 7) return null;
|
|
285
|
-
return {
|
|
286
|
-
r: parseInt(normalized.slice(1, 3), 16),
|
|
287
|
-
g: parseInt(normalized.slice(3, 5), 16),
|
|
288
|
-
b: parseInt(normalized.slice(5, 7), 16)
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
function rgbToHex(r, g, b) {
|
|
292
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
293
|
-
}
|
|
294
|
-
function toHex(value) {
|
|
295
|
-
return Math.round(Math.max(0, Math.min(255, value))).toString(16).padStart(2, "0");
|
|
296
|
-
}
|
|
297
|
-
function rgbToHsl(r, g, b) {
|
|
298
|
-
const red = r / 255;
|
|
299
|
-
const green = g / 255;
|
|
300
|
-
const blue = b / 255;
|
|
301
|
-
const maxChannel = Math.max(red, green, blue);
|
|
302
|
-
const minChannel = Math.min(red, green, blue);
|
|
303
|
-
const delta = maxChannel - minChannel;
|
|
304
|
-
const lightness = (maxChannel + minChannel) / 2;
|
|
305
|
-
if (delta === 0) {
|
|
306
|
-
return { h: 0, s: 0, l: lightness };
|
|
307
|
-
}
|
|
308
|
-
const saturation = lightness > 0.5 ? delta / (2 - maxChannel - minChannel) : delta / (maxChannel + minChannel);
|
|
309
|
-
let hue = 0;
|
|
310
|
-
if (maxChannel === red) {
|
|
311
|
-
hue = ((green - blue) / delta + (green < blue ? 6 : 0)) * 60;
|
|
312
|
-
} else if (maxChannel === green) {
|
|
313
|
-
hue = ((blue - red) / delta + 2) * 60;
|
|
314
|
-
} else {
|
|
315
|
-
hue = ((red - green) / delta + 4) * 60;
|
|
316
|
-
}
|
|
317
|
-
return { h: hue, s: saturation, l: lightness };
|
|
318
|
-
}
|
|
319
|
-
function hslToRgb(h, s, l) {
|
|
320
|
-
if (s === 0) {
|
|
321
|
-
const channel = l * 255;
|
|
322
|
-
return { r: channel, g: channel, b: channel };
|
|
323
|
-
}
|
|
324
|
-
const chroma = (1 - Math.abs(2 * l - 1)) * s;
|
|
325
|
-
const hueSegment = h / 60;
|
|
326
|
-
const x = chroma * (1 - Math.abs(hueSegment % 2 - 1));
|
|
327
|
-
let red = 0;
|
|
328
|
-
let green = 0;
|
|
329
|
-
let blue = 0;
|
|
330
|
-
if (hueSegment >= 0 && hueSegment < 1) {
|
|
331
|
-
red = chroma;
|
|
332
|
-
green = x;
|
|
333
|
-
} else if (hueSegment < 2) {
|
|
334
|
-
red = x;
|
|
335
|
-
green = chroma;
|
|
336
|
-
} else if (hueSegment < 3) {
|
|
337
|
-
green = chroma;
|
|
338
|
-
blue = x;
|
|
339
|
-
} else if (hueSegment < 4) {
|
|
340
|
-
green = x;
|
|
341
|
-
blue = chroma;
|
|
342
|
-
} else if (hueSegment < 5) {
|
|
343
|
-
red = x;
|
|
344
|
-
blue = chroma;
|
|
345
|
-
} else {
|
|
346
|
-
red = chroma;
|
|
347
|
-
blue = x;
|
|
348
|
-
}
|
|
349
|
-
const match = l - chroma / 2;
|
|
350
|
-
return {
|
|
351
|
-
r: (red + match) * 255,
|
|
352
|
-
g: (green + match) * 255,
|
|
353
|
-
b: (blue + match) * 255
|
|
354
|
-
};
|
|
272
|
+
const lightThemeColor = DEFAULT_COLORS[colorStyle];
|
|
273
|
+
if (!lightThemeColor) return colorStyle;
|
|
274
|
+
if (theme === "light") return lightThemeColor;
|
|
275
|
+
return DARK_COLORS[colorStyle] ?? lightThemeColor;
|
|
355
276
|
}
|
|
356
277
|
var CanvasRenderer = class {
|
|
357
278
|
theme = "light";
|
|
@@ -1387,10 +1308,26 @@ function recordsToDocumentSnapshot(records) {
|
|
|
1387
1308
|
// src/editor/Editor.ts
|
|
1388
1309
|
var shapeIdCounter = 0;
|
|
1389
1310
|
var shapeIdRuntimeSeed = Math.random().toString(36).slice(2, 8);
|
|
1311
|
+
var MAX_HISTORY_ENTRIES = 100;
|
|
1390
1312
|
function createShapeId() {
|
|
1391
1313
|
shapeIdCounter += 1;
|
|
1392
1314
|
return `shape:${Date.now().toString(36)}-${shapeIdRuntimeSeed}-${shapeIdCounter.toString(36)}`;
|
|
1393
1315
|
}
|
|
1316
|
+
function cloneDocumentSnapshot(snapshot) {
|
|
1317
|
+
if (typeof structuredClone === "function") {
|
|
1318
|
+
return structuredClone(snapshot);
|
|
1319
|
+
}
|
|
1320
|
+
return JSON.parse(JSON.stringify(snapshot));
|
|
1321
|
+
}
|
|
1322
|
+
function areDocumentSnapshotsEqual(left, right) {
|
|
1323
|
+
if (left.records.length !== right.records.length) return false;
|
|
1324
|
+
for (let i = 0; i < left.records.length; i += 1) {
|
|
1325
|
+
if (JSON.stringify(left.records[i]) !== JSON.stringify(right.records[i])) {
|
|
1326
|
+
return false;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return true;
|
|
1330
|
+
}
|
|
1394
1331
|
var Editor = class {
|
|
1395
1332
|
store = new DocumentStore();
|
|
1396
1333
|
input = new InputManager();
|
|
@@ -1406,10 +1343,22 @@ var Editor = class {
|
|
|
1406
1343
|
};
|
|
1407
1344
|
toolStateContext;
|
|
1408
1345
|
listeners = /* @__PURE__ */ new Set();
|
|
1346
|
+
historyListeners = /* @__PURE__ */ new Set();
|
|
1347
|
+
undoStack = [];
|
|
1348
|
+
redoStack = [];
|
|
1349
|
+
lastDocumentSnapshot;
|
|
1350
|
+
suppressHistoryCapture = false;
|
|
1351
|
+
historyBatchDepth = 0;
|
|
1352
|
+
historyBatchStartSnapshot = null;
|
|
1353
|
+
historyBatchChanged = false;
|
|
1409
1354
|
// Creates a new editor instance with the given options (with defaults if not provided)
|
|
1410
1355
|
constructor(opts = {}) {
|
|
1411
1356
|
this.options = { dragDistanceSquared: opts.dragDistanceSquared ?? DRAG_DISTANCE_SQUARED };
|
|
1412
|
-
this.
|
|
1357
|
+
this.lastDocumentSnapshot = this.getDocumentSnapshot();
|
|
1358
|
+
this.store.listen(() => {
|
|
1359
|
+
this.captureDocumentHistory();
|
|
1360
|
+
this.emitChange();
|
|
1361
|
+
});
|
|
1413
1362
|
this.toolStateContext = {
|
|
1414
1363
|
transition: (id, info) => this.tools.transition(id, info)
|
|
1415
1364
|
};
|
|
@@ -1420,6 +1369,25 @@ var Editor = class {
|
|
|
1420
1369
|
this.registerToolDefinition(customTool);
|
|
1421
1370
|
}
|
|
1422
1371
|
this.setCurrentTool(opts.initialToolId ?? "pen");
|
|
1372
|
+
this.lastDocumentSnapshot = this.getDocumentSnapshot();
|
|
1373
|
+
}
|
|
1374
|
+
captureDocumentHistory() {
|
|
1375
|
+
const nextSnapshot = this.getDocumentSnapshot();
|
|
1376
|
+
const previousSnapshot = this.lastDocumentSnapshot;
|
|
1377
|
+
this.lastDocumentSnapshot = nextSnapshot;
|
|
1378
|
+
if (this.suppressHistoryCapture || areDocumentSnapshotsEqual(previousSnapshot, nextSnapshot)) {
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
if (this.historyBatchDepth > 0) {
|
|
1382
|
+
this.historyBatchChanged = true;
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
this.undoStack.push(cloneDocumentSnapshot(previousSnapshot));
|
|
1386
|
+
if (this.undoStack.length > MAX_HISTORY_ENTRIES) {
|
|
1387
|
+
this.undoStack.splice(0, this.undoStack.length - MAX_HISTORY_ENTRIES);
|
|
1388
|
+
}
|
|
1389
|
+
this.redoStack = [];
|
|
1390
|
+
this.emitHistoryChange();
|
|
1423
1391
|
}
|
|
1424
1392
|
registerToolDefinition(toolDefinition) {
|
|
1425
1393
|
for (const stateConstructor of toolDefinition.stateConstructors) {
|
|
@@ -1514,7 +1482,9 @@ var Editor = class {
|
|
|
1514
1482
|
loadDocumentSnapshot(snapshot) {
|
|
1515
1483
|
const documentSnapshot = recordsToDocumentSnapshot(snapshot.records);
|
|
1516
1484
|
if (!documentSnapshot) return;
|
|
1517
|
-
this.
|
|
1485
|
+
this.runWithoutHistoryCapture(() => {
|
|
1486
|
+
this.store.loadSnapshot(documentSnapshot);
|
|
1487
|
+
});
|
|
1518
1488
|
}
|
|
1519
1489
|
getSessionStateSnapshot(args) {
|
|
1520
1490
|
return {
|
|
@@ -1552,12 +1522,92 @@ var Editor = class {
|
|
|
1552
1522
|
}
|
|
1553
1523
|
return [];
|
|
1554
1524
|
}
|
|
1525
|
+
getHistorySnapshot() {
|
|
1526
|
+
return {
|
|
1527
|
+
version: 1,
|
|
1528
|
+
undoStack: this.undoStack.map(cloneDocumentSnapshot),
|
|
1529
|
+
redoStack: this.redoStack.map(cloneDocumentSnapshot)
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
loadHistorySnapshot(snapshot) {
|
|
1533
|
+
if (!snapshot || snapshot.version !== 1) return;
|
|
1534
|
+
this.undoStack = snapshot.undoStack.map(cloneDocumentSnapshot).slice(-MAX_HISTORY_ENTRIES);
|
|
1535
|
+
this.redoStack = snapshot.redoStack.map(cloneDocumentSnapshot).slice(-MAX_HISTORY_ENTRIES);
|
|
1536
|
+
this.emitHistoryChange();
|
|
1537
|
+
}
|
|
1538
|
+
clearRedoHistory() {
|
|
1539
|
+
if (this.redoStack.length === 0) return;
|
|
1540
|
+
this.redoStack = [];
|
|
1541
|
+
this.emitHistoryChange();
|
|
1542
|
+
}
|
|
1543
|
+
beginHistoryEntry() {
|
|
1544
|
+
if (this.historyBatchDepth === 0) {
|
|
1545
|
+
this.historyBatchStartSnapshot = cloneDocumentSnapshot(this.lastDocumentSnapshot);
|
|
1546
|
+
this.historyBatchChanged = false;
|
|
1547
|
+
}
|
|
1548
|
+
this.historyBatchDepth += 1;
|
|
1549
|
+
}
|
|
1550
|
+
endHistoryEntry() {
|
|
1551
|
+
if (this.historyBatchDepth === 0) return;
|
|
1552
|
+
this.historyBatchDepth -= 1;
|
|
1553
|
+
if (this.historyBatchDepth > 0) return;
|
|
1554
|
+
const startSnapshot = this.historyBatchStartSnapshot;
|
|
1555
|
+
this.historyBatchStartSnapshot = null;
|
|
1556
|
+
if (!startSnapshot) return;
|
|
1557
|
+
const endSnapshot = this.getDocumentSnapshot();
|
|
1558
|
+
this.lastDocumentSnapshot = endSnapshot;
|
|
1559
|
+
const didDocumentChange = this.historyBatchChanged || !areDocumentSnapshotsEqual(startSnapshot, endSnapshot);
|
|
1560
|
+
this.historyBatchChanged = false;
|
|
1561
|
+
if (!didDocumentChange) return;
|
|
1562
|
+
this.undoStack.push(cloneDocumentSnapshot(startSnapshot));
|
|
1563
|
+
if (this.undoStack.length > MAX_HISTORY_ENTRIES) {
|
|
1564
|
+
this.undoStack.splice(0, this.undoStack.length - MAX_HISTORY_ENTRIES);
|
|
1565
|
+
}
|
|
1566
|
+
this.redoStack = [];
|
|
1567
|
+
this.emitHistoryChange();
|
|
1568
|
+
}
|
|
1569
|
+
canUndo() {
|
|
1570
|
+
return this.undoStack.length > 0;
|
|
1571
|
+
}
|
|
1572
|
+
canRedo() {
|
|
1573
|
+
return this.redoStack.length > 0;
|
|
1574
|
+
}
|
|
1575
|
+
undo() {
|
|
1576
|
+
const previousSnapshot = this.undoStack.pop();
|
|
1577
|
+
if (!previousSnapshot) return false;
|
|
1578
|
+
const currentSnapshot = this.getDocumentSnapshot();
|
|
1579
|
+
this.redoStack.push(cloneDocumentSnapshot(currentSnapshot));
|
|
1580
|
+
if (this.redoStack.length > MAX_HISTORY_ENTRIES) {
|
|
1581
|
+
this.redoStack.splice(0, this.redoStack.length - MAX_HISTORY_ENTRIES);
|
|
1582
|
+
}
|
|
1583
|
+
this.loadDocumentSnapshot(previousSnapshot);
|
|
1584
|
+
this.emitHistoryChange();
|
|
1585
|
+
return true;
|
|
1586
|
+
}
|
|
1587
|
+
redo() {
|
|
1588
|
+
const nextSnapshot = this.redoStack.pop();
|
|
1589
|
+
if (!nextSnapshot) return false;
|
|
1590
|
+
const currentSnapshot = this.getDocumentSnapshot();
|
|
1591
|
+
this.undoStack.push(cloneDocumentSnapshot(currentSnapshot));
|
|
1592
|
+
if (this.undoStack.length > MAX_HISTORY_ENTRIES) {
|
|
1593
|
+
this.undoStack.splice(0, this.undoStack.length - MAX_HISTORY_ENTRIES);
|
|
1594
|
+
}
|
|
1595
|
+
this.loadDocumentSnapshot(nextSnapshot);
|
|
1596
|
+
this.emitHistoryChange();
|
|
1597
|
+
return true;
|
|
1598
|
+
}
|
|
1555
1599
|
listen(listener) {
|
|
1556
1600
|
this.listeners.add(listener);
|
|
1557
1601
|
return () => {
|
|
1558
1602
|
this.listeners.delete(listener);
|
|
1559
1603
|
};
|
|
1560
1604
|
}
|
|
1605
|
+
listenHistory(listener) {
|
|
1606
|
+
this.historyListeners.add(listener);
|
|
1607
|
+
return () => {
|
|
1608
|
+
this.historyListeners.delete(listener);
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1561
1611
|
// Convert screen coords to page coords
|
|
1562
1612
|
screenToPage(screenX, screenY) {
|
|
1563
1613
|
return screenToPage(this.viewport, screenX, screenY);
|
|
@@ -1574,6 +1624,21 @@ var Editor = class {
|
|
|
1574
1624
|
listener();
|
|
1575
1625
|
}
|
|
1576
1626
|
}
|
|
1627
|
+
emitHistoryChange() {
|
|
1628
|
+
for (const listener of this.historyListeners) {
|
|
1629
|
+
listener();
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
runWithoutHistoryCapture(fn) {
|
|
1633
|
+
const previousValue = this.suppressHistoryCapture;
|
|
1634
|
+
this.suppressHistoryCapture = true;
|
|
1635
|
+
try {
|
|
1636
|
+
fn();
|
|
1637
|
+
} finally {
|
|
1638
|
+
this.suppressHistoryCapture = previousValue;
|
|
1639
|
+
this.lastDocumentSnapshot = this.getDocumentSnapshot();
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1577
1642
|
};
|
|
1578
1643
|
|
|
1579
1644
|
// src/tools/select/selectHelpers.ts
|