* 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:
parent
6559fdd125
commit
c6919f518a
4 changed files with 843 additions and 783 deletions
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,14 +117,9 @@ 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({
|
||||||
bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
|
bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue