f047_Edit-der-Namen #15
@@ -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,
|
||||||
|
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 = '';
|
// Automatisch berechneter Fehler
|
||||||
export let variant: Variant = '';
|
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
|
|||||||
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);
|
||||||
|
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.
|
|||||||
export let deleteItem: (payload: {
|
|
||||||
inputValue: string;
|
|
||||||
variant: Variant;
|
|
||||||
customEvent: Event;
|
|
||||||
}) => void;
|
|
||||||
export let cancel: () => void = () => {};
|
|
||||||
const existingNames = existings.map((item) => item.name);
|
|
||||||
|
|
||||||
|
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;
|
// Validierungsfunktion
|
||||||
let oldValue = inputValue;
|
function validateName(name: string) {
|
||||||
let showWarning = false;
|
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 });
|
if (!trimmed) {
|
||||||
|
return 'Name darf nicht leer sein.';
|
||||||
function startEdit() {
|
|
||||||
oldValue = inputValue;
|
|
||||||
internalValue = inputValue;
|
|
||||||
editing = true;
|
|
||||||
editStart({ id, inputValue, variant, editing });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelEdit() {
|
const duplicate = list.some(
|
||||||
internalValue = oldValue;
|
(item: ListItem) => item.name === trimmed && item.name !== currentName
|
||||||
editing = false;
|
);
|
||||||
showWarning = false;
|
|
||||||
|
|
||||||
console.log('Abgebrochen');
|
if (duplicate) return 'Name existiert bereits.';
|
||||||
|
|
||||||
cancel();
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveEdit(ev?: MouseEvent | KeyboardEvent) {
|
// Speichern, wenn gültig und zurück an Eltern
|
||||||
ev?.preventDefault();
|
function commitIfValid() {
|
||||||
ev?.stopPropagation();
|
if (!validateName(localName) && !wasCancelled && localName != currentName) {
|
||||||
|
editedName = localName.trim();
|
||||||
const name = internalValue.trim();
|
onSave(editedName, currentName); //Eltern benachrichtigen
|
||||||
|
} else {
|
||||||
if (errors.length !== 0) {
|
resetEdit();
|
||||||
showWarning = true;
|
|
||||||
console.log('Abgebrochen', errors);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (!name) {
|
|
||||||
showWarning = true;
|
|
||||||
console.log('Abgebrochen', showWarning);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingNames.includes(name) && name !== oldValue.trim()) {
|
// Abbrechen – Eingabe zurücksetzen
|
||||||
showWarning = true;
|
function resetEdit() {
|
||||||
console.log('Abgebrochen', showWarning);
|
wasCancelled = false;
|
||||||
return;
|
manualError = '';
|
||||||
|
inputRef?.blur();
|
||||||
|
isEditing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
editing = false;
|
// Tastatursteuerung
|
||||||
showWarning = false;
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
save({ newValue: internalValue, oldValue, variant });
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
commitIfValid();
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
editedName = currentName;
|
||||||
|
resetEdit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKey(e: KeyboardEvent) {
|
async function startEdit() {
|
||||||
if (e.key === 'Enter') saveEdit(e);
|
isEditing = true;
|
||||||
if (e.key === 'Escape') cancelEdit();
|
await tick();
|
||||||
|
inputRef?.focus();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="">
|
<div>
|
||||||
<div class="flex gap-x-2">
|
|
||||||
{#if editing}
|
|
||||||
<input
|
<input
|
||||||
bind:value={internalValue}
|
bind:this={inputRef}
|
||||||
on:keydown|preventDefault={handleKey}
|
bind:value={localName}
|
||||||
on:blur|preventDefault|stopPropagation={cancelEdit}
|
onblur={commitIfValid}
|
||||||
class=""
|
onkeydown={handleKeydown}
|
||||||
class:bg-red-200={(showWarning && editing) || errors.length !== 0}
|
|
||||||
/>
|
/>
|
||||||
{:else}
|
<button onclick={startEdit}><Edit /></button>
|
||||||
<span>{inputValue}</span>
|
<button onclick={() => onDelete(currentName)}><Trash /></button>
|
||||||
{#if !editing && editable}
|
{#if manualError || error}
|
||||||
<button on:click|preventDefault|stopPropagation={startEdit}>
|
<p style="color: red;">{manualError || error}</p>
|
||||||
<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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
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;
|
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 />
|
||||||
|
</a>
|
||||||
|
|
||||||
<div class="min-w-0 flex-auto">
|
<div class="min-w-0 flex-auto">
|
||||||
<EditableItem
|
<EditableItem
|
||||||
class=""
|
list={caseList}
|
||||||
id={i}
|
bind:editedName={names[i]}
|
||||||
inputValue={item.name}
|
currentName={item.name}
|
||||||
editing={editingId === i}
|
onSave={handleSave}
|
||||||
editStart={() => (editingId = i)}
|
onDelete={handleDelete}
|
||||||
variant="casename"
|
|
||||||
existings={caseList}
|
|
||||||
save={(data) => {
|
|
||||||
console.log('Gespeichert:', data);
|
|
||||||
}}
|
|
||||||
deleteItem={(data) => {
|
|
||||||
console.log('Gelöscht:', data);
|
|
||||||
delete_item(data.customEvent);
|
|
||||||
}}
|
|
||||||
></EditableItem>
|
></EditableItem>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden sm:flex sm:flex-col sm:items-end">
|
<div class="hidden sm:flex sm:flex-col sm:items-end">
|
||||||
<p class="text-sm leading-6 text-gray-900">Vorgang</p>
|
<p class="text-sm leading-6 text-gray-900">Vorgang</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -122,17 +122,17 @@
|
|||||||
<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>
|
||||||
|
<div class=" flex gap-x-4">
|
||||||
<a
|
<a
|
||||||
href="/view/{$page.params.vorgang}/{item.name}?token={token}"
|
href="/view/{$page.params.vorgang}/{item.name}?token={token}"
|
||||||
class=" flex justify-between gap-x-6 py-5"
|
class=" flex justify-between gap-x-6 py-5"
|
||||||
aria-label="zum 3D-modell"
|
aria-label="zum 3D-modell"
|
||||||
>
|
>
|
||||||
<div class=" flex gap-x-4">
|
|
||||||
<Cube />
|
<Cube />
|
||||||
|
</a>
|
||||||
<div class="min-w-0 flex-auto">
|
<div class="min-w-0 flex-auto">
|
||||||
{#if admin}
|
{#if admin}
|
||||||
<EditableItem
|
<EditableItem
|
||||||
class="bg-lred-500"
|
|
||||||
id={i}
|
id={i}
|
||||||
inputValue={item.name}
|
inputValue={item.name}
|
||||||
editing={editingId === i}
|
editing={editingId === i}
|
||||||
@@ -164,7 +164,6 @@
|
|||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
// }
|
||||||
|
|||||||
Der Kommentar könnte m.M.n. raus