f047_Edit-der-Namen #15
@@ -1,4 +1,15 @@
|
||||
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h1>Du wurdest automatisch ausgeloggt</h1>
|
||||
<p>Lösche deine Cookies aus dem Browser und logge dich neu ein</p>
|
||||
<p>Code %sveltekit.status%</p>
|
||||
<p>%sveltekit.error.message%</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
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,
|
||||
|
mina marked this conversation as resolved
Outdated
|
||||
onSave = () => {},
|
||||
onDelete = () => {}
|
||||
} = $props();
|
||||
|
||||
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 wasCancelled = $state(false);
|
||||
|
||||
let error: string = $derived(validateName(localName));
|
||||
|
||||
let manualError = $state('');
|
||||
|
mina marked this conversation as resolved
Outdated
jared
commented
Typo editSart to editStart Typo editSart to editS**t**art
|
||||
|
||||
let isEditing = $state(false);
|
||||
let inputRef: HTMLInputElement;
|
||||
|
||||
function validateName(name: string) {
|
||||
|
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.
|
||||
const trimmed = name.trim();
|
||||
|
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
|
||||
|
||||
if (!trimmed) {
|
||||
return 'Name darf nicht leer sein.';
|
||||
}
|
||||
|
||||
const duplicate = list.some(
|
||||
|
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
|
||||
(item: ListItem) => item.name === trimmed && item.name !== currentName
|
||||
);
|
||||
|
||||
if (duplicate) return 'Name existiert bereits.';
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function commitIfValid() {
|
||||
if (!error && !wasCancelled && localName != currentName) {
|
||||
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,3 +1,3 @@
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
export default JSON.parse(readFileSync('./config.json').toString());
|
||||
export default JSON.parse(readFileSync('./config_prod.json').toString());
|
||||
|
||||
27
src/lib/helper/error-utils.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
*
|
||||
* @param oldValue
|
||||
* @param inputValue
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
export function validateNameInput(oldValue:string, inputValue: string, options: { minLength?: number; existingNames?: string[] }) {
|
||||
const errors: string[] = [];
|
||||
|
jared
commented
Grundsätzlich wird hier der Value in diesem Fall ein Name validiert. Es ist keine generische validateInput Funktion. Daher sollte sie entsprechend benannt werden. Darüber hinaus wird sie nur in EditableItem verwendet. Daher ist ggf. ein Auslagern nicht sinnvoll, oder soll sie noch wo anders verwendet werden? Grundsätzlich wird hier der Value in diesem Fall ein Name validiert. Es ist keine generische validateInput Funktion. Daher sollte sie entsprechend benannt werden. Darüber hinaus wird sie nur in EditableItem verwendet. Daher ist ggf. ein Auslagern nicht sinnvoll, oder soll sie noch wo anders verwendet werden?
mina
commented
Werde ich nochmal überarbeiten Werde ich nochmal überarbeiten
|
||||
|
||||
if (!inputValue.trim()) errors.push('Feld darf nicht leer sein');
|
||||
if (options.existingNames?.includes(inputValue) && oldValue !== inputValue)
|
||||
errors.push('Name existiert bereits');
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
||||
// siehe suche: fail
|
||||
|
||||
// Button
|
||||
// BaseInputField
|
||||
|
||||
// fetch siehe b038
|
||||
|
||||
// api/list/vorgang/tatort/server.ts
|
||||
// api/list/vorgang/server.ts
|
||||
@@ -1,10 +1,15 @@
|
||||
<script>
|
||||
let classNames = '';
|
||||
export { classNames as class };
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class=" w-6 h-6"
|
||||
class=" w-6 h-6 {classNames}"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
|
||||
|
Before Width: | Height: | Size: 483 B After Width: | Height: | Size: 571 B |
@@ -19,7 +19,7 @@ export const checkIfExactDirectoryExists = (dir: string): Promise<boolean> => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getContentOfTextObject = async (bucket: string, objPath: string) => {
|
||||
export const getContentOfTextObject = async (bucket: string, objPath: string) => { //Was kriege ich da raus?
|
||||
const res = await client.getObject(bucket, objPath);
|
||||
|
||||
const text = await new Response(res).text();
|
||||
|
||||
@@ -2,9 +2,9 @@ import { getListOfVorgänge } from '$lib/server/vorgangService';
|
||||
import type { PageServerLoad } from '../../(token-based)/view/$types';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
const caseList = await getListOfVorgänge();
|
||||
const vorgangList = getListOfVorgänge();
|
||||
|
||||
return {
|
||||
caseList
|
||||
vorgangList
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
<script lang="ts">
|
||||
import Trash from '$lib/icons/Trash.svelte';
|
||||
import Folder from '$lib/icons/Folder.svelte';
|
||||
import type { PageData } from '../$types';
|
||||
import EditableItem from '$lib/components/EditableItem.svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
|
||||
export let data: PageData;
|
||||
interface ListItem {
|
||||
name: string;
|
||||
token?: string;
|
||||
|
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.
|
||||
// add other properties as needed
|
||||
}
|
||||
|
||||
const caseList = data.caseList;
|
||||
let { data } = $props();
|
||||
|
||||
const caseList: ListItem[] = $state(data.caseList);
|
||||
|
||||
let names: string[] = $state(caseList.map((l) => l.name));
|
||||
let editedName: string = $state('');
|
||||
let currentName: string = $state('');
|
||||
|
||||
async function delete_item(ev: Event) {
|
||||
let delete_item = window.confirm('Bist du sicher?');
|
||||
@@ -36,6 +46,51 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUpdateCase(newName: string, oldName: string) {
|
||||
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 Update:', msg);
|
||||
} else {
|
||||
console.log('✅ Erfolgreich gespeichert:', newName);
|
||||
await invalidate('');
|
||||
currentName = newName;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('⚠️ Netzwerkfehler:', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(vorgang: string) {
|
||||
let url = `/api/list/${vorgang}`;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ vorgang })
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const msg = await res.text();
|
||||
console.error('❌ Fehler beim Löschen:', msg);
|
||||
} else {
|
||||
console.log('🗑️ Erfolgreich gelöscht:', vorgang);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('⚠️ Netzwerkfehler beim Löschen:', err);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="-z-10 bg-white">
|
||||
@@ -44,29 +99,32 @@
|
||||
</div>
|
||||
<div class="mx-auto flex justify-center max-w-7xl h-full">
|
||||
<ul role="list" class="divide-y divide-gray-100">
|
||||
{#each caseList as item}
|
||||
{#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 -->
|
||||
<Folder />
|
||||
<div class="min-w-0 flex-auto">
|
||||
<span class="text-sm font-semibold leading-6 text-gray-900">{item.name}</span>
|
||||
<!-- Delete button -->
|
||||
<button
|
||||
style="padding: 2px"
|
||||
id="del__{item.name}"
|
||||
on:click|preventDefault={delete_item}
|
||||
aria-label="Vorgang {item.name} löschen"
|
||||
<a
|
||||
href="/list/{currentName}?token={item.token}"
|
||||
class=" flex justify-between gap-x-6 py-5"
|
||||
>
|
||||
<Trash />
|
||||
</button>
|
||||
</div>
|
||||
<Folder />
|
||||
</a>
|
||||
{#if data.user.admin}
|
||||
<div class="min-w-0 flex-auto">
|
||||
<EditableItem
|
||||
list={caseList}
|
||||
bind:editedName={names[i]}
|
||||
currentName={item.name}
|
||||
onSave={handleUpdateCase}
|
||||
onDelete={handleDelete}
|
||||
></EditableItem>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="hidden sm:flex sm:flex-col sm:items-end">
|
||||
<p class="text-sm leading-6 text-gray-900">Vorgang</p>
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
23
src/routes/(angemeldet)/list/+page.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
export async function load({ fetch }) {
|
||||
|
||||
//const caseToken = url.searchParams.get('token');
|
||||
const adminRes = await fetch(`/api/user`)
|
||||
const user = await adminRes.json()
|
||||
|
||||
const res = await fetch(`/api/list`, {
|
||||
/* headers: {
|
||||
Authorization: `Bearer ${caseToken}`
|
||||
} */
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
// console.log("Kay", data.caseList);
|
||||
|
||||
const caseList = data.caseList
|
||||
|
||||
return {
|
||||
caseList, //{name, token}
|
||||
user //für Abfrage ob eingeloggt
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
//Kann komplett gelöscht werden
|
||||
import Alert from '$lib/components/Alert.svelte';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Modal from '$lib/components/Modal/Modal.svelte';
|
||||
@@ -75,19 +76,14 @@
|
||||
</div>
|
||||
|
||||
<label for="code">
|
||||
<span >Zugangscode (optional) </span>
|
||||
<span>Zugangscode (optional) </span>
|
||||
</label>
|
||||
|
||||
<div class="mt-2">
|
||||
<div
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="code"
|
||||
/>
|
||||
<div>
|
||||
<input type="text" id="code" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { getVorgangByCaseId } from '$lib/server/vorgangService';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, url }) => {
|
||||
const caseId = params.vorgang;
|
||||
const caseToken = url.searchParams.get('token');
|
||||
|
||||
const crimesList = await getVorgangByCaseId(caseId);
|
||||
|
||||
return {
|
||||
crimesList,
|
||||
caseToken
|
||||
};
|
||||
};
|
||||
@@ -1,64 +1,59 @@
|
||||
<script lang="ts">
|
||||
import shortenFileSize from '$lib/helper/shortenFileSize';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import timeElapsed from '$lib/helper/timeElapsed';
|
||||
|
||||
import Alert from '$lib/components/Alert.svelte';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Modal from '$lib/components/Modal/Modal.svelte';
|
||||
import ModalTitle from '$lib/components/Modal/ModalTitle.svelte';
|
||||
import ModalContent from '$lib/components/Modal/ModalContent.svelte';
|
||||
import ModalFooter from '$lib/components/Modal/ModalFooter.svelte';
|
||||
import EditableItem from '$lib/components/EditableItem.svelte';
|
||||
import Cube from '$lib/icons/Cube.svelte';
|
||||
import Edit from '$lib/icons/Edit.svelte';
|
||||
import Trash from '$lib/icons/Trash.svelte';
|
||||
import shortenFileSize from '$lib/helper/shortenFileSize.js';
|
||||
import timeElapsed from '$lib/helper/timeElapsed.js';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
/** export let data; */
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
//Seite für die Tatort-Liste
|
||||
let { data } = $props();
|
||||
|
jared
commented
s. o. s. o.
mina
commented
Ja, das können wir mal im Daily ansprechen, ich habe ein Ticket daraus gemacht. Ja, das können wir mal im Daily ansprechen, ich habe ein Ticket daraus gemacht.
|
||||
|
||||
console.log('tatorte: debug ', data);
|
||||
|
||||
interface ListItem {
|
||||
name: string;
|
||||
size: number;
|
||||
lastModified: string | number | Date;
|
||||
size?: number;
|
||||
lastModified?: string | number | Date | undefined;
|
||||
show_button?: boolean;
|
||||
// add other properties as needed
|
||||
}
|
||||
|
||||
const crimesList: ListItem[] = data.crimesList;
|
||||
const token: string = data.caseToken;
|
||||
const crimesList: ListItem[] = $state(data.crimesList);
|
||||
const token: string | null = data.caseToken;
|
||||
let vorgang = data.caseId;
|
||||
/* export let vorgang = $page.params.vorgang; */
|
||||
|
||||
let open = false;
|
||||
$: open;
|
||||
let inProgress = false;
|
||||
$: inProgress;
|
||||
let err = false;
|
||||
$: err;
|
||||
//Variabeln für EditableItem
|
||||
let names: string[] = $state(crimesList.map((l) => l.name));
|
||||
let editedName: string = $state('');
|
||||
let currentName: string = $state('');
|
||||
let open = $state(false);
|
||||
let inProgress = $state(false);
|
||||
let err = $state(false);
|
||||
|
||||
let editingId: number;
|
||||
|
||||
// let admin = data?.user?.admin;
|
||||
let admin = true;
|
||||
|
||||
let rename_input;
|
||||
$: rename_input;
|
||||
|
||||
$effect(() => {
|
||||
console.log('rename_input hat sich geändert:', rename_input);
|
||||
});
|
||||
|
||||
function uploadSuccessful() {
|
||||
open = false;
|
||||
}
|
||||
|
||||
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 handle_input(ev: KeyboardEvent, i: number) {
|
||||
/* async function handle_input(ev: KeyboardEvent, i: number) {
|
||||
let item = crimesList[i];
|
||||
if (ev.key == 'Escape') {
|
||||
let text_field_id = `label__${item.name}`;
|
||||
@@ -127,125 +122,111 @@
|
||||
|
||||
return;
|
||||
}
|
||||
} */
|
||||
|
||||
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;
|
||||
|
||||
console.log('Tatort Update: ', message);
|
||||
|
||||
if (!res.ok) {
|
||||
const msg = await clone.text();
|
||||
console.error('❌ Fehler beim Speichern:', msg);
|
||||
} else {
|
||||
console.log('✅ Erfolgreich gespeichert:', newName);
|
||||
await invalidate('');
|
||||
currentName = newName;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('⚠️ Netzwerkfehler:', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(tatort: string) {
|
||||
let url = new URL($page.url);
|
||||
url.pathname += `/${tatort}`;
|
||||
console.log('Delete tatort: ', `/api${url.pathname}`, url.pathname);
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api${url.pathname}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ vorgang, tatort })
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="-z-10 bg-white">
|
||||
<div class="flex flex-col items-center justify-center w-full">
|
||||
<h1 class="text-xl">Vorgang {$page.params.vorgang}</h1>
|
||||
<h1 class="text-xl">Vorgang {vorgang}</h1>
|
||||
</div>
|
||||
<div class="mx-auto flex justify-center max-w-7xl h-full">
|
||||
<ul class="divide-y divide-gray-100">
|
||||
{#each crimesList as item, i}
|
||||
<li>
|
||||
<div class=" flex gap-x-4">
|
||||
<a
|
||||
href="/view/{$page.params.vorgang}/{item.name}?token={token}"
|
||||
href="/view/{$page.params.vorgang}/{currentName}?token={token}"
|
||||
class=" flex justify-between gap-x-6 py-5"
|
||||
aria-label="zum 3D-modell"
|
||||
>
|
||||
<div class=" flex gap-x-4">
|
||||
<Cube />
|
||||
</a>
|
||||
<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(i);
|
||||
}}
|
||||
on:keydown|stopPropagation={// event needed to identify ID
|
||||
// TO-DO: check if event is needed or if index is sufficient
|
||||
async (ev) => {
|
||||
handle_input(ev, i);
|
||||
}}>{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>
|
||||
{#if admin}
|
||||
<EditableItem
|
||||
list={crimesList}
|
||||
bind:editedName={names[i]}
|
||||
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}
|
||||
{#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
|
||||
>
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
34
src/routes/(token-based)/list/[vorgang]/+page.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// load für die Tatort-Liste
|
||||
export async function load({ fetch, params, url }) {
|
||||
|
||||
const caseId = params.vorgang;
|
||||
const caseToken = url.searchParams.get('token');
|
||||
|
||||
const adminRes = await fetch(`/api/user`)
|
||||
const user = await adminRes.json()
|
||||
|
||||
const res = await fetch(`/api/list/${caseId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${caseToken}`
|
||||
}
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
|
||||
const crimesList = data.crimesList
|
||||
|
||||
|
||||
|
||||
return {
|
||||
caseId,
|
||||
crimesList,
|
||||
caseToken,
|
||||
user
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { client } from '$lib/minio';
|
||||
/* import { client } from '$lib/minio';
|
||||
import { json } from '@sveltejs/kit';
|
||||
|
||||
|
||||
@@ -33,3 +33,4 @@ export async function PUT({ request }: {request: Request}) {
|
||||
|
||||
return json({ success: 'success' }, { status: 200 });
|
||||
};
|
||||
*/
|
||||
|
||||
27
src/routes/api/list/+server.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { getListOfVorgänge } from "$lib/server/vorgangService";
|
||||
import { json } from "@sveltejs/kit";
|
||||
|
||||
|
||||
//Get für die Vorgangsliste mit token, daher authorized Abfrage
|
||||
export async function GET({locals, url, request}) {
|
||||
/* if(!locals.user?.admin){
|
||||
return new Response('Unauthorized',{status: 401})
|
||||
}
|
||||
|
||||
const urlToken = url.searchParams.get('token');
|
||||
const caseToken = request.headers.get('Authorization')?.replace('Bearer ', '');
|
||||
|
||||
if(!(urlToken || caseToken)) return new Response('Unauthorized', { status: 401 });
|
||||
*/
|
||||
let caseList;
|
||||
|
||||
try {
|
||||
|
||||
caseList = await getListOfVorgänge();
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Laden der Liste:', err);
|
||||
}
|
||||
if(!caseList) return new Response(`Keine Liste vorhanden ${caseList}`, {status: 404})
|
||||
const data = {caseList}
|
||||
return json(data)
|
||||
}
|
||||
@@ -1,14 +1,110 @@
|
||||
import { client } from '$lib/minio';
|
||||
import { BUCKET, client } from '$lib/minio';
|
||||
import { getVorgangByCaseId } from '$lib/server/vorgangService';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { json } from '@sveltejs/kit';
|
||||
|
||||
export async function DELETE({ params }) {
|
||||
const vorgang = params.vorgang;
|
||||
// Seite für die Vorgangs-Liste
|
||||
|
||||
const object_list = await new Promise((resolve, reject) => {
|
||||
const res = [];
|
||||
const items_str = client.listObjects('tatort', vorgang, true);
|
||||
//Get für die Tatortliste mit token, daher authorized Abfrage
|
||||
export async function GET({locals,request, params, url}) {
|
||||
if(!locals.user?.admin){
|
||||
return new Response('Unauthorized',{status: 401})
|
||||
}
|
||||
|
||||
const caseId = params.vorgang;
|
||||
const urlToken = url.searchParams.get('token');
|
||||
const caseToken = request.headers.get('Authorization')?.replace('Bearer ', '');
|
||||
|
||||
|
||||
if(!(urlToken || caseToken)) return new Response('Unauthorized', { status: 401 });
|
||||
|
||||
|
||||
|
||||
|
||||
const crimesList = await getVorgangByCaseId(caseId);
|
||||
const data = {
|
||||
caseId,
|
||||
crimesList,
|
||||
caseToken
|
||||
}
|
||||
return json(data)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// rename operation
|
||||
export const PUT: RequestHandler= async ({request, locals}) => {
|
||||
if(!locals.user?.admin){
|
||||
return new Response('Forbidden', { status: 403 });
|
||||
}
|
||||
|
||||
const { oldName, newName} = await request.json();
|
||||
|
||||
if (!newName || !newName.trim()) {
|
||||
return new Response('Ungültiger Name', { status: 400 });
|
||||
}
|
||||
|
||||
|
||||
|
||||
try {
|
||||
const stat = await client.statObject(BUCKET, newName).catch(()=> null)
|
||||
if(stat){
|
||||
return json({ error: 'Datei mit dem neuen Namen existiert bereits.' }, { status: 409 });
|
||||
}
|
||||
// 2. Kopieren
|
||||
const Object_list:string[] = await new Promise((resolve, reject) => {
|
||||
const res: string[] = [];
|
||||
const items_str = client.listObjects(BUCKET, oldName, true);
|
||||
|
||||
|
||||
|
||||
items_str.on('data', (obj) => {
|
||||
if (obj.name) res.push(obj.name);
|
||||
});
|
||||
|
||||
items_str.on('error', reject);
|
||||
|
||||
items_str.on('end', async () => {
|
||||
try {
|
||||
for (const oldKey of res) {
|
||||
|
||||
const oldPath = `${oldName}/`;
|
||||
const newPath = `${newName}/`;
|
||||
const newKey = oldKey.replace(oldPath, newPath);
|
||||
await client.copyObject(BUCKET, newKey, `/${BUCKET}/${oldKey}`);
|
||||
}
|
||||
resolve(res);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
//3. Löschen
|
||||
await client.removeObjects(BUCKET, Object_list)
|
||||
const data = { success: true, message: 'Datei erfolgreich umbenannt.' }
|
||||
|
||||
return json(data, { status: 200 });
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Umbenennen', err)
|
||||
return json({error: `Fehler beim Umbenennen der Datei ${oldName} zu ${newName}`}, {status: 500})
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export const DELETE: RequestHandler = async ({ request })=> { //body: {request}, keine params // params= de?param1=value¶ms2
|
||||
// const vorgang = params.vorgang;
|
||||
const { vorgang } = await request.json();
|
||||
|
||||
const object_list:string[] = await new Promise((resolve, reject) => {
|
||||
const res: string[] = [];
|
||||
const items_str = client.listObjects(BUCKET, vorgang, true);
|
||||
|
||||
items_str.on('data', (obj) => {
|
||||
res.push(obj.name);
|
||||
if(obj.name) res.push(obj.name);
|
||||
});
|
||||
|
||||
items_str.on('error', reject);
|
||||
@@ -21,4 +117,4 @@ export async function DELETE({ params }) {
|
||||
await client.removeObjects('tatort', object_list);
|
||||
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { BUCKET, client } from '$lib/minio';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { json } from '@sveltejs/kit';
|
||||
|
||||
|
||||
// Seite für die Tatorte-Liste
|
||||
export async function GET() {
|
||||
const stream = client.listObjectsV2(BUCKET, '', true);
|
||||
const result = new ReadableStream({
|
||||
@@ -21,15 +25,51 @@ export async function GET() {
|
||||
'content-type': 'text/event-stream'
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export const PUT: RequestHandler= async ({request, locals}) => {
|
||||
|
||||
export async function DELETE({ request }: { request: Request }) {
|
||||
if(!locals.user?.admin){
|
||||
return new Response('Forbidden', { status: 403 });
|
||||
}
|
||||
const {vorgang, oldName, newName} = await request.json();
|
||||
|
||||
if (!newName || !newName.trim()) {
|
||||
return new Response('Ungültiger Name', { status: 400 });
|
||||
}
|
||||
|
||||
|
||||
const oldKey = `${vorgang}/${oldName}`;
|
||||
const newKey = `${vorgang}/${newName}`;
|
||||
console.log("PUT Tatorte-Liste: ", vorgang, oldKey, newKey);
|
||||
try {
|
||||
const stat = await client.statObject(BUCKET, newKey).catch(()=> null)
|
||||
if(stat){
|
||||
return json({ error: 'Datei mit dem neuen Namen existiert bereits.' }, { status: 409 });
|
||||
}
|
||||
|
||||
// 2. Kopieren
|
||||
await client.copyObject(BUCKET, newKey, `/${BUCKET}/${oldKey}`);
|
||||
//3. Löschen
|
||||
await client.removeObject(BUCKET, oldKey)
|
||||
const data = { success: true, message: 'Datei erfolgreich umbenannt.' }
|
||||
return json(data);
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Umbenennen', err)
|
||||
return json({error: 'Fehler beim Umbenennen der Datei'}, {status: 500})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export const DELETE: RequestHandler = async ({ request })=> { //body: {request}, keine params // params= de?param1=value¶ms2
|
||||
const url_fragments = request.url.split('/');
|
||||
const item = url_fragments.at(-1);
|
||||
const vorgang = url_fragments.at(-2);
|
||||
|
||||
await client.removeObject(BUCKET, `${vorgang}/${item}`);
|
||||
|
||||
return new Response(null, { status: 204 });
|
||||
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
Der Kommentar könnte m.M.n. raus