@fieldnotes/core 0.44.0 → 0.46.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 +153 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -10
- package/dist/index.d.ts +58 -10
- package/dist/index.js +150 -33
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -29,9 +29,12 @@ __export(index_exports, {
|
|
|
29
29
|
HandTool: () => HandTool,
|
|
30
30
|
HistoryStack: () => HistoryStack,
|
|
31
31
|
ImageTool: () => ImageTool,
|
|
32
|
+
IndexedDBAdapter: () => IndexedDBAdapter,
|
|
32
33
|
LaserTool: () => LaserTool,
|
|
33
34
|
LayerManager: () => LayerManager,
|
|
35
|
+
LocalStorageAdapter: () => LocalStorageAdapter,
|
|
34
36
|
MeasureTool: () => MeasureTool,
|
|
37
|
+
MemoryAdapter: () => MemoryAdapter,
|
|
35
38
|
NoteTool: () => NoteTool,
|
|
36
39
|
PencilTool: () => PencilTool,
|
|
37
40
|
SelectTool: () => SelectTool,
|
|
@@ -368,6 +371,22 @@ function migrateElement(obj) {
|
|
|
368
371
|
}
|
|
369
372
|
}
|
|
370
373
|
|
|
374
|
+
// src/core/storage/local-storage-adapter.ts
|
|
375
|
+
var LocalStorageAdapter = class {
|
|
376
|
+
async load(key) {
|
|
377
|
+
if (typeof localStorage === "undefined") return null;
|
|
378
|
+
return localStorage.getItem(key);
|
|
379
|
+
}
|
|
380
|
+
async save(key, value) {
|
|
381
|
+
if (typeof localStorage === "undefined") return;
|
|
382
|
+
localStorage.setItem(key, value);
|
|
383
|
+
}
|
|
384
|
+
async clear(key) {
|
|
385
|
+
if (typeof localStorage === "undefined") return;
|
|
386
|
+
localStorage.removeItem(key);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
371
390
|
// src/core/auto-save.ts
|
|
372
391
|
var DEFAULT_KEY = "fieldnotes-autosave";
|
|
373
392
|
var DEFAULT_DEBOUNCE_MS = 1e3;
|
|
@@ -378,14 +397,18 @@ var AutoSave = class {
|
|
|
378
397
|
this.key = options.key ?? DEFAULT_KEY;
|
|
379
398
|
this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
380
399
|
this.layerManager = options.layerManager;
|
|
400
|
+
this.adapter = options.adapter ?? new LocalStorageAdapter();
|
|
381
401
|
this.onError = options.onError;
|
|
382
402
|
}
|
|
383
403
|
key;
|
|
384
404
|
debounceMs;
|
|
385
405
|
layerManager;
|
|
406
|
+
adapter;
|
|
386
407
|
timerId = null;
|
|
387
408
|
unsubscribers = [];
|
|
388
409
|
onError;
|
|
410
|
+
saving = false;
|
|
411
|
+
pendingSave = false;
|
|
389
412
|
start() {
|
|
390
413
|
const schedule = () => this.scheduleSave();
|
|
391
414
|
this.unsubscribers = [
|
|
@@ -403,9 +426,8 @@ var AutoSave = class {
|
|
|
403
426
|
this.unsubscribers.forEach((fn) => fn());
|
|
404
427
|
this.unsubscribers = [];
|
|
405
428
|
}
|
|
406
|
-
load() {
|
|
407
|
-
|
|
408
|
-
const json = localStorage.getItem(this.key);
|
|
429
|
+
async load() {
|
|
430
|
+
const json = await this.adapter.load(this.key);
|
|
409
431
|
if (!json) return null;
|
|
410
432
|
try {
|
|
411
433
|
return parseState(json);
|
|
@@ -413,13 +435,12 @@ var AutoSave = class {
|
|
|
413
435
|
return null;
|
|
414
436
|
}
|
|
415
437
|
}
|
|
416
|
-
clear() {
|
|
417
|
-
|
|
418
|
-
localStorage.removeItem(this.key);
|
|
438
|
+
async clear() {
|
|
439
|
+
await this.adapter.clear(this.key);
|
|
419
440
|
}
|
|
420
441
|
scheduleSave() {
|
|
421
442
|
this.cancelPending();
|
|
422
|
-
this.timerId = setTimeout(() => this.save(), this.debounceMs);
|
|
443
|
+
this.timerId = setTimeout(() => void this.save(), this.debounceMs);
|
|
423
444
|
}
|
|
424
445
|
cancelPending() {
|
|
425
446
|
if (this.timerId !== null) {
|
|
@@ -427,19 +448,108 @@ var AutoSave = class {
|
|
|
427
448
|
this.timerId = null;
|
|
428
449
|
}
|
|
429
450
|
}
|
|
430
|
-
save() {
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
451
|
+
async save() {
|
|
452
|
+
if (this.saving) {
|
|
453
|
+
this.pendingSave = true;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
this.saving = true;
|
|
434
457
|
try {
|
|
435
|
-
|
|
458
|
+
const layers = this.layerManager?.snapshot() ?? [];
|
|
459
|
+
const state = exportState(this.store.snapshot(), this.camera, layers);
|
|
460
|
+
await this.adapter.save(this.key, JSON.stringify(state));
|
|
436
461
|
} catch (e) {
|
|
437
|
-
console.warn("Auto-save failed: storage quota exceeded. State too large for localStorage.");
|
|
438
462
|
this.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
463
|
+
} finally {
|
|
464
|
+
this.saving = false;
|
|
465
|
+
if (this.pendingSave) {
|
|
466
|
+
this.pendingSave = false;
|
|
467
|
+
void this.save();
|
|
468
|
+
}
|
|
439
469
|
}
|
|
440
470
|
}
|
|
441
471
|
};
|
|
442
472
|
|
|
473
|
+
// src/core/storage/memory-adapter.ts
|
|
474
|
+
var MemoryAdapter = class {
|
|
475
|
+
store = /* @__PURE__ */ new Map();
|
|
476
|
+
async load(key) {
|
|
477
|
+
return this.store.get(key) ?? null;
|
|
478
|
+
}
|
|
479
|
+
async save(key, value) {
|
|
480
|
+
this.store.set(key, value);
|
|
481
|
+
}
|
|
482
|
+
async clear(key) {
|
|
483
|
+
this.store.delete(key);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/core/storage/indexeddb-adapter.ts
|
|
488
|
+
var DEFAULT_DB = "fieldnotes";
|
|
489
|
+
var DEFAULT_STORE = "state";
|
|
490
|
+
var IndexedDBAdapter = class {
|
|
491
|
+
dbName;
|
|
492
|
+
storeName;
|
|
493
|
+
idb;
|
|
494
|
+
dbPromise = null;
|
|
495
|
+
constructor(options = {}) {
|
|
496
|
+
this.dbName = options.dbName ?? DEFAULT_DB;
|
|
497
|
+
this.storeName = options.storeName ?? DEFAULT_STORE;
|
|
498
|
+
this.idb = options.indexedDB ?? (typeof indexedDB !== "undefined" ? indexedDB : null);
|
|
499
|
+
}
|
|
500
|
+
open() {
|
|
501
|
+
const idb = this.idb;
|
|
502
|
+
if (!idb) return Promise.reject(new Error("IndexedDB unavailable"));
|
|
503
|
+
if (!this.dbPromise) {
|
|
504
|
+
const storeName = this.storeName;
|
|
505
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
506
|
+
const req = idb.open(this.dbName, 1);
|
|
507
|
+
req.onupgradeneeded = () => {
|
|
508
|
+
const db = req.result;
|
|
509
|
+
if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName);
|
|
510
|
+
};
|
|
511
|
+
req.onsuccess = () => resolve(req.result);
|
|
512
|
+
req.onerror = () => reject(req.error ?? new Error("IndexedDB open failed"));
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
return this.dbPromise;
|
|
516
|
+
}
|
|
517
|
+
async load(key) {
|
|
518
|
+
if (!this.idb) return null;
|
|
519
|
+
const db = await this.open();
|
|
520
|
+
return new Promise((resolve, reject) => {
|
|
521
|
+
const req = db.transaction(this.storeName, "readonly").objectStore(this.storeName).get(key);
|
|
522
|
+
req.onsuccess = () => {
|
|
523
|
+
const v = req.result;
|
|
524
|
+
resolve(typeof v === "string" ? v : null);
|
|
525
|
+
};
|
|
526
|
+
req.onerror = () => reject(req.error ?? new Error("IndexedDB read failed"));
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
async save(key, value) {
|
|
530
|
+
if (!this.idb) return;
|
|
531
|
+
const db = await this.open();
|
|
532
|
+
return new Promise((resolve, reject) => {
|
|
533
|
+
const tx = db.transaction(this.storeName, "readwrite");
|
|
534
|
+
tx.objectStore(this.storeName).put(value, key);
|
|
535
|
+
tx.oncomplete = () => resolve();
|
|
536
|
+
tx.onerror = () => reject(tx.error ?? new Error("IndexedDB write failed"));
|
|
537
|
+
tx.onabort = () => reject(tx.error ?? new Error("IndexedDB write aborted"));
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
async clear(key) {
|
|
541
|
+
if (!this.idb) return;
|
|
542
|
+
const db = await this.open();
|
|
543
|
+
return new Promise((resolve, reject) => {
|
|
544
|
+
const tx = db.transaction(this.storeName, "readwrite");
|
|
545
|
+
tx.objectStore(this.storeName).delete(key);
|
|
546
|
+
tx.oncomplete = () => resolve();
|
|
547
|
+
tx.onerror = () => reject(tx.error ?? new Error("IndexedDB delete failed"));
|
|
548
|
+
tx.onabort = () => reject(tx.error ?? new Error("IndexedDB delete aborted"));
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
443
553
|
// src/canvas/camera.ts
|
|
444
554
|
var DEFAULT_MIN_ZOOM = 0.1;
|
|
445
555
|
var DEFAULT_MAX_ZOOM = 10;
|
|
@@ -2122,6 +2232,7 @@ var Background = class {
|
|
|
2122
2232
|
};
|
|
2123
2233
|
|
|
2124
2234
|
// src/core/event-bus.ts
|
|
2235
|
+
var EMPTY_META = Object.freeze({});
|
|
2125
2236
|
var EventBus = class {
|
|
2126
2237
|
listeners = /* @__PURE__ */ new Map();
|
|
2127
2238
|
on(event, listener) {
|
|
@@ -2137,10 +2248,10 @@ var EventBus = class {
|
|
|
2137
2248
|
off(event, listener) {
|
|
2138
2249
|
this.listeners.get(event)?.delete(listener);
|
|
2139
2250
|
}
|
|
2140
|
-
emit(event, data) {
|
|
2251
|
+
emit(event, data, meta = EMPTY_META) {
|
|
2141
2252
|
this.listeners.get(event)?.forEach((listener) => {
|
|
2142
2253
|
try {
|
|
2143
|
-
listener(data);
|
|
2254
|
+
listener(data, meta);
|
|
2144
2255
|
} catch (err) {
|
|
2145
2256
|
console.error(`[fieldnotes] listener error for "${String(event)}"`, err);
|
|
2146
2257
|
}
|
|
@@ -2460,15 +2571,15 @@ var ElementStore = class {
|
|
|
2460
2571
|
const angle = element.rotation ?? 0;
|
|
2461
2572
|
return angle === 0 ? bounds : rotatedAABB(bounds, angle);
|
|
2462
2573
|
}
|
|
2463
|
-
add(element) {
|
|
2574
|
+
add(element, meta) {
|
|
2464
2575
|
this.sortedCache = null;
|
|
2465
2576
|
this._versions.set(element.id, 0);
|
|
2466
2577
|
this.elements.set(element.id, element);
|
|
2467
2578
|
const bounds = this.indexBounds(element);
|
|
2468
2579
|
if (bounds) this.spatialIndex.insert(element.id, bounds);
|
|
2469
|
-
this.bus.emit("add", element);
|
|
2580
|
+
this.bus.emit("add", element, meta);
|
|
2470
2581
|
}
|
|
2471
|
-
update(id, partial) {
|
|
2582
|
+
update(id, partial, meta) {
|
|
2472
2583
|
const existing = this.elements.get(id);
|
|
2473
2584
|
if (!existing) return;
|
|
2474
2585
|
this.sortedCache = null;
|
|
@@ -2495,28 +2606,28 @@ var ElementStore = class {
|
|
|
2495
2606
|
if (newBounds) {
|
|
2496
2607
|
this.spatialIndex.update(id, newBounds);
|
|
2497
2608
|
}
|
|
2498
|
-
this.bus.emit("update", { previous: existing, current: updated });
|
|
2609
|
+
this.bus.emit("update", { previous: existing, current: updated }, meta);
|
|
2499
2610
|
}
|
|
2500
|
-
remove(id) {
|
|
2611
|
+
remove(id, meta) {
|
|
2501
2612
|
const element = this.elements.get(id);
|
|
2502
2613
|
if (!element) return;
|
|
2503
2614
|
this.sortedCache = null;
|
|
2504
2615
|
this._versions.delete(id);
|
|
2505
2616
|
this.elements.delete(id);
|
|
2506
2617
|
this.spatialIndex.remove(id);
|
|
2507
|
-
this.bus.emit("remove", element);
|
|
2618
|
+
this.bus.emit("remove", element, meta);
|
|
2508
2619
|
}
|
|
2509
|
-
clear() {
|
|
2620
|
+
clear(meta) {
|
|
2510
2621
|
this.sortedCache = null;
|
|
2511
2622
|
this._versions.clear();
|
|
2512
2623
|
this.elements.clear();
|
|
2513
2624
|
this.spatialIndex.clear();
|
|
2514
|
-
this.bus.emit("clear", null);
|
|
2625
|
+
this.bus.emit("clear", null, meta);
|
|
2515
2626
|
}
|
|
2516
2627
|
snapshot() {
|
|
2517
2628
|
return this.getAll().map((el) => ({ ...el }));
|
|
2518
2629
|
}
|
|
2519
|
-
loadSnapshot(elements) {
|
|
2630
|
+
loadSnapshot(elements, meta) {
|
|
2520
2631
|
this.sortedCache = null;
|
|
2521
2632
|
this._versions.clear();
|
|
2522
2633
|
this.elements.clear();
|
|
@@ -2533,9 +2644,9 @@ var ElementStore = class {
|
|
|
2533
2644
|
el.cachedControlPoint = getArrowControlPoint(el.from, el.to, el.bend);
|
|
2534
2645
|
}
|
|
2535
2646
|
}
|
|
2536
|
-
this.bus.emit("clear", null);
|
|
2647
|
+
this.bus.emit("clear", null, meta);
|
|
2537
2648
|
for (const el of elements) {
|
|
2538
|
-
this.bus.emit("add", el);
|
|
2649
|
+
this.bus.emit("add", el, meta);
|
|
2539
2650
|
}
|
|
2540
2651
|
}
|
|
2541
2652
|
bringToFront(id) {
|
|
@@ -4810,15 +4921,18 @@ var UpdateLayerCommand = class {
|
|
|
4810
4921
|
};
|
|
4811
4922
|
|
|
4812
4923
|
// src/history/history-recorder.ts
|
|
4924
|
+
function isExternalChange(meta) {
|
|
4925
|
+
return meta.origin !== void 0 && meta.origin !== "local";
|
|
4926
|
+
}
|
|
4813
4927
|
var HistoryRecorder = class {
|
|
4814
4928
|
constructor(store, stack, layerManager) {
|
|
4815
4929
|
this.store = store;
|
|
4816
4930
|
this.stack = stack;
|
|
4817
4931
|
this.layerManager = layerManager;
|
|
4818
4932
|
this.unsubscribers = [
|
|
4819
|
-
store.on("add", (el) => this.onAdd(el)),
|
|
4820
|
-
store.on("remove", (el) => this.onRemove(el)),
|
|
4821
|
-
store.on("update", ({ previous, current }) => this.onUpdate(previous, current))
|
|
4933
|
+
store.on("add", (el, meta) => this.onAdd(el, meta)),
|
|
4934
|
+
store.on("remove", (el, meta) => this.onRemove(el, meta)),
|
|
4935
|
+
store.on("update", ({ previous, current }, meta) => this.onUpdate(previous, current, meta))
|
|
4822
4936
|
];
|
|
4823
4937
|
if (layerManager) {
|
|
4824
4938
|
this.unsubscribers.push(
|
|
@@ -4874,18 +4988,21 @@ var HistoryRecorder = class {
|
|
|
4874
4988
|
this.stack.push(command);
|
|
4875
4989
|
}
|
|
4876
4990
|
}
|
|
4877
|
-
onAdd(element) {
|
|
4991
|
+
onAdd(element, meta) {
|
|
4992
|
+
if (isExternalChange(meta)) return;
|
|
4878
4993
|
if (!this.recording) return;
|
|
4879
4994
|
this.record(new AddElementCommand(element));
|
|
4880
4995
|
}
|
|
4881
|
-
onRemove(element) {
|
|
4996
|
+
onRemove(element, meta) {
|
|
4997
|
+
if (isExternalChange(meta)) return;
|
|
4882
4998
|
if (!this.recording) return;
|
|
4883
4999
|
if (this.transaction && this.updateSnapshots.has(element.id)) {
|
|
4884
5000
|
this.updateSnapshots.delete(element.id);
|
|
4885
5001
|
}
|
|
4886
5002
|
this.record(new RemoveElementCommand(element));
|
|
4887
5003
|
}
|
|
4888
|
-
onUpdate(previous, current) {
|
|
5004
|
+
onUpdate(previous, current, meta) {
|
|
5005
|
+
if (isExternalChange(meta)) return;
|
|
4889
5006
|
if (!this.recording) return;
|
|
4890
5007
|
if (this.transaction) {
|
|
4891
5008
|
if (!this.updateSnapshots.has(current.id)) {
|
|
@@ -10057,7 +10174,7 @@ var LaserTool = class {
|
|
|
10057
10174
|
};
|
|
10058
10175
|
|
|
10059
10176
|
// src/index.ts
|
|
10060
|
-
var VERSION = "0.
|
|
10177
|
+
var VERSION = "0.46.0";
|
|
10061
10178
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10062
10179
|
0 && (module.exports = {
|
|
10063
10180
|
ArrowTool,
|
|
@@ -10069,9 +10186,12 @@ var VERSION = "0.44.0";
|
|
|
10069
10186
|
HandTool,
|
|
10070
10187
|
HistoryStack,
|
|
10071
10188
|
ImageTool,
|
|
10189
|
+
IndexedDBAdapter,
|
|
10072
10190
|
LaserTool,
|
|
10073
10191
|
LayerManager,
|
|
10192
|
+
LocalStorageAdapter,
|
|
10074
10193
|
MeasureTool,
|
|
10194
|
+
MemoryAdapter,
|
|
10075
10195
|
NoteTool,
|
|
10076
10196
|
PencilTool,
|
|
10077
10197
|
SelectTool,
|