copy paste file items

This commit is contained in:
vcoppe
2024-06-20 15:18:21 +02:00
parent c062908000
commit cc92ccc193
10 changed files with 289 additions and 44 deletions

View File

@@ -25,7 +25,7 @@
<Toaster richColors /> <Toaster richColors />
{#if !$verticalFileView} {#if !$verticalFileView}
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30"> <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> </div>
{/if} {/if}
</div> </div>

View File

@@ -45,7 +45,13 @@
toggleSelectionVisibility, toggleSelectionVisibility,
updateSelectionFromKey updateSelectionFromKey
} from '$lib/stores'; } 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 { derived } from 'svelte/store';
import { canUndo, canRedo, dbUtils, fileObservers, settings } from '$lib/db'; import { canUndo, canRedo, dbUtils, fileObservers, settings } from '$lib/db';
import { anySelectedLayer } from '$lib/components/layer-control/utils'; import { anySelectedLayer } from '$lib/components/layer-control/utils';
@@ -368,6 +374,15 @@
} else if (e.key === 'd' && (e.metaKey || e.ctrlKey)) { } else if (e.key === 'd' && (e.metaKey || e.ctrlKey)) {
dbUtils.duplicateSelection(); dbUtils.duplicateSelection();
e.preventDefault(); 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)) { } else if ((e.key === 's' || e.key == 'S') && (e.metaKey || e.ctrlKey)) {
if (e.shiftKey) { if (e.shiftKey) {
exportAllFiles(); exportAllFiles();

View File

@@ -1,11 +1,15 @@
<script lang="ts"> <script lang="ts">
import { ScrollArea } from '$lib/components/ui/scroll-area/index'; import { ScrollArea } from '$lib/components/ui/scroll-area/index';
import * as ContextMenu from '$lib/components/ui/context-menu';
import FileListNode from './FileListNode.svelte'; import FileListNode from './FileListNode.svelte';
import { fileObservers, settings } from '$lib/db'; import { fileObservers, settings } from '$lib/db';
import { setContext } from 'svelte'; import { setContext } from 'svelte';
import { ListFileItem, ListRootItem } from './FileList'; import { ListFileItem, ListLevel, ListRootItem, allowedPastes } from './FileList';
import { selection } from './Selection'; 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 orientation: 'vertical' | 'horizontal';
export let recursive = false; export let recursive = false;
@@ -40,12 +44,39 @@
</script> </script>
<ScrollArea <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} {orientation}
scrollbarXClasses={orientation === 'vertical' ? '' : 'mt-1 h-2'} scrollbarXClasses={orientation === 'vertical' ? '' : 'mt-1 h-2'}
scrollbarYClasses={orientation === 'vertical' ? '' : ''} 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()} /> <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> </div>
</ScrollArea> </ScrollArea>

View File

@@ -1,6 +1,6 @@
import { dbUtils } from "$lib/db"; import { dbUtils, getFile, getFileIds } from "$lib/db";
import { castDraft, freeze } from "immer"; import { castDraft, freeze } from "immer";
import { Track, TrackSegment, Waypoint } from "gpx"; import { GPXFile, Track, TrackSegment, Waypoint } from "gpx";
import { selection } from "./Selection"; import { selection } from "./Selection";
import { newGPXFile } from "$lib/stores"; import { newGPXFile } from "$lib/stores";
@@ -13,6 +13,24 @@ export enum ListLevel {
WAYPOINT 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 { export abstract class ListItem {
level: ListLevel; level: ListLevel;
@@ -24,6 +42,7 @@ export abstract class ListItem {
abstract getFullId(): string; abstract getFullId(): string;
abstract getIdAtLevel(level: ListLevel): string | number | undefined; abstract getIdAtLevel(level: ListLevel): string | number | undefined;
abstract getFileId(): string; abstract getFileId(): string;
abstract getParent(): ListItem;
abstract extend(id: string | number): ListItem; abstract extend(id: string | number): ListItem;
} }
@@ -48,6 +67,10 @@ export class ListRootItem extends ListItem {
return ''; return '';
} }
getParent(): ListItem {
return this;
}
extend(id: string): ListFileItem { extend(id: string): ListFileItem {
return new ListFileItem(id); return new ListFileItem(id);
} }
@@ -82,6 +105,10 @@ export class ListFileItem extends ListItem {
return this.fileId; return this.fileId;
} }
getParent(): ListItem {
return new ListRootItem();
}
extend(id: number | 'waypoints'): ListTrackItem | ListWaypointsItem { extend(id: number | 'waypoints'): ListTrackItem | ListWaypointsItem {
if (id === 'waypoints') { if (id === 'waypoints') {
return new ListWaypointsItem(this.fileId); return new ListWaypointsItem(this.fileId);
@@ -128,6 +155,10 @@ export class ListTrackItem extends ListItem {
return this.trackIndex; return this.trackIndex;
} }
getParent(): ListItem {
return new ListFileItem(this.fileId);
}
extend(id: number): ListTrackSegmentItem { extend(id: number): ListTrackSegmentItem {
return new ListTrackSegmentItem(this.fileId, this.trackIndex, id); return new ListTrackSegmentItem(this.fileId, this.trackIndex, id);
} }
@@ -178,6 +209,10 @@ export class ListTrackSegmentItem extends ListItem {
return this.segmentIndex; return this.segmentIndex;
} }
getParent(): ListItem {
return new ListTrackItem(this.fileId, this.trackIndex);
}
extend(): ListTrackSegmentItem { extend(): ListTrackSegmentItem {
return this; return this;
} }
@@ -214,6 +249,10 @@ export class ListWaypointsItem extends ListItem {
return this.fileId; return this.fileId;
} }
getParent(): ListItem {
return new ListFileItem(this.fileId);
}
extend(id: number): ListWaypointItem { extend(id: number): ListWaypointItem {
return new ListWaypointItem(this.fileId, id); return new ListWaypointItem(this.fileId, id);
} }
@@ -258,6 +297,10 @@ export class ListWaypointItem extends ListItem {
return this.waypointIndex; return this.waypointIndex;
} }
getParent(): ListItem {
return new ListWaypointsItem(this.fileId);
}
extend(): ListWaypointItem { extend(): ListWaypointItem {
return this; return this;
} }
@@ -279,12 +322,37 @@ export function sortItems(items: ListItem[], reverse: boolean = false) {
} }
} }
export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: ListItem[], toItems: ListItem[]) { export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: ListItem[], toItems: ListItem[], remove: boolean = true) {
sortItems(fromItems, true); if (fromItems.length === 0) {
return;
}
sortItems(fromItems, remove && !(fromParent instanceof ListRootItem));
sortItems(toItems, false); sortItems(toItems, false);
dbUtils.applyEachToFilesAndGlobal([fromParent.getFileId(), toParent.getFileId()], [ let context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[] = [];
(file, context: (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; let newFile = file;
fromItems.forEach((item) => { fromItems.forEach((item) => {
if (item instanceof ListTrackItem) { if (item instanceof ListTrackItem) {
@@ -308,7 +376,7 @@ export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: L
context.reverse(); context.reverse();
return newFile; return newFile;
}, },
(file, context: (Track | TrackSegment | Waypoint[] | Waypoint)[]) => { (file, context: (GPXFile | Track | TrackSegment | Waypoint[] | Waypoint)[]) => {
let newFile = file; let newFile = file;
toItems.forEach((item, i) => { toItems.forEach((item, i) => {
if (item instanceof ListTrackItem) { if (item instanceof ListTrackItem) {
@@ -339,10 +407,27 @@ export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: L
}); });
return newFile; 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) => { toItems.forEach((item, i) => {
if (item instanceof ListFileItem) { 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(); let newFile = newGPXFile();
newFile._data.id = item.getFileId(); newFile._data.id = item.getFileId();
if (context[i].name) { if (context[i].name) {
@@ -360,7 +445,7 @@ export function moveItems(fromParent: ListItem, toParent: ListItem, fromItems: L
} }
} }
}); });
}, []); }, context);
selection.update(($selection) => { selection.update(($selection) => {
$selection.clear(); $selection.clear();

View File

@@ -1,13 +1,4 @@
<script lang="ts" context="module"> <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 dragging: Writable<ListLevel | null> = writable(null);
let updating = false; let updating = false;
@@ -21,7 +12,7 @@
import { get, writable, type Readable, type Writable } from 'svelte/store'; import { get, writable, type Readable, type Writable } from 'svelte/store';
import FileListNodeStore from './FileListNodeStore.svelte'; import FileListNodeStore from './FileListNodeStore.svelte';
import FileListNode from './FileListNode.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 { selection } from './Selection';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
@@ -132,7 +123,7 @@
sortable = Sortable.create(container, { sortable = Sortable.create(container, {
group: { group: {
name: sortableLevel, name: sortableLevel,
pull: pull[sortableLevel], pull: allowedMoves[sortableLevel],
put: true put: true
}, },
direction: orientation, direction: orientation,
@@ -261,7 +252,7 @@
: parseInt(id); : parseInt(id);
} }
$: canDrop = $dragging !== null && pull[$dragging].includes(sortableLevel); $: canDrop = $dragging !== null && allowedMoves[$dragging].includes(sortableLevel);
</script> </script>
<div <div

View File

@@ -18,16 +18,27 @@
Trash2, Trash2,
Waypoints, Waypoints,
Eye, Eye,
EyeOff EyeOff,
ClipboardCopy,
ClipboardPaste,
Scissors
} from 'lucide-svelte'; } from 'lucide-svelte';
import { import {
ListFileItem, ListFileItem,
ListLevel, ListLevel,
ListTrackItem, ListTrackItem,
ListWaypointItem, ListWaypointItem,
allowedPastes,
type ListItem type ListItem
} from './FileList'; } from './FileList';
import { selectItem, selection } from './Selection'; import {
copied,
copySelection,
cutSelection,
pasteSelection,
selectItem,
selection
} from './Selection';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { gpxLayers, map, toggleSelectionVisibility } from '$lib/stores'; import { gpxLayers, map, toggleSelectionVisibility } from '$lib/stores';
@@ -198,7 +209,7 @@
class="relative w-full p-0 px-1 border-none overflow-hidden focus-visible:ring-0 focus-visible:ring-offset-0 {orientation === class="relative w-full p-0 px-1 border-none overflow-hidden focus-visible:ring-0 focus-visible:ring-offset-0 {orientation ===
'vertical' 'vertical'
? 'h-fit' ? 'h-fit'
: 'h-9 px-1.5 shadow-md'}" : 'h-9 px-1.5 shadow-md'} pointer-events-auto"
> >
{#if item instanceof ListFileItem || item instanceof ListTrackItem} {#if item instanceof ListFileItem || item instanceof ListTrackItem}
<Popover.Root bind:open={openEditMetadata}> <Popover.Root bind:open={openEditMetadata}>
@@ -416,12 +427,36 @@
<ContextMenu.Separator /> <ContextMenu.Separator />
{/if} {/if}
{/if} {/if}
{#if $verticalFileView || item.level !== ListLevel.WAYPOINTS}
{#if item.level !== ListLevel.WAYPOINTS} {#if item.level !== ListLevel.WAYPOINTS}
<ContextMenu.Item on:click={dbUtils.duplicateSelection}> <ContextMenu.Item on:click={dbUtils.duplicateSelection}>
<Copy size="16" class="mr-1" /> <Copy size="16" class="mr-1" />
{$_('menu.duplicate')} {$_('menu.duplicate')}
<Shortcut key="D" ctrl={true} /></ContextMenu.Item <Shortcut key="D" ctrl={true} /></ContextMenu.Item
> >
{/if}
{#if $verticalFileView}
<ContextMenu.Item on:click={copySelection}>
<ClipboardCopy size="16" class="mr-1" />
{$_('menu.copy')}
<Shortcut key="C" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Item on:click={cutSelection}>
<Scissors size="16" class="mr-1" />
{$_('menu.cut')}
<Shortcut key="X" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Item
disabled={$copied === undefined ||
$copied.length === 0 ||
!allowedPastes[$copied[0].level].includes(item.level)}
on:click={pasteSelection}
>
<ClipboardPaste size="16" class="mr-1" />
{$_('menu.paste')}
<Shortcut key="V" ctrl={true} />
</ContextMenu.Item>
{/if}
<ContextMenu.Separator /> <ContextMenu.Separator />
{/if} {/if}
<ContextMenu.Item on:click={dbUtils.deleteSelection}> <ContextMenu.Item on:click={dbUtils.deleteSelection}>

View File

@@ -1,6 +1,6 @@
import { get, writable } from "svelte/store"; import { get, writable } from "svelte/store";
import { ListFileItem, ListItem, ListRootItem, ListTrackItem, ListTrackSegmentItem, ListWaypointItem, type ListLevel, sortItems, ListWaypointsItem } from "./FileList"; import { ListFileItem, ListItem, ListRootItem, ListTrackItem, ListTrackSegmentItem, ListWaypointItem, ListLevel, sortItems, ListWaypointsItem, moveItems } from "./FileList";
import { fileObservers, getFile, settings } from "$lib/db"; import { fileObservers, getFile, getFileIds, settings } from "$lib/db";
export class SelectionTreeType { export class SelectionTreeType {
item: ListItem; item: ListItem;
@@ -223,3 +223,88 @@ export function applyToOrderedItemsFromFile(selectedItems: ListItem[], callback:
export function applyToOrderedSelectedItemsFromFile(callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void, reverse: boolean = true) { export function applyToOrderedSelectedItemsFromFile(callback: (fileId: string, level: ListLevel | undefined, items: ListItem[]) => void, reverse: boolean = true) {
applyToOrderedItemsFromFile(get(selection).getSelected(), callback, reverse); applyToOrderedItemsFromFile(get(selection).getSelected(), callback, reverse);
} }
export const copied = writable<ListItem[] | undefined>(undefined);
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();
}
}

View File

@@ -102,7 +102,7 @@ export const settings = {
distanceMarkers: dexieSettingStore('distanceMarkers', false), distanceMarkers: dexieSettingStore('distanceMarkers', false),
stravaHeatmapColor: dexieSettingStore('stravaHeatmapColor', 'bluered'), stravaHeatmapColor: dexieSettingStore('stravaHeatmapColor', 'bluered'),
fileOrder: dexieSettingStore<string[]>('fileOrder', []), fileOrder: dexieSettingStore<string[]>('fileOrder', []),
defaultOpacity: dexieSettingStore('defaultOpacity', 0.6), defaultOpacity: dexieSettingStore('defaultOpacity', 0.7),
defaultWeight: dexieSettingStore('defaultWeight', 5), defaultWeight: dexieSettingStore('defaultWeight', 5),
}; };

View File

@@ -134,14 +134,14 @@ export const currentTool = writable<Tool | null>(null);
export const splitAs = writable(SplitType.FILES); export const splitAs = writable(SplitType.FILES);
export function newGPXFile() { export function newGPXFile() {
const newFileName = get(_)("menu.new_filename"); const newFileName = get(_)("menu.new_file");
let file = new GPXFile(); let file = new GPXFile();
let maxNewFileNumber = 0; let maxNewFileNumber = 0;
get(fileObservers).forEach((f) => { get(fileObservers).forEach((f) => {
let file = get(f)?.file; let file = get(f)?.file;
if (file && file.metadata.name.startsWith(newFileName)) { if (file && file.metadata.name && file.metadata.name.startsWith(newFileName)) {
let number = parseInt(file.metadata.name.split(' ').pop() ?? '0'); let number = parseInt(file.metadata.name.split(' ').pop() ?? '0');
if (!isNaN(number) && number > maxNewFileNumber) { if (!isNaN(number) && number > maxNewFileNumber) {
maxNewFileNumber = number; maxNewFileNumber = number;

View File

@@ -1,12 +1,15 @@
{ {
"menu": { "menu": {
"new": "New", "new": "New",
"new_filename": "New file", "new_file": "New file",
"new_track": "New track", "new_track": "New track",
"new_segment": "New segment", "new_segment": "New segment",
"load_desktop": "Load...", "load_desktop": "Load...",
"load_drive": "Load from Google Drive...", "load_drive": "Load from Google Drive...",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"copy": "Copy",
"paste": "Paste",
"cut": "Cut",
"export": "Export...", "export": "Export...",
"export_all": "Export all...", "export_all": "Export all...",
"edit": "Edit", "edit": "Edit",