mirror of
				https://github.com/gpxstudio/gpx.studio.git
				synced 2025-11-04 05:21:09 +00:00 
			
		
		
		
	bounds management
This commit is contained in:
		@@ -73,6 +73,7 @@
 | 
			
		||||
    import { fileActionManager } from '$lib/logic/file-action-manager';
 | 
			
		||||
    import { copied, selection } from '$lib/logic/selection';
 | 
			
		||||
    import { allHidden } from '$lib/logic/hidden';
 | 
			
		||||
    import { boundsManager } from '$lib/logic/bounds';
 | 
			
		||||
 | 
			
		||||
    const {
 | 
			
		||||
        distanceUnits,
 | 
			
		||||
@@ -281,7 +282,7 @@
 | 
			
		||||
                    {/if}
 | 
			
		||||
                    <Menubar.Separator />
 | 
			
		||||
                    <Menubar.Item
 | 
			
		||||
                        onclick={selection.selectAll}
 | 
			
		||||
                        onclick={() => selection.selectAll()}
 | 
			
		||||
                        disabled={fileStateCollection.size == 0}
 | 
			
		||||
                    >
 | 
			
		||||
                        <FileStack size="16" />
 | 
			
		||||
@@ -291,9 +292,10 @@
 | 
			
		||||
                    <Menubar.Item
 | 
			
		||||
                        onclick={() => {
 | 
			
		||||
                            if ($selection.size > 0) {
 | 
			
		||||
                                // centerMapOnSelection();
 | 
			
		||||
                                boundsManager.centerMapOnSelection();
 | 
			
		||||
                            }
 | 
			
		||||
                        }}
 | 
			
		||||
                        disabled={$selection.size == 0}
 | 
			
		||||
                    >
 | 
			
		||||
                        <Maximize size="16" />
 | 
			
		||||
                        {i18n._('menu.center')}
 | 
			
		||||
@@ -302,7 +304,7 @@
 | 
			
		||||
                    {#if $treeFileView}
 | 
			
		||||
                        <Menubar.Separator />
 | 
			
		||||
                        <Menubar.Item
 | 
			
		||||
                            onclick={selection.copySelection}
 | 
			
		||||
                            onclick={() => selection.copySelection()}
 | 
			
		||||
                            disabled={$selection.size === 0}
 | 
			
		||||
                        >
 | 
			
		||||
                            <ClipboardCopy size="16" />
 | 
			
		||||
@@ -310,7 +312,7 @@
 | 
			
		||||
                            <Shortcut key="C" ctrl={true} />
 | 
			
		||||
                        </Menubar.Item>
 | 
			
		||||
                        <Menubar.Item
 | 
			
		||||
                            onclick={selection.cutSelection}
 | 
			
		||||
                            onclick={() => selection.cutSelection()}
 | 
			
		||||
                            disabled={$selection.size === 0}
 | 
			
		||||
                        >
 | 
			
		||||
                            <Scissors size="16" />
 | 
			
		||||
@@ -375,7 +377,7 @@
 | 
			
		||||
                        />
 | 
			
		||||
                    </Menubar.CheckboxItem>
 | 
			
		||||
                    <Menubar.Separator />
 | 
			
		||||
                    <Menubar.Item inset onclick={map.toggle3D}>
 | 
			
		||||
                    <Menubar.Item inset onclick={() => map.toggle3D()}>
 | 
			
		||||
                        <Box size="16" />
 | 
			
		||||
                        {i18n._('menu.toggle_3d')}
 | 
			
		||||
                        <Shortcut key="{i18n._('menu.ctrl')} {i18n._('menu.drag')}" />
 | 
			
		||||
@@ -629,9 +631,9 @@
 | 
			
		||||
            }
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
        } else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
 | 
			
		||||
            // if ($selection.size > 0) {
 | 
			
		||||
            //     centerMapOnSelection();
 | 
			
		||||
            // }
 | 
			
		||||
            if ($selection.size > 0) {
 | 
			
		||||
                boundsManager.centerMapOnSelection();
 | 
			
		||||
            }
 | 
			
		||||
        } else if (e.key === 'F1') {
 | 
			
		||||
            switchBasemaps();
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@
 | 
			
		||||
    import { map } from '$lib/components/map/map';
 | 
			
		||||
    import { fileActions, pasteSelection } from '$lib/logic/file-actions';
 | 
			
		||||
    import { allHidden } from '$lib/logic/hidden';
 | 
			
		||||
    import { boundsManager } from '$lib/logic/bounds';
 | 
			
		||||
 | 
			
		||||
    let {
 | 
			
		||||
        node,
 | 
			
		||||
@@ -287,7 +288,7 @@
 | 
			
		||||
                <Shortcut key="A" ctrl={true} />
 | 
			
		||||
            </ContextMenu.Item>
 | 
			
		||||
        {/if}
 | 
			
		||||
        <ContextMenu.Item onclick={centerMapOnSelection}>
 | 
			
		||||
        <ContextMenu.Item onclick={() => boundsManager.centerMapOnSelection()}>
 | 
			
		||||
            <Maximize size="16" class="mr-1" />
 | 
			
		||||
            {i18n._('menu.center')}
 | 
			
		||||
            <Shortcut key="⏎" ctrl={true} />
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,11 @@ export class GPXLayerCollection {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
 | 
			
		||||
            (fileId, fileState) => {
 | 
			
		||||
                const layer = new GPXLayer(fileId, fileState);
 | 
			
		||||
                this._layers.set(fileId, layer);
 | 
			
		||||
            (newFiles) => {
 | 
			
		||||
                newFiles.forEach((fileState, fileId) => {
 | 
			
		||||
                    const layer = new GPXLayer(fileId, fileState);
 | 
			
		||||
                    this._layers.set(fileId, layer);
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
            (fileId) => {
 | 
			
		||||
                const layer = this._layers.get(fileId);
 | 
			
		||||
 
 | 
			
		||||
@@ -221,105 +221,3 @@ export class 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._simplified = new Map();
 | 
			
		||||
        this._fileStateCollectionOberver = new GPXFileStateCollectionObserver(
 | 
			
		||||
            (fileId, fileState) => {
 | 
			
		||||
                this._layers.set(fileId, new ReducedGPXLayer(fileState, this._updateSimplified));
 | 
			
		||||
            (newFiles) => {
 | 
			
		||||
                newFiles.forEach((fileState, fileId) => {
 | 
			
		||||
                    this._layers.set(
 | 
			
		||||
                        fileId,
 | 
			
		||||
                        new ReducedGPXLayer(fileState, this._updateSimplified)
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
            (fileId) => {
 | 
			
		||||
                this._layers.get(fileId)?.destroy();
 | 
			
		||||
 
 | 
			
		||||
@@ -84,11 +84,13 @@
 | 
			
		||||
    onMount(() => {
 | 
			
		||||
        if ($map && popup && popupElement) {
 | 
			
		||||
            fileStateCollectionObserver = new GPXFileStateCollectionObserver(
 | 
			
		||||
                (fileId, fileState) => {
 | 
			
		||||
                    routingControls.set(
 | 
			
		||||
                        fileId,
 | 
			
		||||
                        new RoutingControls(fileId, fileState, popup, popupElement)
 | 
			
		||||
                    );
 | 
			
		||||
                (newFiles) => {
 | 
			
		||||
                    newFiles.forEach((fileState, fileId) => {
 | 
			
		||||
                        routingControls.set(
 | 
			
		||||
                            fileId,
 | 
			
		||||
                            new RoutingControls(fileId, fileState, popup, popupElement)
 | 
			
		||||
                        );
 | 
			
		||||
                    });
 | 
			
		||||
                },
 | 
			
		||||
                (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,15 +35,17 @@ export class FileActionManager {
 | 
			
		||||
        this._files = new Map();
 | 
			
		||||
        this._fileSubscriptions = new Map();
 | 
			
		||||
        this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
 | 
			
		||||
            (fileId, fileState) => {
 | 
			
		||||
                this._fileSubscriptions.set(
 | 
			
		||||
                    fileId,
 | 
			
		||||
                    fileState.subscribe((fileWithStatistics) => {
 | 
			
		||||
                        if (fileWithStatistics) {
 | 
			
		||||
                            this._files.set(fileId, fileWithStatistics.file);
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                );
 | 
			
		||||
            (newFiles) => {
 | 
			
		||||
                newFiles.forEach((fileState, fileId) => {
 | 
			
		||||
                    this._fileSubscriptions.set(
 | 
			
		||||
                        fileId,
 | 
			
		||||
                        fileState.subscribe((fileWithStatistics) => {
 | 
			
		||||
                            if (fileWithStatistics) {
 | 
			
		||||
                                this._files.set(fileId, fileWithStatistics.file);
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
            (fileId) => {
 | 
			
		||||
                let unsubscribe = this._fileSubscriptions.get(fileId);
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ import { get } from 'svelte/store';
 | 
			
		||||
import { settings } from '$lib/logic/settings';
 | 
			
		||||
import { getClosestLinePoint, getElevation } from '$lib/utils';
 | 
			
		||||
import { gpxStatistics } from '$lib/logic/statistics';
 | 
			
		||||
import { boundsManager } from './bounds';
 | 
			
		||||
 | 
			
		||||
// Generate unique file ids, different from the ones in the database
 | 
			
		||||
export function getFileIds(n: number) {
 | 
			
		||||
@@ -97,9 +98,8 @@ export async function loadFiles(list: FileList | File[]) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let ids = fileActions.addMultiple(files);
 | 
			
		||||
 | 
			
		||||
    // initTargetMapBounds(ids);
 | 
			
		||||
    selection.selectFileWhenLoaded(ids[0]);
 | 
			
		||||
    boundsManager.fitBoundsOnLoad(ids);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function loadFile(file: File): Promise<GPXFile | null> {
 | 
			
		||||
 
 | 
			
		||||
@@ -13,23 +13,12 @@ export class GPXFileState {
 | 
			
		||||
 | 
			
		||||
    constructor(db: Database, fileId: string) {
 | 
			
		||||
        this._file = writable(undefined);
 | 
			
		||||
        let first = true;
 | 
			
		||||
 | 
			
		||||
        this._subscription = liveQuery(() => db.files.get(fileId)).subscribe((value) => {
 | 
			
		||||
            if (value !== undefined) {
 | 
			
		||||
                let file = new GPXFile(value);
 | 
			
		||||
                updateAnchorPoints(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 });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
@@ -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
 | 
			
		||||
export class GPXFileStateCollection {
 | 
			
		||||
    private _db: Database;
 | 
			
		||||
    private _files: Writable<Map<string, GPXFileState>>;
 | 
			
		||||
 | 
			
		||||
    constructor(db: Database) {
 | 
			
		||||
        this._db = db;
 | 
			
		||||
        this._files = writable(new Map());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    subscribe(run: Subscriber<Map<string, GPXFileState>>, invalidate?: () => void) {
 | 
			
		||||
        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;
 | 
			
		||||
            }
 | 
			
		||||
        liveQuery(() => db.fileids.toArray()).subscribe((dbFileIds) => {
 | 
			
		||||
            const currentFiles = get(this._files);
 | 
			
		||||
            // Find new files to observe
 | 
			
		||||
            let newFiles = dbFileIds
 | 
			
		||||
@@ -90,7 +64,7 @@ export class GPXFileStateCollection {
 | 
			
		||||
                // Update the map of file states
 | 
			
		||||
                this._files.update(($files) => {
 | 
			
		||||
                    newFiles.forEach((id) => {
 | 
			
		||||
                        $files.set(id, new GPXFileState(this._db, id));
 | 
			
		||||
                        $files.set(id, new GPXFileState(db, id));
 | 
			
		||||
                    });
 | 
			
		||||
                    deletedFiles.forEach((id) => {
 | 
			
		||||
                        $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 {
 | 
			
		||||
        return get(this._files).size;
 | 
			
		||||
    }
 | 
			
		||||
@@ -141,21 +119,21 @@ export class GPXFileStateCollection {
 | 
			
		||||
// Collection of all file states
 | 
			
		||||
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 {
 | 
			
		||||
    private _fileIds: Set<string>;
 | 
			
		||||
    private _onFileAdded: GPXFileStateCallback;
 | 
			
		||||
    private _onFilesAdded: GPXFileStateCallback;
 | 
			
		||||
    private _onFileRemoved: (fileId: string) => void;
 | 
			
		||||
    private _onDestroy: () => void;
 | 
			
		||||
    private _unsubscribe: () => void;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        onFileAdded: GPXFileStateCallback,
 | 
			
		||||
        onFilesAdded: GPXFileStateCallback,
 | 
			
		||||
        onFileRemoved: (fileId: string) => void,
 | 
			
		||||
        onDestroy: () => void
 | 
			
		||||
    ) {
 | 
			
		||||
        this._fileIds = new Set();
 | 
			
		||||
        this._onFileAdded = onFileAdded;
 | 
			
		||||
        this._onFilesAdded = onFilesAdded;
 | 
			
		||||
        this._onFileRemoved = onFileRemoved;
 | 
			
		||||
        this._onDestroy = onDestroy;
 | 
			
		||||
 | 
			
		||||
@@ -166,12 +144,16 @@ export class GPXFileStateCollectionObserver {
 | 
			
		||||
                    this._fileIds.delete(fileId);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            let newFiles = new Map<string, GPXFileState>();
 | 
			
		||||
            files.forEach((file: GPXFileState, fileId: string) => {
 | 
			
		||||
                if (!this._fileIds.has(fileId)) {
 | 
			
		||||
                    this._onFileAdded(fileId, file);
 | 
			
		||||
                    newFiles.set(fileId, file);
 | 
			
		||||
                    this._fileIds.add(fileId);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            if (newFiles.size > 0) {
 | 
			
		||||
                this._onFilesAdded(newFiles);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -261,15 +261,17 @@ export class SelectedGPXFilesObserver {
 | 
			
		||||
    constructor(onSelectedFileChange: () => void) {
 | 
			
		||||
        this._unsubscribes = new Map();
 | 
			
		||||
        this._fileStateCollectionObserver = new GPXFileStateCollectionObserver(
 | 
			
		||||
            (fileId, fileState) => {
 | 
			
		||||
                this._unsubscribes.set(
 | 
			
		||||
                    fileId,
 | 
			
		||||
                    fileState.subscribe(() => {
 | 
			
		||||
                        if (get(selection).hasAnyChildren(new ListFileItem(fileId))) {
 | 
			
		||||
                            onSelectedFileChange();
 | 
			
		||||
                        }
 | 
			
		||||
                    })
 | 
			
		||||
                );
 | 
			
		||||
            (newFiles) => {
 | 
			
		||||
                newFiles.forEach((fileState, fileId) => {
 | 
			
		||||
                    this._unsubscribes.set(
 | 
			
		||||
                        fileId,
 | 
			
		||||
                        fileState.subscribe(() => {
 | 
			
		||||
                            if (get(selection).hasAnyChildren(new ListFileItem(fileId))) {
 | 
			
		||||
                                onSelectedFileChange();
 | 
			
		||||
                            }
 | 
			
		||||
                        })
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
            (fileId) => {
 | 
			
		||||
                this._unsubscribes.get(fileId)?.();
 | 
			
		||||
 
 | 
			
		||||
@@ -11,18 +11,15 @@
 | 
			
		||||
    // import CoordinatesPopup from '$lib/components/map/CoordinatesPopup.svelte';
 | 
			
		||||
    import Resizer from '$lib/components/Resizer.svelte';
 | 
			
		||||
    import { Toaster } from '$lib/components/ui/sonner';
 | 
			
		||||
    // import { onMount } from 'svelte';
 | 
			
		||||
    // import { page } from '$app/state';
 | 
			
		||||
    import { languages } from '$lib/languages';
 | 
			
		||||
    import { getURLForLanguage } from '$lib/utils';
 | 
			
		||||
    // import { getURLForGoogleDriveFile } from '$lib/components/embedding/Embedding';
 | 
			
		||||
    import { i18n } from '$lib/i18n.svelte';
 | 
			
		||||
    import { settings } from '$lib/logic/settings';
 | 
			
		||||
    import { fileStateCollection } from '$lib/logic/file-state';
 | 
			
		||||
    import { loadFiles } from '$lib/logic/file-actions';
 | 
			
		||||
    import { onMount } from 'svelte';
 | 
			
		||||
    import { page } from '$app/state';
 | 
			
		||||
    import { gpxStatistics, slicedGPXStatistics } from '$lib/logic/statistics';
 | 
			
		||||
    import { getURLForGoogleDriveFile } from '$lib/components/embedding/Embedding';
 | 
			
		||||
 | 
			
		||||
    const {
 | 
			
		||||
        treeFileView,
 | 
			
		||||
@@ -36,9 +33,7 @@
 | 
			
		||||
    onMount(() => {
 | 
			
		||||
        let files: string[] = JSON.parse(page.url.searchParams.get('files') || '[]');
 | 
			
		||||
        let ids: string[] = JSON.parse(page.url.searchParams.get('ids') || '[]');
 | 
			
		||||
        let urls: string[] = []; //files.concat(ids.map(getURLForGoogleDriveFile));
 | 
			
		||||
 | 
			
		||||
        fileStateCollection.initialize(urls.length === 0);
 | 
			
		||||
        let urls: string[] = files.concat(ids.map(getURLForGoogleDriveFile));
 | 
			
		||||
 | 
			
		||||
        if (urls.length > 0) {
 | 
			
		||||
            let downloads: Promise<File | null>[] = [];
 | 
			
		||||
@@ -57,7 +52,7 @@
 | 
			
		||||
    });
 | 
			
		||||
</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>
 | 
			
		||||
    <p>{i18n._('metadata.description')}</p>
 | 
			
		||||
    <h2>{i18n._('toolbar.routing.tooltip')}</h2>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user