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
export let existings: ListItem[];
export let id: number;
export let editable: boolean = true;
export let editing: boolean;
console.log('Debug editing', editing);
const existingNames = existings.map((item) => item.name);
const dispatch = createEventDispatcher<{
editSart: {};
save: {};
delete: {};
cancel: void;
}>();
let internalValue = value;
let oldValue = value;
let showWarning = false;
let duplicate = false;
let errors: string[] = [];
let errorText = '';
$: 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 {
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>