mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-11-04 21:41:07 +00:00
bounds management
This commit is contained in:
@@ -73,6 +73,7 @@
|
|||||||
import { fileActionManager } from '$lib/logic/file-action-manager';
|
import { fileActionManager } from '$lib/logic/file-action-manager';
|
||||||
import { copied, selection } from '$lib/logic/selection';
|
import { copied, selection } from '$lib/logic/selection';
|
||||||
import { allHidden } from '$lib/logic/hidden';
|
import { allHidden } from '$lib/logic/hidden';
|
||||||
|
import { boundsManager } from '$lib/logic/bounds';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
distanceUnits,
|
distanceUnits,
|
||||||
@@ -281,7 +282,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<Menubar.Separator />
|
<Menubar.Separator />
|
||||||
<Menubar.Item
|
<Menubar.Item
|
||||||
onclick={selection.selectAll}
|
onclick={() => selection.selectAll()}
|
||||||
disabled={fileStateCollection.size == 0}
|
disabled={fileStateCollection.size == 0}
|
||||||
>
|
>
|
||||||
<FileStack size="16" />
|
<FileStack size="16" />
|
||||||
@@ -291,9 +292,10 @@
|
|||||||
<Menubar.Item
|
<Menubar.Item
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
if ($selection.size > 0) {
|
if ($selection.size > 0) {
|
||||||
// centerMapOnSelection();
|
boundsManager.centerMapOnSelection();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
disabled={$selection.size == 0}
|
||||||
>
|
>
|
||||||
<Maximize size="16" />
|
<Maximize size="16" />
|
||||||
{i18n._('menu.center')}
|
{i18n._('menu.center')}
|
||||||
@@ -302,7 +304,7 @@
|
|||||||
{#if $treeFileView}
|
{#if $treeFileView}
|
||||||
<Menubar.Separator />
|
<Menubar.Separator />
|
||||||
<Menubar.Item
|
<Menubar.Item
|
||||||
onclick={selection.copySelection}
|
onclick={() => selection.copySelection()}
|
||||||
disabled={$selection.size === 0}
|
disabled={$selection.size === 0}
|
||||||
>
|
>
|
||||||
<ClipboardCopy size="16" />
|
<ClipboardCopy size="16" />
|
||||||
@@ -310,7 +312,7 @@
|
|||||||
<Shortcut key="C" ctrl={true} />
|
<Shortcut key="C" ctrl={true} />
|
||||||
</Menubar.Item>
|
</Menubar.Item>
|
||||||
<Menubar.Item
|
<Menubar.Item
|
||||||
onclick={selection.cutSelection}
|
onclick={() => selection.cutSelection()}
|
||||||
disabled={$selection.size === 0}
|
disabled={$selection.size === 0}
|
||||||
>
|
>
|
||||||
<Scissors size="16" />
|
<Scissors size="16" />
|
||||||
@@ -375,7 +377,7 @@
|
|||||||
/>
|
/>
|
||||||
</Menubar.CheckboxItem>
|
</Menubar.CheckboxItem>
|
||||||
<Menubar.Separator />
|
<Menubar.Separator />
|
||||||
<Menubar.Item inset onclick={map.toggle3D}>
|
<Menubar.Item inset onclick={() => map.toggle3D()}>
|
||||||
<Box size="16" />
|
<Box size="16" />
|
||||||
{i18n._('menu.toggle_3d')}
|
{i18n._('menu.toggle_3d')}
|
||||||
<Shortcut key="{i18n._('menu.ctrl')} {i18n._('menu.drag')}" />
|
<Shortcut key="{i18n._('menu.ctrl')} {i18n._('menu.drag')}" />
|
||||||
@@ -629,9 +631,9 @@
|
|||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
} else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
} else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
||||||
// if ($selection.size > 0) {
|
if ($selection.size > 0) {
|
||||||
// centerMapOnSelection();
|
boundsManager.centerMapOnSelection();
|
||||||
// }
|
}
|
||||||
} else if (e.key === 'F1') {
|
} else if (e.key === 'F1') {
|
||||||
switchBasemaps();
|
switchBasemaps();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
import { map } from '$lib/components/map/map';
|
import { map } from '$lib/components/map/map';
|
||||||
import { fileActions, pasteSelection } from '$lib/logic/file-actions';
|
import { fileActions, pasteSelection } from '$lib/logic/file-actions';
|
||||||
import { allHidden } from '$lib/logic/hidden';
|
import { allHidden } from '$lib/logic/hidden';
|
||||||
|
import { boundsManager } from '$lib/logic/bounds';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
node,
|
node,
|
||||||
@@ -287,7 +288,7 @@
|
|||||||
<Shortcut key="A" ctrl={true} />
|
<Shortcut key="A" ctrl={true} />
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
{/if}
|
{/if}
|
||||||
<ContextMenu.Item onclick={centerMapOnSelection}>
|
<ContextMenu.Item onclick={() => boundsManager.centerMapOnSelection()}>
|
||||||
<Maximize size="16" class="mr-1" />
|
<Maximize size="16" class="mr-1" />
|
||||||
{i18n._('menu.center')}
|
{i18n._('menu.center')}
|
||||||
<Shortcut key="⏎" ctrl={true} />
|
<Shortcut key="⏎" ctrl={true} />
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ export class GPXLayerCollection {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
|
this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
|
||||||
(fileId, fileState) => {
|
(newFiles) => {
|
||||||
|
newFiles.forEach((fileState, fileId) => {
|
||||||
const layer = new GPXLayer(fileId, fileState);
|
const layer = new GPXLayer(fileId, fileState);
|
||||||
this._layers.set(fileId, layer);
|
this._layers.set(fileId, layer);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
(fileId) => {
|
(fileId) => {
|
||||||
const layer = this._layers.get(fileId);
|
const layer = this._layers.get(fileId);
|
||||||
|
|||||||
@@ -221,105 +221,3 @@ export class MapboxGLMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const map = new MapboxGLMap();
|
export const map = new MapboxGLMap();
|
||||||
|
|
||||||
// const targetMapBounds: {
|
|
||||||
// bounds: mapboxgl.LngLatBounds;
|
|
||||||
// ids: string[];
|
|
||||||
// total: number;
|
|
||||||
// } = $state({
|
|
||||||
// bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
|
|
||||||
// ids: [],
|
|
||||||
// total: 0,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// $effect(() => {
|
|
||||||
// if (
|
|
||||||
// map.current === null ||
|
|
||||||
// targetMapBounds.ids.length > 0 ||
|
|
||||||
// (targetMapBounds.bounds.getSouth() === 90 &&
|
|
||||||
// targetMapBounds.bounds.getWest() === 180 &&
|
|
||||||
// targetMapBounds.bounds.getNorth() === -90 &&
|
|
||||||
// targetMapBounds.bounds.getEast() === -180)
|
|
||||||
// ) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let currentZoom = map.current.getZoom();
|
|
||||||
// let currentBounds = map.current.getBounds();
|
|
||||||
// if (
|
|
||||||
// targetMapBounds.total !== get(fileObservers).size &&
|
|
||||||
// currentBounds &&
|
|
||||||
// currentZoom > 2 // Extend current bounds only if the map is zoomed in
|
|
||||||
// ) {
|
|
||||||
// // There are other files on the map
|
|
||||||
// if (
|
|
||||||
// currentBounds.contains(targetMapBounds.bounds.getSouthEast()) &&
|
|
||||||
// currentBounds.contains(targetMapBounds.bounds.getNorthWest())
|
|
||||||
// ) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// targetMapBounds.bounds.extend(currentBounds.getSouthWest());
|
|
||||||
// targetMapBounds.bounds.extend(currentBounds.getNorthEast());
|
|
||||||
// }
|
|
||||||
|
|
||||||
// map.current.fitBounds(targetMapBounds.bounds, { padding: 80, linear: true, easing: () => 1 });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export function initTargetMapBounds(ids: string[]) {
|
|
||||||
// targetMapBounds.bounds = new mapboxgl.LngLatBounds([180, 90, -180, -90]);
|
|
||||||
// targetMapBounds.ids = ids;
|
|
||||||
// targetMapBounds.total = ids.length;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export function updateTargetMapBounds(
|
|
||||||
// id: string,
|
|
||||||
// bounds: { southWest: Coordinates; northEast: Coordinates }
|
|
||||||
// ) {
|
|
||||||
// if (targetMapBounds.ids.indexOf(id) === -1) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (
|
|
||||||
// bounds.southWest.lat !== 90 ||
|
|
||||||
// bounds.southWest.lon !== 180 ||
|
|
||||||
// bounds.northEast.lat !== -90 ||
|
|
||||||
// bounds.northEast.lon !== -180
|
|
||||||
// ) {
|
|
||||||
// // Avoid update for empty (new) files
|
|
||||||
// targetMapBounds.ids = targetMapBounds.ids.filter((x) => x !== id);
|
|
||||||
// targetMapBounds.bounds.extend(bounds.southWest);
|
|
||||||
// targetMapBounds.bounds.extend(bounds.northEast);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export function centerMapOnSelection() {
|
|
||||||
// let selected = get(selection).getSelected();
|
|
||||||
// let bounds = new mapboxgl.LngLatBounds();
|
|
||||||
|
|
||||||
// if (selected.find((item) => item instanceof ListWaypointItem)) {
|
|
||||||
// applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
|
||||||
// let file = getFile(fileId);
|
|
||||||
// if (file) {
|
|
||||||
// items.forEach((item) => {
|
|
||||||
// if (item instanceof ListWaypointItem) {
|
|
||||||
// let waypoint = file.wpt[item.getWaypointIndex()];
|
|
||||||
// if (waypoint) {
|
|
||||||
// bounds.extend([waypoint.getLongitude(), waypoint.getLatitude()]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// let selectionBounds = get(gpxStatistics).global.bounds;
|
|
||||||
// bounds.setNorthEast(selectionBounds.northEast);
|
|
||||||
// bounds.setSouthWest(selectionBounds.southWest);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// get(map)?.fitBounds(bounds, {
|
|
||||||
// padding: 80,
|
|
||||||
// easing: () => 1,
|
|
||||||
// maxZoom: 15,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -61,8 +61,13 @@ export class ReducedGPXLayerCollection {
|
|||||||
this._layers = new Map();
|
this._layers = new Map();
|
||||||
this._simplified = new Map();
|
this._simplified = new Map();
|
||||||
this._fileStateCollectionOberver = new GPXFileStateCollectionObserver(
|
this._fileStateCollectionOberver = new GPXFileStateCollectionObserver(
|
||||||
(fileId, fileState) => {
|
(newFiles) => {
|
||||||
this._layers.set(fileId, new ReducedGPXLayer(fileState, this._updateSimplified));
|
newFiles.forEach((fileState, fileId) => {
|
||||||
|
this._layers.set(
|
||||||
|
fileId,
|
||||||
|
new ReducedGPXLayer(fileState, this._updateSimplified)
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
(fileId) => {
|
(fileId) => {
|
||||||
this._layers.get(fileId)?.destroy();
|
this._layers.get(fileId)?.destroy();
|
||||||
|
|||||||
@@ -84,11 +84,13 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
if ($map && popup && popupElement) {
|
if ($map && popup && popupElement) {
|
||||||
fileStateCollectionObserver = new GPXFileStateCollectionObserver(
|
fileStateCollectionObserver = new GPXFileStateCollectionObserver(
|
||||||
(fileId, fileState) => {
|
(newFiles) => {
|
||||||
|
newFiles.forEach((fileState, fileId) => {
|
||||||
routingControls.set(
|
routingControls.set(
|
||||||
fileId,
|
fileId,
|
||||||
new RoutingControls(fileId, fileState, popup, popupElement)
|
new RoutingControls(fileId, fileState, popup, popupElement)
|
||||||
);
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
(fileId) => {
|
(fileId) => {
|
||||||
const controls = routingControls.get(fileId);
|
const controls = routingControls.get(fileId);
|
||||||
|
|||||||
185
website/src/lib/logic/bounds.ts
Normal file
185
website/src/lib/logic/bounds.ts
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import { get } from 'svelte/store';
|
||||||
|
import { selection } from '$lib/logic/selection';
|
||||||
|
import mapboxgl from 'mapbox-gl';
|
||||||
|
import { ListFileItem, ListWaypointItem } from '$lib/components/file-list/file-list';
|
||||||
|
import {
|
||||||
|
fileStateCollection,
|
||||||
|
GPXFileState,
|
||||||
|
GPXFileStateCollectionObserver,
|
||||||
|
} from '$lib/logic/file-state';
|
||||||
|
import { gpxStatistics } from '$lib/logic/statistics';
|
||||||
|
import { map } from '$lib/components/map/map';
|
||||||
|
import type { GPXFileWithStatistics } from './statistics-tree';
|
||||||
|
import type { Coordinates } from 'gpx';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
// const targetMapBounds: {
|
||||||
|
// bounds: mapboxgl.LngLatBounds;
|
||||||
|
// ids: string[];
|
||||||
|
// total: number;
|
||||||
|
// } = $state({
|
||||||
|
// bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
|
||||||
|
// ids: [],
|
||||||
|
// total: 0,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// $effect(() => {
|
||||||
|
// if (
|
||||||
|
// map.current === null ||
|
||||||
|
// targetMapBounds.ids.length > 0 ||
|
||||||
|
// (targetMapBounds.bounds.getSouth() === 90 &&
|
||||||
|
// targetMapBounds.bounds.getWest() === 180 &&
|
||||||
|
// targetMapBounds.bounds.getNorth() === -90 &&
|
||||||
|
// targetMapBounds.bounds.getEast() === -180)
|
||||||
|
// ) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let currentZoom = map.current.getZoom();
|
||||||
|
// let currentBounds = map.current.getBounds();
|
||||||
|
// if (
|
||||||
|
// targetMapBounds.total !== get(fileObservers).size &&
|
||||||
|
// currentBounds &&
|
||||||
|
// currentZoom > 2 // Extend current bounds only if the map is zoomed in
|
||||||
|
// ) {
|
||||||
|
// // There are other files on the map
|
||||||
|
// if (
|
||||||
|
// currentBounds.contains(targetMapBounds.bounds.getSouthEast()) &&
|
||||||
|
// currentBounds.contains(targetMapBounds.bounds.getNorthWest())
|
||||||
|
// ) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// targetMapBounds.bounds.extend(currentBounds.getSouthWest());
|
||||||
|
// targetMapBounds.bounds.extend(currentBounds.getNorthEast());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// map.current.fitBounds(targetMapBounds.bounds, { padding: 80, linear: true, easing: () => 1 });
|
||||||
|
// });
|
||||||
|
|
||||||
|
export class BoundsManager {
|
||||||
|
private _bounds: mapboxgl.LngLatBounds = new mapboxgl.LngLatBounds();
|
||||||
|
private _files: Set<string> = new Set();
|
||||||
|
private _fileStateCollectionObserver: GPXFileStateCollectionObserver | null = null;
|
||||||
|
private _unsubscribes: (() => void)[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
|
||||||
|
(newFiles) => {
|
||||||
|
if (page.url.hash.length == 0) {
|
||||||
|
this.fitBoundsOnLoad(Array.from(newFiles.keys()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(fileId) => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fitBoundsOnLoad(files: string[]) {
|
||||||
|
this.reset();
|
||||||
|
|
||||||
|
this._files = new Set(files);
|
||||||
|
this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
|
||||||
|
(newFiles) => {
|
||||||
|
newFiles.forEach((fileState, fileId) => {
|
||||||
|
if (this._files.has(fileId)) {
|
||||||
|
this._unsubscribes.push(
|
||||||
|
fileState.subscribe((state) => {
|
||||||
|
this.addBoundsFromFile(fileId, state);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(fileId) => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addBoundsFromFile(fileId: string, file: GPXFileWithStatistics | undefined) {
|
||||||
|
if (!file || !this._files.has(fileId)) return;
|
||||||
|
|
||||||
|
this._files.delete(fileId);
|
||||||
|
|
||||||
|
const bounds = file.statistics.getStatisticsFor(new ListFileItem(fileId)).global.bounds;
|
||||||
|
if (!this.validBounds(bounds)) return;
|
||||||
|
|
||||||
|
this._bounds.extend(bounds.southWest);
|
||||||
|
this._bounds.extend(bounds.northEast);
|
||||||
|
|
||||||
|
if (this._files.size === 0) {
|
||||||
|
this.finalizeFitBounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finalizeFitBounds() {
|
||||||
|
if (
|
||||||
|
this._bounds.getSouth() === 90 &&
|
||||||
|
this._bounds.getWest() === 180 &&
|
||||||
|
this._bounds.getNorth() === -90 &&
|
||||||
|
this._bounds.getEast() === -180
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._unsubscribes.push(
|
||||||
|
map.subscribe((map_) => {
|
||||||
|
if (!map_) return;
|
||||||
|
map_.fitBounds(this._bounds, { padding: 80, linear: true, easing: () => 1 });
|
||||||
|
this.reset();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
if (this._fileStateCollectionObserver) {
|
||||||
|
this._fileStateCollectionObserver.destroy();
|
||||||
|
}
|
||||||
|
this._unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||||
|
this._unsubscribes = [];
|
||||||
|
this._bounds = new mapboxgl.LngLatBounds([180, 90, -180, -90]);
|
||||||
|
}
|
||||||
|
|
||||||
|
centerMapOnSelection() {
|
||||||
|
let selected = get(selection).getSelected();
|
||||||
|
let bounds = new mapboxgl.LngLatBounds();
|
||||||
|
|
||||||
|
if (selected.find((item) => item instanceof ListWaypointItem)) {
|
||||||
|
selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
||||||
|
let file = fileStateCollection.getFile(fileId);
|
||||||
|
if (file) {
|
||||||
|
items.forEach((item) => {
|
||||||
|
if (item instanceof ListWaypointItem) {
|
||||||
|
let waypoint = file.wpt[item.getWaypointIndex()];
|
||||||
|
if (waypoint) {
|
||||||
|
bounds.extend([waypoint.getLongitude(), waypoint.getLatitude()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let selectionBounds = get(gpxStatistics).global.bounds;
|
||||||
|
bounds.setNorthEast(selectionBounds.northEast);
|
||||||
|
bounds.setSouthWest(selectionBounds.southWest);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(map)?.fitBounds(bounds, {
|
||||||
|
padding: 80,
|
||||||
|
easing: () => 1,
|
||||||
|
maxZoom: 15,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validBounds(bounds: { southWest: Coordinates; northEast: Coordinates }) {
|
||||||
|
return (
|
||||||
|
bounds.southWest.lat !== 90 ||
|
||||||
|
bounds.southWest.lon !== 180 ||
|
||||||
|
bounds.northEast.lat !== -90 ||
|
||||||
|
bounds.northEast.lon !== -180
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const boundsManager = new BoundsManager();
|
||||||
@@ -35,7 +35,8 @@ export class FileActionManager {
|
|||||||
this._files = new Map();
|
this._files = new Map();
|
||||||
this._fileSubscriptions = new Map();
|
this._fileSubscriptions = new Map();
|
||||||
this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
|
this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
|
||||||
(fileId, fileState) => {
|
(newFiles) => {
|
||||||
|
newFiles.forEach((fileState, fileId) => {
|
||||||
this._fileSubscriptions.set(
|
this._fileSubscriptions.set(
|
||||||
fileId,
|
fileId,
|
||||||
fileState.subscribe((fileWithStatistics) => {
|
fileState.subscribe((fileWithStatistics) => {
|
||||||
@@ -44,6 +45,7 @@ export class FileActionManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
(fileId) => {
|
(fileId) => {
|
||||||
let unsubscribe = this._fileSubscriptions.get(fileId);
|
let unsubscribe = this._fileSubscriptions.get(fileId);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { get } from 'svelte/store';
|
|||||||
import { settings } from '$lib/logic/settings';
|
import { settings } from '$lib/logic/settings';
|
||||||
import { getClosestLinePoint, getElevation } from '$lib/utils';
|
import { getClosestLinePoint, getElevation } from '$lib/utils';
|
||||||
import { gpxStatistics } from '$lib/logic/statistics';
|
import { gpxStatistics } from '$lib/logic/statistics';
|
||||||
|
import { boundsManager } from './bounds';
|
||||||
|
|
||||||
// Generate unique file ids, different from the ones in the database
|
// Generate unique file ids, different from the ones in the database
|
||||||
export function getFileIds(n: number) {
|
export function getFileIds(n: number) {
|
||||||
@@ -97,9 +98,8 @@ export async function loadFiles(list: FileList | File[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ids = fileActions.addMultiple(files);
|
let ids = fileActions.addMultiple(files);
|
||||||
|
|
||||||
// initTargetMapBounds(ids);
|
|
||||||
selection.selectFileWhenLoaded(ids[0]);
|
selection.selectFileWhenLoaded(ids[0]);
|
||||||
|
boundsManager.fitBoundsOnLoad(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadFile(file: File): Promise<GPXFile | null> {
|
export async function loadFile(file: File): Promise<GPXFile | null> {
|
||||||
|
|||||||
@@ -13,23 +13,12 @@ export class GPXFileState {
|
|||||||
|
|
||||||
constructor(db: Database, fileId: string) {
|
constructor(db: Database, fileId: string) {
|
||||||
this._file = writable(undefined);
|
this._file = writable(undefined);
|
||||||
let first = true;
|
|
||||||
|
|
||||||
this._subscription = liveQuery(() => db.files.get(fileId)).subscribe((value) => {
|
this._subscription = liveQuery(() => db.files.get(fileId)).subscribe((value) => {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
let file = new GPXFile(value);
|
let file = new GPXFile(value);
|
||||||
updateAnchorPoints(file);
|
updateAnchorPoints(file);
|
||||||
|
|
||||||
let statistics = new GPXStatisticsTree(file);
|
let statistics = new GPXStatisticsTree(file);
|
||||||
if (first) {
|
|
||||||
// Update the map bounds for new files
|
|
||||||
// updateTargetMapBounds(
|
|
||||||
// id,
|
|
||||||
// statistics.getStatisticsFor(new ListFileItem(id)).global.bounds
|
|
||||||
// );
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._file.set({ file, statistics });
|
this._file.set({ file, statistics });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -55,27 +44,12 @@ export class GPXFileState {
|
|||||||
|
|
||||||
// Observe the file ids in the database, and maintain a map of file states for the corresponding files
|
// Observe the file ids in the database, and maintain a map of file states for the corresponding files
|
||||||
export class GPXFileStateCollection {
|
export class GPXFileStateCollection {
|
||||||
private _db: Database;
|
|
||||||
private _files: Writable<Map<string, GPXFileState>>;
|
private _files: Writable<Map<string, GPXFileState>>;
|
||||||
|
|
||||||
constructor(db: Database) {
|
constructor(db: Database) {
|
||||||
this._db = db;
|
|
||||||
this._files = writable(new Map());
|
this._files = writable(new Map());
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(run: Subscriber<Map<string, GPXFileState>>, invalidate?: () => void) {
|
liveQuery(() => db.fileids.toArray()).subscribe((dbFileIds) => {
|
||||||
return this._files.subscribe(run, invalidate);
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(fitBounds: boolean) {
|
|
||||||
let initialize = true;
|
|
||||||
liveQuery(() => this._db.fileids.toArray()).subscribe((dbFileIds) => {
|
|
||||||
if (initialize) {
|
|
||||||
// if (fitBounds && dbFileIds.length > 0) {
|
|
||||||
// initTargetMapBounds(dbFileIds);
|
|
||||||
// }
|
|
||||||
initialize = false;
|
|
||||||
}
|
|
||||||
const currentFiles = get(this._files);
|
const currentFiles = get(this._files);
|
||||||
// Find new files to observe
|
// Find new files to observe
|
||||||
let newFiles = dbFileIds
|
let newFiles = dbFileIds
|
||||||
@@ -90,7 +64,7 @@ export class GPXFileStateCollection {
|
|||||||
// Update the map of file states
|
// Update the map of file states
|
||||||
this._files.update(($files) => {
|
this._files.update(($files) => {
|
||||||
newFiles.forEach((id) => {
|
newFiles.forEach((id) => {
|
||||||
$files.set(id, new GPXFileState(this._db, id));
|
$files.set(id, new GPXFileState(db, id));
|
||||||
});
|
});
|
||||||
deletedFiles.forEach((id) => {
|
deletedFiles.forEach((id) => {
|
||||||
$files.get(id)?.destroy();
|
$files.get(id)?.destroy();
|
||||||
@@ -111,6 +85,10 @@ export class GPXFileStateCollection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscribe(run: Subscriber<Map<string, GPXFileState>>, invalidate?: () => void) {
|
||||||
|
return this._files.subscribe(run, invalidate);
|
||||||
|
}
|
||||||
|
|
||||||
get size(): number {
|
get size(): number {
|
||||||
return get(this._files).size;
|
return get(this._files).size;
|
||||||
}
|
}
|
||||||
@@ -141,21 +119,21 @@ export class GPXFileStateCollection {
|
|||||||
// Collection of all file states
|
// Collection of all file states
|
||||||
export const fileStateCollection = new GPXFileStateCollection(db);
|
export const fileStateCollection = new GPXFileStateCollection(db);
|
||||||
|
|
||||||
export type GPXFileStateCallback = (fileId: string, fileState: GPXFileState) => void;
|
export type GPXFileStateCallback = (files: Map<string, GPXFileState>) => void;
|
||||||
export class GPXFileStateCollectionObserver {
|
export class GPXFileStateCollectionObserver {
|
||||||
private _fileIds: Set<string>;
|
private _fileIds: Set<string>;
|
||||||
private _onFileAdded: GPXFileStateCallback;
|
private _onFilesAdded: GPXFileStateCallback;
|
||||||
private _onFileRemoved: (fileId: string) => void;
|
private _onFileRemoved: (fileId: string) => void;
|
||||||
private _onDestroy: () => void;
|
private _onDestroy: () => void;
|
||||||
private _unsubscribe: () => void;
|
private _unsubscribe: () => void;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
onFileAdded: GPXFileStateCallback,
|
onFilesAdded: GPXFileStateCallback,
|
||||||
onFileRemoved: (fileId: string) => void,
|
onFileRemoved: (fileId: string) => void,
|
||||||
onDestroy: () => void
|
onDestroy: () => void
|
||||||
) {
|
) {
|
||||||
this._fileIds = new Set();
|
this._fileIds = new Set();
|
||||||
this._onFileAdded = onFileAdded;
|
this._onFilesAdded = onFilesAdded;
|
||||||
this._onFileRemoved = onFileRemoved;
|
this._onFileRemoved = onFileRemoved;
|
||||||
this._onDestroy = onDestroy;
|
this._onDestroy = onDestroy;
|
||||||
|
|
||||||
@@ -166,12 +144,16 @@ export class GPXFileStateCollectionObserver {
|
|||||||
this._fileIds.delete(fileId);
|
this._fileIds.delete(fileId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let newFiles = new Map<string, GPXFileState>();
|
||||||
files.forEach((file: GPXFileState, fileId: string) => {
|
files.forEach((file: GPXFileState, fileId: string) => {
|
||||||
if (!this._fileIds.has(fileId)) {
|
if (!this._fileIds.has(fileId)) {
|
||||||
this._onFileAdded(fileId, file);
|
newFiles.set(fileId, file);
|
||||||
this._fileIds.add(fileId);
|
this._fileIds.add(fileId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (newFiles.size > 0) {
|
||||||
|
this._onFilesAdded(newFiles);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -261,7 +261,8 @@ export class SelectedGPXFilesObserver {
|
|||||||
constructor(onSelectedFileChange: () => void) {
|
constructor(onSelectedFileChange: () => void) {
|
||||||
this._unsubscribes = new Map();
|
this._unsubscribes = new Map();
|
||||||
this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
|
this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
|
||||||
(fileId, fileState) => {
|
(newFiles) => {
|
||||||
|
newFiles.forEach((fileState, fileId) => {
|
||||||
this._unsubscribes.set(
|
this._unsubscribes.set(
|
||||||
fileId,
|
fileId,
|
||||||
fileState.subscribe(() => {
|
fileState.subscribe(() => {
|
||||||
@@ -270,6 +271,7 @@ export class SelectedGPXFilesObserver {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
(fileId) => {
|
(fileId) => {
|
||||||
this._unsubscribes.get(fileId)?.();
|
this._unsubscribes.get(fileId)?.();
|
||||||
|
|||||||
@@ -11,18 +11,15 @@
|
|||||||
// import CoordinatesPopup from '$lib/components/map/CoordinatesPopup.svelte';
|
// import CoordinatesPopup from '$lib/components/map/CoordinatesPopup.svelte';
|
||||||
import Resizer from '$lib/components/Resizer.svelte';
|
import Resizer from '$lib/components/Resizer.svelte';
|
||||||
import { Toaster } from '$lib/components/ui/sonner';
|
import { Toaster } from '$lib/components/ui/sonner';
|
||||||
// import { onMount } from 'svelte';
|
|
||||||
// import { page } from '$app/state';
|
|
||||||
import { languages } from '$lib/languages';
|
import { languages } from '$lib/languages';
|
||||||
import { getURLForLanguage } from '$lib/utils';
|
import { getURLForLanguage } from '$lib/utils';
|
||||||
// import { getURLForGoogleDriveFile } from '$lib/components/embedding/Embedding';
|
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
import { settings } from '$lib/logic/settings';
|
import { settings } from '$lib/logic/settings';
|
||||||
import { fileStateCollection } from '$lib/logic/file-state';
|
|
||||||
import { loadFiles } from '$lib/logic/file-actions';
|
import { loadFiles } from '$lib/logic/file-actions';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { gpxStatistics, slicedGPXStatistics } from '$lib/logic/statistics';
|
import { gpxStatistics, slicedGPXStatistics } from '$lib/logic/statistics';
|
||||||
|
import { getURLForGoogleDriveFile } from '$lib/components/embedding/Embedding';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treeFileView,
|
treeFileView,
|
||||||
@@ -36,9 +33,7 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
let files: string[] = JSON.parse(page.url.searchParams.get('files') || '[]');
|
let files: string[] = JSON.parse(page.url.searchParams.get('files') || '[]');
|
||||||
let ids: string[] = JSON.parse(page.url.searchParams.get('ids') || '[]');
|
let ids: string[] = JSON.parse(page.url.searchParams.get('ids') || '[]');
|
||||||
let urls: string[] = []; //files.concat(ids.map(getURLForGoogleDriveFile));
|
let urls: string[] = files.concat(ids.map(getURLForGoogleDriveFile));
|
||||||
|
|
||||||
fileStateCollection.initialize(urls.length === 0);
|
|
||||||
|
|
||||||
if (urls.length > 0) {
|
if (urls.length > 0) {
|
||||||
let downloads: Promise<File | null>[] = [];
|
let downloads: Promise<File | null>[] = [];
|
||||||
@@ -57,7 +52,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="fixed -z-10 text-transparent">
|
<div class="fixed mt-[100%] -z-10 text-transparent">
|
||||||
<h1>{i18n._('metadata.home_title')} — {i18n._('metadata.app_title')}</h1>
|
<h1>{i18n._('metadata.home_title')} — {i18n._('metadata.app_title')}</h1>
|
||||||
<p>{i18n._('metadata.description')}</p>
|
<p>{i18n._('metadata.description')}</p>
|
||||||
<h2>{i18n._('toolbar.routing.tooltip')}</h2>
|
<h2>{i18n._('toolbar.routing.tooltip')}</h2>
|
||||||
|
|||||||
Reference in New Issue
Block a user