[web] Improve modals
This commit is contained in:
parent
863254f846
commit
bb722b437e
76
web/src/lib/ConfirmModal.svelte
Normal file
76
web/src/lib/ConfirmModal.svelte
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import type bootstrap from 'bootstrap';
|
||||||
|
import { beforeNavigate } from '$app/navigation';
|
||||||
|
|
||||||
|
export let modalTitle = 'Confirm';
|
||||||
|
export let modalText = 'Are you sure?';
|
||||||
|
|
||||||
|
let confirmModal: HTMLDivElement;
|
||||||
|
let modal: bootstrap.Modal | undefined;
|
||||||
|
|
||||||
|
let confirmAction: (() => void) | undefined;
|
||||||
|
let cancelAction: (() => void) | undefined;
|
||||||
|
|
||||||
|
export function confirm() {
|
||||||
|
if (confirmAction !== undefined) {
|
||||||
|
confirmAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancel() {
|
||||||
|
if (cancelAction !== undefined) {
|
||||||
|
cancelAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function prompt(text = 'Are you sure?', title = 'Confirm'): Promise<boolean> {
|
||||||
|
modalText = text;
|
||||||
|
modalTitle = title;
|
||||||
|
if (modal === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
modal.show();
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
confirmAction = () => {
|
||||||
|
resolve(true);
|
||||||
|
modal?.hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelAction = () => {
|
||||||
|
resolve(false);
|
||||||
|
modal?.hide();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const bootstrap = await import('bootstrap');
|
||||||
|
modal = new bootstrap.Modal(confirmModal);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeNavigate(() => {
|
||||||
|
modal?.hide();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={confirmModal} class="modal fade" tabindex="-1" data-bs-backdrop="static">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="exampleModalLabel">{modalTitle}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{#if $$slots.default}
|
||||||
|
<slot />
|
||||||
|
{:else}
|
||||||
|
{modalText}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button on:click={cancel} type="button" class="btn btn-secondary">Cancel</button>
|
||||||
|
<button on:click={confirm} type="button" class="btn btn-primary">Confirm</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
46
web/src/lib/FormAlert.svelte
Normal file
46
web/src/lib/FormAlert.svelte
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { afterUpdate } from 'svelte';
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
|
let dismissed = false;
|
||||||
|
let success = false;
|
||||||
|
let message: string | undefined;
|
||||||
|
let manualPopup = false;
|
||||||
|
|
||||||
|
$: if ($page.form !== null) {
|
||||||
|
manualPopup = false;
|
||||||
|
dismissed = false;
|
||||||
|
success = $page.form.success;
|
||||||
|
message = $page.form.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
if ($page.form !== null) {
|
||||||
|
success = $page.form.success;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function popup(data: { success: boolean; message?: string }) {
|
||||||
|
dismissed = false;
|
||||||
|
manualPopup = true;
|
||||||
|
success = data.success;
|
||||||
|
message = data.message;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if ($page.form !== null && dismissed === false) || (manualPopup === true && dismissed === false)}
|
||||||
|
<div
|
||||||
|
transition:slide|local
|
||||||
|
class={`mt-2 mb-2 alert alert-dismissible alert-${success ? 'success' : 'danger'}`}
|
||||||
|
>
|
||||||
|
{success ? 'Success' : message ?? 'Unknown error'}
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
dismissed = true;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
class="btn-close"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
@ -8,13 +8,11 @@
|
|||||||
<title>Contests</title>
|
<title>Contests</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4"><i class="bi bi-flag"></i> Contests</h1>
|
<h1 style="text-align:center" class="mb-1"><i class="bi bi-flag"></i> Contests</h1>
|
||||||
|
|
||||||
<div class="row">
|
<div class="d-flex flex-row justify-content-end">
|
||||||
<div class="text-end">
|
|
||||||
<a href="/admin/contests/create" class="btn btn-outline-success">Create</a>
|
<a href="/admin/contests/create" class="btn btn-outline-success">Create</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-3 list-group">
|
<div class="mt-3 list-group">
|
||||||
{#each data.contests as contest}
|
{#each data.contests as contest}
|
||||||
|
@ -1,26 +1,29 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import type { Actions, PageData } from './$types';
|
import ConfirmModal from '$lib/ConfirmModal.svelte';
|
||||||
|
import FormAlert from '$lib/FormAlert.svelte';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
export let form: Actions;
|
|
||||||
|
let confirmModal: ConfirmModal;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>{data.name}</title>
|
<title>{data.name}</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
|
<ConfirmModal bind:this={confirmModal} />
|
||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4">{data.name}</h1>
|
<h1 style="text-align:center" class="mb-4">{data.name}</h1>
|
||||||
|
|
||||||
|
<FormAlert />
|
||||||
|
|
||||||
{#if data.activeTeams !== 0}
|
{#if data.activeTeams !== 0}
|
||||||
<div class="alert alert-success">In Progress</div>
|
<div class="alert alert-success">In Progress</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if form && !form.success}
|
|
||||||
<div class="alert alert-danger">An error occured</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<a href="/admin/contests" class="btn btn-outline-primary">All Contests</a>
|
<a href="/admin/contests" class="btn btn-outline-primary">All Contests</a>
|
||||||
@ -28,12 +31,12 @@
|
|||||||
<div class="col-6 text-end">
|
<div class="col-6 text-end">
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
use:enhance={({ cancel }) => {
|
use:enhance={async ({ cancel }) => {
|
||||||
if (!confirm('Are you sure?')) {
|
if ((await confirmModal.prompt('Are you sure?')) !== true) {
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
return async ({ update }) => {
|
return async ({ update }) => {
|
||||||
update();
|
await update();
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import FormAlert from '$lib/FormAlert.svelte';
|
||||||
import type { Actions, PageData } from './$types';
|
import type { Actions, PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
@ -39,14 +40,12 @@
|
|||||||
<title>Create Contest</title>
|
<title>Create Contest</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4">Create Contest</h1>
|
<h1 style="text-align:center" class="mb-4"><i class="bi bi-flag"></i> Create Contest</h1>
|
||||||
|
|
||||||
|
<FormAlert />
|
||||||
|
|
||||||
<a href="/admin/contests" class="mb-3 btn btn-outline-secondary">Cancel</a>
|
<a href="/admin/contests" class="mb-3 btn btn-outline-secondary">Cancel</a>
|
||||||
|
|
||||||
{#if form && !form.success}
|
|
||||||
<div class="alert alert-danger">Invalid entry</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<form method="POST" action="?/create" use:enhance>
|
<form method="POST" action="?/create" use:enhance>
|
||||||
<h4>Name</h4>
|
<h4>Name</h4>
|
||||||
<input name="name" class="form-control" />
|
<input name="name" class="form-control" />
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { stretchTextarea } from '$lib/util';
|
import { stretchTextarea } from '$lib/util';
|
||||||
|
import FormAlert from '$lib/FormAlert.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
export let form: Actions;
|
export let form: Actions;
|
||||||
@ -58,11 +59,7 @@
|
|||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4">Review Submission</h1>
|
<h1 style="text-align:center" class="mb-4">Review Submission</h1>
|
||||||
|
|
||||||
{#if form && !form.success}
|
<FormAlert />
|
||||||
<div class="alert alert-danger">Submission was not successful</div>
|
|
||||||
{:else if form && form.success}
|
|
||||||
<div class="alert alert-success">Success!</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="mb-3 col">
|
<div class="mb-3 col">
|
||||||
<a href="/admin/reviews" class="btn btn-outline-primary">All Reviews</a>
|
<a href="/admin/reviews" class="btn btn-outline-primary">All Reviews</a>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<title>Problems</title>
|
<title>Problems</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4"><i class="bi bi-question-circle"></i> Problems</h1>
|
<h1 style="text-align:center" class="mb-1"><i class="bi bi-question-circle"></i> Problems</h1>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
import ConfirmModal from '$lib/ConfirmModal.svelte';
|
||||||
import { stretchTextarea } from '$lib/util';
|
import { stretchTextarea } from '$lib/util';
|
||||||
import type { Actions, PageData } from './$types';
|
import type { Actions, PageData } from './$types';
|
||||||
|
|
||||||
@ -11,7 +12,7 @@
|
|||||||
export let form: Actions;
|
export let form: Actions;
|
||||||
|
|
||||||
async function deleteProblem() {
|
async function deleteProblem() {
|
||||||
const sure = confirm('Are you sure?');
|
const sure = await confirmModal.prompt('Are you sure?');
|
||||||
if (!sure) {
|
if (!sure) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -23,9 +24,15 @@
|
|||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let confirmModal: ConfirmModal;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4">{data.problemData.friendlyName}</h1>
|
<ConfirmModal bind:this={confirmModal} />
|
||||||
|
|
||||||
|
<h1 style="text-align:center" class="mb-1">
|
||||||
|
<i class="bi bi-question-circle"></i> Problem - {data.problemData.friendlyName}
|
||||||
|
</h1>
|
||||||
|
|
||||||
{#if error}
|
{#if error}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
<title>Reviews</title>
|
<title>Reviews</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4"><i class="bi bi-eye"></i> Reviews</h1>
|
<h1 style="text-align:center" class="mb-1"><i class="bi bi-eye"></i> Reviews</h1>
|
||||||
|
|
||||||
<div class="mb-3 text-end">
|
<div class="mb-3 text-end">
|
||||||
{#if updating}
|
{#if updating}
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<title>Admin Scoreboard</title>
|
<title>Admin Scoreboard</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4"><i class="bi bi-trophy"></i> Admin Scoreboards</h1>
|
<h1 style="text-align:center" class="mb-1"><i class="bi bi-trophy"></i> Admin Scoreboards</h1>
|
||||||
|
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
{#if updating}
|
{#if updating}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'diff2html/bundles/css/diff2html.min.css';
|
import 'diff2html/bundles/css/diff2html.min.css';
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { stretchTextarea } from '$lib/util';
|
import { stretchTextarea } from '$lib/util';
|
||||||
|
import ConfirmModal from '$lib/ConfirmModal.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
export let form: Actions;
|
export let form: Actions;
|
||||||
@ -25,12 +26,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let confirmModal: ConfirmModal;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Submission</title>
|
<title>Submission</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
|
<ConfirmModal bind:this={confirmModal} />
|
||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4">Submission</h1>
|
<h1 style="text-align:center" class="mb-4">Submission</h1>
|
||||||
|
|
||||||
{#if form && !form.success}
|
{#if form && !form.success}
|
||||||
@ -45,12 +50,12 @@
|
|||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
action="?/delete"
|
action="?/delete"
|
||||||
use:enhance={({ cancel }) => {
|
use:enhance={async ({ cancel }) => {
|
||||||
if (!confirm('Are you sure?')) {
|
if ((await confirmModal.prompt('Are you sure?')) !== true) {
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
return async ({ update }) => {
|
return async ({ update }) => {
|
||||||
update();
|
await update();
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<title>Teams</title>
|
<title>Teams</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4"><i class="bi bi-people"></i> Teams</h1>
|
<h1 style="text-align:center" class="mb-1"><i class="bi bi-people"></i> Teams</h1>
|
||||||
|
|
||||||
{#if form && !form.success}
|
{#if form && !form.success}
|
||||||
<div class="alert alert-danger">Invalid action</div>
|
<div class="alert alert-danger">Invalid action</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
|
import ConfirmModal from '$lib/ConfirmModal.svelte';
|
||||||
import { genPassword } from '../util';
|
import { genPassword } from '../util';
|
||||||
import type { Actions, PageData } from './$types';
|
import type { Actions, PageData } from './$types';
|
||||||
|
|
||||||
@ -16,12 +17,16 @@
|
|||||||
const passEntry = document.getElementById('pass_entry') as HTMLInputElement;
|
const passEntry = document.getElementById('pass_entry') as HTMLInputElement;
|
||||||
passEntry.value = genPassword();
|
passEntry.value = genPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let confirmModal: ConfirmModal;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Team</title>
|
<title>Team</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
|
<ConfirmModal bind:this={confirmModal} />
|
||||||
|
|
||||||
<h1 style="text-align:center" class="mb-4">{data.team.name}</h1>
|
<h1 style="text-align:center" class="mb-4">{data.team.name}</h1>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -32,12 +37,12 @@
|
|||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
action="?/delete"
|
action="?/delete"
|
||||||
use:enhance={({ cancel }) => {
|
use:enhance={async ({ cancel }) => {
|
||||||
if (!confirm('Are you sure?')) {
|
if ((await confirmModal.prompt('Are you sure?')) !== true) {
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
return async ({ update }) => {
|
return async ({ update }) => {
|
||||||
update();
|
await update();
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -7,6 +7,9 @@ const config = {
|
|||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
adapter: adapter()
|
adapter: adapter()
|
||||||
|
},
|
||||||
|
vitePlugin: {
|
||||||
|
inspector: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user