Added custom PWA reloading - 10 minutes of idle or when moved to the background or when loading initially or manually with the new button in settings
This commit is contained in:
@@ -0,0 +1,110 @@
|
|||||||
|
import { type Component, createEffect, onCleanup } from 'solid-js';
|
||||||
|
import { useRegisterSW } from 'virtual:pwa-register/solid';
|
||||||
|
|
||||||
|
// We export a function so other parts of the app can manually trigger an update
|
||||||
|
let manualUpdateFn: (() => void) | undefined;
|
||||||
|
|
||||||
|
export const forceServiceWorkerUpdate = () => {
|
||||||
|
if (manualUpdateFn) {
|
||||||
|
manualUpdateFn();
|
||||||
|
} else {
|
||||||
|
console.warn("PWA Updater not initialized yet");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PwaUpdater: Component = () => {
|
||||||
|
let registration: ServiceWorkerRegistration | undefined;
|
||||||
|
|
||||||
|
const {
|
||||||
|
needRefresh: [needRefresh],
|
||||||
|
updateServiceWorker,
|
||||||
|
} = useRegisterSW({
|
||||||
|
onRegistered(r: ServiceWorkerRegistration | undefined) {
|
||||||
|
console.log('SW Registered:', r);
|
||||||
|
registration = r;
|
||||||
|
|
||||||
|
// Periodically check for updates (every 1 hour)
|
||||||
|
if (r) {
|
||||||
|
setInterval(() => {
|
||||||
|
console.log('Periodic SW update check...');
|
||||||
|
r.update();
|
||||||
|
}, 60 * 60 * 1000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRegisterError(error: Error | any) {
|
||||||
|
console.error('SW registration error', error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
// Expose function globally
|
||||||
|
manualUpdateFn = () => {
|
||||||
|
if (needRefresh()) {
|
||||||
|
console.log('User manually forced an update. Reloading now...');
|
||||||
|
updateServiceWorker(true);
|
||||||
|
} else if (registration) {
|
||||||
|
console.log('User manually asked to check for updates...');
|
||||||
|
registration.update();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
// Check for updates when the app regains focus/visibility
|
||||||
|
const handleVisibilityFocus = () => {
|
||||||
|
if (document.visibilityState === 'visible' && registration) {
|
||||||
|
console.log('App became visible, checking for SW updates...');
|
||||||
|
registration.update();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityFocus);
|
||||||
|
window.addEventListener('focus', handleVisibilityFocus);
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
document.removeEventListener('visibilitychange', handleVisibilityFocus);
|
||||||
|
window.removeEventListener('focus', handleVisibilityFocus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (needRefresh()) {
|
||||||
|
console.log('New PWA version available. Monitoring for idle/hidden state to update...');
|
||||||
|
|
||||||
|
let idleTimeout: number | undefined;
|
||||||
|
|
||||||
|
const triggerUpdate = () => {
|
||||||
|
console.log('Triggering PWA update...');
|
||||||
|
updateServiceWorker(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetIdle = () => {
|
||||||
|
if (idleTimeout) window.clearTimeout(idleTimeout);
|
||||||
|
// 10 minutes idle
|
||||||
|
idleTimeout = window.setTimeout(triggerUpdate, 10 * 60 * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVisibility = () => {
|
||||||
|
if (document.visibilityState === 'hidden') {
|
||||||
|
triggerUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start idle timer
|
||||||
|
resetIdle();
|
||||||
|
|
||||||
|
// Listen for user activity
|
||||||
|
const events = ['mousedown', 'mousemove', 'keydown', 'scroll', 'touchstart'];
|
||||||
|
events.forEach(e => window.addEventListener(e, resetIdle, { passive: true }));
|
||||||
|
document.addEventListener('visibilitychange', handleVisibility);
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (idleTimeout) window.clearTimeout(idleTimeout);
|
||||||
|
events.forEach(e => window.removeEventListener(e, resetIdle));
|
||||||
|
document.removeEventListener('visibilitychange', handleVisibility);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -3,12 +3,14 @@ import './index.css'
|
|||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
import { ThemeProvider } from './components/ThemeProvider'
|
import { ThemeProvider } from './components/ThemeProvider'
|
||||||
import { Toaster } from './components/ui/sonner'
|
import { Toaster } from './components/ui/sonner'
|
||||||
|
import { PwaUpdater } from './components/PwaUpdater'
|
||||||
|
|
||||||
const root = document.getElementById('root')
|
const root = document.getElementById('root')
|
||||||
|
|
||||||
render(() => {
|
render(() => {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
|
<PwaUpdater />
|
||||||
<App />
|
<App />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
+3
-1
@@ -221,10 +221,12 @@ export const startContextPolling = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
createEffect(() => {
|
createRoot(() => {
|
||||||
|
createEffect(() => {
|
||||||
// Re-run whenever ctx changes
|
// Re-run whenever ctx changes
|
||||||
currentTaskContext();
|
currentTaskContext();
|
||||||
startContextPolling();
|
startContextPolling();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface FilterTemplate {
|
export interface FilterTemplate {
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="flex flex-col gap-12">
|
<div class="flex flex-col gap-12 mt-4">
|
||||||
|
|
||||||
|
|
||||||
{/* Resources Category (Mostly for Mobile) */}
|
{/* Resources Category (Mostly for Mobile) */}
|
||||||
@@ -1086,6 +1086,23 @@ export const SettingsView: Component<{ setView?: (v: string) => void }> = (props
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<footer class="pt-40 pb-20 flex flex-col items-center gap-4">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="h-8 gap-2 text-[0.625rem] font-bold uppercase tracking-widest text-muted-foreground hover:text-foreground opacity-30 hover:opacity-100 transition-opacity"
|
||||||
|
onClick={async () => {
|
||||||
|
const { forceServiceWorkerUpdate } = await import('@/components/PwaUpdater');
|
||||||
|
forceServiceWorkerUpdate();
|
||||||
|
toast.success("Checking for updates...");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Upload class="rotate-180" size={14} />
|
||||||
|
Check for Updates
|
||||||
|
</Button>
|
||||||
|
<p class="text-[0.5rem] font-mono text-muted-foreground/30 uppercase tracking-[0.2em]">Tasgrid v1.0.0</p>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+3
-1
@@ -10,7 +10,9 @@
|
|||||||
"DOM.Iterable"
|
"DOM.Iterable"
|
||||||
],
|
],
|
||||||
"types": [
|
"types": [
|
||||||
"vite/client"
|
"vite/client",
|
||||||
|
"vite-plugin-pwa/client",
|
||||||
|
"vite-plugin-pwa/solid"
|
||||||
],
|
],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
solid(),
|
solid(),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
registerType: 'autoUpdate',
|
registerType: 'prompt',
|
||||||
includeAssets: ['icon.webp', 'vite.svg'],
|
includeAssets: ['icon.webp', 'vite.svg'],
|
||||||
manifest: {
|
manifest: {
|
||||||
name: 'Tasgrid',
|
name: 'Tasgrid',
|
||||||
|
|||||||
Reference in New Issue
Block a user