basic file operations

This commit is contained in:
vcoppe
2024-04-18 15:30:19 +02:00
parent d800ff5540
commit 78258454be
7 changed files with 218 additions and 39 deletions

View File

@@ -1,16 +1,30 @@
<script lang="ts">
import { files } from '$lib/stores';
import { files, selectedFiles, addSelectFile, selectFile } from '$lib/stores';
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
import { Button } from '$lib/components/ui/button';
import { Label } from '$lib/components/ui/label';
</script>
<ScrollArea class="w-full h-full">
<div class="flex flex-col">
{#each $files as file}
<Button variant="outline" class="w-full">
{file.metadata.name}
</Button>
{/each}
</div>
</ScrollArea>
<div class="flex flex-col h-full w-full">
<Label class="w-full">Files</Label>
<ScrollArea class="w-full h-full">
<div class="flex flex-col">
{#each $files as file}
<Button
variant={$selectedFiles.has(file) ? 'outline' : 'secondary'}
class="w-full {$selectedFiles.has(file) ? 'hover:bg-background' : 'hover:bg-secondary'}"
on:click={(e) => {
if (e.shiftKey) {
addSelectFile(file);
} else {
selectFile(file);
}
}}
>
{file.metadata.name}
</Button>
{/each}
</div>
</ScrollArea>
</div>

View File

@@ -32,17 +32,42 @@
colorCount[color]++;
return color;
}
function decrementColor(color: string) {
colorCount[color]--;
}
</script>
<script lang="ts">
import { GPXFile } from 'gpx';
import { map } from '$lib/stores';
import { map, selectedFiles, addSelectFile, selectFile } from '$lib/stores';
import { onDestroy } from 'svelte';
export let file: GPXFile;
let layerId = getLayerId();
let layerColor = getColor();
function selectOnClick(e: any) {
if (e.originalEvent.shiftKey) {
addSelectFile(file);
} else {
selectFile(file);
}
}
function toPointerCursor() {
if ($map) {
$map.getCanvas().style.cursor = 'pointer';
}
}
function toDefaultCursor() {
if ($map) {
$map.getCanvas().style.cursor = '';
}
}
function addGPXLayer() {
if ($map) {
if (!$map.getSource(layerId)) {
@@ -80,6 +105,10 @@
'line-opacity': ['get', 'opacity']
}
});
$map.on('click', layerId, selectOnClick);
$map.on('mouseenter', layerId, toPointerCursor);
$map.on('mouseleave', layerId, toDefaultCursor);
}
}
}
@@ -91,4 +120,22 @@
addGPXLayer();
});
}
$: if ($selectedFiles.has(file)) {
if ($map) {
$map.moveLayer(layerId);
}
}
onDestroy(() => {
if ($map) {
$map.off('click', layerId, selectOnClick);
$map.off('mouseenter', layerId, toPointerCursor);
$map.off('mouseleave', layerId, toDefaultCursor);
$map.removeLayer(layerId);
$map.removeSource(layerId);
}
decrementColor(layerColor);
});
</script>

View File

@@ -15,7 +15,16 @@
import Fa from 'svelte-fa';
import { faGoogleDrive } from '@fortawesome/free-brands-svg-icons';
import { triggerFileInput } from '$lib/stores';
import {
files,
selectedFiles,
duplicateSelectedFiles,
exportAllFiles,
exportSelectedFiles,
removeAllFiles,
removeSelectedFiles,
triggerFileInput
} from '$lib/stores';
let distanceUnits = 'metric';
let velocityUnits = 'speed';
@@ -45,14 +54,14 @@
Load from Google Drive...</Menubar.Item
>
<Menubar.Separator />
<Menubar.Item>
<Menubar.Item on:click={duplicateSelectedFiles} disabled={$selectedFiles.size == 0}>
<Copy size="16" class="mr-1" /> Duplicate <Menubar.Shortcut>⌘D</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Separator />
<Menubar.Item>
<Menubar.Item on:click={exportSelectedFiles} disabled={$selectedFiles.size == 0}>
<Download size="16" class="mr-1" /> Export... <Menubar.Shortcut>⌘S</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Item>
<Menubar.Item on:click={exportAllFiles} disabled={$files.length == 0}>
<Download size="16" class="mr-1" /> Export all... <Menubar.Shortcut
>⇧⌘S</Menubar.Shortcut
>
@@ -69,12 +78,16 @@
<Redo2 size="16" class="mr-1" /> Redo <Menubar.Shortcut>⇧⌘Z</Menubar.Shortcut>
</Menubar.Item>
<Menubar.Separator />
<Menubar.Item
><Trash2 size="16" class="mr-1" /> Delete <Menubar.Shortcut>⌘⌫</Menubar.Shortcut
<Menubar.Item on:click={removeSelectedFiles} disabled={$selectedFiles.size == 0}>
<Trash2 size="16" class="mr-1" /> Delete <Menubar.Shortcut>⌘⌫</Menubar.Shortcut
></Menubar.Item
>
<Menubar.Item class="text-destructive data-[highlighted]:text-destructive"
><Trash2 size="16" class="mr-1" /> Delete all<Menubar.Shortcut>⇧⌘⌫</Menubar.Shortcut
<Menubar.Item
class="text-destructive data-[highlighted]:text-destructive"
on:click={removeAllFiles}
disabled={$files.length == 0}
>
<Trash2 size="16" class="mr-1" /> Delete all<Menubar.Shortcut>⇧⌘⌫</Menubar.Shortcut
></Menubar.Item
>
</Menubar.Content>
@@ -134,6 +147,34 @@
</div>
</div>
<svelte:window
on:keydown={(e) => {
e.stopImmediatePropagation();
if (e.key === 'o' && (e.metaKey || e.ctrlKey)) {
triggerFileInput();
e.preventDefault();
} else if (e.key === 'd' && (e.metaKey || e.ctrlKey)) {
duplicateSelectedFiles();
e.preventDefault();
} else if (e.key === 's' && (e.metaKey || e.ctrlKey)) {
if (e.shiftKey) {
exportAllFiles();
} else {
exportSelectedFiles();
}
e.preventDefault();
} else if ((e.key === 'Backspace' || e.key === 'Delete') && (e.metaKey || e.ctrlKey)) {
if (e.shiftKey) {
console.log('removeAllFiles');
removeAllFiles();
} else {
removeSelectedFiles();
}
e.preventDefault();
}
}}
/>
<style lang="postcss">
div :global(button) {
@apply hover:bg-accent;

View File

@@ -1,11 +1,18 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button';
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { selectedFiles } from '$lib/stores';
</script>
<Tooltip.Root openDelay="300">
<Tooltip.Root openDelay={300}>
<Tooltip.Trigger asChild let:builder>
<Button builders={[builder]} variant="ghost" class="h-fit px-1 py-1.5">
<Button
builders={[builder]}
variant="ghost"
class="h-fit px-1 py-1.5"
disabled={$selectedFiles.size == 0}
>
<slot name="icon" />
</Button>
</Tooltip.Trigger>

View File

@@ -9,11 +9,13 @@
$: if ($map && container) {
$map.on('load', () => {
if (position.includes('right')) container.classList.add('float-right');
else container.classList.add('float-left');
container.classList.remove('hidden');
let control = new CustomControl(container);
$map.addControl(control, position);
if ($map && container) {
if (position.includes('right')) container.classList.add('float-right');
else container.classList.add('float-left');
container.classList.remove('hidden');
let control = new CustomControl(container);
$map.addControl(control, position);
}
});
}
</script>

View File

@@ -1,10 +1,11 @@
import { writable } from 'svelte/store';
import { writable, get } from 'svelte/store';
import mapboxgl from 'mapbox-gl';
import { GPXFile, parseGPX } from 'gpx';
import { GPXFile, buildGPX, parseGPX } from 'gpx';
export const map = writable<mapboxgl.Map | null>(null);
export const files = writable<GPXFile[]>([]);
export const selectedFiles = writable<Set<GPXFile>>(new Set());
export function triggerFileInput() {
const input = document.createElement('input');
@@ -36,7 +37,85 @@ export function loadFile(file: File) {
gpx.metadata['name'] = file.name.split('.').slice(0, -1).join('.');
}
files.update($files => [...$files, gpx]);
selectFile(gpx);
}
};
reader.readAsText(file);
}
export function duplicateSelectedFiles() {
let selected: GPXFile[] = [];
get(selectedFiles).forEach(file => selected.push(file));
selected.forEach(file => duplicateFile(file));
}
export function duplicateFile(file: GPXFile) {
let clone = file.clone();
files.update($files => [...$files, clone]);
selectFile(clone);
}
export function removeSelectedFiles() {
let index = 0;
while (index < get(files).length) {
if (get(selectedFiles).has(get(files)[index])) {
files.update($files => {
$files.splice(index, 1);
return $files;
});
} else {
index++;
}
}
selectedFiles.update($selectedFiles => {
$selectedFiles.clear();
return $selectedFiles;
});
}
export function removeAllFiles() {
files.update($files => {
$files.splice(0, $files.length);
return $files;
});
selectedFiles.update($selectedFiles => {
$selectedFiles.clear();
return $selectedFiles;
});
}
export function exportSelectedFiles() {
get(selectedFiles).forEach(file => exportFile(file));
}
export async function exportAllFiles() {
for (let i = 0; i < get(files).length; i++) {
exportFile(get(files)[i]);
await new Promise(resolve => setTimeout(resolve, 200));
}
}
export function exportFile(file: GPXFile) {
let blob = new Blob([buildGPX(file)], { type: 'application/gpx+xml' });
let url = URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = url;
a.download = file.metadata.name + '.gpx';
a.click();
URL.revokeObjectURL(url);
}
export function selectFile(file: GPXFile) {
selectedFiles.update($selectedFiles => {
$selectedFiles.clear();
$selectedFiles.add(file);
return $selectedFiles;
});
}
export function addSelectFile(file: GPXFile) {
selectedFiles.update($selectedFiles => {
$selectedFiles.add(file);
return $selectedFiles;
});
}

View File

@@ -5,8 +5,6 @@
import Menu from '$lib/components/Menu.svelte';
import Toolbar from '$lib/components/Toolbar.svelte';
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
import { triggerFileInput } from '$lib/stores';
</script>
<div class="flex flex-col w-screen h-screen">
@@ -21,12 +19,3 @@
<FileList />
</div>
</div>
<svelte:window
on:keydown={(e) => {
if (e.key === 'o' && (e.metaKey || e.ctrlKey)) {
triggerFileInput();
e.preventDefault();
}
}}
/>