aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/Alerts.svelte
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Alerts.svelte')
-rw-r--r--src/lib/Alerts.svelte184
1 files changed, 184 insertions, 0 deletions
diff --git a/src/lib/Alerts.svelte b/src/lib/Alerts.svelte
new file mode 100644
index 0000000..1e4641b
--- /dev/null
+++ b/src/lib/Alerts.svelte
@@ -0,0 +1,184 @@
+<!--
+MIT License
+
+Copyright (c) 2025 memdmp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ -->
+
+<script lang="ts" module>
+ let ids = $state([] as number[]);
+ let idCtr = $state(0);
+ let dialogObject = $state({
+ open: false,
+ title: '',
+ content: '',
+ closeBtn: false,
+ buttons: [
+ {
+ text: 'Close',
+ },
+ ] as {
+ text: string;
+ callback?: () =>
+ | {
+ success: true;
+ content: string;
+ }
+ | {
+ success: false;
+ content: any;
+ };
+ }[],
+ });
+ let closeAlert = (m: string) => void 0;
+ let cancelAlert = (err?: any) => void 0;
+ let dialogPromises = [] as Promise<string>[];
+ let dialogPromise = Promise.resolve('');
+ const createDialogPromise = (setAlertTo?: typeof dialogObject) => (
+ dialogPromises.push(
+ (dialogPromise = new Promise<string>((rs, rj) => {
+ let oldCloseAlert = closeAlert;
+ let oldCancelAlert = cancelAlert;
+ closeAlert = (m) => {
+ closeAlert = oldCloseAlert;
+ dialogObject = setAlertTo ?? dialogObject;
+ rs(m);
+ };
+ cancelAlert = (m) => {
+ cancelAlert = oldCancelAlert;
+ dialogObject = setAlertTo ?? dialogObject;
+ rj(m);
+ };
+ }))
+ ),
+ dialogPromise
+ );
+ export const alert = async (
+ message: string,
+ options?: {
+ title?: string;
+ /** prioritise over existing alerts */
+ priority?: boolean;
+ /** if we can close the dialog with the x at the top */
+ canClose?: boolean;
+ /** button list */
+ buttons?: typeof dialogObject.buttons;
+ }
+ ) => {
+ if (!options?.priority) await Promise.allSettled(dialogPromises);
+ const oldAlert = dialogObject;
+ dialogObject = {
+ open: true,
+ title: options?.title ?? 'Alert',
+ closeBtn: options?.canClose ?? true,
+ buttons: options?.buttons ?? [
+ {
+ text: 'Close',
+ },
+ ],
+ content: message,
+ };
+ return createDialogPromise(oldAlert);
+ };
+ export const confirm = async (
+ message: string,
+ options?: Omit<Parameters<typeof alert>[1], 'buttons'>
+ ) => {
+ const rs = await alert(message, {
+ title: 'Confirmation',
+ ...(options ?? {}),
+ buttons: [
+ {
+ text: 'OK',
+ callback: () => ({
+ success: true,
+ content: 'OK',
+ }),
+ },
+ {
+ text: 'Cancel',
+ callback: () => ({
+ success: true,
+ content: 'Cancel',
+ }),
+ },
+ ],
+ });
+ return rs === 'OK';
+ };
+</script>
+
+<script lang="ts">
+ import { onDestroy, onMount } from 'svelte';
+
+ let dialog = $state(null as HTMLDialogElement | null);
+ let id: number = $state(-1);
+ onMount(() => {
+ id = idCtr++;
+ ids.push(id);
+ });
+ onDestroy(() => (ids = ids.filter((v) => v !== id)));
+ $effect(() => {
+ dialog?.showModal();
+ });
+</script>
+
+{#if ids[0] === id && dialogObject.open}
+ <section>
+ <dialog bind:this={dialog}>
+ <form method="dialog">
+ <div class="heading">
+ <div class="wrapper">
+ <div class="icon"></div>
+ <p class="text">{dialogObject.title}</p>
+ </div>
+ <button
+ class="close"
+ aria-label="Close"
+ disabled={!dialogObject.closeBtn}
+ onclick={() => {
+ cancelAlert(new Error('User Cancelled Alert'));
+ }}
+ ></button>
+ </div>
+ <div class="content">
+ {dialogObject.content}
+ </div>
+ <menu class="footer-btns" style="gap: 4px; display: flex;">
+ {#each dialogObject.buttons as button}
+ <button
+ onclick={(e) => {
+ const callback =
+ button.callback ??
+ (() => ({
+ success: true,
+ content: button.text,
+ }));
+ const result = callback();
+ if (result.success) closeAlert(result.content);
+ else cancelAlert(result.content);
+ }}>{button.text}</button
+ >
+ {/each}
+ </menu>
+ </form>
+ </dialog>
+ </section>
+{/if}