f047_Edit-der-Namen #15
@@ -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,
|
||||
|
mina marked this conversation as resolved
Outdated
|
||||
onSave = () => {},
|
||||
onDelete = () => {}
|
||||
} = $props();
|
||||
|
||||
type Variant = '' | 'casename' | 'crimename';
|
||||
let names = list.map((l: ListItem) => l.name);
|
||||
let localName = $state(currentName);
|
||||
|
mina marked this conversation as resolved
Outdated
jared
commented
Bitte Console.log entfernen. Bitte Console.log entfernen.
|
||||
//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));
|
||||
|
mina marked this conversation as resolved
Outdated
jared
commented
Typo editSart to editStart Typo editSart to editS**t**art
|
||||
|
||||
//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);
|
||||
|
jared marked this conversation as resolved
Outdated
jared
commented
The signature '(): EventDispatcher<{ editSart: {}; save: {}; delete: {}; cancel: void; }>' of 'createEventDispatcher' is deprecated. Mittlerweile wird ein Event mittels Callback function dispatched. Gucke ggf. noch einmal in die Docs. Zum Beispiel: <script type="ts"> const props: { onClick(): void; onExplode(name: string, age: number): void; } = $props(); </script><MyComponent The signature '(): EventDispatcher<{ editSart: {}; save: {}; delete: {}; cancel: void; }>' of 'createEventDispatcher' is deprecated.
Mittlerweile wird ein Event mittels Callback function dispatched. Gucke ggf. noch einmal in die Docs.
Zum Beispiel:
<script type="ts">
const props: {
onClick(): void;
onExplode(name: string, age: number): void;
} = $props();
</script>
<MyComponent
onClick={() => alert('clicked')}
onExplode={(name, age) => alert(name + ' ' + age)}
/>
mina
commented
Ich habe es angepasst, es musste einiges verändert werden, bitte schaue es dir nochmal an. Ich habe es angepasst, es musste einiges verändert werden, bitte schaue es dir nochmal an.
|
||||
|
||||
|
jared marked this conversation as resolved
Outdated
jared
commented
Villt die Variable noch besser bennennen. InternalValue oder oldValue, kann halt alles sein... Villt die Variable noch besser bennennen. InternalValue oder oldValue, kann halt alles sein...
mina
commented
Allgemein, weil es eine Komponente ist, die vielseitig einsetzbar. Es geht darum quasi den Startwert? zu speichern, für den Abbruch und den aktuellen Wert, um ein editieren in Gang zu bringen. Man könnte es auch cancelValue und EditValue nennen? Vielleicht besser? Allgemein, weil es eine Komponente ist, die vielseitig einsetzbar. Es geht darum quasi den Startwert? zu speichern, für den Abbruch und den aktuellen Wert, um ein editieren in Gang zu bringen. Man könnte es auch cancelValue und EditValue nennen? Vielleicht besser?
jared
commented
Naja, der Begriff Value bringt halt nicht, weil das quasi immer ein value ist. Daher so explizit bennen wie es geht Naja, der Begriff Value bringt halt nicht, weil das quasi immer ein value ist. Daher so explizit bennen wie es geht
|
||||
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();
|
||||
|
mina marked this conversation as resolved
Outdated
jared
commented
Wird im weiteren Verlauf nicht verwendet, daher löschen Wird im weiteren Verlauf nicht verwendet, daher löschen
|
||||
|
||||
$: 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>
|
||||
|
||||
@@ -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
|
||||
|
jared
commented
Man könnte einen Ordner für type anlegen src/lib/types/... Man könnte einen Ordner für type anlegen src/lib/types/...
mina
commented
s.u. s.u.
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 });
|
||||
// }
|
||||
|
||||
Der Kommentar könnte m.M.n. raus