mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-10-14 03:28:18 +00:00
progress
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
variant = 'default',
|
||||
label,
|
||||
side = 'top',
|
||||
disabled = false,
|
||||
class: className = '',
|
||||
children,
|
||||
onclick,
|
||||
@@ -14,6 +15,7 @@
|
||||
variant?: 'default' | 'secondary' | 'link' | 'destructive' | 'outline' | 'ghost';
|
||||
label: string;
|
||||
side?: 'top' | 'right' | 'bottom' | 'left';
|
||||
disabled?: boolean;
|
||||
class?: string;
|
||||
children: Snippet;
|
||||
onclick?: (event: MouseEvent) => void;
|
||||
|
@@ -55,25 +55,31 @@
|
||||
// updateSelectionFromKey,
|
||||
// allHidden,
|
||||
// } from '$lib/stores';
|
||||
import {
|
||||
copied,
|
||||
copySelection,
|
||||
cutSelection,
|
||||
pasteSelection,
|
||||
selectAll,
|
||||
selection,
|
||||
} from '$lib/components/file-list/Selection';
|
||||
// import { canUndo, canRedo, dbUtils, fileObservers, settings } from '$lib/db';
|
||||
// import { canUndo, canRedo, fileActions, fileObservers, settings } from '$lib/db';
|
||||
import { anySelectedLayer } from '$lib/components/map/layer-control/utils.svelte';
|
||||
import { defaultOverlays } from '$lib/assets/layers';
|
||||
// import LayerControlSettings from '$lib/components/map/layer-control/LayerControlSettings.svelte';
|
||||
import { allowedPastes, ListFileItem, ListTrackItem } from '$lib/components/file-list/FileList';
|
||||
import {
|
||||
allowedPastes,
|
||||
ListFileItem,
|
||||
ListTrackItem,
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import Export from '$lib/components/export/Export.svelte';
|
||||
import { mode, setMode } from 'mode-watcher';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { languages } from '$lib/languages';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
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';
|
||||
|
||||
const {
|
||||
distanceUnits,
|
||||
@@ -91,9 +97,6 @@
|
||||
routing,
|
||||
} = settings;
|
||||
|
||||
// let undoDisabled = derived(canUndo, ($canUndo) => !$canUndo);
|
||||
// let redoDisabled = derived(canRedo, ($canRedo) => !$canRedo);
|
||||
|
||||
function switchBasemaps() {
|
||||
[currentBasemap.value, previousBasemap.value] = [
|
||||
previousBasemap.value,
|
||||
@@ -103,15 +106,11 @@
|
||||
|
||||
function toggleOverlays() {
|
||||
if (currentOverlays.value && anySelectedLayer(currentOverlays.value)) {
|
||||
[currentOverlays.value, previousOverlays.value] = [
|
||||
defaultOverlays,
|
||||
currentOverlays.value,
|
||||
];
|
||||
previousOverlays.value = JSON.parse(JSON.stringify(currentOverlays.value));
|
||||
currentOverlays.value = defaultOverlays;
|
||||
} else {
|
||||
[currentOverlays.value, previousOverlays.value] = [
|
||||
previousOverlays.value,
|
||||
defaultOverlays,
|
||||
];
|
||||
currentOverlays.value = JSON.parse(JSON.stringify(previousOverlays.value));
|
||||
previousOverlays.value = defaultOverlays;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +125,7 @@
|
||||
<Logo class="h-5 mt-0.5 mx-2 md:hidden" iconOnly={true} width="16" />
|
||||
<Logo class="h-5 mt-0.5 mx-2 hidden md:block" width="96" />
|
||||
</a>
|
||||
<Menubar.Root class="border-none h-fit p-0">
|
||||
<Menubar.Root class="border-none shadow-none h-fit p-0">
|
||||
<Menubar.Menu>
|
||||
<Menubar.Trigger aria-label={i18n._('gpx.file')}>
|
||||
<File size="18" class="md:hidden" />
|
||||
@@ -146,8 +145,8 @@
|
||||
</Menubar.Item>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item
|
||||
onclick={dbUtils.duplicateSelection}
|
||||
disabled={$selection.size == 0}
|
||||
onclick={fileActions.duplicateSelection}
|
||||
disabled={selection.value.size == 0}
|
||||
>
|
||||
<Copy size="16" class="mr-1" />
|
||||
{i18n._('menu.duplicate')}
|
||||
@@ -155,16 +154,16 @@
|
||||
</Menubar.Item>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item
|
||||
onclick={dbUtils.deleteSelectedFiles}
|
||||
disabled={$selection.size == 0}
|
||||
onclick={fileActions.deleteSelectedFiles}
|
||||
disabled={selection.value.size == 0}
|
||||
>
|
||||
<FileX size="16" class="mr-1" />
|
||||
{i18n._('menu.close')}
|
||||
<Shortcut key="⌫" ctrl={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Item
|
||||
onclick={dbUtils.deleteAllFiles}
|
||||
disabled={$fileObservers.size == 0}
|
||||
onclick={fileActions.deleteAllFiles}
|
||||
disabled={fileStateCollection.size == 0}
|
||||
>
|
||||
<FileX size="16" class="mr-1" />
|
||||
{i18n._('menu.close_all')}
|
||||
@@ -173,7 +172,7 @@
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item
|
||||
onclick={() => (exportState.current = ExportState.SELECTION)}
|
||||
disabled={$selection.size == 0}
|
||||
disabled={selection.value.size == 0}
|
||||
>
|
||||
<Download size="16" class="mr-1" />
|
||||
{i18n._('menu.export')}
|
||||
@@ -181,7 +180,7 @@
|
||||
</Menubar.Item>
|
||||
<Menubar.Item
|
||||
onclick={() => (exportState.current = ExportState.ALL)}
|
||||
disabled={$fileObservers.size == 0}
|
||||
disabled={fileStateCollection.size == 0}
|
||||
>
|
||||
<Download size="16" class="mr-1" />
|
||||
{i18n._('menu.export_all')}
|
||||
@@ -195,20 +194,26 @@
|
||||
<span class="hidden md:block">{i18n._('menu.edit')}</span>
|
||||
</Menubar.Trigger>
|
||||
<Menubar.Content class="border-none">
|
||||
<Menubar.Item onclick={dbUtils.undo} disabled={$undoDisabled}>
|
||||
<Menubar.Item
|
||||
onclick={fileActionManager.undo}
|
||||
disabled={!fileActionManager.canUndo}
|
||||
>
|
||||
<Undo2 size="16" class="mr-1" />
|
||||
{i18n._('menu.undo')}
|
||||
<Shortcut key="Z" ctrl={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Item onclick={dbUtils.redo} disabled={$redoDisabled}>
|
||||
<Menubar.Item
|
||||
onclick={fileActionManager.redo}
|
||||
disabled={!fileActionManager.canRedo}
|
||||
>
|
||||
<Redo2 size="16" class="mr-1" />
|
||||
{i18n._('menu.redo')}
|
||||
<Shortcut key="Z" ctrl={true} shift={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item
|
||||
disabled={$selection.size !== 1 ||
|
||||
!$selection
|
||||
disabled={selection.value.size !== 1 ||
|
||||
!selection.value
|
||||
.getSelected()
|
||||
.every(
|
||||
(item) =>
|
||||
@@ -222,8 +227,8 @@
|
||||
<Shortcut key="I" ctrl={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Item
|
||||
disabled={$selection.size === 0 ||
|
||||
!$selection
|
||||
disabled={selection.value.size === 0 ||
|
||||
!selection.value
|
||||
.getSelected()
|
||||
.every(
|
||||
(item) =>
|
||||
@@ -237,44 +242,51 @@
|
||||
</Menubar.Item>
|
||||
<Menubar.Item
|
||||
onclick={() => {
|
||||
if ($allHidden) {
|
||||
dbUtils.setHiddenToSelection(false);
|
||||
} else {
|
||||
dbUtils.setHiddenToSelection(true);
|
||||
}
|
||||
// if ($allHidden) {
|
||||
// fileActions.setHiddenToSelection(false);
|
||||
// } else {
|
||||
// fileActions.setHiddenToSelection(true);
|
||||
// }
|
||||
}}
|
||||
disabled={$selection.size == 0}
|
||||
disabled={selection.value.size == 0}
|
||||
>
|
||||
{#if $allHidden}
|
||||
<!-- {#if $allHidden}
|
||||
<Eye size="16" class="mr-1" />
|
||||
{i18n._('menu.unhide')}
|
||||
{:else}
|
||||
<EyeOff size="16" class="mr-1" />
|
||||
{i18n._('menu.hide')}
|
||||
{/if}
|
||||
{/if} -->
|
||||
<Shortcut key="H" ctrl={true} />
|
||||
</Menubar.Item>
|
||||
{#if treeFileView.value}
|
||||
{#if $selection.getSelected().some((item) => item instanceof ListFileItem)}
|
||||
{#if selection.value
|
||||
.getSelected()
|
||||
.some((item) => item instanceof ListFileItem)}
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item
|
||||
onclick={() =>
|
||||
dbUtils.addNewTrack($selection.getSelected()[0].getFileId())}
|
||||
disabled={$selection.size !== 1}
|
||||
fileActions.addNewTrack(
|
||||
selection.value.getSelected()[0].getFileId()
|
||||
)}
|
||||
disabled={selection.value.size !== 1}
|
||||
>
|
||||
<Plus size="16" class="mr-1" />
|
||||
{i18n._('menu.new_track')}
|
||||
</Menubar.Item>
|
||||
{:else if $selection
|
||||
{:else if selection.value
|
||||
.getSelected()
|
||||
.some((item) => item instanceof ListTrackItem)}
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item
|
||||
onclick={() => {
|
||||
let item = $selection.getSelected()[0];
|
||||
dbUtils.addNewSegment(item.getFileId(), item.getTrackIndex());
|
||||
let item = selection.value.getSelected()[0];
|
||||
fileActions.addNewSegment(
|
||||
item.getFileId(),
|
||||
item.getTrackIndex()
|
||||
);
|
||||
}}
|
||||
disabled={$selection.size !== 1}
|
||||
disabled={selection.value.size !== 1}
|
||||
>
|
||||
<Plus size="16" class="mr-1" />
|
||||
{i18n._('menu.new_segment')}
|
||||
@@ -282,15 +294,18 @@
|
||||
{/if}
|
||||
{/if}
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item onclick={selectAll} disabled={$fileObservers.size == 0}>
|
||||
<Menubar.Item
|
||||
onclick={selection.selectAll}
|
||||
disabled={fileStateCollection.size == 0}
|
||||
>
|
||||
<FileStack size="16" class="mr-1" />
|
||||
{i18n._('menu.select_all')}
|
||||
<Shortcut key="A" ctrl={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Item
|
||||
onclick={() => {
|
||||
if ($selection.size > 0) {
|
||||
centerMapOnSelection();
|
||||
if (selection.value.size > 0) {
|
||||
// centerMapOnSelection();
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -300,22 +315,28 @@
|
||||
</Menubar.Item>
|
||||
{#if treeFileView.value}
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item onclick={copySelection} disabled={$selection.size === 0}>
|
||||
<Menubar.Item
|
||||
onclick={selection.copySelection}
|
||||
disabled={selection.value.size === 0}
|
||||
>
|
||||
<ClipboardCopy size="16" class="mr-1" />
|
||||
{i18n._('menu.copy')}
|
||||
<Shortcut key="C" ctrl={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Item onclick={cutSelection} disabled={$selection.size === 0}>
|
||||
<Menubar.Item
|
||||
onclick={selection.cutSelection}
|
||||
disabled={selection.value.size === 0}
|
||||
>
|
||||
<Scissors size="16" class="mr-1" />
|
||||
{i18n._('menu.cut')}
|
||||
<Shortcut key="X" ctrl={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Item
|
||||
disabled={$copied === undefined ||
|
||||
$copied.length === 0 ||
|
||||
($selection.size > 0 &&
|
||||
!allowedPastes[$copied[0].level].includes(
|
||||
$selection.getSelected().pop()?.level
|
||||
disabled={selection.copied === undefined ||
|
||||
selection.copied.length === 0 ||
|
||||
(selection.value.size > 0 &&
|
||||
!allowedPastes[selection.copied[0].level].includes(
|
||||
selection.value.getSelected().pop()?.level
|
||||
))}
|
||||
onclick={pasteSelection}
|
||||
>
|
||||
@@ -325,7 +346,10 @@
|
||||
</Menubar.Item>
|
||||
{/if}
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item onclick={dbUtils.deleteSelection} disabled={$selection.size == 0}>
|
||||
<Menubar.Item
|
||||
onclick={fileActions.deleteSelection}
|
||||
disabled={selection.value.size == 0}
|
||||
>
|
||||
<Trash2 size="16" class="mr-1" />
|
||||
{i18n._('menu.delete')}
|
||||
<Shortcut key="⌫" ctrl={true} />
|
||||
@@ -552,16 +576,16 @@
|
||||
triggerFileInput();
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'd' && (e.metaKey || e.ctrlKey)) {
|
||||
dbUtils.duplicateSelection();
|
||||
fileActions.duplicateSelection();
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'c' && (e.metaKey || e.ctrlKey)) {
|
||||
if (!targetInput) {
|
||||
copySelection();
|
||||
selection.copySelection();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else if (e.key === 'x' && (e.metaKey || e.ctrlKey)) {
|
||||
if (!targetInput) {
|
||||
cutSelection();
|
||||
selection.cutSelection();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else if (e.key === 'v' && (e.metaKey || e.ctrlKey)) {
|
||||
@@ -571,38 +595,38 @@
|
||||
}
|
||||
} else if ((e.key === 's' || e.key == 'S') && (e.metaKey || e.ctrlKey)) {
|
||||
if (e.shiftKey) {
|
||||
if ($fileObservers.size > 0) {
|
||||
if (fileStateCollection.size > 0) {
|
||||
exportState.current = ExportState.ALL;
|
||||
}
|
||||
} else if ($selection.size > 0) {
|
||||
} else if (selection.value.size > 0) {
|
||||
exportState.current = ExportState.SELECTION;
|
||||
}
|
||||
e.preventDefault();
|
||||
} else if ((e.key === 'z' || e.key == 'Z') && (e.metaKey || e.ctrlKey)) {
|
||||
if (e.shiftKey) {
|
||||
dbUtils.redo();
|
||||
fileActionManager.redo();
|
||||
} else {
|
||||
dbUtils.undo();
|
||||
fileActionManager.undo();
|
||||
}
|
||||
e.preventDefault();
|
||||
} else if ((e.key === 'Backspace' || e.key === 'Delete') && (e.metaKey || e.ctrlKey)) {
|
||||
if (!targetInput) {
|
||||
if (e.shiftKey) {
|
||||
dbUtils.deleteAllFiles();
|
||||
fileActions.deleteAllFiles();
|
||||
} else {
|
||||
dbUtils.deleteSelection();
|
||||
fileActions.deleteSelection();
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
} else if (e.key === 'a' && (e.metaKey || e.ctrlKey)) {
|
||||
if (!targetInput) {
|
||||
selectAll();
|
||||
selection.selectAll();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else if (e.key === 'i' && (e.metaKey || e.ctrlKey)) {
|
||||
if (
|
||||
$selection.size === 1 &&
|
||||
$selection
|
||||
selection.value.size === 1 &&
|
||||
selection.value
|
||||
.getSelected()
|
||||
.every((item) => item instanceof ListFileItem || item instanceof ListTrackItem)
|
||||
) {
|
||||
@@ -610,22 +634,22 @@
|
||||
}
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'p' && (e.metaKey || e.ctrlKey)) {
|
||||
$elevationProfile = !$elevationProfile;
|
||||
elevationProfile.value = !elevationProfile.value;
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'l' && (e.metaKey || e.ctrlKey)) {
|
||||
$treeFileView = !$treeFileView;
|
||||
treeFileView.value = !treeFileView.value;
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'h' && (e.metaKey || e.ctrlKey)) {
|
||||
if ($allHidden) {
|
||||
dbUtils.setHiddenToSelection(false);
|
||||
} else {
|
||||
dbUtils.setHiddenToSelection(true);
|
||||
}
|
||||
// if ($allHidden) {
|
||||
// fileActions.setHiddenToSelection(false);
|
||||
// } else {
|
||||
// fileActions.setHiddenToSelection(true);
|
||||
// }
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
||||
if ($selection.size > 0) {
|
||||
centerMapOnSelection();
|
||||
}
|
||||
// if ($selection.size > 0) {
|
||||
// centerMapOnSelection();
|
||||
// }
|
||||
} else if (e.key === 'F1') {
|
||||
switchBasemaps();
|
||||
e.preventDefault();
|
||||
@@ -633,13 +657,13 @@
|
||||
toggleOverlays();
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'F3') {
|
||||
$distanceMarkers = !$distanceMarkers;
|
||||
distanceMarkers.value = !distanceMarkers.value;
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'F4') {
|
||||
$directionMarkers = !$directionMarkers;
|
||||
directionMarkers.value = !directionMarkers.value;
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'F5') {
|
||||
$routing = !$routing;
|
||||
routing.value = !routing.value;
|
||||
e.preventDefault();
|
||||
} else if (
|
||||
e.key === 'ArrowRight' ||
|
||||
@@ -648,7 +672,7 @@
|
||||
e.key === 'ArrowUp'
|
||||
) {
|
||||
if (!targetInput) {
|
||||
updateSelectionFromKey(e.key === 'ArrowRight' || e.key === 'ArrowDown', e.shiftKey);
|
||||
// updateSelectionFromKey(e.key === 'ArrowRight' || e.key === 'ArrowDown', e.shiftKey);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import GPXLayers from '$lib/components/map/gpx-layer/GPXLayers.svelte';
|
||||
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
|
||||
import FileList from '$lib/components/file-list/FileList.svelte';
|
||||
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
|
||||
// import GPXLayers from '$lib/components/map/gpx-layer/GPXLayers.svelte';
|
||||
// import ElevationProfile from '$lib/components/ElevationProfile.svelte';
|
||||
// import FileList from '$lib/components/file-list/FileList.svelte';
|
||||
// import GPXStatistics from '$lib/components/GPXStatistics.svelte';
|
||||
import Map from '$lib/components/map/Map.svelte';
|
||||
import { map } from '$lib/components/map/map.svelte';
|
||||
import LayerControl from '$lib/components/map/layer-control/LayerControl.svelte';
|
||||
import { map } from '$lib/components/map/utils.svelte';
|
||||
// import LayerControl from '$lib/components/map/layer-control/LayerControl.svelte';
|
||||
import OpenIn from '$lib/components/embedding/OpenIn.svelte';
|
||||
import {
|
||||
gpxStatistics,
|
||||
@@ -14,12 +14,10 @@
|
||||
loadFile,
|
||||
updateGPXData,
|
||||
} from '$lib/stores';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { fileObservers, settings, GPXStatisticsTree } from '$lib/db';
|
||||
import { onDestroy, onMount, setContext } from 'svelte';
|
||||
import { readable } from 'svelte/store';
|
||||
import type { GPXFile } from 'gpx';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { ListFileItem } from '$lib/components/file-list/FileList';
|
||||
import { ListFileItem } from '$lib/components/file-list/file-list';
|
||||
import {
|
||||
allowedEmbeddingBasemaps,
|
||||
getFilesFromEmbeddingOptions,
|
||||
@@ -27,8 +25,16 @@
|
||||
} from './Embedding';
|
||||
import { mode, setMode } from 'mode-watcher';
|
||||
import { browser } from '$app/environment';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
|
||||
$embedding = true;
|
||||
let {
|
||||
useHash = true,
|
||||
options = $bindable(),
|
||||
hash,
|
||||
}: { useHash?: boolean; options: EmbeddingOptions; hash: string } = $props();
|
||||
|
||||
setContext('embedding', true);
|
||||
|
||||
const {
|
||||
currentBasemap,
|
||||
@@ -40,11 +46,14 @@
|
||||
directionMarkers,
|
||||
} = settings;
|
||||
|
||||
export let useHash = true;
|
||||
export let options: EmbeddingOptions;
|
||||
export let hash: string;
|
||||
|
||||
let prevSettings = {
|
||||
let prevSettings: {
|
||||
distanceMarkers: boolean;
|
||||
directionMarkers: boolean;
|
||||
distanceUnits: 'metric' | 'imperial' | 'nautical';
|
||||
velocityUnits: 'speed' | 'pace';
|
||||
temperatureUnits: 'celsius' | 'fahrenheit';
|
||||
theme: 'light' | 'dark' | 'system';
|
||||
} = {
|
||||
distanceMarkers: false,
|
||||
directionMarkers: false,
|
||||
distanceUnits: 'metric',
|
||||
@@ -54,191 +63,173 @@
|
||||
};
|
||||
|
||||
function applyOptions() {
|
||||
fileObservers.update(($fileObservers) => {
|
||||
$fileObservers.clear();
|
||||
return $fileObservers;
|
||||
});
|
||||
|
||||
let downloads: Promise<GPXFile | null>[] = [];
|
||||
getFilesFromEmbeddingOptions(options).forEach((url) => {
|
||||
downloads.push(
|
||||
fetch(url)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => new File([blob], url.split('/').pop() ?? url))
|
||||
.then(loadFile)
|
||||
);
|
||||
});
|
||||
|
||||
Promise.all(downloads).then((files) => {
|
||||
let ids: string[] = [];
|
||||
let bounds = {
|
||||
southWest: {
|
||||
lat: 90,
|
||||
lon: 180,
|
||||
},
|
||||
northEast: {
|
||||
lat: -90,
|
||||
lon: -180,
|
||||
},
|
||||
};
|
||||
|
||||
fileObservers.update(($fileObservers) => {
|
||||
files.forEach((file, index) => {
|
||||
if (file === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let id = `gpx-${index}-embed`;
|
||||
file._data.id = id;
|
||||
let statistics = new GPXStatisticsTree(file);
|
||||
|
||||
$fileObservers.set(
|
||||
id,
|
||||
readable({
|
||||
file,
|
||||
statistics,
|
||||
})
|
||||
);
|
||||
|
||||
ids.push(id);
|
||||
let fileBounds = statistics.getStatisticsFor(new ListFileItem(id)).global
|
||||
.bounds;
|
||||
|
||||
bounds.southWest.lat = Math.min(bounds.southWest.lat, fileBounds.southWest.lat);
|
||||
bounds.southWest.lon = Math.min(bounds.southWest.lon, fileBounds.southWest.lon);
|
||||
bounds.northEast.lat = Math.max(bounds.northEast.lat, fileBounds.northEast.lat);
|
||||
bounds.northEast.lon = Math.max(bounds.northEast.lon, fileBounds.northEast.lon);
|
||||
});
|
||||
|
||||
return $fileObservers;
|
||||
});
|
||||
|
||||
$fileOrder = [...$fileOrder.filter((id) => !id.includes('embed')), ...ids];
|
||||
|
||||
selection.update(($selection) => {
|
||||
$selection.clear();
|
||||
ids.forEach((id) => {
|
||||
$selection.toggle(new ListFileItem(id));
|
||||
});
|
||||
return $selection;
|
||||
});
|
||||
|
||||
if (hash.length === 0) {
|
||||
map.subscribe(($map) => {
|
||||
if ($map) {
|
||||
$map.fitBounds(
|
||||
[
|
||||
bounds.southWest.lon,
|
||||
bounds.southWest.lat,
|
||||
bounds.northEast.lon,
|
||||
bounds.northEast.lat,
|
||||
],
|
||||
{
|
||||
padding: 80,
|
||||
linear: true,
|
||||
easing: () => 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
options.basemap !== $currentBasemap &&
|
||||
allowedEmbeddingBasemaps.includes(options.basemap)
|
||||
) {
|
||||
$currentBasemap = options.basemap;
|
||||
}
|
||||
|
||||
if (options.distanceMarkers !== $distanceMarkers) {
|
||||
$distanceMarkers = options.distanceMarkers;
|
||||
}
|
||||
|
||||
if (options.directionMarkers !== $directionMarkers) {
|
||||
$directionMarkers = options.directionMarkers;
|
||||
}
|
||||
|
||||
if (options.distanceUnits !== $distanceUnits) {
|
||||
$distanceUnits = options.distanceUnits;
|
||||
}
|
||||
|
||||
if (options.velocityUnits !== $velocityUnits) {
|
||||
$velocityUnits = options.velocityUnits;
|
||||
}
|
||||
|
||||
if (options.temperatureUnits !== $temperatureUnits) {
|
||||
$temperatureUnits = options.temperatureUnits;
|
||||
}
|
||||
|
||||
if (options.theme !== $mode) {
|
||||
setMode(options.theme);
|
||||
}
|
||||
// fileObservers.update(($fileObservers) => {
|
||||
// $fileObservers.clear();
|
||||
// return $fileObservers;
|
||||
// });
|
||||
// let downloads: Promise<GPXFile | null>[] = [];
|
||||
// getFilesFromEmbeddingOptions(options).forEach((url) => {
|
||||
// downloads.push(
|
||||
// fetch(url)
|
||||
// .then((response) => response.blob())
|
||||
// .then((blob) => new File([blob], url.split('/').pop() ?? url))
|
||||
// .then(loadFile)
|
||||
// );
|
||||
// });
|
||||
// Promise.all(downloads).then((files) => {
|
||||
// let ids: string[] = [];
|
||||
// let bounds = {
|
||||
// southWest: {
|
||||
// lat: 90,
|
||||
// lon: 180,
|
||||
// },
|
||||
// northEast: {
|
||||
// lat: -90,
|
||||
// lon: -180,
|
||||
// },
|
||||
// };
|
||||
// fileObservers.update(($fileObservers) => {
|
||||
// files.forEach((file, index) => {
|
||||
// if (file === null) {
|
||||
// return;
|
||||
// }
|
||||
// let id = `gpx-${index}-embed`;
|
||||
// file._data.id = id;
|
||||
// let statistics = new GPXStatisticsTree(file);
|
||||
// $fileObservers.set(
|
||||
// id,
|
||||
// readable({
|
||||
// file,
|
||||
// statistics,
|
||||
// })
|
||||
// );
|
||||
// ids.push(id);
|
||||
// let fileBounds = statistics.getStatisticsFor(new ListFileItem(id)).global
|
||||
// .bounds;
|
||||
// bounds.southWest.lat = Math.min(bounds.southWest.lat, fileBounds.southWest.lat);
|
||||
// bounds.southWest.lon = Math.min(bounds.southWest.lon, fileBounds.southWest.lon);
|
||||
// bounds.northEast.lat = Math.max(bounds.northEast.lat, fileBounds.northEast.lat);
|
||||
// bounds.northEast.lon = Math.max(bounds.northEast.lon, fileBounds.northEast.lon);
|
||||
// });
|
||||
// return $fileObservers;
|
||||
// });
|
||||
// $fileOrder = [...$fileOrder.filter((id) => !id.includes('embed')), ...ids];
|
||||
// selection.update(($selection) => {
|
||||
// $selection.clear();
|
||||
// ids.forEach((id) => {
|
||||
// $selection.toggle(new ListFileItem(id));
|
||||
// });
|
||||
// return $selection;
|
||||
// });
|
||||
// if (hash.length === 0) {
|
||||
// map.subscribe(($map) => {
|
||||
// if ($map) {
|
||||
// $map.fitBounds(
|
||||
// [
|
||||
// bounds.southWest.lon,
|
||||
// bounds.southWest.lat,
|
||||
// bounds.northEast.lon,
|
||||
// bounds.northEast.lat,
|
||||
// ],
|
||||
// {
|
||||
// padding: 80,
|
||||
// linear: true,
|
||||
// easing: () => 1,
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// if (
|
||||
// options.basemap !== $currentBasemap &&
|
||||
// allowedEmbeddingBasemaps.includes(options.basemap)
|
||||
// ) {
|
||||
// $currentBasemap = options.basemap;
|
||||
// }
|
||||
// if (options.distanceMarkers !== $distanceMarkers) {
|
||||
// $distanceMarkers = options.distanceMarkers;
|
||||
// }
|
||||
// if (options.directionMarkers !== $directionMarkers) {
|
||||
// $directionMarkers = options.directionMarkers;
|
||||
// }
|
||||
// if (options.distanceUnits !== $distanceUnits) {
|
||||
// $distanceUnits = options.distanceUnits;
|
||||
// }
|
||||
// if (options.velocityUnits !== $velocityUnits) {
|
||||
// $velocityUnits = options.velocityUnits;
|
||||
// }
|
||||
// if (options.temperatureUnits !== $temperatureUnits) {
|
||||
// $temperatureUnits = options.temperatureUnits;
|
||||
// }
|
||||
// if (options.theme !== $mode) {
|
||||
// setMode(options.theme);
|
||||
// }
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
prevSettings.distanceMarkers = $distanceMarkers;
|
||||
prevSettings.directionMarkers = $directionMarkers;
|
||||
prevSettings.distanceUnits = $distanceUnits;
|
||||
prevSettings.velocityUnits = $velocityUnits;
|
||||
prevSettings.temperatureUnits = $temperatureUnits;
|
||||
prevSettings.theme = $mode ?? 'system';
|
||||
prevSettings.distanceMarkers = distanceMarkers.value;
|
||||
prevSettings.directionMarkers = directionMarkers.value;
|
||||
prevSettings.distanceUnits = distanceUnits.value;
|
||||
prevSettings.velocityUnits = velocityUnits.value;
|
||||
prevSettings.temperatureUnits = temperatureUnits.value;
|
||||
prevSettings.theme = mode.current ?? 'system';
|
||||
});
|
||||
|
||||
$: if (browser && options) {
|
||||
applyOptions();
|
||||
}
|
||||
// $: if (browser && options) {
|
||||
// applyOptions();
|
||||
// }
|
||||
|
||||
$: if ($fileOrder) {
|
||||
updateGPXData();
|
||||
}
|
||||
// $: if ($fileOrder) {
|
||||
// updateGPXData();
|
||||
// }
|
||||
|
||||
onDestroy(() => {
|
||||
if ($distanceMarkers !== prevSettings.distanceMarkers) {
|
||||
$distanceMarkers = prevSettings.distanceMarkers;
|
||||
if (distanceMarkers.value !== prevSettings.distanceMarkers) {
|
||||
distanceMarkers.value = prevSettings.distanceMarkers;
|
||||
}
|
||||
|
||||
if ($directionMarkers !== prevSettings.directionMarkers) {
|
||||
$directionMarkers = prevSettings.directionMarkers;
|
||||
if (directionMarkers.value !== prevSettings.directionMarkers) {
|
||||
directionMarkers.value = prevSettings.directionMarkers;
|
||||
}
|
||||
|
||||
if ($distanceUnits !== prevSettings.distanceUnits) {
|
||||
$distanceUnits = prevSettings.distanceUnits;
|
||||
if (distanceUnits.value !== prevSettings.distanceUnits) {
|
||||
distanceUnits.value = prevSettings.distanceUnits;
|
||||
}
|
||||
|
||||
if ($velocityUnits !== prevSettings.velocityUnits) {
|
||||
$velocityUnits = prevSettings.velocityUnits;
|
||||
if (velocityUnits.value !== prevSettings.velocityUnits) {
|
||||
velocityUnits.value = prevSettings.velocityUnits;
|
||||
}
|
||||
|
||||
if ($temperatureUnits !== prevSettings.temperatureUnits) {
|
||||
$temperatureUnits = prevSettings.temperatureUnits;
|
||||
if (temperatureUnits.value !== prevSettings.temperatureUnits) {
|
||||
temperatureUnits.value = prevSettings.temperatureUnits;
|
||||
}
|
||||
|
||||
if ($mode !== prevSettings.theme) {
|
||||
if (mode.current !== prevSettings.theme) {
|
||||
setMode(prevSettings.theme);
|
||||
}
|
||||
|
||||
$selection.clear();
|
||||
$fileObservers.clear();
|
||||
$fileOrder = $fileOrder.filter((id) => !id.includes('embed'));
|
||||
// $selection.clear();
|
||||
// $fileObservers.clear();
|
||||
fileOrder.value = fileOrder.value.filter((id) => !id.includes('embed'));
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="absolute flex flex-col h-full w-full border rounded-xl overflow-clip">
|
||||
<div class="grow relative">
|
||||
<Map
|
||||
class="h-full {$fileObservers.size > 1 ? 'horizontal' : ''}"
|
||||
class="h-full {fileStateCollection.files.size > 1 ? 'horizontal' : ''}"
|
||||
accessToken={options.token}
|
||||
geocoder={false}
|
||||
geolocate={false}
|
||||
hash={useHash}
|
||||
/>
|
||||
<OpenIn bind:files={options.files} bind:ids={options.ids} />
|
||||
<LayerControl />
|
||||
<GPXLayers />
|
||||
{#if $fileObservers.size > 1}
|
||||
<OpenIn files={options.files} ids={options.ids} />
|
||||
<!-- <LayerControl /> -->
|
||||
<!-- <GPXLayers /> -->
|
||||
{#if fileStateCollection.files.size > 1}
|
||||
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
|
||||
<FileList orientation="horizontal" />
|
||||
<!-- <FileList orientation="horizontal" /> -->
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -246,14 +237,14 @@
|
||||
class="{options.elevation.show ? '' : 'h-10'} flex flex-row gap-2 px-2 sm:px-4"
|
||||
style={options.elevation.show ? `height: ${options.elevation.height}px` : ''}
|
||||
>
|
||||
<GPXStatistics
|
||||
<!-- <GPXStatistics
|
||||
{gpxStatistics}
|
||||
{slicedGPXStatistics}
|
||||
panelSize={options.elevation.height}
|
||||
orientation={options.elevation.show ? 'vertical' : 'horizontal'}
|
||||
/>
|
||||
/> -->
|
||||
{#if options.elevation.show}
|
||||
<ElevationProfile
|
||||
<!-- <ElevationProfile
|
||||
{gpxStatistics}
|
||||
{slicedGPXStatistics}
|
||||
additionalDatasets={[
|
||||
@@ -265,7 +256,7 @@
|
||||
].filter((dataset) => dataset !== null)}
|
||||
elevationFill={options.elevation.fill}
|
||||
showControls={options.elevation.controls}
|
||||
/>
|
||||
/> -->
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -11,8 +11,7 @@
|
||||
exportState,
|
||||
} from '$lib/components/export/utils.svelte';
|
||||
import { tool } from '$lib/components/toolbar/utils.svelte';
|
||||
import { gpxStatistics } from '$lib/stores';
|
||||
import { fileObservers } from '$lib/db';
|
||||
// import { gpxStatistics } from '$lib/stores';
|
||||
import {
|
||||
Download,
|
||||
Zap,
|
||||
@@ -23,10 +22,10 @@
|
||||
SquareActivity,
|
||||
} from '@lucide/svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { get } from 'svelte/store';
|
||||
import { GPXStatistics } from 'gpx';
|
||||
import { ListRootItem } from '$lib/components/file-list/FileList';
|
||||
import { ListRootItem } from '$lib/components/file-list/file-list';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
|
||||
let open = $derived(exportState.current !== ExportState.NONE);
|
||||
let exportOptions: Record<string, boolean> = $state({
|
||||
@@ -38,7 +37,36 @@
|
||||
extensions: false,
|
||||
});
|
||||
let hide: Record<string, boolean> = $derived.by(() => {
|
||||
if (exportState.current === ExportState.NONE) {
|
||||
// if (exportState.current === ExportState.NONE) {
|
||||
// return {
|
||||
// time: false,
|
||||
// hr: false,
|
||||
// cad: false,
|
||||
// atemp: false,
|
||||
// power: false,
|
||||
// extensions: false,
|
||||
// };
|
||||
// } else {
|
||||
// let statistics = $gpxStatistics;
|
||||
// if (exportState.current === ExportState.ALL) {
|
||||
// statistics = Array.from(fileStateCollection.files.values())
|
||||
// .map((file) => file.statistics)
|
||||
// .reduce((acc, cur) => {
|
||||
// if (cur !== undefined) {
|
||||
// acc.mergeWith(cur.getStatisticsFor(new ListRootItem()));
|
||||
// }
|
||||
// return acc;
|
||||
// }, new GPXStatistics());
|
||||
// }
|
||||
// return {
|
||||
// time: statistics.global.time.total === 0,
|
||||
// hr: statistics.global.hr.count === 0,
|
||||
// cad: statistics.global.cad.count === 0,
|
||||
// atemp: statistics.global.atemp.count === 0,
|
||||
// power: statistics.global.power.count === 0,
|
||||
// extensions: Object.keys(statistics.global.extensions).length === 0,
|
||||
// };
|
||||
// }
|
||||
return {
|
||||
time: false,
|
||||
hr: false,
|
||||
@@ -47,27 +75,6 @@
|
||||
power: false,
|
||||
extensions: false,
|
||||
};
|
||||
} else {
|
||||
let statistics = $gpxStatistics;
|
||||
if (exportState.current === ExportState.ALL) {
|
||||
statistics = Array.from($fileObservers.values())
|
||||
.map((file) => get(file)?.statistics)
|
||||
.reduce((acc, cur) => {
|
||||
if (cur !== undefined) {
|
||||
acc.mergeWith(cur.getStatisticsFor(new ListRootItem()));
|
||||
}
|
||||
return acc;
|
||||
}, new GPXStatistics());
|
||||
}
|
||||
return {
|
||||
time: statistics.global.time.total === 0,
|
||||
hr: statistics.global.hr.count === 0,
|
||||
cad: statistics.global.cad.count === 0,
|
||||
atemp: statistics.global.atemp.count === 0,
|
||||
power: statistics.global.power.count === 0,
|
||||
extensions: Object.keys(statistics.global.extensions).length === 0,
|
||||
};
|
||||
}
|
||||
});
|
||||
let exclude = $derived(Object.keys(exportOptions).filter((key) => !exportOptions[key]));
|
||||
|
||||
@@ -118,7 +125,7 @@
|
||||
}}
|
||||
>
|
||||
<Download size="16" class="mr-1" />
|
||||
{#if $fileObservers.size === 1 || (exportState.current === ExportState.SELECTION && $selection.size === 1)}
|
||||
{#if fileStateCollection.files.size === 1 || (exportState.current === ExportState.SELECTION && selection.value.size === 1)}
|
||||
{i18n._('menu.download_file')}
|
||||
{:else}
|
||||
{i18n._('menu.download_files')}
|
||||
|
@@ -1,12 +1,10 @@
|
||||
import { getFile, settings } from '$lib/db';
|
||||
import { applyToOrderedSelectedItemsFromFile } from '$lib/components/file-list/Selection';
|
||||
import { get } from 'svelte/store';
|
||||
import { applyToOrderedSelectedItemsFromFile } from '$lib/logic/selection.svelte';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import { buildGPX, type GPXFile } from 'gpx';
|
||||
import FileSaver from 'file-saver';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
const { fileOrder } = settings;
|
||||
|
||||
export enum ExportState {
|
||||
NONE,
|
||||
SELECTION,
|
||||
@@ -22,7 +20,7 @@ async function exportFiles(fileIds: string[], exclude: string[]) {
|
||||
} else {
|
||||
const firstFileId = fileIds.at(0);
|
||||
if (firstFileId != null) {
|
||||
const file = getFile(firstFileId);
|
||||
const file = fileStateCollection.getFile(firstFileId);
|
||||
if (file) {
|
||||
exportFile(file, exclude);
|
||||
}
|
||||
@@ -39,7 +37,7 @@ export async function exportSelectedFiles(exclude: string[]) {
|
||||
}
|
||||
|
||||
export async function exportAllFiles(exclude: string[]) {
|
||||
await exportFiles(get(fileOrder), exclude);
|
||||
await exportFiles(settings.fileOrder.value, exclude);
|
||||
}
|
||||
|
||||
function exportFile(file: GPXFile, exclude: string[]) {
|
||||
@@ -50,7 +48,7 @@ function exportFile(file: GPXFile, exclude: string[]) {
|
||||
async function exportFilesAsZip(fileIds: string[], exclude: string[]) {
|
||||
const zip = new JSZip();
|
||||
for (const fileId of fileIds) {
|
||||
const file = getFile(fileId);
|
||||
const file = fileStateCollection.getFile(fileId);
|
||||
if (file) {
|
||||
const gpx = buildGPX(file, exclude);
|
||||
let filename = file.metadata.name;
|
||||
|
@@ -4,7 +4,7 @@
|
||||
import FileListNode from './FileListNode.svelte';
|
||||
import { fileObservers, settings } from '$lib/db';
|
||||
import { setContext } from 'svelte';
|
||||
import { ListFileItem, ListLevel, ListRootItem, allowedPastes } from './FileList';
|
||||
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';
|
||||
|
@@ -20,7 +20,7 @@
|
||||
ListWaypointsItem,
|
||||
type ListItem,
|
||||
type ListTrackItem,
|
||||
} from './FileList';
|
||||
} from './file-list';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { selection } from './Selection';
|
||||
|
||||
|
@@ -20,7 +20,7 @@
|
||||
allowedMoves,
|
||||
moveItems,
|
||||
type ListItem,
|
||||
} from './FileList';
|
||||
} from './file-list';
|
||||
import { selection } from './Selection';
|
||||
import { isMac } from '$lib/utils';
|
||||
|
||||
|
@@ -27,7 +27,7 @@
|
||||
ListWaypointItem,
|
||||
allowedPastes,
|
||||
type ListItem,
|
||||
} from './FileList';
|
||||
} from './file-list';
|
||||
import {
|
||||
copied,
|
||||
copySelection,
|
||||
@@ -40,7 +40,7 @@
|
||||
} from './Selection';
|
||||
import { getContext } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { allHidden, embedding, gpxLayers } from '$lib/stores';
|
||||
import { allHidden, gpxLayers } from '$lib/stores';
|
||||
import { map, centerMapOnSelection } from '$lib/components/map/map.svelte';
|
||||
import { GPXTreeElement, Track, type AnyGPXTreeElement, Waypoint, GPXFile } from 'gpx';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
@@ -62,6 +62,7 @@
|
||||
} = $props();
|
||||
|
||||
let orientation = getContext<'vertical' | 'horizontal'>('orientation');
|
||||
let embedding = getContext<boolean>('embedding');
|
||||
|
||||
let singleSelection = $derived($selection.size === 1);
|
||||
|
||||
@@ -169,7 +170,7 @@
|
||||
? 'text-muted-foreground'
|
||||
: ''}"
|
||||
oncontextmenu={(e) => {
|
||||
if ($embedding) {
|
||||
if (embedding) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
|
@@ -5,7 +5,7 @@
|
||||
import type { GPXFileWithStatistics } from '$lib/db';
|
||||
import { getContext } from 'svelte';
|
||||
import type { Readable } from 'svelte/store';
|
||||
import { ListFileItem } from './FileList';
|
||||
import { ListFileItem } from './file-list';
|
||||
|
||||
let {
|
||||
file,
|
||||
|
@@ -1,375 +0,0 @@
|
||||
import { get, writable } from 'svelte/store';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListItem,
|
||||
ListRootItem,
|
||||
ListTrackItem,
|
||||
ListTrackSegmentItem,
|
||||
ListWaypointItem,
|
||||
ListLevel,
|
||||
sortItems,
|
||||
ListWaypointsItem,
|
||||
moveItems,
|
||||
} from './FileList';
|
||||
import { fileObservers, getFile, getFileIds } from '$lib/db';
|
||||
// import { settings } from '$lib/logic/settings.svelte';
|
||||
|
||||
export class SelectionTreeType {
|
||||
item: ListItem;
|
||||
selected: boolean;
|
||||
children: {
|
||||
[key: string | number]: SelectionTreeType;
|
||||
};
|
||||
size: number = 0;
|
||||
|
||||
constructor(item: ListItem) {
|
||||
this.item = item;
|
||||
this.selected = false;
|
||||
this.children = {};
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.selected = false;
|
||||
for (let key in this.children) {
|
||||
this.children[key].clear();
|
||||
}
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
_setOrToggle(item: ListItem, value?: boolean) {
|
||||
if (item.level === this.item.level) {
|
||||
let newSelected = value === undefined ? !this.selected : value;
|
||||
if (this.selected !== newSelected) {
|
||||
this.selected = newSelected;
|
||||
this.size += this.selected ? 1 : -1;
|
||||
}
|
||||
} else {
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (!this.children.hasOwnProperty(id)) {
|
||||
this.children[id] = new SelectionTreeType(this.item.extend(id));
|
||||
}
|
||||
this.size -= this.children[id].size;
|
||||
this.children[id]._setOrToggle(item, value);
|
||||
this.size += this.children[id].size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set(item: ListItem, value: boolean) {
|
||||
this._setOrToggle(item, value);
|
||||
}
|
||||
|
||||
toggle(item: ListItem) {
|
||||
this._setOrToggle(item);
|
||||
}
|
||||
|
||||
has(item: ListItem): boolean {
|
||||
if (item.level === this.item.level) {
|
||||
return this.selected;
|
||||
} else {
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
return this.children[id].has(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasAnyParent(item: ListItem, self: boolean = true): boolean {
|
||||
if (
|
||||
this.selected &&
|
||||
this.item.level <= item.level &&
|
||||
(self || this.item.level < item.level)
|
||||
) {
|
||||
return this.selected;
|
||||
}
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
return this.children[id].hasAnyParent(item, self);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasAnyChildren(item: ListItem, self: boolean = true, ignoreIds?: (string | number)[]): boolean {
|
||||
if (
|
||||
this.selected &&
|
||||
this.item.level >= item.level &&
|
||||
(self || this.item.level > item.level)
|
||||
) {
|
||||
return this.selected;
|
||||
}
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (ignoreIds === undefined || ignoreIds.indexOf(id) === -1) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
return this.children[id].hasAnyChildren(item, self, ignoreIds);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let key in this.children) {
|
||||
if (ignoreIds === undefined || ignoreIds.indexOf(key) === -1) {
|
||||
if (this.children[key].hasAnyChildren(item, self, ignoreIds)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getSelected(selection: ListItem[] = []): ListItem[] {
|
||||
if (this.selected) {
|
||||
selection.push(this.item);
|
||||
}
|
||||
for (let key in this.children) {
|
||||
this.children[key].getSelected(selection);
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
|
||||
forEach(callback: (item: ListItem) => void) {
|
||||
if (this.selected) {
|
||||
callback(this.item);
|
||||
}
|
||||
for (let key in this.children) {
|
||||
this.children[key].forEach(callback);
|
||||
}
|
||||
}
|
||||
|
||||
getChild(id: string | number): SelectionTreeType | undefined {
|
||||
return this.children[id];
|
||||
}
|
||||
|
||||
deleteChild(id: string | number) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
this.size -= this.children[id].size;
|
||||
delete this.children[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const selection = writable<SelectionTreeType>(new SelectionTreeType(new ListRootItem()));
|
||||
|
||||
export function selectItem(item: ListItem) {
|
||||
selection.update(($selection) => {
|
||||
$selection.clear();
|
||||
$selection.set(item, true);
|
||||
return $selection;
|
||||
});
|
||||
}
|
||||
|
||||
export function selectFile(fileId: string) {
|
||||
selectItem(new ListFileItem(fileId));
|
||||
}
|
||||
|
||||
export function addSelectItem(item: ListItem) {
|
||||
selection.update(($selection) => {
|
||||
$selection.toggle(item);
|
||||
return $selection;
|
||||
});
|
||||
}
|
||||
|
||||
export function addSelectFile(fileId: string) {
|
||||
addSelectItem(new ListFileItem(fileId));
|
||||
}
|
||||
|
||||
export function selectAll() {
|
||||
selection.update(($selection) => {
|
||||
let item: ListItem = new ListRootItem();
|
||||
$selection.forEach((i) => {
|
||||
item = i;
|
||||
});
|
||||
|
||||
if (item instanceof ListRootItem || item instanceof ListFileItem) {
|
||||
$selection.clear();
|
||||
get(fileObservers).forEach((_file, fileId) => {
|
||||
$selection.set(new ListFileItem(fileId), true);
|
||||
});
|
||||
} else if (item instanceof ListTrackItem) {
|
||||
let file = getFile(item.getFileId());
|
||||
if (file) {
|
||||
file.trk.forEach((_track, trackId) => {
|
||||
$selection.set(new ListTrackItem(item.getFileId(), trackId), true);
|
||||
});
|
||||
}
|
||||
} else if (item instanceof ListTrackSegmentItem) {
|
||||
let file = getFile(item.getFileId());
|
||||
if (file) {
|
||||
file.trk[item.getTrackIndex()].trkseg.forEach((_segment, segmentId) => {
|
||||
$selection.set(
|
||||
new ListTrackSegmentItem(item.getFileId(), item.getTrackIndex(), segmentId),
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
} else if (item instanceof ListWaypointItem) {
|
||||
let file = getFile(item.getFileId());
|
||||
if (file) {
|
||||
file.wpt.forEach((_waypoint, waypointId) => {
|
||||
$selection.set(new ListWaypointItem(item.getFileId(), waypointId), true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return $selection;
|
||||
});
|
||||
}
|
||||
|
||||
export function getOrderedSelection(reverse: boolean = false): ListItem[] {
|
||||
let selected: ListItem[] = [];
|
||||
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
||||
selected.push(...items);
|
||||
}, reverse);
|
||||
return selected;
|
||||
}
|
||||
|
||||
export function applyToOrderedItemsFromFile(
|
||||
selectedItems: ListItem[],
|
||||
callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void,
|
||||
reverse: boolean = true
|
||||
) {
|
||||
// settings.fileOrder.value.forEach((fileId) => {
|
||||
// let level: ListLevel | undefined = undefined;
|
||||
// let items: ListItem[] = [];
|
||||
// selectedItems.forEach((item) => {
|
||||
// if (item.getFileId() === fileId) {
|
||||
// level = item.level;
|
||||
// if (
|
||||
// item instanceof ListFileItem ||
|
||||
// item instanceof ListTrackItem ||
|
||||
// item instanceof ListTrackSegmentItem ||
|
||||
// item instanceof ListWaypointsItem ||
|
||||
// item instanceof ListWaypointItem
|
||||
// ) {
|
||||
// items.push(item);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// if (items.length > 0) {
|
||||
// sortItems(items, reverse);
|
||||
// callback(fileId, level, items);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
export function applyToOrderedSelectedItemsFromFile(
|
||||
callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void,
|
||||
reverse: boolean = true
|
||||
) {
|
||||
applyToOrderedItemsFromFile(get(selection).getSelected(), callback, reverse);
|
||||
}
|
||||
|
||||
export const copied = writable<ListItem[] | undefined>(undefined);
|
||||
export const cut = writable(false);
|
||||
|
||||
export function copySelection(): boolean {
|
||||
let selected = get(selection).getSelected();
|
||||
if (selected.length > 0) {
|
||||
copied.set(selected);
|
||||
cut.set(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function cutSelection() {
|
||||
if (copySelection()) {
|
||||
cut.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
function resetCopied() {
|
||||
copied.set(undefined);
|
||||
cut.set(false);
|
||||
}
|
||||
|
||||
export function pasteSelection() {
|
||||
let fromItems = get(copied);
|
||||
if (fromItems === undefined || fromItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected = get(selection).getSelected();
|
||||
if (selected.length === 0) {
|
||||
selected = [new ListRootItem()];
|
||||
}
|
||||
|
||||
let fromParent = fromItems[0].getParent();
|
||||
let toParent = selected[selected.length - 1];
|
||||
|
||||
let startIndex: number | undefined = undefined;
|
||||
|
||||
if (fromItems[0].level === toParent.level) {
|
||||
if (
|
||||
toParent instanceof ListTrackItem ||
|
||||
toParent instanceof ListTrackSegmentItem ||
|
||||
toParent instanceof ListWaypointItem
|
||||
) {
|
||||
startIndex = toParent.getId() + 1;
|
||||
}
|
||||
toParent = toParent.getParent();
|
||||
}
|
||||
|
||||
let toItems: ListItem[] = [];
|
||||
if (toParent.level === ListLevel.ROOT) {
|
||||
let fileIds = getFileIds(fromItems.length);
|
||||
fileIds.forEach((fileId) => {
|
||||
toItems.push(new ListFileItem(fileId));
|
||||
});
|
||||
} else {
|
||||
let toFile = getFile(toParent.getFileId());
|
||||
if (toFile) {
|
||||
fromItems.forEach((item, index) => {
|
||||
if (toParent instanceof ListFileItem) {
|
||||
if (item instanceof ListTrackItem || item instanceof ListTrackSegmentItem) {
|
||||
toItems.push(
|
||||
new ListTrackItem(
|
||||
toParent.getFileId(),
|
||||
(startIndex ?? toFile.trk.length) + index
|
||||
)
|
||||
);
|
||||
} else if (item instanceof ListWaypointsItem) {
|
||||
toItems.push(new ListWaypointsItem(toParent.getFileId()));
|
||||
} else if (item instanceof ListWaypointItem) {
|
||||
toItems.push(
|
||||
new ListWaypointItem(
|
||||
toParent.getFileId(),
|
||||
(startIndex ?? toFile.wpt.length) + index
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (toParent instanceof ListTrackItem) {
|
||||
if (item instanceof ListTrackSegmentItem) {
|
||||
let toTrackIndex = toParent.getTrackIndex();
|
||||
toItems.push(
|
||||
new ListTrackSegmentItem(
|
||||
toParent.getFileId(),
|
||||
toTrackIndex,
|
||||
(startIndex ?? toFile.trk[toTrackIndex].trkseg.length) + index
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (toParent instanceof ListWaypointsItem) {
|
||||
if (item instanceof ListWaypointItem) {
|
||||
toItems.push(
|
||||
new ListWaypointItem(
|
||||
toParent.getFileId(),
|
||||
(startIndex ?? toFile.wpt.length) + index
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (fromItems.length === toItems.length) {
|
||||
moveItems(fromParent, toParent, fromItems, toItems, get(cut));
|
||||
resetCopied();
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
import { dbUtils, getFile } from '$lib/db';
|
||||
import { freeze } from 'immer';
|
||||
import { GPXFile, Track, TrackSegment, Waypoint } from 'gpx';
|
||||
import { selection } from './Selection';
|
||||
import { newGPXFile } from '$lib/stores';
|
||||
// import { dbUtils, getFile } from '$lib/db';
|
||||
// import { freeze } from 'immer';
|
||||
// import { GPXFile, Track, TrackSegment, Waypoint } from 'gpx';
|
||||
// import { selection } from './Selection';
|
||||
// import { newGPXFile } from '$lib/stores';
|
||||
|
||||
export enum ListLevel {
|
||||
ROOT,
|
||||
@@ -32,6 +32,7 @@ export const allowedPastes: Record<ListLevel, ListLevel[]> = {
|
||||
};
|
||||
|
||||
export abstract class ListItem {
|
||||
[x: string]: any;
|
||||
level: ListLevel;
|
||||
|
||||
constructor(level: ListLevel) {
|
||||
@@ -321,163 +322,3 @@ export function sortItems(items: ListItem[], reverse: boolean = false) {
|
||||
items.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
export function moveItems(
|
||||
fromParent: ListItem,
|
||||
toParent: ListItem,
|
||||
fromItems: ListItem[],
|
||||
toItems: ListItem[],
|
||||
remove: boolean = true
|
||||
) {
|
||||
if (fromItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sortItems(fromItems, false);
|
||||
sortItems(toItems, false);
|
||||
|
||||
let context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[] = [];
|
||||
fromItems.forEach((item) => {
|
||||
let file = getFile(item.getFileId());
|
||||
if (file) {
|
||||
if (item instanceof ListFileItem) {
|
||||
context.push(file.clone());
|
||||
} else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) {
|
||||
context.push(file.trk[item.getTrackIndex()].clone());
|
||||
} else if (
|
||||
item instanceof ListTrackSegmentItem &&
|
||||
item.getTrackIndex() < file.trk.length &&
|
||||
item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length
|
||||
) {
|
||||
context.push(file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()].clone());
|
||||
} else if (item instanceof ListWaypointsItem) {
|
||||
context.push(file.wpt.map((wpt) => wpt.clone()));
|
||||
} else if (
|
||||
item instanceof ListWaypointItem &&
|
||||
item.getWaypointIndex() < file.wpt.length
|
||||
) {
|
||||
context.push(file.wpt[item.getWaypointIndex()].clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (remove && !(fromParent instanceof ListRootItem)) {
|
||||
sortItems(fromItems, true);
|
||||
}
|
||||
|
||||
let files = [fromParent.getFileId(), toParent.getFileId()];
|
||||
let callbacks = [
|
||||
(file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
|
||||
fromItems.forEach((item) => {
|
||||
if (item instanceof ListTrackItem) {
|
||||
file.replaceTracks(item.getTrackIndex(), item.getTrackIndex(), []);
|
||||
} else if (item instanceof ListTrackSegmentItem) {
|
||||
file.replaceTrackSegments(
|
||||
item.getTrackIndex(),
|
||||
item.getSegmentIndex(),
|
||||
item.getSegmentIndex(),
|
||||
[]
|
||||
);
|
||||
} else if (item instanceof ListWaypointsItem) {
|
||||
file.replaceWaypoints(0, file.wpt.length - 1, []);
|
||||
} else if (item instanceof ListWaypointItem) {
|
||||
file.replaceWaypoints(item.getWaypointIndex(), item.getWaypointIndex(), []);
|
||||
}
|
||||
});
|
||||
},
|
||||
(file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
|
||||
toItems.forEach((item, i) => {
|
||||
if (item instanceof ListTrackItem) {
|
||||
if (context[i] instanceof Track) {
|
||||
file.replaceTracks(item.getTrackIndex(), item.getTrackIndex() - 1, [
|
||||
context[i],
|
||||
]);
|
||||
} else if (context[i] instanceof TrackSegment) {
|
||||
file.replaceTracks(item.getTrackIndex(), item.getTrackIndex() - 1, [
|
||||
new Track({
|
||||
trkseg: [context[i]],
|
||||
}),
|
||||
]);
|
||||
}
|
||||
} else if (
|
||||
item instanceof ListTrackSegmentItem &&
|
||||
context[i] instanceof TrackSegment
|
||||
) {
|
||||
file.replaceTrackSegments(
|
||||
item.getTrackIndex(),
|
||||
item.getSegmentIndex(),
|
||||
item.getSegmentIndex() - 1,
|
||||
[context[i]]
|
||||
);
|
||||
} else if (item instanceof ListWaypointsItem) {
|
||||
if (
|
||||
Array.isArray(context[i]) &&
|
||||
context[i].length > 0 &&
|
||||
context[i][0] instanceof Waypoint
|
||||
) {
|
||||
file.replaceWaypoints(file.wpt.length, file.wpt.length - 1, context[i]);
|
||||
} else if (context[i] instanceof Waypoint) {
|
||||
file.replaceWaypoints(file.wpt.length, file.wpt.length - 1, [context[i]]);
|
||||
}
|
||||
} else if (item instanceof ListWaypointItem && context[i] instanceof Waypoint) {
|
||||
file.replaceWaypoints(item.getWaypointIndex(), item.getWaypointIndex() - 1, [
|
||||
context[i],
|
||||
]);
|
||||
}
|
||||
});
|
||||
},
|
||||
];
|
||||
|
||||
if (fromParent instanceof ListRootItem) {
|
||||
files = [];
|
||||
callbacks = [];
|
||||
} else if (!remove) {
|
||||
files.splice(0, 1);
|
||||
callbacks.splice(0, 1);
|
||||
}
|
||||
|
||||
dbUtils.applyEachToFilesAndGlobal(
|
||||
files,
|
||||
callbacks,
|
||||
(files, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
|
||||
toItems.forEach((item, i) => {
|
||||
if (item instanceof ListFileItem) {
|
||||
if (context[i] instanceof GPXFile) {
|
||||
let newFile = context[i];
|
||||
if (remove) {
|
||||
files.delete(newFile._data.id);
|
||||
}
|
||||
newFile._data.id = item.getFileId();
|
||||
files.set(item.getFileId(), freeze(newFile));
|
||||
} else if (context[i] instanceof Track) {
|
||||
let newFile = newGPXFile();
|
||||
newFile._data.id = item.getFileId();
|
||||
if (context[i].name) {
|
||||
newFile.metadata.name = context[i].name;
|
||||
}
|
||||
newFile.replaceTracks(0, 0, [context[i]]);
|
||||
files.set(item.getFileId(), freeze(newFile));
|
||||
} else if (context[i] instanceof TrackSegment) {
|
||||
let newFile = newGPXFile();
|
||||
newFile._data.id = item.getFileId();
|
||||
newFile.replaceTracks(0, 0, [
|
||||
new Track({
|
||||
trkseg: [context[i]],
|
||||
}),
|
||||
]);
|
||||
files.set(item.getFileId(), freeze(newFile));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
context
|
||||
);
|
||||
|
||||
selection.update(($selection) => {
|
||||
$selection.clear();
|
||||
toItems.forEach((item) => {
|
||||
$selection.set(item, true);
|
||||
});
|
||||
return $selection;
|
||||
});
|
||||
}
|
@@ -6,7 +6,7 @@
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import { dbUtils } from '$lib/db';
|
||||
import { Save } from '@lucide/svelte';
|
||||
import { ListFileItem, ListTrackItem, type ListItem } from '../FileList';
|
||||
import { ListFileItem, ListTrackItem, type ListItem } from '../file-list';
|
||||
import { GPXTreeElement, Track, type AnyGPXTreeElement, Waypoint, GPXFile } from 'gpx';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { editMetadata } from '$lib/components/file-list/metadata/utils.svelte';
|
||||
|
@@ -6,7 +6,11 @@
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import { dbUtils, getFile, settings } from '$lib/db';
|
||||
import { Save } from '@lucide/svelte';
|
||||
import { ListFileItem, ListTrackItem, type ListItem } from '$lib/components/file-list/FileList';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListTrackItem,
|
||||
type ListItem,
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import { editStyle } from '$lib/components/file-list/style/utils.svelte';
|
||||
import { selection } from '../Selection';
|
||||
import { gpxLayers } from '$lib/stores';
|
||||
|
@@ -3,7 +3,6 @@ import { settings, type GPXFileWithStatistics, dbUtils } from '$lib/db';
|
||||
import { get, type Readable } from 'svelte/store';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { waypointPopup, deleteWaypoint, trackpointPopup } from './GPXLayerPopup';
|
||||
import { addSelectItem, selectItem, selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListTrackSegmentItem,
|
||||
ListWaypointItem,
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
ListTrackItem,
|
||||
ListFileItem,
|
||||
ListRootItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import {
|
||||
getClosestLinePoint,
|
||||
getElevation,
|
||||
@@ -20,7 +19,7 @@ import {
|
||||
setPointerCursor,
|
||||
setScissorsCursor,
|
||||
} from '$lib/utils';
|
||||
import { selectedWaypoint } from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import { selectedWaypoint } from '$lib/components/toolbar/tools/waypoint/utils.svelte';
|
||||
import { MapPin, Square } from 'lucide-static';
|
||||
import { getSymbolKey, symbols } from '$lib/assets/symbols';
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import type { LayerTreeType } from '$lib/assets/layers';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export function anySelectedLayer(node: LayerTreeType) {
|
||||
return (
|
||||
|
@@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Tool, tool } from '$lib/components/toolbar/utils.svelte';
|
||||
import { settings } from '$lib/db';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import Routing from '$lib/components/toolbar/tools/routing/Routing.svelte';
|
||||
import Scissors from '$lib/components/toolbar/tools/scissors/Scissors.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
import Time from '$lib/components/toolbar/tools/Time.svelte';
|
||||
import Merge from '$lib/components/toolbar/tools/Merge.svelte';
|
||||
import Extract from '$lib/components/toolbar/tools/Extract.svelte';
|
||||
@@ -14,12 +13,20 @@
|
||||
import RoutingControlPopup from '$lib/components/toolbar/tools/routing/RoutingControlPopup.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
|
||||
let {
|
||||
popupElement,
|
||||
popup,
|
||||
class: className = '',
|
||||
}: {
|
||||
popupElement: HTMLDivElement;
|
||||
popup: mapboxgl.Popup;
|
||||
class: string;
|
||||
} = $props();
|
||||
|
||||
const { minimizeRoutingMenu } = settings;
|
||||
|
||||
let popupElement: HTMLElement;
|
||||
let popup: mapboxgl.Popup;
|
||||
|
||||
onMount(() => {
|
||||
popup = new mapboxgl.Popup({
|
||||
closeButton: false,
|
||||
@@ -31,12 +38,16 @@
|
||||
</script>
|
||||
|
||||
{#if tool.current !== null}
|
||||
<div class="translate-x-1 h-full animate-in animate-out {$$props.class ?? ''}">
|
||||
<div class="translate-x-1 h-full animate-in animate-out {className}">
|
||||
<div class="rounded-md shadow-md pointer-events-auto">
|
||||
<Card.Root class="rounded-md border-none">
|
||||
<Card.Content class="p-2.5">
|
||||
{#if tool.current === Tool.ROUTING}
|
||||
<Routing {popup} {popupElement} bind:minimized={$minimizeRoutingMenu} />
|
||||
<Routing
|
||||
{popup}
|
||||
{popupElement}
|
||||
bind:minimized={minimizeRoutingMenu.value}
|
||||
/>
|
||||
{:else if tool.current === Tool.SCISSORS}
|
||||
<Scissors />
|
||||
{:else if tool.current === Tool.WAYPOINT}
|
||||
|
@@ -15,21 +15,25 @@
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { getURLForLanguage, resetCursor, setCrosshairCursor } from '$lib/utils';
|
||||
import { Trash2 } from '@lucide/svelte';
|
||||
import { map } from '$lib/components/map/map.svelte';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { dbUtils } from '$lib/db';
|
||||
import { map } from '$lib/components/map/utils.svelte';
|
||||
import type { GeoJSONSource } from 'mapbox-gl';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
let cleanType = CleanType.INSIDE;
|
||||
let deleteTrackpoints = true;
|
||||
let deleteWaypoints = true;
|
||||
let rectangleCoordinates: mapboxgl.LngLat[] = [];
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
function updateRectangle() {
|
||||
if (map.current) {
|
||||
let cleanType = $state(CleanType.INSIDE);
|
||||
let deleteTrackpoints = $state(true);
|
||||
let deleteWaypoints = $state(true);
|
||||
let rectangleCoordinates: mapboxgl.LngLat[] = $state([]);
|
||||
|
||||
$effect(() => {
|
||||
if (map.value) {
|
||||
if (rectangleCoordinates.length != 2) {
|
||||
if (map.current.getLayer('rectangle')) {
|
||||
map.current.removeLayer('rectangle');
|
||||
if (map.value.getLayer('rectangle')) {
|
||||
map.value.removeLayer('rectangle');
|
||||
}
|
||||
} else {
|
||||
let data: GeoJSON.Feature = {
|
||||
@@ -48,17 +52,17 @@
|
||||
},
|
||||
properties: {},
|
||||
};
|
||||
let source: GeoJSONSource | undefined = map.current.getSource('rectangle');
|
||||
let source: GeoJSONSource | undefined = map.value.getSource('rectangle');
|
||||
if (source) {
|
||||
source.setData(data);
|
||||
} else {
|
||||
map.current.addSource('rectangle', {
|
||||
map.value.addSource('rectangle', {
|
||||
type: 'geojson',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
if (!map.current.getLayer('rectangle')) {
|
||||
map.current.addLayer({
|
||||
if (!map.value.getLayer('rectangle')) {
|
||||
map.value.addLayer({
|
||||
id: 'rectangle',
|
||||
type: 'fill',
|
||||
source: 'rectangle',
|
||||
@@ -70,11 +74,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: if (rectangleCoordinates) {
|
||||
updateRectangle();
|
||||
}
|
||||
});
|
||||
|
||||
let mousedown = false;
|
||||
function onMouseDown(e: any) {
|
||||
@@ -93,42 +93,42 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (map.current) {
|
||||
setCrosshairCursor(map.current.getCanvas());
|
||||
map.current.on('mousedown', onMouseDown);
|
||||
map.current.on('mousemove', onMouseMove);
|
||||
map.current.on('mouseup', onMouseUp);
|
||||
map.current.on('touchstart', onMouseDown);
|
||||
map.current.on('touchmove', onMouseMove);
|
||||
map.current.on('touchend', onMouseUp);
|
||||
map.current.dragPan.disable();
|
||||
if (map.value) {
|
||||
setCrosshairCursor(map.value.getCanvas());
|
||||
map.value.on('mousedown', onMouseDown);
|
||||
map.value.on('mousemove', onMouseMove);
|
||||
map.value.on('mouseup', onMouseUp);
|
||||
map.value.on('touchstart', onMouseDown);
|
||||
map.value.on('touchmove', onMouseMove);
|
||||
map.value.on('touchend', onMouseUp);
|
||||
map.value.dragPan.disable();
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (map.current) {
|
||||
resetCursor(map.current.getCanvas());
|
||||
map.current.off('mousedown', onMouseDown);
|
||||
map.current.off('mousemove', onMouseMove);
|
||||
map.current.off('mouseup', onMouseUp);
|
||||
map.current.off('touchstart', onMouseDown);
|
||||
map.current.off('touchmove', onMouseMove);
|
||||
map.current.off('touchend', onMouseUp);
|
||||
map.current.dragPan.enable();
|
||||
if (map.value) {
|
||||
resetCursor(map.value.getCanvas());
|
||||
map.value.off('mousedown', onMouseDown);
|
||||
map.value.off('mousemove', onMouseMove);
|
||||
map.value.off('mouseup', onMouseUp);
|
||||
map.value.off('touchstart', onMouseDown);
|
||||
map.value.off('touchmove', onMouseMove);
|
||||
map.value.off('touchend', onMouseUp);
|
||||
map.value.dragPan.enable();
|
||||
|
||||
if (map.current.getLayer('rectangle')) {
|
||||
map.current.removeLayer('rectangle');
|
||||
if (map.value.getLayer('rectangle')) {
|
||||
map.value.removeLayer('rectangle');
|
||||
}
|
||||
if (map.current.getSource('rectangle')) {
|
||||
map.current.removeSource('rectangle');
|
||||
if (map.value.getSource('rectangle')) {
|
||||
map.value.removeSource('rectangle');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$: validSelection = $selection.size > 0;
|
||||
let validSelection = $derived(selection.value.size > 0);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 items-center {$$props.class ?? ''}">
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 items-center {props.class ?? ''}">
|
||||
<fieldset class="flex flex-col gap-3">
|
||||
<div class="flex flex-row items-center gap-[6.4px] h-3">
|
||||
<Checkbox id="delete-trkpt" bind:checked={deleteTrackpoints} class="scale-90" />
|
||||
@@ -158,7 +158,7 @@
|
||||
class="w-full"
|
||||
disabled={!validSelection || rectangleCoordinates.length != 2}
|
||||
onclick={() => {
|
||||
dbUtils.cleanSelection(
|
||||
fileActions.cleanSelection(
|
||||
[
|
||||
{
|
||||
lat: Math.min(rectangleCoordinates[0].lat, rectangleCoordinates[1].lat),
|
||||
|
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { MountainSnow } from '@lucide/svelte';
|
||||
import { dbUtils } from '$lib/db';
|
||||
import { map } from '$lib/components/map/map.svelte';
|
||||
import { map } from '$lib/components/map/utils.svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
let validSelection = $derived($selection.size > 0);
|
||||
let validSelection = $derived(selection.value.size > 0);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||
@@ -21,8 +21,8 @@
|
||||
class="whitespace-normal h-fit"
|
||||
disabled={!validSelection}
|
||||
onclick={async () => {
|
||||
if (map.current) {
|
||||
dbUtils.addElevationToSelection(map.current);
|
||||
if (map.value) {
|
||||
fileActions.addElevationToSelection(map.value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@@ -1,22 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Ungroup } from '@lucide/svelte';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListTrackItem,
|
||||
ListTrackSegmentItem,
|
||||
ListWaypointItem,
|
||||
ListWaypointsItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { dbUtils, getFile } from '$lib/db';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
$: validSelection =
|
||||
$selection.size > 0 &&
|
||||
$selection.getSelected().every((item) => {
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
let validSelection = $derived(
|
||||
selection.value.size > 0 &&
|
||||
selection.value.getSelected().every((item) => {
|
||||
if (
|
||||
item instanceof ListWaypointsItem ||
|
||||
item instanceof ListWaypointItem ||
|
||||
@@ -24,7 +29,7 @@
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let file = getFile(item.getFileId());
|
||||
let file = fileStateCollection.getFile(item.getFileId());
|
||||
if (file) {
|
||||
if (item instanceof ListFileItem) {
|
||||
return file.getSegments().length > 1;
|
||||
@@ -35,11 +40,12 @@
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {$$props.class ?? ''}">
|
||||
<Button variant="outline" disabled={!validSelection} onclick={dbUtils.extractSelection}>
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||
<Button variant="outline" disabled={!validSelection} onclick={fileActions.extractSelection}>
|
||||
<Ungroup size="16" class="mr-1" />
|
||||
{i18n._('toolbar.extract.button')}
|
||||
</Button>
|
||||
|
@@ -6,58 +6,58 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { ListFileItem, ListTrackItem } from '$lib/components/file-list/FileList';
|
||||
import { ListFileItem, ListTrackItem } from '$lib/components/file-list/file-list';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import * as RadioGroup from '$lib/components/ui/radio-group';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { dbUtils, getFile } from '$lib/db';
|
||||
import { Group } from '@lucide/svelte';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import Shortcut from '$lib/components/Shortcut.svelte';
|
||||
import { gpxStatistics } from '$lib/stores';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
let canMergeTraces = false;
|
||||
let canMergeContents = false;
|
||||
let removeGaps = false;
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
$: if ($selection.size > 1) {
|
||||
canMergeTraces = true;
|
||||
} else if ($selection.size === 1) {
|
||||
let selected = $selection.getSelected()[0];
|
||||
let canMergeTraces = $derived.by(() => {
|
||||
if (selection.value.size > 1) {
|
||||
return true;
|
||||
} else if (selection.value.size === 1) {
|
||||
let selected = selection.value.getSelected()[0];
|
||||
if (selected instanceof ListFileItem) {
|
||||
let file = getFile(selected.getFileId());
|
||||
let file = fileStateCollection.getFile(selected.getFileId());
|
||||
if (file) {
|
||||
canMergeTraces = file.getSegments().length > 1;
|
||||
} else {
|
||||
canMergeTraces = false;
|
||||
return file.getSegments().length > 1;
|
||||
}
|
||||
} else if (selected instanceof ListTrackItem) {
|
||||
let trackIndex = selected.getTrackIndex();
|
||||
let file = getFile(selected.getFileId());
|
||||
let file = fileStateCollection.getFile(selected.getFileId());
|
||||
if (file && trackIndex < file.trk.length) {
|
||||
canMergeTraces = file.trk[trackIndex].getSegments().length > 1;
|
||||
} else {
|
||||
canMergeTraces = false;
|
||||
}
|
||||
} else {
|
||||
canMergeContents = false;
|
||||
return file.trk[trackIndex].getSegments().length > 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$: canMergeContents =
|
||||
$selection.size > 1 &&
|
||||
$selection
|
||||
let canMergeContents = $derived(
|
||||
selection.value.size > 1 &&
|
||||
selection.value
|
||||
.getSelected()
|
||||
.some((item) => item instanceof ListFileItem || item instanceof ListTrackItem);
|
||||
.some((item) => item instanceof ListFileItem || item instanceof ListTrackItem)
|
||||
);
|
||||
|
||||
let mergeType = MergeType.TRACES;
|
||||
let removeGaps = $state(false);
|
||||
let mergeType = $state(MergeType.TRACES);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {$$props.class ?? ''}">
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||
<RadioGroup.Root bind:value={mergeType}>
|
||||
<Label class="flex flex-row items-center gap-1.5 leading-5">
|
||||
<RadioGroup.Item value={MergeType.TRACES} />
|
||||
@@ -80,7 +80,7 @@
|
||||
disabled={(mergeType === MergeType.TRACES && !canMergeTraces) ||
|
||||
(mergeType === MergeType.CONTENTS && !canMergeContents)}
|
||||
onclick={() => {
|
||||
dbUtils.mergeSelection(
|
||||
fileActions.mergeSelection(
|
||||
mergeType === MergeType.TRACES,
|
||||
mergeType === MergeType.TRACES && $gpxStatistics.global.time.total > 0 && removeGaps
|
||||
);
|
||||
|
@@ -2,22 +2,22 @@
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Slider } from '$lib/components/ui/slider';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListItem,
|
||||
ListRootItem,
|
||||
ListTrackSegmentItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { Funnel } from '@lucide/svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import WithUnits from '$lib/components/WithUnits.svelte';
|
||||
import { dbUtils, fileObservers } from '$lib/db';
|
||||
import { map } from '$lib/components/map/map.svelte';
|
||||
import { map } from '$lib/components/map/utils.svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { ramerDouglasPeucker, TrackPoint, type SimplifiedTrackPoint } from 'gpx';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import type { GeoJSONSource } from 'mapbox-gl';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
let props: { class?: string } = $props();
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
const maxTolerance = 10000;
|
||||
|
||||
let validSelection = $derived(
|
||||
$selection.hasAnyChildren(new ListRootItem(), true, ['waypoints'])
|
||||
selection.value.hasAnyChildren(new ListRootItem(), true, ['waypoints'])
|
||||
);
|
||||
let tolerance = $derived(
|
||||
minTolerance * 2 ** (sliderValue[0] / (100 / Math.log2(maxTolerance / minTolerance)))
|
||||
@@ -67,18 +67,18 @@
|
||||
});
|
||||
});
|
||||
|
||||
if (map.current) {
|
||||
let source: GeoJSONSource | undefined = map.current.getSource('simplified');
|
||||
if (map.value) {
|
||||
let source: GeoJSONSource | undefined = map.value.getSource('simplified');
|
||||
if (source) {
|
||||
source.setData(data);
|
||||
} else {
|
||||
map.current.addSource('simplified', {
|
||||
map.value.addSource('simplified', {
|
||||
type: 'geojson',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
if (!map.current.getLayer('simplified')) {
|
||||
map.current.addLayer({
|
||||
if (!map.value.getLayer('simplified')) {
|
||||
map.value.addLayer({
|
||||
id: 'simplified',
|
||||
type: 'line',
|
||||
source: 'simplified',
|
||||
@@ -88,52 +88,52 @@
|
||||
},
|
||||
});
|
||||
} else {
|
||||
map.current.moveLayer('simplified');
|
||||
map.value.moveLayer('simplified');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if ($fileObservers) {
|
||||
unsubscribes.forEach((unsubscribe, fileId) => {
|
||||
if (!$fileObservers.has(fileId)) {
|
||||
unsubscribe();
|
||||
unsubscribes.delete(fileId);
|
||||
}
|
||||
});
|
||||
$fileObservers.forEach((fileStore, fileId) => {
|
||||
if (!unsubscribes.has(fileId)) {
|
||||
let unsubscribe = derived([fileStore, selection], ([fs, sel]) => [
|
||||
fs,
|
||||
sel,
|
||||
]).subscribe(([fs, sel]) => {
|
||||
if (fs) {
|
||||
fs.file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
||||
let segmentItem = new ListTrackSegmentItem(
|
||||
fileId,
|
||||
trackIndex,
|
||||
segmentIndex
|
||||
);
|
||||
if (sel.hasAnyParent(segmentItem)) {
|
||||
let statistics = fs.statistics.getStatisticsFor(segmentItem);
|
||||
simplified.set(segmentItem.getFullId(), [
|
||||
segmentItem,
|
||||
statistics.local.points.length,
|
||||
ramerDouglasPeucker(statistics.local.points, minTolerance),
|
||||
]);
|
||||
update();
|
||||
} else if (simplified.has(segmentItem.getFullId())) {
|
||||
simplified.delete(segmentItem.getFullId());
|
||||
update();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
unsubscribes.set(fileId, unsubscribe);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// $effect(() => {
|
||||
// if ($fileObservers) {
|
||||
// unsubscribes.forEach((unsubscribe, fileId) => {
|
||||
// if (!$fileObservers.has(fileId)) {
|
||||
// unsubscribe();
|
||||
// unsubscribes.delete(fileId);
|
||||
// }
|
||||
// });
|
||||
// $fileObservers.forEach((fileStore, fileId) => {
|
||||
// if (!unsubscribes.has(fileId)) {
|
||||
// let unsubscribe = derived([fileStore, selection], ([fs, sel]) => [
|
||||
// fs,
|
||||
// sel,
|
||||
// ]).subscribe(([fs, sel]) => {
|
||||
// if (fs) {
|
||||
// fs.file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
||||
// let segmentItem = new ListTrackSegmentItem(
|
||||
// fileId,
|
||||
// trackIndex,
|
||||
// segmentIndex
|
||||
// );
|
||||
// if (sel.hasAnyParent(segmentItem)) {
|
||||
// let statistics = fs.statistics.getStatisticsFor(segmentItem);
|
||||
// simplified.set(segmentItem.getFullId(), [
|
||||
// segmentItem,
|
||||
// statistics.local.points.length,
|
||||
// ramerDouglasPeucker(statistics.local.points, minTolerance),
|
||||
// ]);
|
||||
// update();
|
||||
// } else if (simplified.has(segmentItem.getFullId())) {
|
||||
// simplified.delete(segmentItem.getFullId());
|
||||
// update();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// unsubscribes.set(fileId, unsubscribe);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
$effect(() => {
|
||||
if (tolerance) {
|
||||
@@ -142,12 +142,12 @@
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (map.current) {
|
||||
if (map.current.getLayer('simplified')) {
|
||||
map.current.removeLayer('simplified');
|
||||
if (map.value) {
|
||||
if (map.value.getLayer('simplified')) {
|
||||
map.value.removeLayer('simplified');
|
||||
}
|
||||
if (map.current.getSource('simplified')) {
|
||||
map.current.removeSource('simplified');
|
||||
if (map.value.getSource('simplified')) {
|
||||
map.value.removeSource('simplified');
|
||||
}
|
||||
}
|
||||
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
||||
@@ -164,7 +164,7 @@
|
||||
.map((point) => point.point)
|
||||
);
|
||||
});
|
||||
dbUtils.reduce(itemsAndPoints);
|
||||
fileActions.reduce(itemsAndPoints);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -5,7 +5,6 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import TimePicker from '$lib/components/ui/time-picker/TimePicker.svelte';
|
||||
import { dbUtils, settings } from '$lib/db';
|
||||
import { gpxStatistics } from '$lib/stores';
|
||||
import {
|
||||
distancePerHourToSecondsPerDistance,
|
||||
@@ -17,15 +16,22 @@
|
||||
import { CalendarClock, CirclePlay, CircleStop, CircleX, Timer, Zap } from '@lucide/svelte';
|
||||
import { tick } from 'svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListRootItem,
|
||||
ListTrackItem,
|
||||
ListTrackSegmentItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import { fileActions } from '$lib/logic/file-actions.svelte';
|
||||
import { fileActionManager } from '$lib/logic/file-action-manager.svelte';
|
||||
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
let startDate: DateValue | undefined = undefined;
|
||||
let startTime: string | undefined = undefined;
|
||||
@@ -47,7 +53,7 @@
|
||||
|
||||
function setSpeed(value: number) {
|
||||
let speedValue = getConvertedVelocity(value);
|
||||
if ($velocityUnits === 'speed') {
|
||||
if (velocityUnits.value === 'speed') {
|
||||
speedValue = parseFloat(speedValue.toFixed(2));
|
||||
}
|
||||
speed = speedValue;
|
||||
@@ -80,9 +86,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: if ($gpxStatistics && $velocityUnits && $distanceUnits) {
|
||||
setGPXData();
|
||||
}
|
||||
// $: if ($gpxStatistics && $velocityUnits && $distanceUnits) {
|
||||
// setGPXData();
|
||||
// }
|
||||
|
||||
function getDate(date: DateValue, time: string): Date {
|
||||
if (date === undefined) {
|
||||
@@ -133,12 +139,12 @@
|
||||
}
|
||||
|
||||
let speedValue = speed;
|
||||
if ($velocityUnits === 'pace') {
|
||||
if (velocityUnits.value === 'pace') {
|
||||
speedValue = distancePerHourToSecondsPerDistance(speed);
|
||||
}
|
||||
if ($distanceUnits === 'imperial') {
|
||||
if (distanceUnits.value === 'imperial') {
|
||||
speedValue = milesToKilometers(speedValue);
|
||||
} else if ($distanceUnits === 'nautical') {
|
||||
} else if (distanceUnits.value === 'nautical') {
|
||||
speedValue = nauticalMilesToKilometers(speedValue);
|
||||
}
|
||||
return speedValue;
|
||||
@@ -171,24 +177,26 @@
|
||||
updateEnd();
|
||||
}
|
||||
|
||||
$: canUpdate =
|
||||
$selection.size === 1 && $selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']);
|
||||
let canUpdate = $derived(
|
||||
selection.value.size === 1 &&
|
||||
selection.value.hasAnyChildren(new ListRootItem(), true, ['waypoints'])
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {$$props.class ?? ''}">
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 {props.class ?? ''}">
|
||||
<fieldset class="flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-2 justify-center">
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<Label for="speed" class="flex flex-row">
|
||||
<Zap size="16" class="mr-1" />
|
||||
{#if $velocityUnits === 'speed'}
|
||||
{#if velocityUnits.value === 'speed'}
|
||||
{i18n._('quantities.speed')}
|
||||
{:else}
|
||||
{i18n._('quantities.pace')}
|
||||
{/if}
|
||||
</Label>
|
||||
<div class="flex flex-row gap-1 items-center">
|
||||
{#if $velocityUnits === 'speed'}
|
||||
{#if velocityUnits.value === 'speed'}
|
||||
<Input
|
||||
id="speed"
|
||||
type="number"
|
||||
@@ -199,11 +207,11 @@
|
||||
onchange={updateDataFromSpeed}
|
||||
/>
|
||||
<span class="text-sm shrink-0">
|
||||
{#if $distanceUnits === 'imperial'}
|
||||
{#if distanceUnits.value === 'imperial'}
|
||||
{i18n._('units.miles_per_hour')}
|
||||
{:else if $distanceUnits === 'metric'}
|
||||
{:else if distanceUnits.value === 'metric'}
|
||||
{i18n._('units.kilometers_per_hour')}
|
||||
{:else if $distanceUnits === 'nautical'}
|
||||
{:else if distanceUnits.value === 'nautical'}
|
||||
{i18n._('units.knots')}
|
||||
{/if}
|
||||
</span>
|
||||
@@ -215,11 +223,11 @@
|
||||
onChange={updateDataFromSpeed}
|
||||
/>
|
||||
<span class="text-sm shrink-0">
|
||||
{#if $distanceUnits === 'imperial'}
|
||||
{#if distanceUnits.value === 'imperial'}
|
||||
{i18n._('units.minutes_per_mile')}
|
||||
{:else if $distanceUnits === 'metric'}
|
||||
{:else if distanceUnits.value === 'metric'}
|
||||
{i18n._('units.minutes_per_kilometer')}
|
||||
{:else if $distanceUnits === 'nautical'}
|
||||
{:else if distanceUnits.value === 'nautical'}
|
||||
{i18n._('units.minutes_per_nautical_mile')}
|
||||
{/if}
|
||||
</span>
|
||||
@@ -260,7 +268,7 @@
|
||||
disabled={!canUpdate}
|
||||
bind:value={startTime}
|
||||
class="w-fit"
|
||||
on:change={updateEnd}
|
||||
onchange={updateEnd}
|
||||
/>
|
||||
</div>
|
||||
<Label class="flex flex-row">
|
||||
@@ -285,7 +293,7 @@
|
||||
disabled={!canUpdate}
|
||||
bind:value={endTime}
|
||||
class="w-fit"
|
||||
on:change={updateStart}
|
||||
onchange={updateStart}
|
||||
/>
|
||||
</div>
|
||||
{#if $gpxStatistics.global.time.moving === 0 || $gpxStatistics.global.time.moving === undefined}
|
||||
@@ -324,9 +332,9 @@
|
||||
ratio = $gpxStatistics.global.speed.moving / effectiveSpeed;
|
||||
}
|
||||
|
||||
let item = $selection.getSelected()[0];
|
||||
let item = selection.value.getSelected()[0];
|
||||
let fileId = item.getFileId();
|
||||
dbUtils.applyToFile(fileId, (file) => {
|
||||
fileActionManager.applyToFile(fileId, (file) => {
|
||||
if (item instanceof ListFileItem) {
|
||||
if (artificial || !$gpxStatistics.global.time.moving) {
|
||||
file.createArtificialTimestamps(
|
||||
|
@@ -21,65 +21,75 @@
|
||||
SquareArrowUpLeft,
|
||||
SquareArrowOutDownRight,
|
||||
} from '@lucide/svelte';
|
||||
|
||||
import { map, newGPXFile, routingControls, selectFileWhenLoaded } from '$lib/stores';
|
||||
import { dbUtils, getFile, getFileIds, settings } from '$lib/db';
|
||||
import { brouterProfiles } from '$lib/components/toolbar/tools/routing/routing.svelte';
|
||||
|
||||
import { brouterProfiles } from '$lib/components/toolbar/tools/routing/utils.svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { RoutingControls } from './RoutingControls';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { fileObservers } from '$lib/db';
|
||||
// import { RoutingControls } from './RoutingControls';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { getOrderedSelection, selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListRootItem,
|
||||
ListTrackItem,
|
||||
ListTrackSegmentItem,
|
||||
type ListItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import { getURLForLanguage, resetCursor, setCrosshairCursor } from '$lib/utils';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { TrackPoint } from 'gpx';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import { map } from '$lib/components/map/utils.svelte';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { fileActions, getFileIds, newGPXFile } from '$lib/logic/file-actions.svelte';
|
||||
|
||||
let {
|
||||
minimized = $bindable(false),
|
||||
minimizable = true,
|
||||
popup = undefined,
|
||||
popupElement = undefined,
|
||||
class: className = '',
|
||||
}: {
|
||||
minimized?: boolean;
|
||||
minimizable?: boolean;
|
||||
popup?: mapboxgl.Popup;
|
||||
popupElement?: HTMLDivElement;
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
export let minimized = false;
|
||||
export let minimizable = true;
|
||||
export let popup: mapboxgl.Popup | undefined = undefined;
|
||||
export let popupElement: HTMLElement | undefined = undefined;
|
||||
let selectedItem: ListItem | null = null;
|
||||
|
||||
const { privateRoads, routing, routingProfile } = settings;
|
||||
|
||||
$: if ($map && popup && popupElement) {
|
||||
// remove controls for deleted files
|
||||
routingControls.forEach((controls, fileId) => {
|
||||
if (!$fileObservers.has(fileId)) {
|
||||
controls.destroy();
|
||||
routingControls.delete(fileId);
|
||||
// $: if (map && popup && popupElement) {
|
||||
// // remove controls for deleted files
|
||||
// routingControls.forEach((controls, fileId) => {
|
||||
// if (!$fileObservers.has(fileId)) {
|
||||
// controls.destroy();
|
||||
// routingControls.delete(fileId);
|
||||
|
||||
if (selectedItem && selectedItem.getFileId() === fileId) {
|
||||
selectedItem = null;
|
||||
}
|
||||
} else if ($map !== controls.map) {
|
||||
controls.updateMap($map);
|
||||
}
|
||||
});
|
||||
// add controls for new files
|
||||
$fileObservers.forEach((file, fileId) => {
|
||||
if (!routingControls.has(fileId)) {
|
||||
routingControls.set(
|
||||
fileId,
|
||||
new RoutingControls($map, fileId, file, popup, popupElement)
|
||||
// if (selectedItem && selectedItem.getFileId() === fileId) {
|
||||
// selectedItem = null;
|
||||
// }
|
||||
// } else if ($map !== controls.map) {
|
||||
// controls.updateMap($map);
|
||||
// }
|
||||
// });
|
||||
// // add controls for new files
|
||||
// fileStateCollection.files.forEach((file, fileId) => {
|
||||
// if (!routingControls.has(fileId)) {
|
||||
// routingControls.set(
|
||||
// fileId,
|
||||
// new RoutingControls($map, fileId, file, popup, popupElement)
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
let validSelection = $derived(
|
||||
selection.value.hasAnyChildren(new ListRootItem(), true, ['waypoints'])
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$: validSelection = $selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']);
|
||||
|
||||
function createFileWithPoint(e: any) {
|
||||
if ($selection.size === 0) {
|
||||
if (selection.value.size === 0) {
|
||||
let file = newGPXFile();
|
||||
file.replaceTrackPoints(0, 0, 0, 0, [
|
||||
new TrackPoint({
|
||||
@@ -90,22 +100,22 @@
|
||||
}),
|
||||
]);
|
||||
file._data.id = getFileIds(1)[0];
|
||||
dbUtils.add(file);
|
||||
selectFileWhenLoaded(file._data.id);
|
||||
fileActions.add(file);
|
||||
// selectFileWhenLoaded(file._data.id);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
setCrosshairCursor();
|
||||
$map?.on('click', createFileWithPoint);
|
||||
// setCrosshairCursor();
|
||||
map.value?.on('click', createFileWithPoint);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
resetCursor();
|
||||
$map?.off('click', createFileWithPoint);
|
||||
// resetCursor();
|
||||
map.value?.off('click', createFileWithPoint);
|
||||
|
||||
routingControls.forEach((controls) => controls.destroy());
|
||||
routingControls.clear();
|
||||
// routingControls.forEach((controls) => controls.destroy());
|
||||
// routingControls.clear();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -116,11 +126,11 @@
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 animate-in animate-out {$$props.class ?? ''}">
|
||||
<div class="flex flex-col gap-3 w-full max-w-80 animate-in animate-out {className ?? ''}">
|
||||
<div class="flex flex-col gap-3">
|
||||
<Label class="flex flex-row justify-between items-center gap-2">
|
||||
<span class="flex flex-row items-center gap-1">
|
||||
{#if $routing}
|
||||
{#if routing.value}
|
||||
<Route size="16" />
|
||||
{:else}
|
||||
<RouteOff size="16" />
|
||||
@@ -128,28 +138,28 @@
|
||||
{i18n._('toolbar.routing.use_routing')}
|
||||
</span>
|
||||
<Tooltip label={i18n._('toolbar.routing.use_routing_tooltip')}>
|
||||
<Switch class="scale-90" bind:checked={$routing} />
|
||||
<Switch class="scale-90" bind:checked={routing.value} />
|
||||
<Shortcut slot="extra" key="F5" />
|
||||
</Tooltip>
|
||||
</Label>
|
||||
{#if $routing}
|
||||
{#if routing.value}
|
||||
<div class="flex flex-col gap-3" in:slide>
|
||||
<Label class="flex flex-row justify-between items-center gap-2">
|
||||
<span class="shrink-0 flex flex-row items-center gap-1">
|
||||
{#if $routingProfile.includes('bike') || $routingProfile.includes('motorcycle')}
|
||||
{#if routingProfile.value.includes('bike') || routingProfile.value.includes('motorcycle')}
|
||||
<Bike size="16" />
|
||||
{:else if $routingProfile.includes('foot')}
|
||||
{:else if routingProfile.value.includes('foot')}
|
||||
<Footprints size="16" />
|
||||
{:else if $routingProfile.includes('water')}
|
||||
{:else if routingProfile.value.includes('water')}
|
||||
<Waves size="16" />
|
||||
{:else if $routingProfile.includes('railway')}
|
||||
{:else if routingProfile.value.includes('railway')}
|
||||
<TrainFront size="16" />
|
||||
{/if}
|
||||
{i18n._('toolbar.routing.activity')}
|
||||
</span>
|
||||
<Select.Root type="single" bind:value={$routingProfile}>
|
||||
<Select.Root type="single" bind:value={routingProfile.value}>
|
||||
<Select.Trigger class="h-8 grow">
|
||||
{i18n._(`toolbar.routing.activities.${$routingProfile}`)}
|
||||
{i18n._(`toolbar.routing.activities.${routingProfile.value}`)}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each Object.keys(brouterProfiles) as profile}
|
||||
@@ -167,7 +177,7 @@
|
||||
<TriangleAlert size="16" />
|
||||
{i18n._('toolbar.routing.allow_private')}
|
||||
</span>
|
||||
<Switch class="scale-90" bind:checked={$privateRoads} />
|
||||
<Switch class="scale-90" bind:checked={privateRoads.value} />
|
||||
</Label>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -178,7 +188,7 @@
|
||||
variant="outline"
|
||||
class="flex flex-row gap-1 text-xs px-2"
|
||||
disabled={!validSelection}
|
||||
onclick={dbUtils.reverseSelection}
|
||||
onclick={fileActions.reverseSelection}
|
||||
>
|
||||
<ArrowRightLeft size="12" />{i18n._('toolbar.routing.reverse.button')}
|
||||
</ButtonWithTooltip>
|
||||
@@ -188,10 +198,10 @@
|
||||
class="flex flex-row gap-1 text-xs px-2"
|
||||
disabled={!validSelection}
|
||||
onclick={() => {
|
||||
const selected = getOrderedSelection();
|
||||
const selected = selection.getOrderedSelection();
|
||||
if (selected.length > 0) {
|
||||
const firstFileId = selected[0].getFileId();
|
||||
const firstFile = getFile(firstFileId);
|
||||
const firstFile = fileStateCollection.getFile(firstFileId);
|
||||
if (firstFile) {
|
||||
let start = (() => {
|
||||
if (selected[0] instanceof ListFileItem) {
|
||||
@@ -208,9 +218,9 @@
|
||||
|
||||
if (start !== undefined) {
|
||||
const lastFileId = selected[selected.length - 1].getFileId();
|
||||
routingControls
|
||||
.get(lastFileId)
|
||||
?.appendAnchorWithCoordinates(start.getCoordinates());
|
||||
// routingControls
|
||||
// .get(lastFileId)
|
||||
// ?.appendAnchorWithCoordinates(start.getCoordinates());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,18 +1,15 @@
|
||||
import { distance, type Coordinates, TrackPoint, TrackSegment, Track, projectedPoint } from 'gpx';
|
||||
import { get, writable, type Readable } from 'svelte/store';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { route } from './routing.svelte';
|
||||
import { route } from './utils.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { dbUtils, settings, type GPXFileWithStatistics } from '$lib/db';
|
||||
import { getOrderedSelection, selection } from '$lib/components/file-list/Selection';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListTrackItem,
|
||||
ListTrackSegmentItem,
|
||||
} from '$lib/components/file-list/FileList';
|
||||
import { currentTool, streetViewEnabled, Tool } from '$lib/stores';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import { getClosestLinePoint, resetCursor, setGrabbingCursor } from '$lib/utils';
|
||||
import type { GPXFileWithStatistics } from '$lib/logic/statistics';
|
||||
|
||||
// const { streetViewSource } = settings;
|
||||
export const canChangeStart = writable(false);
|
||||
|
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { splitAs, SplitType } from '$lib/components/toolbar/tools/scissors/utils.svelte';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
import { ListRootItem } from '$lib/components/file-list/FileList';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { ListRootItem } from '$lib/components/file-list/file-list';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Slider } from '$lib/components/ui/slider';
|
||||
@@ -15,8 +14,9 @@
|
||||
import { onDestroy, tick } from 'svelte';
|
||||
import { Crop } from '@lucide/svelte';
|
||||
import { dbUtils } from '$lib/db';
|
||||
import { SplitControls } from './SplitControls.svelte';
|
||||
import { SplitControls } from './split-controls';
|
||||
import { getURLForLanguage } from '$lib/utils';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
|
||||
let props: {
|
||||
class?: string;
|
||||
@@ -35,7 +35,7 @@
|
||||
});
|
||||
|
||||
let validSelection = $derived(
|
||||
$selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']) &&
|
||||
selection.value.hasAnyChildren(new ListRootItem(), true, ['waypoints']) &&
|
||||
$gpxStatistics.local.points.length > 0
|
||||
);
|
||||
|
||||
|
@@ -1,16 +1,12 @@
|
||||
import { TrackPoint, TrackSegment } from 'gpx';
|
||||
import { get } from 'svelte/store';
|
||||
import mapboxgl from 'mapbox-gl';
|
||||
import { dbUtils, getFile } from '$lib/db';
|
||||
import {
|
||||
applyToOrderedSelectedItemsFromFile,
|
||||
selection,
|
||||
} from '$lib/components/file-list/Selection';
|
||||
import { ListTrackSegmentItem } from '$lib/components/file-list/FileList';
|
||||
import { ListTrackSegmentItem } from '$lib/components/file-list/file-list';
|
||||
import { gpxStatistics } from '$lib/stores';
|
||||
import { tool, Tool } from '$lib/components/toolbar/utils.svelte';
|
||||
import { splitAs } from '$lib/components/toolbar/tools/scissors/utils.svelte';
|
||||
import { Scissors } from 'lucide-static';
|
||||
import { applyToOrderedSelectedItemsFromFile, selection } from '$lib/logic/selection.svelte';
|
||||
|
||||
export class SplitControls {
|
||||
active: boolean = false;
|
||||
@@ -25,10 +21,9 @@ export class SplitControls {
|
||||
constructor(map: mapboxgl.Map) {
|
||||
this.map = map;
|
||||
|
||||
this.unsubscribes.push(selection.subscribe(this.addIfNeeded.bind(this)));
|
||||
this.unsubscribes.push(gpxStatistics.subscribe(this.addIfNeeded.bind(this)));
|
||||
$effect(() => {
|
||||
tool.current, this.addIfNeeded.bind(this);
|
||||
tool.current, selection.value, this.addIfNeeded.bind(this);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +59,7 @@ export class SplitControls {
|
||||
if (file) {
|
||||
file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
||||
if (
|
||||
get(selection).hasAnyParent(
|
||||
selection.value.hasAnyParent(
|
||||
new ListTrackSegmentItem(fileId, trackIndex, segmentIndex)
|
||||
)
|
||||
) {
|
@@ -1,19 +1,11 @@
|
||||
<script lang="ts" context="module">
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const selectedWaypoint = writable<[Waypoint, string] | undefined>(undefined);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { selection } from '$lib/components/file-list/Selection';
|
||||
import { Waypoint } from 'gpx';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { ListWaypointItem } from '$lib/components/file-list/FileList';
|
||||
import { ListWaypointItem } from '$lib/components/file-list/file-list';
|
||||
import { dbUtils, fileObservers, getFile, settings, type GPXFileWithStatistics } from '$lib/db';
|
||||
import { get } from 'svelte/store';
|
||||
import Help from '$lib/components/Help.svelte';
|
||||
@@ -22,61 +14,21 @@
|
||||
import { getURLForLanguage, resetCursor, setCrosshairCursor } from '$lib/utils';
|
||||
import { MapPin, CircleX, Save } from '@lucide/svelte';
|
||||
import { getSymbolKey, symbols } from '$lib/assets/symbols';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { selectedWaypoint } from './utils.svelte';
|
||||
|
||||
let name: string;
|
||||
let description: string;
|
||||
let link: string;
|
||||
let longitude: number;
|
||||
let latitude: number;
|
||||
let symbolKey: string;
|
||||
let props: {
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
const { treeFileView } = settings;
|
||||
let name = $state('');
|
||||
let description = $state('');
|
||||
let link = $state('');
|
||||
let symbolKey = $state('');
|
||||
let longitude = $state(0);
|
||||
let latitude = $state(0);
|
||||
|
||||
$: canCreate = $selection.size > 0;
|
||||
|
||||
$: if ($treeFileView && $selection) {
|
||||
selectedWaypoint.update(() => {
|
||||
if ($selection.size === 1) {
|
||||
let item = $selection.getSelected()[0];
|
||||
if (item instanceof ListWaypointItem) {
|
||||
let file = getFile(item.getFileId());
|
||||
let waypoint = file?.wpt[item.getWaypointIndex()];
|
||||
if (waypoint) {
|
||||
return [waypoint, item.getFileId()];
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
let unsubscribe: (() => void) | undefined = undefined;
|
||||
function updateWaypointData(fileStore: GPXFileWithStatistics | undefined) {
|
||||
if ($selectedWaypoint) {
|
||||
if (fileStore) {
|
||||
if ($selectedWaypoint[0]._data.index < fileStore.file.wpt.length) {
|
||||
$selectedWaypoint[0] = fileStore.file.wpt[$selectedWaypoint[0]._data.index];
|
||||
name = $selectedWaypoint[0].name ?? '';
|
||||
description = $selectedWaypoint[0].desc ?? '';
|
||||
if (
|
||||
$selectedWaypoint[0].cmt !== undefined &&
|
||||
$selectedWaypoint[0].cmt !== $selectedWaypoint[0].desc
|
||||
) {
|
||||
description += '\n\n' + $selectedWaypoint[0].cmt;
|
||||
}
|
||||
link = $selectedWaypoint[0].link?.attributes?.href ?? '';
|
||||
let symbol = $selectedWaypoint[0].sym ?? '';
|
||||
symbolKey = getSymbolKey(symbol) ?? symbol ?? '';
|
||||
longitude = parseFloat($selectedWaypoint[0].getLongitude().toFixed(6));
|
||||
latitude = parseFloat($selectedWaypoint[0].getLatitude().toFixed(6));
|
||||
} else {
|
||||
selectedWaypoint.set(undefined);
|
||||
}
|
||||
} else {
|
||||
selectedWaypoint.set(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
let canCreate = $derived(selection.value.size > 0);
|
||||
|
||||
function resetWaypointData() {
|
||||
name = '';
|
||||
@@ -87,21 +39,6 @@
|
||||
latitude = 0;
|
||||
}
|
||||
|
||||
$: {
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
unsubscribe = undefined;
|
||||
}
|
||||
if ($selectedWaypoint) {
|
||||
let fileStore = get(fileObservers).get($selectedWaypoint[1]);
|
||||
if (fileStore) {
|
||||
unsubscribe = fileStore.subscribe(updateWaypointData);
|
||||
}
|
||||
} else {
|
||||
resetWaypointData();
|
||||
}
|
||||
}
|
||||
|
||||
function createOrUpdateWaypoint() {
|
||||
if (typeof latitude === 'string') {
|
||||
latitude = parseFloat(latitude);
|
||||
@@ -124,12 +61,12 @@
|
||||
link: link.length > 0 ? { attributes: { href: link } } : undefined,
|
||||
sym: symbols[symbolKey]?.value ?? '',
|
||||
},
|
||||
$selectedWaypoint
|
||||
? new ListWaypointItem($selectedWaypoint[1], $selectedWaypoint[0]._data.index)
|
||||
selectedWaypoint.wpt && selectedWaypoint.fileId
|
||||
? new ListWaypointItem(selectedWaypoint.fileId, selectedWaypoint.wpt._data.index)
|
||||
: undefined
|
||||
);
|
||||
|
||||
selectedWaypoint.set(undefined);
|
||||
selectedWaypoint.reset();
|
||||
resetWaypointData();
|
||||
}
|
||||
|
||||
@@ -138,49 +75,46 @@
|
||||
longitude = e.lngLat.lng.toFixed(6);
|
||||
}
|
||||
|
||||
$: sortedSymbols = Object.entries(symbols).sort((a, b) => {
|
||||
return i18n._(`gpx.symbol.${a[0]}`).localeCompare(i18n._(`gpx.symbol.${b[0]}`), i18n.lang);
|
||||
});
|
||||
let sortedSymbols = $derived(
|
||||
Object.entries(symbols).sort((a, b) => {
|
||||
return i18n
|
||||
._(`gpx.symbol.${a[0]}`)
|
||||
.localeCompare(i18n._(`gpx.symbol.${b[0]}`), i18n.lang);
|
||||
})
|
||||
);
|
||||
|
||||
onMount(() => {
|
||||
let m = get(map);
|
||||
m?.on('click', setCoordinates);
|
||||
setCrosshairCursor();
|
||||
map.value?.on('click', setCoordinates);
|
||||
// setCrosshairCursor();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
let m = get(map);
|
||||
m?.off('click', setCoordinates);
|
||||
resetCursor();
|
||||
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
unsubscribe = undefined;
|
||||
}
|
||||
map.value?.off('click', setCoordinates);
|
||||
// resetCursor();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full max-w-96 {$$props.class ?? ''}">
|
||||
<div class="flex flex-col gap-3 w-full max-w-96 {props.class ?? ''}">
|
||||
<fieldset class="flex flex-col gap-2">
|
||||
<Label for="name">{i18n._('menu.metadata.name')}</Label>
|
||||
<Input
|
||||
bind:value={name}
|
||||
id="name"
|
||||
class="font-semibold h-8"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
/>
|
||||
<Label for="description">{i18n._('menu.metadata.description')}</Label>
|
||||
<Textarea
|
||||
bind:value={description}
|
||||
id="description"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
/>
|
||||
<Label for="symbol">{i18n._('toolbar.waypoint.icon')}</Label>
|
||||
<Select.Root bind:value={symbolKey} type="single">
|
||||
<Select.Trigger
|
||||
id="symbol"
|
||||
class="w-full h-8"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
>
|
||||
{#if symbolKey in symbols}
|
||||
{i18n._(`gpx.symbol.${symbolKey}`)}
|
||||
@@ -212,7 +146,7 @@
|
||||
bind:value={link}
|
||||
id="link"
|
||||
class="h-8"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="grow">
|
||||
@@ -225,7 +159,7 @@
|
||||
min={-90}
|
||||
max={90}
|
||||
class="text-xs h-8"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
/>
|
||||
</div>
|
||||
<div class="grow">
|
||||
@@ -238,7 +172,7 @@
|
||||
min={-180}
|
||||
max={180}
|
||||
class="text-xs h-8"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -246,11 +180,11 @@
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={!canCreate && !$selectedWaypoint}
|
||||
disabled={!canCreate && !selectedWaypoint.wpt}
|
||||
class="grow whitespace-normal h-fit"
|
||||
onclick={createOrUpdateWaypoint}
|
||||
>
|
||||
{#if $selectedWaypoint}
|
||||
{#if selectedWaypoint.wpt}
|
||||
<Save size="16" class="mr-1 shrink-0" />
|
||||
{i18n._('menu.metadata.save')}
|
||||
{:else}
|
||||
@@ -261,7 +195,7 @@
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
selectedWaypoint.set(undefined);
|
||||
selectedWaypoint.reset();
|
||||
resetWaypointData();
|
||||
}}
|
||||
>
|
||||
@@ -269,7 +203,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
<Help link={getURLForLanguage(i18n.lang, '/help/toolbar/poi')}>
|
||||
{#if $selectedWaypoint || canCreate}
|
||||
{#if selectedWaypoint.wpt || canCreate}
|
||||
{i18n._('toolbar.waypoint.help')}
|
||||
{:else}
|
||||
{i18n._('toolbar.waypoint.help_no_selection')}
|
@@ -0,0 +1,67 @@
|
||||
import { ListWaypointItem } from '$lib/components/file-list/file-list';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { selection } from '$lib/logic/selection.svelte';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import type { Waypoint } from 'gpx';
|
||||
|
||||
export class WaypointSelection {
|
||||
private _selection: [Waypoint, string] | undefined;
|
||||
|
||||
constructor() {
|
||||
this._selection = $derived.by(() => {
|
||||
if (settings.treeFileView.value && selection.value.size === 1) {
|
||||
let item = selection.value.getSelected()[0];
|
||||
if (item instanceof ListWaypointItem) {
|
||||
let file = fileStateCollection.getFile(item.getFileId());
|
||||
let waypoint = file?.wpt[item.getWaypointIndex()];
|
||||
if (waypoint) {
|
||||
return [waypoint, item.getFileId()];
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._selection = undefined;
|
||||
}
|
||||
|
||||
get wpt(): Waypoint | undefined {
|
||||
return this._selection ? this._selection[0] : undefined;
|
||||
}
|
||||
|
||||
get fileId(): string | undefined {
|
||||
return this._selection ? this._selection[1] : undefined;
|
||||
}
|
||||
|
||||
// TODO update the waypoint data if the file changes
|
||||
// function updateWaypointData(fileStore: GPXFileWithStatistics | undefined) {
|
||||
// if (selectedWaypoint.wpt) {
|
||||
// if (fileStore) {
|
||||
// if ($selectedWaypoint[0]._data.index < fileStore.file.wpt.length) {
|
||||
// $selectedWaypoint[0] = fileStore.file.wpt[$selectedWaypoint[0]._data.index];
|
||||
// name = $selectedWaypoint[0].name ?? '';
|
||||
// description = $selectedWaypoint[0].desc ?? '';
|
||||
// if (
|
||||
// $selectedWaypoint[0].cmt !== undefined &&
|
||||
// $selectedWaypoint[0].cmt !== $selectedWaypoint[0].desc
|
||||
// ) {
|
||||
// description += '\n\n' + $selectedWaypoint[0].cmt;
|
||||
// }
|
||||
// link = $selectedWaypoint[0].link?.attributes?.href ?? '';
|
||||
// let symbol = $selectedWaypoint[0].sym ?? '';
|
||||
// symbolKey = getSymbolKey(symbol) ?? symbol ?? '';
|
||||
// longitude = parseFloat($selectedWaypoint[0].getLongitude().toFixed(6));
|
||||
// latitude = parseFloat($selectedWaypoint[0].getLatitude().toFixed(6));
|
||||
// } else {
|
||||
// selectedWaypoint.reset();
|
||||
// }
|
||||
// } else {
|
||||
// selectedWaypoint.reset();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
export const selectedWaypoint = new WaypointSelection();
|
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Punts d'interès
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Body zájmu
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Interessante Orte
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Puntos de interés
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Interesguneak
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points d'intérêt
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Nevezetes helyek
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Punti di interesse
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Svarbios vietos
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Interessante punten (POI's)
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Pontos de interesse
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Точки интереса
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: İlgi alanları
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: Points of interest
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
@@ -4,7 +4,7 @@ title: 创建或编辑兴趣点
|
||||
|
||||
<script>
|
||||
import { MapPin } from '@lucide/svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/Waypoint.svelte';
|
||||
import Waypoint from '$lib/components/toolbar/tools/waypoint/Waypoint.svelte';
|
||||
</script>
|
||||
|
||||
# <MapPin size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
|
||||
|
206
website/src/lib/logic/file-action-manager.svelte.ts
Normal file
206
website/src/lib/logic/file-action-manager.svelte.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { db, type Database } from '$lib/db';
|
||||
import { liveQuery } from 'dexie';
|
||||
import type { GPXFile } from 'gpx';
|
||||
import { applyPatches, produceWithPatches, type Patch, type WritableDraft } from 'immer';
|
||||
import { fileStateCollection, type GPXFileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
|
||||
const MAX_PATCHES = 100;
|
||||
|
||||
export class FileActionManager {
|
||||
private _db: Database;
|
||||
private _files: Map<string, GPXFile>;
|
||||
private _patchIndex: number;
|
||||
private _patchMinIndex: number;
|
||||
private _patchMaxIndex: number;
|
||||
|
||||
constructor(db: Database, fileStateCollection: GPXFileStateCollection) {
|
||||
this._db = db;
|
||||
|
||||
this._files = $derived.by(() => {
|
||||
let files = new Map<string, GPXFile>();
|
||||
fileStateCollection.files.forEach((state, id) => {
|
||||
if (state.file) {
|
||||
files.set(id, state.file);
|
||||
}
|
||||
});
|
||||
return files;
|
||||
});
|
||||
|
||||
this._patchIndex = $state(-1);
|
||||
this._patchMinIndex = $state(0);
|
||||
this._patchMaxIndex = $state(0);
|
||||
|
||||
liveQuery(() => db.settings.get('patchIndex')).subscribe((value) => {
|
||||
if (value !== undefined) {
|
||||
this._patchIndex = value;
|
||||
}
|
||||
});
|
||||
liveQuery(() =>
|
||||
(db.patches.orderBy(':id').keys() as Promise<number[]>).then((keys) => {
|
||||
if (keys.length === 0) {
|
||||
return { min: 0, max: 0 };
|
||||
} else {
|
||||
return { min: keys[0], max: keys[keys.length - 1] + 1 };
|
||||
}
|
||||
})
|
||||
).subscribe((value) => {
|
||||
this._patchMinIndex = value.min;
|
||||
this._patchMaxIndex = value.max;
|
||||
});
|
||||
}
|
||||
|
||||
async store(patch: Patch[], inversePatch: Patch[]) {
|
||||
this._db.patches.where(':id').above(this._patchIndex).delete(); // Delete all patches after the current patch to avoid redoing them
|
||||
if (this._patchMaxIndex - this._patchMinIndex + 1 > MAX_PATCHES) {
|
||||
this._db.patches
|
||||
.where(':id')
|
||||
.belowOrEqual(this._patchMaxIndex - MAX_PATCHES)
|
||||
.delete();
|
||||
}
|
||||
this._db.transaction('rw', this._db.patches, this._db.settings, async () => {
|
||||
let index = this._patchIndex + 1;
|
||||
await this._db.patches.put(
|
||||
{
|
||||
patch,
|
||||
inversePatch,
|
||||
index,
|
||||
},
|
||||
index
|
||||
);
|
||||
await this._db.settings.put(index, 'patchIndex');
|
||||
});
|
||||
}
|
||||
|
||||
get canUndo(): boolean {
|
||||
return this._patchIndex >= this._patchMinIndex;
|
||||
}
|
||||
|
||||
get canRedo(): boolean {
|
||||
return this._patchIndex < this._patchMaxIndex - 1;
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (this.canUndo) {
|
||||
this._db.patches.get(this._patchIndex).then((patch) => {
|
||||
if (patch) {
|
||||
this.apply(patch.inversePatch);
|
||||
this._db.settings.put(this._patchIndex - 1, 'patchIndex');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
redo() {
|
||||
if (this.canRedo) {
|
||||
this._db.patches.get(this._patchIndex + 1).then((patch) => {
|
||||
if (patch) {
|
||||
this.apply(patch.patch);
|
||||
this._db.settings.put(this._patchIndex + 1, 'patchIndex');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
apply(patch: Patch[]) {
|
||||
let newFiles = applyPatches(this._files, patch);
|
||||
return this.commitFileStateChange(newFiles, patch);
|
||||
}
|
||||
|
||||
commitFileStateChange(newFiles: ReadonlyMap<string, GPXFile>, patch: Patch[]) {
|
||||
let changedFileIds = getChangedFileIds(patch);
|
||||
let updatedFileIds: string[] = [],
|
||||
deletedFileIds: string[] = [];
|
||||
|
||||
changedFileIds.forEach((id) => {
|
||||
if (newFiles.has(id)) {
|
||||
updatedFileIds.push(id);
|
||||
} else {
|
||||
deletedFileIds.push(id);
|
||||
}
|
||||
});
|
||||
|
||||
let updatedFiles = updatedFileIds
|
||||
.map((id) => newFiles.get(id))
|
||||
.filter((file) => file !== undefined) as GPXFile[];
|
||||
updatedFileIds = updatedFiles.map((file) => file._data.id);
|
||||
|
||||
// updateSelection(updatedFiles, deletedFileIds);
|
||||
|
||||
// @ts-ignore
|
||||
return db.transaction('rw', db.fileids, db.files, async () => {
|
||||
if (updatedFileIds.length > 0) {
|
||||
await this._db.fileids.bulkPut(updatedFileIds, updatedFileIds);
|
||||
await this._db.files.bulkPut(updatedFiles, updatedFileIds);
|
||||
}
|
||||
if (deletedFileIds.length > 0) {
|
||||
await this._db.fileids.bulkDelete(deletedFileIds);
|
||||
await this._db.files.bulkDelete(deletedFileIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
applyGlobal(callback: (files: Map<string, GPXFile>) => void) {
|
||||
const [newFileCollection, patch, inversePatch] = produceWithPatches(this._files, callback);
|
||||
|
||||
this.store(patch, inversePatch);
|
||||
|
||||
return this.commitFileStateChange(newFileCollection, patch);
|
||||
}
|
||||
|
||||
applyToFiles(fileIds: string[], callback: (file: WritableDraft<GPXFile>) => void) {
|
||||
const [newFileCollection, patch, inversePatch] = produceWithPatches(
|
||||
this._files,
|
||||
(draft) => {
|
||||
fileIds.forEach((fileId) => {
|
||||
let file = draft.get(fileId);
|
||||
if (file) {
|
||||
callback(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
this.store(patch, inversePatch);
|
||||
|
||||
return this.commitFileStateChange(newFileCollection, patch);
|
||||
}
|
||||
|
||||
applyToFile(fileId: string, callback: (file: WritableDraft<GPXFile>) => void) {
|
||||
return this.applyToFiles([fileId], callback);
|
||||
}
|
||||
|
||||
applyEachToFilesAndGlobal(
|
||||
fileIds: string[],
|
||||
callbacks: ((file: WritableDraft<GPXFile>, context?: any) => void)[],
|
||||
globalCallback: (files: Map<string, GPXFile>, context?: any) => void,
|
||||
context?: any
|
||||
) {
|
||||
const [newFileCollection, patch, inversePatch] = produceWithPatches(
|
||||
this._files,
|
||||
(draft) => {
|
||||
fileIds.forEach((fileId, index) => {
|
||||
let file = draft.get(fileId);
|
||||
if (file) {
|
||||
callbacks[index](file, context);
|
||||
}
|
||||
});
|
||||
globalCallback(draft, context);
|
||||
}
|
||||
);
|
||||
|
||||
this.store(patch, inversePatch);
|
||||
|
||||
return this.commitFileStateChange(newFileCollection, patch);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the file ids of the files that have changed in the patch
|
||||
function getChangedFileIds(patch: Patch[]): string[] {
|
||||
let changedFileIds = new Set<string>();
|
||||
for (let p of patch) {
|
||||
changedFileIds.add(p.path[0] as string);
|
||||
}
|
||||
return Array.from(changedFileIds);
|
||||
}
|
||||
|
||||
export const fileActionManager = new FileActionManager(db, fileStateCollection);
|
1161
website/src/lib/logic/file-actions.svelte.ts
Normal file
1161
website/src/lib/logic/file-actions.svelte.ts
Normal file
File diff suppressed because it is too large
Load Diff
128
website/src/lib/logic/file-state.svelte.ts
Normal file
128
website/src/lib/logic/file-state.svelte.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simplify';
|
||||
import { db, type Database } from '$lib/db';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { GPXFile } from 'gpx';
|
||||
import { GPXStatisticsTree, type GPXFileWithStatistics } from '$lib/logic/statistics';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
|
||||
// Observe a single file from the database, and maintain its statistics
|
||||
class GPXFileState {
|
||||
private _file: GPXFileWithStatistics | undefined;
|
||||
private _subscription: { unsubscribe: () => void } | undefined;
|
||||
|
||||
constructor(db: Database, fileId: string) {
|
||||
this._file = $state(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 = { file, statistics };
|
||||
|
||||
// if (get(selection).hasAnyChildren(new ListFileItem(id))) {
|
||||
// updateAllHidden();
|
||||
// }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._subscription?.unsubscribe();
|
||||
this._subscription = undefined;
|
||||
this._file = undefined;
|
||||
}
|
||||
|
||||
get file(): GPXFile | undefined {
|
||||
return this._file?.file;
|
||||
}
|
||||
|
||||
get statistics(): GPXStatisticsTree | undefined {
|
||||
return this._file?.statistics;
|
||||
}
|
||||
}
|
||||
|
||||
// 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: Map<string, GPXFileState>;
|
||||
|
||||
constructor(db: Database) {
|
||||
this._db = db;
|
||||
this._files = $state(new Map());
|
||||
}
|
||||
|
||||
initialize(fitBounds: boolean) {
|
||||
let initialize = true;
|
||||
liveQuery(() => this._db.fileids.toArray()).subscribe((dbFileIds) => {
|
||||
if (initialize) {
|
||||
// if (fitBounds && dbFileIds.length > 0) {
|
||||
// initTargetMapBounds(dbFileIds);
|
||||
// }
|
||||
initialize = false;
|
||||
}
|
||||
// Find new files to observe
|
||||
let newFiles = dbFileIds
|
||||
.filter((id) => !this._files.has(id))
|
||||
.sort((a, b) => parseInt(a.split('-')[1]) - parseInt(b.split('-')[1]));
|
||||
// Find deleted files to stop observing
|
||||
let deletedFiles = Array.from(this._files.keys()).filter(
|
||||
(id) => !dbFileIds.find((fileId) => fileId === id)
|
||||
);
|
||||
|
||||
if (newFiles.length > 0 || deletedFiles.length > 0) {
|
||||
// Update the map of file states
|
||||
let files = new Map(this._files);
|
||||
newFiles.forEach((id) => {
|
||||
files.set(id, new GPXFileState(this._db, id));
|
||||
});
|
||||
deletedFiles.forEach((id) => {
|
||||
files.get(id)?.destroy();
|
||||
files.delete(id);
|
||||
});
|
||||
this._files = files;
|
||||
|
||||
// Update the file order
|
||||
let fileOrder = settings.fileOrder.value.filter((id) => !deletedFiles.includes(id));
|
||||
newFiles.forEach((id) => {
|
||||
if (!fileOrder.includes(id)) {
|
||||
fileOrder.push(id);
|
||||
}
|
||||
});
|
||||
settings.fileOrder.value = fileOrder;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get files(): ReadonlyMap<string, GPXFileState> {
|
||||
return this._files;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._files.size;
|
||||
}
|
||||
|
||||
getFile(fileId: string): GPXFile | undefined {
|
||||
let fileState = this._files.get(fileId);
|
||||
return fileState?.file;
|
||||
}
|
||||
|
||||
getStatistics(fileId: string): GPXStatisticsTree | undefined {
|
||||
let fileState = this._files.get(fileId);
|
||||
return fileState?.statistics;
|
||||
}
|
||||
}
|
||||
|
||||
// Collection of all file states
|
||||
export const fileStateCollection = new GPXFileStateCollection(db);
|
@@ -1,40 +0,0 @@
|
||||
import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simplify';
|
||||
import { GPXStatisticsTree, type Database } from '$lib/db';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { GPXFile } from 'gpx';
|
||||
|
||||
class GPXFileState {
|
||||
private _db: Database;
|
||||
private _file: GPXFile | undefined;
|
||||
|
||||
constructor(db: Database, fileId: string, file: GPXFile) {
|
||||
this._db = db;
|
||||
this._file = $state(undefined);
|
||||
|
||||
liveQuery(() => db.files.get(fileId)).subscribe((value) => {
|
||||
if (value !== undefined) {
|
||||
let gpx = new GPXFile(value);
|
||||
updateAnchorPoints(gpx);
|
||||
|
||||
let statistics = new GPXStatisticsTree(gpx);
|
||||
if (!fileState.has(id)) {
|
||||
// Update the map bounds for new files
|
||||
updateTargetMapBounds(
|
||||
id,
|
||||
statistics.getStatisticsFor(new ListFileItem(id)).global.bounds
|
||||
);
|
||||
}
|
||||
|
||||
fileState.set(id, gpx);
|
||||
store.set({
|
||||
file: gpx,
|
||||
statistics,
|
||||
});
|
||||
|
||||
if (get(selection).hasAnyChildren(new ListFileItem(id))) {
|
||||
updateAllHidden();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,4 +1,3 @@
|
||||
import { get, writable } from 'svelte/store';
|
||||
import {
|
||||
ListFileItem,
|
||||
ListItem,
|
||||
@@ -9,224 +8,188 @@ import {
|
||||
ListLevel,
|
||||
sortItems,
|
||||
ListWaypointsItem,
|
||||
moveItems,
|
||||
} from './FileList';
|
||||
import { fileObservers, getFile, getFileIds, settings } from '$lib/db';
|
||||
} from '$lib/components/file-list/file-list';
|
||||
import { SelectionTreeType } from '$lib/logic/selection';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import type { GPXFile } from 'gpx';
|
||||
|
||||
export class SelectionTreeType {
|
||||
item: ListItem;
|
||||
selected: boolean;
|
||||
children: {
|
||||
[key: string | number]: SelectionTreeType;
|
||||
};
|
||||
size: number = 0;
|
||||
export class Selection {
|
||||
private _selection: SelectionTreeType;
|
||||
private _copied: ListItem[] | undefined;
|
||||
private _cut: boolean;
|
||||
|
||||
constructor(item: ListItem) {
|
||||
this.item = item;
|
||||
this.selected = false;
|
||||
this.children = {};
|
||||
constructor() {
|
||||
this._selection = $state(new SelectionTreeType(new ListRootItem()));
|
||||
this._copied = $state(undefined);
|
||||
this._cut = $state(false);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.selected = false;
|
||||
for (let key in this.children) {
|
||||
this.children[key].clear();
|
||||
}
|
||||
this.size = 0;
|
||||
get value(): SelectionTreeType {
|
||||
return this._selection;
|
||||
}
|
||||
|
||||
_setOrToggle(item: ListItem, value?: boolean) {
|
||||
if (item.level === this.item.level) {
|
||||
let newSelected = value === undefined ? !this.selected : value;
|
||||
if (this.selected !== newSelected) {
|
||||
this.selected = newSelected;
|
||||
this.size += this.selected ? 1 : -1;
|
||||
}
|
||||
} else {
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (!this.children.hasOwnProperty(id)) {
|
||||
this.children[id] = new SelectionTreeType(this.item.extend(id));
|
||||
}
|
||||
this.size -= this.children[id].size;
|
||||
this.children[id]._setOrToggle(item, value);
|
||||
this.size += this.children[id].size;
|
||||
}
|
||||
}
|
||||
selectItem(item: ListItem) {
|
||||
let selection = new SelectionTreeType(new ListRootItem());
|
||||
selection.set(item, true);
|
||||
this._selection = selection;
|
||||
}
|
||||
|
||||
set(item: ListItem, value: boolean) {
|
||||
this._setOrToggle(item, value);
|
||||
selectFile(fileId: string) {
|
||||
this.selectItem(new ListFileItem(fileId));
|
||||
}
|
||||
|
||||
toggle(item: ListItem) {
|
||||
this._setOrToggle(item);
|
||||
addSelectItem(item: ListItem) {
|
||||
this._selection.toggle(item);
|
||||
}
|
||||
|
||||
has(item: ListItem): boolean {
|
||||
if (item.level === this.item.level) {
|
||||
return this.selected;
|
||||
} else {
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
return this.children[id].has(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
addSelectFile(fileId: string) {
|
||||
this.addSelectItem(new ListFileItem(fileId));
|
||||
}
|
||||
|
||||
hasAnyParent(item: ListItem, self: boolean = true): boolean {
|
||||
if (
|
||||
this.selected &&
|
||||
this.item.level <= item.level &&
|
||||
(self || this.item.level < item.level)
|
||||
) {
|
||||
return this.selected;
|
||||
}
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
return this.children[id].hasAnyParent(item, self);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasAnyChildren(item: ListItem, self: boolean = true, ignoreIds?: (string | number)[]): boolean {
|
||||
if (
|
||||
this.selected &&
|
||||
this.item.level >= item.level &&
|
||||
(self || this.item.level > item.level)
|
||||
) {
|
||||
return this.selected;
|
||||
}
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (ignoreIds === undefined || ignoreIds.indexOf(id) === -1) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
return this.children[id].hasAnyChildren(item, self, ignoreIds);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let key in this.children) {
|
||||
if (ignoreIds === undefined || ignoreIds.indexOf(key) === -1) {
|
||||
if (this.children[key].hasAnyChildren(item, self, ignoreIds)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getSelected(selection: ListItem[] = []): ListItem[] {
|
||||
if (this.selected) {
|
||||
selection.push(this.item);
|
||||
}
|
||||
for (let key in this.children) {
|
||||
this.children[key].getSelected(selection);
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
|
||||
forEach(callback: (item: ListItem) => void) {
|
||||
if (this.selected) {
|
||||
callback(this.item);
|
||||
}
|
||||
for (let key in this.children) {
|
||||
this.children[key].forEach(callback);
|
||||
}
|
||||
}
|
||||
|
||||
getChild(id: string | number): SelectionTreeType | undefined {
|
||||
return this.children[id];
|
||||
}
|
||||
|
||||
deleteChild(id: string | number) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
this.size -= this.children[id].size;
|
||||
delete this.children[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const selection = writable<SelectionTreeType>(new SelectionTreeType(new ListRootItem()));
|
||||
|
||||
export function selectItem(item: ListItem) {
|
||||
selection.update(($selection) => {
|
||||
$selection.clear();
|
||||
$selection.set(item, true);
|
||||
return $selection;
|
||||
});
|
||||
}
|
||||
|
||||
export function selectFile(fileId: string) {
|
||||
selectItem(new ListFileItem(fileId));
|
||||
}
|
||||
|
||||
export function addSelectItem(item: ListItem) {
|
||||
selection.update(($selection) => {
|
||||
$selection.toggle(item);
|
||||
return $selection;
|
||||
});
|
||||
}
|
||||
|
||||
export function addSelectFile(fileId: string) {
|
||||
addSelectItem(new ListFileItem(fileId));
|
||||
}
|
||||
|
||||
export function selectAll() {
|
||||
selection.update(($selection) => {
|
||||
selectAll() {
|
||||
let item: ListItem = new ListRootItem();
|
||||
$selection.forEach((i) => {
|
||||
this._selection.forEach((i) => {
|
||||
item = i;
|
||||
});
|
||||
|
||||
let selection = new SelectionTreeType(new ListRootItem());
|
||||
if (item instanceof ListRootItem || item instanceof ListFileItem) {
|
||||
$selection.clear();
|
||||
get(fileObservers).forEach((_file, fileId) => {
|
||||
$selection.set(new ListFileItem(fileId), true);
|
||||
fileStateCollection.files.forEach((_file, fileId) => {
|
||||
selection.set(new ListFileItem(fileId), true);
|
||||
});
|
||||
} else if (item instanceof ListTrackItem) {
|
||||
let file = getFile(item.getFileId());
|
||||
let file = fileStateCollection.getFile(item.getFileId());
|
||||
if (file) {
|
||||
file.trk.forEach((_track, trackId) => {
|
||||
$selection.set(new ListTrackItem(item.getFileId(), trackId), true);
|
||||
selection.set(new ListTrackItem(item.getFileId(), trackId), true);
|
||||
});
|
||||
}
|
||||
} else if (item instanceof ListTrackSegmentItem) {
|
||||
let file = getFile(item.getFileId());
|
||||
let file = fileStateCollection.getFile(item.getFileId());
|
||||
if (file) {
|
||||
file.trk[item.getTrackIndex()].trkseg.forEach((_segment, segmentId) => {
|
||||
$selection.set(
|
||||
selection.set(
|
||||
new ListTrackSegmentItem(item.getFileId(), item.getTrackIndex(), segmentId),
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
} else if (item instanceof ListWaypointItem) {
|
||||
let file = getFile(item.getFileId());
|
||||
let file = fileStateCollection.getFile(item.getFileId());
|
||||
if (file) {
|
||||
file.wpt.forEach((_waypoint, waypointId) => {
|
||||
$selection.set(new ListWaypointItem(item.getFileId(), waypointId), true);
|
||||
selection.set(new ListWaypointItem(item.getFileId(), waypointId), true);
|
||||
});
|
||||
}
|
||||
}
|
||||
this._selection = selection;
|
||||
}
|
||||
|
||||
return $selection;
|
||||
set(items: ListItem[]) {
|
||||
let selection = new SelectionTreeType(new ListRootItem());
|
||||
items.forEach((item) => {
|
||||
selection.set(item, true);
|
||||
});
|
||||
}
|
||||
this._selection = selection;
|
||||
}
|
||||
|
||||
export function getOrderedSelection(reverse: boolean = false): ListItem[] {
|
||||
update(updatedFiles: GPXFile[], deletedFileIds: string[]) {
|
||||
// TODO do it the other way around: get all selected items, and check if they still exist?
|
||||
// let removedItems: ListItem[] = [];
|
||||
// applyToOrderedItemsFromFile(selection.value.getSelected(), (fileId, level, items) => {
|
||||
// let file = updatedFiles.find((file) => file._data.id === fileId);
|
||||
// if (file) {
|
||||
// items.forEach((item) => {
|
||||
// if (item instanceof ListTrackItem) {
|
||||
// let newTrackIndex = file.trk.findIndex(
|
||||
// (track) => track._data.trackIndex === item.getTrackIndex()
|
||||
// );
|
||||
// if (newTrackIndex === -1) {
|
||||
// removedItems.push(item);
|
||||
// }
|
||||
// } else if (item instanceof ListTrackSegmentItem) {
|
||||
// let newTrackIndex = file.trk.findIndex(
|
||||
// (track) => track._data.trackIndex === item.getTrackIndex()
|
||||
// );
|
||||
// if (newTrackIndex === -1) {
|
||||
// removedItems.push(item);
|
||||
// } else {
|
||||
// let newSegmentIndex = file.trk[newTrackIndex].trkseg.findIndex(
|
||||
// (segment) => segment._data.segmentIndex === item.getSegmentIndex()
|
||||
// );
|
||||
// if (newSegmentIndex === -1) {
|
||||
// removedItems.push(item);
|
||||
// }
|
||||
// }
|
||||
// } else if (item instanceof ListWaypointItem) {
|
||||
// let newWaypointIndex = file.wpt.findIndex(
|
||||
// (wpt) => wpt._data.index === item.getWaypointIndex()
|
||||
// );
|
||||
// if (newWaypointIndex === -1) {
|
||||
// removedItems.push(item);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// } else if (deletedFileIds.includes(fileId)) {
|
||||
// items.forEach((item) => {
|
||||
// removedItems.push(item);
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// if (removedItems.length > 0) {
|
||||
// selection.update(($selection) => {
|
||||
// removedItems.forEach((item) => {
|
||||
// if (item instanceof ListFileItem) {
|
||||
// $selection.deleteChild(item.getFileId());
|
||||
// } else {
|
||||
// $selection.set(item, false);
|
||||
// }
|
||||
// });
|
||||
// return $selection;
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
getOrderedSelection(reverse: boolean = false): ListItem[] {
|
||||
let selected: ListItem[] = [];
|
||||
applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
||||
selected.push(...items);
|
||||
}, reverse);
|
||||
return selected;
|
||||
}
|
||||
|
||||
copySelection(): boolean {
|
||||
let selected = this._selection.getSelected();
|
||||
if (selected.length > 0) {
|
||||
this._copied = selected;
|
||||
this._cut = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
cutSelection() {
|
||||
if (this.copySelection()) {
|
||||
this._cut = true;
|
||||
}
|
||||
}
|
||||
|
||||
resetCopied() {
|
||||
this._copied = undefined;
|
||||
this._cut = false;
|
||||
}
|
||||
|
||||
get copied(): ListItem[] | undefined {
|
||||
return this._copied;
|
||||
}
|
||||
|
||||
get cut(): boolean {
|
||||
return this._cut;
|
||||
}
|
||||
}
|
||||
|
||||
export const selection = new Selection();
|
||||
|
||||
export function applyToOrderedItemsFromFile(
|
||||
selectedItems: ListItem[],
|
||||
callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void,
|
||||
@@ -261,115 +224,5 @@ export function applyToOrderedSelectedItemsFromFile(
|
||||
callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void,
|
||||
reverse: boolean = true
|
||||
) {
|
||||
applyToOrderedItemsFromFile(get(selection).getSelected(), callback, reverse);
|
||||
}
|
||||
|
||||
export const copied = writable<ListItem[] | undefined>(undefined);
|
||||
export const cut = writable(false);
|
||||
|
||||
export function copySelection(): boolean {
|
||||
let selected = get(selection).getSelected();
|
||||
if (selected.length > 0) {
|
||||
copied.set(selected);
|
||||
cut.set(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function cutSelection() {
|
||||
if (copySelection()) {
|
||||
cut.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
function resetCopied() {
|
||||
copied.set(undefined);
|
||||
cut.set(false);
|
||||
}
|
||||
|
||||
export function pasteSelection() {
|
||||
let fromItems = get(copied);
|
||||
if (fromItems === undefined || fromItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected = get(selection).getSelected();
|
||||
if (selected.length === 0) {
|
||||
selected = [new ListRootItem()];
|
||||
}
|
||||
|
||||
let fromParent = fromItems[0].getParent();
|
||||
let toParent = selected[selected.length - 1];
|
||||
|
||||
let startIndex: number | undefined = undefined;
|
||||
|
||||
if (fromItems[0].level === toParent.level) {
|
||||
if (
|
||||
toParent instanceof ListTrackItem ||
|
||||
toParent instanceof ListTrackSegmentItem ||
|
||||
toParent instanceof ListWaypointItem
|
||||
) {
|
||||
startIndex = toParent.getId() + 1;
|
||||
}
|
||||
toParent = toParent.getParent();
|
||||
}
|
||||
|
||||
let toItems: ListItem[] = [];
|
||||
if (toParent.level === ListLevel.ROOT) {
|
||||
let fileIds = getFileIds(fromItems.length);
|
||||
fileIds.forEach((fileId) => {
|
||||
toItems.push(new ListFileItem(fileId));
|
||||
});
|
||||
} else {
|
||||
let toFile = getFile(toParent.getFileId());
|
||||
if (toFile) {
|
||||
fromItems.forEach((item, index) => {
|
||||
if (toParent instanceof ListFileItem) {
|
||||
if (item instanceof ListTrackItem || item instanceof ListTrackSegmentItem) {
|
||||
toItems.push(
|
||||
new ListTrackItem(
|
||||
toParent.getFileId(),
|
||||
(startIndex ?? toFile.trk.length) + index
|
||||
)
|
||||
);
|
||||
} else if (item instanceof ListWaypointsItem) {
|
||||
toItems.push(new ListWaypointsItem(toParent.getFileId()));
|
||||
} else if (item instanceof ListWaypointItem) {
|
||||
toItems.push(
|
||||
new ListWaypointItem(
|
||||
toParent.getFileId(),
|
||||
(startIndex ?? toFile.wpt.length) + index
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (toParent instanceof ListTrackItem) {
|
||||
if (item instanceof ListTrackSegmentItem) {
|
||||
let toTrackIndex = toParent.getTrackIndex();
|
||||
toItems.push(
|
||||
new ListTrackSegmentItem(
|
||||
toParent.getFileId(),
|
||||
toTrackIndex,
|
||||
(startIndex ?? toFile.trk[toTrackIndex].trkseg.length) + index
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (toParent instanceof ListWaypointsItem) {
|
||||
if (item instanceof ListWaypointItem) {
|
||||
toItems.push(
|
||||
new ListWaypointItem(
|
||||
toParent.getFileId(),
|
||||
(startIndex ?? toFile.wpt.length) + index
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (fromItems.length === toItems.length) {
|
||||
moveItems(fromParent, toParent, fromItems, toItems, get(cut));
|
||||
resetCopied();
|
||||
}
|
||||
applyToOrderedItemsFromFile(selection.value.getSelected(), callback, reverse);
|
||||
}
|
||||
|
140
website/src/lib/logic/selection.ts
Normal file
140
website/src/lib/logic/selection.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import type { ListItem } from '$lib/components/file-list/file-list';
|
||||
|
||||
export class SelectionTreeType {
|
||||
item: ListItem;
|
||||
selected: boolean;
|
||||
children: {
|
||||
[key: string | number]: SelectionTreeType;
|
||||
};
|
||||
size: number = 0;
|
||||
|
||||
constructor(item: ListItem) {
|
||||
this.item = item;
|
||||
this.selected = false;
|
||||
this.children = {};
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.selected = false;
|
||||
for (let key in this.children) {
|
||||
this.children[key].clear();
|
||||
}
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
_setOrToggle(item: ListItem, value?: boolean) {
|
||||
if (item.level === this.item.level) {
|
||||
let newSelected = value === undefined ? !this.selected : value;
|
||||
if (this.selected !== newSelected) {
|
||||
this.selected = newSelected;
|
||||
this.size += this.selected ? 1 : -1;
|
||||
}
|
||||
} else {
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (!this.children.hasOwnProperty(id)) {
|
||||
this.children[id] = new SelectionTreeType(this.item.extend(id));
|
||||
}
|
||||
this.size -= this.children[id].size;
|
||||
this.children[id]._setOrToggle(item, value);
|
||||
this.size += this.children[id].size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set(item: ListItem, value: boolean) {
|
||||
this._setOrToggle(item, value);
|
||||
}
|
||||
|
||||
toggle(item: ListItem) {
|
||||
this._setOrToggle(item);
|
||||
}
|
||||
|
||||
has(item: ListItem): boolean {
|
||||
if (item.level === this.item.level) {
|
||||
return this.selected;
|
||||
} else {
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
return this.children[id].has(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasAnyParent(item: ListItem, self: boolean = true): boolean {
|
||||
if (
|
||||
this.selected &&
|
||||
this.item.level <= item.level &&
|
||||
(self || this.item.level < item.level)
|
||||
) {
|
||||
return this.selected;
|
||||
}
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
return this.children[id].hasAnyParent(item, self);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasAnyChildren(item: ListItem, self: boolean = true, ignoreIds?: (string | number)[]): boolean {
|
||||
if (
|
||||
this.selected &&
|
||||
this.item.level >= item.level &&
|
||||
(self || this.item.level > item.level)
|
||||
) {
|
||||
return this.selected;
|
||||
}
|
||||
let id = item.getIdAtLevel(this.item.level);
|
||||
if (id !== undefined) {
|
||||
if (ignoreIds === undefined || ignoreIds.indexOf(id) === -1) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
return this.children[id].hasAnyChildren(item, self, ignoreIds);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let key in this.children) {
|
||||
if (ignoreIds === undefined || ignoreIds.indexOf(key) === -1) {
|
||||
if (this.children[key].hasAnyChildren(item, self, ignoreIds)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getSelected(selection: ListItem[] = []): ListItem[] {
|
||||
if (this.selected) {
|
||||
selection.push(this.item);
|
||||
}
|
||||
for (let key in this.children) {
|
||||
this.children[key].getSelected(selection);
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
|
||||
forEach(callback: (item: ListItem) => void) {
|
||||
if (this.selected) {
|
||||
callback(this.item);
|
||||
}
|
||||
for (let key in this.children) {
|
||||
this.children[key].forEach(callback);
|
||||
}
|
||||
}
|
||||
|
||||
getChild(id: string | number): SelectionTreeType | undefined {
|
||||
return this.children[id];
|
||||
}
|
||||
|
||||
deleteChild(id: string | number) {
|
||||
if (this.children.hasOwnProperty(id)) {
|
||||
this.size -= this.children[id].size;
|
||||
delete this.children[id];
|
||||
}
|
||||
}
|
||||
}
|
46
website/src/lib/logic/statistics.ts
Normal file
46
website/src/lib/logic/statistics.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ListItem, ListLevel } from '$lib/components/file-list/file-list';
|
||||
import { GPXFile, GPXStatistics, type Track } from 'gpx';
|
||||
|
||||
export class GPXStatisticsTree {
|
||||
level: ListLevel;
|
||||
statistics: {
|
||||
[key: string]: GPXStatisticsTree | GPXStatistics;
|
||||
} = {};
|
||||
|
||||
constructor(element: GPXFile | Track) {
|
||||
if (element instanceof GPXFile) {
|
||||
this.level = ListLevel.FILE;
|
||||
element.children.forEach((child, index) => {
|
||||
this.statistics[index] = new GPXStatisticsTree(child);
|
||||
});
|
||||
} else {
|
||||
this.level = ListLevel.TRACK;
|
||||
element.children.forEach((child, index) => {
|
||||
this.statistics[index] = child.getStatistics();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getStatisticsFor(item: ListItem): GPXStatistics {
|
||||
let statistics = new GPXStatistics();
|
||||
let id = item.getIdAtLevel(this.level);
|
||||
if (id === undefined || id === 'waypoints') {
|
||||
Object.keys(this.statistics).forEach((key) => {
|
||||
if (this.statistics[key] instanceof GPXStatistics) {
|
||||
statistics.mergeWith(this.statistics[key]);
|
||||
} else {
|
||||
statistics.mergeWith(this.statistics[key].getStatisticsFor(item));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let child = this.statistics[id];
|
||||
if (child instanceof GPXStatistics) {
|
||||
statistics.mergeWith(child);
|
||||
} else if (child !== undefined) {
|
||||
statistics.mergeWith(child.getStatisticsFor(item));
|
||||
}
|
||||
}
|
||||
return statistics;
|
||||
}
|
||||
}
|
||||
export type GPXFileWithStatistics = { file: GPXFile; statistics: GPXStatisticsTree };
|
@@ -82,88 +82,6 @@
|
||||
// export const gpxLayers: Map<string, GPXLayer> = new Map();
|
||||
// export const routingControls: Map<string, RoutingControls> = new Map();
|
||||
|
||||
// export function newGPXFile() {
|
||||
// const newFileName = i18n._('menu.new_file');
|
||||
|
||||
// let file = new GPXFile();
|
||||
|
||||
// let maxNewFileNumber = 0;
|
||||
// get(fileObservers).forEach((f) => {
|
||||
// let file = get(f)?.file;
|
||||
// if (file && file.metadata.name && file.metadata.name.startsWith(newFileName)) {
|
||||
// let number = parseInt(file.metadata.name.split(' ').pop() ?? '0');
|
||||
// if (!isNaN(number) && number > maxNewFileNumber) {
|
||||
// maxNewFileNumber = number;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// file.metadata.name = `${newFileName} ${maxNewFileNumber + 1}`;
|
||||
|
||||
// return file;
|
||||
// }
|
||||
|
||||
// export function createFile() {
|
||||
// let file = newGPXFile();
|
||||
|
||||
// dbUtils.add(file);
|
||||
|
||||
// selectFileWhenLoaded(file._data.id);
|
||||
// currentTool.set(Tool.ROUTING);
|
||||
// }
|
||||
|
||||
// export function triggerFileInput() {
|
||||
// const input = document.createElement('input');
|
||||
// input.type = 'file';
|
||||
// input.accept = '.gpx';
|
||||
// input.multiple = true;
|
||||
// input.className = 'hidden';
|
||||
// input.onchange = () => {
|
||||
// if (input.files) {
|
||||
// loadFiles(input.files);
|
||||
// }
|
||||
// };
|
||||
// input.click();
|
||||
// }
|
||||
|
||||
// export async function loadFiles(list: FileList | File[]) {
|
||||
// let files: GPXFile[] = [];
|
||||
// for (let i = 0; i < list.length; i++) {
|
||||
// let file = await loadFile(list[i]);
|
||||
// if (file) {
|
||||
// files.push(file);
|
||||
// }
|
||||
// }
|
||||
|
||||
// let ids = dbUtils.addMultiple(files);
|
||||
|
||||
// initTargetMapBounds(ids);
|
||||
// selectFileWhenLoaded(ids[0]);
|
||||
// }
|
||||
|
||||
// export async function loadFile(file: File): Promise<GPXFile | null> {
|
||||
// let result = await new Promise<GPXFile | null>((resolve) => {
|
||||
// const reader = new FileReader();
|
||||
// reader.onload = () => {
|
||||
// let data = reader.result?.toString() ?? null;
|
||||
// if (data) {
|
||||
// let gpx = parseGPX(data);
|
||||
// if (gpx.metadata === undefined) {
|
||||
// gpx.metadata = {};
|
||||
// }
|
||||
// if (gpx.metadata.name === undefined || gpx.metadata.name.trim() === '') {
|
||||
// gpx.metadata.name = file.name.split('.').slice(0, -1).join('.');
|
||||
// }
|
||||
// resolve(gpx);
|
||||
// } else {
|
||||
// resolve(null);
|
||||
// }
|
||||
// };
|
||||
// reader.readAsText(file);
|
||||
// });
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// export function selectFileWhenLoaded(fileId: string) {
|
||||
// const unsubscribe = fileObservers.subscribe((files) => {
|
||||
// if (files.has(fileId)) {
|
||||
|
@@ -4,14 +4,13 @@
|
||||
// import FileList from '$lib/components/file-list/FileList.svelte';
|
||||
// import GPXStatistics from '$lib/components/GPXStatistics.svelte';
|
||||
import Map from '$lib/components/map/Map.svelte';
|
||||
// import Menu from '$lib/components/Menu.svelte';
|
||||
import Menu from '$lib/components/Menu.svelte';
|
||||
// import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
|
||||
import StreetViewControl from '$lib/components/map/street-view-control/StreetViewControl.svelte';
|
||||
import LayerControl from '$lib/components/map/layer-control/LayerControl.svelte';
|
||||
// import CoordinatesPopup from '$lib/components/map/CoordinatesPopup.svelte';
|
||||
import Resizer from '$lib/components/Resizer.svelte';
|
||||
import { Toaster } from '$lib/components/ui/sonner';
|
||||
// import { observeFilesFromDatabase } from '$lib/db';
|
||||
// import { gpxStatistics, loadFiles, slicedGPXStatistics } from '$lib/stores';
|
||||
// import { onMount } from 'svelte';
|
||||
// import { page } from '$app/state';
|
||||
@@ -20,6 +19,10 @@
|
||||
// import { getURLForGoogleDriveFile } from '$lib/components/embedding/Embedding';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import { fileStateCollection } from '$lib/logic/file-state.svelte';
|
||||
import { loadFiles } from '$lib/logic/file-actions.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/state';
|
||||
|
||||
const {
|
||||
treeFileView,
|
||||
@@ -30,28 +33,28 @@
|
||||
elevationFill,
|
||||
} = settings;
|
||||
|
||||
// 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));
|
||||
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));
|
||||
|
||||
// observeFilesFromDatabase(urls.length === 0);
|
||||
fileStateCollection.initialize(urls.length === 0);
|
||||
|
||||
// if (urls.length > 0) {
|
||||
// let downloads: Promise<File | null>[] = [];
|
||||
// urls.forEach((url) => {
|
||||
// downloads.push(
|
||||
// fetch(url)
|
||||
// .then((response) => response.blob())
|
||||
// .then((blob) => new File([blob], url.split('/').pop() ?? ''))
|
||||
// );
|
||||
// });
|
||||
if (urls.length > 0) {
|
||||
let downloads: Promise<File | null>[] = [];
|
||||
urls.forEach((url) => {
|
||||
downloads.push(
|
||||
fetch(url)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => new File([blob], url.split('/').pop() ?? ''))
|
||||
);
|
||||
});
|
||||
|
||||
// Promise.all(downloads).then((files) => {
|
||||
// loadFiles(files.filter((file) => file !== null));
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
Promise.all(downloads).then((files) => {
|
||||
loadFiles(files.filter((file) => file !== null));
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="fixed -z-10 text-transparent">
|
||||
@@ -94,7 +97,7 @@
|
||||
<div class="fixed flex flex-row w-screen h-screen supports-dvh:h-dvh">
|
||||
<div class="flex flex-col grow h-full min-w-0">
|
||||
<div class="grow relative">
|
||||
<!-- <Menu /> -->
|
||||
<Menu />
|
||||
<div
|
||||
class="absolute top-0 bottom-0 left-0 z-20 flex flex-col justify-center pointer-events-none"
|
||||
>
|
||||
|
Reference in New Issue
Block a user