Files
gpx.studio/website/src/lib/components/Menu.svelte

685 lines
30 KiB
Svelte
Raw Normal View History

2024-04-08 17:12:39 +02:00
<script lang="ts">
import * as Menubar from '$lib/components/ui/menubar/index.js';
import { Button } from '$lib/components/ui/button';
import Logo from '$lib/components/Logo.svelte';
import Shortcut from '$lib/components/Shortcut.svelte';
import {
Plus,
Copy,
Download,
Undo2,
Redo2,
Trash2,
Heart,
Map,
Layers2,
Box,
Milestone,
Coins,
Ruler,
Zap,
Thermometer,
Sun,
Moon,
Layers,
ListTree,
Languages,
Settings,
Info,
File,
View,
FilePen,
HeartHandshake,
PersonStanding,
Eye,
EyeOff,
ClipboardCopy,
Scissors,
ClipboardPaste,
PaintBucket,
FolderOpen,
FileStack,
FileX,
BookOpenText,
ChartArea,
Maximize,
2025-06-21 21:07:36 +02:00
} from '@lucide/svelte';
2025-10-17 23:54:45 +02:00
import { map } from '$lib/components/map/map';
2025-06-21 21:07:36 +02:00
import { editMetadata } from '$lib/components/file-list/metadata/utils.svelte';
import { editStyle } from '$lib/components/file-list/style/utils.svelte';
import { exportState, ExportState } from '$lib/components/export/utils.svelte';
2025-10-17 23:54:45 +02:00
import { anySelectedLayer } from '$lib/components/map/layer-control/utils';
import { defaultOverlays } from '$lib/assets/layers';
2025-10-17 23:54:45 +02:00
import LayerControlSettings from '$lib/components/map/layer-control/LayerControlSettings.svelte';
2025-10-05 19:34:05 +02:00
import {
allowedPastes,
ListFileItem,
ListTrackItem,
} from '$lib/components/file-list/file-list';
2025-06-21 21:07:36 +02:00
import Export from '$lib/components/export/Export.svelte';
2025-06-08 16:32:41 +02:00
import { mode, setMode } from 'mode-watcher';
2025-06-21 21:07:36 +02:00
import { i18n } from '$lib/i18n.svelte';
import { languages } from '$lib/languages';
import { getURLForLanguage } from '$lib/utils';
2025-10-17 23:54:45 +02:00
import { settings } from '$lib/logic/settings';
2025-10-05 19:34:05 +02:00
import {
createFile,
fileActions,
loadFiles,
pasteSelection,
triggerFileInput,
2025-10-17 23:54:45 +02:00
} from '$lib/logic/file-actions';
import { fileStateCollection } from '$lib/logic/file-state';
import { fileActionManager } from '$lib/logic/file-action-manager';
2025-10-18 09:36:55 +02:00
import { copied, selection } from '$lib/logic/selection';
2024-04-24 16:12:50 +02:00
const {
distanceUnits,
velocityUnits,
temperatureUnits,
elevationProfile,
treeFileView,
currentBasemap,
previousBasemap,
currentOverlays,
previousOverlays,
distanceMarkers,
directionMarkers,
streetViewSource,
routing,
} = settings;
2024-05-08 12:35:31 +02:00
function switchBasemaps() {
2025-10-17 23:54:45 +02:00
[$currentBasemap, $previousBasemap] = [$previousBasemap, $currentBasemap];
}
2024-05-06 15:52:11 +02:00
function toggleOverlays() {
2025-10-17 23:54:45 +02:00
if ($currentOverlays && anySelectedLayer($currentOverlays)) {
[$currentOverlays, $previousOverlays] = [defaultOverlays, $currentOverlays];
} else {
2025-10-17 23:54:45 +02:00
[$currentOverlays, $previousOverlays] = [$previousOverlays, defaultOverlays];
}
}
2024-05-23 11:21:57 +02:00
2025-06-21 21:07:36 +02:00
let layerSettingsOpen = $state(false);
2024-04-08 17:12:39 +02:00
</script>
2024-06-20 11:59:21 +02:00
<div class="absolute md:top-2 left-0 right-0 z-20 flex flex-row justify-center pointer-events-none">
<div
class="w-fit flex flex-row items-center justify-center p-1 bg-background rounded-b-md md:rounded-md pointer-events-auto shadow-md"
>
2025-06-21 21:07:36 +02:00
<a href={getURLForLanguage(i18n.lang, '/')} target="_blank" class="shrink-0">
<Logo class="h-5 mt-0.5 mx-2 md:hidden" iconOnly={true} width="16" />
<Logo class="h-5 mt-0.5 mx-2 hidden md:block" width="96" />
</a>
2025-10-05 19:34:05 +02:00
<Menubar.Root class="border-none shadow-none h-fit p-0">
<Menubar.Menu>
2025-06-21 21:07:36 +02:00
<Menubar.Trigger aria-label={i18n._('gpx.file')}>
<File size="18" class="md:hidden" />
2025-06-21 21:07:36 +02:00
<span class="hidden md:block">{i18n._('gpx.file')}</span>
</Menubar.Trigger>
<Menubar.Content class="border-none">
2025-06-21 21:07:36 +02:00
<Menubar.Item onclick={createFile}>
<Plus size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.new')}
<Shortcut key="+" ctrl={true} />
</Menubar.Item>
<Menubar.Separator />
2025-06-21 21:07:36 +02:00
<Menubar.Item onclick={triggerFileInput}>
<FolderOpen size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.open')}
<Shortcut key="O" ctrl={true} />
</Menubar.Item>
<Menubar.Separator />
<Menubar.Item
2025-10-05 19:34:05 +02:00
onclick={fileActions.duplicateSelection}
2025-10-17 23:54:45 +02:00
disabled={$selection.size == 0}
>
<Copy size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.duplicate')}
<Shortcut key="D" ctrl={true} />
</Menubar.Item>
<Menubar.Separator />
<Menubar.Item
2025-10-05 19:34:05 +02:00
onclick={fileActions.deleteSelectedFiles}
2025-10-17 23:54:45 +02:00
disabled={$selection.size == 0}
>
<FileX size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.close')}
<Shortcut key="⌫" ctrl={true} />
</Menubar.Item>
<Menubar.Item
2025-10-05 19:34:05 +02:00
onclick={fileActions.deleteAllFiles}
disabled={fileStateCollection.size == 0}
>
<FileX size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.close_all')}
<Shortcut key="⌫" ctrl={true} shift={true} />
</Menubar.Item>
<Menubar.Separator />
<Menubar.Item
2025-06-21 21:07:36 +02:00
onclick={() => (exportState.current = ExportState.SELECTION)}
2025-10-17 23:54:45 +02:00
disabled={$selection.size == 0}
>
<Download size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.export')}
<Shortcut key="S" ctrl={true} />
</Menubar.Item>
<Menubar.Item
2025-06-21 21:07:36 +02:00
onclick={() => (exportState.current = ExportState.ALL)}
2025-10-05 19:34:05 +02:00
disabled={fileStateCollection.size == 0}
>
<Download size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.export_all')}
<Shortcut key="S" ctrl={true} shift={true} />
</Menubar.Item>
</Menubar.Content>
</Menubar.Menu>
<Menubar.Menu>
2025-06-21 21:07:36 +02:00
<Menubar.Trigger aria-label={i18n._('menu.edit')}>
<FilePen size="18" class="md:hidden" />
2025-06-21 21:07:36 +02:00
<span class="hidden md:block">{i18n._('menu.edit')}</span>
</Menubar.Trigger>
<Menubar.Content class="border-none">
2025-10-05 19:34:05 +02:00
<Menubar.Item
2025-10-17 23:54:45 +02:00
onclick={() => fileActionManager.undo()}
2025-10-05 19:34:05 +02:00
disabled={!fileActionManager.canUndo}
>
<Undo2 size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.undo')}
<Shortcut key="Z" ctrl={true} />
</Menubar.Item>
2025-10-05 19:34:05 +02:00
<Menubar.Item
2025-10-17 23:54:45 +02:00
onclick={() => fileActionManager.redo()}
2025-10-05 19:34:05 +02:00
disabled={!fileActionManager.canRedo}
>
<Redo2 size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.redo')}
<Shortcut key="Z" ctrl={true} shift={true} />
</Menubar.Item>
<Menubar.Separator />
<Menubar.Item
2025-10-17 23:54:45 +02:00
disabled={$selection.size !== 1 ||
!$selection
.getSelected()
.every(
(item) =>
item instanceof ListFileItem ||
item instanceof ListTrackItem
)}
2025-06-21 21:07:36 +02:00
onclick={() => (editMetadata.current = true)}
>
<Info size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.metadata.button')}
<Shortcut key="I" ctrl={true} />
</Menubar.Item>
<Menubar.Item
2025-10-17 23:54:45 +02:00
disabled={$selection.size === 0 ||
!$selection
.getSelected()
.every(
(item) =>
item instanceof ListFileItem ||
item instanceof ListTrackItem
)}
2025-06-21 21:07:36 +02:00
onclick={() => (editStyle.current = true)}
>
<PaintBucket size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.style.button')}
</Menubar.Item>
<Menubar.Item
2025-06-21 21:07:36 +02:00
onclick={() => {
2025-10-05 19:34:05 +02:00
// if ($allHidden) {
// fileActions.setHiddenToSelection(false);
// } else {
// fileActions.setHiddenToSelection(true);
// }
}}
2025-10-17 23:54:45 +02:00
disabled={$selection.size == 0}
>
2025-10-05 19:34:05 +02:00
<!-- {#if $allHidden}
<Eye size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.unhide')}
{:else}
<EyeOff size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.hide')}
2025-10-05 19:34:05 +02:00
{/if} -->
<Shortcut key="H" ctrl={true} />
</Menubar.Item>
2025-10-17 23:54:45 +02:00
{#if $treeFileView}
{#if $selection.getSelected().some((item) => item instanceof ListFileItem)}
<Menubar.Separator />
<Menubar.Item
2025-06-21 21:07:36 +02:00
onclick={() =>
2025-10-05 19:34:05 +02:00
fileActions.addNewTrack(
2025-10-17 23:54:45 +02:00
$selection.getSelected()[0].getFileId()
2025-10-05 19:34:05 +02:00
)}
2025-10-17 23:54:45 +02:00
disabled={$selection.size !== 1}
>
<Plus size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.new_track')}
</Menubar.Item>
2025-10-17 23:54:45 +02:00
{:else if $selection
.getSelected()
.some((item) => item instanceof ListTrackItem)}
<Menubar.Separator />
<Menubar.Item
2025-06-21 21:07:36 +02:00
onclick={() => {
2025-10-17 23:54:45 +02:00
let item = $selection.getSelected()[0];
2025-10-05 19:34:05 +02:00
fileActions.addNewSegment(
item.getFileId(),
item.getTrackIndex()
);
}}
2025-10-17 23:54:45 +02:00
disabled={$selection.size !== 1}
>
<Plus size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.new_segment')}
</Menubar.Item>
{/if}
{/if}
<Menubar.Separator />
2025-10-05 19:34:05 +02:00
<Menubar.Item
onclick={selection.selectAll}
disabled={fileStateCollection.size == 0}
>
<FileStack size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.select_all')}
<Shortcut key="A" ctrl={true} />
</Menubar.Item>
<Menubar.Item
2025-06-21 21:07:36 +02:00
onclick={() => {
2025-10-17 23:54:45 +02:00
if ($selection.size > 0) {
2025-10-05 19:34:05 +02:00
// centerMapOnSelection();
}
}}
>
<Maximize size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.center')}
<Shortcut key="⏎" ctrl={true} />
</Menubar.Item>
2025-10-17 23:54:45 +02:00
{#if $treeFileView}
<Menubar.Separator />
2025-10-05 19:34:05 +02:00
<Menubar.Item
onclick={selection.copySelection}
2025-10-17 23:54:45 +02:00
disabled={$selection.size === 0}
2025-10-05 19:34:05 +02:00
>
<ClipboardCopy size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.copy')}
<Shortcut key="C" ctrl={true} />
</Menubar.Item>
2025-10-05 19:34:05 +02:00
<Menubar.Item
onclick={selection.cutSelection}
2025-10-17 23:54:45 +02:00
disabled={$selection.size === 0}
2025-10-05 19:34:05 +02:00
>
<Scissors size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.cut')}
<Shortcut key="X" ctrl={true} />
</Menubar.Item>
<Menubar.Item
2025-10-18 09:36:55 +02:00
disabled={$copied === undefined ||
$copied.length === 0 ||
2025-10-17 23:54:45 +02:00
($selection.size > 0 &&
2025-10-18 09:36:55 +02:00
!allowedPastes[$copied[0].level].includes(
2025-10-17 23:54:45 +02:00
$selection.getSelected().pop()?.level
))}
2025-06-21 21:07:36 +02:00
onclick={pasteSelection}
>
<ClipboardPaste size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.paste')}
<Shortcut key="V" ctrl={true} />
</Menubar.Item>
{/if}
<Menubar.Separator />
2025-10-05 19:34:05 +02:00
<Menubar.Item
onclick={fileActions.deleteSelection}
2025-10-17 23:54:45 +02:00
disabled={$selection.size == 0}
2025-10-05 19:34:05 +02:00
>
<Trash2 size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.delete')}
<Shortcut key="⌫" ctrl={true} />
</Menubar.Item>
</Menubar.Content>
</Menubar.Menu>
<Menubar.Menu>
2025-06-21 21:07:36 +02:00
<Menubar.Trigger aria-label={i18n._('menu.view')}>
<View size="18" class="md:hidden" />
2025-06-21 21:07:36 +02:00
<span class="hidden md:block">{i18n._('menu.view')}</span>
</Menubar.Trigger>
<Menubar.Content class="border-none">
2025-10-17 23:54:45 +02:00
<Menubar.CheckboxItem bind:checked={$elevationProfile}>
<ChartArea size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.elevation_profile')}
<Shortcut key="P" ctrl={true} />
</Menubar.CheckboxItem>
2025-10-17 23:54:45 +02:00
<Menubar.CheckboxItem bind:checked={$treeFileView}>
<ListTree size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.tree_file_view')}
<Shortcut key="L" ctrl={true} />
</Menubar.CheckboxItem>
<Menubar.Separator />
2025-06-21 21:07:36 +02:00
<Menubar.Item inset onclick={switchBasemaps}>
<Map size="16" class="mr-1" />{i18n._('menu.switch_basemap')}<Shortcut
key="F1"
/>
</Menubar.Item>
2025-06-21 21:07:36 +02:00
<Menubar.Item inset onclick={toggleOverlays}>
<Layers2 size="16" class="mr-1" />{i18n._('menu.toggle_overlays')}<Shortcut
key="F2"
/>
</Menubar.Item>
<Menubar.Separator />
2025-10-17 23:54:45 +02:00
<Menubar.CheckboxItem bind:checked={$distanceMarkers}>
2025-06-21 21:07:36 +02:00
<Coins size="16" class="mr-1" />{i18n._('menu.distance_markers')}<Shortcut
key="F3"
/>
</Menubar.CheckboxItem>
2025-10-17 23:54:45 +02:00
<Menubar.CheckboxItem bind:checked={$directionMarkers}>
2025-06-21 21:07:36 +02:00
<Milestone size="16" class="mr-1" />{i18n._(
'menu.direction_markers'
)}<Shortcut key="F4" />
</Menubar.CheckboxItem>
<Menubar.Separator />
2025-06-21 21:07:36 +02:00
<Menubar.Item inset onclick={map.toggle3D}>
<Box size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.toggle_3d')}
<Shortcut key="{i18n._('menu.ctrl')}+{i18n._('menu.drag')}" />
</Menubar.Item>
</Menubar.Content>
</Menubar.Menu>
<Menubar.Menu>
2025-06-21 21:07:36 +02:00
<Menubar.Trigger aria-label={i18n._('menu.settings')}>
<Settings size="18" class="md:hidden" />
<span class="hidden md:block">
2025-06-21 21:07:36 +02:00
{i18n._('menu.settings')}
</span>
</Menubar.Trigger>
<Menubar.Content class="border-none">
<Menubar.Sub>
<Menubar.SubTrigger>
2025-06-21 21:07:36 +02:00
<Ruler size="16" class="mr-1" />{i18n._('menu.distance_units')}
</Menubar.SubTrigger>
<Menubar.SubContent>
2025-10-17 23:54:45 +02:00
<Menubar.RadioGroup bind:value={$distanceUnits}>
<Menubar.RadioItem value="metric"
2025-06-21 21:07:36 +02:00
>{i18n._('menu.metric')}</Menubar.RadioItem
>
<Menubar.RadioItem value="imperial"
2025-06-21 21:07:36 +02:00
>{i18n._('menu.imperial')}</Menubar.RadioItem
>
<Menubar.RadioItem value="nautical"
2025-06-21 21:07:36 +02:00
>{i18n._('menu.nautical')}</Menubar.RadioItem
>
</Menubar.RadioGroup>
</Menubar.SubContent>
</Menubar.Sub>
<Menubar.Sub>
<Menubar.SubTrigger>
2025-06-21 21:07:36 +02:00
<Zap size="16" class="mr-1" />{i18n._('menu.velocity_units')}
</Menubar.SubTrigger>
<Menubar.SubContent>
2025-10-17 23:54:45 +02:00
<Menubar.RadioGroup bind:value={$velocityUnits}>
<Menubar.RadioItem value="speed"
2025-06-21 21:07:36 +02:00
>{i18n._('quantities.speed')}</Menubar.RadioItem
>
<Menubar.RadioItem value="pace"
2025-06-21 21:07:36 +02:00
>{i18n._('quantities.pace')}</Menubar.RadioItem
>
</Menubar.RadioGroup>
</Menubar.SubContent>
</Menubar.Sub>
<Menubar.Sub>
<Menubar.SubTrigger>
2025-06-21 21:07:36 +02:00
<Thermometer size="16" class="mr-1" />{i18n._('menu.temperature_units')}
</Menubar.SubTrigger>
<Menubar.SubContent>
2025-10-17 23:54:45 +02:00
<Menubar.RadioGroup bind:value={$temperatureUnits}>
<Menubar.RadioItem value="celsius"
2025-06-21 21:07:36 +02:00
>{i18n._('menu.celsius')}</Menubar.RadioItem
>
<Menubar.RadioItem value="fahrenheit"
2025-06-21 21:07:36 +02:00
>{i18n._('menu.fahrenheit')}</Menubar.RadioItem
>
</Menubar.RadioGroup>
</Menubar.SubContent>
</Menubar.Sub>
<Menubar.Separator />
<Menubar.Sub>
<Menubar.SubTrigger>
<Languages size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.language')}
</Menubar.SubTrigger>
<Menubar.SubContent>
2025-06-21 21:07:36 +02:00
<Menubar.RadioGroup value={i18n.lang}>
{#each Object.entries(languages) as [lang, label]}
<a href={getURLForLanguage(lang, '/app')}>
<Menubar.RadioItem value={lang}>{label}</Menubar.RadioItem>
</a>
{/each}
</Menubar.RadioGroup>
</Menubar.SubContent>
</Menubar.Sub>
<Menubar.Sub>
<Menubar.SubTrigger>
2025-06-08 16:32:41 +02:00
{#if mode.current === 'light' || !mode.current}
<Sun size="16" class="mr-1" />
{:else}
<Moon size="16" class="mr-1" />
{/if}
2025-06-21 21:07:36 +02:00
{i18n._('menu.mode')}
</Menubar.SubTrigger>
<Menubar.SubContent>
<Menubar.RadioGroup
2025-06-08 16:32:41 +02:00
value={mode.current ?? 'light'}
onValueChange={(value) => {
2025-06-21 21:07:36 +02:00
setMode(value as 'light' | 'dark');
}}
>
<Menubar.RadioItem value="light"
2025-06-21 21:07:36 +02:00
>{i18n._('menu.light')}</Menubar.RadioItem
>
2025-06-21 21:07:36 +02:00
<Menubar.RadioItem value="dark"
>{i18n._('menu.dark')}</Menubar.RadioItem
>
</Menubar.RadioGroup>
</Menubar.SubContent>
</Menubar.Sub>
<Menubar.Separator />
<Menubar.Sub>
<Menubar.SubTrigger>
<PersonStanding size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.street_view_source')}
</Menubar.SubTrigger>
<Menubar.SubContent>
2025-10-17 23:54:45 +02:00
<Menubar.RadioGroup bind:value={$streetViewSource}>
<Menubar.RadioItem value="mapillary"
2025-06-21 21:07:36 +02:00
>{i18n._('menu.mapillary')}</Menubar.RadioItem
>
<Menubar.RadioItem value="google"
2025-06-21 21:07:36 +02:00
>{i18n._('menu.google')}</Menubar.RadioItem
>
</Menubar.RadioGroup>
</Menubar.SubContent>
</Menubar.Sub>
2025-06-21 21:07:36 +02:00
<Menubar.Item onclick={() => (layerSettingsOpen = true)}>
<Layers size="16" class="mr-1" />
2025-06-21 21:07:36 +02:00
{i18n._('menu.layers')}
</Menubar.Item>
</Menubar.Content>
</Menubar.Menu>
</Menubar.Root>
<div class="h-fit flex flex-row items-center ml-1 gap-1">
<Button
variant="ghost"
href="./help"
target="_blank"
class="cursor-default h-fit rounded-sm px-3 py-0.5"
2025-06-21 21:07:36 +02:00
aria-label={i18n._('menu.help')}
>
<BookOpenText size="18" class="md:hidden" />
<span class="hidden md:block">
2025-06-21 21:07:36 +02:00
{i18n._('menu.help')}
</span>
</Button>
<Button
variant="ghost"
href="https://ko-fi.com/gpxstudio"
target="_blank"
class="cursor-default h-fit rounded-sm font-bold text-support hover:text-support px-3 py-0.5"
2025-06-21 21:07:36 +02:00
aria-label={i18n._('menu.donate')}
>
<HeartHandshake size="18" class="md:hidden" />
<span class="hidden md:flex flex-row items-center">
2025-06-21 21:07:36 +02:00
{i18n._('menu.donate')}
<Heart size="16" class="ml-1" fill="rgb(var(--support))" />
</span>
</Button>
</div>
</div>
2024-04-08 17:12:39 +02:00
</div>
2024-04-09 18:46:01 +02:00
2024-06-28 15:43:57 +02:00
<Export />
2025-10-17 23:54:45 +02:00
<LayerControlSettings bind:open={layerSettingsOpen} />
2024-05-23 11:21:57 +02:00
2024-04-18 15:30:19 +02:00
<svelte:window
on:keydown={(e) => {
let targetInput =
2025-10-18 16:10:08 +02:00
e &&
e.target &&
(e.target.tagName === 'INPUT' ||
e.target.tagName === 'TEXTAREA' ||
e.target.tagName === 'SELECT' ||
e.target.role === 'combobox' ||
e.target.role === 'radio' ||
e.target.role === 'menu' ||
e.target.role === 'menuitem' ||
e.target.role === 'menuitemradio' ||
e.target.role === 'menuitemcheckbox');
if (e.key === '+' && (e.metaKey || e.ctrlKey)) {
createFile();
e.preventDefault();
} else if (e.key === 'o' && (e.metaKey || e.ctrlKey)) {
triggerFileInput();
e.preventDefault();
} else if (e.key === 'd' && (e.metaKey || e.ctrlKey)) {
2025-10-05 19:34:05 +02:00
fileActions.duplicateSelection();
e.preventDefault();
} else if (e.key === 'c' && (e.metaKey || e.ctrlKey)) {
if (!targetInput) {
2025-10-05 19:34:05 +02:00
selection.copySelection();
e.preventDefault();
}
} else if (e.key === 'x' && (e.metaKey || e.ctrlKey)) {
if (!targetInput) {
2025-10-05 19:34:05 +02:00
selection.cutSelection();
e.preventDefault();
}
} else if (e.key === 'v' && (e.metaKey || e.ctrlKey)) {
if (!targetInput) {
pasteSelection();
e.preventDefault();
}
} else if ((e.key === 's' || e.key == 'S') && (e.metaKey || e.ctrlKey)) {
if (e.shiftKey) {
2025-10-05 19:34:05 +02:00
if (fileStateCollection.size > 0) {
2025-06-21 21:07:36 +02:00
exportState.current = ExportState.ALL;
}
2025-10-17 23:54:45 +02:00
} else if ($selection.size > 0) {
2025-06-21 21:07:36 +02:00
exportState.current = ExportState.SELECTION;
}
e.preventDefault();
} else if ((e.key === 'z' || e.key == 'Z') && (e.metaKey || e.ctrlKey)) {
if (e.shiftKey) {
2025-10-05 19:34:05 +02:00
fileActionManager.redo();
} else {
2025-10-05 19:34:05 +02:00
fileActionManager.undo();
}
e.preventDefault();
} else if ((e.key === 'Backspace' || e.key === 'Delete') && (e.metaKey || e.ctrlKey)) {
if (!targetInput) {
if (e.shiftKey) {
2025-10-05 19:34:05 +02:00
fileActions.deleteAllFiles();
} else {
2025-10-05 19:34:05 +02:00
fileActions.deleteSelection();
}
e.preventDefault();
}
} else if (e.key === 'a' && (e.metaKey || e.ctrlKey)) {
if (!targetInput) {
2025-10-05 19:34:05 +02:00
selection.selectAll();
e.preventDefault();
}
} else if (e.key === 'i' && (e.metaKey || e.ctrlKey)) {
if (
2025-10-17 23:54:45 +02:00
$selection.size === 1 &&
$selection
.getSelected()
.every((item) => item instanceof ListFileItem || item instanceof ListTrackItem)
) {
2025-06-21 21:07:36 +02:00
editMetadata.current = true;
}
e.preventDefault();
} else if (e.key === 'p' && (e.metaKey || e.ctrlKey)) {
2025-10-17 23:54:45 +02:00
$elevationProfile = !$elevationProfile;
e.preventDefault();
} else if (e.key === 'l' && (e.metaKey || e.ctrlKey)) {
2025-10-17 23:54:45 +02:00
$treeFileView = !$treeFileView;
e.preventDefault();
} else if (e.key === 'h' && (e.metaKey || e.ctrlKey)) {
2025-10-05 19:34:05 +02:00
// if ($allHidden) {
// fileActions.setHiddenToSelection(false);
// } else {
// fileActions.setHiddenToSelection(true);
// }
e.preventDefault();
} else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
2025-10-05 19:34:05 +02:00
// if ($selection.size > 0) {
// centerMapOnSelection();
// }
} else if (e.key === 'F1') {
switchBasemaps();
e.preventDefault();
} else if (e.key === 'F2') {
toggleOverlays();
e.preventDefault();
} else if (e.key === 'F3') {
2025-10-17 23:54:45 +02:00
$distanceMarkers = !$distanceMarkers;
e.preventDefault();
} else if (e.key === 'F4') {
2025-10-17 23:54:45 +02:00
$directionMarkers = !$directionMarkers;
e.preventDefault();
} else if (e.key === 'F5') {
2025-10-17 23:54:45 +02:00
$routing = !$routing;
e.preventDefault();
} else if (
e.key === 'ArrowRight' ||
e.key === 'ArrowDown' ||
e.key === 'ArrowLeft' ||
e.key === 'ArrowUp'
) {
if (!targetInput) {
2025-10-05 19:34:05 +02:00
// updateSelectionFromKey(e.key === 'ArrowRight' || e.key === 'ArrowDown', e.shiftKey);
e.preventDefault();
}
}
}}
on:dragover={(e) => e.preventDefault()}
on:drop={(e) => {
e.preventDefault();
if (e.dataTransfer.files.length > 0) {
loadFiles(e.dataTransfer.files);
}
}}
2024-04-18 15:30:19 +02:00
/>
2024-04-09 18:46:01 +02:00
<style lang="postcss">
2025-06-21 21:07:36 +02:00
@reference "../../app.css";
div :global(button) {
@apply hover:bg-accent;
@apply px-3;
@apply py-0.5;
}
2024-04-09 18:46:01 +02:00
</style>