Skip to content

Commit

Permalink
make loading indicator more robust (#14)
Browse files Browse the repository at this point in the history
* make loading indicator more robust

* Update dry-shoes-prove.md
  • Loading branch information
martrapp authored Jan 30, 2024
1 parent c05f26f commit 877b6d6
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 51 deletions.
5 changes: 5 additions & 0 deletions .changeset/dry-shoes-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"astro-vtbot": patch
---

Makes the implementation of `<LoadingIndicator />` more robust and adds configuration options for image and position
25 changes: 18 additions & 7 deletions components/LoadingIndicator.astro
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
---
export interface Props {}
export interface Props {
top?: string;
bottom?: string;
left?: string;
right?: string;
src?: string;
}
const { top = '', bottom = '', left = '', right = '', src = '' } = Astro.props;
---

<meta name="vtbot-loading-indicator" content=`${top};${bottom};${left};${right};${src}` />
<script>
import { initialize, vtbotLoadingIndicator } from './loading-indicator';
const data = document.querySelectorAll("meta[name='vtbot-loading-indicator']");
const [top, bottom, left, right, src] = (data[data.length - 1] as HTMLMetaElement).content.split(
';'
);
initialize(() => vtbotLoadingIndicator({ top, bottom, left, right, src }), true);
</script>

<style is:global>
@layer vtbot {
@keyframes vtbot-blink {
Expand All @@ -23,8 +40,6 @@ export interface Props {}

#vtbot-loading-indicator {
position: fixed;
top: 3vh;
right: 3vw;
width: min(10vw, 10vh);
z-index: 1000;
opacity: 0;
Expand All @@ -39,7 +54,3 @@ export interface Props {}
}
}
</style>
<script>
import { init } from './loading-indicator';
init(true);
</script>
7 changes: 2 additions & 5 deletions components/ProgressBar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ export interface Props {}
() => plugin.startShowingProgress(),
() => plugin.stopShowingProgress()
);
</script>

<style is:global>
.swup-progress-bar {
}
</style>
// you can style the progressbar by defining the .swup-progress-bar class
</script>
95 changes: 61 additions & 34 deletions components/loading-indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,81 @@ import {

let show: () => void;
let hide: () => void;
let ownIndicator: boolean = false;
let initializer: (() => void | Promise<void>) | undefined;

export function loading(newShow: () => void, newHide: () => void) {
init(false);
export function loading(newShow: () => void, newHide: () => void, newInit: () => void = () => {}) {
show = newShow;
hide = newHide;
initialize(newInit);
}

export async function ensureLoadingIndicator() {
const doShow = () => {
document.documentElement.classList.add(`loading`);
show && show();
};

const doHide = () => {
document.documentElement.classList.remove(`loading`);
hide && hide();
};

const doInit = () => {
initializer && initializer();
};

export function initialize(onPageLoad?: () => void | Promise<void>, lowPrio = false) {
if (!(initializer && lowPrio)) initializer = onPageLoad;
document.addEventListener(TRANSITION_PAGE_LOAD, doInit);
document.addEventListener(TRANSITION_BEFORE_PREPARATION, doShow);
document.addEventListener(TRANSITION_BEFORE_SWAP, doHide);
}

type Options = { src: string; top: string; bottom: string; left: string; right: string };

export async function vtbotLoadingIndicator(options: Options) {
const loadingIndicator = document.getElementById('vtbot-loading-indicator');
if (!loadingIndicator) {
const favicon = (document.querySelector(`link[rel="icon"]:last-of-type`) as HTMLLinkElement)
?.href;
let img: HTMLImageElement | SVGSVGElement | null;
if (favicon && favicon.endsWith('.svg')) {
if (loadingIndicator) return;

const favicon =
(options.src ||
(document.querySelector(`link[rel="icon"]:last-of-type`) as HTMLLinkElement)?.href) ??
'/favicon.ico';

let src = '';
try {
if (!(await fetch(favicon)).ok) throw new Error();
} catch (_) {
// not ok, or aborted in fetch
src =
'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"%3E%3Ccircle cx="50" cy="50" r="50" fill="%23888" /%3E%3C/svg%3E';
}

let img: HTMLImageElement | SVGSVGElement | null = null;

if (!src) {
if (favicon?.endsWith('.svg')) {
const response = await fetch(favicon);
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'image/svg+xml');
img = doc.querySelector('svg');
} else {
img = document.createElement('img');
img.src = favicon;
img.alt = 'Loading indicator';
src = favicon;
}
const div = document.createElement('div');
div.id = 'vtbot-loading-indicator';
div.appendChild(img);
document.body.appendChild(div);
}
}

const beforePreparation = () => {
if (!ownIndicator) ensureLoadingIndicator();
document.documentElement.classList.add(`loading`);
show && show();
};
if (!img) {
img = document.createElement('img');
img.src = src;
img.alt = 'Loading indicator';
}
const div = document.createElement('div');

const beforeSwap = (event: Event) => {
document.documentElement.classList.remove(`loading`);
hide && hide();
};
div.style[options.top || !options.bottom ? 'top' : 'bottom'] =
options.top || options.bottom || '3vh';
div.style[options.right || !options.left ? 'right' : 'left'] =
options.right || options.left || '3vw';

export function init(createIndicator: boolean = false) {
if (ownIndicator) return;
ownIndicator = !createIndicator;
document.addEventListener(TRANSITION_BEFORE_PREPARATION, beforePreparation);
document.addEventListener(TRANSITION_BEFORE_SWAP, beforeSwap);
createIndicator && document.addEventListener(TRANSITION_PAGE_LOAD, ensureLoadingIndicator);
!createIndicator && document.removeEventListener(TRANSITION_PAGE_LOAD, ensureLoadingIndicator);
div.id = 'vtbot-loading-indicator';
div.appendChild(img!);
document.body.appendChild(div);
}
5 changes: 3 additions & 2 deletions components/template/LoadingIndicatorTemplate_CSS.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import LoadingIndicator from 'astro-vtbot/components/LoadingIndicator.astro';
---

{
/* By rendering <LoadingIndicator/> here,
/* By rendering <LoadingIndicator/> here,
you inherit the logic that sets the "loading" CSS class */
}
<LoadingIndicator />

{
/* Defining this div is only necessary
/* Defining this div is only necessary
if you do not want to use the default element that holds the favicon image */
}
<div id="vtbot-loading-indicator">
<!-- make sure to use this id -->
<!-- or you might end up with two indicators -->
<!-- ... -->
<div>
<style is:global>
Expand Down
22 changes: 20 additions & 2 deletions components/template/LoadingIndicatorTemplate_JS.astro
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
---
// just define javascript or typescript code for hide & show functions in the script below
// Just define javascript or typescript code for hide & show functions
// in the script below.
// You also have to provide the HTML for the loading indicator.
// Include it on your pages, preferable via a common layout
// or - if it is simple - generate it dynamically in the `show` function.
// If you have time consuming setup tasks, you should not run them in `show`.
// It is better to move them to the optional `initializer` function,
// which is executed shortly after a page has been loaded,
// and not when the indicator is already to be displayed
// You can generate the default loading indicator of the back of tricks
// by calling `vtbotLoadingIndicator()`, preferably in the `initializer` function.
---

<script>
import { loading } from 'astro-vtbot/components/loading-indicator';
loading(show, hide);
loading(show, hide, initializer);

function show() {
// show loading indicator
}
function hide() {
// hide loading indicator
}

function initializer() {
// this function is optional
// remove the init parameter in the call to loading above if you do not need it
}
</script>
2 changes: 1 addition & 1 deletion test/fixture/src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const { title } = Astro.props;
<ViewTransitions />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, minimum-scale=1, viewport-fit=cover" />
<link rel="icon" type="image/svg+xml" href="/favicon.png" />
<link rel="icon" href="/favicon.png" />
<title>{title}</title>
</head>
<body>
Expand Down

0 comments on commit 877b6d6

Please sign in to comment.