This commit is contained in:
vcoppe
2025-10-17 23:54:45 +02:00
parent 0733562c0d
commit a73da0d81d
62 changed files with 1343 additions and 1162 deletions

View File

@@ -2,14 +2,15 @@
import { ScrollArea } from '$lib/components/ui/scroll-area/index';
import * as ContextMenu from '$lib/components/ui/context-menu';
import FileListNode from './FileListNode.svelte';
import { fileObservers, settings } from '$lib/db';
import { setContext } from 'svelte';
import { ListFileItem, ListLevel, ListRootItem, allowedPastes } from './file-list';
import { copied, pasteSelection, selectAll, selection } from './Selection';
import { ClipboardPaste, FileStack, Plus } from '@lucide/svelte';
import Shortcut from '$lib/components/Shortcut.svelte';
import { i18n } from '$lib/i18n.svelte';
import { createFile } from '$lib/stores';
import { settings } from '$lib/logic/settings';
import { fileStateCollection } from '$lib/logic/file-state';
import { createFile, pasteSelection } from '$lib/logic/file-actions';
import { selection } from '$lib/logic/selection';
let {
orientation,
@@ -28,28 +29,28 @@
const { treeFileView } = settings;
treeFileView.subscribe(($vertical) => {
if ($vertical) {
selection.update(($selection) => {
$selection.forEach((item) => {
if ($selection.hasAnyChildren(item, false)) {
$selection.toggle(item);
}
});
return $selection;
});
} else {
selection.update(($selection) => {
$selection.forEach((item) => {
if (!(item instanceof ListFileItem)) {
$selection.toggle(item);
$selection.set(new ListFileItem(item.getFileId()), true);
}
});
return $selection;
});
}
});
// treeFileView.subscribe(($vertical) => {
// if ($vertical) {
// selection.update(($selection) => {
// $selection.forEach((item) => {
// if ($selection.hasAnyChildren(item, false)) {
// $selection.toggle(item);
// }
// });
// return $selection;
// });
// } else {
// selection.update(($selection) => {
// $selection.forEach((item) => {
// if (!(item instanceof ListFileItem)) {
// $selection.toggle(item);
// $selection.set(new ListFileItem(item.getFileId()), true);
// }
// });
// return $selection;
// });
// }
// });
</script>
<ScrollArea
@@ -64,7 +65,7 @@
: 'flex-row'} {className ?? ''}"
{style}
>
<FileListNode bind:node={$fileObservers} item={new ListRootItem()} />
<FileListNode node={$fileStateCollection} item={new ListRootItem()} />
{#if orientation === 'vertical'}
<ContextMenu.Root>
<ContextMenu.Trigger class="grow" />
@@ -75,16 +76,19 @@
<Shortcut key="+" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item onclick={selectAll} disabled={$fileObservers.size === 0}>
<ContextMenu.Item
onclick={() => selection.selectAll()}
disabled={$fileStateCollection.size === 0}
>
<FileStack size="16" class="mr-1" />
{i18n._('menu.select_all')}
<Shortcut key="A" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item
disabled={$copied === undefined ||
$copied.length === 0 ||
!allowedPastes[$copied[0].level].includes(ListLevel.ROOT)}
disabled={selection.copied === undefined ||
selection.copied.length === 0 ||
!allowedPastes[selection.copied[0].level].includes(ListLevel.ROOT)}
onclick={pasteSelection}
>
<ClipboardPaste size="16" class="mr-1" />

View File

@@ -8,11 +8,10 @@
type GPXTreeElement,
} from 'gpx';
import { CollapsibleTreeNode } from '$lib/components/collapsible-tree/index';
import { settings, type GPXFileWithStatistics } from '$lib/db';
import { get, type Readable } from 'svelte/store';
import { type Readable } from 'svelte/store';
import FileListNodeContent from './FileListNodeContent.svelte';
import FileListNodeLabel from './FileListNodeLabel.svelte';
import { afterUpdate, getContext } from 'svelte';
import { getContext } from 'svelte';
import {
ListFileItem,
ListTrackSegmentItem,
@@ -22,20 +21,27 @@
type ListTrackItem,
} from './file-list';
import { i18n } from '$lib/i18n.svelte';
import { selection } from './Selection';
import { settings } from '$lib/logic/settings';
import type { GPXFileWithStatistics } from '$lib/logic/statistics';
import { selection } from '$lib/logic/selection';
export let node:
| Map<string, Readable<GPXFileWithStatistics | undefined>>
| GPXTreeElement<AnyGPXTreeElement>
| Waypoint[]
| Waypoint;
export let item: ListItem;
let {
node,
item,
}: {
node:
| Map<string, Readable<GPXFileWithStatistics | undefined>>
| GPXTreeElement<AnyGPXTreeElement>
| Waypoint[]
| Waypoint;
item: ListItem;
} = $props();
let recursive = getContext<boolean>('recursive');
let collapsible: CollapsibleTreeNode;
let collapsible: CollapsibleTreeNode | undefined = $state();
$: label =
let label = $derived(
node instanceof GPXFile && item instanceof ListFileItem
? node.metadata.name
: node instanceof Track
@@ -47,12 +53,13 @@
`${i18n._('gpx.waypoint')} ${(item as ListWaypointItem).waypointIndex + 1}`)
: node instanceof GPXFile && item instanceof ListWaypointsItem
? i18n._('gpx.waypoints')
: '';
: ''
);
const { treeFileView } = settings;
function openIfSelectedChild() {
if (collapsible && get(treeFileView) && $selection.hasAnyChildren(item, false)) {
if (collapsible && treeFileView.value && $selection.hasAnyChildren(item, false)) {
collapsible.openNode();
}
}
@@ -61,7 +68,7 @@
openIfSelectedChild();
}
afterUpdate(openIfSelectedChild);
// afterUpdate(openIfSelectedChild);
</script>
{#if node instanceof Map}

View File

@@ -6,9 +6,8 @@
<script lang="ts">
import { GPXFile, Track, Waypoint, type AnyGPXTreeElement, type GPXTreeElement } from 'gpx';
import { afterUpdate, getContext, onDestroy, onMount } from 'svelte';
import { getContext, onDestroy, onMount } from 'svelte';
import Sortable from 'sortablejs/Sortable';
import { getFileIds, settings, type GPXFileWithStatistics } from '$lib/db';
import { get, writable, type Readable, type Writable } from 'svelte/store';
import FileListNodeStore from './FileListNodeStore.svelte';
import FileListNode from './FileListNode.svelte';
@@ -18,18 +17,25 @@
ListRootItem,
ListWaypointsItem,
allowedMoves,
moveItems,
type ListItem,
} from './file-list';
import { selection } from './Selection';
import { isMac } from '$lib/utils';
import type { GPXFileWithStatistics } from '$lib/logic/statistics';
import { settings } from '$lib/logic/settings';
import { getFileIds, moveItems } from '$lib/logic/file-actions';
export let node:
| Map<string, Readable<GPXFileWithStatistics | undefined>>
| GPXTreeElement<AnyGPXTreeElement>
| Waypoint;
export let item: ListItem;
export let waypointRoot: boolean = false;
let {
node,
item,
waypointRoot = false,
}: {
node:
| Map<string, Readable<GPXFileWithStatistics | undefined>>
| GPXTreeElement<AnyGPXTreeElement>
| Waypoint;
item: ListItem;
waypointRoot?: boolean;
} = $props();
let container: HTMLElement;
let elements: { [id: string]: HTMLElement } = {};
@@ -126,28 +132,26 @@
updateFromSelection();
}
const { fileOrder } = settings;
function syncFileOrder() {
function syncFileOrder(order: string[]) {
if (!sortable || sortableLevel !== ListLevel.FILE) {
return;
}
const currentOrder = sortable.toArray();
if (currentOrder.length !== $fileOrder.length) {
sortable.sort($fileOrder);
if (currentOrder.length !== order.length) {
sortable.sort(order);
} else {
for (let i = 0; i < currentOrder.length; i++) {
if (currentOrder[i] !== $fileOrder[i]) {
sortable.sort($fileOrder);
if (currentOrder[i] !== order[i]) {
sortable.sort(order);
break;
}
}
}
}
$: if ($fileOrder) {
syncFileOrder();
}
const { fileOrder } = settings;
$effect(() => syncFileOrder(fileOrder.value));
function createSortable() {
sortable = Sortable.create(container, {
@@ -172,12 +176,12 @@
onSort: (e) => {
if (sortableLevel === ListLevel.FILE) {
let newFileOrder = sortable.toArray();
if (newFileOrder.length !== get(fileOrder).length) {
fileOrder.set(newFileOrder);
if (newFileOrder.length !== fileOrder.value.length) {
fileOrder.value = newFileOrder;
} else {
for (let i = 0; i < newFileOrder.length; i++) {
if (newFileOrder[i] !== get(fileOrder)[i]) {
fileOrder.set(newFileOrder);
if (newFileOrder[i] !== fileOrder.value[i]) {
fileOrder.value = newFileOrder;
break;
}
}
@@ -222,7 +226,7 @@
if (toItem instanceof ListRootItem) {
let newFileIds = getFileIds(newIndices.length);
toItems = newIndices.map((i, index) => {
$fileOrder.splice(i, 0, newFileIds[index]);
fileOrder.value.splice(i, 0, newFileIds[index]);
return item.extend(newFileIds[index]);
});
} else {

View File

@@ -2,7 +2,6 @@
import { Button } from '$lib/components/ui/button';
import * as ContextMenu from '$lib/components/ui/context-menu';
import Shortcut from '$lib/components/Shortcut.svelte';
import { dbUtils, getFile } from '$lib/db';
import {
Copy,
Info,
@@ -28,20 +27,8 @@
allowedPastes,
type ListItem,
} from './file-list';
import {
copied,
copySelection,
cut,
cutSelection,
pasteSelection,
selectAll,
selectItem,
selection,
} from './Selection';
import { getContext } from 'svelte';
import { get } from 'svelte/store';
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';
import MetadataDialog from '$lib/components/file-list/metadata/MetadataDialog.svelte';
@@ -50,6 +37,9 @@
import { editStyle } from '$lib/components/file-list/style/utils.svelte';
import { waypointPopup } from '$lib/components/map/gpx-layer/GPXLayerPopup';
import { getSymbolKey, symbols } from '$lib/assets/symbols';
import { selection } from '$lib/logic/selection';
import { map } from '$lib/components/map/map';
import { fileActions, pasteSelection } from '$lib/logic/file-actions';
let {
node,
@@ -66,9 +56,9 @@
let singleSelection = $derived($selection.size === 1);
let nodeColors: string[] = $derived.by(() => {
let nodeColors: string[] = []; /* $derived.by(() => {
let colors: string[] = [];
if (node && map.current) {
if (node && map.value) {
if (node instanceof GPXFile) {
let defaultColor = undefined;
@@ -102,23 +92,18 @@
}
}
return colors;
});
});*/
let symbolKey = $derived(node instanceof Waypoint ? getSymbolKey(node.sym) : undefined);
let openEditMetadata: boolean = $state(false);
let openEditStyle: boolean = $state(false);
$effect(() => {
openEditMetadata = editMetadata.current && singleSelection && $selection.has(item);
});
$effect(() => {
openEditStyle =
editStyle.current &&
let openEditMetadata: boolean = $derived(
editMetadata.current && singleSelection && $selection.has(item)
);
let openEditStyle: boolean = $derived(
editStyle.current &&
$selection.has(item) &&
$selection.getSelected().findIndex((i) => i.getFullId() === item.getFullId()) === 0;
});
$selection.getSelected().findIndex((i) => i.getFullId() === item.getFullId()) === 0
);
let hidden = $derived(
item.level === ListLevel.WAYPOINTS ? node._data.hiddenWpt : node._data.hidden
@@ -166,7 +151,8 @@
<span
class="w-full text-left truncate py-1 flex flex-row items-center {hidden
? 'text-muted-foreground'
: ''} {$cut && $copied?.some((i) => i.getFullId() === item.getFullId())
: ''} {selection.cut &&
selection.copied?.some((i) => i.getFullId() === item.getFullId())
? 'text-muted-foreground'
: ''}"
oncontextmenu={(e) => {
@@ -255,20 +241,20 @@
{/if}
<ContextMenu.Item
onclick={() => {
if ($allHidden) {
dbUtils.setHiddenToSelection(false);
} else {
dbUtils.setHiddenToSelection(true);
}
// if ($allHidden) {
// dbUtils.setHiddenToSelection(false);
// } else {
// dbUtils.setHiddenToSelection(true);
// }
}}
>
{#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} />
</ContextMenu.Item>
<ContextMenu.Separator />
@@ -276,7 +262,7 @@
{#if item instanceof ListFileItem}
<ContextMenu.Item
disabled={!singleSelection}
onclick={() => dbUtils.addNewTrack(item.getFileId())}
onclick={() => fileActions.addNewTrack(item.getFileId())}
>
<Plus size="16" class="mr-1" />
{i18n._('menu.new_track')}
@@ -285,7 +271,8 @@
{:else if item instanceof ListTrackItem}
<ContextMenu.Item
disabled={!singleSelection}
onclick={() => dbUtils.addNewSegment(item.getFileId(), item.getTrackIndex())}
onclick={() =>
fileActions.addNewSegment(item.getFileId(), item.getTrackIndex())}
>
<Plus size="16" class="mr-1" />
{i18n._('menu.new_segment')}
@@ -294,7 +281,7 @@
{/if}
{/if}
{#if item.level !== ListLevel.WAYPOINTS}
<ContextMenu.Item onclick={selectAll}>
<ContextMenu.Item onclick={() => selection.selectAll()}>
<FileStack size="16" class="mr-1" />
{i18n._('menu.select_all')}
<Shortcut key="A" ctrl={true} />
@@ -306,26 +293,26 @@
<Shortcut key="⏎" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item onclick={dbUtils.duplicateSelection}>
<ContextMenu.Item onclick={fileActions.duplicateSelection}>
<Copy size="16" class="mr-1" />
{i18n._('menu.duplicate')}
<Shortcut key="D" ctrl={true} /></ContextMenu.Item
>
{#if orientation === 'vertical'}
<ContextMenu.Item onclick={copySelection}>
<ContextMenu.Item onclick={() => selection.copySelection()}>
<ClipboardCopy size="16" class="mr-1" />
{i18n._('menu.copy')}
<Shortcut key="C" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Item onclick={cutSelection}>
<ContextMenu.Item onclick={() => selection.cutSelection()}>
<Scissors size="16" class="mr-1" />
{i18n._('menu.cut')}
<Shortcut key="X" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Item
disabled={$copied === undefined ||
$copied.length === 0 ||
!allowedPastes[$copied[0].level].includes(item.level)}
disabled={selection.copied === undefined ||
selection.copied.length === 0 ||
!allowedPastes[selection.copied[0].level].includes(item.level)}
onclick={pasteSelection}
>
<ClipboardPaste size="16" class="mr-1" />
@@ -334,7 +321,7 @@
</ContextMenu.Item>
{/if}
<ContextMenu.Separator />
<ContextMenu.Item onclick={dbUtils.deleteSelection}>
<ContextMenu.Item onclick={fileActions.deleteSelection}>
{#if item instanceof ListFileItem}
<FileX size="16" class="mr-1" />
{i18n._('menu.close')}

View File

@@ -2,10 +2,10 @@
import CollapsibleTree from '$lib/components/collapsible-tree/CollapsibleTree.svelte';
import FileListNode from '$lib/components/file-list/FileListNode.svelte';
import type { GPXFileWithStatistics } from '$lib/db';
import { getContext } from 'svelte';
import type { Readable } from 'svelte/store';
import { ListFileItem } from './file-list';
import type { GPXFileWithStatistics } from '$lib/logic/statistics';
let {
file,

View File

@@ -1,9 +1,3 @@
// 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,
FILE,