diff --git a/src/components/App.svelte b/src/components/App.svelte index e1eaf9c..846689c 100644 --- a/src/components/App.svelte +++ b/src/components/App.svelte @@ -5,7 +5,7 @@ import Header from './Header.svelte'; import { demoFile, demoRows } from '../lib/parse/float-control'; import Picker from './Picker.svelte'; - import type { EventHandler } from 'svelte/elements'; + import type { DragEventHandler, EventHandler } from 'svelte/elements'; import { DataSource, State, Units, type RowWithIndex } from '../lib/parse/types'; import Modal from './Modal.svelte'; import { riderSvg } from '../lib/map-helpers'; @@ -13,7 +13,7 @@ import SettingsModal from './SettingsModal.svelte'; import Button from './Button.svelte'; import { ChartColours } from '../lib/chart-helpers'; - import { parse } from '../lib/parse'; + import { parse, supportedMimeTypes } from '../lib/parse'; import { speedMapper } from '../lib/misc'; /** source of data*/ @@ -205,6 +205,35 @@ } }); + /** + * Allow dragging and dropping files to open them. + */ + + let draggingFile = $state(false); + const filterSupported = (item: { type: string }) => supportedMimeTypes.includes(item.type); + const ondragenter: DragEventHandler = (e) => { + if (!e.dataTransfer) return; + draggingFile = Array.from(e.dataTransfer.items).some(filterSupported); + }; + const ondragleave: DragEventHandler = (_) => (draggingFile = false); + const ondragover: DragEventHandler = (e) => { + e.preventDefault(); + if (draggingFile && e.dataTransfer) { + e.dataTransfer.dropEffect = 'copy'; + } + }; + const ondrop: DragEventHandler = (e) => { + e.preventDefault(); + if (!e.dataTransfer) return; + + const droppedFile = Array.from(e.dataTransfer.files).find(filterSupported); + if (droppedFile) { + file = droppedFile; + } + + draggingFile = false; + }; + const chartClass = 'bg-slate-950 flex overflow-hidden h-[--grid-width] wide:h-[unset]'; @@ -231,7 +260,19 @@ grid-cols-[repeat(auto-fit,minmax(var(--grid-width),1fr))] wide:h-[calc(100vh-var(--header-height))] wide:grid-cols-[repeat(3,1fr)]" + {ondragenter} + {ondragover} + {ondrop} > + {#if draggingFile} + +
+
{@html riderSvg}
+

Drop your file to open it!

+
{@html riderSvg}
+
+
+ {/if}
import type { Snippet } from 'svelte'; + import type { HTMLAttributes } from 'svelte/elements'; - export interface Props { + export interface Props extends HTMLAttributes { open?: boolean; title: string; closable?: boolean; @@ -21,18 +22,19 @@ closable = true, title, children, + ...rest }: Props = $props(); {#if open}
-
+

{title}

-
+
{@render children()}
{#if closable} diff --git a/src/components/Picker.svelte b/src/components/Picker.svelte index 8820503..6e16742 100644 --- a/src/components/Picker.svelte +++ b/src/components/Picker.svelte @@ -9,7 +9,7 @@ import Modal from './Modal.svelte'; import { demoFile } from '../lib/parse/float-control'; import Link from './Link.svelte'; - import { supportedMimeTypes } from '../lib/parse'; + import { supportedMimeTypeString } from '../lib/parse'; let { file = $bindable() }: Props = $props(); @@ -34,7 +34,7 @@ file:text-sm file:font-bold file:bg-slate-700 active:file:bg-slate-800" type="file" {onchange} - accept={supportedMimeTypes} + accept={supportedMimeTypeString} />
diff --git a/src/lib/parse/index.ts b/src/lib/parse/index.ts index ca5e5c8..9574029 100644 --- a/src/lib/parse/index.ts +++ b/src/lib/parse/index.ts @@ -23,7 +23,8 @@ export enum SupportedMimeTypes { Json = 'application/json', } -export const supportedMimeTypes = Object.values(SupportedMimeTypes).join(','); +export const supportedMimeTypes = Object.values(SupportedMimeTypes); +export const supportedMimeTypeString = supportedMimeTypes.join(','); export async function parse(file: File): Promise { const lowerName = file.name.toLowerCase();