Fix animation stale reference
This commit is contained in:
@@ -1,18 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, ref, watch } from 'vue'
|
||||
import { useRegisterSW } from 'virtual:pwa-register/vue'
|
||||
import { useNativeRouter } from '@native-vue-router/core'
|
||||
import { recordPwaUpdateState } from '../pwa'
|
||||
|
||||
const native = useNativeRouter()
|
||||
const { needRefresh, updateServiceWorker } = useRegisterSW()
|
||||
const reloadPending = ref(false)
|
||||
let registration: ServiceWorkerRegistration | undefined
|
||||
let checkTimer: number | undefined
|
||||
let reloading = false
|
||||
let checking = false
|
||||
|
||||
function reloadWhenIdle() {
|
||||
if (!reloadPending.value || native.transaction.value || reloading) return
|
||||
reloading = true
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
async function checkForUpdate() {
|
||||
if (!registration || checking || document.visibilityState === 'hidden' || !navigator.onLine) return
|
||||
checking = true
|
||||
recordPwaUpdateState('checking', true)
|
||||
try {
|
||||
await registration.update()
|
||||
if (!reloadPending.value) recordPwaUpdateState('current')
|
||||
} catch {
|
||||
recordPwaUpdateState('error')
|
||||
} finally {
|
||||
checking = false
|
||||
}
|
||||
}
|
||||
|
||||
const { needRefresh, updateServiceWorker } = useRegisterSW({
|
||||
immediate: true,
|
||||
onRegisteredSW(_workerUrl, workerRegistration) {
|
||||
registration = workerRegistration
|
||||
void checkForUpdate()
|
||||
if (checkTimer !== undefined) window.clearInterval(checkTimer)
|
||||
checkTimer = window.setInterval(() => void checkForUpdate(), 60_000)
|
||||
},
|
||||
onNeedRefresh() {
|
||||
recordPwaUpdateState('ready')
|
||||
},
|
||||
onNeedReload() {
|
||||
recordPwaUpdateState('ready')
|
||||
reloadPending.value = true
|
||||
reloadWhenIdle()
|
||||
},
|
||||
onRegisterError() {
|
||||
recordPwaUpdateState('error')
|
||||
},
|
||||
})
|
||||
|
||||
function checkWhenActive() {
|
||||
if (document.visibilityState === 'visible') void checkForUpdate()
|
||||
}
|
||||
|
||||
window.addEventListener('focus', checkWhenActive)
|
||||
window.addEventListener('online', checkWhenActive)
|
||||
document.addEventListener('visibilitychange', checkWhenActive)
|
||||
watch(() => native.transaction.value, reloadWhenIdle, { flush: 'post' })
|
||||
|
||||
function update() {
|
||||
reloadPending.value = true
|
||||
if (!native.transaction.value) void updateServiceWorker(true)
|
||||
reloadWhenIdle()
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (checkTimer !== undefined) window.clearInterval(checkTimer)
|
||||
window.removeEventListener('focus', checkWhenActive)
|
||||
window.removeEventListener('online', checkWhenActive)
|
||||
document.removeEventListener('visibilitychange', checkWhenActive)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside v-if="needRefresh" class="update-toast" role="status">
|
||||
<span>A fresh build is ready.</span>
|
||||
<button type="button" :disabled="Boolean(native.transaction.value)" @click="update">Update</button>
|
||||
<aside v-if="needRefresh || reloadPending" class="update-toast" role="status">
|
||||
<span>{{ native.transaction.value ? 'A fresh build will open after this gesture.' : 'A fresh build is ready.' }}</span>
|
||||
<button v-if="needRefresh" type="button" :disabled="Boolean(native.transaction.value)" @click="update">Update</button>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -6,6 +6,11 @@ interface StandaloneNavigator extends Navigator {
|
||||
}
|
||||
|
||||
type ServiceWorkerState = 'unsupported' | 'installing' | 'ready'
|
||||
export type PwaUpdateState = 'idle' | 'checking' | 'current' | 'ready' | 'error'
|
||||
|
||||
declare const __NVR_BUILD_ID__: string
|
||||
|
||||
export const pwaBuildId = __NVR_BUILD_ID__
|
||||
|
||||
const state = reactive({
|
||||
ios: false,
|
||||
@@ -13,10 +18,19 @@ const state = reactive({
|
||||
edgeGuard: false,
|
||||
edgeClaims: 0,
|
||||
serviceWorker: 'installing' as ServiceWorkerState,
|
||||
updateState: 'idle' as PwaUpdateState,
|
||||
updateChecks: 0,
|
||||
})
|
||||
|
||||
export const pwaEnvironment = readonly(state)
|
||||
|
||||
export function recordPwaUpdateState(updateState: PwaUpdateState, checked = false) {
|
||||
state.updateState = updateState
|
||||
if (checked) state.updateChecks += 1
|
||||
document.documentElement.dataset.pwaUpdateState = state.updateState
|
||||
document.documentElement.dataset.pwaUpdateChecks = String(state.updateChecks)
|
||||
}
|
||||
|
||||
export function isIOSWebKit() {
|
||||
const navigatorWithTouch = navigator as Navigator & { maxTouchPoints?: number }
|
||||
return /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
@@ -37,6 +51,8 @@ function updateEnvironment() {
|
||||
document.documentElement.dataset.pwaDisplayMode = state.standalone ? 'standalone' : 'browser'
|
||||
document.documentElement.dataset.pwaEdgeGuard = state.edgeGuard ? 'active' : 'inactive'
|
||||
document.documentElement.dataset.pwaEdgeClaims = String(state.edgeClaims)
|
||||
document.documentElement.dataset.pwaUpdateState = state.updateState
|
||||
document.documentElement.dataset.pwaUpdateChecks = String(state.updateChecks)
|
||||
}
|
||||
|
||||
export interface PwaAdapterOptions {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import AppHeader from '../components/AppHeader.vue'
|
||||
import { useDemoStore } from '../data'
|
||||
import { pwaEnvironment } from '../pwa'
|
||||
import { pwaBuildId, pwaEnvironment } from '../pwa'
|
||||
|
||||
const store = useDemoStore()
|
||||
</script>
|
||||
@@ -17,6 +17,7 @@ const store = useDemoStore()
|
||||
<h2>PWA environment</h2>
|
||||
<div><span><strong>Display mode</strong><small>Home Screen installation state</small></span><b :class="{ offline: !pwaEnvironment.standalone }">{{ pwaEnvironment.standalone ? 'Standalone' : 'Browser tab' }}</b></div>
|
||||
<div><span><strong>Offline worker</strong><small>Cached application shell</small></span><b :class="{ offline: pwaEnvironment.serviceWorker !== 'ready' }">{{ pwaEnvironment.serviceWorker }}</b></div>
|
||||
<div><span><strong>App build</strong><small>{{ pwaBuildId }}</small></span><b :class="{ offline: pwaEnvironment.updateState === 'error' }">{{ pwaEnvironment.updateState }} · {{ pwaEnvironment.updateChecks }} checks</b></div>
|
||||
<div><span><strong>iOS edge ownership</strong><small>Leading-edge touches claimed by this app: {{ pwaEnvironment.edgeClaims }}</small></span><b :class="{ offline: !pwaEnvironment.edgeGuard }">{{ pwaEnvironment.edgeGuard ? 'App reserved' : pwaEnvironment.ios ? 'Install required' : 'Not iOS' }}</b></div>
|
||||
<p v-if="pwaEnvironment.ios && !pwaEnvironment.standalone">In Safari, choose Share → Add to Home Screen, then launch the new icon. Edge interception is intentionally disabled inside a normal browser tab.</p>
|
||||
<p v-else-if="pwaEnvironment.ios">The leading edge is reserved before WebKit navigation begins. Open a conversation and drag from the extreme left edge to verify the live back preview.</p>
|
||||
|
||||
Reference in New Issue
Block a user