Files
gpx.studio/website/src/lib/logic/file-state.ts

165 lines
5.6 KiB
TypeScript
Raw Normal View History

2025-10-05 19:34:05 +02:00
import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simplify';
import { db, type Database } from '$lib/db';
import { liveQuery } from 'dexie';
import { GPXFile } from 'gpx';
2025-10-18 00:31:14 +02:00
import { GPXStatisticsTree, type GPXFileWithStatistics } from '$lib/logic/statistics-tree';
2025-10-17 23:54:45 +02:00
import { settings } from '$lib/logic/settings';
import { get, writable, type Subscriber, type Writable } from 'svelte/store';
2025-10-05 19:34:05 +02:00
// Observe a single file from the database, and maintain its statistics
2025-10-18 00:31:14 +02:00
export class GPXFileState {
2025-10-17 23:54:45 +02:00
private _file: Writable<GPXFileWithStatistics | undefined>;
2025-10-05 19:34:05 +02:00
private _subscription: { unsubscribe: () => void } | undefined;
constructor(db: Database, fileId: string) {
2025-10-17 23:54:45 +02:00
this._file = writable(undefined);
2025-10-05 19:34:05 +02:00
this._subscription = liveQuery(() => db.files.get(fileId)).subscribe((value) => {
if (value !== undefined) {
let file = new GPXFile(value);
updateAnchorPoints(file);
let statistics = new GPXStatisticsTree(file);
2025-10-17 23:54:45 +02:00
this._file.set({ file, statistics });
2025-10-05 19:34:05 +02:00
}
});
}
2025-10-17 23:54:45 +02:00
subscribe(run: Subscriber<GPXFileWithStatistics | undefined>, invalidate?: () => void) {
return this._file.subscribe(run, invalidate);
}
2025-10-05 19:34:05 +02:00
destroy() {
this._subscription?.unsubscribe();
this._subscription = undefined;
}
get file(): GPXFile | undefined {
2025-10-17 23:54:45 +02:00
return get(this._file)?.file;
2025-10-05 19:34:05 +02:00
}
get statistics(): GPXStatisticsTree | undefined {
2025-10-17 23:54:45 +02:00
return get(this._file)?.statistics;
2025-10-05 19:34:05 +02:00
}
}
// Observe the file ids in the database, and maintain a map of file states for the corresponding files
export class GPXFileStateCollection {
2025-10-17 23:54:45 +02:00
private _files: Writable<Map<string, GPXFileState>>;
2025-10-05 19:34:05 +02:00
constructor(db: Database) {
2025-10-17 23:54:45 +02:00
this._files = writable(new Map());
2025-10-05 19:34:05 +02:00
2025-10-19 16:14:05 +02:00
liveQuery(() => db.fileids.toArray()).subscribe((dbFileIds) => {
2025-10-17 23:54:45 +02:00
const currentFiles = get(this._files);
2025-10-05 19:34:05 +02:00
// Find new files to observe
let newFiles = dbFileIds
2025-10-17 23:54:45 +02:00
.filter((id) => !currentFiles.has(id))
2025-10-05 19:34:05 +02:00
.sort((a, b) => parseInt(a.split('-')[1]) - parseInt(b.split('-')[1]));
// Find deleted files to stop observing
2025-10-17 23:54:45 +02:00
let deletedFiles = Array.from(currentFiles.keys()).filter(
2025-10-05 19:34:05 +02:00
(id) => !dbFileIds.find((fileId) => fileId === id)
);
if (newFiles.length > 0 || deletedFiles.length > 0) {
// Update the map of file states
2025-10-17 23:54:45 +02:00
this._files.update(($files) => {
newFiles.forEach((id) => {
2025-10-19 16:14:05 +02:00
$files.set(id, new GPXFileState(db, id));
2025-10-17 23:54:45 +02:00
});
deletedFiles.forEach((id) => {
$files.get(id)?.destroy();
$files.delete(id);
});
return $files;
2025-10-05 19:34:05 +02:00
});
// Update the file order
2025-10-17 23:54:45 +02:00
let fileOrder = get(settings.fileOrder).filter((id) => !deletedFiles.includes(id));
2025-10-05 19:34:05 +02:00
newFiles.forEach((id) => {
if (!fileOrder.includes(id)) {
fileOrder.push(id);
}
});
2025-10-17 23:54:45 +02:00
settings.fileOrder.set(fileOrder);
2025-10-05 19:34:05 +02:00
}
});
}
2025-10-19 16:14:05 +02:00
subscribe(run: Subscriber<Map<string, GPXFileState>>, invalidate?: () => void) {
return this._files.subscribe(run, invalidate);
}
2025-10-05 19:34:05 +02:00
get size(): number {
2025-10-17 23:54:45 +02:00
return get(this._files).size;
2025-10-05 19:34:05 +02:00
}
2025-10-18 00:31:14 +02:00
getFileState(fileId: string): GPXFileState | undefined {
return get(this._files).get(fileId);
}
2025-10-05 19:34:05 +02:00
getFile(fileId: string): GPXFile | undefined {
2025-10-17 23:54:45 +02:00
let fileState = get(this._files).get(fileId);
2025-10-05 19:34:05 +02:00
return fileState?.file;
}
getStatistics(fileId: string): GPXStatisticsTree | undefined {
2025-10-17 23:54:45 +02:00
let fileState = get(this._files).get(fileId);
2025-10-05 19:34:05 +02:00
return fileState?.statistics;
}
2025-10-17 23:54:45 +02:00
forEach(callback: (fileId: string, file: GPXFile) => void) {
get(this._files).forEach((fileState, fileId) => {
if (fileState.file) {
callback(fileId, fileState.file);
}
});
}
2025-10-05 19:34:05 +02:00
}
// Collection of all file states
export const fileStateCollection = new GPXFileStateCollection(db);
2025-10-17 23:54:45 +02:00
2025-10-19 16:14:05 +02:00
export type GPXFileStateCallback = (files: Map<string, GPXFileState>) => void;
2025-10-17 23:54:45 +02:00
export class GPXFileStateCollectionObserver {
private _fileIds: Set<string>;
2025-10-19 16:14:05 +02:00
private _onFilesAdded: GPXFileStateCallback;
2025-10-17 23:54:45 +02:00
private _onFileRemoved: (fileId: string) => void;
private _onDestroy: () => void;
2025-10-18 16:10:08 +02:00
private _unsubscribe: () => void;
2025-10-17 23:54:45 +02:00
constructor(
2025-10-19 16:14:05 +02:00
onFilesAdded: GPXFileStateCallback,
2025-10-17 23:54:45 +02:00
onFileRemoved: (fileId: string) => void,
onDestroy: () => void
) {
this._fileIds = new Set();
2025-10-19 16:14:05 +02:00
this._onFilesAdded = onFilesAdded;
2025-10-17 23:54:45 +02:00
this._onFileRemoved = onFileRemoved;
this._onDestroy = onDestroy;
2025-10-18 16:10:08 +02:00
this._unsubscribe = fileStateCollection.subscribe((files) => {
2025-10-17 23:54:45 +02:00
this._fileIds.forEach((fileId) => {
if (!files.has(fileId)) {
this._onFileRemoved(fileId);
this._fileIds.delete(fileId);
}
});
2025-10-19 16:14:05 +02:00
let newFiles = new Map<string, GPXFileState>();
2025-10-17 23:54:45 +02:00
files.forEach((file: GPXFileState, fileId: string) => {
if (!this._fileIds.has(fileId)) {
2025-10-19 16:14:05 +02:00
newFiles.set(fileId, file);
2025-10-17 23:54:45 +02:00
this._fileIds.add(fileId);
}
});
2025-10-19 16:14:05 +02:00
if (newFiles.size > 0) {
this._onFilesAdded(newFiles);
}
2025-10-17 23:54:45 +02:00
});
}
destroy() {
this._onDestroy();
2025-10-18 16:10:08 +02:00
this._unsubscribe();
2025-10-17 23:54:45 +02:00
}
}