diff --git a/frontend/index.html b/frontend/index.html index 67901dc..b149186 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,10 +2,12 @@ - - - + + + + + MediaHive diff --git a/frontend/package.json b/frontend/package.json index 2a36843..47c83d9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ "@vitejs/plugin-vue": "^5.0.0", "typescript": "^5.3.0", "vite": "^5.0.0", + "vite-plugin-pwa": "^1.3.0", "vue-tsc": "^2.0.0" } } diff --git a/frontend/public/mediahive-32.webp b/frontend/public/mediahive-32.webp new file mode 100644 index 0000000..15df114 Binary files /dev/null and b/frontend/public/mediahive-32.webp differ diff --git a/frontend/public/mediahive.webp b/frontend/public/mediahive.webp new file mode 100644 index 0000000..88249aa Binary files /dev/null and b/frontend/public/mediahive.webp differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 22805a0..1b76b86 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -219,6 +219,10 @@ interface ScoredMediaItem { const searchCategories = ref([]); +function requestInitialFullscreen() { + void document.documentElement.requestFullscreen(); +} + // Focus state per page for Escape navigation const focusStateMap = new Map(); // Track the last viewed item ID to restore focus to the right card @@ -308,10 +312,12 @@ function handleEscapeKey(event: KeyboardEvent) { onMounted(() => { document.addEventListener('keydown', handleEscapeKey); + window.addEventListener('click', requestInitialFullscreen, { once: true }); }); onUnmounted(() => { document.removeEventListener('keydown', handleEscapeKey); + window.removeEventListener('click', requestInitialFullscreen); }); // Search query stored in ref (not URL-based) diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 8e5cde2..e5e0140 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -9,4 +9,10 @@ import { installGamepadNavigation } from './composables/useGamepadNavigation' installKeyboardNavigation() installGamepadNavigation() +if ('serviceWorker' in navigator && !navigator.serviceWorker.controller) { + navigator.serviceWorker.addEventListener('controllerchange', () => { + window.location.reload() + }, { once: true }) +} + createApp(App).use(router).mount('#app') diff --git a/frontend/src/pwa.ts b/frontend/src/pwa.ts new file mode 100644 index 0000000..650e249 --- /dev/null +++ b/frontend/src/pwa.ts @@ -0,0 +1,8 @@ +import { registerSW } from 'virtual:pwa-register' + +export function registerMediaHivePwa(): void { + const updateServiceWorker = registerSW({ + immediate: true, + onNeedRefresh: () => void updateServiceWorker(true), + }) +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index 323c78a..0dcaebc 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -1,4 +1,5 @@ /// +/// declare module '*.vue' { import type { DefineComponent } from 'vue' diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index df2a826..d6ecc5b 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,10 +1,60 @@ import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; +import { VitePWA } from "vite-plugin-pwa"; import fastapiVue from './vite-plugin-fastapi.js' // https://vitejs.dev/config/ export default defineConfig(async () => ({ - plugins: [fastapiVue(), vue()], + plugins: [ + fastapiVue(), + vue(), + VitePWA({ + registerType: "autoUpdate", + injectRegister: "script", + manifest: { + id: "/", + name: "MediaHive", + short_name: "MediaHive", + description: "Movies and Series", + start_url: "/", + scope: "/", + display_override: ["window-controls-overlay", "fullscreen", "standalone"], + display: "fullscreen", + background_color: "#0a0a0a", + theme_color: "#0a0a0a", + icons: [ + { + src: "/mediahive-32.webp", + sizes: "32x32", + type: "image/webp" + }, + { + src: "/mediahive.webp", + sizes: "192x192", + type: "image/webp" + } + ] + }, + workbox: { + cleanupOutdatedCaches: true, + clientsClaim: true, + skipWaiting: true, + manifestTransforms: [ + async (entries) => { + const manifest = entries.map((entry) => + entry.url === "index.html" ? { ...entry, url: "/" } : entry + ); + return { manifest, warnings: [] }; + }, + ], + navigateFallback: null, + navigateFallbackDenylist: [/^\/api\//], + }, + devOptions: { + enabled: false, + }, + }), + ], // Vite dev server options clearScreen: false,