From 2067f0615f058c73a7947e5a460ad15696b394cf Mon Sep 17 00:00:00 2001 From: team3 Date: Fri, 9 Oct 2026 18:29:38 +0200 Subject: [PATCH] =?UTF-8?q?Als=20App=20installierbar=20(Web-Manifest,=20Vo?= =?UTF-8?q?llbild)=20und=20Vollbild-R=C3=BCckholung=20im=20Lesemodus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Manifest mit Icons, display fullscreen; Backend liefert statische Dateien des Frontends direkt aus. - Nach dem Sperren des Handys holt das nächste Umblättern das Vollbild zurück. Co-Authored-By: Claude Fable 5.1 --- backend/main.py | 4 ++++ frontend/index.html | 4 ++++ frontend/public/icons/icon-192.png | Bin 0 -> 1091 bytes frontend/public/icons/icon-512.png | Bin 0 -> 3490 bytes frontend/public/manifest.webmanifest | 16 ++++++++++++++++ frontend/src/components/Lesemodus.vue | 10 ++++++++-- 6 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 frontend/public/icons/icon-192.png create mode 100644 frontend/public/icons/icon-512.png create mode 100644 frontend/public/manifest.webmanifest diff --git a/backend/main.py b/backend/main.py index 64eb22b..0060e20 100644 --- a/backend/main.py +++ b/backend/main.py @@ -190,4 +190,8 @@ if config.FRONTEND_DIST.exists(): async def spa(pfad: str): if pfad.startswith("api/"): raise HTTPException(404, "Unbekannter API-Pfad") + # Statische Dateien des Frontends (Manifest, Icons) direkt ausliefern, alles andere ist die App + datei = (config.FRONTEND_DIST / pfad).resolve() + if pfad and datei.is_file() and config.FRONTEND_DIST.resolve() in datei.parents: + return FileResponse(datei) return FileResponse(config.FRONTEND_DIST / "index.html") diff --git a/frontend/index.html b/frontend/index.html index 5d20fea..16c5ed3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,6 +4,10 @@ buchwerk + + + + diff --git a/frontend/public/icons/icon-192.png b/frontend/public/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..701acdb49bd1fb0f757096b8cc6ba5715fbe847e GIT binary patch literal 1091 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 literal 0 HcmV?d00001 diff --git a/frontend/public/manifest.webmanifest b/frontend/public/manifest.webmanifest new file mode 100644 index 0000000..592c62f --- /dev/null +++ b/frontend/public/manifest.webmanifest @@ -0,0 +1,16 @@ +{ + "name": "buchwerk", + "short_name": "buchwerk", + "description": "Sachbücher lesen", + "lang": "de", + "start_url": "/", + "scope": "/", + "display": "fullscreen", + "orientation": "portrait", + "background_color": "#000000", + "theme_color": "#000000", + "icons": [ + { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, + { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } + ] +} diff --git a/frontend/src/components/Lesemodus.vue b/frontend/src/components/Lesemodus.vue index 69252d4..54f1bb7 100644 --- a/frontend/src/components/Lesemodus.vue +++ b/frontend/src/components/Lesemodus.vue @@ -46,13 +46,19 @@ function html(s) { return cache.get(key) } -function vor() { if (pos.value < screens.value.length - 1) pos.value++ } +// Nach dem Sperren des Handys ist das Vollbild weg; ein Tippen darf es wieder anfordern. Als installierte App unnötig. +function vollbildHolen() { + if (document.fullscreenElement || !document.documentElement.requestFullscreen) return + if (matchMedia('(display-mode: fullscreen), (display-mode: standalone)').matches) return + document.documentElement.requestFullscreen().catch(() => {}) +} +function vor() { vollbildHolen(); if (pos.value < screens.value.length - 1) pos.value++ } // Von vorn: auch die Buchansicht zurücksetzen, sonst zöge sie den Lesemodus wieder nach hinten async function vonVorn() { await api.positionSetzen(props.slug, props.version, { abschnitt: 'vorwort', lesemodus: 'vorwort', zuletzt: 'lesen' }).catch(() => {}) pos.value = 0 } -function zurueck() { if (pos.value > 0) pos.value-- } +function zurueck() { vollbildHolen(); if (pos.value > 0) pos.value-- } let merkTimer = null watch(pos, (p) => { buehne.value?.scrollTo({ top: 0 })