From 43fdd7081c77341621b04c625d0d161a40779c3e Mon Sep 17 00:00:00 2001 From: Florian Heinz Date: Thu, 16 Apr 2026 15:31:52 +0200 Subject: [PATCH] Polish mobile shopping rows and stabilize backup archives --- nouri/backup.py | 18 ++++++-- nouri/static/css/styles.css | 68 +++++++++++++++++++++++++++--- nouri/templates/shopping/list.html | 5 ++- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/nouri/backup.py b/nouri/backup.py index a1a8bc0..3c1b1fc 100644 --- a/nouri/backup.py +++ b/nouri/backup.py @@ -52,13 +52,25 @@ def export_backup_archive( payload["tables"][table_name] = [dict(row) for row in rows] uploads_root = Path(upload_folder) - with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive: - archive.writestr("backup.json", json.dumps(payload, ensure_ascii=False, indent=2)) + uploads_snapshot_dir = Path(tempfile.mkdtemp(prefix="nouri-backup-uploads-")) + try: if uploads_root.exists(): for file_path in uploads_root.rglob("*"): + if not file_path.is_file(): + continue + relative_path = file_path.relative_to(uploads_root) + snapshot_path = uploads_snapshot_dir / relative_path + snapshot_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(file_path, snapshot_path) + + with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive: + archive.writestr("backup.json", json.dumps(payload, ensure_ascii=False, indent=2)) + for file_path in uploads_snapshot_dir.rglob("*"): if file_path.is_file(): - relative_path = file_path.relative_to(uploads_root) + relative_path = file_path.relative_to(uploads_snapshot_dir) archive.write(file_path, f"uploads/{relative_path.as_posix()}") + finally: + shutil.rmtree(uploads_snapshot_dir, ignore_errors=True) return archive_path, backup_name diff --git a/nouri/static/css/styles.css b/nouri/static/css/styles.css index ef6d6ed..18ed17a 100644 --- a/nouri/static/css/styles.css +++ b/nouri/static/css/styles.css @@ -1356,6 +1356,18 @@ h3 { white-space: nowrap; } +.shopping-entry-check-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.shopping-entry-check-mark { + font-size: 1rem; + line-height: 1; +} + .shopping-entry-close-form { flex: 0 0 auto; margin: 0; @@ -1415,24 +1427,68 @@ h3 { @media (max-width: 680px) { .shopping-entry-row { - align-items: stretch; - flex-direction: column; + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto; + align-items: center; + gap: 0.7rem; } .shopping-entry-open { - width: 100%; + min-width: 0; + } + + .shopping-entry-main { + grid-template-columns: 56px minmax(0, 1fr); + gap: 0.8rem; + align-items: center; + } + + .shopping-entry-visual, + .shopping-entry-fallback { + width: 56px; + height: 56px; + border-radius: 16px; + } + + .shopping-entry-copy { + gap: 0.12rem; + } + + .shopping-entry-copy strong { + font-size: 1rem; + } + + .shopping-entry-copy .muted { + display: none; } .shopping-entry-actions, .shopping-entry-actions form, .shopping-entry-actions button, .shopping-entry-close-form { - width: 100%; + width: auto; + } + + .shopping-entry-check-button { + min-width: 0; + padding: 0.75rem 0.9rem; + border-radius: 16px; + gap: 0; + } + + .shopping-entry-check-label { + display: none; + } + + .shopping-entry-check-mark { + font-size: 1.05rem; } .shopping-entry-close { - width: 100%; - border-radius: 18px; + width: 2.75rem; + height: 2.75rem; + min-width: 2.75rem; + border-radius: 16px; } } diff --git a/nouri/templates/shopping/list.html b/nouri/templates/shopping/list.html index 73f912d..ac1eed0 100644 --- a/nouri/templates/shopping/list.html +++ b/nouri/templates/shopping/list.html @@ -131,7 +131,10 @@
{{ csrf_input() }} - +
{% if entry.can_edit %}