diff --git a/website/src/lib/components/GPXStatistics.svelte b/website/src/lib/components/GPXStatistics.svelte index 2f0cfed6..2630c3ab 100644 --- a/website/src/lib/components/GPXStatistics.svelte +++ b/website/src/lib/components/GPXStatistics.svelte @@ -7,23 +7,26 @@ import { i18n } from '$lib/i18n.svelte'; import type { GPXStatistics } from 'gpx'; - import type { Writable } from 'svelte/store'; - import { settings } from '$lib/db'; - - export let gpxStatistics: Writable; - export let slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>; - export let orientation: 'horizontal' | 'vertical'; - export let panelSize: number; + import type { Readable } from 'svelte/store'; + import { settings } from '$lib/logic/settings'; const { velocityUnits } = settings; - let statistics: GPXStatistics; + let { + gpxStatistics, + slicedGPXStatistics, + orientation, + panelSize, + }: { + gpxStatistics: Readable; + slicedGPXStatistics: Readable<[GPXStatistics, number, number] | undefined>; + orientation: 'horizontal' | 'vertical'; + panelSize: number; + } = $props(); - $: if ($slicedGPXStatistics !== undefined) { - statistics = $slicedGPXStatistics[0]; - } else { - statistics = $gpxStatistics; - } + let statistics = $derived( + $slicedGPXStatistics !== undefined ? $slicedGPXStatistics[0] : $gpxStatistics + ); ; private _subscription: { unsubscribe: () => void } | undefined; @@ -119,6 +119,10 @@ export class GPXFileStateCollection { return get(this._files).size; } + getFileState(fileId: string): GPXFileState | undefined { + return get(this._files).get(fileId); + } + getFile(fileId: string): GPXFile | undefined { let fileState = get(this._files).get(fileId); return fileState?.file; diff --git a/website/src/lib/logic/selection.ts b/website/src/lib/logic/selection.ts index 5bf8fee6..a7878ffa 100644 --- a/website/src/lib/logic/selection.ts +++ b/website/src/lib/logic/selection.ts @@ -12,7 +12,7 @@ import { import { fileStateCollection } from '$lib/logic/file-state'; import { settings } from '$lib/logic/settings'; import type { GPXFile } from 'gpx'; -import { get, writable, type Writable } from 'svelte/store'; +import { get, writable, type Readable, type Writable } from 'svelte/store'; import { SelectionTreeType } from '$lib/logic/selection-tree'; export class Selection { @@ -203,11 +203,11 @@ export class Selection { this._cut.set(false); } - get copied(): ListItem[] | undefined { + get copied(): Readable { return this._copied; } - get cut(): boolean { + get cut(): Readable { return this._cut; } } @@ -219,7 +219,7 @@ export function applyToOrderedItemsFromFile( callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void, reverse: boolean = true ) { - settings.fileOrder.value.forEach((fileId) => { + get(settings.fileOrder).forEach((fileId) => { let level: ListLevel | undefined = undefined; let items: ListItem[] = []; selectedItems.forEach((item) => { diff --git a/website/src/lib/logic/statistics-tree.ts b/website/src/lib/logic/statistics-tree.ts new file mode 100644 index 00000000..5a114d94 --- /dev/null +++ b/website/src/lib/logic/statistics-tree.ts @@ -0,0 +1,46 @@ +import { ListItem, ListLevel } from '$lib/components/file-list/file-list'; +import { GPXFile, GPXStatistics, type Track } from 'gpx'; + +export class GPXStatisticsTree { + level: ListLevel; + statistics: { + [key: string]: GPXStatisticsTree | GPXStatistics; + } = {}; + + constructor(element: GPXFile | Track) { + if (element instanceof GPXFile) { + this.level = ListLevel.FILE; + element.children.forEach((child, index) => { + this.statistics[index] = new GPXStatisticsTree(child); + }); + } else { + this.level = ListLevel.TRACK; + element.children.forEach((child, index) => { + this.statistics[index] = child.getStatistics(); + }); + } + } + + getStatisticsFor(item: ListItem): GPXStatistics { + let statistics = new GPXStatistics(); + let id = item.getIdAtLevel(this.level); + if (id === undefined || id === 'waypoints') { + Object.keys(this.statistics).forEach((key) => { + if (this.statistics[key] instanceof GPXStatistics) { + statistics.mergeWith(this.statistics[key]); + } else { + statistics.mergeWith(this.statistics[key].getStatisticsFor(item)); + } + }); + } else { + let child = this.statistics[id]; + if (child instanceof GPXStatistics) { + statistics.mergeWith(child); + } else if (child !== undefined) { + statistics.mergeWith(child.getStatisticsFor(item)); + } + } + return statistics; + } +} +export type GPXFileWithStatistics = { file: GPXFile; statistics: GPXStatisticsTree }; diff --git a/website/src/lib/logic/statistics.svelte.ts b/website/src/lib/logic/statistics.svelte.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/website/src/lib/logic/statistics.ts b/website/src/lib/logic/statistics.ts index 5a114d94..a0aed647 100644 --- a/website/src/lib/logic/statistics.ts +++ b/website/src/lib/logic/statistics.ts @@ -1,46 +1,80 @@ -import { ListItem, ListLevel } from '$lib/components/file-list/file-list'; -import { GPXFile, GPXStatistics, type Track } from 'gpx'; +import { selection } from '$lib/logic/selection'; +import { GPXStatistics } from 'gpx'; +import { fileStateCollection, GPXFileState } from '$lib/logic/file-state'; +import { + ListFileItem, + ListWaypointItem, + ListWaypointsItem, +} from '$lib/components/file-list/file-list'; +import { get, writable, type Writable } from 'svelte/store'; -export class GPXStatisticsTree { - level: ListLevel; - statistics: { - [key: string]: GPXStatisticsTree | GPXStatistics; - } = {}; - - constructor(element: GPXFile | Track) { - if (element instanceof GPXFile) { - this.level = ListLevel.FILE; - element.children.forEach((child, index) => { - this.statistics[index] = new GPXStatisticsTree(child); - }); - } else { - this.level = ListLevel.TRACK; - element.children.forEach((child, index) => { - this.statistics[index] = child.getStatistics(); - }); +export class SelectedGPXStatistics { + private _statistics: Writable; + private _files: Map< + string, + { + file: GPXFileState; + unsubscribe: () => void; } + >; + + constructor() { + this._statistics = writable(new GPXStatistics()); + this._files = new Map(); + selection.subscribe(() => this.update()); } - getStatisticsFor(item: ListItem): GPXStatistics { + subscribe(run: (value: GPXStatistics) => void, invalidate?: (value?: GPXStatistics) => void) { + return this._statistics.subscribe(run, invalidate); + } + + update() { let statistics = new GPXStatistics(); - let id = item.getIdAtLevel(this.level); - if (id === undefined || id === 'waypoints') { - Object.keys(this.statistics).forEach((key) => { - if (this.statistics[key] instanceof GPXStatistics) { - statistics.mergeWith(this.statistics[key]); - } else { - statistics.mergeWith(this.statistics[key].getStatisticsFor(item)); + selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => { + let stats = fileStateCollection.getStatistics(fileId); + if (stats) { + let first = true; + items.forEach((item) => { + if ( + !(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) || + first + ) { + statistics.mergeWith(stats.getStatisticsFor(item)); + first = false; + } + }); + } + + if (!this._files.has(fileId)) { + let file = fileStateCollection.getFileState(fileId); + if (file) { + let first = true; + let unsubscribe = file.subscribe(() => { + if (first) first = false; + else this.update(); + }); + this._files.set(fileId, { file, unsubscribe }); } - }); - } else { - let child = this.statistics[id]; - if (child instanceof GPXStatistics) { - statistics.mergeWith(child); - } else if (child !== undefined) { - statistics.mergeWith(child.getStatisticsFor(item)); + } + }, false); + this._statistics.set(statistics); + for (let [fileId, entry] of this._files) { + if ( + !get(fileStateCollection).has(fileId) || + !get(selection).hasAnyChildren(new ListFileItem(fileId)) + ) { + entry.unsubscribe(); + this._files.delete(fileId); } } - return statistics; } } -export type GPXFileWithStatistics = { file: GPXFile; statistics: GPXStatisticsTree }; + +export const gpxStatistics = new SelectedGPXStatistics(); + +export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> = + writable(undefined); + +gpxStatistics.subscribe(() => { + slicedGPXStatistics.set(undefined); +}); diff --git a/website/src/lib/stores.ts b/website/src/lib/stores.ts index d9ac91a8..8b9c7c06 100644 --- a/website/src/lib/stores.ts +++ b/website/src/lib/stores.ts @@ -22,64 +22,6 @@ // export const embedding = writable(false); // export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({}); -// export const gpxStatistics: Writable = writable(new GPXStatistics()); -// export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> = -// writable(undefined); - -// export function updateGPXData() { -// let statistics = new GPXStatistics(); -// applyToOrderedSelectedItemsFromFile((fileId, level, items) => { -// let stats = getStatistics(fileId); -// if (stats) { -// let first = true; -// items.forEach((item) => { -// if ( -// !(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) || -// first -// ) { -// statistics.mergeWith(stats.getStatisticsFor(item)); -// first = false; -// } -// }); -// } -// }, false); -// gpxStatistics.set(statistics); -// } - -// let unsubscribes: Map void> = new Map(); -// selection.subscribe(($selection) => { -// // Maintain up-to-date statistics for the current selection -// updateGPXData(); - -// while (unsubscribes.size > 0) { -// let [fileId, unsubscribe] = unsubscribes.entries().next().value; -// unsubscribe(); -// unsubscribes.delete(fileId); -// } - -// $selection.forEach((item) => { -// let fileId = item.getFileId(); -// if (!unsubscribes.has(fileId)) { -// let fileObserver = get(fileObservers).get(fileId); -// if (fileObserver) { -// let first = true; -// unsubscribes.set( -// fileId, -// fileObserver.subscribe(() => { -// if (first) first = false; -// else updateGPXData(); -// }) -// ); -// } -// } -// }); -// }); - -// gpxStatistics.subscribe(() => { -// slicedGPXStatistics.set(undefined); -// }); - -// export const gpxLayers: Map = new Map(); // export const routingControls: Map = new Map(); // export function selectFileWhenLoaded(fileId: string) { diff --git a/website/src/routes/[[language]]/app/+page.svelte b/website/src/routes/[[language]]/app/+page.svelte index 4f0ed33e..dd17fb32 100644 --- a/website/src/routes/[[language]]/app/+page.svelte +++ b/website/src/routes/[[language]]/app/+page.svelte @@ -2,7 +2,7 @@ import GPXLayers from '$lib/components/map/gpx-layer/GPXLayers.svelte'; // import ElevationProfile from '$lib/components/ElevationProfile.svelte'; // import FileList from '$lib/components/file-list/FileList.svelte'; - // import GPXStatistics from '$lib/components/GPXStatistics.svelte'; + import GPXStatistics from '$lib/components/GPXStatistics.svelte'; import Map from '$lib/components/map/Map.svelte'; import Menu from '$lib/components/Menu.svelte'; // import Toolbar from '$lib/components/toolbar/Toolbar.svelte'; @@ -11,7 +11,6 @@ // import CoordinatesPopup from '$lib/components/map/CoordinatesPopup.svelte'; import Resizer from '$lib/components/Resizer.svelte'; import { Toaster } from '$lib/components/ui/sonner'; - // import { gpxStatistics, loadFiles, slicedGPXStatistics } from '$lib/stores'; // import { onMount } from 'svelte'; // import { page } from '$app/state'; import { languages } from '$lib/languages'; @@ -23,6 +22,7 @@ import { loadFiles } from '$lib/logic/file-actions'; import { onMount } from 'svelte'; import { page } from '$app/state'; + import { gpxStatistics, slicedGPXStatistics } from '$lib/logic/statistics'; const { treeFileView, @@ -127,12 +127,12 @@ class="{$elevationProfile ? '' : 'h-10'} flex flex-row gap-2 px-2 sm:px-4" style={$elevationProfile ? `height: ${$bottomPanelSize}px` : ''} > - + />