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">
import Edit from '$lib/icons/Edit.svelte';
import Trash from '$lib/icons/Trash.svelte';
import { validateInput } from '$lib/helper/error-utils';
import { tick } from 'svelte';
interface ListItem {
name: string;
token?: string;
// 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 = '';
export let variant: Variant = '';
export let existings: ListItem[];
export let id: number;
export let editable: boolean = true;
export let editing: boolean;
// Automatisch berechneter Fehler
let error: string = $derived(validateName(localName));
//CustomEvents:
export let editStart: (payload: {
id: number;
inputValue: string;
variant: Variant;
editing: boolean;
}) => void;
// Manuell steuerbarer Fehlerstatus
let manualError = $state('');
export let save: (payload: { newValue: string; oldValue: string; variant: Variant }) => void;
export let deleteItem: (payload: {
inputValue: string;
variant: Variant;
customEvent: Event;
}) => void;
export let cancel: () => void = () => {};
const existingNames = existings.map((item) => item.name);
console.log('EditableItem: Beginn', names, editedName);
export { classNames as class };
let isEditing = $state(false);
let inputRef: HTMLInputElement;
let internalValue = inputValue;
let oldValue = inputValue;
let showWarning = false;
// Validierungsfunktion
function validateName(name: string) {
const trimmed = name.trim();
$: errors = validateInput(oldValue, internalValue, { minLength: 3, existingNames });
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 (!trimmed) {
return 'Name darf nicht leer sein.';
}
if (existingNames.includes(name) && name !== oldValue.trim()) {
showWarning = true;
console.log('Abgebrochen', showWarning);
return;
}
const duplicate = list.some(
(item: ListItem) => item.name === trimmed && item.name !== currentName
);
editing = false;
showWarning = false;
save({ newValue: internalValue, oldValue, variant });
if (duplicate) return 'Name existiert bereits.';
return '';
}
function handleKey(e: KeyboardEvent) {
if (e.key === 'Enter') saveEdit(e);
if (e.key === 'Escape') cancelEdit();
// Speichern, wenn gültig und zurück an Eltern
function commitIfValid() {
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>
<div class="">
<div class="flex gap-x-2">
{#if editing}
<input
bind:value={internalValue}
on:keydown|preventDefault={handleKey}
on:blur|preventDefault|stopPropagation={cancelEdit}
class=""
class:bg-red-200={(showWarning && editing) || errors.length !== 0}
/>
{:else}
<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>
<div>
<input
bind:this={inputRef}
bind:value={localName}
onblur={commitIfValid}
onkeydown={handleKeydown}
/>
<button onclick={startEdit}><Edit /></button>
<button onclick={() => onDelete(currentName)}><Trash /></button>
{#if manualError || error}
<p style="color: red;">{manualError || error}</p>
{/if}
</div>

View File

@@ -2,17 +2,23 @@
import Folder from '$lib/icons/Folder.svelte';
import EditableItem from '$lib/components/EditableItem.svelte';
export let data;
export let editingId: number;
interface ListItem {
name: string;
token?: string;
// add other properties as needed
}
let { data } = $props();
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) {
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>
<div class="-z-10 bg-white">
@@ -52,33 +103,28 @@
<ul role="list" class="divide-y divide-gray-100">
{#each caseList as item, i}
<li>
<a href="/list/{item.name}?token={item.token}" class="flex justify-between gap-x-6 py-5">
<div class="flex gap-x-4">
<!-- Ordner -->
<div class="flex gap-x-4">
<!-- Ordner -->
<a
href="/list/{item.name}?token={item.token}"
class="bg-red-500 flex justify-between gap-x-6 py-5"
>
<Folder />
<div class="min-w-0 flex-auto">
<EditableItem
class=""
id={i}
inputValue={item.name}
editing={editingId === i}
editStart={() => (editingId = i)}
variant="casename"
existings={caseList}
save={(data) => {
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>
</a>
<div class="min-w-0 flex-auto">
<EditableItem
list={caseList}
bind:editedName={names[i]}
currentName={item.name}
onSave={handleSave}
onDelete={handleDelete}
></EditableItem>
</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>
{/each}
</ul>

View File

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

View File

@@ -1,24 +1,83 @@
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) => {
res.push(obj.name);
});
// Beispiel-Datenquelle (ersetzen durch echte DB oder Datei)
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 () => {
resolve(res);
});
});
// TODO: Datei lesen oder Datenbankabfrage
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 });
// }