From 9ff7a6d57c62a663f86e4da624bad6972761c1c4 Mon Sep 17 00:00:00 2001 From: Florian Heinz Date: Sun, 12 Apr 2026 17:24:37 +0200 Subject: [PATCH] release nouri 0.5.1 mobile nav and header fixes --- CloudronManifest.json | 4 +- RELEASE_NOTES_0.5.1.md | 32 ++++++++ nouri/__init__.py | 2 +- nouri/static/css/styles.css | 125 ++++++++++++++++++++++------- nouri/static/js/ui.js | 28 ++++--- nouri/static/pwa/service-worker.js | 2 +- nouri/templates/base.html | 92 ++++++++++----------- start.sh | 23 +++--- 8 files changed, 204 insertions(+), 104 deletions(-) create mode 100644 RELEASE_NOTES_0.5.1.md diff --git a/CloudronManifest.json b/CloudronManifest.json index 95e32e1..b20cd3a 100644 --- a/CloudronManifest.json +++ b/CloudronManifest.json @@ -4,8 +4,8 @@ "author": "Florian Heinz", "description": "Private Flask app for meals, shopping and gentle food planning", "tagline": "einfach essen planen", - "version": "0.5.0", - "upstreamVersion": "0.5.0", + "version": "0.5.1", + "upstreamVersion": "0.5.1", "healthCheckPath": "/", "httpPort": 8000, "manifestVersion": 2, diff --git a/RELEASE_NOTES_0.5.1.md b/RELEASE_NOTES_0.5.1.md new file mode 100644 index 0000000..f7eda8c --- /dev/null +++ b/RELEASE_NOTES_0.5.1.md @@ -0,0 +1,32 @@ +# Nouri 0.5.1 + +## Highlights + +- Smartphone-Navigation unten neu als echte Erweiterung umgesetzt +- obere Nouri-Leiste auf kleinen Geräten nicht mehr sticky, sondern sauber fest positioniert +- PWA-Cache für frische Layout- und Einstellungsänderungen bereinigt +- Cloudron-Version auf `0.5.1` angehoben + +## Neu in 0.5.1 + +### Mobile Navigation + +- `Mehr` ist auf Smartphones kein schwebendes Overlay mehr. +- Die zusätzlichen Punkte klappen jetzt direkt aus der unteren Navigation heraus auf. +- Die Zusatzpunkte nutzen dieselbe kompakte Größe wie die unteren Menüpunkte. +- Der untere Navigationsbereich wird dabei nicht weichgezeichnet. + +### Mobile Header + +- Die obere Nouri-Leiste scrollt auf kleinen Geräten nicht mehr mit dem Inhalt. +- Die bisherige `sticky`-Logik für den Header wurde entfernt, damit es keine widersprüchlichen Zustände mehr gibt. + +### PWA + +- Der Service Worker verwendet einen aktualisierten Cache-Namen. +- Navigationsseiten werden frischer geladen, damit Änderungen an Einstellungen und Layout nicht an altem Cache hängen bleiben. + +## Cloudron + +- `CloudronManifest.json` wurde auf `0.5.1` angehoben. +- Damit lässt sich das Update sauber als neue App-Version ausrollen. diff --git a/nouri/__init__.py b/nouri/__init__.py index 9c436a4..dedfb8f 100644 --- a/nouri/__init__.py +++ b/nouri/__init__.py @@ -68,7 +68,7 @@ def create_app() -> Flask: PERMANENT_SESSION_LIFETIME=timedelta(days=30), SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Lax", - APP_VERSION="0.5.0", + APP_VERSION="0.5.1", VAPID_PUBLIC_KEY=os.environ.get("NOURI_VAPID_PUBLIC_KEY", ""), VAPID_PRIVATE_KEY=os.environ.get("NOURI_VAPID_PRIVATE_KEY", ""), VAPID_SUBJECT=os.environ.get("NOURI_VAPID_SUBJECT", "mailto:mail@hnz.io"), diff --git a/nouri/static/css/styles.css b/nouri/static/css/styles.css index f3e5bdd..cb658a2 100644 --- a/nouri/static/css/styles.css +++ b/nouri/static/css/styles.css @@ -152,8 +152,7 @@ button.secondary:hover, } .site-header { - position: sticky; - top: 1rem; + position: static; z-index: 10; display: grid; grid-template-columns: auto 1fr auto; @@ -969,6 +968,16 @@ legend { color: var(--accent-strong); } +.menu-card-button { + width: 100%; + cursor: pointer; + font: inherit; +} + +.menu-card-form { + margin: 0; +} + .roomy-row { padding: 1rem 1.2rem; } @@ -1191,6 +1200,10 @@ legend { backdrop-filter: blur(6px); } +.mobile-nav-stack { + display: none; +} + .mobile-more-sheet { position: fixed; left: 0.75rem; @@ -1223,6 +1236,10 @@ legend { margin: 1rem 0; } +.mobile-menu-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + .mobile-sheet-actions { flex-wrap: wrap; } @@ -1241,9 +1258,19 @@ legend { align-items: flex-start; } + body.has-mobile-nav { + padding-top: 5.3rem; + } + .site-header { - position: static; - grid-template-columns: 1fr; + position: fixed; + top: calc(env(safe-area-inset-top, 0px) + 0.5rem); + left: 0.5rem; + right: 0.5rem; + grid-template-columns: 1fr auto; + z-index: 30; + width: auto; + margin: 0; } .stats-grid, @@ -1273,13 +1300,12 @@ legend { } .site-header { - position: sticky; - top: 0.7rem; grid-template-columns: 1fr auto; gap: 0.6rem; padding: 0.75rem 0.9rem; - margin-bottom: 0.85rem; + margin-bottom: 0; border-radius: 22px; + z-index: 30; } .desktop-nav, @@ -1377,54 +1403,95 @@ legend { min-width: 100%; } - .mobile-bottom-nav { + .mobile-nav-stack { position: fixed; left: 0.75rem; right: 0.75rem; bottom: 0.75rem; - z-index: 20; + z-index: 24; display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 0.35rem; padding: 0.5rem; border-radius: 22px; - background: var(--bg-elevated); + background: color-mix(in srgb, var(--bg) 96%, #f6decb 4%); border: 1px solid var(--line); box-shadow: var(--shadow); - backdrop-filter: blur(26px) saturate(1.15); } - .mobile-bottom-nav a { + .mobile-nav-extension { + display: none; + } + + .mobile-nav-stack.is-open .mobile-nav-extension { display: grid; - justify-items: center; - gap: 0.28rem; - padding: 0.55rem 0.35rem; - border-radius: 16px; - color: var(--muted); - font-size: 0.78rem; } + .mobile-nav-extension, + .mobile-sheet-links.mobile-menu-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.35rem; + margin: 0; + } + + .mobile-extra-link, + .mobile-extra-button, + .mobile-bottom-nav a, .mobile-nav-button { - display: grid; justify-items: center; + align-content: center; + display: grid; + min-height: 3.95rem; + padding: 0.55rem 0.2rem 0.5rem; + text-align: center; gap: 0.28rem; - padding: 0.55rem 0.35rem; border-radius: 16px; - border: 0; background: transparent; + box-shadow: none; color: var(--muted); font-size: 0.78rem; + border: 0; } - .mobile-bottom-nav a.active, - .mobile-nav-button.is-open { - background: var(--accent-soft); - color: var(--text); - } - + .mobile-extra-link .ui-icon, + .mobile-extra-button .ui-icon, .mobile-bottom-nav .ui-icon { width: 1rem; height: 1rem; + margin-top: 0; + } + + .mobile-extra-link span:last-child, + .mobile-extra-button span:last-child, + .mobile-bottom-nav a span:last-child, + .mobile-nav-button span:last-child { + font-size: 0.72rem; + line-height: 1.08; + } + + .mobile-extra-form { + display: contents; + } + + .mobile-bottom-nav { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 0.35rem; + padding: 0; + } + + .mobile-bottom-nav a { + } + + .mobile-nav-button { + cursor: pointer; + font: inherit; + } + + .mobile-bottom-nav a.active, + .mobile-extra-link.active, + .mobile-nav-button.is-open { + background: var(--accent-soft); + color: var(--text); } .mobile-profile-link { @@ -1438,8 +1505,6 @@ legend { height: 2.15rem; } - .mobile-sheet-head, - .mobile-sheet-actions, .week-template-row { align-items: flex-start; } diff --git a/nouri/static/js/ui.js b/nouri/static/js/ui.js index c43afec..b9dc948 100644 --- a/nouri/static/js/ui.js +++ b/nouri/static/js/ui.js @@ -1,32 +1,33 @@ (() => { const initMobileSheet = () => { const sheet = document.querySelector("[data-mobile-sheet]"); - const backdrop = document.querySelector("[data-mobile-sheet-backdrop]"); + const navStack = document.querySelector("[data-mobile-nav-stack]"); const openButtons = document.querySelectorAll("[data-mobile-sheet-open]"); - const closeButtons = document.querySelectorAll("[data-mobile-sheet-close]"); - if (!sheet || !backdrop || !openButtons.length) return; + if (!sheet || !navStack || !openButtons.length) return; const closeSheet = () => { sheet.hidden = true; - backdrop.hidden = true; - document.body.classList.remove("sheet-open"); + navStack.classList.remove("is-open"); openButtons.forEach((button) => button.classList.remove("is-open")); }; const openSheet = () => { sheet.hidden = false; - backdrop.hidden = false; - document.body.classList.add("sheet-open"); + navStack.classList.add("is-open"); openButtons.forEach((button) => button.classList.add("is-open")); }; + const toggleSheet = () => { + if (sheet.hidden) { + openSheet(); + } else { + closeSheet(); + } + }; + openButtons.forEach((button) => { - button.addEventListener("click", openSheet); + button.addEventListener("click", toggleSheet); }); - closeButtons.forEach((button) => { - button.addEventListener("click", closeSheet); - }); - backdrop.addEventListener("click", closeSheet); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { @@ -37,6 +38,9 @@ sheet.querySelectorAll("a").forEach((link) => { link.addEventListener("click", closeSheet); }); + sheet.querySelectorAll("button[data-theme-toggle]").forEach((button) => { + button.addEventListener("click", closeSheet); + }); }; const initFilterInputs = () => { diff --git a/nouri/static/pwa/service-worker.js b/nouri/static/pwa/service-worker.js index ec700f0..599460a 100644 --- a/nouri/static/pwa/service-worker.js +++ b/nouri/static/pwa/service-worker.js @@ -1,4 +1,4 @@ -const CACHE_NAME = "nouri-v0-5-1"; +const CACHE_NAME = "nouri-v0-5-1-1"; const STATIC_ASSETS = [ "/static/css/styles.css", "/static/js/theme.js", diff --git a/nouri/templates/base.html b/nouri/templates/base.html index 3142b0f..7cb78f6 100644 --- a/nouri/templates/base.html +++ b/nouri/templates/base.html @@ -93,59 +93,55 @@ {% if g.user %} - - + - + + {% endif %} diff --git a/start.sh b/start.sh index a5ff9ed..7039d63 100755 --- a/start.sh +++ b/start.sh @@ -1,13 +1,16 @@ -#!/bin/sh +#!/bin/bash set -eu -export NOURI_DATA_DIR="${NOURI_DATA_DIR:-/app/data}" -mkdir -p "${NOURI_DATA_DIR}" -mkdir -p "${NOURI_DATA_DIR}/uploads" +mkdir -p /app/data/uploads -exec gunicorn \ - --bind 0.0.0.0:8000 \ - --workers 2 \ - --threads 4 \ - --timeout 60 \ - wsgi:app +# Vorhandene lokale SQLite-Datei beim allerersten Start übernehmen +if [ ! -f /app/data/nouri.sqlite3 ] && [ -f /app/bootstrap-data/nouri.sqlite3 ]; then + cp /app/bootstrap-data/nouri.sqlite3 /app/data/nouri.sqlite3 +fi + +# Vorhandene Uploads beim allerersten Start übernehmen +if [ -d /app/bootstrap-data/uploads ] && [ -z "$(ls -A /app/data/uploads 2>/dev/null || true)" ]; then + cp -a /app/bootstrap-data/uploads/. /app/data/uploads/ +fi + +exec gunicorn --bind 0.0.0.0:8000 --workers 2 --threads 4 wsgi:app \ No newline at end of file