Enhance PWA support with new install prompt and configuration updates
- Added PWAInstallPrompt component to facilitate app installation beyond the "Add to Home Screen" option. - Updated vite.config.ts to include display_override and id properties for improved PWA manifest. - Modified nginx configuration to ensure proper serving of the manifest with application/manifest+json type for better installability on Android.
This commit is contained in:
parent
aa08160556
commit
129d7fe6f4
@ -30,6 +30,9 @@
|
|||||||
<!-- PWA Update Prompt -->
|
<!-- PWA Update Prompt -->
|
||||||
<PWAUpdatePrompt />
|
<PWAUpdatePrompt />
|
||||||
|
|
||||||
|
<!-- PWA Install Prompt (Install app, not just Add to Home Screen) -->
|
||||||
|
<PWAInstallPrompt />
|
||||||
|
|
||||||
<!-- Toast notifications - top right, glass style, any page -->
|
<!-- Toast notifications - top right, glass style, any page -->
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<Transition name="toast">
|
<Transition name="toast">
|
||||||
@ -61,6 +64,7 @@ import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
|||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import SplashScreen from './components/SplashScreen.vue'
|
import SplashScreen from './components/SplashScreen.vue'
|
||||||
import PWAUpdatePrompt from './components/PWAUpdatePrompt.vue'
|
import PWAUpdatePrompt from './components/PWAUpdatePrompt.vue'
|
||||||
|
import PWAInstallPrompt from './components/PWAInstallPrompt.vue'
|
||||||
import SpotlightSearch from './components/SpotlightSearch.vue'
|
import SpotlightSearch from './components/SpotlightSearch.vue'
|
||||||
import CLIPopup from './components/CLIPopup.vue'
|
import CLIPopup from './components/CLIPopup.vue'
|
||||||
import AppLauncherOverlay from './components/AppLauncherOverlay.vue'
|
import AppLauncherOverlay from './components/AppLauncherOverlay.vue'
|
||||||
|
|||||||
91
neode-ui/src/components/PWAInstallPrompt.vue
Normal file
91
neode-ui/src/components/PWAInstallPrompt.vue
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<Transition name="fade">
|
||||||
|
<div
|
||||||
|
v-if="showInstallPrompt"
|
||||||
|
class="fixed bottom-4 left-4 right-4 md:left-auto md:right-6 md:bottom-6 md:max-w-sm z-[9998]"
|
||||||
|
>
|
||||||
|
<div class="glass-card p-4 flex items-center gap-4 shadow-xl">
|
||||||
|
<img
|
||||||
|
src="/assets/icon/pwa-192x192.png"
|
||||||
|
alt="Archipelago"
|
||||||
|
class="w-14 h-14 rounded-xl shrink-0"
|
||||||
|
/>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="text-white font-medium">Install Archipelago</p>
|
||||||
|
<p class="text-white/70 text-sm">Add to your home screen for quick access</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 shrink-0">
|
||||||
|
<button
|
||||||
|
@click="dismiss"
|
||||||
|
class="px-3 py-2 text-sm text-white/70 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Not now
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="install"
|
||||||
|
class="px-4 py-2 gradient-button rounded-lg text-sm font-medium"
|
||||||
|
>
|
||||||
|
Install
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
|
const showInstallPrompt = ref(false)
|
||||||
|
let deferredPrompt: { prompt: () => Promise<{ outcome: string }> } | null = null
|
||||||
|
const DISMISS_KEY = 'archipelago_pwa_install_dismissed'
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Don't show if already dismissed this session or if already installed
|
||||||
|
if (sessionStorage.getItem(DISMISS_KEY) === '1') return
|
||||||
|
if (window.matchMedia('(display-mode: standalone)').matches) return
|
||||||
|
if ((window.navigator as any).standalone) return
|
||||||
|
|
||||||
|
const handler = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
deferredPrompt = e as unknown as { prompt: () => Promise<{ outcome: string }> }
|
||||||
|
showInstallPrompt.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeinstallprompt', handler)
|
||||||
|
;(window as any).__beforeinstallpromptHandler = handler
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('beforeinstallprompt', (window as any).__beforeinstallpromptHandler)
|
||||||
|
})
|
||||||
|
|
||||||
|
function dismiss() {
|
||||||
|
showInstallPrompt.value = false
|
||||||
|
sessionStorage.setItem(DISMISS_KEY, '1')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function install() {
|
||||||
|
if (!deferredPrompt) return
|
||||||
|
const result = await deferredPrompt.prompt()
|
||||||
|
const outcome = result?.outcome ?? 'dismissed'
|
||||||
|
showInstallPrompt.value = false
|
||||||
|
deferredPrompt = null
|
||||||
|
if (outcome === 'accepted') {
|
||||||
|
sessionStorage.removeItem(DISMISS_KEY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -17,9 +17,11 @@ export default defineConfig({
|
|||||||
theme_color: '#000000',
|
theme_color: '#000000',
|
||||||
background_color: '#000000',
|
background_color: '#000000',
|
||||||
display: 'standalone',
|
display: 'standalone',
|
||||||
|
display_override: ['standalone', 'minimal-ui'],
|
||||||
orientation: 'any',
|
orientation: 'any',
|
||||||
scope: '/',
|
scope: '/',
|
||||||
start_url: '/',
|
start_url: '/',
|
||||||
|
id: '/',
|
||||||
categories: ['productivity', 'utilities'],
|
categories: ['productivity', 'utilities'],
|
||||||
prefer_related_applications: false,
|
prefer_related_applications: false,
|
||||||
icons: [
|
icons: [
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
# PWA installability - required for Add to Home Screen on Android
|
# PWA installability - required for Install (not just Add to Home Screen) on Android
|
||||||
# Include this inside the HTTPS server block for archipelago
|
# Manifest MUST be served with application/manifest+json - Chrome rejects otherwise
|
||||||
# Manifest must have Content-Type: application/manifest+json
|
|
||||||
location = /manifest.webmanifest {
|
location = /manifest.webmanifest {
|
||||||
add_header Content-Type application/manifest+json;
|
default_type application/manifest+json;
|
||||||
add_header Cache-Control "public, max-age=0, must-revalidate";
|
add_header Cache-Control "public, max-age=0, must-revalidate";
|
||||||
}
|
}
|
||||||
# Service worker - no cache so updates apply
|
# Service worker - no cache so updates apply
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user