feat(pwa): add install PWA widget (#1451)

This commit is contained in:
Joaquín Sánchez 2023-01-25 16:26:51 +01:00 committed by GitHub
parent 3c888d3914
commit f96cdae1ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 73 additions and 1 deletions

View file

@ -42,6 +42,7 @@ const wideLayout = computed(() => route.meta.wideLayout ?? false)
<slot name="header" /> <slot name="header" />
</div> </div>
<div :class="{ 'xl:block': $route.name !== 'tag' }" hidden h-6 /> <div :class="{ 'xl:block': $route.name !== 'tag' }" hidden h-6 />
<PwaInstallPrompt lg:hidden />
<div :class="isHydrated && wideLayout ? 'xl:w-full sm:max-w-600px' : 'sm:max-w-600px md:shrink-0'" m-auto> <div :class="isHydrated && wideLayout ? 'xl:w-full sm:max-w-600px' : 'sm:max-w-600px md:shrink-0'" m-auto>
<slot /> <slot />
</div> </div>

View file

@ -0,0 +1,22 @@
<template>
<div
v-if="$pwa?.showInstallPrompt && !$pwa?.needRefresh"
m-2 p5 bg="primary-fade" relative
rounded-lg of-hidden
flex="~ col gap-3"
v-bind="$attrs"
>
<h2 flex="~ gap-2" items-center>
{{ $t('pwa.install_title') }}
</h2>
<div flex="~ gap-1">
<button type="button" btn-solid px-4 py-1 text-center text-sm @click="$pwa.install()">
{{ $t('pwa.install') }}
</button>
<button type="button" btn-text filter-saturate-0 px-4 py-1 text-center text-sm @click="$pwa.cancelInstall()">
{{ $t('pwa.dismiss') }}
</button>
</div>
<div i-material-symbols:install-desktop-rounded absolute text-6em bottom--2 inset-ie--2 text-primary dark:text-white op10 dark:op45 class="-z-1 rtl-flip" />
</div>
</template>

View file

@ -16,6 +16,6 @@
{{ $t('pwa.dismiss') }} {{ $t('pwa.dismiss') }}
</button> </button>
</div> </div>
<div i-ri-arrow-down-circle-line absolute text-8em bottom--10 inset-ie--10 text-primary op10 class="-z-1" /> <div i-ri-arrow-down-circle-line absolute text-8em bottom--10 inset-ie--10 text-primary dark:text-white op10 dark:op45 class="-z-1" />
</div> </div>
</template> </template>

View file

@ -65,6 +65,7 @@ const isGrayscale = usePreferences('grayscaleMode')
<div flex-auto /> <div flex-auto />
<PwaPrompt /> <PwaPrompt />
<PwaInstallPrompt />
<LazyCommonPreviewPrompt v-if="info.env === 'preview'" /> <LazyCommonPreviewPrompt v-if="info.env === 'preview'" />
<NavFooter /> <NavFooter />
</slot> </slot>

View file

@ -248,6 +248,8 @@
}, },
"pwa": { "pwa": {
"dismiss": "Dismiss", "dismiss": "Dismiss",
"install": "Install",
"install_title": "Install Elk",
"title": "New Elk update available!", "title": "New Elk update available!",
"update": "Update", "update": "Update",
"update_available_short": "Update Elk", "update_available_short": "Update Elk",

View file

@ -204,6 +204,8 @@
}, },
"pwa": { "pwa": {
"dismiss": "Descartar", "dismiss": "Descartar",
"install": "Instalar",
"install_title": "Instalar Elk",
"title": "Nueva versión de Elk disponible", "title": "Nueva versión de Elk disponible",
"update": "Actualizar", "update": "Actualizar",
"update_available_short": "Actualiza Elk", "update_available_short": "Actualiza Elk",

View file

@ -253,6 +253,8 @@
}, },
"pwa": { "pwa": {
"dismiss": "Fermer", "dismiss": "Fermer",
"install": "Installer",
"install_title": "Installer Elk",
"title": "Nouvelle mise à jour Elk disponible !", "title": "Nouvelle mise à jour Elk disponible !",
"update": "Mettre à jour", "update": "Mettre à jour",
"update_available_short": "Mettre à jour Elk", "update_available_short": "Mettre à jour Elk",

View file

@ -4,6 +4,7 @@ export default defineNuxtPlugin(() => {
const online = useOnline() const online = useOnline()
const registrationError = ref(false) const registrationError = ref(false)
const swActivated = ref(false) const swActivated = ref(false)
const showInstallPrompt = ref(false)
// https://thomashunter.name/posts/2021-12-11-detecting-if-pwa-twa-is-installed // https://thomashunter.name/posts/2021-12-11-detecting-if-pwa-twa-is-installed
const ua = navigator.userAgent const ua = navigator.userAgent
@ -57,10 +58,51 @@ export default defineNuxtPlugin(() => {
needRefresh.value = false needRefresh.value = false
} }
type InstallPromptEvent = Event & {
prompt: () => void
userChoice: Promise<{ outcome: 'dismissed' | 'accepted' }>
}
let deferredPrompt: InstallPromptEvent | undefined
const beforeInstallPrompt = (e: Event) => {
e.preventDefault()
deferredPrompt = e as InstallPromptEvent
showInstallPrompt.value = true
}
window.addEventListener('beforeinstallprompt', beforeInstallPrompt)
window.addEventListener('appinstalled', () => {
deferredPrompt = undefined
showInstallPrompt.value = false
})
const cancelInstall = () => {
deferredPrompt = undefined
showInstallPrompt.value = false
window.removeEventListener('beforeinstallprompt', beforeInstallPrompt)
}
const install = async () => {
if (!showInstallPrompt.value || !deferredPrompt) {
showInstallPrompt.value = false
return
}
showInstallPrompt.value = false
await nextTick()
deferredPrompt.prompt()
const { outcome } = await deferredPrompt.userChoice
if (outcome === 'dismissed')
cancelInstall()
}
return { return {
provide: { provide: {
pwa: reactive({ pwa: reactive({
isInstalled, isInstalled,
showInstallPrompt,
cancelInstall,
install,
swActivated, swActivated,
registrationError, registrationError,
needRefresh, needRefresh,