This commit is contained in:
vcoppe
2026-10-04 15:47:09 +02:00
parent bf946f179a
commit ec448b72e2
10 changed files with 1079 additions and 449 deletions
@@ -32,6 +32,7 @@
import { isHidden } from '$lib/file-visibility';
import {
elementId,
isInClipboard,
isSelected,
selectedElementIds,
selectionSize,
@@ -105,7 +106,7 @@
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));
let isCut = $derived($clipboard?.cut === true && isInClipboard($clipboard, node));
</script>
<!-- svelte-ignore a11y_click_events_have_key_events -->
+5 -1
View File
@@ -362,7 +362,11 @@ class Engine {
* 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.
* same kind. The pasted elements are selected.
*
* What is pasted is always the elements as they were when they were copied or cut, even if
* they were changed or deleted since. It can be pasted several times: what was copied is
* copied again, what was cut is moved the first time and copied after that.
*/
paste() {
return this.run((w) => w.paste());
+22 -1
View File
@@ -1,11 +1,12 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import type { FileStructure, Selection } from 'gpx-rs';
import type { Clipboard, FileStructure, Selection } from 'gpx-rs';
import type { VisibilityState } from './file-visibility';
import {
elementId,
hasSelectionWithin,
isCovered,
isInClipboard,
isEmpty,
isSelected,
isSelectionHidden,
@@ -75,6 +76,26 @@ describe('selection helpers', () => {
);
});
it('tells which nodes are in the clipboard', () => {
const clipboard = (type: Clipboard['type'], ids: string[]): Clipboard => ({
type,
ids,
cut: true,
});
assert.deepEqual(
matching(empty, (_, node) => isInClipboard(undefined, node)),
[]
);
const inClipboard = (c: Clipboard) =>
Object.keys(nodes).filter((name) => isInClipboard(c, nodes[name]));
assert.deepEqual(inClipboard(clipboard('files', ['F2'])), ['F2']);
assert.deepEqual(inClipboard(clipboard('tracks', ['T2', 'S1'])), ['T2']);
assert.deepEqual(inClipboard(clipboard('segments', ['S1', 'S3'])), ['S1', 'S3']);
assert.deepEqual(inClipboard(clipboard('waypoints', ['W2'])), ['W2']);
// not mixed up with the other kinds of elements
assert.deepEqual(inClipboard(clipboard('files', ['T1', 'S1', 'W1'])), []);
});
it('counts the selected elements', () => {
assert.equal(selectionSize(empty), 0);
assert.equal(selectionSize(files('F1', 'F2')), 2);
+20 -1
View File
@@ -1,4 +1,4 @@
import type { Selection } from 'gpx-rs';
import type { Clipboard, Selection } from 'gpx-rs';
import { isHidden, waypointsKey, type VisibilityState } from '$lib/file-visibility';
/**
@@ -51,6 +51,25 @@ export function selectedFileIds(selection: Selection): string[] {
}
}
/** Whether the node is one of the elements in the clipboard. */
export function isInClipboard(clipboard: Clipboard | undefined, node: FileTreeNode): boolean {
if (!clipboard) {
return false;
}
switch (node.type) {
case 'file':
return clipboard.type === 'files' && clipboard.ids.includes(node.fileId);
case 'track':
return clipboard.type === 'tracks' && clipboard.ids.includes(node.trackId);
case 'segment':
return clipboard.type === 'segments' && clipboard.ids.includes(node.segmentId);
case 'waypoint':
return clipboard.type === 'waypoints' && clipboard.ids.includes(node.waypointId);
case 'waypoints':
return false;
}
}
/** Number of selected elements (the waypoints node counts for one). */
export function selectionSize(selection: Selection): number {
switch (selection.type) {