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,