f047_neu_Edit-der-Namen #28

Merged
trachi93 merged 14 commits from f047_neu_Edit-der-Namen into development 2025-08-19 09:30:13 +02:00
9 changed files with 116 additions and 360 deletions
Showing only changes of commit 7bdfa1f69b - Show all commits

View File

@@ -1,3 +1,31 @@
<script lang="ts">
export let href = null;
export let type = 'button';
mina marked this conversation as resolved Outdated
Outdated
Review

type hinzufügen
export let type: 'button' | 'submit' | 'reset' = 'button';

type hinzufügen export let type: 'button' | 'submit' | 'reset' = 'button';
export let size = 'md';
export let variant = 'primary';
export let fullWidth = false;
export let align = 'center';
export let disabled = false;
let classNames = '';
export { classNames as class };
</script>
{#if href}
<a on:click {href} class:w-full={fullWidth} class="button {variant} {size} {classNames} {align}"
><slot />
</a>
{:else}
<button
on:click
{type}
{disabled}
class:w-full={fullWidth}
class="button {variant} {size} {classNames} {align}"
>
<slot />
</button>
{/if}
<style>
.button {
@apply inline-flex;
@@ -172,31 +200,3 @@
@apply justify-end;
}
</style>
<script lang="ts">
export let href = null;
export let type = 'button';
export let size = 'md';
export let variant = 'primary';
export let fullWidth = false;
export let align = 'center';
export let disabled = false;
let classNames = '';
export { classNames as class };
</script>
{#if href}
<a on:click {href} class:w-full={fullWidth} class="button {variant} {size} {classNames} {align}"
><slot />
</a>
{:else}
<button
on:click
{type}
{disabled}
class:w-full={fullWidth}
class="button {variant} {size} {classNames} {align}"
>
<slot />
</button>
{/if}

View File

@@ -16,7 +16,6 @@
onDelete = () => {}
} = $props();
let names = list.map((l: ListItem) => l.name);
let localName = $state(currentName);
let wasCancelled = $state(false);
@@ -46,6 +45,8 @@
function commitIfValid() {
if (!error && !wasCancelled && localName != currentName) {
editedName = localName.trim();
Outdated
Review

Die Funktion commitIfValid setzt editedName = localName.trim();, aber editedName ist ein Prop und sollte nicht direkt überschrieben werden. Stattdessen sollte ein Event ausgelöst werden oder ein Callback genutzt werden.

Die Funktion commitIfValid setzt editedName = localName.trim();, aber editedName ist ein Prop und sollte nicht direkt überschrieben werden. Stattdessen sollte ein Event ausgelöst werden oder ein Callback genutzt werden.
Outdated
Review

Ich habe es nun eine neue Variable und dann in onSave übergeben, dabei ist mir die Frage gekommen wie sieht die Abfrage mit Leerzeichen insgesamt aus, habe es als offene Frage ins Backlog geschrieben

Ich habe es nun eine neue Variable und dann in onSave übergeben, dabei ist mir die Frage gekommen wie sieht die Abfrage mit Leerzeichen insgesamt aus, habe es als offene Frage ins Backlog geschrieben
inputRef?.blur();
isEditing = false;
onSave(editedName, currentName);
} else {
localName = currentName;

View File

@@ -1,3 +1,3 @@
import { readFileSync } from 'fs';
export default JSON.parse(readFileSync('./config.json').toString());
export default JSON.parse(readFileSync('./config_prod.json').toString());
mina marked this conversation as resolved Outdated
Outdated
Review

Stimmt das wirklich? Ich glaube, dass ist nicht korrekt es sollte config.json bleiben, da der Container auch eine config.json anlegt

Stimmt das wirklich? Ich glaube, dass ist nicht korrekt es sollte config.json bleiben, da der Container auch eine config.json anlegt

Binary file not shown.

View File

@@ -1,31 +0,0 @@
import { getVorgangByToken, getCrimesListByToken } from '$lib/server/vorgangService';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({fetch, params, url }) => {
const vorgangToken = params.vorgang;
const vorgangPIN = url.searchParams.get('pin');
const adminRes = await fetch(`/api/user`)
const user = await adminRes.json()
const crimesList = await getCrimesListByToken(vorgangToken); // TatortList zum Vorgang
const vorgangObjekt = getVorgangByToken(vorgangToken); //einzelner Vorgang //TypeScript darf nicht undefined sein
let vorgangName:string;
if(vorgangObjekt){
vorgangName = vorgangObjekt.name;
}else{
vorgangName = '';
}
return {
crimesList, //crimesList
vorgangPIN, //caseToken
vorgangName, //caseId?
vorgangObjekt,
user
};
};
// Ersetze diese Verbindung zum Server durch API Endpunkte

View File

@@ -9,77 +9,59 @@
import ModalContent from '$lib/components/Modal/ModalContent.svelte';
import ModalFooter from '$lib/components/Modal/ModalFooter.svelte';
import Cube from '$lib/icons/Cube.svelte';
import EditableItem from '$lib/components/EditableItem.svelte';
import { invalidate } from '$app/navigation';
import { page } from '$app/stores';
import { invalidate, invalidateAll } from '$app/navigation';
//Seite für die Tatort-Liste
let { data } = $props();
console.log('tatorte: debug ', data);
console.log('tatorte: debug ', data); //zur besseren Nachvollziehbarkeit noch drin gelassen, kann vorm merge gelöscht werden
interface ListItem {
//sollte Typ Vorgang sein, aber der einfachheit ist es noch ListItem, damit die Komponente EditableItem für Vorgang und Tatort eingesetzt werden kann
name: string;
size: number;
lastModified: string | number | Date;
show_button?: boolean;
prefix?: string;
// add other properties as needed
}
let vorgang: string = data.vorgangName; // let vorgang = data.caseId; <!--caseId, vorgang.name aber das funktioniert nicht richtig, daher in server.ts geändert-->
let vorgangName: string = data.vorgang.vorgangName; // let vorgang = data.caseId; <!--caseId, vorgang.name aber das funktioniert nicht richtig, daher in server.ts geändert-->
let crimesList: ListItem[] = $state(data.crimesList);
const vorgangPIN: string = data.vorgangPIN; //caseToken?? // const token: string | null = data.caseToken;
const vorgangPIN: string = data.vorgang.vorgangPIN; //caseToken?? // const token: string | null = data.caseToken;
let vorgangToken: string = data.vorgang.vorgangToken;
//Variablen für Modal
let open = $state(false);
let inProgress = $state(false);
mina marked this conversation as resolved Outdated
Outdated
Review

In Progress wird nie gesetzt. Daher bisher keine Funktion

In Progress wird nie gesetzt. Daher bisher keine Funktion
Outdated
Review

Da es zum Modal gehört habe ich es angepasst und nicht gelöscht.

Da es zum Modal gehört habe ich es angepasst und nicht gelöscht.
let err = $state(false);
function uploadSuccessful() {
open = false;
}
//Variabeln für EditableItem
let names: string[] = $state(crimesList.map((l) => l.name));
let editedName: string = $state('');
let currentName: string = $state('');
let editingId: number;
// let admin = data?.user?.admin;
let admin = true;
let rename_input;
$effect(() => {
console.log('rename_input hat sich geändert:', rename_input);
});
//Variable um nur admin UI anzuzeigen
let admin = data?.user?.admin;
async function handleSave(newName: string, oldName: string) {
console.log('Eltern, speichern erfolgreich', newName, oldName);
open = true;
console.log('Eltern, speichern erfolgreich', newName, oldName); //zur besseren Nachvollziehbarkeit noch drin gelassen, kann vorm merge gelöscht werden
try {
const res = await fetch(`/api/list/${vorgang}/${oldName}`, {
const res = await fetch(`/api/list/${vorgangToken}/${oldName}`, {
//irgendwas stimmt hier nicht, vorgangToken führt zu Fehler in API,
mina marked this conversation as resolved Outdated
Outdated
Review

Funktioniert es denn jetzt???

Funktioniert es denn jetzt???
Outdated
Review

Ja, funktioniert.

Ja, funktioniert.
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ vorgang, oldName, newName })
body: JSON.stringify({ vorgangToken, oldName, newName })
});
const clone = res.clone();
const data = await res.json();
let message = data.message;
let error = !data.success;
console.log('Tatort Update: ', message);
if (!res.ok) {
const msg = await clone.text();
const msg = await res.text();
console.error('❌ Fehler beim Speichern:', msg);
} else {
console.log('✅ Erfolgreich gespeichert:', newName);
await invalidate('');
currentName = newName;
await invalidate(`/api/list/${vorgangToken}`);
await invalidateAll();
crimesList = data.crimesList;
console.log('✅ Erfolgreich gespeichert:', crimesList, data.crimesList, newName); //zur besseren Nachvollziehbarkeit noch drin gelassen, kann vorm merge gelöscht werden
open = false;
}
} catch (err) {
console.error('⚠️ Netzwerkfehler:', err);
@@ -87,7 +69,7 @@
}
async function handleDelete(tatort: string) {
let url = new URL($page.url);
let url = new URL(data.url);
url.pathname += `/${tatort}`;
console.log('Delete tatort: ', `/api${url.pathname}`, url.pathname);
@@ -97,7 +79,7 @@
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ vorgang, tatort })
body: JSON.stringify({ vorgangToken, tatort })
});
if (!res.ok) {
@@ -105,255 +87,67 @@
console.error('❌ Fehler beim Löschen:', msg);
} else {
console.log('🗑️ Erfolgreich gelöscht:', url.pathname);
await invalidate(`/api/list/${vorgangToken}`);
await invalidateAll();
crimesList = data.crimesList;
}
} catch (err) {
console.error('⚠️ Netzwerkfehler beim Löschen:', err);
}
}
// function defocus_element(i: number) {
// let item = crimesList[i];
// let text_field_id = `label__${item.name}`;
function constructMailToLink() {
const subject = 'Link zum Tatvorgang';
// let text_field = document.getElementById(text_field_id);
// if (text_field) {
// text_field.setAttribute('contenteditable', 'false');
// text_field.textContent = item.name;
// }
const link = data.url.origin + data.url.pathname;
const body = `Hallo,
// // reshow button
// crimesList[i].show_button = true;
// return;
// }
hier ist der Link zum Tatvorgang:
${link}
// async function handleEditFieldInput(ev: KeyboardEvent, listItemIndex: number) {
// let item = crimesList[listItemIndex];
// if (ev.key == 'Escape') {
// let text_field_id = `label__${item.name}`;
Der Zugangs-PIN wird zur Sicherheit über einen zweiten Kommunikationskanal übermittelt.
// let text_field = document.getElementById(text_field_id);
// if (text_field) {
// text_field.setAttribute('contenteditable', 'false');
// text_field.textContent = item.name;
// }
Mit freundlichen Grüßen,
`;
// // reshow button
// item.show_button = true;
// return;
// }
// if (ev.key == 'Enter') {
// let name_field = ev.currentTarget as HTMLElement | null;
// let new_name = name_field
// ? name_field.textContent || (name_field as any).innerText || ''
// : '';
const mailtoLink = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
// if (new_name == '') {
// alert('Bitte einen gültigen Namen eingeben.');
// ev.preventDefault();
// return;
// }
return mailtoLink;
}
// // actual upload
// // -------------
// // to prevent from item being selected
// ev.preventDefault();
// // construct PUT URL
// const url = $page.url.pathname.split('?')[0];
// let data_obj: { new_name: string; old_name: string } = { new_name: '', old_name: '' };
// data_obj['new_name'] = new_name;
// data_obj['old_name'] =
// ev.currentTarget && (ev.currentTarget as HTMLElement).id
// ? (ev.currentTarget as HTMLElement).id.split('__')[1]
// : '';
// open = true;
// inProgress = true;
// const response = await fetch(url, { method: 'PUT', body: JSON.stringify(data_obj) });
// inProgress = false;
// if (!response.ok) {
// err = true;
// if (response.status == 400) {
// let json_res = await response.json();
// return;
// }
// throw new Error(`Fehlgeschlagen: ${response.status}`);
// } else {
// uploadSuccessful();
// setTimeout(() => {
// window.location.reload();
// }, 500);
// }
// // --- upload finished ---
// return;
// }
// }
// function constructMailToLink() {
// const subject = 'Link zum Tatvorgang';
// const link = $page.url.toString().split('?')[0];
// const body = `Hallo,
// hier ist der Link zum Tatvorgang:
// ${link}
// Der Zugangs-PIN wird zur Sicherheit über einen zweiten Kommunikationskanal übermittelt.
// Mit freundlichen Grüßen,
// `;
// const mailtoLink = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
// return mailtoLink;
// }
function closeModal() {
open = false;
}
</script>
{#if vorgang}
{#if data.vorgang && data.crimesList}
<div class="-z-10 bg-white">
<div class="flex flex-col items-center justify-center w-full">
<h1 class="text-xl">Vorgang {vorgang}</h1>
<h1 class="text-xl">Vorgang {vorgangName}</h1>
<!-- {#if data?.user?.admin}
Zugangs-PIN: {vorgang.pin}
<a href={constructMailToLink()}><Button>Share Link</Button></a>
{/if} -->
{#if admin}
Zugangs-PIN: {vorgangPIN}
<a class="pt-2 pb-6" href={constructMailToLink()}><Button>Share Link</Button></a>
{/if}
</div>
<div class="mx-auto flex justify-center max-w-7xl h-full">
<ul class="divide-y divide-gray-100">
{#each crimesList as item, crimeListItemIndex}
<!-- <li>
<a
href="/view/{$page.params.vorgang}/{item.name}?pin={vorgangPIN}"
class=" flex justify-between gap-x-6 py-5"
aria-label="zum 3D-modell"
>
<div class=" flex gap-x-4">
<Cube />
<div class="min-w-0 flex-auto">
{#if data?.user?.admin}
<span
id="label__{item.name}"
class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
contenteditable={!item.show_button}
role="textbox"
tabindex="0"
aria-label="Dateiname bearbeiten"
on:focusout={() => {
defocus_element(crimeListItemIndex);
}}
on:keydown|stopPropagation={// event needed to identify ID
// TO-DO: check if event is needed or if index is sufficient
async (ev) => {
handleEditFieldInput(ev, crimeListItemIndex);
}}>{item.name}</span
>
{#if item.show_button}
<button
style="padding: 2px"
id="edit__{item.name}"
aria-label="Datei umbenennen"
on:click|preventDefault={(ev) => {
let text_field_id = `label__${item.name}`;
let text_field = document.getElementById(text_field_id);
if (text_field) {
text_field.setAttribute('contenteditable', 'true');
text_field.focus();
text_field.textContent = '';
}
// hide button
item.show_button = false;
}}
>
<Edit />
</button>
{/if}
<button
style="padding: 2px"
id="del__{item.name}"
on:click|preventDefault={async (ev) => {
let delete_item = window.confirm('Bist du sicher?');
if (delete_item) {
// bucket: tatort, name: <vorgang>/item-name
let vorgang = $page.params.vorgang;
let filename = '';
if (ev && ev.currentTarget && (ev.currentTarget as HTMLElement).id) {
filename = (ev.currentTarget as HTMLElement).id.split('del__')[1];
}
// delete request
// --------------
let url = new URL($page.url);
url.pathname += `/${filename}`;
try {
const response = await fetch(`/api${url.pathname}`, {
method: 'DELETE'
});
if (response.status == 204) {
setTimeout(() => {
window.location.reload();
}, 500);
}
} catch (error) {
if (error instanceof Error) {
console.log(error.message);
} else {
console.log(error);
}
}
}
}}
aria-label="Datei löschen"
>
<Trash />
</button>
{:else}
<span class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
>{item.name}</span
>
{/if}
<p class="mt-1 truncate text-xs leading-5 text-gray-500">
{shortenFileSize(item.size)}
</p>
</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>
<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>
</div>
</a>
</li> -->
{#each data.crimesList as item, crimeListItemIndex}
<li>
<div class=" flex gap-x-4">
<a
href="/view/{$page.params.vorgang}/{currentName}?pin={vorgangPIN}"
href="/view/{vorgangToken}/{item.name}?pin={vorgangPIN}"
class=" flex justify-between gap-x-6 py-5"
aria-label="zum 3D-modell"
aria-label="/view/{vorgangToken}/{item.name}?pin={vorgangPIN}"
title={item.name}
>
<Cube />
</a>
<div class="min-w-0 flex-auto">
{#if admin}
<EditableItem
list={crimesList}
bind:editedName={names[crimeListItemIndex]}
list={data.crimesList}
editedName={data.crimeNames[crimeListItemIndex]}
currentName={item.name}
onSave={handleSave}
onDelete={handleDelete}
@@ -394,9 +188,7 @@
<Alert class="w-full" type="error">Fehler beim Umbenennen</Alert>
{/if}
</ModalContent>
<ModalFooter
><Button disabled={inProgress} on:click={uploadSuccessful}>Ok</Button></ModalFooter
>
<ModalFooter><Button disabled={inProgress} on:click={closeModal}>Ok</Button></ModalFooter>
</Modal>
</div>
{/if}

View File

@@ -0,0 +1,27 @@
import { redirect } from '@sveltejs/kit';
export async function load({fetch, params, url}){
const vorgangResponse = await fetch(`/api/list`);
const vorgangList = await vorgangResponse.json()
const vorgangToken = params.vorgang;
const crimesListResponse = await fetch(`/api/list/${vorgangToken}`)
const crimesList = await crimesListResponse.json();
const vorgang = vorgangList.find(v => v.vorgangToken === vorgangToken); //vorgang sollte ein eigener Typ werden, und dann kann man es hier vernünftig typisieren
if(!vorgang || !crimesList){
throw new Error(`Fehlgeschlagen, es wurden keine Daten zum token gefunden`);
}
//Variabeln für EditableItem
const crimeNames: string[] = crimesList.map((l) => l.name);
if (crimesList.length === 0) {
throw redirect(302, '/upload'); // weiterleiten auf die hinzufügen seite
Review

Ok, aber warum. Erwarte ich hier nicht dann die Liste??

Ok, aber warum. Erwarte ich hier nicht dann die Liste??
Review

Ich habe die Umleitung gesetzt, wenn die Liste leer ist, also wenn keine Dateien zum Anzeigen vorhanden sind. So kann man quasi direkt eine Datei neu upload. Wenn nicht gewünscht bitte eigenständig bearbeiten

Ich habe die Umleitung gesetzt, wenn die Liste leer ist, also wenn keine Dateien zum Anzeigen vorhanden sind. So kann man quasi direkt eine Datei neu upload. Wenn nicht gewünscht bitte eigenständig bearbeiten
}
return {
vorgang,
vorgangList,
crimesList,
url,
crimeNames
}
}

View File

@@ -1,34 +0,0 @@
import { client } from '$lib/minio';
import { json } from '@sveltejs/kit';
// rename operation for crimes
export async function PUT({ request }: {request: Request}) {
const data = await request.json();
// Vorgang
const vorgangToken = request.url.split('/').at(-1);
// prepare copy, incl. check if new name exists already
const crimeOldName = data["old_name"];
const crimeS3FullBucketPathOld = `/tatort/${vorgangToken}/${crimeOldName}`;
const crimeNewName = `${vorgangToken}/${data["new_name"]}`;
try {
await client.statObject('tatort', crimeNewName);
return json({ msg: 'Die Datei existiert bereits.' }, { status: 400 });
} catch (error) {
// continue operation
console.log(error, 'continue operation');
}
// actual copy operation
await client.copyObject('tatort', crimeNewName, crimeS3FullBucketPathOld)
// delete
await client.removeObject('tatort', `${vorgangToken}/${crimeOldName}`)
// return success or failure
return json({ success: 'success' }, { status: 200 });
};

View File

@@ -1,5 +1,5 @@
import { BUCKET, client } from '$lib/minio';
import { json, type RequestHandler } from '@sveltejs/kit';
import { json } from '@sveltejs/kit';
import { getVorgangByName } from '$lib/server/vorgangService';
export async function GET() {
@@ -36,12 +36,13 @@ export async function DELETE({ request }: { request: Request }) {
}
// rename operation for crimes
export async function PUT({ params, request }: { params: RequestHandler; request: Request }) {
export async function PUT({ params, request }) {
const data = await request.json();
// Vorgang
const vorgangName = params.vorgang;
const vorgangToken = getVorgangByName(vorgangName)?.token;
const vorgangToken = params.vorgang;
//const vorgangToken = getVorgangByName(vorgangName)?.token;
// prepare copy, incl. check if new name exists already
const crimeOldName = data['oldName'];