diff --git a/website/src/lib/components/Menu.svelte b/website/src/lib/components/Menu.svelte
index ca67c0a3..ed13e1ec 100644
--- a/website/src/lib/components/Menu.svelte
+++ b/website/src/lib/components/Menu.svelte
@@ -41,7 +41,8 @@
FileStack,
FileX,
BookOpenText,
- ChartArea
+ ChartArea,
+ Maximize
} from 'lucide-svelte';
import {
@@ -54,7 +55,8 @@
editMetadata,
editStyle,
exportState,
- ExportState
+ ExportState,
+ centerMapOnSelection
} from '$lib/stores';
import {
copied,
@@ -247,6 +249,17 @@
{$_('menu.select_all')}
+ {
+ if ($selection.size > 0) {
+ centerMapOnSelection();
+ }
+ }}
+ >
+
+ {$_('menu.center')}
+
+
{#if $verticalFileView}
@@ -536,6 +549,10 @@
dbUtils.setHiddenToSelection(true);
}
e.preventDefault();
+ } else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
+ if ($selection.size > 0) {
+ centerMapOnSelection();
+ }
} else if (e.key === 'F1') {
switchBasemaps();
e.preventDefault();
diff --git a/website/src/lib/components/file-list/FileListNodeLabel.svelte b/website/src/lib/components/file-list/FileListNodeLabel.svelte
index 1efe3c31..f6880557 100644
--- a/website/src/lib/components/file-list/FileListNodeLabel.svelte
+++ b/website/src/lib/components/file-list/FileListNodeLabel.svelte
@@ -15,6 +15,7 @@
EyeOff,
ClipboardCopy,
ClipboardPaste,
+ Maximize,
Scissors,
FileStack,
FileX
@@ -39,7 +40,15 @@
} from './Selection';
import { getContext } from 'svelte';
import { get } from 'svelte/store';
- import { allHidden, editMetadata, editStyle, embedding, gpxLayers, map } from '$lib/stores';
+ import {
+ allHidden,
+ editMetadata,
+ editStyle,
+ embedding,
+ centerMapOnSelection,
+ gpxLayers,
+ map
+ } from '$lib/stores';
import {
GPXTreeElement,
Track,
@@ -275,8 +284,13 @@
{$_('menu.select_all')}
-
{/if}
+
+
+ {$_('menu.center')}
+
+
+
{$_('menu.duplicate')}
diff --git a/website/src/lib/stores.ts b/website/src/lib/stores.ts
index 397ae336..65643db1 100644
--- a/website/src/lib/stores.ts
+++ b/website/src/lib/stores.ts
@@ -6,8 +6,21 @@ import { tick } from 'svelte';
import { _ } from 'svelte-i18n';
import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer';
import { dbUtils, fileObservers, getFile, getStatistics, settings } from './db';
-import { addSelectItem, applyToOrderedSelectedItemsFromFile, selectFile, selectItem, selection } from '$lib/components/file-list/Selection';
-import { ListFileItem, ListItem, ListTrackItem, ListTrackSegmentItem, ListWaypointItem, ListWaypointsItem } from '$lib/components/file-list/FileList';
+import {
+ addSelectItem,
+ applyToOrderedSelectedItemsFromFile,
+ selectFile,
+ selectItem,
+ selection
+} from '$lib/components/file-list/Selection';
+import {
+ ListFileItem,
+ ListItem,
+ ListTrackItem,
+ ListTrackSegmentItem,
+ ListWaypointItem,
+ ListWaypointsItem
+} from '$lib/components/file-list/FileList';
import type { RoutingControls } from '$lib/components/toolbar/tools/routing/RoutingControls';
import { SplitType } from '$lib/components/toolbar/tools/scissors/Scissors.svelte';
@@ -18,7 +31,8 @@ export const embedding = writable(false);
export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({});
export const gpxStatistics: Writable = writable(new GPXStatistics());
-export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> = writable(undefined);
+export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> =
+ writable(undefined);
export function updateGPXData() {
let statistics = new GPXStatistics();
@@ -38,7 +52,8 @@ export function updateGPXData() {
}
let unsubscribes: Map void> = new Map();
-selection.subscribe(($selection) => { // Maintain up-to-date statistics for the current selection
+selection.subscribe(($selection) => {
+ // Maintain up-to-date statistics for the current selection
updateGPXData();
while (unsubscribes.size > 0) {
@@ -53,10 +68,13 @@ selection.subscribe(($selection) => { // Maintain up-to-date statistics for the
let fileObserver = get(fileObservers).get(fileId);
if (fileObserver) {
let first = true;
- unsubscribes.set(fileId, fileObserver.subscribe(() => {
- if (first) first = false;
- else updateGPXData();
- }));
+ unsubscribes.set(
+ fileId,
+ fileObserver.subscribe(() => {
+ if (first) first = false;
+ else updateGPXData();
+ })
+ );
}
}
});
@@ -72,8 +90,15 @@ const targetMapBounds = writable({
total: -1
});
-derived([targetMapBounds, map], x => x).subscribe(([bounds, $map]) => {
- if ($map === null || bounds.count !== bounds.total || (bounds.bounds.getSouth() === 90 && bounds.bounds.getWest() === 180 && bounds.bounds.getNorth() === -90 && bounds.bounds.getEast() === -180)) {
+derived([targetMapBounds, map], (x) => x).subscribe(([bounds, $map]) => {
+ if (
+ $map === null ||
+ bounds.count !== bounds.total ||
+ (bounds.bounds.getSouth() === 90 &&
+ bounds.bounds.getWest() === 180 &&
+ bounds.bounds.getNorth() === -90 &&
+ bounds.bounds.getEast() === -180)
+ ) {
return;
}
@@ -81,7 +106,10 @@ derived([targetMapBounds, map], x => x).subscribe(([bounds, $map]) => {
if (bounds.count !== get(fileObservers).size && currentBounds) {
// There are other files on the map
- if (currentBounds.contains(bounds.bounds.getSouthEast()) && currentBounds.contains(bounds.bounds.getNorthWest())) {
+ if (
+ currentBounds.contains(bounds.bounds.getSouthEast()) &&
+ currentBounds.contains(bounds.bounds.getNorthWest())
+ ) {
return;
}
@@ -89,14 +117,9 @@ derived([targetMapBounds, map], x => x).subscribe(([bounds, $map]) => {
bounds.bounds.extend(currentBounds.getNorthEast());
}
- $map.fitBounds(bounds.bounds, {
- padding: 80,
- linear: true,
- easing: () => 1
- });
+ $map.fitBounds(bounds.bounds, { padding: 80, linear: true, easing: () => 1 });
});
-
export function initTargetMapBounds(total: number) {
targetMapBounds.set({
bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
@@ -105,11 +128,14 @@ export function initTargetMapBounds(total: number) {
});
}
-export function updateTargetMapBounds(bounds: {
- southWest: Coordinates,
- northEast: Coordinates
-}) {
- if (bounds.southWest.lat == 90 && bounds.southWest.lon == 180 && bounds.northEast.lat == -90 && bounds.northEast.lon == -180) { // Avoid update for empty (new) files
+export function updateTargetMapBounds(bounds: { southWest: Coordinates; northEast: Coordinates }) {
+ if (
+ bounds.southWest.lat == 90 &&
+ bounds.southWest.lon == 180 &&
+ bounds.northEast.lat == -90 &&
+ bounds.northEast.lon == -180
+ ) {
+ // Avoid update for empty (new) files
targetMapBounds.update((target) => {
target.count += 1;
return target;
@@ -125,6 +151,38 @@ export function updateTargetMapBounds(bounds: {
});
}
+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
+ });
+}
+
export const gpxLayers: Map = new Map();
export const routingControls: Map = new Map();
@@ -143,7 +201,7 @@ export const splitAs = writable(SplitType.FILES);
export const streetViewEnabled = writable(false);
export function newGPXFile() {
- const newFileName = get(_)("menu.new_file");
+ const newFileName = get(_)('menu.new_file');
let file = new GPXFile();
@@ -247,7 +305,11 @@ export function updateSelectionFromKey(down: boolean, shift: boolean) {
let limitIndex: number | undefined = undefined;
selected.forEach((item) => {
let index = order.indexOf(item.getFileId());
- if (limitIndex === undefined || (down && index > limitIndex) || (!down && index < limitIndex)) {
+ if (
+ limitIndex === undefined ||
+ (down && index > limitIndex) ||
+ (!down && index < limitIndex)
+ ) {
limitIndex = index;
}
});
@@ -274,37 +336,52 @@ export function updateSelectionFromKey(down: boolean, shift: boolean) {
nextIndex += down ? 1 : -1;
}
}
- } else if (selected[0] instanceof ListTrackItem && selected[selected.length - 1] instanceof ListTrackItem) {
+ } else if (
+ selected[0] instanceof ListTrackItem &&
+ selected[selected.length - 1] instanceof ListTrackItem
+ ) {
let fileId = selected[0].getFileId();
let file = getFile(fileId);
if (file) {
let numberOfTracks = file.trk.length;
- let trackIndex = down ? selected[selected.length - 1].getTrackIndex() : selected[0].getTrackIndex();
+ let trackIndex = down
+ ? selected[selected.length - 1].getTrackIndex()
+ : selected[0].getTrackIndex();
if (down && trackIndex < numberOfTracks - 1) {
next = new ListTrackItem(fileId, trackIndex + 1);
} else if (!down && trackIndex > 0) {
next = new ListTrackItem(fileId, trackIndex - 1);
}
}
- } else if (selected[0] instanceof ListTrackSegmentItem && selected[selected.length - 1] instanceof ListTrackSegmentItem) {
+ } else if (
+ selected[0] instanceof ListTrackSegmentItem &&
+ selected[selected.length - 1] instanceof ListTrackSegmentItem
+ ) {
let fileId = selected[0].getFileId();
let file = getFile(fileId);
if (file) {
let trackIndex = selected[0].getTrackIndex();
let numberOfSegments = file.trk[trackIndex].trkseg.length;
- let segmentIndex = down ? selected[selected.length - 1].getSegmentIndex() : selected[0].getSegmentIndex();
+ let segmentIndex = down
+ ? selected[selected.length - 1].getSegmentIndex()
+ : selected[0].getSegmentIndex();
if (down && segmentIndex < numberOfSegments - 1) {
next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex + 1);
} else if (!down && segmentIndex > 0) {
next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex - 1);
}
}
- } else if (selected[0] instanceof ListWaypointItem && selected[selected.length - 1] instanceof ListWaypointItem) {
+ } else if (
+ selected[0] instanceof ListWaypointItem &&
+ selected[selected.length - 1] instanceof ListWaypointItem
+ ) {
let fileId = selected[0].getFileId();
let file = getFile(fileId);
if (file) {
let numberOfWaypoints = file.wpt.length;
- let waypointIndex = down ? selected[selected.length - 1].getWaypointIndex() : selected[0].getWaypointIndex();
+ let waypointIndex = down
+ ? selected[selected.length - 1].getWaypointIndex()
+ : selected[0].getWaypointIndex();
if (down && waypointIndex < numberOfWaypoints - 1) {
next = new ListWaypointItem(fileId, waypointIndex + 1);
} else if (!down && waypointIndex > 0) {
@@ -327,7 +404,7 @@ async function exportFiles(fileIds: string[], exclude: string[]) {
let file = getFile(fileId);
if (file) {
exportFile(file, exclude);
- await new Promise(resolve => setTimeout(resolve, 200));
+ await new Promise((resolve) => setTimeout(resolve, 200));
}
}
}
@@ -367,15 +444,21 @@ export function updateAllHidden() {
}
if (item instanceof ListFileItem) {
- hidden = hidden && (file._data.hidden === true);
+ hidden = hidden && file._data.hidden === true;
} else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) {
- hidden = hidden && (file.trk[item.getTrackIndex()]._data.hidden === true);
- } else if (item instanceof ListTrackSegmentItem && item.getTrackIndex() < file.trk.length && item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length) {
- hidden = hidden && (file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()]._data.hidden === true);
+ hidden = hidden && file.trk[item.getTrackIndex()]._data.hidden === true;
+ } else if (
+ item instanceof ListTrackSegmentItem &&
+ item.getTrackIndex() < file.trk.length &&
+ item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length
+ ) {
+ hidden =
+ hidden &&
+ file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()]._data.hidden === true;
} else if (item instanceof ListWaypointsItem) {
- hidden = hidden && (file._data.hiddenWpt === true);
+ hidden = hidden && file._data.hiddenWpt === true;
} else if (item instanceof ListWaypointItem && item.getWaypointIndex() < file.wpt.length) {
- hidden = hidden && (file.wpt[item.getWaypointIndex()]._data.hidden === true);
+ hidden = hidden && file.wpt[item.getWaypointIndex()]._data.hidden === true;
}
}
}
diff --git a/website/src/locales/en.json b/website/src/locales/en.json
index 4c9b687e..3ee8d24c 100644
--- a/website/src/locales/en.json
+++ b/website/src/locales/en.json
@@ -77,6 +77,7 @@
},
"hide": "Hide",
"unhide": "Unhide",
+ "center": "Center",
"open_in": "Open in"
},
"toolbar": {