f111_frontend_ueberarbeitung #39
@@ -20,26 +20,11 @@ export const actions = {
|
|||||||
const vorgangName: string | null = data.get('vorgang') as string;
|
const vorgangName: string | null = data.get('vorgang') as string;
|
||||||
const crimeName: string | null = data.get('name') as string;
|
const crimeName: string | null = data.get('name') as string;
|
||||||
const type: string | null = data.get('type') as string;
|
const type: string | null = data.get('type') as string;
|
||||||
const vorgangPIN: string | null = data.get('vorgangPIN') as string;
|
|
||||||
const fileName: string | null = data.get('fileName') as string;
|
const fileName: string | null = data.get('fileName') as string;
|
||||||
|
|
||||||
const vorgangExists = vorgangNameExists(vorgangName);
|
|
||||||
let vorgangToken;
|
let vorgangToken;
|
||||||
|
const vorgang = getVorgangByName(vorgangName);
|
||||||
if (!vorgangExists) {
|
vorgangToken = vorgang.token;
|
||||||
vorgangToken = uuidv4();
|
|
||||||
const insertSQLStatement = `INSERT INTO cases (token, name, pin) VALUES (?, ?, ?)`;
|
|
||||||
const statement = db.prepare(insertSQLStatement);
|
|
||||||
statement.run(vorgangToken, vorgangName, vorgangPIN);
|
|
||||||
} else {
|
|
||||||
const vorgang = getVorgangByName(vorgangName);
|
|
||||||
vorgangToken = vorgang.token;
|
|
||||||
if (vorgang && vorgang.pin != vorgangPIN) {
|
|
||||||
const updateSQLStmt = `UPDATE cases SET pin = ? WHERE token = ?`;
|
|
||||||
const statement = db.prepare(updateSQLStmt);
|
|
||||||
statement.run(vorgangPIN, vorgangToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let objectName = `${vorgangToken}/${crimeName}`;
|
let objectName = `${vorgangToken}/${crimeName}`;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -60,7 +45,6 @@ export const actions = {
|
|||||||
const data = Object.fromEntries(requestData);
|
const data = Object.fromEntries(requestData);
|
||||||
const vorgang = data.vorgang;
|
const vorgang = data.vorgang;
|
||||||
const name = data.name;
|
const name = data.name;
|
||||||
const vorgangPIN = data.vorgangPIN;
|
|
||||||
let success = true;
|
let success = true;
|
||||||
const err = {};
|
const err = {};
|
||||||
if (isRequiredFieldValid(vorgang)) {
|
if (isRequiredFieldValid(vorgang)) {
|
||||||
@@ -77,13 +61,6 @@ export const actions = {
|
|||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRequiredFieldValid(vorgangPIN)) {
|
|
||||||
err.vorgangPIN = null;
|
|
||||||
} else {
|
|
||||||
err.vorgangPIN = 'Das Feld Zugangspasswort darf nicht leer bleiben.';
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) return { success };
|
if (success) return { success };
|
||||||
|
|
||||||
return fail(400, err);
|
return fail(400, err);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import shortenFileSize from '$lib/helper/shortenFileSize';
|
import shortenFileSize from '$lib/helper/shortenFileSize';
|
||||||
import timeElapsed from '$lib/helper/timeElapsed';
|
import timeElapsed from '$lib/helper/timeElapsed';
|
||||||
|
import { deserialize } from '$app/forms';
|
||||||
import Alert from '$lib/components/Alert.svelte';
|
import Alert from '$lib/components/Alert.svelte';
|
||||||
import Button from '$lib/components/Button.svelte';
|
import Button from '$lib/components/Button.svelte';
|
||||||
import Modal from '$lib/components/Modal/Modal.svelte';
|
import Modal from '$lib/components/Modal/Modal.svelte';
|
||||||
@@ -12,10 +12,12 @@
|
|||||||
import { invalidateAll } from '$app/navigation';
|
import { invalidateAll } from '$app/navigation';
|
||||||
import NameItemEditor from '$lib/components/NameItemEditor.svelte';
|
import NameItemEditor from '$lib/components/NameItemEditor.svelte';
|
||||||
import EmptyList from '$lib/components/EmptyList.svelte';
|
import EmptyList from '$lib/components/EmptyList.svelte';
|
||||||
|
import FileRect from '$lib/icons/File-rect.svelte';
|
||||||
|
import Exclamation from '$lib/icons/Exclamation.svelte';
|
||||||
import { API_ROUTES, ROUTE_NAMES } from '../../../index.js';
|
import { API_ROUTES, ROUTE_NAMES } from '../../../index.js';
|
||||||
|
|
||||||
//Seite für die Tatort-Liste
|
//Seite für die Tatort-Liste
|
||||||
let { data } = $props();
|
let { data, form } = $props();
|
||||||
|
|
||||||
interface ListItem {
|
interface ListItem {
|
||||||
//sollte Typ Vorgang sein, aber der einfachheit ist es noch ListItem, damit die Komponente NameItemEditor für Vorgang und Tatort eingesetzt werden kann
|
//sollte Typ Vorgang sein, aber der einfachheit ist es noch ListItem, damit die Komponente NameItemEditor für Vorgang und Tatort eingesetzt werden kann
|
||||||
@@ -33,6 +35,119 @@
|
|||||||
let vorgangToken: string = data.vorgang.vorgangToken;
|
let vorgangToken: string = data.vorgang.vorgangToken;
|
||||||
let isEmptyList = $derived(crimesList.length === 0);
|
let isEmptyList = $derived(crimesList.length === 0);
|
||||||
|
|
||||||
|
// File Upload Variablen
|
||||||
|
let name = $state('');
|
||||||
|
let formErrors: Record<string, any> | null = $state(null);
|
||||||
|
let etag: string | null = $state(null);
|
||||||
|
let files: FileList | null = $state(null);
|
||||||
|
|
||||||
|
// Model Variablen für Upload
|
||||||
|
let openUL = $state(false);
|
||||||
|
let inProgressUL = $state(form === null);
|
||||||
|
|
||||||
|
async function buttonClick(event: MouseEvent) {
|
||||||
|
if (!(await validateForm())) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = await getUrl();
|
||||||
|
open = true;
|
||||||
|
inProgress = true;
|
||||||
|
|
||||||
|
fetch(url, { method: 'PUT', body: files[0] })
|
||||||
|
.then((response) => {
|
||||||
|
inProgress = false;
|
||||||
|
etag = '123';
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
inProgress = false;
|
||||||
|
etag = null;
|
||||||
|
console.log('ERROR', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function validateForm() {
|
||||||
|
let data = new FormData();
|
||||||
|
data.append('vorgang', vorgangName);
|
||||||
|
data.append('name', name);
|
||||||
|
const response = await fetch(ROUTE_NAMES.UPLOAD_VALIDATE, { method: 'POST', body: data });
|
||||||
|
const result = deserialize(await response.text());
|
||||||
|
|
||||||
|
let success = true;
|
||||||
|
if (result.type === 'success') {
|
||||||
|
formErrors = null;
|
||||||
|
} else {
|
||||||
|
if (result.type === 'failure' && result.data) formErrors = result.data;
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!files?.length) {
|
||||||
|
formErrors = { file: 'Sie haben keine Datei ausgewählt.', ...formErrors };
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await check_valid_glb_file())) {
|
||||||
|
formErrors = { file: 'Keine gültige .GLD-Datei', ...formErrors };
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `val` is hex string
|
||||||
|
function swap_endian(val) {
|
||||||
|
// from https://www.geeksforgeeks.org/bit-manipulation-swap-endianness-of-a-number/
|
||||||
|
|
||||||
|
let leftmost_byte = (val & eval(0x000000ff)) >> 0;
|
||||||
|
let left_middle_byte = (val & eval(0x0000ff00)) >> 8;
|
||||||
|
let right_middle_byte = (val & eval(0x00ff0000)) >> 16;
|
||||||
|
let rightmost_byte = (val & eval(0xff000000)) >> 24;
|
||||||
|
|
||||||
|
leftmost_byte <<= 24;
|
||||||
|
left_middle_byte <<= 16;
|
||||||
|
right_middle_byte <<= 8;
|
||||||
|
rightmost_byte <<= 0;
|
||||||
|
|
||||||
|
let res = leftmost_byte | left_middle_byte | right_middle_byte | rightmost_byte;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function check_valid_glb_file() {
|
||||||
|
// GLD Header, magic value 0x46546C67, identifies data as binary glTF, 4 bytes
|
||||||
|
// little endian!
|
||||||
|
const GLD_MAGIC = 0x46546c67;
|
||||||
|
|
||||||
|
// big endian!
|
||||||
|
let file = files[0];
|
||||||
|
let file_header = file.slice(0, 4);
|
||||||
|
console.log(file_header);
|
||||||
|
let header_bytes = await file_header.bytes();
|
||||||
|
let file_header_hex = '0x' + header_bytes.toHex().toString();
|
||||||
|
|
||||||
|
if (GLD_MAGIC == swap_endian(file_header_hex)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUrl() {
|
||||||
|
let data = new FormData();
|
||||||
|
data.append('vorgang', vorgangName);
|
||||||
|
data.append('name', name);
|
||||||
|
if (files?.length === 1) {
|
||||||
|
data.append('type', files[0].type);
|
||||||
|
data.append('fileName', files[0].name);
|
||||||
|
}
|
||||||
|
const response = await fetch(ROUTE_NAMES.UPLOAD_URL, { method: 'POST', body: data });
|
||||||
|
const result = deserialize(await response.text());
|
||||||
|
if (result.type === 'success') return result.data?.url;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
//Variablen für Modal
|
//Variablen für Modal
|
||||||
let open = $state(false);
|
let open = $state(false);
|
||||||
let inProgress = $state(false);
|
let inProgress = $state(false);
|
||||||
@@ -71,7 +186,7 @@
|
|||||||
open = true;
|
open = true;
|
||||||
inProgress = true;
|
inProgress = true;
|
||||||
isError = false;
|
isError = false;
|
||||||
let path = API_ROUTES.CRIME(vorgangToken, tatort)
|
let path = API_ROUTES.CRIME(vorgangToken, tatort);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(path, {
|
const res = await fetch(path, {
|
||||||
@@ -144,9 +259,9 @@ Mit freundlichen Grüßen,
|
|||||||
<div class=" flex gap-x-4">
|
<div class=" flex gap-x-4">
|
||||||
<a
|
<a
|
||||||
data-testid="crime-link"
|
data-testid="crime-link"
|
||||||
href="{ROUTE_NAMES.CRIME(vorgangToken, item.name, vorgangPIN)}"
|
href={ROUTE_NAMES.CRIME(vorgangToken, item.name, vorgangPIN)}
|
||||||
class=" flex justify-between gap-x-6 py-5"
|
class=" flex justify-between gap-x-6 py-5"
|
||||||
aria-label="{ROUTE_NAMES.CRIME(vorgangToken, item.name, vorgangPIN)}"
|
aria-label={ROUTE_NAMES.CRIME(vorgangToken, item.name, vorgangPIN)}
|
||||||
title={item.name}
|
title={item.name}
|
||||||
>
|
>
|
||||||
<Cube />
|
<Cube />
|
||||||
@@ -190,6 +305,89 @@ Mit freundlichen Grüßen,
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if admin}
|
||||||
|
<div class="mx-auto max-w-2xl">
|
||||||
|
<div class="flex flex-col items-center space-y-6">
|
||||||
|
<!-- Name Input -->
|
||||||
|
<div class="w-full max-w-md">
|
||||||
|
<label for="name" class="block text-sm font-medium leading-6 text-gray-900">
|
||||||
|
<span class="flex">
|
||||||
|
{#if formErrors?.name}
|
||||||
|
<span class="inline-block mr-1"><Exclamation /></span>
|
||||||
|
{/if} Modellname
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<div
|
||||||
|
class="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
bind:value={name}
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
id="name"
|
||||||
|
autocomplete={name}
|
||||||
|
class="block flex-1 border-0 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if formErrors?.name}
|
||||||
|
<p class="block text-sm leading-6 text-red-900 mt-2">{formErrors.name}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- File Upload -->
|
||||||
|
<div class="w-full max-w-md">
|
||||||
|
<label for="file" class="block text-sm font-medium leading-6 text-gray-900">
|
||||||
|
<span class="flex">
|
||||||
|
{#if formErrors?.file}
|
||||||
|
<span class="inline-block mr-1"><Exclamation /></span>
|
||||||
|
{/if} Datei
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 px-6 py-10"
|
||||||
|
>
|
||||||
|
<div class="text-center">
|
||||||
|
<FileRect />
|
||||||
|
<div class="mt-4 flex text-sm leading-6 text-gray-600">
|
||||||
|
<label
|
||||||
|
for="file"
|
||||||
|
class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500"
|
||||||
|
>
|
||||||
|
<span>Wähle eine Datei aus</span>
|
||||||
|
<input id="file" bind:files name="file" type="file" class="sr-only" />
|
||||||
|
</label>
|
||||||
|
<p class="pl-1">oder ziehe sie ins Feld</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs leading-5 text-gray-600">GLB Dateien bis zu 1GB</p>
|
||||||
|
{#if files?.length}
|
||||||
|
<div class="flex justify-center text-xs mt-2">
|
||||||
|
<p class="mx-2">Datei: <span class="font-bold">{files[0].name}</span></p>
|
||||||
|
<p class="mx-2">
|
||||||
|
Größe: <span class="font-bold">{shortenFileSize(files[0].size)}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if formErrors?.file}
|
||||||
|
<p class="block text-sm leading-6 text-red-900 mt-2">{formErrors.file}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 flex items-center justify-end gap-x-6">
|
||||||
|
<Button
|
||||||
|
on:click={buttonClick}
|
||||||
|
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||||
|
>
|
||||||
|
Hinzufügen
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<Modal {open}
|
<Modal {open}
|
||||||
><ModalTitle>Umbenennen</ModalTitle><ModalContent>
|
><ModalTitle>Umbenennen</ModalTitle><ModalContent>
|
||||||
{#if inProgress}
|
{#if inProgress}
|
||||||
@@ -202,6 +400,20 @@ Mit freundlichen Grüßen,
|
|||||||
</ModalContent>
|
</ModalContent>
|
||||||
<ModalFooter><Button disabled={inProgress} on:click={closeModal}>Ok</Button></ModalFooter>
|
<ModalFooter><Button disabled={inProgress} on:click={closeModal}>Ok</Button></ModalFooter>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal {openUL}
|
||||||
|
><ModalTitle>Upload</ModalTitle><ModalContent>
|
||||||
|
{#if inProgressUL}
|
||||||
|
<p class="py-2 mb-1">Upload läuft...</p>
|
||||||
|
{:else if etag}
|
||||||
|
<Alert class="w-full">Upload erfolgreich</Alert>
|
||||||
|
{:else}
|
||||||
|
<Alert class="w-full" type="error">Fehler beim Upload</Alert>
|
||||||
|
{/if}
|
||||||
|
</ModalContent>
|
||||||
|
<ModalFooter><Button disabled={inProgressUL} on:click={uploadSuccessful}>Ok</Button></ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -210,3 +422,4 @@ Mit freundlichen Grüßen,
|
|||||||
min-width: 24rem;
|
min-width: 24rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user