* Change icon to Maximize

* Use default animation
* Rename feature to "Fly to"
* Move logic to stores so that it can be reused in the main menu (and eventually a keyboard shortcut)
This commit is contained in:
mbof 2024-08-23 07:18:36 -07:00
parent 6559fdd125
commit c6919f518a
4 changed files with 843 additions and 783 deletions

View file

@ -41,7 +41,8 @@
FileStack, FileStack,
FileX, FileX,
BookOpenText, BookOpenText,
ChartArea ChartArea,
Maximize
} from 'lucide-svelte'; } from 'lucide-svelte';
import { import {
@ -54,7 +55,9 @@
editMetadata, editMetadata,
editStyle, editStyle,
exportState, exportState,
ExportState ExportState,
flyToBounds,
gpxStatistics
} from '$lib/stores'; } from '$lib/stores';
import { import {
copied, copied,
@ -222,6 +225,13 @@
<PaintBucket size="16" class="mr-1" /> <PaintBucket size="16" class="mr-1" />
{$_('menu.style.button')} {$_('menu.style.button')}
</Menubar.Item> </Menubar.Item>
<Menubar.Item
disabled={$selection.size === 0}
on:click={() => flyToBounds($gpxStatistics.global.bounds, $map)}
>
<Maximize size="16" class="mr-1" />
{$_('menu.fly_to_selection')}
</Menubar.Item>
<Menubar.Item <Menubar.Item
on:click={() => { on:click={() => {
if ($allHidden) { if ($allHidden) {

View file

@ -15,7 +15,7 @@
EyeOff, EyeOff,
ClipboardCopy, ClipboardCopy,
ClipboardPaste, ClipboardPaste,
Maximize2, Maximize,
Scissors, Scissors,
FileStack, FileStack,
FileX FileX
@ -45,9 +45,10 @@
editMetadata, editMetadata,
editStyle, editStyle,
embedding, embedding,
flyToBounds,
gpxLayers, gpxLayers,
map, gpxStatistics,
updateTargetMapBounds map
} from '$lib/stores'; } from '$lib/stores';
import { import {
GPXTreeElement, GPXTreeElement,
@ -226,30 +227,14 @@
{$_('menu.style.button')} {$_('menu.style.button')}
</ContextMenu.Item> </ContextMenu.Item>
{/if} {/if}
{#if node instanceof GPXTreeElement}
<ContextMenu.Item <ContextMenu.Item
on:click={() => { on:click={() => {
const targetBounds = node.getStatistics().global.bounds; flyToBounds($gpxStatistics.global.bounds, $map);
const mapBoxBounds = new mapboxgl.LngLatBounds([
[targetBounds.northEast.lon, targetBounds.northEast.lat],
[targetBounds.southWest.lon, targetBounds.southWest.lat]
]);
$map?.fitBounds(mapBoxBounds, {
padding: 80,
linear: true,
duration: 1000,
easing: (t) => {
return t < 0.5
? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2
: (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
}
});
}} }}
> >
<Maximize2 size="16" class="mr-1" /> <Maximize size="16" class="mr-1" />
{$_('menu.fit_map')} {$_('menu.fly_to')}
</ContextMenu.Item> </ContextMenu.Item>
{/if}
<ContextMenu.Item <ContextMenu.Item
on:click={() => { on:click={() => {
if ($allHidden) { if ($allHidden) {

View file

@ -6,8 +6,21 @@ import { tick } from 'svelte';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer'; import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer';
import { dbUtils, fileObservers, getFile, getStatistics, settings } from './db'; import { dbUtils, fileObservers, getFile, getStatistics, settings } from './db';
import { addSelectItem, applyToOrderedSelectedItemsFromFile, selectFile, selectItem, selection } from '$lib/components/file-list/Selection'; import {
import { ListFileItem, ListItem, ListTrackItem, ListTrackSegmentItem, ListWaypointItem, ListWaypointsItem } from '$lib/components/file-list/FileList'; 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 type { RoutingControls } from '$lib/components/toolbar/tools/routing/RoutingControls';
import { SplitType } from '$lib/components/toolbar/tools/scissors/Scissors.svelte'; 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 selectFiles = writable<{ [key: string]: (fileId?: string) => void }>({});
export const gpxStatistics: Writable<GPXStatistics> = writable(new GPXStatistics()); export const gpxStatistics: Writable<GPXStatistics> = 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() { export function updateGPXData() {
let statistics = new GPXStatistics(); let statistics = new GPXStatistics();
@ -38,7 +52,8 @@ export function updateGPXData() {
} }
let unsubscribes: Map<string, () => void> = new Map(); let unsubscribes: Map<string, () => 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(); updateGPXData();
while (unsubscribes.size > 0) { while (unsubscribes.size > 0) {
@ -53,10 +68,13 @@ selection.subscribe(($selection) => { // Maintain up-to-date statistics for the
let fileObserver = get(fileObservers).get(fileId); let fileObserver = get(fileObservers).get(fileId);
if (fileObserver) { if (fileObserver) {
let first = true; let first = true;
unsubscribes.set(fileId, fileObserver.subscribe(() => { unsubscribes.set(
fileId,
fileObserver.subscribe(() => {
if (first) first = false; if (first) first = false;
else updateGPXData(); else updateGPXData();
})); })
);
} }
} }
}); });
@ -72,8 +90,15 @@ const targetMapBounds = writable({
total: -1 total: -1
}); });
derived([targetMapBounds, map], x => x).subscribe(([bounds, $map]) => { 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)) { if (
$map === null ||
bounds.count !== bounds.total ||
(bounds.bounds.getSouth() === 90 &&
bounds.bounds.getWest() === 180 &&
bounds.bounds.getNorth() === -90 &&
bounds.bounds.getEast() === -180)
) {
return; return;
} }
@ -81,7 +106,10 @@ derived([targetMapBounds, map], x => x).subscribe(([bounds, $map]) => {
if (bounds.count !== get(fileObservers).size && currentBounds) { if (bounds.count !== get(fileObservers).size && currentBounds) {
// There are other files on the map // 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; return;
} }
@ -89,13 +117,8 @@ derived([targetMapBounds, map], x => x).subscribe(([bounds, $map]) => {
bounds.bounds.extend(currentBounds.getNorthEast()); bounds.bounds.extend(currentBounds.getNorthEast());
} }
$map.fitBounds(bounds.bounds, { $map.fitBounds(bounds.bounds, { padding: 80, linear: true, easing: () => 1 });
padding: 80,
linear: true,
easing: () => 1
}); });
});
export function initTargetMapBounds(total: number) { export function initTargetMapBounds(total: number) {
targetMapBounds.set({ targetMapBounds.set({
@ -105,11 +128,14 @@ export function initTargetMapBounds(total: number) {
}); });
} }
export function updateTargetMapBounds(bounds: { export function updateTargetMapBounds(bounds: { southWest: Coordinates; northEast: Coordinates }) {
southWest: Coordinates, if (
northEast: Coordinates bounds.southWest.lat == 90 &&
}) { bounds.southWest.lon == 180 &&
if (bounds.southWest.lat == 90 && bounds.southWest.lon == 180 && bounds.northEast.lat == -90 && bounds.northEast.lon == -180) { // Avoid update for empty (new) files bounds.northEast.lat == -90 &&
bounds.northEast.lon == -180
) {
// Avoid update for empty (new) files
targetMapBounds.update((target) => { targetMapBounds.update((target) => {
target.count += 1; target.count += 1;
return target; return target;
@ -125,6 +151,19 @@ export function updateTargetMapBounds(bounds: {
}); });
} }
export function flyToBounds(
bounds: { southWest: Coordinates; northEast: Coordinates },
map: mapboxgl.Map | null
) {
const mapBoxBounds = new mapboxgl.LngLatBounds([
[bounds.northEast.lon, bounds.northEast.lat],
[bounds.southWest.lon, bounds.southWest.lat]
]);
map?.fitBounds(mapBoxBounds, {
padding: 80
});
}
export const gpxLayers: Map<string, GPXLayer> = new Map(); export const gpxLayers: Map<string, GPXLayer> = new Map();
export const routingControls: Map<string, RoutingControls> = new Map(); export const routingControls: Map<string, RoutingControls> = new Map();
@ -143,7 +182,7 @@ export const splitAs = writable(SplitType.FILES);
export const streetViewEnabled = writable(false); export const streetViewEnabled = writable(false);
export function newGPXFile() { export function newGPXFile() {
const newFileName = get(_)("menu.new_file"); const newFileName = get(_)('menu.new_file');
let file = new GPXFile(); let file = new GPXFile();
@ -247,7 +286,11 @@ export function updateSelectionFromKey(down: boolean, shift: boolean) {
let limitIndex: number | undefined = undefined; let limitIndex: number | undefined = undefined;
selected.forEach((item) => { selected.forEach((item) => {
let index = order.indexOf(item.getFileId()); 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; limitIndex = index;
} }
}); });
@ -274,37 +317,52 @@ export function updateSelectionFromKey(down: boolean, shift: boolean) {
nextIndex += down ? 1 : -1; 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 fileId = selected[0].getFileId();
let file = getFile(fileId); let file = getFile(fileId);
if (file) { if (file) {
let numberOfTracks = file.trk.length; 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) { if (down && trackIndex < numberOfTracks - 1) {
next = new ListTrackItem(fileId, trackIndex + 1); next = new ListTrackItem(fileId, trackIndex + 1);
} else if (!down && trackIndex > 0) { } else if (!down && trackIndex > 0) {
next = new ListTrackItem(fileId, trackIndex - 1); 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 fileId = selected[0].getFileId();
let file = getFile(fileId); let file = getFile(fileId);
if (file) { if (file) {
let trackIndex = selected[0].getTrackIndex(); let trackIndex = selected[0].getTrackIndex();
let numberOfSegments = file.trk[trackIndex].trkseg.length; 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) { if (down && segmentIndex < numberOfSegments - 1) {
next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex + 1); next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex + 1);
} else if (!down && segmentIndex > 0) { } else if (!down && segmentIndex > 0) {
next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex - 1); 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 fileId = selected[0].getFileId();
let file = getFile(fileId); let file = getFile(fileId);
if (file) { if (file) {
let numberOfWaypoints = file.wpt.length; 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) { if (down && waypointIndex < numberOfWaypoints - 1) {
next = new ListWaypointItem(fileId, waypointIndex + 1); next = new ListWaypointItem(fileId, waypointIndex + 1);
} else if (!down && waypointIndex > 0) { } else if (!down && waypointIndex > 0) {
@ -327,7 +385,7 @@ async function exportFiles(fileIds: string[], exclude: string[]) {
let file = getFile(fileId); let file = getFile(fileId);
if (file) { if (file) {
exportFile(file, exclude); exportFile(file, exclude);
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise((resolve) => setTimeout(resolve, 200));
} }
} }
} }
@ -367,15 +425,21 @@ export function updateAllHidden() {
} }
if (item instanceof ListFileItem) { 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) { } else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) {
hidden = hidden && (file.trk[item.getTrackIndex()]._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) { } else if (
hidden = hidden && (file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()]._data.hidden === true); 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) { } 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) { } 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;
} }
} }
} }

View file

@ -77,7 +77,8 @@
"hide": "Hide", "hide": "Hide",
"unhide": "Unhide", "unhide": "Unhide",
"open_in": "Open in", "open_in": "Open in",
"fit_map": "Fit on map" "fly_to": "Fly to",
"fly_to_selection": "Fly to selection"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {