33 Commits

Author SHA1 Message Date
0557d42207 temp Commit Mina 2025-05-26 14:11:01 +02:00
59fc791b85 sdf 2025-05-23 11:22:20 +02:00
c437926002 verbindungsprobleme test mina 2025-05-23 11:21:55 +02:00
99a27fc079 update frontend 2025-05-22 17:25:33 +02:00
bebd226c80 fixed fluechtigkeitsfehler 2025-05-22 14:16:44 +02:00
4adea19981 temp 22.05 2025-05-22 14:13:53 +02:00
6b7188350f temporär zum Testen 2025-05-22 10:22:45 +02:00
20c74d49d9 Merge branch 'development' into f03_user-management 2025-05-21 10:21:51 +02:00
131318da14 input field fuer Zugangscode beim Anlegen 2025-05-21 10:13:50 +02:00
8bc7fad146 fix two style element 2025-05-16 10:40:50 +02:00
titver968
255f14faae Merge branch 'development' of ssh://gitea.innovation-hub-niedersachsen.de:4422/innohub/tatort into development 2025-05-16 08:35:16 +02:00
0fc496cd5c Merge branch 'CameraSteuerung' into development 2025-05-15 12:02:53 +02:00
23123c6291 warnings korrigiert 2025-05-15 12:01:46 +02:00
f56eeeee2c update kleine Anpassungen 2025-05-15 11:58:10 +02:00
9058ea38e2 Merge branch 'CameraSteuerung' into development 2025-05-14 17:14:38 +02:00
9519b0fdd1 update des Tages CameraSteuerung 2025-05-14 17:08:30 +02:00
titver968
2278b18f02 Fehlerkorrekturen 2025-05-14 13:59:06 +02:00
titver968
18038903cb warning korrigiert 2025-05-14 13:50:31 +02:00
75f88e3e49 Merge pull request 'development' (#1) from development into main
Reviewed-on: #1
2025-05-14 13:42:23 +02:00
a7c8bf91d9 Merge branches 'f01_verify_file_upload' and 'f02_rename_file' into dev_merge 2025-05-14 11:02:51 +02:00
06427253e4 comment on renaming operation of tatort 2025-05-14 10:59:11 +02:00
9b2ebd3d31 refactor success message 2025-05-14 10:51:14 +02:00
8d71633f50 Update Buttons in model viewer 2025-05-12 16:22:27 +02:00
657fd422bc refactor event handlers 2025-05-12 13:20:53 +02:00
2809df9fd4 Add modal for renaming operation 2025-05-12 13:05:29 +02:00
33edc125c3 implement delete of Vorgaenge 2025-05-09 12:36:57 +02:00
617033fccb check is name already exists during renaming 2025-05-09 09:42:17 +02:00
9575ef84d4 fixed issues with dashes in name and id 2025-05-09 09:08:58 +02:00
f3850649c7 zwischenspeichern 2025-05-08 15:35:24 +02:00
49d2ee04f1 remove vertical align 2025-05-08 14:03:21 +02:00
aa3dcc1a75 add delete function 2025-05-08 14:02:17 +02:00
985d4a424f Tages-Update 2025-05-07 18:09:47 +02:00
fac5cc80a9 check for valid .GLD file 2025-04-28 10:22:33 +02:00
25 changed files with 875 additions and 261 deletions

View File

@@ -8,7 +8,7 @@
},
"jwt": {
"secret": "@S2!q@@wXz$dCQ8JoVsHLpzaJ6JCfB",
"expiresIn": 3600
"expiresIn": 36000
},
"auth": {
"admin": { "password": "A-InnoHUB_2025!", "admin": true },

40
package-lock.json generated
View File

@@ -1454,9 +1454,9 @@
}
},
"node_modules/@sveltejs/kit": {
"version": "2.20.7",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.7.tgz",
"integrity": "sha512-dVbLMubpJJSLI4OYB+yWYNHGAhgc2bVevWuBjDj8jFUXIJOAnLwYP3vsmtcgoxNGUXoq0rHS5f7MFCsryb6nzg==",
"version": "2.20.8",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.8.tgz",
"integrity": "sha512-ep9qTxL7WALhfm0kFecL3VHeuNew8IccbYGqv5TqL/KSqWRKzEgDG8blNlIu1CkLTTua/kHjI+f5T8eCmWIxKw==",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0",
@@ -3242,10 +3242,9 @@
}
},
"node_modules/fdir": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"license": "MIT",
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -5959,6 +5958,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinypool": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
@@ -6227,14 +6241,16 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "6.2.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
"integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
"license": "MIT",
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
"picomatch": "^4.0.2",
"postcss": "^8.5.3",
"rollup": "^4.30.1"
"rollup": "^4.34.9",
"tinyglobby": "^0.2.13"
},
"bin": {
"vite": "bin/vite.js"

4
src/error.html Normal file
View File

@@ -0,0 +1,4 @@
<h1>Du wurdest automatisch ausgeloggt</h1>
<p>Lösche deine Cookies aus dem Browser und logge dich neu ein</p>
<p>Code %sveltekit.status%</p>
<p>%sveltekit.error.message%</p>

View File

@@ -12,5 +12,5 @@ export async function handle({ event, resolve }) {
await event.cookies.delete('session');
event.locals.user = null;
}
return resolve(event);
return await resolve(event);
}

View File

@@ -3,7 +3,7 @@ import { client } from '$lib/minio';
/**
* Check if caseNumber is used
* @param {string} caseNumber
* @returns {Promise<boolean}
* @returns {Promise<boolean>}
*/
export default async function caseNumberOccupied(caseNumber) {
const prefix = `${caseNumber}/config.json`;

View File

@@ -0,0 +1,19 @@
<script>
let classNames = '';
export { classNames as class };
</script>
<svg
class="h-6 w-6 text-gray-600 {classNames}"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 002.25-2.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v2.25A2.25 2.25 0 006 10.5zm0 9.75h2.25A2.25 2.25 0 0010.5 18v-2.25a2.25 2.25 0 00-2.25-2.25H6a2.25 2.25 0 00-2.25 2.25V18A2.25 2.25 0 006 20.25zm9.75-9.75H18a2.25 2.25 0 002.25-2.25V6A2.25 2.25 0 0018 3.75h-2.25A2.25 2.25 0 0013.5 6v2.25a2.25 2.25 0 002.25 2.25z"
/>
</svg>

14
src/lib/icons/Cube.svelte Normal file
View File

@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class=" w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25"
/>
</svg>

After

Width:  |  Height:  |  Size: 483 B

View File

@@ -0,0 +1,7 @@
<svg height="20" width="20" xmlns="http://www.w3.org/2000/svg"
><path
d="m14.996094 3-11.9921878 11.992188h-.0039062v4.007812h1 2 1.0078125v-.0039l11.9921875-11.9921938-.001953-.0019531.001953-.0019531-4-4-.002.00195zm-1.998047 3.4121094 2.589844 2.5898437-7.587891 7.5878909v-1.589844h-1-1v-1-.589844zm-7.998047 7.9980466v1.589844h1 1v1 .589844l-.4101562.410156h-1.5898438l-1-1v-1.589844z"
fill="#373737"
transform="translate(1 1)"
/></svg
>

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -0,0 +1,35 @@
<script>
export let outline = false;
let classNames = '';
export { classNames as class };
</script>
{#if outline}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 {classNames}"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
/>
</svg>
{:else}
<svg
class="mx-auto h-12 w-12 text-gray-300 {classNames}"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M1.5 6a2.25 2.25 0 012.25-2.25h16.5A2.25 2.25 0 0122.5 6v12a2.25 2.25 0 01-2.25 2.25H3.75A2.25 2.25 0 011.5 18V6zM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0021 18v-1.94l-2.69-2.689a1.5 1.5 0 00-2.12 0l-.88.879.97.97a.75.75 0 11-1.06 1.06l-5.16-5.159a1.5 1.5 0 00-2.12 0L3 16.061zm10.125-7.81a1.125 1.125 0 112.25 0 1.125 1.125 0 01-2.25 0z"
clip-rule="evenodd"
/>
</svg>
{/if}

View File

@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"
/>
</svg>

After

Width:  |  Height:  |  Size: 460 B

View File

@@ -0,0 +1,19 @@
<script>
let classNames = '';
export { classNames as class };
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6 {classNames}"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
/>
</svg>

View File

@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>

After

Width:  |  Height:  |  Size: 402 B

View File

@@ -1,6 +1,7 @@
<script>
import Chevron from '$lib/icons/Chevron-right.svelte';
import Login from '$lib/icons/Login.svelte';
import Profile from '$lib/icons/Profile.svelte';
export let data;
</script>
@@ -59,20 +60,9 @@
href="/"
class="px-4 py-1 -mr-4 flex items-center justify-center gap-x-2.5 text-sm font-semibold leading-6 text-gray-500 hover:bg-gray-200 hover:text-gray-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
<!--icon-->
<Profile />
{data.user.id}
</a>
</div>

View File

@@ -1,10 +1,11 @@
<style>
</style>
<script>
import Panel from '$lib/components/ui/Panel.svelte';
import AddProcess from '$lib/icons/Add-Process.svelte';
import FileRect from '$lib/icons/File-rect.svelte';
import ListIcon from '$lib/icons/List-icon.svelte';
export let data;
export let outline = true;
</script>
<div
@@ -16,20 +17,7 @@
<div
class="flex h-11 w-11 items-center justify-center rounded-lg bg-gray-50 group-hover:bg-white"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zM3.75 12h.007v.008H3.75V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm-.375 5.25h.007v.008H3.75v-.008zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
/>
</svg>
<ListIcon class=" group-hover:text-indigo-600" />
</div>
<a href="/list" class="mt-6 block font-semibold text-gray-900">
Liste
@@ -40,7 +28,7 @@
</p>
</div>
{/if}
{#if data.user.admin}
<!--{#if data.user.admin}
<div class="group relative rounded-lg p-6 text-sm leading-6 hover:bg-gray-50 w-1/4">
<div
class="flex h-11 w-11 items-center justify-center rounded-lg bg-gray-50 group-hover:bg-white"
@@ -66,26 +54,13 @@
</a>
<p class="mt-1 text-gray-600">Stelle einen weiteren Tatort für die Anwendung bereit.</p>
</div>
{/if}
{/if}-->
{#if data.user.admin}
<div class="group relative rounded-lg p-6 text-sm leading-6 hover:bg-gray-50 w-1/4">
<div
class="flex h-11 w-11 items-center justify-center rounded-lg bg-gray-50 group-hover:bg-white"
>
<svg
class="h-6 w-6 text-gray-600 group-hover:text-indigo-600"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 002.25-2.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v2.25A2.25 2.25 0 006 10.5zm0 9.75h2.25A2.25 2.25 0 0010.5 18v-2.25a2.25 2.25 0 00-2.25-2.25H6a2.25 2.25 0 00-2.25 2.25V18A2.25 2.25 0 006 20.25zm9.75-9.75H18a2.25 2.25 0 002.25-2.25V6A2.25 2.25 0 0018 3.75h-2.25A2.25 2.25 0 0013.5 6v2.25a2.25 2.25 0 002.25 2.25z"
/>
</svg>
<AddProcess class=" group-hover:text-indigo-600" />
</div>
<a href="/upload" class="mt-6 block font-semibold text-gray-900">
Hinzufügen
@@ -98,20 +73,7 @@
<div
class="flex h-11 w-11 items-center justify-center rounded-lg bg-gray-50 group-hover:bg-white"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
/>
</svg>
<FileRect class=" group-hover:text-indigo-600" {outline} />
</div>
<a href="/view" class="mt-6 block font-semibold text-gray-900">
Ansicht
@@ -121,3 +83,6 @@
</div>
</div>
</div>
<style>
</style>

View File

@@ -1,16 +1,13 @@
<style>
ul {
min-width: 24rem;
}
</style>
<script>
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import Trash from '$lib/icons/Trash.svelte';
import Folder from '$lib/icons/Folder.svelte';
/**
* @type any[]
*/
let list = [];
let list: any[] = [];
//$: list;
onMount(async () => {
@@ -34,6 +31,37 @@
list = list.concat(objs);
}
});
async function delete_item(ev: Event) {
let delete_item = window.confirm('Bist du sicher?');
if (delete_item) {
const target = ev.currentTarget as HTMLElement | null;
if (!target) return;
let filename = target.id.split('del__')[1];
// delete request
// --------------
let url = `/api/list/${filename}`;
console.log(`--- ${filename} + ${url}`);
try {
const response = await fetch(url, { method: 'DELETE' });
if (response.status == 204) {
setTimeout(() => {
window.location.reload();
}, 500);
}
} catch (error) {
if (error instanceof Error) {
console.log(error.message);
} else {
console.log(error);
}
}
}
}
</script>
<div class="-z-10 bg-white">
@@ -46,22 +74,19 @@
<li>
<a href="/list/{item.name}" class="flex justify-between gap-x-6 py-5">
<div class="flex gap-x-4">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"
/>
</svg>
<!-- Ordner -->
<Folder />
<div class="min-w-0 flex-auto">
<p class="text-sm font-semibold leading-6 text-gray-900">{item.name}</p>
<span class="text-sm font-semibold leading-6 text-gray-900">{item.name}</span>
<!-- Delete button -->
<button
style="padding: 2px"
id="del__{item.name}"
on:click|preventDefault={delete_item}
aria-label="Vorgang {item.name} löschen"
>
<Trash />
</button>
</div>
</div>
<div class="hidden sm:flex sm:flex-col sm:items-end">
@@ -73,3 +98,9 @@
</ul>
</div>
</div>
<style>
ul {
min-width: 24rem;
}
</style>

View File

@@ -1,25 +1,43 @@
<style>
ul {
min-width: 24rem;
}
</style>
<script>
<script lang="ts">
import { onMount, tick } from 'svelte';
import shortenFileSize from '$lib/helper/shortenFileSize';
import { page } from '$app/stores';
import timeElapsed from '$lib/helper/timeElapsed';
/** @type {import('./$types').PageData} */
/** export let data; */
import Alert from '$lib/components/ui/Alert.svelte';
import Button from '$lib/components/ui/Button.svelte';
import Modal from '$lib/components/ui/Modal/Modal.svelte';
import ModalTitle from '$lib/components/ui/Modal/ModalTitle.svelte';
import ModalContent from '$lib/components/ui/Modal/ModalContent.svelte';
import ModalFooter from '$lib/components/ui/Modal/ModalFooter.svelte';
import Cube from '$lib/icons/Cube.svelte';
import Edit from '$lib/icons/Edit.svelte';
import Trash from '$lib/icons/Trash.svelte';
/**
* @type any[]
*/
let list = [];
/** @type {import('./$types').PageData} */
export let data;
console.log(`--- ${data.user.admin}`);
interface ListItem {
name: string;
size: number;
lastModified: string | number | Date;
show_button?: boolean;
// add other properties as needed
}
let list: ListItem[] = [];
$: list;
let open = false;
$: open;
let inProgress = false;
$: inProgress;
let err = false;
$: err;
let rename_input;
$: rename_input;
@@ -46,9 +64,102 @@
list = list.map((item) => {
item.show_button = true;
return item;
})
});
}
});
function uploadSuccessful() {
console.log('reset');
open = false;
}
function defocus_element(i: number) {
let item = list[i];
let text_field_id = `label__${item.name}`;
let text_field = document.getElementById(text_field_id);
if (text_field) {
text_field.setAttribute('contenteditable', 'false');
text_field.textContent = item.name;
}
// reshow button
list[i].show_button = true;
return;
}
async function handle_input(ev: KeyboardEvent, i: number) {
let item = list[i];
if (ev.key == 'Escape') {
let text_field_id = `label__${item.name}`;
let text_field = document.getElementById(text_field_id);
if (text_field) {
text_field.setAttribute('contenteditable', 'false');
text_field.textContent = item.name;
}
// reshow button
item.show_button = true;
return;
}
if (ev.key == 'Enter') {
console.log('--- hitted');
let name_field = ev.currentTarget as HTMLElement | null;
let new_name = name_field
? name_field.textContent || (name_field as any).innerText || ''
: '';
if (new_name == '') {
alert('Bitte einen gültigen Namen eingeben.');
ev.preventDefault();
return;
}
// actual upload
// -------------
// to prevent from item being selected
ev.preventDefault();
// construct PUT URL
const url = $page.url;
console.log(url);
let data_obj: { new_name: string; old_name: string } = { new_name: '', old_name: '' };
data_obj['new_name'] = new_name;
data_obj['old_name'] =
ev.currentTarget && (ev.currentTarget as HTMLElement).id
? (ev.currentTarget as HTMLElement).id.split('__')[1]
: '';
open = true;
inProgress = true;
const response = await fetch(url, { method: 'PUT', body: JSON.stringify(data_obj) });
inProgress = false;
if (!response.ok) {
err = true;
if (response.status == 400) {
let json_res = await response.json();
// alert(json_res['msg'])
return;
}
throw new Error(`Fehlgeschlagen: ${response.status}`);
} else {
uploadSuccessful();
setTimeout(() => {
window.location.reload();
}, 500);
}
// --- upload finished ---
return;
}
}
</script>
<div class="-z-10 bg-white">
@@ -57,138 +168,151 @@
</div>
<div class="mx-auto flex justify-center max-w-7xl h-full">
<ul class="divide-y divide-gray-100">
{#each list as item}
{#each list as item, i}
<li>
<a
href="/view/{$page.params.vorgang}/{item.name}"
class="flex justify-between gap-x-6 py-5"
>
<div class="flex gap-x-4">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25"
/>
</svg>
<div class="min-w-0 flex-auto">
<div class=" flex gap-x-4">
<a
href="/view/{$page.params.vorgang}/{item.name}"
class=" flex justify-between gap-x-6 py-5"
aria-label="zum 3D-modell"
>
<Cube />
</a>
<div class="min-w-0 flex-auto">
{#if data.user.admin}
<span
style="display: inline-block; min-width: 5px;"
id="{item.name}-label"
class="text-sm font-semibold leading-6 text-gray-900"
contenteditable={! item.button}
on:focusout={(event) => {
let text_field_id = item.name + "-label"
id="label__{item.name}"
class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
contenteditable={!item.show_button}
role="textbox"
tabindex="0"
aria-label="Dateiname bearbeiten"
on:focusout={() => {
defocus_element(i);
}}
on:keydown|stopPropagation={// event needed to identify ID
// TO-DO: check if event is needed or if index is sufficient
async (ev) => {
handle_input(ev, i);
}}>{item.name}</span
>
let text_field = document.getElementById(text_field_id)
text_field.setAttribute("contenteditable", false)
text_field.textContent = item.name;
// reshow button
item.show_button = true
return;
}}
on:keydown|stopPropagation={async (event) => {
if (event.key == "Escape") {
let text_field_id = item.name + "-label"
let text_field = document.getElementById(text_field_id)
text_field.setAttribute("contenteditable", false)
text_field.textContent = item.name;
// reshow button
item.show_button = true
return;
}
if (event.key == "Enter") {
console.log('--- hitted')
let name_field = event.currentTarget
let new_name = name_field.textContent || name_field.innerText || '';
if (new_name == '') {
alert('Bitte einen gültigen Namen eingeben.');
event.preventDefault();
return;
}
// actual upload
// -------------
// to prevent from item being selected
event.preventDefault();
// construct PUT URL
const url = $page.url
console.log(url);
let data_obj = {}
data_obj["new_name"] = new_name
data_obj["old_name"] = event.currentTarget.id.split('-')[0]
const response = await fetch(url,
{method: 'PUT', body: JSON.stringify( data_obj )
})
if (!response.ok) {
throw new Error(`Fehlgeschlagen: ${response.status}`)
} else {
setTimeout(() => {window.location.reload()}, 500)
}
// --- upload finished ---
return;
}
}}
>{item.name}</span>
<input
class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
type="text"
name=""
on:keydown|stopPropagation={// event needed to identify ID
// TO-DO: check if event is needed or if index is sufficient
async (ev) => {
handle_input(ev, i);
}}
bind:value={item.name}
id="label__{item.name}"
/>
<!-- disabled={item.show_button} -->
<!-- https://iconduck.com/icons/192863/edit-rename -->
{#if item.show_button}
<button
style="padding: 2px"
id="{item.name}-btn"
on:click|preventDefault={(ev) => {
let text_field_id = item.name + "-label"
<button
style="padding: 2px"
id="edit__{item.name}"
aria-label="Datei umbenennen"
on:click|preventDefault={(ev) => {
let text_field_id = `label__${item.name}`;
let text_field = document.getElementById(text_field_id)
text_field.setAttribute("contenteditable", true)
text_field.focus();
text_field.textContent = '';
let text_field = document.getElementById(text_field_id);
if (text_field) {
text_field.setAttribute('contenteditable', 'true');
text_field.focus();
text_field.textContent = '';
}
// hide button
item.show_button = false
}}
>
<svg
style="vertical-align: middle" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m14.996094 3-11.9921878 11.992188h-.0039062v4.007812h1 2 1.0078125v-.0039l11.9921875-11.9921938-.001953-.0019531.001953-.0019531-4-4-.002.00195zm-1.998047 3.4121094 2.589844 2.5898437-7.587891 7.5878909v-1.589844h-1-1v-1-.589844zm-7.998047 7.9980466v1.589844h1 1v1 .589844l-.4101562.410156h-1.5898438l-1-1v-1.589844z" fill="#373737" transform="translate(1 1)"/></svg>
</button>
// hide button
item.show_button = false;
}}
>
<Edit />
</button>
{/if}
<p class="mt-1 truncate text-xs leading-5 text-gray-500">
{shortenFileSize(item.size)}
</p>
</div>
</div>
<div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">3D Tatort</p>
<p class="mt-1 text-xs leading-5 text-gray-500">
Zuletzt geändert <time datetime="2023-01-23T13:23Z"
>{timeElapsed(new Date(item.lastModified))}</time
<button
style="padding: 2px"
id="del__{item.name}"
on:click|preventDefault={async (ev) => {
let delete_item = window.confirm('Bist du sicher?');
if (delete_item) {
// bucket: tatort, name: <vorgang>/item-name
let vorgang = $page.params.vorgang;
let filename = '';
if (ev && ev.currentTarget && (ev.currentTarget as HTMLElement).id) {
filename = (ev.currentTarget as HTMLElement).id.split('del__')[1];
}
// delete request
// --------------
let url = new URL($page.url);
url.pathname += `/${filename}`;
console.log(`--- ${vorgang} + ${filename} + ${url}`);
try {
const response = await fetch(url, { method: 'DELETE' });
if (response.status == 204) {
setTimeout(() => {
window.location.reload();
}, 500);
}
} catch (error) {
if (error instanceof Error) {
console.log(error.message);
} else {
console.log(error);
}
}
}
}}
aria-label="Datei löschen"
>
<Trash />
</button>
{:else}
<span class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
>{item.name}</span
>
{/if}
<p class="mt-1 truncate text-xs leading-5 text-gray-500">
{shortenFileSize(item.size)}
</p>
</div>
</a>
</div>
<div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">3D Tatort</p>
<p class="mt-1 text-xs leading-5 text-gray-500">
Zuletzt geändert <time datetime="2023-01-23T13:23Z"
>{timeElapsed(new Date(item.lastModified))}</time
>
</p>
</div>
</li>
{/each}
</ul>
</div>
<Modal {open}
><ModalTitle>Umbenennen</ModalTitle><ModalContent>
{#if inProgress}
<p class="py-2 mb-1">Vorgang läuft...</p>
{/if}
{#if err}
<Alert class="w-full" type="error">Fehler beim Umbenennen</Alert>
{/if}
</ModalContent>
<ModalFooter><Button disabled={inProgress} on:click={uploadSuccessful}>Ok</Button></ModalFooter>
</Modal>
</div>
<style>
ul {
min-width: 24rem;
}
</style>

View File

@@ -2,7 +2,7 @@ import { client } from '$lib/minio';
import { json } from '@sveltejs/kit';
// rename operation
export async function PUT({ request }) {
const data = await request.json();
@@ -11,10 +11,20 @@ export async function PUT({ request }) {
// Vorgang
let vorgang = request.url.split('/').at(-1)
// copy
// prepare copy, incl. check if new name exists already
let old_name = data["old_name"]
let src_full_path = `/tatort/${vorgang}/${old_name}`
let new_name = `${vorgang}/${data["new_name"]}`
try {
let file_stats = await client.statObject('tatort', new_name)
return json({ msg: 'Die Datei existiert bereits.' }, { status: 400 })
} catch (error) {
// continue operation
console.log('continue operation')
}
// actual copy operation
await client.copyObject('tatort', new_name, src_full_path)
// delete
@@ -22,5 +32,5 @@ export async function PUT({ request }) {
// return success or failure
return json({ success: 'SUCKZESS' }, { status: 200 });
return json({ success: 'success' }, { status: 200 });
};

View File

@@ -0,0 +1,13 @@
import { client } from '$lib/minio';
import { json } from '@sveltejs/kit';
export async function DELETE({ request }) {
let url_fragments = request.url.split('/')
let item = url_fragments.at(-1);
let vorgang = url_fragments.at(-2);
await client.removeObject('tatort', `${vorgang}/${item}`)
return new Response(null, { status: 204 });
};

View File

@@ -73,8 +73,24 @@
<p class="block text-sm leading-6 text-red-900 mt-2">{form.error.description}</p>
{/if}
</div>
<label for="code">
<span >Zugangscode (optional) </span>
</label>
<div class="mt-2">
<div
>
<input
type="text"
id="code"
/>
</div>
</div>
</div>
</div>
<div class="mt-6 flex items-center justify-end gap-x-6">
<button type="button" class="text-sm font-semibold leading-6 text-gray-900">Cancel</button>
<Button

View File

@@ -42,6 +42,7 @@ export const actions = {
const data = Object.fromEntries(requestData);
const vorgang = data.vorgang;
const name = data.name;
const zugangscode = data.zugangscode;
let success = true;
let err = {};
@@ -57,6 +58,12 @@ export const actions = {
success = false;
}
if (isRequiredFieldValid(zugangscode)) err.zugangscode = null;
else {
err.zugangscode = 'Das Feld Zugangscode darf nicht leer bleiben.';
success = false;
}
if (success) return { success };
return fail(400, err);

View File

@@ -8,12 +8,18 @@
import ModalFooter from '$lib/components/ui/Modal/ModalFooter.svelte';
import shortenFileSize from '$lib/helper/shortenFileSize.js';
import Exclamation from '$lib/icons/Exclamation.svelte';
import FileRect from '$lib/icons/File-rect.svelte';
export let form;
let open = false;
let inProgress = false;
let vorgang = '';
const code_len = 8;
let zugangscode = Math.random()
.toString(36)
.slice(2, 2 + code_len);
let case_existing = undefined;
let name = '';
/** @type {?string}*/
let etag = null;
@@ -29,6 +35,7 @@
let data = new FormData();
data.append('vorgang', vorgang);
data.append('name', name);
data.append('zugangscode', zugangscode);
const response = await fetch('?/validate', { method: 'POST', body: data });
/** @type {import('@sveltejs/kit').ActionResult} */
const result = deserialize(await response.text());
@@ -47,6 +54,11 @@
success = false;
}
if (!(await check_valid_glb_file())) {
formErrors = { file: 'Keine gültige .GLD-Datei', ...formErrors };
success = false;
}
return success;
}
@@ -97,6 +109,63 @@
name = '';
files = null;
}
// `val` is hex string
function swap_endian(val) {
// from https://www.geeksforgeeks.org/bit-manipulation-swap-endianness-of-a-number/
let leftmost_byte = (val & eval(0x000000ff)) >> 0;
let left_middle_byte = (val & eval(0x0000ff00)) >> 8;
let right_middle_byte = (val & eval(0x00ff0000)) >> 16;
let rightmost_byte = (val & eval(0xff000000)) >> 24;
leftmost_byte <<= 24;
left_middle_byte <<= 16;
right_middle_byte <<= 8;
rightmost_byte <<= 0;
let res = leftmost_byte | left_middle_byte | right_middle_byte | rightmost_byte;
return res;
}
async function check_valid_glb_file() {
// GLD Header, magic value 0x46546C67, identifies data as binary glTF, 4 bytes
// little endian!
const GLD_MAGIC = 0x46546c67;
// big endian!
let file = files[0];
let file_header = file.slice(0, 4);
let header_bytes = await file_header.bytes();
let file_header_hex = '0x' + header_bytes.toHex().toString();
if (GLD_MAGIC == swap_endian(file_header_hex)) {
return true;
} else {
return false;
}
}
// return true or false
async function case_exists(case_no) {
console.log('--- fired');
// ping `/(angemeldet)/view` with caseNumber in POST body
let url = '/view';
let data = new FormData();
data.append('caseNumber', case_no);
const response = await fetch(url, { method: 'POST', body: data });
const code = response.status;
console.log(`+++ ${response.redirected}`);
if (code == 303) {
return true;
}
return false;
}
</script>
<div class="mx-auto max-w-2xl">
@@ -131,6 +200,7 @@
id="vorgang"
autocomplete={vorgang}
class="block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
on:input={() => case_exists(vorgang)}
/>
</div>
</div>
@@ -166,6 +236,32 @@
{/if}
</div>
<div>
<label for="zugangscode" class="block text-sm font-medium leading-6 text-gray-900"
><span class="flex"
>{#if formErrors?.zugangscode}
<span class="inline-block mr-1"><Exclamation /></span>
{/if} Zugangscode</span
></label
>
<div class="mt-2">
<div
class="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600"
>
<input
bind:value={zugangscode}
type="text"
name="zugangscode"
id="zugangscode"
class="block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
/>
</div>
</div>
{#if formErrors?.code}
<p class="block text-sm leading-6 text-red-900 mt-2">{formErrors.code}</p>
{/if}
</div>
<div class="col-span-full">
<label for="file" class="block text-sm font-medium leading-6 text-gray-900"
><span class="flex"
@@ -178,18 +274,7 @@
class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 px-6 py-10"
>
<div class="text-center">
<svg
class="mx-auto h-12 w-12 text-gray-300"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M1.5 6a2.25 2.25 0 012.25-2.25h16.5A2.25 2.25 0 0122.5 6v12a2.25 2.25 0 01-2.25 2.25H3.75A2.25 2.25 0 011.5 18V6zM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0021 18v-1.94l-2.69-2.689a1.5 1.5 0 00-2.12 0l-.88.879.97.97a.75.75 0 11-1.06 1.06l-5.16-5.159a1.5 1.5 0 00-2.12 0L3 16.061zm10.125-7.81a1.125 1.125 0 112.25 0 1.125 1.125 0 01-2.25 0z"
clip-rule="evenodd"
/>
</svg>
<FileRect />
<div class="mt-4 flex text-sm leading-6 text-gray-600">
<label
for="file"

View File

@@ -5,9 +5,11 @@ import { fail, redirect } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
console.log(`--- ${Object.keys(data)}`)
const caseNumber = data.get('caseNumber');
if (!caseNumber) {
console.log('^^^ here')
return fail(400, {
success: false,
caseNumber,
@@ -15,13 +17,20 @@ export const actions = {
});
}
let res = (await caseNumberOccupied(caseNumber))
console.log(`gibt es? ${res} + ${caseNumber}`)
if (!(await caseNumberOccupied(caseNumber))) {
console.log('^^^ there')
return fail(400, {
success: false,
caseNumber,
error: { caseNumber: 'Die Vorgangsnummer existiert in dieser Anwendung nicht.' }
});
}
throw redirect(303, `/list/${caseNumber}`);
else {
throw redirect(303, `/list/${caseNumber}`);
console.log(`---blabla ${caseNumber}`)
}
}
};

View File

@@ -1,14 +1,8 @@
<style>
model-viewer {
height: 100%;
width: 100%;
}
</style>
<script>
import { preloadCode } from '$app/navigation';
import Panel from '$lib/components/ui/Panel.svelte';
import { onMount } from 'svelte';
import Button from '$lib/components/ui/Button.svelte';
export let data;
onMount(() => {
@@ -17,8 +11,33 @@
let progress = 0;
let hideProgressScreen = false;
let cameraOrbit = '0deg 0deg 100%';
let cameraTarget = '0m 0m 0m';
let fieldOfView = '10deg';
let cameraAzimuth = 0;
let cameraPolar = 0;
let frontView = cameraAzimuth === 0 && cameraPolar === 0 ? true : false;
let topView = cameraAzimuth === 0 && cameraPolar === 90 ? true : false;
let cameraZoom = 100;
let xRotation = 0;
let yRotation = 0;
let zRotation = 0;
/**
* @type {any}
*/
let modelViewer;
$: style = `width: ${progress}%`;
/**
* @param {any} detail
*/
function onProgress({ detail }) {
progress = Math.ceil(detail.totalProgress * 100.0);
if (progress == 100) {
@@ -27,29 +46,190 @@
}, 250);
} else hideProgressScreen = false;
}
function onResetView() {
console.log(
'show cameraOrbit:',
modelViewer.getCameraOrbit(),
modelViewer.cameraOrbit,
modelViewer.getDimensions()
);
console.log(
'Camera-orbit: ',
modelViewer.getAttribute('camera-orbit'),
'camera-target: ',
modelViewer.getAttribute('camera-target'),
'object-rotation: ',
modelViewer.getAttribute('rotation')
);
cameraAzimuth = 0;
cameraPolar = 0;
cameraZoom = 100;
modelViewer.cameraOrbit = cameraOrbit;
modelViewer.cameraTarget = cameraTarget;
modelViewer.fieldOfView = fieldOfView;
cameraAzimuth = 0;
cameraPolar = 0;
cameraZoom = 100;
fieldOfView = '10deg';
}
/**
* @param {number} azimuth
* @param {number} polar
* @param {number} zoom
*/
function updateCameraOrbit(azimuth, polar, zoom) {
cameraAzimuth = azimuth;
cameraPolar = polar;
cameraZoom = zoom;
}
</script>
<div class="h-full w-full bg-neutral-100 p-4">
<div class="h-full model w-full bg-neutral-200 p-4 transition-all delay-250">
<!-- xr-environment -->
<model-viewer
ar
shadow-intensity="1"
src={data.url}
camera-controls
field-of-view="auto"
bind:this={modelViewer}
touch-action="pan-y"
field-of-view={fieldOfView}
min-field-of-view="1deg"
max-field-of-view="10deg"
min-field-of-view="0.1deg"
camera-controls
orientation={`${xRotation}deg ${yRotation}deg ${zRotation}deg`}
camera-target="0m 0m 0m"
camera-orbit={`${cameraAzimuth}deg ${cameraPolar}deg ${cameraZoom}%`}
on:progress={onProgress}
>
<!--Buttons zum Steuern-->
<div
class=" p-4 flex z-10 absolute bottom-0"
class:opacity-0={!hideProgressScreen}
class:hidden={!hideProgressScreen}
>
<button slot="ar-button" id="ar-button"> 👋 Activate AR </button>
<div id="ar-prompt">AR-Prompt</div>
<button id="ar-failure"> AR is not tracking! </button>
<div class="flex flex-col bg-white/50">
<button
on:click={() => {
console.log(modelViewer.ar, modelViewer.getAttribute('ar-status'));
}}>Test</button
>
<!--3 Buttons-->
<div class="p-2">
<Button
on:click={() => {
updateCameraOrbit(0, 0, cameraZoom);
}}
type="button"
variant="white"
align="left"
class="relative cursor-default justify-start py-2 pl-3 pr-10 text-left "
>
Draufsicht
</Button>
<Button
on:click={() => {
onResetView();
}}
type="button"
variant="white"
align="left"
class="relative cursor-default justify-start py-2 pl-3 pr-10 text-left"
>
Werte zurücksetzen
</Button>
<Button
on:click={() => {
updateCameraOrbit(0, 90, cameraZoom);
}}
type="button"
variant="white"
align="left"
class="relative cursor-default justify-start py-2 pl-3 pr-10 text-left"
>
Frontansicht
</Button>
</div>
<!--Horizontal-->
<div class="p-2">
<label for="cazimuth">Horizontal drehen (Azimut):</label>
<input id="cazimuth" type="range" min="0" max="360" bind:value={cameraAzimuth} />
</div>
<!--Vertikal-->
<div class="p-2">
<label class=" mb-2" for="polarSlider">Vertikal drehen (Polar):</label>
<input id="polarSlider" type="range" min="0" max="90" bind:value={cameraPolar} />
</div>
<!--Zoom/Distanz-->
<div class="p-2">
<label for="cZoom">Abstand (zoom):</label>
<input id="cZoom" type="range" min="0" max="100" bind:value={cameraZoom} />
</div>
</div>
<!--Rotationen-->
<div class="flex flex-col ml-2 p-4 bg-white/50">
Modell rotieren lassen:
<br />
Modell auf dem Kopf? -> y auf 270°
<div class="p-2">
<label class="" for="polarSlider">Rotation X:</label>
<input id="polarSlider" type="range" min="0" max="360" bind:value={xRotation} />
</div>
<div class="p-2">
<label class="" for="polarSlider">Rotation Y:</label>
<input id="polarSlider" type="range" min="0" max="360" bind:value={yRotation} />
</div>
<div class="p-2">
<label class="" for="polarSlider">Rotation Z:</label>
<input id="polarSlider" type="range" min="0" max="360" bind:value={zRotation} />
</div>
</div>
</div>
<!--Loading-->
<div
slot="progress-bar"
class="flex items-center justify-center h-full w-full transition-all delay-250"
class="flex items-center justify-center h-5/6 w-full transition-all delay-250"
class:opacity-0={hideProgressScreen}
class:hidden={hideProgressScreen}
>
<Panel class="w-72 bg-gray-50 flex items-center flex-col"
><p class="mb-5">Loading {progress}%</p>
<div class="h-1 w-full bg-neutral-200 dark:bg-neutral-600">
<div class="h-1 bg-blue-500" {style} ></div>
<div class="h-1 bg-blue-500" {style}></div>
</div></Panel
>
</div>
</model-viewer>
</div>
<style>
model-viewer {
height: 100%;
width: 100%;
}
/* .vertical-slider {
writing-mode: bt-lr; /* Schreibt von unten nach oben (Vertikale Darstellung)
transform: rotate(270deg); /* Slider um 270° drehen
height: 200px;
} */
.model {
height: calc(100%-84px);
}
/* .active-border {
border: 2px blue solid;
} */
</style>

View File

@@ -33,3 +33,31 @@ export async function GET({ params }) {
}
});
}
export async function DELETE({ params }) {
const vorgang = params.vorgang
const object_list = await new Promise((resolve, reject) => {
const res = []
const items_str = client.listObjects('tatort', vorgang, true)
items_str.on('data', (obj) => {
res.push(obj.name)
})
items_str.on('error', reject)
items_str.on('end', async () => {
resolve(res)
})
console.log(`+++ ${vorgang}`)
})
await client.removeObjects('tatort', object_list)
return new Response(null, { status: 204 });
};

14
static/1.svg Normal file
View File

@@ -0,0 +1,14 @@
<svg
class="h-6 w-6 text-gray-600 group-hover:text-indigo-600"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 002.25-2.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v2.25A2.25 2.25 0 006 10.5zm0 9.75h2.25A2.25 2.25 0 0010.5 18v-2.25a2.25 2.25 0 00-2.25-2.25H6a2.25 2.25 0 00-2.25 2.25V18A2.25 2.25 0 006 20.25zm9.75-9.75H18a2.25 2.25 0 002.25-2.25V6A2.25 2.25 0 0018 3.75h-2.25A2.25 2.25 0 0013.5 6v2.25a2.25 2.25 0 002.25 2.25z"
/>
</svg>

After

Width:  |  Height:  |  Size: 714 B