mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-12-03 18:32:12 +00:00
progress
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user