hochladen

This commit is contained in:
2025-07-08 09:32:19 +02:00
parent 1c471b4239
commit f335f7a13f
4 changed files with 262 additions and 183 deletions

View File

@@ -1,126 +1,101 @@
<script lang="ts"> <script lang="ts">
import Edit from '$lib/icons/Edit.svelte'; import Edit from '$lib/icons/Edit.svelte';
import Trash from '$lib/icons/Trash.svelte'; import Trash from '$lib/icons/Trash.svelte';
import { validateInput } from '$lib/helper/error-utils'; import { tick } from 'svelte';
interface ListItem { interface ListItem {
name: string; name: string;
token?: string; token?: string;
// add other properties as needed // add other properties as needed
} }
let {
list,
editedName = $bindable(),
currentName,
onSave = () => {},
onDelete = () => {}
} = $props();
type Variant = '' | 'casename' | 'crimename'; let names = list.map((l: ListItem) => l.name);
let localName = $state(currentName);
//let names = list;
let wasCancelled = $state(false);
export let inputValue: string = ''; // Automatisch berechneter Fehler
export let variant: Variant = ''; let error: string = $derived(validateName(localName));
export let existings: ListItem[];
export let id: number;
export let editable: boolean = true;
export let editing: boolean;
//CustomEvents: // Manuell steuerbarer Fehlerstatus
export let editStart: (payload: { let manualError = $state('');
id: number;
inputValue: string;
variant: Variant;
editing: boolean;
}) => void;
export let save: (payload: { newValue: string; oldValue: string; variant: Variant }) => void; console.log('EditableItem: Beginn', names, editedName);
export let deleteItem: (payload: {
inputValue: string;
variant: Variant;
customEvent: Event;
}) => void;
export let cancel: () => void = () => {};
const existingNames = existings.map((item) => item.name);
export { classNames as class }; let isEditing = $state(false);
let inputRef: HTMLInputElement;
let internalValue = inputValue; // Validierungsfunktion
let oldValue = inputValue; function validateName(name: string) {
let showWarning = false; const trimmed = name.trim();
$: errors = validateInput(oldValue, internalValue, { minLength: 3, existingNames }); if (!trimmed) {
return 'Name darf nicht leer sein.';
function startEdit() {
oldValue = inputValue;
internalValue = inputValue;
editing = true;
editStart({ id, inputValue, variant, editing });
}
function cancelEdit() {
internalValue = oldValue;
editing = false;
showWarning = false;
console.log('Abgebrochen');
cancel();
}
function saveEdit(ev?: MouseEvent | KeyboardEvent) {
ev?.preventDefault();
ev?.stopPropagation();
const name = internalValue.trim();
if (errors.length !== 0) {
showWarning = true;
console.log('Abgebrochen', errors);
return;
}
if (!name) {
showWarning = true;
console.log('Abgebrochen', showWarning);
return;
} }
if (existingNames.includes(name) && name !== oldValue.trim()) { const duplicate = list.some(
showWarning = true; (item: ListItem) => item.name === trimmed && item.name !== currentName
console.log('Abgebrochen', showWarning); );
return;
}
editing = false; if (duplicate) return 'Name existiert bereits.';
showWarning = false;
save({ newValue: internalValue, oldValue, variant }); return '';
} }
function handleKey(e: KeyboardEvent) { // Speichern, wenn gültig und zurück an Eltern
if (e.key === 'Enter') saveEdit(e); function commitIfValid() {
if (e.key === 'Escape') cancelEdit(); if (!validateName(localName) && !wasCancelled && localName != currentName) {
editedName = localName.trim();
onSave(editedName, currentName); //Eltern benachrichtigen
} else {
resetEdit();
}
}
// Abbrechen Eingabe zurücksetzen
function resetEdit() {
wasCancelled = false;
manualError = '';
inputRef?.blur();
isEditing = false;
}
// Tastatursteuerung
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
event.preventDefault();
commitIfValid();
} else if (event.key === 'Escape') {
event.preventDefault();
editedName = currentName;
resetEdit();
}
}
async function startEdit() {
isEditing = true;
await tick();
inputRef?.focus();
} }
</script> </script>
<div class=""> <div>
<div class="flex gap-x-2"> <input
{#if editing} bind:this={inputRef}
<input bind:value={localName}
bind:value={internalValue} onblur={commitIfValid}
on:keydown|preventDefault={handleKey} onkeydown={handleKeydown}
on:blur|preventDefault|stopPropagation={cancelEdit} />
class="" <button onclick={startEdit}><Edit /></button>
class:bg-red-200={(showWarning && editing) || errors.length !== 0} <button onclick={() => onDelete(currentName)}><Trash /></button>
/> {#if manualError || error}
{:else} <p style="color: red;">{manualError || error}</p>
<span>{inputValue}</span>
{#if !editing && editable}
<button on:click|preventDefault|stopPropagation={startEdit}>
<Edit />
</button>
<button
on:click|preventDefault={(e: Event) => {
deleteItem({ inputValue, variant, customEvent: e });
}}
>
<Trash />
</button>
{/if}
{/if}
</div>
{#if editing && errors}
<p class="text-red-600 text-sm mt-1 font-medium">{errors[0]}</p>
{/if} {/if}
</div> </div>

View File

@@ -2,17 +2,23 @@
import Folder from '$lib/icons/Folder.svelte'; import Folder from '$lib/icons/Folder.svelte';
import EditableItem from '$lib/components/EditableItem.svelte'; import EditableItem from '$lib/components/EditableItem.svelte';
export let data;
export let editingId: number;
interface ListItem { interface ListItem {
name: string; name: string;
token?: string; token?: string;
// add other properties as needed // add other properties as needed
} }
let { data } = $props();
const caseList: ListItem[] = data.caseList; const caseList: ListItem[] = data.caseList;
//Variabeln für EditableItem
let names: string[] = $state(caseList.map((l) => l.name));
let editedName: string = $state('');
function getNameById(list: ListItem[], id: number) {
return list[id].name;
}
async function delete_item(ev: Event) { async function delete_item(ev: Event) {
let delete_item = window.confirm('Bist du sicher?'); let delete_item = window.confirm('Bist du sicher?');
@@ -42,6 +48,51 @@
} }
} }
} }
async function handleSave(newName: string, oldName: string) {
console.log('Eltern, speichern erfolgreich', newName, oldName);
try {
const res = await fetch(`/api/list/${oldName}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ oldName, newName })
});
if (!res.ok) {
const msg = await res.text();
console.error('❌ Fehler beim Speichern:', msg);
} else {
console.log('✅ Erfolgreich gespeichert:', newName);
}
} catch (err) {
console.error('⚠️ Netzwerkfehler:', err);
}
}
async function handleDelete(name: string) {
try {
const res = await fetch(`/api/list/${name}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name })
});
if (!res.ok) {
const msg = await res.text();
console.error('❌ Fehler beim Löschen:', msg);
} else {
console.log('🗑️ Erfolgreich gelöscht:', name);
// Optional: Lokale Liste aktualisieren
//S items = items.filter((item) => item.name !== name);
}
} catch (err) {
console.error('⚠️ Netzwerkfehler beim Löschen:', err);
}
}
</script> </script>
<div class="-z-10 bg-white"> <div class="-z-10 bg-white">
@@ -52,33 +103,28 @@
<ul role="list" class="divide-y divide-gray-100"> <ul role="list" class="divide-y divide-gray-100">
{#each caseList as item, i} {#each caseList as item, i}
<li> <li>
<a href="/list/{item.name}?token={item.token}" class="flex justify-between gap-x-6 py-5"> <div class="flex gap-x-4">
<div class="flex gap-x-4"> <!-- Ordner -->
<!-- Ordner --> <a
href="/list/{item.name}?token={item.token}"
class="bg-red-500 flex justify-between gap-x-6 py-5"
>
<Folder /> <Folder />
<div class="min-w-0 flex-auto"> </a>
<EditableItem
class="" <div class="min-w-0 flex-auto">
id={i} <EditableItem
inputValue={item.name} list={caseList}
editing={editingId === i} bind:editedName={names[i]}
editStart={() => (editingId = i)} currentName={item.name}
variant="casename" onSave={handleSave}
existings={caseList} onDelete={handleDelete}
save={(data) => { ></EditableItem>
console.log('Gespeichert:', data);
}}
deleteItem={(data) => {
console.log('Gelöscht:', data);
delete_item(data.customEvent);
}}
></EditableItem>
</div>
<div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">Vorgang</p>
</div>
</div> </div>
</a> <div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">Vorgang</p>
</div>
</div>
</li> </li>
{/each} {/each}
</ul> </ul>

View File

@@ -122,49 +122,48 @@
<ul class="divide-y divide-gray-100"> <ul class="divide-y divide-gray-100">
{#each crimesList as item, i} {#each crimesList as item, i}
<li> <li>
<a <div class=" flex gap-x-4">
href="/view/{$page.params.vorgang}/{item.name}?token={token}" <a
class=" flex justify-between gap-x-6 py-5" href="/view/{$page.params.vorgang}/{item.name}?token={token}"
aria-label="zum 3D-modell" class=" flex justify-between gap-x-6 py-5"
> aria-label="zum 3D-modell"
<div class=" flex gap-x-4"> >
<Cube /> <Cube />
<div class="min-w-0 flex-auto"> </a>
{#if admin} <div class="min-w-0 flex-auto">
<EditableItem {#if admin}
class="bg-lred-500" <EditableItem
id={i} id={i}
inputValue={item.name} inputValue={item.name}
editing={editingId === i} editing={editingId === i}
editStart={() => (editingId = i)} editStart={() => (editingId = i)}
variant="crimename" variant="crimename"
existings={crimesList} existings={crimesList}
save={(e) => console.log('Gespeichert:', e)} save={(e) => console.log('Gespeichert:', e)}
deleteItem={(e) => console.log('Gelöscht:', e)} deleteItem={(e) => console.log('Gelöscht:', e)}
></EditableItem> ></EditableItem>
{:else} {:else}
<span class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1" <span class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
>{item.name}</span >{item.name}</span
> >
{/if} {/if}
{#if item.size} {#if item.size}
<p class="mt-1 truncate text-xs leading-5 text-gray-500"> <p class="mt-1 truncate text-xs leading-5 text-gray-500">
{shortenFileSize(item.size)} {shortenFileSize(item.size)}
</p>
{/if}
</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>
{#if item.lastModified}
<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> </p>
{/if} {/if}
</div> </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>
{#if item.lastModified}
<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>
{/if}
</div>
</li> </li>
{/each} {/each}
</ul> </ul>

View File

@@ -1,24 +1,83 @@
import { client } from '$lib/minio'; import { client } from '$lib/minio';
import type { RequestHandler } from '@sveltejs/kit';
import { json } from '@sveltejs/kit';
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) => { // Beispiel-Datenquelle (ersetzen durch echte DB oder Datei)
res.push(obj.name); let mockList = [ "202505-test-se", "Minas-TestVorgang", "Testing-Mina", "xyz-123" ];
});
items_str.on('error', reject); export const GET: RequestHandler = async ({ params }) => {
const { filename } = params;
items_str.on('end', async () => { // TODO: Datei lesen oder Datenbankabfrage
resolve(res); return json({ filename, list: mockList });
}); };
});
await client.removeObjects('tatort', object_list); export const PUT: RequestHandler = async ({ request, params }) => {
const { filename } = params;
const { oldName, newName } = await request.json();
return new Response(null, { status: 204 }); if (!newName || !newName.trim()) {
} return new Response('Ungültiger Name', { status: 400 });
}
const index = mockList.findIndex((name) => name === oldName);
if (index === -1) {
return new Response('Name nicht gefunden', { status: 404 });
}
if (mockList.includes(newName)) {
return new Response('Name existiert bereits', { status: 409 });
}
console.log('📥 PUT-Request empfangen:', mockList);
mockList[index] = newName;
console.log('📄 Datei:', filename);
console.log('🔁 Umbenennen:', oldName, '→', newName, mockList);
// return new Response(JSON.stringify({ success: true }), { status: 200 });
return json({ success: true, updated: newName });
};
export const DELETE: RequestHandler = async ({ request, params }) => {
const { filename } = params;
const { name } = await request.json();
const index = mockList.findIndex((n) => n === name);
if (index === -1) {
return new Response('Name nicht gefunden', { status: 404 });
}
console.log('📥 DELETE-Request empfangen:', mockList);
mockList.splice(index, 1);
console.log('📄 Datei:', filename);
console.log('🔁 gelöscht', mockList);
return json({ success: true, deleted: name });
};
// 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);
// });
// });
// await client.removeObjects('tatort', object_list);
// return new Response(null, { status: 204 });
// }