@tsdraw/core 0.4.0 → 0.5.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 CHANGED
@@ -71,6 +71,12 @@ function decodePathToPoints(segments, ox, oy) {
71
71
  }
72
72
 
73
73
  // src/store/documentStore.ts
74
+ function cloneValue(value) {
75
+ if (typeof structuredClone === "function") {
76
+ return structuredClone(value);
77
+ }
78
+ return JSON.parse(JSON.stringify(value));
79
+ }
74
80
  var DocumentStore = class {
75
81
  state = {
76
82
  id: "page-1",
@@ -78,6 +84,7 @@ var DocumentStore = class {
78
84
  erasingShapeIds: []
79
85
  };
80
86
  order = [];
87
+ listeners = /* @__PURE__ */ new Set();
81
88
  getPage() {
82
89
  return this.state;
83
90
  }
@@ -98,15 +105,18 @@ var DocumentStore = class {
98
105
  }
99
106
  setErasingShapes(ids) {
100
107
  this.state.erasingShapeIds = ids;
108
+ this.emitChange();
101
109
  }
102
110
  createShape(shape) {
103
111
  this.state.shapes[shape.id] = shape;
104
112
  this.order.push(shape.id);
113
+ this.emitChange();
105
114
  }
106
115
  updateShape(id, partial) {
107
116
  const existing = this.state.shapes[id];
108
117
  if (!existing) return;
109
118
  this.state.shapes[id] = { ...existing, ...partial, id };
119
+ this.emitChange();
110
120
  }
111
121
  deleteShapes(ids) {
112
122
  for (const id of ids) {
@@ -114,6 +124,7 @@ var DocumentStore = class {
114
124
  this.order = this.order.filter((i) => i !== id);
115
125
  }
116
126
  this.state.erasingShapeIds = this.state.erasingShapeIds.filter((i) => !ids.includes(i));
127
+ this.emitChange();
117
128
  }
118
129
  getCurrentPageShapes() {
119
130
  return Object.values(this.state.shapes);
@@ -129,6 +140,35 @@ var DocumentStore = class {
129
140
  }
130
141
  return ids;
131
142
  }
143
+ getSnapshot() {
144
+ return {
145
+ page: cloneValue(this.state),
146
+ order: [...this.order]
147
+ };
148
+ }
149
+ // Load snapshot into the document when loading a persistence snapshot (so on page reload)
150
+ loadSnapshot(snapshot) {
151
+ const pageState = cloneValue(snapshot.page);
152
+ const normalizedOrder = [...snapshot.order].filter((shapeId) => pageState.shapes[shapeId] != null);
153
+ this.state = {
154
+ id: pageState.id,
155
+ shapes: pageState.shapes,
156
+ erasingShapeIds: pageState.erasingShapeIds.filter((shapeId) => pageState.shapes[shapeId] != null)
157
+ };
158
+ this.order = normalizedOrder;
159
+ this.emitChange();
160
+ }
161
+ listen(listener) {
162
+ this.listeners.add(listener);
163
+ return () => {
164
+ this.listeners.delete(listener);
165
+ };
166
+ }
167
+ emitChange() {
168
+ for (const listener of this.listeners) {
169
+ listener();
170
+ }
171
+ }
132
172
  };
133
173
  function getShapeBounds(shape) {
134
174
  if (shape.type !== "draw") {
@@ -1290,10 +1330,68 @@ var HandDraggingState = class extends StateNode {
1290
1330
  }
1291
1331
  };
1292
1332
 
1333
+ // src/persistence/snapshots.ts
1334
+ var PAGE_RECORD_ID = "page:current";
1335
+ function cloneValue2(value) {
1336
+ if (typeof structuredClone === "function") {
1337
+ return structuredClone(value);
1338
+ }
1339
+ return JSON.parse(JSON.stringify(value));
1340
+ }
1341
+ function asDrawShape(value) {
1342
+ return cloneValue2(value);
1343
+ }
1344
+ function documentSnapshotToRecords(snapshot) {
1345
+ const shapeIds = [...snapshot.order].filter((id) => snapshot.page.shapes[id] != null);
1346
+ const pageRecord = {
1347
+ id: PAGE_RECORD_ID,
1348
+ typeName: "page",
1349
+ pageId: snapshot.page.id,
1350
+ shapeIds,
1351
+ erasingShapeIds: [...snapshot.page.erasingShapeIds]
1352
+ };
1353
+ const shapeRecords = shapeIds.map((shapeId) => snapshot.page.shapes[shapeId]).filter((shape) => shape != null).map((shape) => ({
1354
+ id: shape.id,
1355
+ typeName: "shape",
1356
+ shape: asDrawShape(shape)
1357
+ }));
1358
+ return [pageRecord, ...shapeRecords];
1359
+ }
1360
+ function recordsToDocumentSnapshot(records) {
1361
+ const pageRecord = records.find((record) => record.typeName === "page");
1362
+ if (!pageRecord) {
1363
+ return null;
1364
+ }
1365
+ const shapeRecordMap = /* @__PURE__ */ new Map();
1366
+ for (const record of records) {
1367
+ if (record.typeName === "shape") {
1368
+ shapeRecordMap.set(record.id, record);
1369
+ }
1370
+ }
1371
+ const shapes = {};
1372
+ const order = [];
1373
+ for (const shapeId of pageRecord.shapeIds) {
1374
+ const shapeRecord = shapeRecordMap.get(shapeId);
1375
+ if (!shapeRecord) continue;
1376
+ shapes[shapeId] = asDrawShape(shapeRecord.shape);
1377
+ order.push(shapeId);
1378
+ }
1379
+ return {
1380
+ page: {
1381
+ id: pageRecord.pageId,
1382
+ shapes,
1383
+ erasingShapeIds: [...pageRecord.erasingShapeIds].filter((shapeId) => shapes[shapeId] != null)
1384
+ },
1385
+ order
1386
+ };
1387
+ }
1388
+
1293
1389
  // src/editor/Editor.ts
1294
1390
  var shapeIdCounter = 0;
1391
+ var shapeIdRuntimeSeed = Math.random().toString(36).slice(2, 8);
1295
1392
  function createShapeId() {
1296
- return `shape:${String(++shapeIdCounter).padStart(6, "0")}`;
1393
+ shapeIdCounter += 1;
1394
+ return `shape:${Date.now().toString(36)}-${shapeIdRuntimeSeed}-${shapeIdCounter.toString(36)}`;
1297
1395
  }
1298
1396
  var Editor = class {
1299
1397
  store = new DocumentStore();
@@ -1309,9 +1407,11 @@ var Editor = class {
1309
1407
  size: "m"
1310
1408
  };
1311
1409
  toolStateContext;
1410
+ listeners = /* @__PURE__ */ new Set();
1312
1411
  // Creates a new editor instance with the given options (with defaults if not provided)
1313
1412
  constructor(opts = {}) {
1314
1413
  this.options = { dragDistanceSquared: opts.dragDistanceSquared ?? DRAG_DISTANCE_SQUARED };
1414
+ this.store.listen(() => this.emitChange());
1315
1415
  this.toolStateContext = {
1316
1416
  transition: (id, info) => this.tools.transition(id, info)
1317
1417
  };
@@ -1321,7 +1421,7 @@ var Editor = class {
1321
1421
  for (const customTool of opts.toolDefinitions ?? []) {
1322
1422
  this.registerToolDefinition(customTool);
1323
1423
  }
1324
- this.tools.setCurrentTool(opts.initialToolId ?? "pen");
1424
+ this.setCurrentTool(opts.initialToolId ?? "pen");
1325
1425
  }
1326
1426
  registerToolDefinition(toolDefinition) {
1327
1427
  for (const stateConstructor of toolDefinition.stateConstructors) {
@@ -1382,6 +1482,7 @@ var Editor = class {
1382
1482
  }
1383
1483
  setCurrentTool(id) {
1384
1484
  this.tools.setCurrentTool(id);
1485
+ this.emitChange();
1385
1486
  }
1386
1487
  getCurrentToolId() {
1387
1488
  return this.tools.getCurrentToolId();
@@ -1391,10 +1492,73 @@ var Editor = class {
1391
1492
  }
1392
1493
  setCurrentDrawStyle(partial) {
1393
1494
  this.drawStyle = { ...this.drawStyle, ...partial };
1495
+ this.emitChange();
1496
+ }
1497
+ setViewport(partial) {
1498
+ this.viewport = {
1499
+ x: partial.x ?? this.viewport.x,
1500
+ y: partial.y ?? this.viewport.y,
1501
+ zoom: partial.zoom ?? this.viewport.zoom
1502
+ };
1503
+ this.emitChange();
1394
1504
  }
1395
1505
  panBy(dx, dy) {
1396
- this.viewport.x += dx;
1397
- this.viewport.y += dy;
1506
+ this.setViewport({
1507
+ x: this.viewport.x + dx,
1508
+ y: this.viewport.y + dy
1509
+ });
1510
+ }
1511
+ getDocumentSnapshot() {
1512
+ return {
1513
+ records: documentSnapshotToRecords(this.store.getSnapshot())
1514
+ };
1515
+ }
1516
+ loadDocumentSnapshot(snapshot) {
1517
+ const documentSnapshot = recordsToDocumentSnapshot(snapshot.records);
1518
+ if (!documentSnapshot) return;
1519
+ this.store.loadSnapshot(documentSnapshot);
1520
+ }
1521
+ getSessionStateSnapshot(args) {
1522
+ return {
1523
+ version: 1,
1524
+ viewport: {
1525
+ x: this.viewport.x,
1526
+ y: this.viewport.y,
1527
+ zoom: this.viewport.zoom
1528
+ },
1529
+ currentToolId: this.getCurrentToolId(),
1530
+ drawStyle: this.getCurrentDrawStyle(),
1531
+ selectedShapeIds: [...args?.selectedShapeIds ?? []]
1532
+ };
1533
+ }
1534
+ loadSessionStateSnapshot(snapshot) {
1535
+ this.setViewport(snapshot.viewport);
1536
+ this.setCurrentDrawStyle(snapshot.drawStyle);
1537
+ if (this.tools.hasTool(snapshot.currentToolId)) {
1538
+ this.setCurrentTool(snapshot.currentToolId);
1539
+ }
1540
+ return [...snapshot.selectedShapeIds];
1541
+ }
1542
+ getPersistenceSnapshot(args) {
1543
+ return {
1544
+ document: this.getDocumentSnapshot(),
1545
+ state: this.getSessionStateSnapshot(args)
1546
+ };
1547
+ }
1548
+ loadPersistenceSnapshot(snapshot) {
1549
+ if (snapshot.document) {
1550
+ this.loadDocumentSnapshot(snapshot.document);
1551
+ }
1552
+ if (snapshot.state) {
1553
+ return this.loadSessionStateSnapshot(snapshot.state);
1554
+ }
1555
+ return [];
1556
+ }
1557
+ listen(listener) {
1558
+ this.listeners.add(listener);
1559
+ return () => {
1560
+ this.listeners.delete(listener);
1561
+ };
1398
1562
  }
1399
1563
  // Convert screen coords to page coords
1400
1564
  screenToPage(screenX, screenY) {
@@ -1407,6 +1571,11 @@ var Editor = class {
1407
1571
  const visible = shapes.filter((s) => !erasingIds.has(s.id));
1408
1572
  this.renderer.render(ctx, this.viewport, visible);
1409
1573
  }
1574
+ emitChange() {
1575
+ for (const listener of this.listeners) {
1576
+ listener();
1577
+ }
1578
+ }
1410
1579
  };
1411
1580
 
1412
1581
  // src/tools/select/selectHelpers.ts
@@ -1684,6 +1853,7 @@ exports.decodeLastPoint = decodeLastPoint;
1684
1853
  exports.decodePathToPoints = decodePathToPoints;
1685
1854
  exports.decodePoints = decodePoints;
1686
1855
  exports.distance = distance;
1856
+ exports.documentSnapshotToRecords = documentSnapshotToRecords;
1687
1857
  exports.encodePoints = encodePoints;
1688
1858
  exports.getSelectionBoundsPage = getSelectionBoundsPage;
1689
1859
  exports.getShapeBounds = getShapeBounds2;
@@ -1696,6 +1866,7 @@ exports.padBounds = padBounds;
1696
1866
  exports.pageToScreen = pageToScreen;
1697
1867
  exports.panViewport = panViewport;
1698
1868
  exports.pointHitsShape = pointHitsShape;
1869
+ exports.recordsToDocumentSnapshot = recordsToDocumentSnapshot;
1699
1870
  exports.resolveThemeColor = resolveThemeColor;
1700
1871
  exports.rotatePoint = rotatePoint;
1701
1872
  exports.screenToPage = screenToPage;