add contextmenu actions to edit menu

This commit is contained in:
vcoppe
2024-06-27 18:23:11 +02:00
parent aad5cf8770
commit 804a155257
7 changed files with 141 additions and 27 deletions

View File

@@ -33,7 +33,13 @@
View, View,
FilePen, FilePen,
HeartHandshake, HeartHandshake,
PersonStanding PersonStanding,
Eye,
EyeOff,
ClipboardCopy,
Scissors,
ClipboardPaste,
PaintBucket
} from 'lucide-svelte'; } from 'lucide-svelte';
import { import {
@@ -44,9 +50,15 @@
createFile, createFile,
loadFiles, loadFiles,
toggleSelectionVisibility, toggleSelectionVisibility,
updateSelectionFromKey updateSelectionFromKey,
showSelection,
hideSelection,
anyHidden,
editMetadata,
editStyle
} from '$lib/stores'; } from '$lib/stores';
import { import {
copied,
copySelection, copySelection,
cutSelection, cutSelection,
pasteSelection, pasteSelection,
@@ -65,6 +77,7 @@
import { languages } from '$lib/languages'; import { languages } from '$lib/languages';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { base } from '$app/paths'; import { base } from '$app/paths';
import { allowedPastes, ListFileItem, ListTrackItem } from './file-list/FileList';
const { const {
distanceUnits, distanceUnits,
@@ -180,11 +193,75 @@
<Shortcut key="Z" ctrl={true} shift={true} /> <Shortcut key="Z" ctrl={true} shift={true} />
</Menubar.Item> </Menubar.Item>
<Menubar.Separator /> <Menubar.Separator />
<Menubar.Item
disabled={$selection.size !== 1 ||
!$selection
.getSelected()
.every((item) => item instanceof ListFileItem || item instanceof ListTrackItem)}
on:click={() => ($editMetadata = true)}
>
<Info size="16" class="mr-1" />
{$_('menu.metadata.button')}
</Menubar.Item>
<Menubar.Item
disabled={$selection.size === 0 ||
!$selection
.getSelected()
.every((item) => item instanceof ListFileItem || item instanceof ListTrackItem)}
on:click={() => ($editStyle = true)}
>
<PaintBucket size="16" class="mr-1" />
{$_('menu.style.button')}
</Menubar.Item>
<Menubar.Item
on:click={() => {
if ($anyHidden) {
showSelection();
} else {
hideSelection();
}
}}
disabled={$selection.size == 0}
>
{#if $anyHidden}
<Eye size="16" class="mr-1" />
{$_('menu.unhide')}
{:else}
<EyeOff size="16" class="mr-1" />
{$_('menu.hide')}
{/if}
<Shortcut key="H" ctrl={true} />
</Menubar.Item>
<Menubar.Separator />
<Menubar.Item on:click={selectAll}> <Menubar.Item on:click={selectAll}>
<span class="w-4 mr-1"></span> <span class="w-4 mr-1"></span>
{$_('menu.select_all')} {$_('menu.select_all')}
<Shortcut key="A" ctrl={true} /> <Shortcut key="A" ctrl={true} />
</Menubar.Item> </Menubar.Item>
{#if $verticalFileView}
<Menubar.Separator />
<Menubar.Item on:click={copySelection} disabled={$selection.size === 0}>
<ClipboardCopy size="16" class="mr-1" />
{$_('menu.copy')}
<Shortcut key="C" ctrl={true} />
</Menubar.Item>
<Menubar.Item on:click={cutSelection} disabled={$selection.size === 0}>
<Scissors size="16" class="mr-1" />
{$_('menu.cut')}
<Shortcut key="X" ctrl={true} />
</Menubar.Item>
<Menubar.Item
disabled={$copied === undefined ||
$copied.length === 0 ||
($selection.size > 0 &&
!allowedPastes[$copied[0].level].includes($selection.getSelected().pop()?.level))}
on:click={pasteSelection}
>
<ClipboardPaste size="16" class="mr-1" />
{$_('menu.paste')}
<Shortcut key="V" ctrl={true} />
</Menubar.Item>
{/if}
<Menubar.Separator /> <Menubar.Separator />
<Menubar.Item on:click={dbUtils.deleteSelection} disabled={$selection.size == 0}> <Menubar.Item on:click={dbUtils.deleteSelection} disabled={$selection.size == 0}>
<Trash2 size="16" class="mr-1" /> <Trash2 size="16" class="mr-1" />

View File

@@ -35,7 +35,15 @@
} from './Selection'; } from './Selection';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { gpxLayers, hideSelection, map, showSelection } from '$lib/stores'; import {
anyHidden,
editMetadata,
editStyle,
gpxLayers,
hideSelection,
map,
showSelection
} from '$lib/stores';
import { import {
GPXTreeElement, GPXTreeElement,
Track, Track,
@@ -54,7 +62,6 @@
| Readonly<Waypoint>; | Readonly<Waypoint>;
export let item: ListItem; export let item: ListItem;
export let label: string | undefined; export let label: string | undefined;
let hidden = false;
let orientation = getContext<'vertical' | 'horizontal'>('orientation'); let orientation = getContext<'vertical' | 'horizontal'>('orientation');
@@ -62,9 +69,6 @@
$: singleSelection = $selection.size === 1; $: singleSelection = $selection.size === 1;
let openEditMetadata: boolean = false;
let openEditStyle: boolean = false;
let nodeColors: string[] = []; let nodeColors: string[] = [];
$: if (node && $map) { $: if (node && $map) {
@@ -98,6 +102,15 @@
} }
} }
} }
let openEditMetadata: boolean = false;
let openEditStyle: boolean = false;
$: openEditMetadata = $editMetadata && singleSelection && $selection.has(item);
$: openEditStyle =
$editStyle &&
$selection.has(item) &&
$selection.getSelected().findIndex((i) => i.getFullId() === item.getFullId()) === 0;
</script> </script>
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -108,10 +121,6 @@
if (!get(selection).has(item)) { if (!get(selection).has(item)) {
selectItem(item); selectItem(item);
} }
let layer = gpxLayers.get(item.getFileId());
if (layer) {
hidden = layer.hidden;
}
} }
}} }}
> >
@@ -185,25 +194,25 @@
</ContextMenu.Trigger> </ContextMenu.Trigger>
<ContextMenu.Content> <ContextMenu.Content>
{#if item instanceof ListFileItem || item instanceof ListTrackItem} {#if item instanceof ListFileItem || item instanceof ListTrackItem}
<ContextMenu.Item disabled={!singleSelection} on:click={() => (openEditMetadata = true)}> <ContextMenu.Item disabled={!singleSelection} on:click={() => ($editMetadata = true)}>
<Info size="16" class="mr-1" /> <Info size="16" class="mr-1" />
{$_('menu.metadata.button')} {$_('menu.metadata.button')}
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item on:click={() => (openEditStyle = true)}> <ContextMenu.Item on:click={() => ($editStyle = true)}>
<PaintBucket size="16" class="mr-1" /> <PaintBucket size="16" class="mr-1" />
{$_('menu.style.button')} {$_('menu.style.button')}
</ContextMenu.Item> </ContextMenu.Item>
{#if item instanceof ListFileItem} {#if item instanceof ListFileItem}
<ContextMenu.Item <ContextMenu.Item
on:click={() => { on:click={() => {
if (hidden) { if ($anyHidden) {
showSelection(); showSelection();
} else { } else {
hideSelection(); hideSelection();
} }
}} }}
> >
{#if hidden} {#if $anyHidden}
<Eye size="16" class="mr-1" /> <Eye size="16" class="mr-1" />
{$_('menu.unhide')} {$_('menu.unhide')}
{:else} {:else}
@@ -252,14 +261,12 @@
<ContextMenu.Separator /> <ContextMenu.Separator />
{/if} {/if}
{/if} {/if}
{#if $verticalFileView || item.level !== ListLevel.WAYPOINTS} {#if $verticalFileView}
{#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} {#if $verticalFileView}
<ContextMenu.Item on:click={copySelection}> <ContextMenu.Item on:click={copySelection}>
<ClipboardCopy size="16" class="mr-1" /> <ClipboardCopy size="16" class="mr-1" />

View File

@@ -9,6 +9,7 @@
import { ListFileItem, ListTrackItem, type ListItem } from './FileList'; import { ListFileItem, ListTrackItem, type ListItem } from './FileList';
import { GPXTreeElement, Track, type AnyGPXTreeElement, Waypoint, GPXFile } from 'gpx'; import { GPXTreeElement, Track, type AnyGPXTreeElement, Waypoint, GPXFile } from 'gpx';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import { editMetadata } from '$lib/stores';
export let node: export let node:
| GPXTreeElement<AnyGPXTreeElement> | GPXTreeElement<AnyGPXTreeElement>
@@ -29,6 +30,10 @@
: node instanceof Track : node instanceof Track
? node.desc ?? '' ? node.desc ?? ''
: ''; : '';
$: if (!open) {
$editMetadata = false;
}
</script> </script>
<Popover.Root bind:open> <Popover.Root bind:open>

View File

@@ -8,7 +8,7 @@
import { Save } from 'lucide-svelte'; import { Save } from 'lucide-svelte';
import { ListFileItem, ListTrackItem, type ListItem } from './FileList'; import { ListFileItem, ListTrackItem, type ListItem } from './FileList';
import { selection } from './Selection'; import { selection } from './Selection';
import { gpxLayers } from '$lib/stores'; import { editStyle, gpxLayers } from '$lib/stores';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
export let item: ListItem; export let item: ListItem;
@@ -89,6 +89,10 @@
$: if ($selection && open) { $: if ($selection && open) {
setStyleInputs(); setStyleInputs();
} }
$: if (!open) {
$editStyle = false;
}
</script> </script>
<Popover.Root bind:open> <Popover.Root bind:open>

View File

@@ -22,7 +22,9 @@
import { Crop } from 'lucide-svelte'; import { Crop } from 'lucide-svelte';
import { dbUtils } from '$lib/db'; import { dbUtils } from '$lib/db';
$: validSelection = $selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']); $: validSelection =
$selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']) &&
$gpxStatistics.local.points.length > 0;
let maxSliderValue = 100; let maxSliderValue = 100;
let sliderValues = [0, 100]; let sliderValues = [0, 100];
@@ -82,8 +84,9 @@
variant="outline" variant="outline"
disabled={!validSelection || !canCrop} disabled={!validSelection || !canCrop}
on:click={() => dbUtils.cropSelection(sliderValues[0], sliderValues[1])} on:click={() => dbUtils.cropSelection(sliderValues[0], sliderValues[1])}
><Crop size="16" class="mr-1" />{$_('toolbar.scissors.crop')}</Button
> >
<Crop size="16" class="mr-1" />{$_('toolbar.scissors.crop')}
</Button>
<Separator /> <Separator />
<Label class="flex flex-row gap-3 items-center"> <Label class="flex flex-row gap-3 items-center">
<span class="shrink-0"> <span class="shrink-0">

View File

@@ -446,6 +446,9 @@ export const dbUtils = {
let [result, _removed] = newFile.replaceTrackSegments(trackIndex, segmentIndex + 1, segmentIndex, [file.trk[trackIndex].trkseg[segmentIndex].clone()]); let [result, _removed] = newFile.replaceTrackSegments(trackIndex, segmentIndex + 1, segmentIndex, [file.trk[trackIndex].trkseg[segmentIndex].clone()]);
newFile = result; newFile = result;
} }
} else if (level === ListLevel.WAYPOINTS) {
let [result, _removed] = newFile.replaceWaypoints(file.wpt.length, file.wpt.length - 1, file.wpt.map((wpt) => wpt.clone()));
newFile = result;
} else if (level === ListLevel.WAYPOINT) { } else if (level === ListLevel.WAYPOINT) {
for (let item of items) { for (let item of items) {
let waypointIndex = (item as ListWaypointItem).getWaypointIndex(); let waypointIndex = (item as ListWaypointItem).getWaypointIndex();
@@ -846,7 +849,7 @@ export const dbUtils = {
applyGlobal((draft) => { applyGlobal((draft) => {
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = original(draft)?.get(fileId); let file = original(draft)?.get(fileId);
if (file) { if (file && (level === ListLevel.FILE || level === ListLevel.TRACK)) {
let newFile = file; let newFile = file;
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
newFile = file.setStyle(style); newFile = file.setStyle(style);

View File

@@ -344,6 +344,15 @@ export function exportFile(file: GPXFile) {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
export const anyHidden = writable(false);
function updateAnyHidden() {
anyHidden.set(get(selection).getSelected().some((item) => {
let layer = gpxLayers.get(item.getFileId());
return layer && layer.hidden;
}));
}
selection.subscribe(updateAnyHidden);
export function toggleSelectionVisibility() { export function toggleSelectionVisibility() {
let files = new Set<string>(); let files = new Set<string>();
get(selection).forEach((item) => { get(selection).forEach((item) => {
@@ -355,6 +364,7 @@ export function toggleSelectionVisibility() {
layer.toggleVisibility(); layer.toggleVisibility();
} }
}); });
updateAnyHidden();
} }
export function hideSelection() { export function hideSelection() {
@@ -368,6 +378,7 @@ export function hideSelection() {
layer.toggleVisibility(); layer.toggleVisibility();
} }
}); });
anyHidden.set(true);
} }
export function showSelection() { export function showSelection() {
@@ -381,8 +392,12 @@ export function showSelection() {
layer.toggleVisibility(); layer.toggleVisibility();
} }
}); });
anyHidden.set(false);
} }
export const editMetadata = writable(false);
export const editStyle = writable(false);
let stravaCookies: any = null; let stravaCookies: any = null;
function refreshStravaCookies() { function refreshStravaCookies() {
/* /*