Skip to content

Commit

Permalink
copy paste file items
Browse files Browse the repository at this point in the history
  • Loading branch information
vcoppe committed Jun 20, 2024
1 parent c062908 commit cc92ccc
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 44 deletions.
2 changes: 1 addition & 1 deletion website/src/lib/components/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<Toaster richColors />
{#if !$verticalFileView}
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
<FileList orientation="horizontal" class="pointer-events-auto" />
<FileList orientation="horizontal" />
</div>
{/if}
</div>
Expand Down
17 changes: 16 additions & 1 deletion website/src/lib/components/Menu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@
toggleSelectionVisibility,
updateSelectionFromKey
} from '$lib/stores';
import { selectAll, selection } from '$lib/components/file-list/Selection';
import {
copySelection,
cutSelection,
pasteSelection,
selectAll,
selection
} from '$lib/components/file-list/Selection';
import { derived } from 'svelte/store';
import { canUndo, canRedo, dbUtils, fileObservers, settings } from '$lib/db';
import { anySelectedLayer } from '$lib/components/layer-control/utils';
Expand Down Expand Up @@ -368,6 +374,15 @@
} else if (e.key === 'd' && (e.metaKey || e.ctrlKey)) {
dbUtils.duplicateSelection();
e.preventDefault();
} else if (e.key === 'c' && (e.metaKey || e.ctrlKey)) {
copySelection();
e.preventDefault();
} else if (e.key === 'x' && (e.metaKey || e.ctrlKey)) {
cutSelection();
e.preventDefault();
} else if (e.key === 'v' && (e.metaKey || e.ctrlKey)) {
pasteSelection();
e.preventDefault();
} else if ((e.key === 's' || e.key == 'S') && (e.metaKey || e.ctrlKey)) {
if (e.shiftKey) {
exportAllFiles();
Expand Down
41 changes: 36 additions & 5 deletions website/src/lib/components/file-list/FileList.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<script lang="ts">
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, ListRootItem } from './FileList';
import { selection } from './Selection';
import { ListFileItem, ListLevel, ListRootItem, allowedPastes } from './FileList';
import { copied, pasteSelection, selection } from './Selection';
import { ClipboardPaste, Plus } from 'lucide-svelte';
import Shortcut from '$lib/components/Shortcut.svelte';
import { _ } from 'svelte-i18n';
import { createFile } from '$lib/stores';
export let orientation: 'vertical' | 'horizontal';
export let recursive = false;
Expand Down Expand Up @@ -40,12 +44,39 @@
</script>

<ScrollArea
class="shrink-0 {orientation === 'vertical' ? 'p-1 pr-3' : 'h-10 px-1'}"
class="shrink-0 {orientation === 'vertical' ? 'p-0 pr-3' : 'h-10 px-1'}"
{orientation}
scrollbarXClasses={orientation === 'vertical' ? '' : 'mt-1 h-2'}
scrollbarYClasses={orientation === 'vertical' ? '' : ''}
>
<div class="flex {orientation === 'vertical' ? 'flex-col' : 'flex-row'} {$$props.class ?? ''}">
<div
class="flex {orientation === 'vertical'
? 'flex-col py-1 pl-1 min-h-screen'
: 'flex-row'} {$$props.class ?? ''}"
>
<FileListNode bind:node={$fileObservers} item={new ListRootItem()} />
{#if orientation === 'vertical'}
<ContextMenu.Root>
<ContextMenu.Trigger class="grow" />
<ContextMenu.Content>
<ContextMenu.Item on:click={createFile}>
<Plus size="16" class="mr-1" />
{$_('menu.new_file')}
<Shortcut key="+" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item
disabled={$copied === undefined ||
$copied.length === 0 ||
!allowedPastes[$copied[0].level].includes(ListLevel.ROOT)}
on:click={pasteSelection}
>
<ClipboardPaste size="16" class="mr-1" />
{$_('menu.paste')}
<Shortcut key="V" ctrl={true} />
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
{/if}
</div>
</ScrollArea>
105 changes: 95 additions & 10 deletions website/src/lib/components/file-list/FileList.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { dbUtils } from "$lib/db";
import { dbUtils, getFile, getFileIds } from "$lib/db";
import { castDraft, freeze } from "immer";
import { Track, TrackSegment, Waypoint } from "gpx";
import { GPXFile, Track, TrackSegment, Waypoint } from "gpx";
import { selection } from "./Selection";
import { newGPXFile } from "$lib/stores";

Expand All @@ -13,6 +13,24 @@ export enum ListLevel {
WAYPOINT
}

export const allowedMoves: Record<ListLevel, ListLevel[]> = {
[ListLevel.ROOT]: [],
[ListLevel.FILE]: [ListLevel.FILE],
[ListLevel.TRACK]: [ListLevel.FILE, ListLevel.TRACK],
[ListLevel.SEGMENT]: [ListLevel.FILE, ListLevel.TRACK, ListLevel.SEGMENT],
[ListLevel.WAYPOINTS]: [ListLevel.WAYPOINTS],
[ListLevel.WAYPOINT]: [ListLevel.WAYPOINTS, ListLevel.WAYPOINT]
};

export const allowedPastes: Record<ListLevel, ListLevel[]> = {
[ListLevel.ROOT]: [],
[ListLevel.FILE]: [ListLevel.ROOT, ListLevel.FILE],
[ListLevel.TRACK]: [ListLevel.ROOT, ListLevel.FILE, ListLevel.TRACK],
[ListLevel.SEGMENT]: [ListLevel.ROOT, ListLevel.FILE, ListLevel.TRACK, ListLevel.SEGMENT],
[ListLevel.WAYPOINTS]: [ListLevel.FILE, ListLevel.WAYPOINTS, ListLevel.WAYPOINT],
[ListLevel.WAYPOINT]: [ListLevel.FILE, ListLevel.WAYPOINTS, ListLevel.WAYPOINT]
};

export abstract class ListItem {
level: ListLevel;

Expand All @@ -24,6 +42,7 @@ export abstract class ListItem {
abstract getFullId(): string;
abstract getIdAtLevel(level: ListLevel): string | number | undefined;
abstract getFileId(): string;
abstract getParent(): ListItem;
abstract extend(id: string | number): ListItem;
}

Expand All @@ -48,6 +67,10 @@ export class ListRootItem extends ListItem {
return '';
}

getParent(): ListItem {
return this;
}

extend(id: string): ListFileItem {
return new ListFileItem(id);
}
Expand Down Expand Up @@ -82,6 +105,10 @@ export class ListFileItem extends ListItem {
return this.fileId;
}

getParent(): ListItem {
return new ListRootItem();
}

extend(id: number | 'waypoints'): ListTrackItem | ListWaypointsItem {
if (id === 'waypoints') {
return new ListWaypointsItem(this.fileId);
Expand Down Expand Up @@ -128,6 +155,10 @@ export class ListTrackItem extends ListItem {
return this.trackIndex;
}

getParent(): ListItem {
return new ListFileItem(this.fileId);
}

extend(id: number): ListTrackSegmentItem {
return new ListTrackSegmentItem(this.fileId, this.trackIndex, id);
}
Expand Down Expand Up @@ -178,6 +209,10 @@ export class ListTrackSegmentItem extends ListItem {
return this.segmentIndex;
}

getParent(): ListItem {
return new ListTrackItem(this.fileId, this.trackIndex);
}

extend(): ListTrackSegmentItem {
return this;
}
Expand Down Expand Up @@ -214,6 +249,10 @@ export class ListWaypointsItem extends ListItem {
return this.fileId;
}

getParent(): ListItem {
return new ListFileItem(this.fileId);
}

extend(id: number): ListWaypointItem {
return new ListWaypointItem(this.fileId, id);
}
Expand Down Expand Up @@ -258,6 +297,10 @@ export class ListWaypointItem extends ListItem {
return this.waypointIndex;
}

getParent(): ListItem {
return new ListWaypointsItem(this.fileId);
}

extend(): ListWaypointItem {
return this;
}
Expand All @@ -279,12 +322,37 @@ export function sortItems(items: ListItem[], reverse: boolean = false) {
}
}

export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: ListItem[], toItems: ListItem[]) {
sortItems(fromItems, true);
export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: ListItem[], toItems: ListItem[], remove: boolean = true) {
if (fromItems.length === 0) {
return;
}

sortItems(fromItems, remove && !(fromParent instanceof ListRootItem));
sortItems(toItems, false);

dbUtils.applyEachToFilesAndGlobal([fromParent.getFileId(), toParent.getFileId()], [
(file, context: (Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
let context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[] = [];
if (!remove || fromParent instanceof ListRootItem) {
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());
}
}
});
}

let files = [fromParent.getFileId(), toParent.getFileId()];
let callbacks = [
(file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
let newFile = file;
fromItems.forEach((item) => {
if (item instanceof ListTrackItem) {
Expand All @@ -308,7 +376,7 @@ export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: L
context.reverse();
return newFile;
},
(file, context: (Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
(file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
let newFile = file;
toItems.forEach((item, i) => {
if (item instanceof ListTrackItem) {
Expand Down Expand Up @@ -339,10 +407,27 @@ export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: L
});
return newFile;
}
], (files, context: (Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
];

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 Track) {
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) {
Expand All @@ -360,7 +445,7 @@ export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: L
}
}
});
}, []);
}, context);

selection.update(($selection) => {
$selection.clear();
Expand Down
15 changes: 3 additions & 12 deletions website/src/lib/components/file-list/FileListNodeContent.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
<script lang="ts" context="module">
let pull: Record<ListLevel, ListLevel[]> = {
[ListLevel.ROOT]: [],
[ListLevel.FILE]: [ListLevel.FILE],
[ListLevel.TRACK]: [ListLevel.FILE, ListLevel.TRACK],
[ListLevel.SEGMENT]: [ListLevel.FILE, ListLevel.TRACK, ListLevel.SEGMENT],
[ListLevel.WAYPOINTS]: [ListLevel.WAYPOINTS],
[ListLevel.WAYPOINT]: [ListLevel.WAYPOINTS, ListLevel.WAYPOINT]
};
let dragging: Writable<ListLevel | null> = writable(null);
let updating = false;
Expand All @@ -21,7 +12,7 @@
import { get, writable, type Readable, type Writable } from 'svelte/store';
import FileListNodeStore from './FileListNodeStore.svelte';
import FileListNode from './FileListNode.svelte';
import { ListLevel, ListRootItem, moveItems, type ListItem } from './FileList';
import { ListLevel, ListRootItem, allowedMoves, moveItems, type ListItem } from './FileList';
import { selection } from './Selection';
import { _ } from 'svelte-i18n';
Expand Down Expand Up @@ -132,7 +123,7 @@
sortable = Sortable.create(container, {
group: {
name: sortableLevel,
pull: pull[sortableLevel],
pull: allowedMoves[sortableLevel],
put: true
},
direction: orientation,
Expand Down Expand Up @@ -261,7 +252,7 @@
: parseInt(id);
}
$: canDrop = $dragging !== null && pull[$dragging].includes(sortableLevel);
$: canDrop = $dragging !== null && allowedMoves[$dragging].includes(sortableLevel);
</script>

<div
Expand Down
Loading

0 comments on commit cc92ccc

Please sign in to comment.