@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 +175 -4
- 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 +174 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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.
|
|
1397
|
-
|
|
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;
|