make admin table resizable

This commit is contained in:
bytedream 2023-08-29 00:43:15 +02:00
parent 3e259872b3
commit 1fb71fe899
11 changed files with 148 additions and 34 deletions

25
package-lock.json generated
View File

@ -31,11 +31,13 @@
"postcss": "^8.4.27", "postcss": "^8.4.27",
"prettier": "^2.8.0", "prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1", "prettier-plugin-svelte": "^2.10.1",
"sass": "^1.66.1",
"svelte": "^4.0.5", "svelte": "^4.0.5",
"svelte-check": "^3.4.3", "svelte-check": "^3.4.3",
"svelte-heros-v2": "^0.9.3", "svelte-heros-v2": "^0.9.3",
"svelte-local-storage-store": "^0.6.0", "svelte-local-storage-store": "^0.6.0",
"svelte-multicssclass": "^2.1.1", "svelte-multicssclass": "^2.1.1",
"svelte-preprocess": "^5.0.4",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^5.0.0", "typescript": "^5.0.0",
@ -2854,6 +2856,12 @@
"node": ">= 4" "node": ">= 4"
} }
}, },
"node_modules/immutable": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz",
"integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
"dev": true
},
"node_modules/import-fresh": { "node_modules/import-fresh": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -4391,6 +4399,23 @@
"rimraf": "bin.js" "rimraf": "bin.js"
} }
}, },
"node_modules/sass": {
"version": "1.66.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.66.1.tgz",
"integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.5.4", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",

View File

@ -28,11 +28,13 @@
"postcss": "^8.4.27", "postcss": "^8.4.27",
"prettier": "^2.8.0", "prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1", "prettier-plugin-svelte": "^2.10.1",
"sass": "^1.66.1",
"svelte": "^4.0.5", "svelte": "^4.0.5",
"svelte-check": "^3.4.3", "svelte-check": "^3.4.3",
"svelte-heros-v2": "^0.9.3", "svelte-heros-v2": "^0.9.3",
"svelte-local-storage-store": "^0.6.0", "svelte-local-storage-store": "^0.6.0",
"svelte-multicssclass": "^2.1.1", "svelte-multicssclass": "^2.1.1",
"svelte-preprocess": "^5.0.4",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^5.0.0", "typescript": "^5.0.0",

View File

@ -28,7 +28,11 @@
<div class={type === 'submit' && disabled ? 'cursor-not-allowed' : ''}> <div class={type === 'submit' && disabled ? 'cursor-not-allowed' : ''}>
{#if type === 'submit'} {#if type === 'submit'}
<input <input
class={`btn btn-${size}`} class="btn"
class:btn-xs={size === 'xs'}
class:btn-sm={size === 'sm'}
class:btn-md={size === 'md'}
class:btn-lg={size === 'lg'}
{id} {id}
type="submit" type="submit"
{disabled} {disabled}
@ -49,9 +53,16 @@
{/if} {/if}
<div class="relative flex items-center" class:sm:max-w-[16rem]={type !== 'checkbox'}> <div class="relative flex items-center" class:sm:max-w-[16rem]={type !== 'checkbox'}>
<input <input
class={type === 'checkbox' ? `checkbox-${size}` : `input-${size}`}
class:checkbox={type === 'checkbox'} class:checkbox={type === 'checkbox'}
class:input,input-bordered,w-[100%]={type !== 'checkbox'} class:checkbox-xs={type === 'checkbox' && size === 'xs'}
class:checkbox-sm={type === 'checkbox' && size === 'sm'}
class:checkbox-md={type === 'checkbox' && size === 'md'}
class:checkbox-lg={type === 'checkbox' && size === 'lg'}
class:input,input-bordered,w-full={type !== 'checkbox'}
class:input-xs={type !== 'checkbox' && size === 'xs'}
class:input-sm={type !== 'checkbox' && size === 'sm'}
class:input-md={type !== 'checkbox' && size === 'md'}
class:input-lg={type !== 'checkbox' && size === 'lg'}
class:pr-11={initialType === 'password'} class:pr-11={initialType === 'password'}
{id} {id}
{name} {name}

View File

@ -6,6 +6,7 @@
export let notice: string | null = null; export let notice: string | null = null;
export let required = false; export let required = false;
export let disabled = false; export let disabled = false;
export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
</script> </script>
<div> <div>
@ -20,7 +21,11 @@
</label> </label>
{/if} {/if}
<select <select
class="input input-bordered w-[100%] sm:max-w-[16rem]" class="select select-bordered w-[100%] sm:max-w-[16rem]"
class:select-xs={size === 'xs'}
class:select-sm={size === 'sm'}
class:select-md={size === 'md'}
class:select-lg={size === 'lg'}
{id} {id}
{name} {name}
{required} {required}

View File

@ -3,3 +3,52 @@ export async function buttonTriggeredRequest<T>(e: MouseEvent, promise: Promise<
await promise; await promise;
(e.target as HTMLButtonElement).disabled = false; (e.target as HTMLButtonElement).disabled = false;
} }
export function resizeTableColumn(event: MouseEvent, dragOffset: number) {
const element = event.target as HTMLTableCellElement;
const rect = element.getBoundingClientRect();
const posX = event.clientX - rect.left;
const offset = rect.width - event.clientX;
if (posX <= dragOffset || posX >= rect.width - dragOffset) {
// do not resize if resize request is on the table left or right
if (
(posX <= dragOffset && !element.previousElementSibling) ||
(posX >= rect.width - dragOffset && !element.nextElementSibling)
) {
return;
}
const table = element.parentElement!.parentElement!.parentElement as HTMLTableElement;
let resizeRow: HTMLTableRowElement;
if (table.tBodies[0].rows[0].hidden) {
resizeRow = table.rows[0];
} else {
resizeRow = table.tBodies[0].insertRow(0);
resizeRow.hidden = true;
for (let i = 0; i < table.rows[0].cells.length; i++) {
resizeRow.insertCell();
}
// insert an additional to keep the zebra in place pattern which might be applied
const zebraGhostRow = table.tBodies[0].insertRow(0);
zebraGhostRow.hidden = true;
}
const resizeElement =
resizeRow.cells[element.cellIndex - ((posX <= dragOffset) as unknown as number)];
// eslint-disable-next-line svelte/no-inner-declarations,no-inner-declarations
function resize(e: MouseEvent) {
document.body.style.cursor = 'col-resize';
resizeElement.style.width = `${offset + e.clientX}px`;
}
document.addEventListener('mousemove', resize);
document.addEventListener('mouseup', () => {
document.removeEventListener('mousemove', resize);
document.body.style.cursor = 'initial';
});
}
}

View File

@ -6,9 +6,11 @@ export class Permissions {
readonly value: number; readonly value: number;
constructor(value: number | number[]) { constructor(value: number | number[] | null) {
if (typeof value == 'number') { if (typeof value == 'number') {
this.value = value; this.value = value;
} else if (value == null) {
this.value = 0;
} else { } else {
let finalValue = 0; let finalValue = 0;
for (const v of Object.values(value)) { for (const v of Object.values(value)) {

View File

@ -92,9 +92,8 @@
</main> </main>
<nav> <nav>
<div <div
class={onAdminPage class="fixed bottom-4 right-4 sm:left-4 sm:right-[initial] group/menu-bar flex flex-col-reverse justify-center items-center"
? 'fixed bottom-4 right-4 group/menu-bar flex flex-col-reverse justify-center items-center' class:hidden={onAdminPage}
: 'fixed bottom-4 right-4 group/menu-bar flex flex-col-reverse justify-center items-center sm:left-4 sm:right-[initial]'}
bind:this={nav} bind:this={nav}
> >
<button <button
@ -145,8 +144,7 @@
<ul class="flex flex-col bg-base-200 rounded"> <ul class="flex flex-col bg-base-200 rounded">
{#each navPaths as navPath, i} {#each navPaths as navPath, i}
<li <li
class="flex justify-center tooltip tooltip-left" class="flex justify-center tooltip tooltip-left sm:tooltip-right"
class:sm:tooltip-right={!onAdminPage}
data-tip={navPath.name} data-tip={navPath.name}
> >
<a <a

View File

@ -19,7 +19,7 @@
{#if $page.url.pathname !== `${env.PUBLIC_BASE_PATH}/admin/login`} {#if $page.url.pathname !== `${env.PUBLIC_BASE_PATH}/admin/login`}
<div class="flex h-screen"> <div class="flex h-screen">
<div class="h-full"> <div class="h-full w-max">
<ul class="menu p-4 w-fit h-full bg-base-200 text-base-content"> <ul class="menu p-4 w-fit h-full bg-base-200 text-base-content">
<li> <li>
<a href="{env.PUBLIC_BASE_PATH}/admin/admin"> <a href="{env.PUBLIC_BASE_PATH}/admin/admin">
@ -35,7 +35,7 @@
</li> </li>
</ul> </ul>
</div> </div>
<div class="h-full w-full"> <div class="h-full w-full overflow-scroll">
<slot /> <slot />
</div> </div>
</div> </div>

View File

@ -6,7 +6,7 @@
import { Permissions } from '$lib/permissions'; import { Permissions } from '$lib/permissions';
import { env } from '$env/dynamic/public'; import { env } from '$env/dynamic/public';
import ErrorToast from '$lib/components/Toast/ErrorToast.svelte'; import ErrorToast from '$lib/components/Toast/ErrorToast.svelte';
import { buttonTriggeredRequest } from '$lib/components/utils'; import { buttonTriggeredRequest, resizeTableColumn } from '$lib/components/utils';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
let allPermissionBadges = { let allPermissionBadges = {
@ -30,7 +30,9 @@
}) })
}); });
if (response.ok) { if (response.ok) {
data.admins.push(await response.json()); let res = await response.json();
res.permissions = new Permissions(res.permissions).asArray();
data.admins.push(res);
data.admins = data.admins; data.admins = data.admins;
} else { } else {
throw new Error(); throw new Error();
@ -89,28 +91,21 @@
let permissions = new Permissions(data.permissions); let permissions = new Permissions(data.permissions);
</script> </script>
<table class="table table-zebra"> <table class="table table-zebra w-full">
<colgroup>
<col span="1" style="width: 5%" />
<col span="1" style="width: 25%" />
<col span="1" style="width: 25%" />
<col span="1" style="width: 30%" />
<col span="1" style="width: 15%" />
</colgroup>
<thead> <thead>
<tr> <tr>
<th /> <th on:mousedown={(e) => resizeTableColumn(e, 5)} />
<th>Benutzername</th> <th on:mousedown={(e) => resizeTableColumn(e, 5)}>Benutzername</th>
<th>Passwort</th> <th on:mousedown={(e) => resizeTableColumn(e, 5)}>Passwort</th>
<th>Berechtigungen</th> <th on:mousedown={(e) => resizeTableColumn(e, 5)}>Berechtigungen</th>
<th /> <th on:mousedown={(e) => resizeTableColumn(e, 5)} />
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each data.admins as admin, i} {#each data.admins as admin, i}
<tr> <tr>
<td>{i}</td> <td on:mousedown={(e) => resizeTableColumn(e, 5)}>{i}</td>
<td <td on:mousedown={(e) => resizeTableColumn(e, 5)}
><Input ><Input
type="text" type="text"
bind:value={admin.username} bind:value={admin.username}
@ -118,7 +113,7 @@
size="sm" size="sm"
/></td /></td
> >
<td <td on:mousedown={(e) => resizeTableColumn(e, 5)}
><Input ><Input
type="password" type="password"
bind:value={admin.password} bind:value={admin.password}
@ -127,14 +122,14 @@
size="sm" size="sm"
/></td /></td
> >
<td <td on:mousedown={(e) => resizeTableColumn(e, 5)}
><Badges ><Badges
bind:value={admin.permissions} bind:value={admin.permissions}
available={allPermissionBadges} available={allPermissionBadges}
disabled={!permissions.adminWrite() || !admin.edit} disabled={!permissions.adminWrite() || !admin.edit}
/></td /></td
> >
<td> <td on:mousedown={(e) => resizeTableColumn(e, 5)}>
<div> <div>
{#if admin.edit} {#if admin.edit}
<span class="w-min" class:cursor-not-allowed={!permissions.adminWrite()}> <span class="w-min" class:cursor-not-allowed={!permissions.adminWrite()}>
@ -245,3 +240,28 @@
<ErrorToast show={errorMessage !== ''}> <ErrorToast show={errorMessage !== ''}>
<span /> <span />
</ErrorToast> </ErrorToast>
<style lang="scss">
thead tr th,
tbody tr td {
@apply relative;
&:not(:first-child) {
@apply border-l-[1px] border-dashed;
&::before {
@apply absolute left-0 bottom-0 h-full w-[5px] cursor-col-resize;
content: '';
}
}
&:not(:last-child) {
@apply border-r-[1px] border-dashed border-base-300;
&::after {
@apply absolute right-0 bottom-0 h-full w-[5px] cursor-col-resize;
content: '';
}
}
}
</style>

View File

@ -34,6 +34,8 @@ export const POST = (async ({ request, cookies }) => {
permissions: new Permissions(permissions) permissions: new Permissions(permissions)
}); });
delete admin.dataValues.password;
return new Response(JSON.stringify(admin), { return new Response(JSON.stringify(admin), {
status: 201 status: 201
}); });

View File

@ -1,11 +1,11 @@
import adapter from '@sveltejs/adapter-node'; import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/kit/vite'; import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors // Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors // for more information about preprocessors
preprocess: vitePreprocess(), preprocess: preprocess(),
kit: { kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.