mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-02 08:42:31 +00:00
tab context menu and shortcuts
This commit is contained in:
8
website/package-lock.json
generated
8
website/package-lock.json
generated
@@ -9,7 +9,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
||||||
"bits-ui": "^0.21.4",
|
"bits-ui": "^0.21.5",
|
||||||
"chart.js": "^4.4.2",
|
"chart.js": "^4.4.2",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"gpx": "file:../gpx",
|
"gpx": "file:../gpx",
|
||||||
@@ -1891,9 +1891,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bits-ui": {
|
"node_modules/bits-ui": {
|
||||||
"version": "0.21.4",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.21.4.tgz",
|
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.21.5.tgz",
|
||||||
"integrity": "sha512-IL+7s19GW561jwkeYk23dwkTfQ9606I062qqv2AtjCdhhIdoOEJNVBX0kjP5xefSaS6ojL0HGG54att0aRTcAQ==",
|
"integrity": "sha512-EDGHWkxnlcV2fbXn2tMps3SfpS7k6bfX3BrQ4s/h79jT6yprBS8DdDficlDK0SDHmPYHBZ0hSy4OgQUDodS/6w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@internationalized/date": "^3.5.1",
|
"@internationalized/date": "^3.5.1",
|
||||||
"@melt-ui/svelte": "0.76.2",
|
"@melt-ui/svelte": "0.76.2",
|
||||||
|
@@ -42,7 +42,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
||||||
"bits-ui": "^0.21.4",
|
"bits-ui": "^0.21.5",
|
||||||
"chart.js": "^4.4.2",
|
"chart.js": "^4.4.2",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"gpx": "file:../gpx",
|
"gpx": "file:../gpx",
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fileOrder, files, getFileIndex, selectedFiles, selectFiles } from '$lib/stores';
|
import { fileOrder, files, getFileIndex, selectedFiles, selectFiles } from '$lib/stores';
|
||||||
|
|
||||||
import { Button } from '$lib/components/ui/button';
|
|
||||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index';
|
import { ScrollArea } from '$lib/components/ui/scroll-area/index';
|
||||||
import Sortable from 'sortablejs/Sortable';
|
import Sortable from 'sortablejs/Sortable';
|
||||||
|
|
||||||
@@ -9,6 +8,7 @@
|
|||||||
|
|
||||||
import { afterUpdate, onMount } from 'svelte';
|
import { afterUpdate, onMount } from 'svelte';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
import FileListItem from './FileListItem.svelte';
|
||||||
|
|
||||||
let container: HTMLDivElement;
|
let container: HTMLDivElement;
|
||||||
let buttons: HTMLDivElement[] = [];
|
let buttons: HTMLDivElement[] = [];
|
||||||
@@ -133,9 +133,7 @@
|
|||||||
data-id={index}
|
data-id={index}
|
||||||
class="pointer-events-auto first:ml-1 last:mr-1 mb-1 bg-transparent"
|
class="pointer-events-auto first:ml-1 last:mr-1 mb-1 bg-transparent"
|
||||||
>
|
>
|
||||||
<Button variant="outline" class="h-9 px-1.5 py-1 border-none shadow-md">
|
<FileListItem {file} />
|
||||||
{get(file).metadata.name}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -148,7 +146,7 @@
|
|||||||
@apply hover:bg-background;
|
@apply hover:bg-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
div :global(.sortable-selected > button) {
|
div :global(.sortable-selected button) {
|
||||||
@apply bg-background;
|
@apply bg-background;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
50
website/src/lib/components/FileListItem.svelte
Normal file
50
website/src/lib/components/FileListItem.svelte
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import * as ContextMenu from '$lib/components/ui/context-menu';
|
||||||
|
import Shortcut from './Shortcut.svelte';
|
||||||
|
import { Copy, Trash2 } from 'lucide-svelte';
|
||||||
|
|
||||||
|
import type { GPXFile } from 'gpx';
|
||||||
|
|
||||||
|
import { get, type Writable } from 'svelte/store';
|
||||||
|
import {
|
||||||
|
duplicateSelectedFiles,
|
||||||
|
removeSelectedFiles,
|
||||||
|
selectedFiles,
|
||||||
|
selectFiles
|
||||||
|
} from '$lib/stores';
|
||||||
|
|
||||||
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
|
export let file: Writable<GPXFile>;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<div
|
||||||
|
on:contextmenu={() => {
|
||||||
|
if (!get(selectedFiles).has(get(file))) {
|
||||||
|
get(selectFiles).select(get(file));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ContextMenu.Root>
|
||||||
|
<ContextMenu.Trigger>
|
||||||
|
<Button variant="outline" class="h-9 px-1.5 py-1 border-none shadow-md">
|
||||||
|
{$file.metadata.name}
|
||||||
|
</Button>
|
||||||
|
</ContextMenu.Trigger>
|
||||||
|
<ContextMenu.Content>
|
||||||
|
<ContextMenu.Item on:click={duplicateSelectedFiles}>
|
||||||
|
<Copy size="16" class="mr-1" />
|
||||||
|
{$_('menu.duplicate')}
|
||||||
|
<Shortcut key="D" ctrl={true} /></ContextMenu.Item
|
||||||
|
>
|
||||||
|
<ContextMenu.Separator />
|
||||||
|
<ContextMenu.Item on:click={removeSelectedFiles}
|
||||||
|
><Trash2 size="16" class="mr-1" />
|
||||||
|
{$_('menu.delete')}
|
||||||
|
<Shortcut key="⌫" ctrl={true} /></ContextMenu.Item
|
||||||
|
>
|
||||||
|
</ContextMenu.Content>
|
||||||
|
</ContextMenu.Root>
|
||||||
|
</div>
|
@@ -2,6 +2,7 @@
|
|||||||
import * as Menubar from '$lib/components/ui/menubar/index.js';
|
import * as Menubar from '$lib/components/ui/menubar/index.js';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import Logo from './Logo.svelte';
|
import Logo from './Logo.svelte';
|
||||||
|
import Shortcut from './Shortcut.svelte';
|
||||||
import { Plus, Copy, Download, Undo2, Redo2, Trash2, Upload, Cloud, Heart } from 'lucide-svelte';
|
import { Plus, Copy, Download, Undo2, Redo2, Trash2, Upload, Cloud, Heart } from 'lucide-svelte';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -55,13 +56,13 @@
|
|||||||
<Menubar.Item on:click={createFile}>
|
<Menubar.Item on:click={createFile}>
|
||||||
<Plus size="16" class="mr-1" />
|
<Plus size="16" class="mr-1" />
|
||||||
{$_('menu.new')}
|
{$_('menu.new')}
|
||||||
<Menubar.Shortcut>⌘N</Menubar.Shortcut>
|
<Shortcut key="N" ctrl={true} />
|
||||||
</Menubar.Item>
|
</Menubar.Item>
|
||||||
<Menubar.Separator />
|
<Menubar.Separator />
|
||||||
<Menubar.Item on:click={triggerFileInput}>
|
<Menubar.Item on:click={triggerFileInput}>
|
||||||
<Upload size="16" class="mr-1" />
|
<Upload size="16" class="mr-1" />
|
||||||
{$_('menu.load_desktop')}
|
{$_('menu.load_desktop')}
|
||||||
<Menubar.Shortcut>⌘O</Menubar.Shortcut>
|
<Shortcut key="O" ctrl={true} />
|
||||||
</Menubar.Item>
|
</Menubar.Item>
|
||||||
<Menubar.Item>
|
<Menubar.Item>
|
||||||
<Cloud size="16" class="mr-1" />
|
<Cloud size="16" class="mr-1" />
|
||||||
@@ -71,18 +72,18 @@
|
|||||||
<Menubar.Item on:click={duplicateSelectedFiles} disabled={$selectedFiles.size == 0}>
|
<Menubar.Item on:click={duplicateSelectedFiles} disabled={$selectedFiles.size == 0}>
|
||||||
<Copy size="16" class="mr-1" />
|
<Copy size="16" class="mr-1" />
|
||||||
{$_('menu.duplicate')}
|
{$_('menu.duplicate')}
|
||||||
<Menubar.Shortcut>⌘D</Menubar.Shortcut>
|
<Shortcut key="D" ctrl={true} />
|
||||||
</Menubar.Item>
|
</Menubar.Item>
|
||||||
<Menubar.Separator />
|
<Menubar.Separator />
|
||||||
<Menubar.Item on:click={exportSelectedFiles} disabled={$selectedFiles.size == 0}>
|
<Menubar.Item on:click={exportSelectedFiles} disabled={$selectedFiles.size == 0}>
|
||||||
<Download size="16" class="mr-1" />
|
<Download size="16" class="mr-1" />
|
||||||
{$_('menu.export')}
|
{$_('menu.export')}
|
||||||
<Menubar.Shortcut>⌘S</Menubar.Shortcut>
|
<Shortcut key="S" ctrl={true} />
|
||||||
</Menubar.Item>
|
</Menubar.Item>
|
||||||
<Menubar.Item on:click={exportAllFiles} disabled={$files.length == 0}>
|
<Menubar.Item on:click={exportAllFiles} disabled={$files.length == 0}>
|
||||||
<Download size="16" class="mr-1" />
|
<Download size="16" class="mr-1" />
|
||||||
{$_('menu.export_all')}
|
{$_('menu.export_all')}
|
||||||
<Menubar.Shortcut>⇧⌘S</Menubar.Shortcut>
|
<Shortcut key="S" ctrl={true} shift={true} />
|
||||||
</Menubar.Item>
|
</Menubar.Item>
|
||||||
</Menubar.Content>
|
</Menubar.Content>
|
||||||
</Menubar.Menu>
|
</Menubar.Menu>
|
||||||
@@ -92,27 +93,34 @@
|
|||||||
<Menubar.Item>
|
<Menubar.Item>
|
||||||
<Undo2 size="16" class="mr-1" />
|
<Undo2 size="16" class="mr-1" />
|
||||||
{$_('menu.undo')}
|
{$_('menu.undo')}
|
||||||
<Menubar.Shortcut>⌘Z</Menubar.Shortcut>
|
<Shortcut key="Z" ctrl={true} />
|
||||||
</Menubar.Item>
|
</Menubar.Item>
|
||||||
<Menubar.Item>
|
<Menubar.Item>
|
||||||
<Redo2 size="16" class="mr-1" />
|
<Redo2 size="16" class="mr-1" />
|
||||||
{$_('menu.redo')}
|
{$_('menu.redo')}
|
||||||
<Menubar.Shortcut>⇧⌘Z</Menubar.Shortcut>
|
<Shortcut key="Z" ctrl={true} shift={true} />
|
||||||
|
</Menubar.Item>
|
||||||
|
<Menubar.Separator />
|
||||||
|
<Menubar.Item on:click={() => $selectFiles.selectAllFiles()}>
|
||||||
|
<span class="w-4 mr-1"></span>
|
||||||
|
{$_('menu.select_all')}
|
||||||
|
<Shortcut key="A" ctrl={true} />
|
||||||
</Menubar.Item>
|
</Menubar.Item>
|
||||||
<Menubar.Separator />
|
<Menubar.Separator />
|
||||||
<Menubar.Item on:click={removeSelectedFiles} disabled={$selectedFiles.size == 0}>
|
<Menubar.Item on:click={removeSelectedFiles} disabled={$selectedFiles.size == 0}>
|
||||||
<Trash2 size="16" class="mr-1" />
|
<Trash2 size="16" class="mr-1" />
|
||||||
{$_('menu.delete')}
|
{$_('menu.delete')}
|
||||||
<Menubar.Shortcut>⌘⌫</Menubar.Shortcut></Menubar.Item
|
<Shortcut key="⌫" ctrl={true} />
|
||||||
>
|
</Menubar.Item>
|
||||||
<Menubar.Item
|
<Menubar.Item
|
||||||
class="text-destructive data-[highlighted]:text-destructive"
|
class="text-destructive data-[highlighted]:text-destructive"
|
||||||
on:click={removeAllFiles}
|
on:click={removeAllFiles}
|
||||||
disabled={$files.length == 0}
|
disabled={$files.length == 0}
|
||||||
>
|
>
|
||||||
<Trash2 size="16" class="mr-1" />
|
<Trash2 size="16" class="mr-1" />
|
||||||
{$_('menu.delete_all')}<Menubar.Shortcut>⇧⌘⌫</Menubar.Shortcut></Menubar.Item
|
{$_('menu.delete_all')}
|
||||||
>
|
<Shortcut key="⌫" ctrl={true} shift={true} />
|
||||||
|
</Menubar.Item>
|
||||||
</Menubar.Content>
|
</Menubar.Content>
|
||||||
</Menubar.Menu>
|
</Menubar.Menu>
|
||||||
<Menubar.Menu>
|
<Menubar.Menu>
|
||||||
|
21
website/src/lib/components/Shortcut.svelte
Normal file
21
website/src/lib/components/Shortcut.svelte
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
|
export let key: string;
|
||||||
|
export let shift: boolean = false;
|
||||||
|
export let ctrl: boolean = false;
|
||||||
|
export let click: boolean = false;
|
||||||
|
|
||||||
|
let isMac = false;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
isMac = navigator.userAgent.toUpperCase().indexOf('MAC') >= 0;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="ml-auto pl-2 text-xs tracking-widest text-muted-foreground"
|
||||||
|
>{shift ? '⇧' : ''}{ctrl ? (isMac ? '⌘' : $_('menu.ctrl') + '+') : ''}{key}{click
|
||||||
|
? $_('menu.click')
|
||||||
|
: ''}</span
|
||||||
|
>
|
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import Shortcut from '$lib/components/Shortcut.svelte';
|
||||||
import { Trash2 } from 'lucide-svelte';
|
import { Trash2 } from 'lucide-svelte';
|
||||||
|
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
@@ -15,8 +16,11 @@
|
|||||||
class="w-full px-2 py-1 h-6 justify-start"
|
class="w-full px-2 py-1 h-6 justify-start"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
on:click={() => element.dispatchEvent(new CustomEvent('delete'))}
|
on:click={() => element.dispatchEvent(new CustomEvent('delete'))}
|
||||||
><Trash2 size="16" class="mr-1" /> {$_('menu.delete')}</Button
|
|
||||||
>
|
>
|
||||||
|
<Trash2 size="16" class="mr-1" />
|
||||||
|
{$_('menu.delete')}
|
||||||
|
<Shortcut key="" shift={true} click={true} />
|
||||||
|
</Button>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -130,13 +130,19 @@ export class RoutingControls {
|
|||||||
});
|
});
|
||||||
marker.on('dragend', this.moveAnchor.bind(this));
|
marker.on('dragend', this.moveAnchor.bind(this));
|
||||||
marker.getElement().addEventListener('click', (e) => {
|
marker.getElement().addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
if (Date.now() - lastDragEvent < 100) { // Prevent click event during drag
|
if (Date.now() - lastDragEvent < 100) { // Prevent click event during drag
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
this.deleteAnchor(anchor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
marker.setPopup(this.popup);
|
marker.setPopup(this.popup);
|
||||||
marker.togglePopup();
|
marker.togglePopup();
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
let deleteThisAnchor = this.getDeleteAnchor(anchor);
|
let deleteThisAnchor = this.getDeleteAnchor(anchor);
|
||||||
this.popupElement.addEventListener('delete', deleteThisAnchor); // Register the delete event for this anchor
|
this.popupElement.addEventListener('delete', deleteThisAnchor); // Register the delete event for this anchor
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
import Check from "lucide-svelte/icons/check";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = ContextMenuPrimitive.CheckboxItemProps;
|
||||||
|
type $$Events = ContextMenuPrimitive.CheckboxItemEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let checked: $$Props["checked"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ContextMenuPrimitive.CheckboxItem
|
||||||
|
bind:checked
|
||||||
|
class={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
on:keydown
|
||||||
|
on:focusin
|
||||||
|
on:focusout
|
||||||
|
on:pointerdown
|
||||||
|
on:pointerleave
|
||||||
|
on:pointermove
|
||||||
|
>
|
||||||
|
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<ContextMenuPrimitive.CheckboxIndicator>
|
||||||
|
<Check class="h-4 w-4" />
|
||||||
|
</ContextMenuPrimitive.CheckboxIndicator>
|
||||||
|
</span>
|
||||||
|
<slot />
|
||||||
|
</ContextMenuPrimitive.CheckboxItem>
|
@@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
import { cn, flyAndScale } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = ContextMenuPrimitive.ContentProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let transition: $$Props["transition"] = flyAndScale;
|
||||||
|
export let transitionConfig: $$Props["transitionConfig"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ContextMenuPrimitive.Content
|
||||||
|
{transition}
|
||||||
|
{transitionConfig}
|
||||||
|
class={cn(
|
||||||
|
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:keydown
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ContextMenuPrimitive.Content>
|
@@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = ContextMenuPrimitive.ItemProps & {
|
||||||
|
inset?: boolean;
|
||||||
|
};
|
||||||
|
type $$Events = ContextMenuPrimitive.ItemEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let inset: $$Props["inset"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ContextMenuPrimitive.Item
|
||||||
|
class={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
on:keydown
|
||||||
|
on:focusin
|
||||||
|
on:focusout
|
||||||
|
on:pointerdown
|
||||||
|
on:pointerleave
|
||||||
|
on:pointermove
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ContextMenuPrimitive.Item>
|
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = ContextMenuPrimitive.LabelProps & {
|
||||||
|
inset?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let inset: $$Props["inset"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ContextMenuPrimitive.Label
|
||||||
|
class={cn("px-2 py-1.5 text-sm font-semibold text-foreground", inset && "pl-8", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ContextMenuPrimitive.Label>
|
@@ -0,0 +1,11 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
type $$Props = ContextMenuPrimitive.RadioGroupProps;
|
||||||
|
|
||||||
|
export let value: $$Props["value"] = undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ContextMenuPrimitive.RadioGroup {...$$restProps} bind:value>
|
||||||
|
<slot />
|
||||||
|
</ContextMenuPrimitive.RadioGroup>
|
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
import Circle from "lucide-svelte/icons/circle";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = ContextMenuPrimitive.RadioItemProps;
|
||||||
|
type $$Events = ContextMenuPrimitive.RadioItemEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let value: ContextMenuPrimitive.RadioItemProps["value"];
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ContextMenuPrimitive.RadioItem
|
||||||
|
class={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{value}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
on:keydown
|
||||||
|
on:focusin
|
||||||
|
on:focusout
|
||||||
|
on:pointerdown
|
||||||
|
on:pointerleave
|
||||||
|
on:pointermove
|
||||||
|
>
|
||||||
|
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<ContextMenuPrimitive.RadioIndicator>
|
||||||
|
<Circle class="h-2 w-2 fill-current" />
|
||||||
|
</ContextMenuPrimitive.RadioIndicator>
|
||||||
|
</span>
|
||||||
|
<slot />
|
||||||
|
</ContextMenuPrimitive.RadioItem>
|
@@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = ContextMenuPrimitive.SeparatorProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ContextMenuPrimitive.Separator
|
||||||
|
class={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
/>
|
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
@@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
import { cn, flyAndScale } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = ContextMenuPrimitive.SubContentProps;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let transition: $$Props["transition"] = flyAndScale;
|
||||||
|
export let transitionConfig: $$Props["transitionConfig"] = {
|
||||||
|
x: -10,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ContextMenuPrimitive.SubContent
|
||||||
|
{transition}
|
||||||
|
{transitionConfig}
|
||||||
|
class={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:keydown
|
||||||
|
on:focusout
|
||||||
|
on:pointermove
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ContextMenuPrimitive.SubContent>
|
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
import ChevronRight from "lucide-svelte/icons/chevron-right";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = ContextMenuPrimitive.SubTriggerProps & {
|
||||||
|
inset?: boolean;
|
||||||
|
};
|
||||||
|
type $$Events = ContextMenuPrimitive.SubTriggerEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let inset: $$Props["inset"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ContextMenuPrimitive.SubTrigger
|
||||||
|
class={cn(
|
||||||
|
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
on:keydown
|
||||||
|
on:focusin
|
||||||
|
on:focusout
|
||||||
|
on:pointerleave
|
||||||
|
on:pointermove
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
<ChevronRight class="ml-auto h-4 w-4" />
|
||||||
|
</ContextMenuPrimitive.SubTrigger>
|
49
website/src/lib/components/ui/context-menu/index.ts
Normal file
49
website/src/lib/components/ui/context-menu/index.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
import Item from "./context-menu-item.svelte";
|
||||||
|
import Label from "./context-menu-label.svelte";
|
||||||
|
import Content from "./context-menu-content.svelte";
|
||||||
|
import Shortcut from "./context-menu-shortcut.svelte";
|
||||||
|
import RadioItem from "./context-menu-radio-item.svelte";
|
||||||
|
import Separator from "./context-menu-separator.svelte";
|
||||||
|
import RadioGroup from "./context-menu-radio-group.svelte";
|
||||||
|
import SubContent from "./context-menu-sub-content.svelte";
|
||||||
|
import SubTrigger from "./context-menu-sub-trigger.svelte";
|
||||||
|
import CheckboxItem from "./context-menu-checkbox-item.svelte";
|
||||||
|
|
||||||
|
const Sub = ContextMenuPrimitive.Sub;
|
||||||
|
const Root = ContextMenuPrimitive.Root;
|
||||||
|
const Trigger = ContextMenuPrimitive.Trigger;
|
||||||
|
const Group = ContextMenuPrimitive.Group;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Sub,
|
||||||
|
Root,
|
||||||
|
Item,
|
||||||
|
Label,
|
||||||
|
Group,
|
||||||
|
Trigger,
|
||||||
|
Content,
|
||||||
|
Shortcut,
|
||||||
|
Separator,
|
||||||
|
RadioItem,
|
||||||
|
SubContent,
|
||||||
|
SubTrigger,
|
||||||
|
RadioGroup,
|
||||||
|
CheckboxItem,
|
||||||
|
//
|
||||||
|
Root as ContextMenu,
|
||||||
|
Sub as ContextMenuSub,
|
||||||
|
Item as ContextMenuItem,
|
||||||
|
Label as ContextMenuLabel,
|
||||||
|
Group as ContextMenuGroup,
|
||||||
|
Content as ContextMenuContent,
|
||||||
|
Trigger as ContextMenuTrigger,
|
||||||
|
Shortcut as ContextMenuShortcut,
|
||||||
|
RadioItem as ContextMenuRadioItem,
|
||||||
|
Separator as ContextMenuSeparator,
|
||||||
|
RadioGroup as ContextMenuRadioGroup,
|
||||||
|
SubContent as ContextMenuSubContent,
|
||||||
|
SubTrigger as ContextMenuSubTrigger,
|
||||||
|
CheckboxItem as ContextMenuCheckboxItem,
|
||||||
|
};
|
@@ -13,6 +13,7 @@
|
|||||||
"redo": "Redo",
|
"redo": "Redo",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"delete_all": "Delete all",
|
"delete_all": "Delete all",
|
||||||
|
"select_all": "Select all",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"distance_units": "Distance units",
|
"distance_units": "Distance units",
|
||||||
"metric": "Metric",
|
"metric": "Metric",
|
||||||
@@ -28,7 +29,9 @@
|
|||||||
"distance_markers": "Show distance markers",
|
"distance_markers": "Show distance markers",
|
||||||
"direction_markers": "Show direction markers",
|
"direction_markers": "Show direction markers",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"donate": "Donate"
|
"donate": "Donate",
|
||||||
|
"ctrl": "Ctrl",
|
||||||
|
"click": "Click"
|
||||||
},
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"routing": {
|
"routing": {
|
||||||
|
Reference in New Issue
Block a user