diff --git a/gpx/package-lock.json b/gpx/package-lock.json index 37f58da1..2c99d7d8 100644 --- a/gpx/package-lock.json +++ b/gpx/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "fast-xml-parser": "^4.3.6", + "immer": "^10.1.1", "ts-node": "^10.9.2" }, "devDependencies": { @@ -1958,6 +1959,15 @@ "node": ">=10.17.0" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", diff --git a/gpx/package.json b/gpx/package.json index 756fba48..11de6b91 100644 --- a/gpx/package.json +++ b/gpx/package.json @@ -12,6 +12,7 @@ "private": true, "dependencies": { "fast-xml-parser": "^4.3.6", + "immer": "^10.1.1", "ts-node": "^10.9.2" }, "scripts": { @@ -26,4 +27,4 @@ "ts-jest": "^29.1.2", "typescript": "^5.4.5" } -} \ No newline at end of file +} diff --git a/gpx/src/gpx.ts b/gpx/src/gpx.ts index 6641a78a..66434f94 100644 --- a/gpx/src/gpx.ts +++ b/gpx/src/gpx.ts @@ -1,4 +1,5 @@ import { Coordinates, GPXFileAttributes, GPXFileType, Link, Metadata, TrackExtensions, TrackPointExtensions, TrackPointType, TrackSegmentType, TrackType, WaypointType } from "./types"; +import { immerable } from "immer"; function cloneJSON(obj: T): T { if (obj === null || typeof obj !== 'object') { @@ -120,6 +121,8 @@ export class GPXFiles extends GPXTreeNode { // A class that represents a GPX file export class GPXFile extends GPXTreeNode{ + [immerable] = true; + attributes: GPXFileAttributes; metadata: Metadata; wpt: Waypoint[]; @@ -176,6 +179,8 @@ export class GPXFile extends GPXTreeNode{ // A class that represents a Track in a GPX file export class Track extends GPXTreeNode { + [immerable] = true; + name?: string; cmt?: string; desc?: string; @@ -256,6 +261,8 @@ export class Track extends GPXTreeNode { // A class that represents a TrackSegment in a GPX file export class TrackSegment extends GPXTreeLeaf { + [immerable] = true; + trkpt: TrackPoint[]; constructor(segment?: TrackSegmentType | TrackSegment) { @@ -432,6 +439,8 @@ export class TrackSegment extends GPXTreeLeaf { }; export class TrackPoint { + [immerable] = true; + attributes: Coordinates; ele?: number; time?: Date; diff --git a/website/package-lock.json b/website/package-lock.json index 25c9aa64..706d49f5 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -14,11 +14,11 @@ "clsx": "^2.1.0", "dexie": "^4.0.4", "gpx": "file:../gpx", + "immer": "^10.1.1", "lucide-svelte": "^0.365.0", "mapbox-gl": "^3.2.0", "mode-watcher": "^0.3.0", "sortablejs": "^1.15.2", - "structurajs": "^0.12.0", "svelte-i18n": "^4.0.0", "svelte-sonner": "^0.3.22", "tailwind-merge": "^2.2.2", @@ -3363,6 +3363,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5132,11 +5141,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/structurajs": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/structurajs/-/structurajs-0.12.0.tgz", - "integrity": "sha512-vadDl3zCv6OM2dXfzelUH3RLhNu9pmIEZU7zS0jIgMR3BDKIk44fDd/X9KK9LE27MMU+/aw3ibBTQQgBH0zfiw==" - }, "node_modules/subtag": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/subtag/-/subtag-0.5.0.tgz", diff --git a/website/package.json b/website/package.json index 4e9e458a..996a1870 100644 --- a/website/package.json +++ b/website/package.json @@ -47,11 +47,11 @@ "clsx": "^2.1.0", "dexie": "^4.0.4", "gpx": "file:../gpx", + "immer": "^10.1.1", "lucide-svelte": "^0.365.0", "mapbox-gl": "^3.2.0", "mode-watcher": "^0.3.0", "sortablejs": "^1.15.2", - "structurajs": "^0.12.0", "svelte-i18n": "^4.0.0", "svelte-sonner": "^0.3.22", "tailwind-merge": "^2.2.2", diff --git a/website/src/lib/db.ts b/website/src/lib/db.ts index 85972e74..8d31af2a 100644 --- a/website/src/lib/db.ts +++ b/website/src/lib/db.ts @@ -1,15 +1,18 @@ import Dexie, { liveQuery } from 'dexie'; import { GPXFile } from 'gpx'; -import { type FreezedObject, type Patch, produceWithPatches, applyPatches } from 'structurajs'; +import { enableMapSet, enablePatches, produceWithPatches, applyPatches, type Patch } from 'immer'; import { writable, get, derived, type Readable, type Writable } from 'svelte/store'; import { fileOrder, selectedFiles } from './stores'; import { mode } from 'mode-watcher'; import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays } from './assets/layers'; +enableMapSet(); +enablePatches(); + class Database extends Dexie { fileids!: Dexie.Table; - files!: Dexie.Table, string>; + files!: Dexie.Table; patches!: Dexie.Table<{ patch: Patch[], inversePatch: Patch[], index: number }, number>; settings!: Dexie.Table; @@ -103,7 +106,7 @@ function dexieStore(querier: () => T | Promise, initial?: T): Readable } // Wrap Dexie live queries in a Svelte store to avoid triggering the query for every subscriber, also takes care of the conversion to a GPXFile object -function dexieGPXFileStore(querier: () => FreezedObject | undefined | Promise | undefined>): Readable { +function dexieGPXFileStore(querier: () => GPXFile | undefined | Promise): Readable { let store = writable(undefined); liveQuery(querier).subscribe(value => { if (value !== undefined) { @@ -118,8 +121,8 @@ function dexieGPXFileStore(querier: () => FreezedObject | undefined | P } // Add/update the files to the database -function updateDbFiles(files: (FreezedObject | undefined)[], add: boolean = false) { - let filteredFiles = files.filter(file => file !== undefined) as FreezedObject[]; +function updateDbFiles(files: (GPXFile | undefined)[], add: boolean = false) { + let filteredFiles = files.filter(file => file !== undefined) as GPXFile[]; let fileIds = filteredFiles.map(file => file._data.id); if (add) { return db.transaction('rw', db.fileids, db.files, async () => { @@ -140,7 +143,7 @@ function deleteDbFiles(fileIds: string[]) { } // Commit the changes to the file state to the database -function commitFileStateChange(newFileState: ReadonlyMap>, patch: Patch[]) { +function commitFileStateChange(newFileState: ReadonlyMap, patch: Patch[]) { if (newFileState.size > fileState.size) { return updateDbFiles(getChangedFileIds(patch).map((fileId) => newFileState.get(fileId)), true); } else if (newFileState.size === fileState.size) { @@ -238,15 +241,12 @@ function applyPatch(patch: Patch[]) { } // Get the file ids of the files that have changed in the patch -function getChangedFileIds(patch: Patch[]) { - let changedFileIds = []; +function getChangedFileIds(patch: Patch[]): string[] { + let changedFileIds = new Set(); for (let p of patch) { - let fileId = p.p?.toString(); - if (fileId) { - changedFileIds.push(fileId); - } + changedFileIds.add(p.path[0]); } - return changedFileIds; + return Array.from(changedFileIds); } // Generate unique file ids, different from the ones in the database