Adds import from URL

This commit is contained in:
Christian Beutel
2025-02-01 20:36:39 +01:00
parent 0cf17cbd86
commit 3ab591177a
17 changed files with 208 additions and 41 deletions

View File

@@ -1,9 +1,7 @@
<script lang="ts">
import type { Snippet } from "svelte";
import type { MouseEventHandler } from "svelte/elements";
import { createBubbler } from "svelte/legacy";
const bubble = createBubbler();
interface Props {
type?: "button" | "submit" | "reset" | null | undefined;
icon?: string;
@@ -14,6 +12,7 @@
loading?: boolean;
disabled?: boolean;
tooltip?: string | undefined;
form?: string;
id?: string | undefined;
children?: Snippet;
onclick?: MouseEventHandler<HTMLButtonElement>;
@@ -30,6 +29,7 @@
disabled = false,
tooltip = undefined,
id = undefined,
form,
children,
onclick = undefined,
}: Props = $props();
@@ -44,6 +44,7 @@
disabled={disabled || loading}
class:tooltip={(tooltip?.length ?? 0) > 0}
data-title={tooltip}
{form}
{onclick}
{type}
{id}

View File

@@ -14,6 +14,7 @@
import { browser } from "$app/environment";
import { page } from "$app/state";
import { Tween } from "svelte/motion";
import UrlImportModal from "./settings/url_import_modal.svelte";
let navBarItems = [
{ text: "Home", value: "/" },
@@ -28,6 +29,11 @@
{ text: $_("logout"), value: "logout", icon: "right-from-bracket" },
];
const importDropdownItems = [
{ text: $_("from-file"), value: "file", icon: "file-import" },
{ text: $_("from-url"), value: "url", icon: "server" },
];
const indicatorPosition = new Tween(0, {
duration: 300,
easing: cubicOut,
@@ -45,6 +51,8 @@
let drawerOpen: boolean = $state(false);
let urlImportModal: UrlImportModal;
afterNavigate((e) => {
const routeId = e.to?.route.id;
const navBarLinks = document.getElementById("nav-bar-links");
@@ -92,6 +100,14 @@
}
}
function handleImportDropdownClick(item: { text: string; value: any }) {
if (item.value == "file") {
goto(`/settings/export`);
} else if (item.value == "url") {
urlImportModal.openModal();
}
}
let user = $derived(browser ? $currentUser : pb.authStore.model);
</script>
@@ -132,8 +148,12 @@
/>
</a>
<a href="/profile/{user.id}" style="width: calc(100% - 104px)">
<p class="text-sm overflow-hidden text-ellipsis">{user.username}</p>
<p class="text-sm text-gray-500 overflow-hidden text-ellipsis">
<p class="text-sm overflow-hidden text-ellipsis">
{user.username}
</p>
<p
class="text-sm text-gray-500 overflow-hidden text-ellipsis"
>
{user.email}
</p>
</a>
@@ -182,9 +202,26 @@
: 'moon'}"
onclick={() => toggleTheme()}
></button>
<a class="btn-primary btn-large" href="/trail/edit/new"
><i class="fa fa-plus mr-2"></i>{$_("new-trail")}</a
>
<div class="flex">
<a
class="btn-primary btn-large !rounded-r-none focus:ring-0"
href="/trail/edit/new"
><i class="fa fa-plus mr-2"></i>{$_("new-trail")}</a
>
<Dropdown
items={importDropdownItems}
onchange={(item) => handleImportDropdownClick(item)}
>
{#snippet children({ toggleMenu: openDropdown })}
<button
onclick={openDropdown}
class="bg-primary rounded-r-lg text-white min-h-12 hover:bg-primary-hover px-3"
aria-label="Open trail create dropdown"
><i class="fa fa-caret-down"></i></button
>
{/snippet}
</Dropdown>
</div>
{#if page.data.notifications}
<NotificationDropdown></NotificationDropdown>
{/if}
@@ -227,3 +264,5 @@
onclick={() => (drawerOpen = !drawerOpen)}
></button>
</nav>
<UrlImportModal bind:this={urlImportModal}></UrlImportModal>

View File

@@ -96,7 +96,7 @@
</p>
</div>
<div class="space-x-2">
<button aria-label="Dismiss all" onclick={dismissAllCompleted}
<button title={$_('clear-all')} aria-label="Dismiss all" onclick={dismissAllCompleted}
><i class="fa fa-ban"></i></button
>
<button
@@ -134,7 +134,7 @@
></i>
{/if}
</div>
<p class="text-xs basis-full min-w-0 break-all">
<p class="text-xs basis-full min-w-0 break-all mr-2">
{u.file.name}
</p>
{#if u.status == "error" || u.status == "cancelled"}

View File

@@ -0,0 +1,97 @@
<script lang="ts">
import Modal from "$lib/components/base/modal.svelte";
import { trails_upload } from "$lib/stores/trail_store";
import {
processUploadQueue,
uploadStore,
type Upload,
} from "$lib/stores/upload_store.svelte";
import { validator } from "@felte/validator-zod";
import { createForm } from "felte";
import { _ } from "svelte-i18n";
import { z } from "zod";
import TextField from "../base/text_field.svelte";
import Button from "../base/button.svelte";
let modal: Modal;
let loading: boolean = false;
export function openModal() {
setFields("url", "")
setErrors("url", []);
loading = false;
modal.openModal();
}
const { form, errors, setFields, setErrors } = createForm<{
url: string;
}>({
initialValues: { url: "" },
extend: validator({
schema: z.object({
url: z.string().min(1, "required").url("not-a-valid-url"),
}),
}),
onSubmit: async (form) => {
try {
loading = true;
const r = await fetch("/api/v1/trail/download", {
method: "POST",
body: JSON.stringify({ url: form.url }),
});
if (!r.ok) {
throw new Error(`Failed to fetch file: ${r.statusText}`);
}
const blob = await r.blob();
const fileName =
form.url.split("/").pop() ||
"url-import-file-" + new Date().getTime();
const file = new File([blob], fileName, { type: blob.type });
const u: Upload = {
file: file,
progress: 0,
status: "enqueued",
function: trails_upload,
};
uploadStore.enqueuedUploads.push(u);
await processUploadQueue();
loading = false;
modal.closeModal();
} catch (error) {
console.error("Error:", error);
}
},
});
</script>
<Modal
id="url-import-modal"
size="max-w-xl"
title={$_("from-url")}
bind:this={modal}
>
{#snippet content()}
<form id="url-import-form" use:form>
<TextField
name="url"
label="URL"
placeholder="https://..."
error={$errors.url}
></TextField>
</form>
{/snippet}
{#snippet footer()}
<div class="flex items-center gap-4">
<button class="btn-secondary" onclick={() => modal.closeModal()}
>{$_("cancel")}</button
>
<Button primary type="submit" form="url-import-form" {loading}>{$_("import")}</Button>
</div>
{/snippet}</Modal
>

View File

@@ -157,7 +157,7 @@
</script>
<div
class="trail-info-panel mx-auto {mode == 'list'
class="trail-info-panel overflow-x-hidden mx-auto {mode == 'list'
? ''
: 'border border-input-border rounded-3xl'} h-full"
class:overflow-y-scroll={mode !== "overview"}