make admin table resizable
This commit is contained in:
parent
3e259872b3
commit
1fb71fe899
25
package-lock.json
generated
25
package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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)) {
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
});
|
});
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user