f047_Edit-der-Namen #15

Closed
jared wants to merge 11 commits from f047_Edit-der-Namen into development
5 changed files with 196 additions and 213 deletions
Showing only changes of commit f87b106ad2 - Show all commits

View File

@@ -0,0 +1,123 @@
<script lang="ts">
import Edit from '$lib/icons/Edit.svelte';
import Trash from '$lib/icons/Trash.svelte';
import { createEventDispatcher } from 'svelte';
import { validateInput } from '$lib/helper/error-utils';
interface ListItem {
name: string;
token?: string;
// add other properties as needed
}
export let value: string = '';
export let variant: '' | 'casename' | 'crimename' = ''; // casename | crimename
mina marked this conversation as resolved Outdated
Outdated
Review

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

Der Kommentar könnte m.M.n. raus
export let existings: ListItem[];
export let id: number;
export let editable: boolean = true;
export let editing: boolean;
console.log('Debug editing', editing);
mina marked this conversation as resolved Outdated
Outdated
Review

Bitte Console.log entfernen.

Bitte Console.log entfernen.
const existingNames = existings.map((item) => item.name);
const dispatch = createEventDispatcher<{
editSart: {};
mina marked this conversation as resolved Outdated
Outdated
Review

Typo editSart to editStart

Typo editSart to editS**t**art
save: {};
delete: {};
cancel: void;
}>();
jared marked this conversation as resolved Outdated
Outdated
Review

The signature '(): EventDispatcher<{ editSart: {}; save: {}; delete: {}; cancel: void; }>' of 'createEventDispatcher' is deprecated.

Mittlerweile wird ein Event mittels Callback function dispatched. Gucke ggf. noch einmal in die Docs.

Zum Beispiel:

<script type="ts"> const props: { onClick(): void; onExplode(name: string, age: number): void; } = $props(); </script>

<MyComponent
onClick={() => alert('clicked')}
onExplode={(name, age) => alert(name + ' ' + age)}
/>

The signature '(): EventDispatcher<{ editSart: {}; save: {}; delete: {}; cancel: void; }>' of 'createEventDispatcher' is deprecated. Mittlerweile wird ein Event mittels Callback function dispatched. Gucke ggf. noch einmal in die Docs. Zum Beispiel: <script type="ts"> const props: { onClick(): void; onExplode(name: string, age: number): void; } = $props(); </script> <MyComponent onClick={() => alert('clicked')} onExplode={(name, age) => alert(name + ' ' + age)} />
Outdated
Review

Ich habe es angepasst, es musste einiges verändert werden, bitte schaue es dir nochmal an.

Ich habe es angepasst, es musste einiges verändert werden, bitte schaue es dir nochmal an.
let internalValue = value;
jared marked this conversation as resolved Outdated
Outdated
Review

Villt die Variable noch besser bennennen. InternalValue oder oldValue, kann halt alles sein...

Villt die Variable noch besser bennennen. InternalValue oder oldValue, kann halt alles sein...
Outdated
Review

Allgemein, weil es eine Komponente ist, die vielseitig einsetzbar. Es geht darum quasi den Startwert? zu speichern, für den Abbruch und den aktuellen Wert, um ein editieren in Gang zu bringen. Man könnte es auch cancelValue und EditValue nennen? Vielleicht besser?

Allgemein, weil es eine Komponente ist, die vielseitig einsetzbar. Es geht darum quasi den Startwert? zu speichern, für den Abbruch und den aktuellen Wert, um ein editieren in Gang zu bringen. Man könnte es auch cancelValue und EditValue nennen? Vielleicht besser?
Outdated
Review

Naja, der Begriff Value bringt halt nicht, weil das quasi immer ein value ist. Daher so explizit bennen wie es geht

Naja, der Begriff Value bringt halt nicht, weil das quasi immer ein value ist. Daher so explizit bennen wie es geht
let oldValue = value;
let showWarning = false;
let duplicate = false;
let errors: string[] = [];
let errorText = '';
mina marked this conversation as resolved Outdated
Outdated
Review

Wird im weiteren Verlauf nicht verwendet, daher löschen

Wird im weiteren Verlauf nicht verwendet, daher löschen
$: errors = validateInput(oldValue, internalValue, { minLength: 3, existingNames });
function startEdit() {
oldValue = value;
internalValue = value;
editing = true;
dispatch('editSart', { id, value, variant, editing });
}
function cancelEdit() {
internalValue = oldValue;
editing = false;
showWarning = false;
console.log('Abgebrochen');
dispatch('cancel');
}
function saveEdit(ev?: MouseEvent | KeyboardEvent) {
ev?.preventDefault();
ev?.stopPropagation();
const name = internalValue.trim();
if (errors.length !== 0) {
showWarning = true;
console.log('Abgebrochen', errors);
return;
}
if (!name) {
showWarning = true;
console.log('Abgebrochen', showWarning);
return;
}
if (existingNames.includes(name) && name !== oldValue.trim()) {
showWarning = true;
console.log('Abgebrochen', showWarning);
return;
}
editing = false;
showWarning = false;
duplicate = false;
dispatch('save', { newValue: internalValue, oldValue, variant });
}
function deleteItem() {
dispatch('delete', { value, variant });
}
function handleKey(e: KeyboardEvent) {
if (e.key === 'Enter') saveEdit(e);
if (e.key === 'Escape') cancelEdit();
}
</script>
<div class="" {...$$restProps}>
<div class="flex gap-x-2">
{#if editing}
<input
bind:value={internalValue}
on:keydown={handleKey}
on:blur|preventDefault|stopPropagation={cancelEdit}
class=""
class:bg-red-200={(showWarning && editing) || errors.length !== 0}
/>
{:else}
<span>{value}</span>
{#if !editing && editable}
<button on:click|preventDefault|stopPropagation={startEdit}>
<Edit />
</button>
<button on:click|preventDefault={deleteItem}>
<Trash />
</button>
{/if}
{/if}
</div>
{#if editing && errors}
<p class="text-red-600 text-sm mt-1 font-medium">{errors[0]}</p>
{/if}
</div>

View File

@@ -1,85 +0,0 @@
<script lang="ts">
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';
import timeElapsed from '$lib/helper/timeElapsed';
export let data;
export let i;
let crimesList = data.crimesList;
let item = crimesList[i];
let admin = data.user?.admin;
let link = `/view/${item.prefix}/${item.name}?token=${data.caseToken}`;
console.log('Debug Mina', crimesList, data);
function editName() {
console.log('Edit Name');
}
function deleteCase() {
console.log('Delete Case');
}
function checkIsEmpty() {
if (!item.name) console.log('Das Feld ist leer');
}
function cancelEdit() {
console.log('Bearbeitung abgebrochen');
}
</script>
<a href={link} class="flex justify-between items-center gap-x-6 py-5">
<div class=" flex gap-x-4">
<Cube class="h-auto" />
<div class="min-w-0 flex-auto">
{#if admin}
<input
class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
type="text"
value={item.name}
on:click|preventDefault={(ev) => {
editName();
}}
/>
<button
class="p-2"
on:click|preventDefault={(ev) => {
editName();
}}
>
<Edit />
</button>
<button
class="p-2"
on:click|preventDefault={async (ev) => {
deleteCase();
}}
>
<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>

View File

@@ -0,0 +1,9 @@
export function validateInput(oldValue:string, value: string, options: { minLength?: number; existingNames?: string[] }) {
const errors: string[] = [];
if (!value.trim()) errors.push('Feld darf nicht leer sein');
if (options.existingNames?.includes(value) && oldValue !== value)
errors.push('Name existiert bereits');
return errors;
}
Review

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

Werde ich nochmal überarbeiten

Werde ich nochmal überarbeiten

View File

@@ -1,11 +1,17 @@
<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';
export let data: PageData;
export let data;
export let editingId: number;
const caseList = data.caseList;
interface ListItem {
Review

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

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

s.u.

s.u.
name: string;
token?: string;
// add other properties as needed
}
const caseList: ListItem[] = data.caseList;
async function delete_item(ev: Event) {
let delete_item = window.confirm('Bist du sicher?');
@@ -14,7 +20,7 @@
const target = ev.currentTarget as HTMLElement | null;
if (!target) return;
let filename = target.id.split('del__')[1];
// delete request
// --------------
@@ -44,27 +50,31 @@
</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"
>
<Trash />
</button>
<EditableItem
class=""
id={i}
value={item.name}
editing={editingId === i}
on:editStart={() => (editingId = i)}
variant="casename"
existings={caseList}
on:save={(e) => console.log('Gespeichert:', e.detail)}
on:delete={(e) => {
console.log('Gelöscht:', e.detail);
delete_item(e);
}}
></EditableItem>
</div>
<div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">Vorgang</p>
</div>
</div>
<div class="hidden sm:flex sm:flex-col sm:items-end">
<p class="text-sm leading-6 text-gray-900">Vorgang</p>
</div>
</a>
</li>

View File

@@ -6,14 +6,17 @@
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 ListItem from '$lib/components/ListItem.svelte';
import EditableItem from '$lib/components/EditableItem.svelte';
import Cube from '$lib/icons/Cube.svelte';
import shortenFileSize from '$lib/helper/shortenFileSize.js';
import timeElapsed from '$lib/helper/timeElapsed.js';
export let data;
interface ListItem {
Outdated
Review

s. o.

s. o.
Outdated
Review

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.
name: string;
size: number;
lastModified: string | number | Date;
size?: number;
lastModified?: string | number | Date | undefined;
show_button?: boolean;
// add other properties as needed
}
@@ -28,6 +31,9 @@
$: inProgress;
let err = false;
$: err;
let editingId: number;
// let admin = data?.user?.admin;
let admin = true;
let rename_input;
$: rename_input;
@@ -36,21 +42,6 @@
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) {
let item = crimesList[i];
if (ev.key == 'Escape') {
@@ -131,7 +122,7 @@
<ul class="divide-y divide-gray-100">
{#each crimesList as item, i}
<li>
<!-- <a
<a
href="/view/{$page.params.vorgang}/{item.name}?token={token}"
class=" flex justify-between gap-x-6 py-5"
aria-label="zum 3D-modell"
@@ -139,106 +130,41 @@
<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(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}`, { 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
class="bg-lred-500"
id={i}
value={item.name}
editing={editingId === i}
on:editStart={() => (editingId = i)}
variant="crimename"
existings={crimesList}
on:save={(e) => console.log('Gespeichert:', e.detail)}
on:delete={(e) => console.log('Gelöscht:', e.detail)}
></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> -->
<ListItem {data} {i}></ListItem>
</a>
</li>
{/each}
</ul>