Show loading spinners for form actions and page loads

This commit is contained in:
thiloho
2024-09-07 14:28:23 +02:00
parent e153120a47
commit 958b8e3643
13 changed files with 193 additions and 6 deletions

View File

@@ -0,0 +1,32 @@
<div class="spinner"></div>
<style>
@keyframes spinner {
to {
transform: rotate(360deg);
}
}
.spinner {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 40;
}
.spinner::before {
content: "";
position: absolute;
inline-size: 4rem;
block-size: 4rem;
inset-block-start: 50%;
inset-inline-start: 50%;
margin-block-start: -2rem;
margin-inline-start: -2rem;
border-radius: 50%;
border: var(--border-primary);
border-width: 0.125rem;
border-block-start-color: var(--color-accent);
animation: spinner 0.6s linear infinite;
}
</style>

View File

@@ -2,13 +2,29 @@
import { enhance } from "$app/forms"; import { enhance } from "$app/forms";
import SuccessOrError from "$lib/components/SuccessOrError.svelte"; import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import type { ActionData } from "./$types"; import type { ActionData } from "./$types";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
const { form }: { form: ActionData } = $props(); const { form }: { form: ActionData } = $props();
let sending = $state(false);
</script> </script>
<SuccessOrError success={form?.success} message={form?.message} /> <SuccessOrError success={form?.success} message={form?.message} />
<form method="POST" use:enhance> {#if sending}
<LoadingSpinner />
{/if}
<form
method="POST"
use:enhance={() => {
sending = true;
return async ({ update }) => {
await update();
sending = false;
};
}}
>
<label> <label>
Username: Username:
<input type="text" name="username" required /> <input type="text" name="username" required />

View File

@@ -2,13 +2,29 @@
import { enhance } from "$app/forms"; import { enhance } from "$app/forms";
import SuccessOrError from "$lib/components/SuccessOrError.svelte"; import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import type { ActionData } from "./$types"; import type { ActionData } from "./$types";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
const { form }: { form: ActionData } = $props(); const { form }: { form: ActionData } = $props();
let sending = $state(false);
</script> </script>
<SuccessOrError success={form?.success} message={form?.message} /> <SuccessOrError success={form?.success} message={form?.message} />
<form method="POST" use:enhance> {#if sending}
<LoadingSpinner />
{/if}
<form
method="POST"
use:enhance={() => {
sending = true;
return async ({ update }) => {
await update();
sending = false;
};
}}
>
<label> <label>
Username: Username:
<input type="text" name="username" minlength="3" maxlength="16" required /> <input type="text" name="username" minlength="3" maxlength="16" required />

View File

@@ -5,12 +5,19 @@
import Modal from "$lib/components/Modal.svelte"; import Modal from "$lib/components/Modal.svelte";
import SuccessOrError from "$lib/components/SuccessOrError.svelte"; import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import type { ActionData, PageServerData } from "./$types"; import type { ActionData, PageServerData } from "./$types";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
const { form, data }: { form: ActionData; data: PageServerData } = $props(); const { form, data }: { form: ActionData; data: PageServerData } = $props();
let sending = $state(false);
</script> </script>
<SuccessOrError success={form?.success} message={form?.message} /> <SuccessOrError success={form?.success} message={form?.message} />
{#if sending}
<LoadingSpinner />
{/if}
<section id="create-website"> <section id="create-website">
<h2> <h2>
<a href="#create-website">Create website</a> <a href="#create-website">Create website</a>
@@ -23,9 +30,11 @@
method="POST" method="POST"
action="?/createWebsite" action="?/createWebsite"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update(); await update();
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >
@@ -111,9 +120,11 @@
method="POST" method="POST"
action="?/updateWebsite" action="?/updateWebsite"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update({ reset: false }); await update({ reset: false });
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >
@@ -144,9 +155,11 @@
method="POST" method="POST"
action="?/deleteWebsite" action="?/deleteWebsite"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update(); await update();
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >

View File

@@ -2,13 +2,20 @@
import { enhance } from "$app/forms"; import { enhance } from "$app/forms";
import Modal from "$lib/components/Modal.svelte"; import Modal from "$lib/components/Modal.svelte";
import SuccessOrError from "$lib/components/SuccessOrError.svelte"; import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
import type { ActionData, PageServerData } from "./$types"; import type { ActionData, PageServerData } from "./$types";
const { data, form }: { data: PageServerData; form: ActionData } = $props(); const { data, form }: { data: PageServerData; form: ActionData } = $props();
let sending = $state(false);
</script> </script>
<SuccessOrError success={form?.success} message={form?.message} /> <SuccessOrError success={form?.success} message={form?.message} />
{#if sending}
<LoadingSpinner />
{/if}
<section id="overview"> <section id="overview">
<h2> <h2>
<a href="#overview">Overview</a> <a href="#overview">Overview</a>
@@ -31,7 +38,17 @@
<a href="#logout">Logout</a> <a href="#logout">Logout</a>
</h2> </h2>
<form method="POST" action="?/logout" use:enhance> <form
method="POST"
action="?/logout"
use:enhance={() => {
sending = true;
return async ({ update }) => {
await update();
sending = false;
};
}}
>
<button type="submit">Logout</button> <button type="submit">Logout</button>
</form> </form>
</section> </section>
@@ -53,9 +70,11 @@
method="POST" method="POST"
action="?/deleteAccount" action="?/deleteAccount"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update(); await update();
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >

View File

@@ -5,6 +5,7 @@
import SuccessOrError from "$lib/components/SuccessOrError.svelte"; import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import type { ActionData, LayoutServerData, PageServerData } from "./$types"; import type { ActionData, LayoutServerData, PageServerData } from "./$types";
import Modal from "$lib/components/Modal.svelte"; import Modal from "$lib/components/Modal.svelte";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
const { data, form }: { data: PageServerData & LayoutServerData; form: ActionData } = $props(); const { data, form }: { data: PageServerData & LayoutServerData; form: ActionData } = $props();
@@ -24,10 +25,16 @@
previewContent = newContent; previewContent = newContent;
} }
}; };
let sending = $state(false);
</script> </script>
<SuccessOrError success={form?.success} message={form?.message} /> <SuccessOrError success={form?.success} message={form?.message} />
{#if sending}
<LoadingSpinner />
{/if}
<WebsiteEditor <WebsiteEditor
id={data.website.id} id={data.website.id}
contentType={data.website.content_type} contentType={data.website.content_type}
@@ -44,8 +51,10 @@
method="POST" method="POST"
enctype="multipart/form-data" enctype="multipart/form-data"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update({ reset: false }); await update({ reset: false });
sending = false;
}; };
}} }}
> >
@@ -98,8 +107,10 @@
method="POST" method="POST"
enctype="multipart/form-data" enctype="multipart/form-data"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update({ reset: false }); await update({ reset: false });
sending = false;
}; };
}} }}
> >
@@ -148,8 +159,10 @@
action="?/updateHome" action="?/updateHome"
method="POST" method="POST"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update({ reset: false }); await update({ reset: false });
sending = false;
}; };
}} }}
> >
@@ -179,8 +192,10 @@
action="?/updateFooter" action="?/updateFooter"
method="POST" method="POST"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update({ reset: false }); await update({ reset: false });
sending = false;
}; };
}} }}
> >

View File

@@ -16,8 +16,6 @@ export const load: PageServerLoad = async ({ params, fetch, cookies, url, parent
baseFetchUrl += "&order=last_modified_at.desc,created_at.desc"; baseFetchUrl += "&order=last_modified_at.desc,created_at.desc";
} }
console.log(baseFetchUrl);
const parameters = new URLSearchParams(); const parameters = new URLSearchParams();
if (searchQuery) { if (searchQuery) {

View File

@@ -4,13 +4,20 @@
import { enhance } from "$app/forms"; import { enhance } from "$app/forms";
import Modal from "$lib/components/Modal.svelte"; import Modal from "$lib/components/Modal.svelte";
import SuccessOrError from "$lib/components/SuccessOrError.svelte"; import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
import type { ActionData, PageServerData } from "./$types"; import type { ActionData, PageServerData } from "./$types";
const { data, form }: { data: PageServerData; form: ActionData } = $props(); const { data, form }: { data: PageServerData; form: ActionData } = $props();
let sending = $state(false);
</script> </script>
<SuccessOrError success={form?.success} message={form?.message} /> <SuccessOrError success={form?.success} message={form?.message} />
{#if sending}
<LoadingSpinner />
{/if}
<WebsiteEditor <WebsiteEditor
id={data.website.id} id={data.website.id}
contentType={data.website.content_type} contentType={data.website.content_type}
@@ -29,9 +36,11 @@
method="POST" method="POST"
action="?/createArticle" action="?/createArticle"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update(); await update();
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >
@@ -126,9 +135,11 @@
method="POST" method="POST"
action="?/deleteArticle" action="?/deleteArticle"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update(); await update();
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >

View File

@@ -5,6 +5,7 @@
import SuccessOrError from "$lib/components/SuccessOrError.svelte"; import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import type { ActionData, PageServerData } from "./$types"; import type { ActionData, PageServerData } from "./$types";
import Modal from "$lib/components/Modal.svelte"; import Modal from "$lib/components/Modal.svelte";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
import { handleImagePaste } from "$lib/utils"; import { handleImagePaste } from "$lib/utils";
const { data, form }: { data: PageServerData; form: ActionData } = $props(); const { data, form }: { data: PageServerData; form: ActionData } = $props();
@@ -24,10 +25,16 @@
previewContent = newContent; previewContent = newContent;
} }
}; };
let sending = $state(false);
</script> </script>
<SuccessOrError success={form?.success} message={form?.message} /> <SuccessOrError success={form?.success} message={form?.message} />
{#if sending}
<LoadingSpinner />
{/if}
<WebsiteEditor <WebsiteEditor
id={data.website.id} id={data.website.id}
contentType={data.website.content_type} contentType={data.website.content_type}
@@ -46,8 +53,10 @@
action="?/editArticle" action="?/editArticle"
enctype="multipart/form-data" enctype="multipart/form-data"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update({ reset: false }); await update({ reset: false });
sending = false;
}; };
}} }}
> >

View File

@@ -3,13 +3,20 @@
import WebsiteEditor from "$lib/components/WebsiteEditor.svelte"; import WebsiteEditor from "$lib/components/WebsiteEditor.svelte";
import SuccessOrError from "$lib/components/SuccessOrError.svelte"; import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import Modal from "$lib/components/Modal.svelte"; import Modal from "$lib/components/Modal.svelte";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
import type { ActionData, PageServerData } from "./$types"; import type { ActionData, PageServerData } from "./$types";
const { data, form }: { data: PageServerData; form: ActionData } = $props(); const { data, form }: { data: PageServerData; form: ActionData } = $props();
let sending = $state(false);
</script> </script>
<SuccessOrError success={form?.success} message={form?.message} /> <SuccessOrError success={form?.success} message={form?.message} />
{#if sending}
<LoadingSpinner />
{/if}
<WebsiteEditor <WebsiteEditor
id={data.website.id} id={data.website.id}
contentType={data.website.content_type} contentType={data.website.content_type}
@@ -28,9 +35,11 @@
method="POST" method="POST"
action="?/createCategory" action="?/createCategory"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update(); await update();
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >
@@ -70,9 +79,11 @@
method="POST" method="POST"
action="?/updateCategory" action="?/updateCategory"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update({ reset: false }); await update({ reset: false });
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >
@@ -95,9 +106,11 @@
method="POST" method="POST"
action="?/deleteCategory" action="?/deleteCategory"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update(); await update();
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >

View File

@@ -3,13 +3,20 @@
import WebsiteEditor from "$lib/components/WebsiteEditor.svelte"; import WebsiteEditor from "$lib/components/WebsiteEditor.svelte";
import SuccessOrError from "$lib/components/SuccessOrError.svelte"; import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import Modal from "$lib/components/Modal.svelte"; import Modal from "$lib/components/Modal.svelte";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
import type { ActionData, PageServerData } from "./$types"; import type { ActionData, PageServerData } from "./$types";
const { data, form }: { data: PageServerData; form: ActionData } = $props(); const { data, form }: { data: PageServerData; form: ActionData } = $props();
let sending = $state(false);
</script> </script>
<SuccessOrError success={form?.success} message={form?.message} /> <SuccessOrError success={form?.success} message={form?.message} />
{#if sending}
<LoadingSpinner />
{/if}
<WebsiteEditor <WebsiteEditor
id={data.website.id} id={data.website.id}
contentType={data.website.content_type} contentType={data.website.content_type}
@@ -28,9 +35,11 @@
method="POST" method="POST"
action="?/addCollaborator" action="?/addCollaborator"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update(); await update();
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >
@@ -74,9 +83,11 @@
method="POST" method="POST"
action="?/updateCollaborator" action="?/updateCollaborator"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update({ reset: false }); await update({ reset: false });
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >
@@ -103,9 +114,11 @@
method="POST" method="POST"
action="?/removeCollaborator" action="?/removeCollaborator"
use:enhance={() => { use:enhance={() => {
sending = true;
return async ({ update }) => { return async ({ update }) => {
await update(); await update();
window.location.hash = "!"; window.location.hash = "!";
sending = false;
}; };
}} }}
> >

View File

@@ -3,14 +3,21 @@
import WebsiteEditor from "$lib/components/WebsiteEditor.svelte"; import WebsiteEditor from "$lib/components/WebsiteEditor.svelte";
import SuccessOrError from "$lib/components/SuccessOrError.svelte"; import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import type { ActionData, PageServerData } from "./$types"; import type { ActionData, PageServerData } from "./$types";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
const { data, form }: { data: PageServerData; form: ActionData } = $props(); const { data, form }: { data: PageServerData; form: ActionData } = $props();
const prodWebsiteUrl = data.websitePreviewUrl.replace("/previews", ""); const prodWebsiteUrl = data.websitePreviewUrl.replace("/previews", "");
let sending = $state(false);
</script> </script>
<SuccessOrError success={form?.success} message={form?.message} /> <SuccessOrError success={form?.success} message={form?.message} />
{#if sending}
<LoadingSpinner />
{/if}
<WebsiteEditor <WebsiteEditor
id={data.website.id} id={data.website.id}
contentType={data.website.content_type} contentType={data.website.content_type}
@@ -27,7 +34,17 @@
is published. If you are happy with the results, click the button below and your website will is published. If you are happy with the results, click the button below and your website will
be published on the Internet. be published on the Internet.
</p> </p>
<form method="POST" action="?/publishWebsite" use:enhance> <form
method="POST"
action="?/publishWebsite"
use:enhance={() => {
sending = true;
return async ({ update }) => {
await update();
sending = false;
};
}}
>
<button type="submit">Publish</button> <button type="submit">Publish</button>
</form> </form>

View File

@@ -3,9 +3,20 @@
import { page } from "$app/stores"; import { page } from "$app/stores";
import type { LayoutServerData } from "./$types"; import type { LayoutServerData } from "./$types";
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import { beforeNavigate, afterNavigate } from "$app/navigation";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
const { data, children }: { data: LayoutServerData; children: Snippet } = $props(); const { data, children }: { data: LayoutServerData; children: Snippet } = $props();
let loading = $state(false);
beforeNavigate(() => {
loading = true;
});
afterNavigate(() => {
loading = false;
});
const isProjectRoute = $derived($page.url.pathname.startsWith("/website") && !$page.error); const isProjectRoute = $derived($page.url.pathname.startsWith("/website") && !$page.error);
const routeName = $derived( const routeName = $derived(
$page.url.pathname === "/" $page.url.pathname === "/"
@@ -14,6 +25,10 @@
); );
</script> </script>
{#if loading}
<LoadingSpinner />
{/if}
<svelte:head> <svelte:head>
<title>archtika | {routeName.replaceAll("/", " - ")}</title> <title>archtika | {routeName.replaceAll("/", " - ")}</title>
</svelte:head> </svelte:head>