mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-07 06:14:07 +00:00
progress
This commit is contained in:
@@ -53,27 +53,19 @@
|
||||
import { anySelectedLayer } from '$lib/components/map/layer-control/utils';
|
||||
import { defaultOverlays } from '$lib/assets/layers';
|
||||
import LayerControlSettings from '$lib/components/map/layer-control/LayerControlSettings.svelte';
|
||||
import { ListLevel } 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';
|
||||
import {
|
||||
createFile,
|
||||
loadFiles,
|
||||
pasteSelection,
|
||||
triggerFileInput,
|
||||
} from '$lib/logic/file-actions';
|
||||
import { createFile, loadFiles, triggerFileInput } from '$lib/logic/file-actions';
|
||||
import { engine } from '$lib/engine';
|
||||
import { allHidden } from '$lib/all-hidden';
|
||||
import { selectionSize } from '$lib/selection-helpers';
|
||||
// TODO the clipboard and centering on the selection still work on the previous implementation
|
||||
import { copied, selection as oldSelection } from '$lib/logic/selection';
|
||||
// TODO centering on the selection still works on the previous implementation
|
||||
import { boundsManager } from '$lib/logic/bounds';
|
||||
import { onMount } from 'svelte';
|
||||
import { allowedPastes } from '$lib/components/file-list/sortable-file-list';
|
||||
|
||||
const {
|
||||
distanceUnits,
|
||||
@@ -91,22 +83,12 @@
|
||||
routing,
|
||||
} = settings;
|
||||
|
||||
const { files, selection, canUndo, canRedo } = engine;
|
||||
const { files, selection, canUndo, canRedo, canPaste } = engine;
|
||||
|
||||
let selectionCount = $derived(selectionSize($selection));
|
||||
let noFiles = $derived($files.size === 0);
|
||||
// metadata and style can be edited for files and tracks only
|
||||
let filesOrTracksSelected = $derived($selection.type === 'file' || $selection.type === 'track');
|
||||
let selectionLevel = $derived(
|
||||
{
|
||||
empty: undefined,
|
||||
file: ListLevel.FILE,
|
||||
track: ListLevel.TRACK,
|
||||
segment: ListLevel.SEGMENT,
|
||||
waypoints: ListLevel.WAYPOINTS,
|
||||
waypoint: ListLevel.WAYPOINT,
|
||||
}[$selection.type]
|
||||
);
|
||||
|
||||
function switchBasemaps() {
|
||||
[$currentBasemap, $previousBasemap] = [$previousBasemap, $currentBasemap];
|
||||
@@ -291,29 +273,17 @@
|
||||
</Menubar.Item>
|
||||
{#if $treeFileView}
|
||||
<Menubar.Separator />
|
||||
<Menubar.Item
|
||||
onclick={() => oldSelection.copySelection()}
|
||||
disabled={selectionCount === 0}
|
||||
>
|
||||
<Menubar.Item onclick={() => engine.copy()} disabled={selectionCount === 0}>
|
||||
<ClipboardCopy size="16" />
|
||||
{i18n._('menu.copy')}
|
||||
<Shortcut key="C" ctrl={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Item
|
||||
onclick={() => oldSelection.cutSelection()}
|
||||
disabled={selectionCount === 0}
|
||||
>
|
||||
<Menubar.Item onclick={() => engine.cut()} disabled={selectionCount === 0}>
|
||||
<Scissors size="16" />
|
||||
{i18n._('menu.cut')}
|
||||
<Shortcut key="X" ctrl={true} />
|
||||
</Menubar.Item>
|
||||
<Menubar.Item
|
||||
disabled={$copied === undefined ||
|
||||
$copied.length === 0 ||
|
||||
(selectionLevel !== undefined &&
|
||||
!allowedPastes[$copied[0].level].includes(selectionLevel))}
|
||||
onclick={pasteSelection}
|
||||
>
|
||||
<Menubar.Item disabled={!$canPaste} onclick={() => engine.paste()}>
|
||||
<ClipboardPaste size="16" />
|
||||
{i18n._('menu.paste')}
|
||||
<Shortcut key="V" ctrl={true} />
|
||||
@@ -558,17 +528,17 @@
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'c' && (e.metaKey || e.ctrlKey)) {
|
||||
if (!targetInput) {
|
||||
oldSelection.copySelection();
|
||||
engine.copy();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else if (e.key === 'x' && (e.metaKey || e.ctrlKey)) {
|
||||
if (!targetInput) {
|
||||
oldSelection.cutSelection();
|
||||
engine.cut();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else if (e.key === 'v' && (e.metaKey || e.ctrlKey)) {
|
||||
if (!targetInput) {
|
||||
pasteSelection();
|
||||
engine.paste();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else if ((e.key === 's' || e.key == 'S') && (e.metaKey || e.ctrlKey)) {
|
||||
|
||||
@@ -3,16 +3,12 @@
|
||||
import * as ContextMenu from '$lib/components/ui/context-menu';
|
||||
import FileListNodeContent from './FileListNodeContent.svelte';
|
||||
import { onMount, setContext } from 'svelte';
|
||||
import { ListLevel } from './file-list';
|
||||
import { ClipboardPaste, FileStack, Plus } from '@lucide/svelte';
|
||||
import Shortcut from '$lib/components/Shortcut.svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { engine } from '$lib/engine';
|
||||
import { selectedFileIds } from '$lib/selection-helpers';
|
||||
import { createFile, pasteSelection } from '$lib/logic/file-actions';
|
||||
// TODO the clipboard still works on the previous implementation
|
||||
import { copied } from '$lib/logic/selection';
|
||||
import { allowedPastes } from './sortable-file-list';
|
||||
import { createFile } from '$lib/logic/file-actions';
|
||||
|
||||
let {
|
||||
orientation,
|
||||
@@ -29,7 +25,7 @@
|
||||
setContext('orientation', orientation);
|
||||
setContext('recursive', recursive);
|
||||
|
||||
const { files, selection } = engine;
|
||||
const { files, selection, canPaste } = engine;
|
||||
|
||||
onMount(() => {
|
||||
if (orientation === 'horizontal' && $selection.type !== 'file') {
|
||||
@@ -71,12 +67,7 @@
|
||||
<Shortcut key="A" ctrl={true} />
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item
|
||||
disabled={$copied === undefined ||
|
||||
$copied.length === 0 ||
|
||||
!allowedPastes[$copied[0].level].includes(ListLevel.ROOT)}
|
||||
onclick={pasteSelection}
|
||||
>
|
||||
<ContextMenu.Item disabled={!$canPaste} onclick={() => engine.paste()}>
|
||||
<ClipboardPaste size="16" />
|
||||
{i18n._('menu.paste')}
|
||||
<Shortcut key="V" ctrl={true} />
|
||||
|
||||
@@ -37,15 +37,12 @@
|
||||
selectionSize,
|
||||
type FileTreeNode,
|
||||
} from '$lib/selection-helpers';
|
||||
// TODO the clipboard, centering on the selection and the waypoint popup still work on the
|
||||
// previous implementation
|
||||
import { copied, selection as oldSelection } from '$lib/logic/selection';
|
||||
import { pasteSelection } from '$lib/logic/file-actions';
|
||||
// TODO centering on the selection and the waypoint popup still work on the previous
|
||||
// implementation
|
||||
import { boundsManager } from '$lib/logic/bounds';
|
||||
// import { gpxLayers } from '$lib/components/map/gpx-layer/gpx-layers';
|
||||
// import { fileStateCollection } from '$lib/logic/file-state';
|
||||
// import { waypointPopup } from '$lib/components/map/gpx-layer/gpx-layer-popup';
|
||||
import { allowedPastes } from './sortable-file-list';
|
||||
|
||||
let {
|
||||
fileState,
|
||||
@@ -57,7 +54,7 @@
|
||||
label: string | undefined;
|
||||
} = $props();
|
||||
|
||||
const { selection } = engine;
|
||||
const { selection, clipboard, canPaste } = engine;
|
||||
|
||||
let orientation = getContext<'vertical' | 'horizontal'>('orientation');
|
||||
let embedding = getContext<boolean>('embedding');
|
||||
@@ -107,6 +104,8 @@
|
||||
);
|
||||
|
||||
let hidden = $derived(isHidden(fileState, elementId(node)));
|
||||
// cut elements are greyed until they are pasted
|
||||
let isCut = $derived($clipboard?.cut === true && isSelected($clipboard.selection, node));
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
@@ -150,7 +149,7 @@
|
||||
<span
|
||||
class="grow text-left truncate ml-1 flex flex-row items-center {hidden
|
||||
? 'text-muted-foreground'
|
||||
: ''} {/* TODO cut elements: the clipboard works on the previous implementation */ ''}"
|
||||
: ''} {isCut ? 'text-muted-foreground' : ''}"
|
||||
oncontextmenu={(e) => {
|
||||
if (embedding) {
|
||||
e.preventDefault();
|
||||
@@ -278,22 +277,17 @@
|
||||
<Shortcut key="D" ctrl={true} />
|
||||
</ContextMenu.Item>
|
||||
{#if orientation === 'vertical'}
|
||||
<ContextMenu.Item onclick={() => oldSelection.copySelection()}>
|
||||
<ContextMenu.Item onclick={() => engine.copy()}>
|
||||
<ClipboardCopy size="16" />
|
||||
{i18n._('menu.copy')}
|
||||
<Shortcut key="C" ctrl={true} />
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onclick={() => oldSelection.cutSelection()}>
|
||||
<ContextMenu.Item onclick={() => engine.cut()}>
|
||||
<Scissors size="16" />
|
||||
{i18n._('menu.cut')}
|
||||
<Shortcut key="X" ctrl={true} />
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item
|
||||
disabled={$copied === undefined ||
|
||||
$copied.length === 0 ||
|
||||
!allowedPastes[$copied[0].level].includes(level)}
|
||||
onclick={pasteSelection}
|
||||
>
|
||||
<ContextMenu.Item disabled={!$canPaste} onclick={() => engine.paste()}>
|
||||
<ClipboardPaste size="16" />
|
||||
{i18n._('menu.paste')}
|
||||
<Shortcut key="V" ctrl={true} />
|
||||
|
||||
@@ -20,15 +20,6 @@ export const allowedMoves: Record<ListLevel, ListLevel[]> = {
|
||||
[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 const dragging = writable<ListLevel | null>(null);
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,9 +4,10 @@ import { FileColorAllocator, normalizeColor } from '$lib/file-colors';
|
||||
import { setHidden, type Visibility } from '$lib/file-visibility';
|
||||
import { selectedElementIds, type FileTreeNode } from '$lib/selection-helpers';
|
||||
import type { Feature, FeatureCollection, LineString, Point } from 'geojson';
|
||||
import type { GlobalStatistics, FileStructure, Selection } from 'gpx-rs';
|
||||
import type { Clipboard, GlobalStatistics, FileStructure, Selection } from 'gpx-rs';
|
||||
|
||||
export type {
|
||||
Clipboard,
|
||||
GlobalStatistics,
|
||||
FileStructure,
|
||||
FilesUpdate,
|
||||
@@ -199,6 +200,8 @@ class Engine {
|
||||
private _selection = writable<Selection>({ type: 'empty' });
|
||||
private _canUndo = writable(false);
|
||||
private _canRedo = writable(false);
|
||||
private _canPaste = writable(false);
|
||||
private _clipboard = writable<Clipboard | undefined>(undefined);
|
||||
private _statistics = writable<SelectionStatistics>(EMPTY_SELECTION_STATISTICS);
|
||||
/** Identifies the statistics currently in the engine's buffers. */
|
||||
private _statisticsVersion = 0;
|
||||
@@ -209,6 +212,10 @@ class Engine {
|
||||
readonly statistics: Readable<SelectionStatistics> = { subscribe: this._statistics.subscribe };
|
||||
readonly canUndo: Readable<boolean> = { subscribe: this._canUndo.subscribe };
|
||||
readonly canRedo: Readable<boolean> = { subscribe: this._canRedo.subscribe };
|
||||
/** What was copied or cut, waiting to be pasted. */
|
||||
readonly clipboard: Readable<Clipboard | undefined> = { subscribe: this._clipboard.subscribe };
|
||||
/** Whether the clipboard can be pasted with the current selection. */
|
||||
readonly canPaste: Readable<boolean> = { subscribe: this._canPaste.subscribe };
|
||||
/** What is currently selected. */
|
||||
readonly selection: Readable<Selection> = { subscribe: this._selection.subscribe };
|
||||
/**
|
||||
@@ -338,6 +345,28 @@ class Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// Clipboard
|
||||
|
||||
/** Puts the selected elements in the clipboard. */
|
||||
copy() {
|
||||
return this.run((w) => w.copy());
|
||||
}
|
||||
|
||||
/** Like `copy`, but the elements are moved when they are pasted. */
|
||||
cut() {
|
||||
return this.run((w) => w.cut());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pastes the clipboard, depending on the selection: the files, tracks and segments become new
|
||||
* files when nothing is selected, tracks and waypoints are added to a selected file,
|
||||
* segments to a selected track, and pasted after a selected track, segment or waypoint of the
|
||||
* same kind. The pasted elements are selected and the clipboard is emptied.
|
||||
*/
|
||||
paste() {
|
||||
return this.run((w) => w.paste());
|
||||
}
|
||||
|
||||
// Edits of the selection
|
||||
|
||||
newTrack() {
|
||||
@@ -408,6 +437,10 @@ class Engine {
|
||||
const update = wasm.last_update();
|
||||
this._canUndo.set(wasm.can_undo());
|
||||
this._canRedo.set(wasm.can_redo());
|
||||
this._canPaste.set(wasm.can_paste());
|
||||
if (update.clipboardChanged) {
|
||||
this._clipboard.set(wasm.clipboard());
|
||||
}
|
||||
if (update.selectionChanged) {
|
||||
this._selection.set(wasm.selection());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user