f047_Edit-der-Namen #15

Closed
jared wants to merge 11 commits from f047_Edit-der-Namen into development
4 changed files with 262 additions and 183 deletions
Showing only changes of commit f335f7a13f - Show all commits

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,
mina marked this conversation as resolved Outdated
Outdated
Review

Der Kommentar könnte m.M.n. raus

Der Kommentar könnte m.M.n. raus
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
Outdated
Review

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
Outdated
Review

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
Outdated
Review

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)}
/>

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)} />
Outdated
Review

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
Outdated
Review

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...
Outdated
Review

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?
Outdated
Review

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
Outdated
Review

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>

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
Review

Man könnte einen Ordner für type anlegen src/lib/types/...

Man könnte einen Ordner für type anlegen src/lib/types/...
Review

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>

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 });
// }