diff --git a/.changeset/dry-shoes-prove.md b/.changeset/dry-shoes-prove.md new file mode 100644 index 0000000..2a0ce53 --- /dev/null +++ b/.changeset/dry-shoes-prove.md @@ -0,0 +1,5 @@ +--- +"astro-vtbot": patch +--- + +Makes the implementation of `` more robust and adds configuration options for image and position diff --git a/components/LoadingIndicator.astro b/components/LoadingIndicator.astro index 38f381b..342d77c 100644 --- a/components/LoadingIndicator.astro +++ b/components/LoadingIndicator.astro @@ -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; --- + + + - diff --git a/components/ProgressBar.astro b/components/ProgressBar.astro index 7303074..433e100 100644 --- a/components/ProgressBar.astro +++ b/components/ProgressBar.astro @@ -15,9 +15,6 @@ export interface Props {} () => plugin.startShowingProgress(), () => plugin.stopShowingProgress() ); - - + // you can style the progressbar by defining the .swup-progress-bar class + diff --git a/components/loading-indicator.ts b/components/loading-indicator.ts index 7ad2efb..af42a9c 100644 --- a/components/loading-indicator.ts +++ b/components/loading-indicator.ts @@ -7,54 +7,81 @@ import { let show: () => void; let hide: () => void; -let ownIndicator: boolean = false; +let initializer: (() => void | Promise) | 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, 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); } diff --git a/components/template/LoadingIndicatorTemplate_CSS.astro b/components/template/LoadingIndicatorTemplate_CSS.astro index 910c539..f3fa6ca 100644 --- a/components/template/LoadingIndicatorTemplate_CSS.astro +++ b/components/template/LoadingIndicatorTemplate_CSS.astro @@ -3,17 +3,18 @@ import LoadingIndicator from 'astro-vtbot/components/LoadingIndicator.astro'; --- { - /* By rendering here, + /* By rendering here, you inherit the logic that sets the "loading" CSS class */ } { - /* 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 */ }
+