dexie progress

This commit is contained in:
vcoppe
2024-05-03 15:59:34 +02:00
parent cd919258ad
commit 6c9faf54b1
10 changed files with 334 additions and 175 deletions

View File

@@ -123,14 +123,14 @@
});
</script>
<div class="h-10 -translate-y-10 w-full pointer-events-none">
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
<ScrollArea orientation="horizontal" class="w-full h-full" scrollbarXClasses="h-2">
<div bind:this={container} class="flex flex-row gap-1">
{#if $fileObservers}
{#each $fileObservers.values() as file}
{#each $fileObservers.entries() as [fileId, file]}
<div
bind:this={buttons[get(file)._data.id]}
data-id={get(file)._data.id}
bind:this={buttons[fileId]}
data-id={fileId}
class="pointer-events-auto first:ml-1 last:mr-1 mb-1 bg-transparent"
>
<FileListItem {file} />

View File

@@ -4,43 +4,47 @@
import Shortcut from './Shortcut.svelte';
import { Copy, Trash2 } from 'lucide-svelte';
import { get, type Readable, type Writable } from 'svelte/store';
import { get, type Readable } from 'svelte/store';
import { selectedFiles, selectFiles } from '$lib/stores';
import { dbUtils } from '$lib/db';
import { _ } from 'svelte-i18n';
import type { GPXFile } from 'gpx';
import type { FreezedObject } from 'structurajs';
import { dbUtils } from '$lib/db';
export let file: Readable<FreezedObject<GPXFile>> | undefined;
export let file: Readable<GPXFile | undefined>;
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
on:contextmenu={() => {
if (!get(selectedFiles).has($file?._data.id)) {
get(selectFiles).select($file?._data.id);
}
}}
>
<ContextMenu.Root>
<ContextMenu.Trigger>
<Button variant="outline" class="h-9 px-1.5 py-1 border-none shadow-md">
{$file?.metadata.name}
</Button>
</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item on:click={dbUtils.duplicateSelectedFiles}>
<Copy size="16" class="mr-1" />
{$_('menu.duplicate')}
<Shortcut key="D" ctrl={true} /></ContextMenu.Item
>
<ContextMenu.Separator />
<ContextMenu.Item on:click={dbUtils.deleteSelectedFiles}
><Trash2 size="16" class="mr-1" />
{$_('menu.delete')}
<Shortcut key="⌫" ctrl={true} /></ContextMenu.Item
>
</ContextMenu.Content>
</ContextMenu.Root>
</div>
{#if $file}
<div
on:contextmenu={() => {
if (!get(selectedFiles).has($file._data.id)) {
get(selectFiles).select($file._data.id);
}
}}
>
<ContextMenu.Root>
<ContextMenu.Trigger>
<Button
variant="outline"
class="h-9 px-1.5 py-1 border-none shadow-md focus-visible:ring-0 focus-visible:ring-offset-0"
>
{$file.metadata.name}
</Button>
</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item on:click={dbUtils.duplicateSelectedFiles}>
<Copy size="16" class="mr-1" />
{$_('menu.duplicate')}
<Shortcut key="D" ctrl={true} /></ContextMenu.Item
>
<ContextMenu.Separator />
<ContextMenu.Item on:click={dbUtils.deleteSelectedFiles}
><Trash2 size="16" class="mr-1" />
{$_('menu.delete')}
<Shortcut key="⌫" ctrl={true} /></ContextMenu.Item
>
</ContextMenu.Content>
</ContextMenu.Root>
</div>
{/if}

View File

@@ -19,7 +19,7 @@
import { _ } from 'svelte-i18n';
import { derived, get } from 'svelte/store';
import { canUndo, dbUtils, fileObservers, redo, undo } from '$lib/db';
import { canUndo, canRedo, dbUtils, fileObservers, redo, undo } from '$lib/db';
let showDistanceMarkers = false;
let showDirectionMarkers = false;
@@ -41,7 +41,7 @@
}
let undoDisabled = derived(canUndo, ($canUndo) => !$canUndo);
let redoDisabled = derived(canUndo, ($canUndo) => !$canUndo);
let redoDisabled = derived(canRedo, ($canRedo) => !$canRedo);
</script>
<div class="absolute top-2 left-0 right-0 z-20 flex flex-row justify-center pointer-events-none">

View File

@@ -39,31 +39,36 @@ function decrementColor(color: string) {
export class GPXLayer {
map: mapboxgl.Map;
file: Readable<FreezedObject<GPXFile>>;
fileId: string;
file: Readable<FreezedObject<GPXFile> | undefined>;
layerColor: string;
popup: mapboxgl.Popup;
popupElement: HTMLElement;
markers: mapboxgl.Marker[] = [];
unsubscribe: () => void;
addBinded: () => void = this.add.bind(this);
updateBinded: () => void = this.update.bind(this);
selectOnClickBinded: (e: any) => void = this.selectOnClick.bind(this);
constructor(map: mapboxgl.Map, file: Readable<FreezedObject<GPXFile>>, popup: mapboxgl.Popup, popupElement: HTMLElement) {
constructor(map: mapboxgl.Map, fileId: string, file: Readable<FreezedObject<GPXFile> | undefined>, popup: mapboxgl.Popup, popupElement: HTMLElement) {
this.map = map;
this.file = file;
this.fileId = get(file)._data.id;
this.fileId = fileId;
this.file = file
this.layerColor = getColor();
this.popup = popup;
this.popupElement = popupElement;
this.unsubscribe = file.subscribe(this.updateData.bind(this));
this.unsubscribe = file.subscribe(this.update.bind(this));
this.add();
this.map.on('style.load', this.addBinded);
this.map.on('style.load', this.updateBinded);
}
add() {
update() {
let file = get(this.file);
if (!file) {
return;
}
let addedSource = false;
if (!this.map.getSource(this.fileId)) {
let data = this.getGeoJSON();
@@ -71,6 +76,7 @@ export class GPXLayer {
type: 'geojson',
data
});
addedSource = true;
}
if (!this.map.getLayer(this.fileId)) {
@@ -93,16 +99,16 @@ export class GPXLayer {
this.map.on('mouseenter', this.fileId, toPointerCursor);
this.map.on('mouseleave', this.fileId, toDefaultCursor);
}
}
updateData() {
let source = this.map.getSource(this.fileId);
if (source) {
source.setData(this.getGeoJSON());
if (!addedSource) {
let source = this.map.getSource(this.fileId);
if (source) {
source.setData(this.getGeoJSON());
}
}
let markerIndex = 0;
get(this.file).wpt.forEach((waypoint) => { // Update markers
file.wpt.forEach((waypoint) => { // Update markers
if (markerIndex < this.markers.length) {
this.markers[markerIndex].setLngLat(waypoint.getCoordinates());
} else {
@@ -131,7 +137,7 @@ export class GPXLayer {
this.map.off('click', this.fileId, this.selectOnClickBinded);
this.map.off('mouseenter', this.fileId, toPointerCursor);
this.map.off('mouseleave', this.fileId, toDefaultCursor);
this.map.off('style.load', this.addBinded);
this.map.off('style.load', this.updateBinded);
this.map.removeLayer(this.fileId);
this.map.removeSource(this.fileId);
@@ -146,7 +152,9 @@ export class GPXLayer {
}
moveToFront() {
this.map.moveLayer(this.fileId);
if (this.map.getLayer(this.fileId)) {
this.map.moveLayer(this.fileId);
}
}
selectOnClick(e: any) {
@@ -161,7 +169,15 @@ export class GPXLayer {
}
getGeoJSON(): GeoJSON.FeatureCollection {
let data = get(this.file).toGeoJSON();
let file = get(this.file);
if (!file) {
return {
type: 'FeatureCollection',
features: []
};
}
let data = file.toGeoJSON();
for (let feature of data.features) {
if (!feature.properties) {
feature.properties = {};

View File

@@ -22,7 +22,7 @@
// add layers for new files
$fileObservers.forEach((file, fileId) => {
if (!$layers.has(fileId)) {
$layers.set(fileId, new GPXLayer(get(map), file, popup, popupElement));
$layers.set(fileId, new GPXLayer(get(map), fileId, file, popup, popupElement));
}
});
return $layers;

View File

@@ -61,7 +61,7 @@
if (selectedFileObserver) {
routingControls.set(
selectedId,
new RoutingControls(get(map), selectedFileObserver, popup, popupElement)
new RoutingControls(get(map), selectedId, selectedFileObserver, popup, popupElement)
);
}
} else {

View File

@@ -1,5 +1,5 @@
import { distance, type Coordinates, type GPXFile, TrackPoint, TrackSegment } from "gpx";
import { get, type Writable } from "svelte/store";
import { get, type Readable, type Writable } from "svelte/store";
import { computeAnchorPoints } from "./Simplify";
import mapboxgl from "mapbox-gl";
import { route } from "./Routing";
@@ -11,7 +11,8 @@ import { dbUtils } from "$lib/db";
export class RoutingControls {
map: mapboxgl.Map;
file: Writable<GPXFile>;
fileId: string = '';
file: Readable<GPXFile | undefined>;
anchors: AnchorWithMarker[] = [];
shownAnchors: AnchorWithMarker[] = [];
popup: mapboxgl.Popup;
@@ -24,8 +25,9 @@ export class RoutingControls {
updateTemporaryAnchorBinded: (e: any) => void = this.updateTemporaryAnchor.bind(this);
appendAnchorBinded: (e: mapboxgl.MapMouseEvent) => void = this.appendAnchor.bind(this);
constructor(map: mapboxgl.Map, file: Writable<GPXFile>, popup: mapboxgl.Popup, popupElement: HTMLElement) {
constructor(map: mapboxgl.Map, fileId: string, file: Writable<GPXFile>, popup: mapboxgl.Popup, popupElement: HTMLElement) {
this.map = map;
this.fileId = fileId;
this.file = file;
this.popup = popup;
this.popupElement = popupElement;
@@ -46,13 +48,18 @@ export class RoutingControls {
this.map.on('zoom', this.toggleAnchorsForZoomLevelAndBoundsBinded);
this.map.on('move', this.toggleAnchorsForZoomLevelAndBoundsBinded);
this.map.on('click', this.appendAnchorBinded);
this.map.on('mousemove', get(this.file)._data.id, this.showTemporaryAnchorBinded);
this.map.on('mousemove', this.fileId, this.showTemporaryAnchorBinded);
this.unsubscribe = this.file.subscribe(this.updateControls.bind(this));
}
updateControls() { // Update the markers when the file changes
let segments = get(this.file).getSegments();
let file = get(this.file);
if (!file) {
return;
}
let segments = file.getSegments();
let anchorIndex = 0;
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
@@ -104,7 +111,7 @@ export class RoutingControls {
this.map.off('zoom', this.toggleAnchorsForZoomLevelAndBoundsBinded);
this.map.off('move', this.toggleAnchorsForZoomLevelAndBoundsBinded);
this.map.off('click', this.appendAnchorBinded);
this.map.off('mousemove', get(this.file)._data.id, this.showTemporaryAnchorBinded);
this.map.off('mousemove', this.fileId, this.showTemporaryAnchorBinded);
this.map.off('mousemove', this.updateTemporaryAnchorBinded);
this.unsubscribe();
@@ -290,17 +297,17 @@ export class RoutingControls {
let [previousAnchor, nextAnchor] = this.getNeighbouringAnchors(anchor);
if (previousAnchor === null && nextAnchor === null) { // Only one point, remove it
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
dbUtils.applyToFile(this.fileId, (file) => {
let segment = file.getSegments()[anchor.segmentIndex];
segment.replace(0, 0, []);
});
} else if (previousAnchor === null) { // First point, remove trackpoints until nextAnchor
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
dbUtils.applyToFile(this.fileId, (file) => {
let segment = file.getSegments()[anchor.segmentIndex];
segment.replace(0, nextAnchor.point._data.index - 1, []);
});
} else if (nextAnchor === null) { // Last point, remove trackpoints from previousAnchor
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
dbUtils.applyToFile(this.fileId, (file) => {
let segment = file.getSegments()[anchor.segmentIndex];
segment.replace(previousAnchor.point._data.index + 1, segment.trkpt.length - 1, []);
});
@@ -323,7 +330,7 @@ export class RoutingControls {
if (!lastAnchor) {
// TODO, create segment if it does not exist
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
dbUtils.applyToFile(this.fileId, (file) => {
let segment = file.getSegments()[0];
segment.replace(0, 0, [newPoint]);
});
@@ -365,7 +372,7 @@ export class RoutingControls {
let segment = anchors[0].segment;
if (anchors.length === 1) { // Only one anchor, update the point in the segment
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
dbUtils.applyToFile(this.fileId, (file) => {
let segment = file.getSegments()[anchors[0].segmentIndex];
segment.replace(0, 0, [new TrackPoint({
attributes: targetCoordinates[0],
@@ -425,7 +432,7 @@ export class RoutingControls {
anchor.point._data.zoom = 0; // Make these anchors permanent
});
dbUtils.applyToFile(get(this.file)._data.id, (file) => {
dbUtils.applyToFile(this.fileId, (file) => {
let segment = file.getSegments()[anchors[0].segmentIndex];
segment.replace(start, end, response);
});

View File

@@ -6,49 +6,96 @@ import { fileOrder, selectedFiles } from './stores';
class Database extends Dexie {
fileids!: Dexie.Table<string, string>;
files!: Dexie.Table<FreezedObject<GPXFile>, string>;
patches!: Dexie.Table<{ patch: Patch[], inversePatch: Patch[] }, number>;
settings!: Dexie.Table<any, string>;
constructor() {
super("Database");
this.version(1).stores({
files: ',file',
patches: '++id,patch,inversePatch',
settings: ',value'
super("Database", {
cache: 'immutable'
});
this.version(1).stores({
fileids: ',&fileid',
files: '',
patches: ',patch',
settings: ''
});
this.files.add
}
}
const db = new Database();
function dexieStore<T>(querier: () => T | Promise<T>): Readable<T> {
const dexieObservable = liveQuery(querier)
return {
subscribe(run, invalidate) {
return dexieObservable.subscribe(run, invalidate).unsubscribe
function dexieFileStore(querier: () => FreezedObject<GPXFile> | undefined | Promise<FreezedObject<GPXFile> | undefined>): Readable<GPXFile> {
let store = writable<GPXFile>(undefined);
liveQuery(querier).subscribe(value => {
if (value !== undefined) {
let gpx = new GPXFile(value);
fileState.set(gpx._data.id, gpx);
store.set(gpx);
}
});
return {
subscribe: store.subscribe,
};
}
function dexieStore<T>(querier: () => T | Promise<T>, initial?: T): Readable<T> {
let store = writable<T>(initial);
liveQuery(querier).subscribe(value => {
if (value !== undefined) {
store.set(value);
}
});
return {
subscribe: store.subscribe,
};
}
function updateFiles(files: (FreezedObject<GPXFile> | undefined)[], add: boolean = false) {
let filteredFiles = files.filter(file => file !== undefined) as FreezedObject<GPXFile>[];
let fileIds = filteredFiles.map(file => file._data.id);
if (add) {
return db.transaction('rw', db.fileids, db.files, async () => {
await db.fileids.bulkAdd(fileIds, fileIds);
await db.files.bulkAdd(filteredFiles, fileIds);
});
} else {
return db.files.bulkPut(filteredFiles, fileIds);
}
}
export function updateFiles(files: FreezedObject<GPXFile>[]) {
console.log(files);
return db.files.bulkPut(files, files.map(file => file._data.id));
function deleteFiles(fileIds: string[]) {
return db.transaction('rw', db.fileids, db.files, async () => {
await db.fileids.bulkDelete(fileIds);
await db.files.bulkDelete(fileIds);
});
}
export const fileObservers: Writable<Map<string, Readable<FreezedObject<GPXFile>>>> = writable(new Map());
export const fileState: Map<string, FreezedObject<GPXFile>> = new Map(); // Used to generate patches
function commitFileStateChange(newFileState: ReadonlyMap<string, FreezedObject<GPXFile>>, patch: Patch[]) {
if (newFileState.size > fileState.size) {
return updateFiles(getChangedFileIds(patch).map((fileId) => newFileState.get(fileId)), true);
} else if (newFileState.size === fileState.size) {
return updateFiles(getChangedFileIds(patch).map((fileId) => newFileState.get(fileId)));
} else {
return deleteFiles(getChangedFileIds(patch));
}
}
liveQuery(() => db.files.toArray()).subscribe(dbFiles => {
export const fileObservers: Writable<Map<string, Readable<GPXFile | undefined>>> = writable(new Map());
const fileState: Map<string, GPXFile> = new Map(); // Used to generate patches
liveQuery(() => db.fileids.toArray()).subscribe(dbFileIds => {
// Find new files to observe
let newFiles = dbFiles.map(file => file._data.id).filter(id => !get(fileObservers).has(id));
let newFiles = dbFileIds.filter(id => !get(fileObservers).has(id));
// Find deleted files to stop observing
let deletedFiles = Array.from(get(fileObservers).keys()).filter(id => !dbFiles.find(file => file._data.id === id));
let deletedFiles = Array.from(get(fileObservers).keys()).filter(id => !dbFileIds.find(fileId => fileId === id));
// Update the store
if (newFiles.length > 0 || deletedFiles.length > 0) {
fileObservers.update($files => {
newFiles.forEach(id => {
$files.set(id, dexieStore(() => db.files.get(id)));
$files.set(id, dexieFileStore(() => db.files.get(id)));
});
deletedFiles.forEach(id => {
$files.delete(id);
@@ -56,71 +103,75 @@ liveQuery(() => db.files.toArray()).subscribe(dbFiles => {
});
return $files;
});
console.log(get(fileObservers));
}
// Update fileState
dbFiles.forEach(file => {
fileState.set(file._data.id, file);
});
});
const patchIndex = dexieStore(() => db.settings.get('patchIndex') ?? -1);
const patches = dexieStore(() => db.patches.toArray());
export const canUndo = derived(patchIndex, $patchIndex => $patchIndex >= 0);
export const canRedo = derived([patchIndex, patches], ([$patchIndex, $patches]) => $patchIndex < $patches.length - 1);
const patchIndex: Readable<number> = dexieStore(() => db.settings.get('patchIndex'), -1);
const patches: Readable<{ patch: Patch[], inversePatch: Patch[] }[]> = dexieStore(() => db.patches.toArray(), []);
export const canUndo: Readable<boolean> = derived(patchIndex, ($patchIndex) => $patchIndex >= 0);
export const canRedo: Readable<boolean> = derived([patchIndex, patches], ([$patchIndex, $patches]) => $patchIndex < $patches.length - 1);
export function applyGlobal(callback: (files: Map<string, GPXFile>) => void) {
const [newFileState, patch, inversePatch] = produceWithPatches(fileState, callback);
appendPatches(patch, inversePatch, true);
return updateFiles(Array.from(newFileState.values()));
return commitFileStateChange(newFileState, patch);
}
function applyToFiles(fileIds: string[], callback: (file: GPXFile) => void) {
const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => {
fileIds.forEach((fileId) => {
callback(draft.get(fileId));
let file = draft.get(fileId);
if (file) {
callback(file);
}
});
});
appendPatches(patch, inversePatch, false);
return updateFiles(fileIds.map((fileId) => newFileState.get(fileId)));
return commitFileStateChange(newFileState, patch);
}
function appendPatches(patch: Patch[], inversePatch: Patch[], global: boolean) {
db.patches.where('id').above(patchIndex).delete();
db.patches.add({
patch,
inversePatch
async function appendPatches(patch: Patch[], inversePatch: Patch[], global: boolean) {
if (get(patchIndex) !== undefined) {
db.patches.where(':id').above(get(patchIndex)).delete();
}
db.transaction('rw', db.patches, db.settings, async () => {
await db.patches.put({
patch,
inversePatch
}, get(patchIndex) + 1);
await db.settings.put(get(patchIndex) + 1, 'patchIndex');
});
db.settings.put(get(patchIndex) + 1, 'patchIndex');
}
function applyPatch(patch: Patch[]) {
let newFileState = applyPatches(fileState, patch);
let changedFiles = [];
return commitFileStateChange(newFileState, patch);
}
function getChangedFileIds(patch: Patch[]) {
let changedFileIds = [];
for (let p of patch) {
let fileId = p.p?.toString();
if (fileId) {
let newFile = newFileState.get(fileId);
if (newFile) {
changedFiles.push(newFile);
}
changedFileIds.push(fileId);
}
}
return updateFiles(changedFiles);
return changedFileIds;
}
function getFileId() {
for (let index = 0; ; index++) {
function getFileIds(n: number) {
let ids = [];
for (let index = 0; ids.length < n; index++) {
let id = `gpx-${index}`;
if (!get(fileObservers).has(id)) {
return id;
ids.push(id);
}
}
return ids;
}
export function undo() {
@@ -141,17 +192,16 @@ export function redo() {
export const dbUtils = {
add: (file: GPXFile) => {
file._data.id = getFileId();
console.log(file._data.id);
let result = applyGlobal((draft) => {
file._data.id = getFileIds(1)[0];
return applyGlobal((draft) => {
draft.set(file._data.id, file);
});
console.log(result);
},
addMultiple: (files: GPXFile[]) => {
applyGlobal((draft) => {
files.forEach((file) => {
file._data.id = getFileId();
return applyGlobal((draft) => {
let ids = getFileIds(files.length);
files.forEach((file, index) => {
file._data.id = ids[index];
draft.set(file._data.id, file);
});
});
@@ -164,12 +214,13 @@ export const dbUtils = {
},
duplicateSelectedFiles: () => {
applyGlobal((draft) => {
get(fileOrder).forEach((fileId) => {
let ids = getFileIds(get(fileOrder).length);
get(fileOrder).forEach((fileId, index) => {
if (get(selectedFiles).has(fileId)) {
let file = draft.get(fileId);
if (file) {
let clone = file.clone();
clone._data.id = getFileId();
clone._data.id = ids[index];
draft.set(clone._data.id, clone);
}
}

View File

@@ -5,7 +5,7 @@ import { GPXFile, buildGPX, parseGPX, GPXFiles } from 'gpx';
import { tick } from 'svelte';
import { _ } from 'svelte-i18n';
import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer';
import { dbUtils, fileObservers, fileState } from './db';
import { dbUtils, fileObservers } from './db';
export const map = writable<mapboxgl.Map | null>(null);
@@ -33,7 +33,10 @@ export const gpxData = writable(new GPXFiles([]).getTrackPointsAndStatistics());
function updateGPXData() {
let fileIds: string[] = get(fileOrder).filter((f) => get(selectedFiles).has(f));
let files: GPXFile[] = fileIds
.map((id) => fileState.get(id))
.map((id) => {
let fileObserver = get(fileObservers).get(id);
return fileObserver ? get(fileObserver) : null;
})
.filter((f) => f) as GPXFile[];
let gpxFiles = new GPXFiles(files);
gpxData.set(gpxFiles.getTrackPointsAndStatistics());
@@ -82,8 +85,7 @@ export function createFile() {
dbUtils.add(file);
tick().then(() => get(selectFiles).select(file._data.id));
currentTool.set(Tool.ROUTING);
selectFileWhenLoaded(file._data.id);
}
export function triggerFileInput() {
@@ -103,7 +105,7 @@ export function triggerFileInput() {
export async function loadFiles(list: FileList) {
let bounds = new mapboxgl.LngLatBounds();
let mapBounds = new mapboxgl.LngLatBounds([180, 90, -180, -90]);
if (fileState.size > 0) {
if (get(fileObservers).size > 0) {
mapBounds = get(map)?.getBounds() ?? mapBounds;
bounds.extend(mapBounds);
}
@@ -121,7 +123,10 @@ export async function loadFiles(list: FileList) {
}
}
dbUtils.addMultiple(files);
console.log('loadFiles', files);
let result = dbUtils.addMultiple(files);
console.log('addMultiple', result);
if (!mapBounds.contains(bounds.getSouthWest()) || !mapBounds.contains(bounds.getNorthEast()) || !mapBounds.contains(bounds.getSouthEast()) || !mapBounds.contains(bounds.getNorthWest())) {
get(map)?.fitBounds(bounds, {
@@ -131,11 +136,7 @@ export async function loadFiles(list: FileList) {
});
}
await tick();
if (files.length > 0) {
get(selectFiles).select(files[0]._data.id);
}
selectFileWhenLoaded(files[0]._data.id);
}
export async function loadFile(file: File): Promise<GPXFile | null> {
@@ -158,20 +159,37 @@ export async function loadFile(file: File): Promise<GPXFile | null> {
return result;
}
export async function exportSelectedFiles() {
for (let file of fileState.values()) {
if (get(selectedFiles).has(file._data.id)) {
exportFile(file);
await new Promise(resolve => setTimeout(resolve, 200));
function selectFileWhenLoaded(fileId: string) {
const unsubscribe = fileObservers.subscribe((files) => {
if (files.has(fileId)) {
tick().then(() => {
get(selectFiles).select(fileId);
});
unsubscribe();
}
}
});
}
export async function exportAllFiles() {
for (let file of fileState.values()) {
exportFile(file);
await new Promise(resolve => setTimeout(resolve, 200));
}
export function exportSelectedFiles() {
get(fileObservers).forEach(async (file, fileId) => {
if (get(selectedFiles).has(fileId)) {
let f = get(file);
if (f) {
exportFile(f);
await new Promise(resolve => setTimeout(resolve, 200));
}
}
});
}
export function exportAllFiles() {
get(fileObservers).forEach(async (file) => {
let f = get(file);
if (f) {
exportFile(f);
await new Promise(resolve => setTimeout(resolve, 200));
}
});
}
export function exportFile(file: GPXFile) {