@fieldnotes/core 0.43.0 → 0.45.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
@@ -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
- if (typeof localStorage === "undefined") return null;
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
- if (typeof localStorage === "undefined") return;
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 (typeof localStorage === "undefined") return;
432
- const layers = this.layerManager?.snapshot() ?? [];
433
- const state = exportState(this.store.snapshot(), this.camera, layers);
451
+ async save() {
452
+ if (this.saving) {
453
+ this.pendingSave = true;
454
+ return;
455
+ }
456
+ this.saving = true;
434
457
  try {
435
- localStorage.setItem(this.key, JSON.stringify(state));
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;
@@ -3204,7 +3314,7 @@ function drawHexPath(ctx, cx, cy, cellSize, orientation) {
3204
3314
  // src/elements/renderers/template-renderer.ts
3205
3315
  function renderTemplate(ctx, template, store) {
3206
3316
  const grid = store?.getElementsByType("grid")[0];
3207
- if (grid && grid.gridType === "hex") {
3317
+ if (grid && grid.gridType === "hex" && template.renderStyle !== "geometric") {
3208
3318
  renderHexTemplate(ctx, template, grid.cellSize, grid.hexOrientation);
3209
3319
  return;
3210
3320
  }
@@ -3880,7 +3990,8 @@ function createTemplate(input) {
3880
3990
  strokeWidth: input.strokeWidth ?? 2,
3881
3991
  opacity: input.opacity ?? 0.6,
3882
3992
  feetPerCell: input.feetPerCell,
3883
- radiusFeet: input.radiusFeet
3993
+ radiusFeet: input.radiusFeet,
3994
+ ...input.renderStyle !== void 0 ? { renderStyle: input.renderStyle } : {}
3884
3995
  };
3885
3996
  }
3886
3997
 
@@ -9695,6 +9806,7 @@ var TemplateTool = class {
9695
9806
  strokeWidth;
9696
9807
  opacity;
9697
9808
  feetPerCell;
9809
+ renderStyle;
9698
9810
  optionListeners = /* @__PURE__ */ new Set();
9699
9811
  constructor(options = {}) {
9700
9812
  this.templateShape = options.templateShape ?? "circle";
@@ -9703,6 +9815,7 @@ var TemplateTool = class {
9703
9815
  this.strokeWidth = options.strokeWidth ?? 2;
9704
9816
  this.opacity = options.opacity ?? 0.6;
9705
9817
  this.feetPerCell = options.feetPerCell ?? 5;
9818
+ this.renderStyle = options.renderStyle ?? "cells";
9706
9819
  }
9707
9820
  getOptions() {
9708
9821
  return {
@@ -9711,7 +9824,8 @@ var TemplateTool = class {
9711
9824
  strokeColor: this.strokeColor,
9712
9825
  strokeWidth: this.strokeWidth,
9713
9826
  opacity: this.opacity,
9714
- feetPerCell: this.feetPerCell
9827
+ feetPerCell: this.feetPerCell,
9828
+ renderStyle: this.renderStyle
9715
9829
  };
9716
9830
  }
9717
9831
  setOptions(options) {
@@ -9721,6 +9835,7 @@ var TemplateTool = class {
9721
9835
  if (options.strokeWidth !== void 0) this.strokeWidth = options.strokeWidth;
9722
9836
  if (options.opacity !== void 0) this.opacity = options.opacity;
9723
9837
  if (options.feetPerCell !== void 0) this.feetPerCell = options.feetPerCell;
9838
+ if (options.renderStyle !== void 0) this.renderStyle = options.renderStyle;
9724
9839
  this.notifyOptionsChange();
9725
9840
  }
9726
9841
  onOptionsChange(listener) {
@@ -9763,6 +9878,7 @@ var TemplateTool = class {
9763
9878
  opacity: this.opacity,
9764
9879
  feetPerCell: this.feetPerCell,
9765
9880
  radiusFeet: radiusFeet > 0 ? radiusFeet : void 0,
9881
+ renderStyle: this.renderStyle,
9766
9882
  layerId: ctx.activeLayerId ?? ""
9767
9883
  });
9768
9884
  ctx.store.add(element);
@@ -9778,7 +9894,7 @@ var TemplateTool = class {
9778
9894
  if (!this.drawing) return;
9779
9895
  const radius = this.computeRadius();
9780
9896
  if (radius <= 0) return;
9781
- if (this.gridType === "hex" && this.hexOrientation) {
9897
+ if (this.gridType === "hex" && this.hexOrientation && this.renderStyle !== "geometric") {
9782
9898
  this.renderHexOverlay(ctx, radius);
9783
9899
  return;
9784
9900
  }
@@ -10051,7 +10167,7 @@ var LaserTool = class {
10051
10167
  };
10052
10168
 
10053
10169
  // src/index.ts
10054
- var VERSION = "0.43.0";
10170
+ var VERSION = "0.45.0";
10055
10171
  // Annotate the CommonJS export names for ESM import in node:
10056
10172
  0 && (module.exports = {
10057
10173
  ArrowTool,
@@ -10063,9 +10179,12 @@ var VERSION = "0.43.0";
10063
10179
  HandTool,
10064
10180
  HistoryStack,
10065
10181
  ImageTool,
10182
+ IndexedDBAdapter,
10066
10183
  LaserTool,
10067
10184
  LayerManager,
10185
+ LocalStorageAdapter,
10068
10186
  MeasureTool,
10187
+ MemoryAdapter,
10069
10188
  NoteTool,
10070
10189
  PencilTool,
10071
10190
  SelectTool,