From a73da0d81df78d3ee421b893e881fd249c1c176c Mon Sep 17 00:00:00 2001 From: vcoppe Date: Fri, 17 Oct 2025 23:54:45 +0200 Subject: [PATCH] progress --- package-lock.json | 6 + website/src/lib/components/Menu.svelte | 121 +++--- website/src/lib/components/WithUnits.svelte | 2 +- .../lib/components/embedding/Embedding.svelte | 6 +- .../src/lib/components/export/Export.svelte | 10 +- .../src/lib/components/export/utils.svelte.ts | 11 +- .../lib/components/file-list/FileList.svelte | 64 ++-- .../components/file-list/FileListNode.svelte | 37 +- .../file-list/FileListNodeContent.svelte | 52 +-- .../file-list/FileListNodeLabel.svelte | 79 ++-- .../file-list/FileListNodeStore.svelte | 2 +- .../src/lib/components/file-list/file-list.ts | 6 - website/src/lib/components/map/Map.svelte | 19 +- .../src/lib/components/map/MapPopup.svelte | 28 +- .../map/custom-control/CustomControl.svelte | 2 +- .../map/gpx-layer/CopyCoordinates.svelte | 2 +- .../map/gpx-layer/DistanceMarkers.ts | 5 +- .../components/map/gpx-layer/GPXLayers.svelte | 83 ++--- .../map/gpx-layer/TrackpointPopup.svelte | 2 +- .../map/gpx-layer/WaypointPopup.svelte | 14 +- .../{GPXLayerPopup.ts => gpx-layer-popup.ts} | 7 +- .../gpx-layer/{GPXLayer.ts => gpx-layer.ts} | 134 +++---- .../components/map/gpx-layer/gpx-layers.ts | 38 ++ .../map/layer-control/CustomLayers.svelte | 24 +- .../map/layer-control/LayerControl.svelte | 88 +++-- .../layer-control/LayerControlSettings.svelte | 51 +-- .../map/layer-control/LayerTreeNode.svelte | 8 +- .../map/layer-control/OverpassLayer.ts | 15 +- .../map/layer-control/OverpassPopup.svelte | 15 +- .../{utils.svelte.ts => utils.ts} | 5 +- website/src/lib/components/map/map-popup.ts | 84 +++++ .../map/{utils.svelte.ts => map.ts} | 205 ++++------- .../StreetViewControl.svelte | 14 +- .../map/street-view-control/utils.svelte.ts | 3 - .../map/street-view-control/utils.ts | 3 + .../src/lib/components/toolbar/Toolbar.svelte | 2 +- .../lib/components/toolbar/ToolbarItem.svelte | 2 +- .../components/toolbar/ToolbarItemMenu.svelte | 4 +- .../toolbar/{utils.svelte.ts => tools.ts} | 8 +- .../lib/components/toolbar/tools/Clean.svelte | 8 +- .../components/toolbar/tools/Elevation.svelte | 6 +- .../components/toolbar/tools/Extract.svelte | 6 +- .../lib/components/toolbar/tools/Merge.svelte | 8 +- .../components/toolbar/tools/Reduce.svelte | 6 +- .../lib/components/toolbar/tools/Time.svelte | 8 +- .../toolbar/tools/routing/Routing.svelte | 20 +- .../toolbar/tools/routing/utils.svelte.ts | 2 +- .../toolbar/tools/scissors/Scissors.svelte | 6 +- .../toolbar/tools/scissors/scissors.ts | 9 + .../toolbar/tools/scissors/split-controls.ts | 8 +- .../toolbar/tools/scissors/utils.svelte.ts | 9 - .../toolbar/tools/waypoint/Waypoint.svelte | 4 +- .../waypoint/{utils.svelte.ts => waypoint.ts} | 33 +- ...nager.svelte.ts => file-action-manager.ts} | 156 +++++--- ...file-actions.svelte.ts => file-actions.ts} | 25 +- .../{file-state.svelte.ts => file-state.ts} | 108 ++++-- website/src/lib/logic/selection-tree.ts | 140 +++++++ website/src/lib/logic/selection.svelte.ts | 228 ------------ website/src/lib/logic/selection.ts | 345 ++++++++++++------ .../logic/{settings.svelte.ts => settings.ts} | 49 ++- website/src/lib/units.ts | 25 +- .../src/routes/[[language]]/app/+page.svelte | 35 +- 62 files changed, 1343 insertions(+), 1162 deletions(-) create mode 100644 package-lock.json rename website/src/lib/components/map/gpx-layer/{GPXLayerPopup.ts => gpx-layer-popup.ts} (78%) rename website/src/lib/components/map/gpx-layer/{GPXLayer.ts => gpx-layer.ts} (82%) create mode 100644 website/src/lib/components/map/gpx-layer/gpx-layers.ts rename website/src/lib/components/map/layer-control/{utils.svelte.ts => utils.ts} (94%) create mode 100644 website/src/lib/components/map/map-popup.ts rename website/src/lib/components/map/{utils.svelte.ts => map.ts} (67%) delete mode 100644 website/src/lib/components/map/street-view-control/utils.svelte.ts create mode 100644 website/src/lib/components/map/street-view-control/utils.ts rename website/src/lib/components/toolbar/{utils.svelte.ts => tools.ts} (52%) create mode 100644 website/src/lib/components/toolbar/tools/scissors/scissors.ts delete mode 100644 website/src/lib/components/toolbar/tools/scissors/utils.svelte.ts rename website/src/lib/components/toolbar/tools/waypoint/{utils.svelte.ts => waypoint.ts} (70%) rename website/src/lib/logic/{file-action-manager.svelte.ts => file-action-manager.ts} (59%) rename website/src/lib/logic/{file-actions.svelte.ts => file-actions.ts} (98%) rename website/src/lib/logic/{file-state.svelte.ts => file-state.ts} (50%) create mode 100644 website/src/lib/logic/selection-tree.ts delete mode 100644 website/src/lib/logic/selection.svelte.ts rename website/src/lib/logic/{settings.svelte.ts => settings.ts} (73%) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..fb42afd3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "gpx.studio", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/website/src/lib/components/Menu.svelte b/website/src/lib/components/Menu.svelte index 65d824c5..735487a4 100644 --- a/website/src/lib/components/Menu.svelte +++ b/website/src/lib/components/Menu.svelte @@ -44,21 +44,13 @@ ChartArea, Maximize, } from '@lucide/svelte'; - import { map } from '$lib/components/map/utils.svelte'; + import { map } from '$lib/components/map/map'; import { editMetadata } from '$lib/components/file-list/metadata/utils.svelte'; import { editStyle } from '$lib/components/file-list/style/utils.svelte'; import { exportState, ExportState } from '$lib/components/export/utils.svelte'; - // import { - // triggerFileInput, - // createFile, - // loadFiles, - // updateSelectionFromKey, - // allHidden, - // } from '$lib/stores'; - // import { canUndo, canRedo, fileActions, fileObservers, settings } from '$lib/db'; - import { anySelectedLayer } from '$lib/components/map/layer-control/utils.svelte'; + import { anySelectedLayer } from '$lib/components/map/layer-control/utils'; import { defaultOverlays } from '$lib/assets/layers'; - // import LayerControlSettings from '$lib/components/map/layer-control/LayerControlSettings.svelte'; + import LayerControlSettings from '$lib/components/map/layer-control/LayerControlSettings.svelte'; import { allowedPastes, ListFileItem, @@ -69,17 +61,17 @@ import { i18n } from '$lib/i18n.svelte'; import { languages } from '$lib/languages'; import { getURLForLanguage } from '$lib/utils'; - import { settings } from '$lib/logic/settings.svelte'; + import { settings } from '$lib/logic/settings'; import { createFile, fileActions, loadFiles, pasteSelection, triggerFileInput, - } from '$lib/logic/file-actions.svelte'; - import { fileStateCollection } from '$lib/logic/file-state.svelte'; - import { fileActionManager } from '$lib/logic/file-action-manager.svelte'; - import { selection } from '$lib/logic/selection.svelte'; + } from '$lib/logic/file-actions'; + import { fileStateCollection } from '$lib/logic/file-state'; + import { fileActionManager } from '$lib/logic/file-action-manager'; + import { selection } from '$lib/logic/selection'; const { distanceUnits, @@ -98,19 +90,14 @@ } = settings; function switchBasemaps() { - [currentBasemap.value, previousBasemap.value] = [ - previousBasemap.value, - currentBasemap.value, - ]; + [$currentBasemap, $previousBasemap] = [$previousBasemap, $currentBasemap]; } function toggleOverlays() { - if (currentOverlays.value && anySelectedLayer(currentOverlays.value)) { - previousOverlays.value = JSON.parse(JSON.stringify(currentOverlays.value)); - currentOverlays.value = defaultOverlays; + if ($currentOverlays && anySelectedLayer($currentOverlays)) { + [$currentOverlays, $previousOverlays] = [defaultOverlays, $currentOverlays]; } else { - currentOverlays.value = JSON.parse(JSON.stringify(previousOverlays.value)); - previousOverlays.value = defaultOverlays; + [$currentOverlays, $previousOverlays] = [$previousOverlays, defaultOverlays]; } } @@ -146,7 +133,7 @@ {i18n._('menu.duplicate')} @@ -155,7 +142,7 @@ {i18n._('menu.close')} @@ -172,7 +159,7 @@ (exportState.current = ExportState.SELECTION)} - disabled={selection.value.size == 0} + disabled={$selection.size == 0} > {i18n._('menu.export')} @@ -195,7 +182,7 @@ fileActionManager.undo()} disabled={!fileActionManager.canUndo} > @@ -203,7 +190,7 @@ fileActionManager.redo()} disabled={!fileActionManager.canRedo} > @@ -212,8 +199,8 @@ @@ -227,8 +214,8 @@ @@ -248,7 +235,7 @@ // fileActions.setHiddenToSelection(true); // } }} - disabled={selection.value.size == 0} + disabled={$selection.size == 0} > - {#if treeFileView.value} - {#if selection.value - .getSelected() - .some((item) => item instanceof ListFileItem)} + {#if $treeFileView} + {#if $selection.getSelected().some((item) => item instanceof ListFileItem)} fileActions.addNewTrack( - selection.value.getSelected()[0].getFileId() + $selection.getSelected()[0].getFileId() )} - disabled={selection.value.size !== 1} + disabled={$selection.size !== 1} > {i18n._('menu.new_track')} - {:else if selection.value + {:else if $selection .getSelected() .some((item) => item instanceof ListTrackItem)} { - let item = selection.value.getSelected()[0]; + let item = $selection.getSelected()[0]; fileActions.addNewSegment( item.getFileId(), item.getTrackIndex() ); }} - disabled={selection.value.size !== 1} + disabled={$selection.size !== 1} > {i18n._('menu.new_segment')} @@ -304,7 +289,7 @@ { - if (selection.value.size > 0) { + if ($selection.size > 0) { // centerMapOnSelection(); } }} @@ -313,11 +298,11 @@ {i18n._('menu.center')} - {#if treeFileView.value} + {#if $treeFileView} {i18n._('menu.copy')} @@ -325,7 +310,7 @@ {i18n._('menu.cut')} @@ -334,9 +319,9 @@ 0 && + ($selection.size > 0 && !allowedPastes[selection.copied[0].level].includes( - selection.value.getSelected().pop()?.level + $selection.getSelected().pop()?.level ))} onclick={pasteSelection} > @@ -348,7 +333,7 @@ {i18n._('menu.delete')} @@ -362,12 +347,12 @@ - + {i18n._('menu.elevation_profile')} - + {i18n._('menu.tree_file_view')} @@ -384,12 +369,12 @@ /> - + {i18n._('menu.distance_markers')} - + {i18n._( 'menu.direction_markers' )} @@ -415,7 +400,7 @@ {i18n._('menu.distance_units')} - + {i18n._('menu.metric')} @@ -433,7 +418,7 @@ {i18n._('menu.velocity_units')} - + {i18n._('quantities.speed')} @@ -448,7 +433,7 @@ {i18n._('menu.temperature_units')} - + {i18n._('menu.celsius')} @@ -506,7 +491,7 @@ {i18n._('menu.street_view_source')} - + {i18n._('menu.mapillary')} @@ -554,7 +539,7 @@ - + { @@ -598,7 +583,7 @@ if (fileStateCollection.size > 0) { exportState.current = ExportState.ALL; } - } else if (selection.value.size > 0) { + } else if ($selection.size > 0) { exportState.current = ExportState.SELECTION; } e.preventDefault(); @@ -625,8 +610,8 @@ } } else if (e.key === 'i' && (e.metaKey || e.ctrlKey)) { if ( - selection.value.size === 1 && - selection.value + $selection.size === 1 && + $selection .getSelected() .every((item) => item instanceof ListFileItem || item instanceof ListTrackItem) ) { @@ -634,10 +619,10 @@ } e.preventDefault(); } else if (e.key === 'p' && (e.metaKey || e.ctrlKey)) { - elevationProfile.value = !elevationProfile.value; + $elevationProfile = !$elevationProfile; e.preventDefault(); } else if (e.key === 'l' && (e.metaKey || e.ctrlKey)) { - treeFileView.value = !treeFileView.value; + $treeFileView = !$treeFileView; e.preventDefault(); } else if (e.key === 'h' && (e.metaKey || e.ctrlKey)) { // if ($allHidden) { @@ -657,13 +642,13 @@ toggleOverlays(); e.preventDefault(); } else if (e.key === 'F3') { - distanceMarkers.value = !distanceMarkers.value; + $distanceMarkers = !$distanceMarkers; e.preventDefault(); } else if (e.key === 'F4') { - directionMarkers.value = !directionMarkers.value; + $directionMarkers = !$directionMarkers; e.preventDefault(); } else if (e.key === 'F5') { - routing.value = !routing.value; + $routing = !$routing; e.preventDefault(); } else if ( e.key === 'ArrowRight' || diff --git a/website/src/lib/components/WithUnits.svelte b/website/src/lib/components/WithUnits.svelte index d9818244..a140440b 100644 --- a/website/src/lib/components/WithUnits.svelte +++ b/website/src/lib/components/WithUnits.svelte @@ -1,5 +1,4 @@ @@ -125,7 +125,7 @@ }} > - {#if fileStateCollection.files.size === 1 || (exportState.current === ExportState.SELECTION && selection.value.size === 1)} + {#if $fileStateCollection.size === 1 || (exportState.current === ExportState.SELECTION && $selection.size === 1)} {i18n._('menu.download_file')} {:else} {i18n._('menu.download_files')} diff --git a/website/src/lib/components/export/utils.svelte.ts b/website/src/lib/components/export/utils.svelte.ts index 734ad754..fd362e90 100644 --- a/website/src/lib/components/export/utils.svelte.ts +++ b/website/src/lib/components/export/utils.svelte.ts @@ -1,9 +1,10 @@ -import { applyToOrderedSelectedItemsFromFile } from '$lib/logic/selection.svelte'; -import { fileStateCollection } from '$lib/logic/file-state.svelte'; -import { settings } from '$lib/logic/settings.svelte'; +import { selection } from '$lib/logic/selection'; +import { fileStateCollection } from '$lib/logic/file-state'; +import { settings } from '$lib/logic/settings'; import { buildGPX, type GPXFile } from 'gpx'; import FileSaver from 'file-saver'; import JSZip from 'jszip'; +import { get } from 'svelte/store'; export enum ExportState { NONE, @@ -30,14 +31,14 @@ async function exportFiles(fileIds: string[], exclude: string[]) { export async function exportSelectedFiles(exclude: string[]) { const fileIds: string[] = []; - applyToOrderedSelectedItemsFromFile(async (fileId, level, items) => { + selection.applyToOrderedSelectedItemsFromFile(async (fileId, level, items) => { fileIds.push(fileId); }); await exportFiles(fileIds, exclude); } export async function exportAllFiles(exclude: string[]) { - await exportFiles(settings.fileOrder.value, exclude); + await exportFiles(get(settings.fileOrder), exclude); } function exportFile(file: GPXFile, exclude: string[]) { diff --git a/website/src/lib/components/file-list/FileList.svelte b/website/src/lib/components/file-list/FileList.svelte index 19ee85cc..f93bb6b9 100644 --- a/website/src/lib/components/file-list/FileList.svelte +++ b/website/src/lib/components/file-list/FileList.svelte @@ -2,14 +2,15 @@ import { ScrollArea } from '$lib/components/ui/scroll-area/index'; import * as ContextMenu from '$lib/components/ui/context-menu'; import FileListNode from './FileListNode.svelte'; - import { fileObservers, settings } from '$lib/db'; import { setContext } from 'svelte'; import { ListFileItem, ListLevel, ListRootItem, allowedPastes } from './file-list'; - import { copied, pasteSelection, selectAll, selection } from './Selection'; import { ClipboardPaste, FileStack, Plus } from '@lucide/svelte'; import Shortcut from '$lib/components/Shortcut.svelte'; import { i18n } from '$lib/i18n.svelte'; - import { createFile } from '$lib/stores'; + import { settings } from '$lib/logic/settings'; + import { fileStateCollection } from '$lib/logic/file-state'; + import { createFile, pasteSelection } from '$lib/logic/file-actions'; + import { selection } from '$lib/logic/selection'; let { orientation, @@ -28,28 +29,28 @@ const { treeFileView } = settings; - treeFileView.subscribe(($vertical) => { - if ($vertical) { - selection.update(($selection) => { - $selection.forEach((item) => { - if ($selection.hasAnyChildren(item, false)) { - $selection.toggle(item); - } - }); - return $selection; - }); - } else { - selection.update(($selection) => { - $selection.forEach((item) => { - if (!(item instanceof ListFileItem)) { - $selection.toggle(item); - $selection.set(new ListFileItem(item.getFileId()), true); - } - }); - return $selection; - }); - } - }); + // treeFileView.subscribe(($vertical) => { + // if ($vertical) { + // selection.update(($selection) => { + // $selection.forEach((item) => { + // if ($selection.hasAnyChildren(item, false)) { + // $selection.toggle(item); + // } + // }); + // return $selection; + // }); + // } else { + // selection.update(($selection) => { + // $selection.forEach((item) => { + // if (!(item instanceof ListFileItem)) { + // $selection.toggle(item); + // $selection.set(new ListFileItem(item.getFileId()), true); + // } + // }); + // return $selection; + // }); + // } + // }); - + {#if orientation === 'vertical'} @@ -75,16 +76,19 @@ - + selection.selectAll()} + disabled={$fileStateCollection.size === 0} + > {i18n._('menu.select_all')} diff --git a/website/src/lib/components/file-list/FileListNode.svelte b/website/src/lib/components/file-list/FileListNode.svelte index f04605f6..b0dddd97 100644 --- a/website/src/lib/components/file-list/FileListNode.svelte +++ b/website/src/lib/components/file-list/FileListNode.svelte @@ -8,11 +8,10 @@ type GPXTreeElement, } from 'gpx'; import { CollapsibleTreeNode } from '$lib/components/collapsible-tree/index'; - import { settings, type GPXFileWithStatistics } from '$lib/db'; - import { get, type Readable } from 'svelte/store'; + import { type Readable } from 'svelte/store'; import FileListNodeContent from './FileListNodeContent.svelte'; import FileListNodeLabel from './FileListNodeLabel.svelte'; - import { afterUpdate, getContext } from 'svelte'; + import { getContext } from 'svelte'; import { ListFileItem, ListTrackSegmentItem, @@ -22,20 +21,27 @@ type ListTrackItem, } from './file-list'; import { i18n } from '$lib/i18n.svelte'; - import { selection } from './Selection'; + import { settings } from '$lib/logic/settings'; + import type { GPXFileWithStatistics } from '$lib/logic/statistics'; + import { selection } from '$lib/logic/selection'; - export let node: - | Map> - | GPXTreeElement - | Waypoint[] - | Waypoint; - export let item: ListItem; + let { + node, + item, + }: { + node: + | Map> + | GPXTreeElement + | Waypoint[] + | Waypoint; + item: ListItem; + } = $props(); let recursive = getContext('recursive'); - let collapsible: CollapsibleTreeNode; + let collapsible: CollapsibleTreeNode | undefined = $state(); - $: label = + let label = $derived( node instanceof GPXFile && item instanceof ListFileItem ? node.metadata.name : node instanceof Track @@ -47,12 +53,13 @@ `${i18n._('gpx.waypoint')} ${(item as ListWaypointItem).waypointIndex + 1}`) : node instanceof GPXFile && item instanceof ListWaypointsItem ? i18n._('gpx.waypoints') - : ''; + : '' + ); const { treeFileView } = settings; function openIfSelectedChild() { - if (collapsible && get(treeFileView) && $selection.hasAnyChildren(item, false)) { + if (collapsible && treeFileView.value && $selection.hasAnyChildren(item, false)) { collapsible.openNode(); } } @@ -61,7 +68,7 @@ openIfSelectedChild(); } - afterUpdate(openIfSelectedChild); + // afterUpdate(openIfSelectedChild); {#if node instanceof Map} diff --git a/website/src/lib/components/file-list/FileListNodeContent.svelte b/website/src/lib/components/file-list/FileListNodeContent.svelte index aaa11acf..dd2742d8 100644 --- a/website/src/lib/components/file-list/FileListNodeContent.svelte +++ b/website/src/lib/components/file-list/FileListNodeContent.svelte @@ -6,9 +6,8 @@
diff --git a/website/src/lib/components/map/MapPopup.svelte b/website/src/lib/components/map/MapPopup.svelte index 007e9d43..ae7caaa8 100644 --- a/website/src/lib/components/map/MapPopup.svelte +++ b/website/src/lib/components/map/MapPopup.svelte @@ -3,20 +3,32 @@ import WaypointPopup from '$lib/components/map/gpx-layer/WaypointPopup.svelte'; import TrackpointPopup from '$lib/components/map/gpx-layer/TrackpointPopup.svelte'; import OverpassPopup from '$lib/components/map/layer-control/OverpassPopup.svelte'; - import type { PopupItem } from '$lib/components/map/map.svelte'; + import type { PopupItem } from '$lib/components/map/map-popup'; + import type { Writable } from 'svelte/store'; - let { item, container = null }: { item: PopupItem | null; container: HTMLDivElement | null } = + let { + item, + onContainerReady, + }: { item: Writable; onContainerReady: (div: HTMLDivElement) => void } = $props(); + + let container: HTMLDivElement | null = $state(null); + + $effect(() => { + if (container) { + onContainerReady(container); + } + });
- {#if item} - {#if item.item instanceof Waypoint} - - {:else if item.item instanceof TrackPoint} - + {#if $item} + {#if $item.item instanceof Waypoint} + + {:else if $item.item instanceof TrackPoint} + {:else} - + {/if} {/if}
diff --git a/website/src/lib/components/map/custom-control/CustomControl.svelte b/website/src/lib/components/map/custom-control/CustomControl.svelte index be97f03a..7e4f827b 100644 --- a/website/src/lib/components/map/custom-control/CustomControl.svelte +++ b/website/src/lib/components/map/custom-control/CustomControl.svelte @@ -1,6 +1,6 @@ diff --git a/website/src/lib/components/map/gpx-layer/DistanceMarkers.ts b/website/src/lib/components/map/gpx-layer/DistanceMarkers.ts index 34949603..40219c59 100644 --- a/website/src/lib/components/map/gpx-layer/DistanceMarkers.ts +++ b/website/src/lib/components/map/gpx-layer/DistanceMarkers.ts @@ -1,9 +1,8 @@ -import { settings } from '$lib/db'; -import { gpxStatistics } from '$lib/stores'; +import { settings } from '$lib/logic/settings'; import type { GeoJSONSource } from 'mapbox-gl'; import { get } from 'svelte/store'; -// const { distanceMarkers, distanceUnits } = settings; +const { distanceMarkers, distanceUnits } = settings; const stops = [ [100, 0], diff --git a/website/src/lib/components/map/gpx-layer/GPXLayers.svelte b/website/src/lib/components/map/gpx-layer/GPXLayers.svelte index 9e86d2fa..3ae77206 100644 --- a/website/src/lib/components/map/gpx-layer/GPXLayers.svelte +++ b/website/src/lib/components/map/gpx-layer/GPXLayers.svelte @@ -1,56 +1,41 @@ diff --git a/website/src/lib/components/map/gpx-layer/TrackpointPopup.svelte b/website/src/lib/components/map/gpx-layer/TrackpointPopup.svelte index 235a4143..9256b98b 100644 --- a/website/src/lib/components/map/gpx-layer/TrackpointPopup.svelte +++ b/website/src/lib/components/map/gpx-layer/TrackpointPopup.svelte @@ -1,11 +1,11 @@ diff --git a/website/src/lib/components/map/gpx-layer/WaypointPopup.svelte b/website/src/lib/components/map/gpx-layer/WaypointPopup.svelte index 330c6755..9a05bbda 100644 --- a/website/src/lib/components/map/gpx-layer/WaypointPopup.svelte +++ b/website/src/lib/components/map/gpx-layer/WaypointPopup.svelte @@ -3,16 +3,16 @@ import { Button } from '$lib/components/ui/button'; import Shortcut from '$lib/components/Shortcut.svelte'; import CopyCoordinates from '$lib/components/map/gpx-layer/CopyCoordinates.svelte'; - import { deleteWaypoint } from './GPXLayerPopup'; import WithUnits from '$lib/components/WithUnits.svelte'; import { Dot, ExternalLink, Trash2 } from '@lucide/svelte'; - import { tool, Tool } from '$lib/components/toolbar/utils.svelte'; + import { currentTool, Tool } from '$lib/components/toolbar/tools'; import { getSymbolKey, symbols } from '$lib/assets/symbols'; import { i18n } from '$lib/i18n.svelte'; import sanitizeHtml from 'sanitize-html'; import type { Waypoint } from 'gpx'; - import type { PopupItem } from '$lib/components/map/map.svelte'; import { ScrollArea } from '$lib/components/ui/scroll-area/index.js'; + import type { PopupItem } from '$lib/components/map/map'; + import { fileActions } from '$lib/logic/file-actions'; export let waypoint: PopupItem; @@ -80,11 +80,15 @@
- {#if tool.current === Tool.WAYPOINT} + {#if $currentTool === Tool.WAYPOINT} -
@@ -338,10 +338,10 @@
{$customLayers[id].name} - -
@@ -373,7 +373,7 @@ /> {#if tileUrls.length > 1} -
{:else} - diff --git a/website/src/lib/components/map/layer-control/LayerControl.svelte b/website/src/lib/components/map/layer-control/LayerControl.svelte index fb2cb581..2e27ee06 100644 --- a/website/src/lib/components/map/layer-control/LayerControl.svelte +++ b/website/src/lib/components/map/layer-control/LayerControl.svelte @@ -1,18 +1,18 @@ @@ -164,11 +168,12 @@ onValueChange={(value) => { if (selectedOverlay) { if ( - map.current && + $map && + $currentOverlays && isSelected($currentOverlays, selectedOverlay) ) { try { - map.current.removeImport(selectedOverlay); + $map.removeImport(selectedOverlay); } catch (e) { // No reliable way to check if the map is ready to remove sources and layers } diff --git a/website/src/lib/components/map/layer-control/LayerTreeNode.svelte b/website/src/lib/components/map/layer-control/LayerTreeNode.svelte index bd8230c6..6ca8adb9 100644 --- a/website/src/lib/components/map/layer-control/LayerTreeNode.svelte +++ b/website/src/lib/components/map/layer-control/LayerTreeNode.svelte @@ -4,9 +4,9 @@ import { Checkbox } from '$lib/components/ui/checkbox'; import CollapsibleTreeNode from '$lib/components/collapsible-tree/CollapsibleTreeNode.svelte'; import { type LayerTreeType } from '$lib/assets/layers'; - import { anySelectedLayer } from './utils.svelte'; + import { anySelectedLayer } from './utils'; import { i18n } from '$lib/i18n.svelte'; - import { settings } from '$lib/logic/settings.svelte'; + import { settings } from '$lib/logic/settings'; let { name, @@ -70,8 +70,8 @@ /> {/if}
- diff --git a/website/src/lib/components/map/layer-control/utils.svelte.ts b/website/src/lib/components/map/layer-control/utils.ts similarity index 94% rename from website/src/lib/components/map/layer-control/utils.svelte.ts rename to website/src/lib/components/map/layer-control/utils.ts index cd53896c..3cd8cab7 100644 --- a/website/src/lib/components/map/layer-control/utils.svelte.ts +++ b/website/src/lib/components/map/layer-control/utils.ts @@ -1,4 +1,5 @@ import type { LayerTreeType } from '$lib/assets/layers'; +import { writable } from 'svelte/store'; export function anySelectedLayer(node: LayerTreeType) { return ( @@ -54,6 +55,4 @@ export function toggle(node: LayerTreeType, id: string) { return node; } -export const customBasemapUpdate = $state({ - value: 0, -}); +export const customBasemapUpdate = writable(0); diff --git a/website/src/lib/components/map/map-popup.ts b/website/src/lib/components/map/map-popup.ts new file mode 100644 index 00000000..ac156c8d --- /dev/null +++ b/website/src/lib/components/map/map-popup.ts @@ -0,0 +1,84 @@ +import { TrackPoint, Waypoint } from 'gpx'; +import mapboxgl from 'mapbox-gl'; +import { mount, tick } from 'svelte'; +import { get, writable, type Writable } from 'svelte/store'; +import MapPopupComponent from '$lib/components/map/MapPopup.svelte'; + +export type PopupItem = { + item: T; + fileId?: string; + hide?: () => void; +}; + +export class MapPopup { + map: mapboxgl.Map; + popup: mapboxgl.Popup; + item: Writable = writable(null); + component: ReturnType; + maybeHideBinded = this.maybeHide.bind(this); + + constructor(map: mapboxgl.Map, options?: mapboxgl.PopupOptions) { + this.map = map; + this.popup = new mapboxgl.Popup(options); + this.component = mount(MapPopupComponent, { + target: document.body, + props: { + item: this.item, + onContainerReady: (container: HTMLDivElement) => { + this.popup.setDOMContent(container); + }, + }, + }); + } + + setItem(item: PopupItem | null) { + if (item) item.hide = () => this.hide(); + this.item.set(item); + if (item === null) { + this.hide(); + } else { + tick().then(() => this.show()); + } + } + + show() { + const item = get(this.item); + if (item === null) { + this.hide(); + return; + } + this.popup.setLngLat(this.getCoordinates()).addTo(this.map); + this.map.on('mousemove', this.maybeHideBinded); + } + + maybeHide(e: mapboxgl.MapMouseEvent) { + const item = get(this.item); + if (item === null) { + this.hide(); + return; + } + if (this.map.project(this.getCoordinates()).dist(this.map.project(e.lngLat)) > 60) { + this.hide(); + } + } + + hide() { + this.popup.remove(); + this.map.off('mousemove', this.maybeHideBinded); + } + + remove() { + this.popup.remove(); + this.component.$destroy(); + } + + getCoordinates() { + const item = get(this.item); + if (item === null) { + return new mapboxgl.LngLat(0, 0); + } + return item.item instanceof Waypoint || item.item instanceof TrackPoint + ? item.item.getCoordinates() + : new mapboxgl.LngLat(item.item.lon, item.item.lat); + } +} diff --git a/website/src/lib/components/map/utils.svelte.ts b/website/src/lib/components/map/map.ts similarity index 67% rename from website/src/lib/components/map/utils.svelte.ts rename to website/src/lib/components/map/map.ts index 768aa5f6..10cd6d1f 100644 --- a/website/src/lib/components/map/utils.svelte.ts +++ b/website/src/lib/components/map/map.ts @@ -1,10 +1,9 @@ -import { TrackPoint, Waypoint, type Coordinates } from 'gpx'; import mapboxgl from 'mapbox-gl'; -import { tick, mount } from 'svelte'; -// import MapPopupComponent from '$lib/components/map/MapPopup.svelte'; -import { get } from 'svelte/store'; -// import { fileObservers } from '$lib/db'; import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder'; +import { get, writable, type Writable } from 'svelte/store'; +import { settings } from '$lib/logic/settings'; + +const { treeFileView, elevationProfile, bottomPanelSize, rightPanelSize, distanceUnits } = settings; let fitBoundsOptions: mapboxgl.MapOptions['fitBoundsOptions'] = { maxZoom: 15, @@ -13,13 +12,17 @@ let fitBoundsOptions: mapboxgl.MapOptions['fitBoundsOptions'] = { }; export class MapboxGLMap { - private _map: mapboxgl.Map | null = $state(null); + private _map: Writable = writable(null); private _onLoadCallbacks: ((map: mapboxgl.Map) => void)[] = []; + private _unsubscribes: (() => void)[] = []; + + subscribe(run: (value: mapboxgl.Map | null) => void, invalidate?: () => void) { + return this._map.subscribe(run, invalidate); + } init( accessToken: string, language: string, - distanceUnits: 'metric' | 'imperial' | 'nautical', hash: boolean, geocoder: boolean, geolocate: boolean @@ -126,7 +129,7 @@ export class MapboxGLMap { ); } const scaleControl = new mapboxgl.ScaleControl({ - unit: distanceUnits, + unit: get(distanceUnits), }); map.addControl(scaleControl); map.on('style.load', () => { @@ -160,46 +163,58 @@ export class MapboxGLMap { }); }); map.on('load', () => { - this._map = map; // only set the store after the map has loaded + this._map.set(map); // only set the store after the map has loaded window._map = map; // entry point for extensions - scaleControl.setUnit(distanceUnits); + scaleControl.setUnit(get(distanceUnits)); this._onLoadCallbacks.forEach((callback) => callback(map)); this._onLoadCallbacks = []; }); + + this._unsubscribes.push(treeFileView.subscribe(() => this.resize())); + this._unsubscribes.push(elevationProfile.subscribe(() => this.resize())); + this._unsubscribes.push(bottomPanelSize.subscribe(() => this.resize())); + this._unsubscribes.push(rightPanelSize.subscribe(() => this.resize())); + this._unsubscribes.push( + distanceUnits.subscribe((units) => { + scaleControl.setUnit(units); + }) + ); } onLoad(callback: (map: mapboxgl.Map) => void) { - if (this._map) { - callback(this._map); + const map = get(this._map); + if (map) { + callback(map); } else { this._onLoadCallbacks.push(callback); } } destroy() { - if (this._map) { - this._map.remove(); - this._map = null; + const map = get(this._map); + if (map) { + map.remove(); + this._map.set(null); } - } - - get value(): mapboxgl.Map | null { - return this._map; + this._unsubscribes.forEach((unsubscribe) => unsubscribe()); + this._unsubscribes = []; } resize() { - if (this._map) { - this._map.resize(); + const map = get(this._map); + if (map) { + map.resize(); } } toggle3D() { - if (this._map) { - if (this._map.getPitch() === 0) { - this._map.easeTo({ pitch: 70 }); + const map = get(this._map); + if (map) { + if (map.getPitch() === 0) { + map.easeTo({ pitch: 70 }); } else { - this._map.easeTo({ pitch: 0 }); + map.easeTo({ pitch: 0 }); } } } @@ -207,15 +222,15 @@ 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, -}); +// const targetMapBounds: { +// bounds: mapboxgl.LngLatBounds; +// ids: string[]; +// total: number; +// } = $state({ +// bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]), +// ids: [], +// total: 0, +// }); // $effect(() => { // if ( @@ -251,32 +266,32 @@ const targetMapBounds: { // 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 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; - } +// 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); - } -} +// 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(); @@ -308,77 +323,3 @@ export function updateTargetMapBounds( // maxZoom: 15, // }); // } - -export type PopupItem = { - item: T; - fileId?: string; - hide?: () => void; -}; - -// export class MapPopup { -// map: mapboxgl.Map; -// popup: mapboxgl.Popup; -// item: PopupItem | null = $state(null); -// maybeHideBinded = this.maybeHide.bind(this); - -// constructor(map: mapboxgl.Map, options?: mapboxgl.PopupOptions) { -// this.map = map; -// this.popup = new mapboxgl.Popup(options); - -// let component = mount(MapPopupComponent, { -// target: document.body, -// props: { -// item: this.item, -// }, -// }); - -// tick().then(() => this.popup.setDOMContent(component.container)); -// } - -// setItem(item: PopupItem | null) { -// if (item) item.hide = () => this.hide(); -// this.item = item; -// if (item === null) { -// this.hide(); -// } else { -// tick().then(() => this.show()); -// } -// } - -// show() { -// if (this.item === null) { -// this.hide(); -// return; -// } -// this.popup.setLngLat(this.getCoordinates()).addTo(this.map); -// this.map.on('mousemove', this.maybeHideBinded); -// } - -// maybeHide(e: mapboxgl.MapMouseEvent) { -// if (this.item === null) { -// this.hide(); -// return; -// } -// if (this.map.project(this.getCoordinates()).dist(this.map.project(e.lngLat)) > 60) { -// this.hide(); -// } -// } - -// hide() { -// this.popup.remove(); -// this.map.off('mousemove', this.maybeHideBinded); -// } - -// remove() { -// this.popup.remove(); -// } - -// getCoordinates() { -// if (this.item === null) { -// return new mapboxgl.LngLat(0, 0); -// } -// return this.item.item instanceof Waypoint || this.item.item instanceof TrackPoint -// ? this.item.item.getCoordinates() -// : new mapboxgl.LngLat(this.item.item.lon, this.item.item.lat); -// } -// } diff --git a/website/src/lib/components/map/street-view-control/StreetViewControl.svelte b/website/src/lib/components/map/street-view-control/StreetViewControl.svelte index 84863398..e62fd43a 100644 --- a/website/src/lib/components/map/street-view-control/StreetViewControl.svelte +++ b/website/src/lib/components/map/street-view-control/StreetViewControl.svelte @@ -1,13 +1,13 @@