diff --git a/package.json b/package.json index 13470f0..308b4b4 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "@tanstack/svelte-query": "^4.35.0", "@vincjo/datatables": "^1.12.4", "axios": "^1.5.0", + "lucide-svelte": "^0.274.0", + "svelte-forms-lib": "^2.0.1", "svelte-local-storage-store": "^0.6.0", "svelte-sonner": "^0.1.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 541643e..05fa01d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ dependencies: axios: specifier: ^1.5.0 version: 1.5.0 + lucide-svelte: + specifier: ^0.274.0 + version: 0.274.0(svelte@4.2.0) + svelte-forms-lib: + specifier: ^2.0.1 + version: 2.0.1 svelte-local-storage-store: specifier: ^0.6.0 version: 0.6.0(svelte@4.2.0) @@ -1496,6 +1502,14 @@ packages: yallist: 4.0.0 dev: true + /lucide-svelte@0.274.0(svelte@4.2.0): + resolution: {integrity: sha512-+8bKZyOdadiaksrGOftNRfz52PMcddSmNDd1invTrqLTaAg00mrVrHuh6hHypFCpQhZLKrsEHqHl/TwITe/VsA==} + peerDependencies: + svelte: '>=3 <5' + dependencies: + svelte: 4.2.0 + dev: false + /magic-string@0.27.0: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} @@ -2075,6 +2089,12 @@ packages: svelte: 4.2.0 dev: true + /svelte-forms-lib@2.0.1: + resolution: {integrity: sha512-kwbJ3ynsepsrrJyAMrvSc0Lj/myc9vfI2DL8OKxgArZimrNYsRh1gENYhvrcKEI3BiZrv8q3VFfmGo/GMyk7Zg==} + dependencies: + dequal: 2.0.3 + dev: false + /svelte-hmr@0.15.3(svelte@4.2.0): resolution: {integrity: sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==} engines: {node: ^12.20 || ^14.13.1 || >= 16} diff --git a/src/components/commandList.svelte b/src/components/commandList.svelte index 1988f0c..2be17e1 100644 --- a/src/components/commandList.svelte +++ b/src/components/commandList.svelte @@ -5,208 +5,166 @@ import { createQuery, useQueryClient } from '@tanstack/svelte-query'; import { toast } from 'svelte-sonner'; import { goto } from '$app/navigation'; - import { DataHandler, Th, Datatable, ThFilter } from '@vincjo/datatables' - // headers for the table + import './list.css' + + // custom icons + import RefreshCw from './icons/RefreshCW.svelte'; + import Check from './icons/Check.svelte'; + import Trash from './icons/Trash.svelte'; + import Pen from './icons/Pen.svelte'; + import { browser } from '$app/environment'; + + import { fly, fade, slide } from 'svelte/transition'; + + // headers for the table let headers: string[] = ['Name', 'Type', 'Description']; // get basePath since same component can be used to get guild commands export let basePath = ''; export let id = 'global'; - const queryClient = useQueryClient() + const queryClient = useQueryClient(); $: queryKey = ['app.commands', id]; $: refreshing = false; $: commandList = createQuery({ queryKey, - queryFn: async () => (await fetchAPI(basePath)).data + queryFn: async () => (await fetchAPI(basePath)).data, + // disable automatic fetching in when not in browser (messes up dev and static builds) + enabled: browser }); - $: loading = $commandList.isLoading; - $: error = $commandList.isError; - $: fetching = $commandList.isFetching; - $: data = $commandList.data || []; + $: data = ($commandList.data || []) as DiscordInteraction[]; + $: deletionConfirmPending = [] as string[] - $: handler = new DataHandler(data, { rowsPerPage: 10 }) - $: rows = handler.getRows() + /** Functions */ + const addLink = () => goto(`${base}/add${id === 'global' ? '' : `?guildId=${id}`}`) // delete a interaction async function deleteInteraction(interaction: DiscordInteraction) { + if(deletionConfirmPending.includes(interaction.id)) return toast.error(`Previous deletion confirmation is active.`) + deletionConfirmPending = deletionConfirmPending.concat([interaction.id]) + + const removeFromPending = () => { deletionConfirmPending = deletionConfirmPending.filter((id) => id !== interaction.id) } // uses toast for confirmation toast.warning( - `Are you sure you want to delete "${interaction.name}" ${typeToName(interaction.type)} Interaction?`, + `Are you sure you want to delete "${interaction.name}" ${typeToName( + interaction.type + )} Interaction?`, { action: { label: 'Delete', onClick: () => { toast.promise( - fetchAPI(`${basePath}/${interaction.id}`, { method: 'DELETE' }).then(refreshList), + fetchAPI(`${basePath}/${interaction.id}`, { method: 'DELETE' }).then(() => { refreshList(); removeFromPending();}), { - success: `${typeToName(interaction.type)} (${interaction.name}) successfully deleted`, + success: `${typeToName(interaction.type)} (${ + interaction.name + }) successfully deleted`, error: 'Error occurred, try again', loading: 'Deletion pending...' } as any ); - } - } + }, + onAutoClose: removeFromPending, + onDismiss: removeFromPending, + duration: 20000 } ); } - + async function refreshList() { - if(refreshing) return; + if (refreshing) return; refreshing = true; - queryClient.invalidateQueries({ queryKey }) + queryClient.invalidateQueries({ queryKey }); setTimeout(() => { // cooldown - refreshing = false + refreshing = false; }, 2000); - } - - - - - {#each headers as header (header)} - + +
+ + {data.length} Interaction +
+ + {#if $commandList.isLoading} + loading... + {:else if $commandList.isFetching} + + {:else if $commandList.isError} +

(╯°□°)╯︵ ┻━┻ | Error!!

+ {:else} + + {/if} +
+
+ +
- - - {#each headers as header (header)} - {header} - {/each} - - - - - - {#if loading} - - - - {:else if error} - - - - {:else if $rows.length == 0} - - - - {:else} - - {#each $rows as row (row.id)} - - - - - - + {/each} {/if} - - - - - - -
{header} - - -
- -
- loading... -
-
-

(╯°□°)╯︵ ┻━┻

- Oops! Something went wrong. + + +
+ +
+
+

Create a new interaction

+

Click here to create a new interaction!

+
+ {#if $commandList.isLoading} + {#each Array.from({ length: 3 }, (_, index) => index) as cardId (cardId)} +
+
+
+
+
+
+
+
-
-
-

¯\_(ツ)_/¯

- Nothing to display here. +
+
-
{row.name}{typeToName(row.type)}{row.description} - - -
- {#if fetching && !loading} - - {:else if !loading && !error} - + {#each data as interaction (interaction.id)} +
+

{interaction.name}

+ {typeToName(interaction.type)} +

{interaction.description}

+ {#if !deletionConfirmPending.includes(interaction.id)} + + + + {/if} -
-
+ + {/each} + + diff --git a/src/components/icons/Check.svelte b/src/components/icons/Check.svelte new file mode 100644 index 0000000..c8e5218 --- /dev/null +++ b/src/components/icons/Check.svelte @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/src/components/icons/Pen.svelte b/src/components/icons/Pen.svelte new file mode 100644 index 0000000..a9d2865 --- /dev/null +++ b/src/components/icons/Pen.svelte @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/src/components/icons/RefreshCW.svelte b/src/components/icons/RefreshCW.svelte new file mode 100644 index 0000000..02fac20 --- /dev/null +++ b/src/components/icons/RefreshCW.svelte @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/src/components/icons/Trash.svelte b/src/components/icons/Trash.svelte new file mode 100644 index 0000000..d013764 --- /dev/null +++ b/src/components/icons/Trash.svelte @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/src/components/list.css b/src/components/list.css new file mode 100644 index 0000000..9061384 --- /dev/null +++ b/src/components/list.css @@ -0,0 +1,12 @@ +@keyframes shake { + 0% { transform: rotate(0deg); } + 10% { transform: rotate(-1deg); } + 25% { transform: rotate(-2deg); } + 50% { transform: rotate(2deg); } + 75% { transform: rotate(0deg); } + 90%, 100% { transform: rotate(0deg); } +} + +.animate-shake { + animation: shake 1s ease-in-out infinite; /* Adjust duration as needed */ +} \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 0a33f72..a032e2d 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -11,7 +11,7 @@ -
+
diff --git a/src/routes/add/+page.svelte b/src/routes/add/+page.svelte index 0831511..ad019f1 100644 --- a/src/routes/add/+page.svelte +++ b/src/routes/add/+page.svelte @@ -2,18 +2,21 @@ import {browser} from "$app/environment"; import { base } from '$app/paths'; import { page } from '$app/stores'; + import { typeToName } from "$lib/constants"; + + let interactionType = 1;
-

Add

+

Add new {typeToName(interactionType)} Interaction

- + + +
\ No newline at end of file diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index 205228f..e2e2fad 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -40,7 +40,7 @@

-
+
{#if guildId && discordIdRegex.test(guildId)}

View Global List