f047_neu_Edit-der-Namen #28
93
src/lib/components/EditableItem.svelte
Normal file
@@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
|
|
||||
import Edit from '$lib/icons/Edit.svelte';
|
||||
import Trash from '$lib/icons/Trash.svelte';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
interface ListItem {
|
||||
name: string;
|
||||
token?: string;
|
||||
// add other properties as needed
|
||||
}
|
||||
let {
|
||||
list,
|
||||
editedName = $bindable(),
|
||||
currentName,
|
||||
onSave = () => {},
|
||||
onDelete = () => {}
|
||||
} = $props();
|
||||
|
||||
let names = list.map((l: ListItem) => l.name);
|
||||
let localName = $state(currentName);
|
||||
let wasCancelled = $state(false);
|
||||
|
||||
let error: string = $derived(validateName(localName));
|
||||
|
||||
|
mina marked this conversation as resolved
Outdated
jared
commented
manualError wird m. E. nicht im code gesetzt. Also es wird nie ein Error definiert, oder? manualError wird m. E. nicht im code gesetzt. Also es wird nie ein Error definiert, oder?
mina
commented
Habe ich rausgenommen Habe ich rausgenommen
|
||||
let manualError = $state('');
|
||||
|
||||
let isEditing = $state(false);
|
||||
let inputRef: HTMLInputElement;
|
||||
|
||||
function validateName(name: string) {
|
||||
const trimmed = name.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
return 'Name darf nicht leer sein.';
|
||||
}
|
||||
|
||||
const duplicate = list.some(
|
||||
(item: ListItem) => item.name === trimmed && item.name !== currentName
|
||||
);
|
||||
|
||||
if (duplicate) return 'Name existiert bereits.';
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function commitIfValid() {
|
||||
if (!error && !wasCancelled && localName != currentName) {
|
||||
|
jared
commented
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.
mina
commented
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
|
||||
editedName = localName.trim();
|
||||
onSave(editedName, currentName);
|
||||
} else {
|
||||
localName = currentName;
|
||||
resetEdit();
|
||||
}
|
||||
}
|
||||
|
||||
function resetEdit() {
|
||||
wasCancelled = false;
|
||||
manualError = '';
|
||||
inputRef?.blur();
|
||||
isEditing = false;
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
commitIfValid();
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
localName = currentName;
|
||||
resetEdit();
|
||||
}
|
||||
}
|
||||
|
||||
async function startEdit() {
|
||||
isEditing = true;
|
||||
await tick();
|
||||
inputRef?.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
<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>
|
||||
@@ -1,17 +1,28 @@
|
||||
import { getVorgangByToken, getCrimesListByToken } from '$lib/server/vorgangService';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, url }) => {
|
||||
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 vorgang = getVorgangByToken(vorgangToken); //einzelner Vorgang
|
||||
const vorgangObjekt = getVorgangByToken(vorgangToken); //einzelner Vorgang //TypeScript darf nicht undefined sein
|
||||
let vorgangName:string;
|
||||
if(vorgangObjekt){
|
||||
vorgangName = vorgangObjekt.name;
|
||||
}else{
|
||||
vorgangName = '';
|
||||
}
|
||||
|
||||
return {
|
||||
crimesList,
|
||||
vorgangPIN,
|
||||
vorgang
|
||||
crimesList, //crimesList
|
||||
vorgangPIN, //caseToken
|
||||
vorgangName, //caseId?
|
||||
vorgangObjekt,
|
||||
user
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
|
jared
commented
Wie du beschrieben hast, unnötige Kommentare und console.logs raus Wie du beschrieben hast, unnötige Kommentare und console.logs raus
mina
commented
Sollte soweit passen, ansonsten bitte selbstständig rauslöschen Sollte soweit passen, ansonsten bitte selbstständig rauslöschen
|
||||
import shortenFileSize from '$lib/helper/shortenFileSize';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import timeElapsed from '$lib/helper/timeElapsed';
|
||||
|
||||
import Alert from '$lib/components/Alert.svelte';
|
||||
@@ -11,14 +9,16 @@
|
||||
import ModalContent from '$lib/components/Modal/ModalContent.svelte';
|
||||
import ModalFooter from '$lib/components/Modal/ModalFooter.svelte';
|
||||
import Cube from '$lib/icons/Cube.svelte';
|
||||
import Edit from '$lib/icons/Edit.svelte';
|
||||
import Trash from '$lib/icons/Trash.svelte';
|
||||
|
||||
/** export let data; */
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
import EditableItem from '$lib/components/EditableItem.svelte';
|
||||
|
||||
console.log(data);
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
//Seite für die Tatort-Liste
|
||||
let { data } = $props();
|
||||
|
||||
console.log('tatorte: debug ', data);
|
||||
|
||||
interface ListItem {
|
||||
name: string;
|
||||
@@ -28,264 +28,378 @@
|
||||
// add other properties as needed
|
||||
}
|
||||
|
||||
const vorgang = data.vorgang;
|
||||
const crimesList: ListItem[] = data.crimesList;
|
||||
const vorgangPIN: string = data.vorgangPIN;
|
||||
|
||||
let open = false;
|
||||
$: open;
|
||||
let inProgress = false;
|
||||
$: inProgress;
|
||||
let err = false;
|
||||
$: err;
|
||||
|
||||
let rename_input;
|
||||
$: rename_input;
|
||||
let vorgang: string = data.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;
|
||||
|
||||
//Variablen für Modal
|
||||
let open = $state(false);
|
||||
let inProgress = $state(false);
|
||||
|
mina marked this conversation as resolved
Outdated
jared
commented
In Progress wird nie gesetzt. Daher bisher keine Funktion In Progress wird nie gesetzt. Daher bisher keine Funktion
mina
commented
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;
|
||||
}
|
||||
|
||||
function defocus_element(i: number) {
|
||||
let item = crimesList[i];
|
||||
let text_field_id = `label__${item.name}`;
|
||||
//Variabeln für EditableItem
|
||||
let names: string[] = $state(crimesList.map((l) => l.name));
|
||||
let editedName: string = $state('');
|
||||
let currentName: string = $state('');
|
||||
|
||||
let text_field = document.getElementById(text_field_id);
|
||||
if (text_field) {
|
||||
text_field.setAttribute('contenteditable', 'false');
|
||||
text_field.textContent = item.name;
|
||||
}
|
||||
let editingId: number;
|
||||
|
mina marked this conversation as resolved
Outdated
jared
commented
Funktioniert es denn jetzt??? Funktioniert es denn jetzt???
mina
commented
Ja, funktioniert. Ja, funktioniert.
|
||||
|
||||
// reshow button
|
||||
crimesList[i].show_button = true;
|
||||
return;
|
||||
}
|
||||
// let admin = data?.user?.admin;
|
||||
let admin = true;
|
||||
|
||||
async function handleEditFieldInput(ev: KeyboardEvent, listItemIndex: number) {
|
||||
let item = crimesList[listItemIndex];
|
||||
if (ev.key == 'Escape') {
|
||||
let text_field_id = `label__${item.name}`;
|
||||
let rename_input;
|
||||
|
||||
let text_field = document.getElementById(text_field_id);
|
||||
if (text_field) {
|
||||
text_field.setAttribute('contenteditable', 'false');
|
||||
text_field.textContent = item.name;
|
||||
}
|
||||
$effect(() => {
|
||||
console.log('rename_input hat sich geändert:', rename_input);
|
||||
});
|
||||
|
||||
// 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 || ''
|
||||
: '';
|
||||
async function handleSave(newName: string, oldName: string) {
|
||||
console.log('Eltern, speichern erfolgreich', newName, oldName);
|
||||
try {
|
||||
const res = await fetch(`/api/list/${vorgang}/${oldName}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ vorgang, oldName, newName })
|
||||
});
|
||||
const clone = res.clone();
|
||||
const data = await res.json();
|
||||
let message = data.message;
|
||||
let error = !data.success;
|
||||
|
||||
if (new_name == '') {
|
||||
alert('Bitte einen gültigen Namen eingeben.');
|
||||
ev.preventDefault();
|
||||
return;
|
||||
}
|
||||
console.log('Tatort Update: ', message);
|
||||
|
||||
// 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}`);
|
||||
if (!res.ok) {
|
||||
const msg = await clone.text();
|
||||
console.error('❌ Fehler beim Speichern:', msg);
|
||||
} else {
|
||||
uploadSuccessful();
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
console.log('✅ Erfolgreich gespeichert:', newName);
|
||||
await invalidate('');
|
||||
currentName = newName;
|
||||
}
|
||||
|
||||
// --- upload finished ---
|
||||
|
||||
return;
|
||||
} catch (err) {
|
||||
console.error('⚠️ Netzwerkfehler:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function constructMailToLink() {
|
||||
const subject = 'Link zum Tatvorgang';
|
||||
const link = $page.url.toString().split('?')[0];
|
||||
const body = `Hallo,
|
||||
async function handleDelete(tatort: string) {
|
||||
let url = new URL($page.url);
|
||||
url.pathname += `/${tatort}`;
|
||||
console.log('Delete tatort: ', `/api${url.pathname}`, url.pathname);
|
||||
|
||||
hier ist der Link zum Tatvorgang:
|
||||
${link}
|
||||
try {
|
||||
const res = await fetch(`/api${url.pathname}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ vorgang, tatort })
|
||||
});
|
||||
|
||||
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;
|
||||
if (!res.ok) {
|
||||
const msg = await res.text();
|
||||
console.error('❌ Fehler beim Löschen:', msg);
|
||||
} else {
|
||||
console.log('🗑️ Erfolgreich gelöscht:', url.pathname);
|
||||
}
|
||||
} 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}`;
|
||||
|
||||
// let text_field = document.getElementById(text_field_id);
|
||||
// if (text_field) {
|
||||
// text_field.setAttribute('contenteditable', 'false');
|
||||
// text_field.textContent = item.name;
|
||||
// }
|
||||
|
||||
// // reshow button
|
||||
// crimesList[i].show_button = true;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// async function handleEditFieldInput(ev: KeyboardEvent, listItemIndex: number) {
|
||||
// let item = crimesList[listItemIndex];
|
||||
// if (ev.key == 'Escape') {
|
||||
// let text_field_id = `label__${item.name}`;
|
||||
|
||||
// let text_field = document.getElementById(text_field_id);
|
||||
// if (text_field) {
|
||||
// text_field.setAttribute('contenteditable', 'false');
|
||||
// text_field.textContent = item.name;
|
||||
// }
|
||||
|
||||
// // 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 || ''
|
||||
// : '';
|
||||
|
||||
// if (new_name == '') {
|
||||
// alert('Bitte einen gültigen Namen eingeben.');
|
||||
// ev.preventDefault();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // 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;
|
||||
// }
|
||||
</script>
|
||||
|
||||
<div class="-z-10 bg-white">
|
||||
<div class="flex flex-col items-center justify-center w-full">
|
||||
<h1 class="text-xl">Vorgang {vorgang.name}</h1>
|
||||
{#if data?.user?.admin}
|
||||
Zugangs-PIN: {vorgang.pin}
|
||||
<a 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 vorgang}
|
||||
<div class="-z-10 bg-white">
|
||||
<div class="flex flex-col items-center justify-center w-full">
|
||||
<h1 class="text-xl">Vorgang {vorgang}</h1>
|
||||
|
||||
{#if item.show_button}
|
||||
<!-- {#if data?.user?.admin}
|
||||
Zugangs-PIN: {vorgang.pin}
|
||||
<a 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="edit__{item.name}"
|
||||
aria-label="Datei umbenennen"
|
||||
on:click|preventDefault={(ev) => {
|
||||
let text_field_id = `label__${item.name}`;
|
||||
id="del__{item.name}"
|
||||
on:click|preventDefault={async (ev) => {
|
||||
let delete_item = window.confirm('Bist du sicher?');
|
||||
|
||||
let text_field = document.getElementById(text_field_id);
|
||||
if (text_field) {
|
||||
text_field.setAttribute('contenteditable', 'true');
|
||||
text_field.focus();
|
||||
text_field.textContent = '';
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hide button
|
||||
item.show_button = false;
|
||||
}}
|
||||
aria-label="Datei löschen"
|
||||
>
|
||||
<Edit />
|
||||
<Trash />
|
||||
</button>
|
||||
{:else}
|
||||
<span class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
|
||||
>{item.name}</span
|
||||
>
|
||||
{/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"
|
||||
<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
|
||||
>
|
||||
<Trash />
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</li> -->
|
||||
|
||||
<li>
|
||||
<div class=" flex gap-x-4">
|
||||
<a
|
||||
href="/view/{$page.params.vorgang}/{currentName}?pin={vorgangPIN}"
|
||||
class=" flex justify-between gap-x-6 py-5"
|
||||
aria-label="zum 3D-modell"
|
||||
>
|
||||
<Cube />
|
||||
</a>
|
||||
<div class="min-w-0 flex-auto">
|
||||
{#if admin}
|
||||
<EditableItem
|
||||
list={crimesList}
|
||||
bind:editedName={names[crimeListItemIndex]}
|
||||
currentName={item.name}
|
||||
onSave={handleSave}
|
||||
onDelete={handleDelete}
|
||||
></EditableItem>
|
||||
{: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>
|
||||
{#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>
|
||||
<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 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>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Modal {open}
|
||||
><ModalTitle>Umbenennen</ModalTitle><ModalContent>
|
||||
{#if inProgress}
|
||||
<p class="py-2 mb-1">Vorgang läuft...</p>
|
||||
{/if}
|
||||
{#if err}
|
||||
<Alert class="w-full" type="error">Fehler beim Umbenennen</Alert>
|
||||
{/if}
|
||||
</ModalContent>
|
||||
<ModalFooter><Button disabled={inProgress} on:click={uploadSuccessful}>Ok</Button></ModalFooter>
|
||||
</Modal>
|
||||
</div>
|
||||
<Modal {open}
|
||||
><ModalTitle>Umbenennen</ModalTitle><ModalContent>
|
||||
{#if inProgress}
|
||||
<p class="py-2 mb-1">Vorgang läuft...</p>
|
||||
{/if}
|
||||
{#if err}
|
||||
<Alert class="w-full" type="error">Fehler beim Umbenennen</Alert>
|
||||
{/if}
|
||||
</ModalContent>
|
||||
<ModalFooter
|
||||
><Button disabled={inProgress} on:click={uploadSuccessful}>Ok</Button></ModalFooter
|
||||
>
|
||||
</Modal>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
ul {
|
||||
|
||||
10
src/routes/api/user/+server.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
//Rollenabfrage ob Benutzer admin ist
|
||||
|
||||
import { json } from "@sveltejs/kit";
|
||||
|
||||
export async function GET({locals}) {
|
||||
const isAdmin = locals.user?.admin === true;
|
||||
const data = {admin: isAdmin}
|
||||
return json(data)
|
||||
|
||||
}
|
||||
Die Componente heißt EditableItem. Das suggeriert, dass damit unterschieldiche Items editierbar sind, also generisch. In meinem Verständins ist das aber eine Componente zugeschnitten für das Editieren des Namens
Habe es umbenannt in NameItemEditor, falls besserer Name bitte eigenständig umbenennen, es wird nur einmal verwendet.