From 3a52b85dfb00a55421f18e9d4c8a658dc5d43efc Mon Sep 17 00:00:00 2001 From: Jonas Kappa Date: Tue, 23 Dec 2025 12:25:24 +0100 Subject: [PATCH] Added listing of files and directories --- .npmrc | 1 + docker-dev.sh | 4 + package.json | 3 +- src/lib/Modal.svelte | 19 ++- src/lib/RessourceManager.svelte | 227 +++++++++++++++++++++++++++++--- src/lib/Types.ts | 23 ++++ src/lib/util.ts | 5 + src/routes/+page.svelte | 7 +- 8 files changed, 266 insertions(+), 23 deletions(-) create mode 100644 docker-dev.sh create mode 100644 src/lib/util.ts diff --git a/.npmrc b/.npmrc index b6f27f1..741e5fd 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ engine-strict=true +script-shell=C:\Program Files\Git\git-bash.exe diff --git a/docker-dev.sh b/docker-dev.sh new file mode 100644 index 0000000..b7ca457 --- /dev/null +++ b/docker-dev.sh @@ -0,0 +1,4 @@ + +docker compose down & +docker build -t jeopardy . +docker compose up -d diff --git a/package.json b/package.json index ac1613e..c1d0f66 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "format": "prettier --write .", "lint": "prettier --check . && eslint .", - "docker-build": "docker build -t jeopardy ." + "docker-build": "docker build -t jeopardy .", + "docker-dev": "./docker-dev.sh" }, "devDependencies": { "@eslint/compat": "^1.2.5", diff --git a/src/lib/Modal.svelte b/src/lib/Modal.svelte index 6a49997..fd64c97 100644 --- a/src/lib/Modal.svelte +++ b/src/lib/Modal.svelte @@ -9,7 +9,10 @@ cancelFn?: () => void; okFn: () => Promise; oncloseFn?: () => void; + okButtonText?: string; [key: string]: unknown; + width?: string; + height?: string; } let { @@ -19,7 +22,10 @@ cancelFn, okFn, oncloseFn, - actionButtons + actionButtons, + okButtonText = "Ok", + width, + height }: Props = $props(); let dialog: HTMLDialogElement | undefined = $state(); // HTMLDialogElement @@ -40,10 +46,15 @@ if (e.target === dialog) dialog.close(); }} class="rounded-md" + style:width + style:height > -
+
{@render header?.()} - {@render children?.()} +
+ {@render children?.()} +
+
{@render actionButtons?.()} @@ -62,7 +73,7 @@ dialog?.close(); } }} - class="btn min-w-[64px]">Ok{okButtonText}
diff --git a/src/lib/RessourceManager.svelte b/src/lib/RessourceManager.svelte index 4b01675..344fadb 100644 --- a/src/lib/RessourceManager.svelte +++ b/src/lib/RessourceManager.svelte @@ -2,15 +2,28 @@ import { env } from "$env/dynamic/public"; import axios from "axios"; import Modal from "./Modal.svelte"; + import { url } from "./util"; + import { isDir, isRessource, type Directory, type Ressource } from "./Types"; + import { onMount } from "svelte"; interface Props { show: boolean; + ok?: (res: Ressource) => void; } - let { show = $bindable(false) }: Props = $props(); + let { show = $bindable(false), ok = (res) => {} }: Props = $props(); let file: File | null = null; - let path: string = "/"; + let path: string = $state("/"); + + let fetchingRessources = $state(false); + let ressources: (Ressource | Directory)[] = $state([]); + let selectedRessource: Ressource | undefined = $state(); + + let error = $state(""); + + let showNewDir = $state(false); + let newDirName = $state(""); function handleFileChange(event: Event) { const target = event.target as HTMLInputElement | null; @@ -27,33 +40,182 @@ formData.append("path", path); formData.append("file", file); - axios.post( - `${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/upload`, - formData, - { - withCredentials: true, - headers: { - "Content-Type": "multipart/form-data" - }, - onUploadProgress: (event) => { - console.log(event); + axios + .post( + `${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}/upload`, + formData, + { + withCredentials: true, + headers: { + "Content-Type": "multipart/form-data" + }, + onUploadProgress: (event) => { + console.log(event); + } } - } - ); + ) + .then((response) => { + if (response.status === 200) { + fetchDirectory(); + } else { + alert("Failed with status: " + response.status); + } + }) + .catch((err) => { + console.error(err); + alert(err); + }); } + + async function fetchDirectory() { + fetchingRessources = true; + return axios + .post( + url("/directory"), + { path }, + { + withCredentials: true + } + ) + .then((response) => { + if (response.status === 200) { + ressources = response.data; + ressources.sort((a, b) => { + if (isDir(a) && !isDir(b)) return -1; + if (!isDir(a) && isDir(b)) return 1; + return a.name.localeCompare(b.name); + }); + if (path !== "/") { + ressources.unshift({ + isDir: true, + name: ".." + }); + } + } + }) + .catch((e) => { + console.log(e); + }) + .finally(() => { + fetchingRessources = false; + }); + } + + async function addDirectoryOk(): Promise { + error = ""; + if (newDirName.length <= 0) { + error = "Gib einen Namen für den Ordner ein"; + return false; + } + + return axios + .put(url("/directory"), { name: newDirName, path }, { withCredentials: true }) + .then((response) => { + if (response.status === 200) { + fetchDirectory(); + return true; + } else { + return false; + } + }) + .catch(() => { + error = "Etwas ist schief gelaufen"; + return false; + }); + } + + function addDirectoryCancel() { + error = ""; + newDirName = ""; + } + + function ressourceClicked(res: Ressource | Directory) { + if (isRessource(res)) { + selectedRessource = res; + } else if (isDir(res)) { + if (res.name === "..") { + let breadcumbs = path.split("/"); + breadcumbs.pop(); + path = breadcumbs.join("/"); + if (path.length <= 0) path = "/"; + } else { + if (!path.endsWith("/")) { + path += "/"; + } + path += res.name; + } + fetchDirectory(); + } + } + + function cancel() { + selectedRessource = undefined; + } + + $effect(() => { + if (show) { + fetchDirectory(); + } + }); { - console.log("ok"); + if (selectedRessource) ok({ ...selectedRessource }); return true; }} + cancelFn={cancel} + okButtonText={selectedRessource !== undefined ? selectedRessource.name : "Ok"} + width="90%" + height="90%" > {#snippet header()} -

Ressourcenmanager

+

Ressourcenmanager - {path}

{/snippet} -
Ressourcenmanager
+
+ {#if fetchingRessources} + Loading... + {:else} +
+ {#each ressources as ressource} + + +
ressourceClicked(ressource)} + class:bg-green-200={isRessource(ressource) && + ressource._id === selectedRessource?._id} + > +
+ {#if isDir(ressource)} + + {ressource.name} + {:else} + {#if ressource.mimetype.includes("image")} + + {:else if ressource.mimetype.includes("audio")} + + {:else} + + {/if} + {ressource.name} + {/if} +
+ + +
+ {/each} +
+ {/if} +
{#snippet actionButtons()}
+ {/snippet}
+ + + {#snippet header()} +

Neuer Ordner

+ {/snippet} +
+ +
+ +
+ {#if error.length > 0} +
{error}
+ {/if} +
+
+ + diff --git a/src/lib/Types.ts b/src/lib/Types.ts index b2149b0..c858822 100644 --- a/src/lib/Types.ts +++ b/src/lib/Types.ts @@ -1 +1,24 @@ export type VisitedQuestions = number[][]; + +export type Directory = { + name: string; + isDir: true; +}; + +export type Ressource = { + _id: string; + fullpath: string; + path: string; + user: string; + mimetype: string; + name: string; + filename: string; +}; + +export function isDir(dir: Directory | Ressource): dir is Directory { + return (dir as Directory).isDir === true; +} + +export function isRessource(ressource: Ressource | Directory): ressource is Ressource { + return (ressource as Directory).isDir === undefined; +} diff --git a/src/lib/util.ts b/src/lib/util.ts new file mode 100644 index 0000000..9aef4c5 --- /dev/null +++ b/src/lib/util.ts @@ -0,0 +1,5 @@ +import { env } from "$env/dynamic/public"; + +export function url(path: string) { + return `${env.PUBLIC_JEOPARDY_SERVER_PROTOCOL}://${env.PUBLIC_JEOPARDY_SERVER}${path.startsWith("/") ? "" : "/"}${path}`; +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index c2129e0..0915e07 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -77,7 +77,12 @@
- + { + console.log(res); + }} +>