From b85dc81c14e24a9d467959bbee46c3d823234eb6 Mon Sep 17 00:00:00 2001 From: vcoppe Date: Sat, 4 May 2024 14:27:12 +0200 Subject: [PATCH] reorganize --- website/package-lock.json | 8 +- website/src/lib/components/Menu.svelte | 10 +-- website/src/lib/db.ts | 106 ++++++++++++++----------- 3 files changed, 68 insertions(+), 56 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index 4338d50a..25c9aa64 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -1893,9 +1893,9 @@ } }, "node_modules/bits-ui": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.21.5.tgz", - "integrity": "sha512-EDGHWkxnlcV2fbXn2tMps3SfpS7k6bfX3BrQ4s/h79jT6yprBS8DdDficlDK0SDHmPYHBZ0hSy4OgQUDodS/6w==", + "version": "0.21.7", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.21.7.tgz", + "integrity": "sha512-1PKp90ly1R6jexIiAUj1Dk4u2pln7ok+L8Vc0rHMY7pi7YZvadFNZvkp1G5BtmL8qh2xsn4MVNgKjPAQMCxW0A==", "dependencies": { "@internationalized/date": "^3.5.1", "@melt-ui/svelte": "0.76.2", @@ -1905,7 +1905,7 @@ "url": "https://github.com/sponsors/huntabyte" }, "peerDependencies": { - "svelte": "^4.0.0" + "svelte": "^4.0.0 || ^5.0.0-next.118" } }, "node_modules/bits-ui/node_modules/nanoid": { diff --git a/website/src/lib/components/Menu.svelte b/website/src/lib/components/Menu.svelte index 0ab11557..50505d4c 100644 --- a/website/src/lib/components/Menu.svelte +++ b/website/src/lib/components/Menu.svelte @@ -19,7 +19,7 @@ import { _ } from 'svelte-i18n'; import { derived, get } from 'svelte/store'; - import { canUndo, canRedo, dbUtils, fileObservers, redo, undo } from '$lib/db'; + import { canUndo, canRedo, dbUtils, fileObservers } from '$lib/db'; let showDistanceMarkers = false; let showDirectionMarkers = false; @@ -93,12 +93,12 @@ {$_('menu.edit')} - + {$_('menu.undo')} - + {$_('menu.redo')} @@ -213,9 +213,9 @@ e.preventDefault(); } else if ((e.key === 'z' || e.key == 'Z') && (e.metaKey || e.ctrlKey)) { if (e.shiftKey) { - redo(); + dbUtils.redo(); } else { - undo(); + dbUtils.undo(); } } else if ((e.key === 'Backspace' || e.key === 'Delete') && (e.metaKey || e.ctrlKey)) { if (e.shiftKey) { diff --git a/website/src/lib/db.ts b/website/src/lib/db.ts index ee382b99..5c70f42f 100644 --- a/website/src/lib/db.ts +++ b/website/src/lib/db.ts @@ -27,7 +27,21 @@ class Database extends Dexie { const db = new Database(); -function dexieFileStore(querier: () => FreezedObject | undefined | Promise | undefined>): Readable { +// Wrap Dexie live queries in a Svelte store to avoid triggering the query for every subscriber +function dexieStore(querier: () => T | Promise, initial?: T): Readable { + let store = writable(initial); + liveQuery(querier).subscribe(value => { + if (value !== undefined) { + store.set(value); + } + }); + return { + subscribe: store.subscribe, + }; +} + +// 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 { let store = writable(undefined); liveQuery(querier).subscribe(value => { if (value !== undefined) { @@ -41,19 +55,8 @@ function dexieFileStore(querier: () => FreezedObject | undefined | Prom }; } -function dexieStore(querier: () => T | Promise, initial?: T): Readable { - let store = writable(initial); - liveQuery(querier).subscribe(value => { - if (value !== undefined) { - store.set(value); - } - }); - return { - subscribe: store.subscribe, - }; -} - -function updateFiles(files: (FreezedObject | undefined)[], add: boolean = false) { +// Add/update the files to the database +function updateDbFiles(files: (FreezedObject | undefined)[], add: boolean = false) { let filteredFiles = files.filter(file => file !== undefined) as FreezedObject[]; let fileIds = filteredFiles.map(file => file._data.id); if (add) { @@ -66,26 +69,29 @@ function updateFiles(files: (FreezedObject | undefined)[], add: boolean } } -function deleteFiles(fileIds: string[]) { +// Delete the files with the given ids from the database +function deleteDbFiles(fileIds: string[]) { return db.transaction('rw', db.fileids, db.files, async () => { await db.fileids.bulkDelete(fileIds); await db.files.bulkDelete(fileIds); }); } +// Commit the changes to the file state to the database function commitFileStateChange(newFileState: ReadonlyMap>, patch: Patch[]) { if (newFileState.size > fileState.size) { - return updateFiles(getChangedFileIds(patch).map((fileId) => newFileState.get(fileId)), true); + return updateDbFiles(getChangedFileIds(patch).map((fileId) => newFileState.get(fileId)), true); } else if (newFileState.size === fileState.size) { - return updateFiles(getChangedFileIds(patch).map((fileId) => newFileState.get(fileId))); + return updateDbFiles(getChangedFileIds(patch).map((fileId) => newFileState.get(fileId))); } else { - return deleteFiles(getChangedFileIds(patch)); + return deleteDbFiles(getChangedFileIds(patch)); } } export const fileObservers: Writable>> = writable(new Map()); const fileState: Map = new Map(); // Used to generate patches +// Observe the file ids in the database, and maintain a map of file observers for the corresponding files liveQuery(() => db.fileids.toArray()).subscribe(dbFileIds => { // Find new files to observe let newFiles = dbFileIds.filter(id => !get(fileObservers).has(id)); @@ -95,7 +101,7 @@ liveQuery(() => db.fileids.toArray()).subscribe(dbFileIds => { if (newFiles.length > 0 || deletedFiles.length > 0) { fileObservers.update($files => { newFiles.forEach(id => { - $files.set(id, dexieFileStore(() => db.files.get(id))); + $files.set(id, dexieGPXFileStore(() => db.files.get(id))); }); deletedFiles.forEach(id => { $files.delete(id); @@ -111,14 +117,16 @@ const patchCount: Readable = dexieStore(() => db.patches.count(), 0); export const canUndo: Readable = derived(patchIndex, ($patchIndex) => $patchIndex >= 0); export const canRedo: Readable = derived([patchIndex, patchCount], ([$patchIndex, $patchCount]) => $patchIndex < $patchCount - 1); -export function applyGlobal(callback: (files: Map) => void) { +// Helper function to apply a callback to the global file state +function applyGlobal(callback: (files: Map) => void) { const [newFileState, patch, inversePatch] = produceWithPatches(fileState, callback); - appendPatches(patch, inversePatch); + storePatches(patch, inversePatch); return commitFileStateChange(newFileState, patch); } +// Helper function to apply a callback to multiple files function applyToFiles(fileIds: string[], callback: (file: GPXFile) => void) { const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => { fileIds.forEach((fileId) => { @@ -129,12 +137,13 @@ function applyToFiles(fileIds: string[], callback: (file: GPXFile) => void) { }); }); - appendPatches(patch, inversePatch); + storePatches(patch, inversePatch); return commitFileStateChange(newFileState, patch); } -async function appendPatches(patch: Patch[], inversePatch: Patch[]) { +// Store the new patches in the database +async function storePatches(patch: Patch[], inversePatch: Patch[]) { if (get(patchIndex) !== undefined) { db.patches.where(':id').above(get(patchIndex)).delete(); } @@ -147,11 +156,13 @@ async function appendPatches(patch: Patch[], inversePatch: Patch[]) { }); } +// Apply a patch to the file state function applyPatch(patch: Patch[]) { let newFileState = applyPatches(fileState, patch); return commitFileStateChange(newFileState, patch); } +// Get the file ids of the files that have changed in the patch function getChangedFileIds(patch: Patch[]) { let changedFileIds = []; for (let p of patch) { @@ -163,6 +174,7 @@ function getChangedFileIds(patch: Patch[]) { return changedFileIds; } +// Generate unique file ids, different from the ones in the database function getFileIds(n: number) { let ids = []; for (let index = 0; ids.length < n; index++) { @@ -174,30 +186,7 @@ function getFileIds(n: number) { return ids; } -export function undo() { - if (get(canUndo)) { - let index = get(patchIndex); - db.patches.get(index).then(patch => { - if (patch) { - applyPatch(patch.inversePatch); - db.settings.put(index - 1, 'patchIndex'); - } - }); - } -} - -export function redo() { - if (get(canRedo)) { - let index = get(patchIndex) + 1; - db.patches.get(index).then(patch => { - if (patch) { - applyPatch(patch.patch); - db.settings.put(index, 'patchIndex'); - } - }); - } -} - +// Helper functions for file operations export const dbUtils = { add: (file: GPXFile) => { file._data.id = getFileIds(1)[0]; @@ -247,4 +236,27 @@ export const dbUtils = { draft.clear(); }); }, + // undo-redo + undo: () => { + if (get(canUndo)) { + let index = get(patchIndex); + db.patches.get(index).then(patch => { + if (patch) { + applyPatch(patch.inversePatch); + db.settings.put(index - 1, 'patchIndex'); + } + }); + } + }, + redo: () => { + if (get(canRedo)) { + let index = get(patchIndex) + 1; + db.patches.get(index).then(patch => { + if (patch) { + applyPatch(patch.patch); + db.settings.put(index, 'patchIndex'); + } + }); + } + } } \ No newline at end of file