From 9850b9d8e7376e00d81245e29ccd44b101127d07 Mon Sep 17 00:00:00 2001 From: librelad Date: Thu, 25 Jun 2026 13:17:14 +0100 Subject: [PATCH] fix(admin/storage): make the Storage page tablet/mobile friendly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The donut legend collided into the stat cards and the image-row details were squished on tablet widths — the 220px sidebar leaves the content cramped while the old breakpoints assumed full-viewport width. - Headline collapses to a single column at <=1024px (was 800px), the two stat cards reflow side-by-side, and on phones the donut stacks above its legend with one stat per row. Legend labels now ellipsis instead of overflowing into the stats. - Image rows group the name+pill and the size/shared/age metadata so the metadata drops onto its own line under the name at <=1024px instead of competing for width; on phones the Delete button collapses to an icon. Co-Authored-By: Claude Opus 4.8 Signed-off-by: librelad --- .../components/admin/core/css/admin.css | 65 ++++++++++++++++++- .../admin/system/js/system-storage-page.js | 16 +++-- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/containers/libreportal/frontend/components/admin/core/css/admin.css b/containers/libreportal/frontend/components/admin/core/css/admin.css index bccd41f..2e338ee 100644 --- a/containers/libreportal/frontend/components/admin/core/css/admin.css +++ b/containers/libreportal/frontend/components/admin/core/css/admin.css @@ -136,6 +136,48 @@ .sys-img-pill.is-inuse { background: rgba(var(--accent-rgb), 0.16); color: var(--accent); } .sys-img-pill.is-unused { background: rgba(var(--text-rgb), 0.10); color: rgba(var(--text-rgb), 0.6); } .sys-img-pill.is-dangling { background: rgba(251, 189, 35, 0.16); color: #fbbd23; } +/* Image row body: name + pill on the left, size/shared/age metadata pushed to + the right on a wide row. On narrow screens (see the @media below) the meta + drops onto its own line under the name so the details stop being squished. */ +.sys-img-body { + display: flex; + align-items: center; + gap: 6px 10px; + flex: 1; + min-width: 0; +} +.sys-img-headline { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; /* lets a long image name ellipsis instead of pushing */ +} +.sys-image-item .task-title { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.sys-img-pill { flex-shrink: 0; } +.sys-img-meta { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 6px 8px; + margin-left: auto; /* right-align the details on a single-line row */ + flex-shrink: 0; +} +/* Tablet & phone (the 220px sidebar leaves the content cramped until 768px): + stack the metadata under the name+pill so the details get their own line. */ +@media (max-width: 1024px) { + .sys-img-body { flex-direction: column; align-items: flex-start; gap: 5px; } + .sys-img-meta { margin-left: 0; } +} +/* Phones: collapse the Delete button to an icon so a long name + actions fit. */ +@media (max-width: 560px) { + .sys-image-item .task-btn.delete .task-btn-label { display: none; } + .sys-image-item .task-btn.delete { padding: 4px 7px; } +} .admin-card-body { display: flex; @@ -1060,8 +1102,23 @@ table.sys-apps tr:hover td { background: rgba(var(--text-rgb), 0.03); } gap: 18px; margin-top: 14px; } -@media (max-width: 800px) { +/* Tablet/narrow: with the 220px sidebar eating the width, the two headline + columns (donut+legend / stat cards) can't share a row without the legend + colliding into the stats — stack them, and let the two stat cards sit side + by side while there's still room for them. */ +@media (max-width: 1024px) { .sys-storage-headline { grid-template-columns: 1fr; } + .sys-storage-head-stats { + grid-template-rows: none; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + } +} +/* Phones (sidebar is now a drawer, content is full-width): donut above its + legend, one stat card per row. */ +@media (max-width: 560px) { + .sys-storage-head-card { flex-direction: column; text-align: center; } + .sys-storage-legend { width: 100%; } + .sys-storage-head-stats { grid-template-columns: 1fr; } } .sys-storage-head-card { background: var(--card-bg); @@ -1100,7 +1157,7 @@ table.sys-apps tr:hover td { background: rgba(var(--text-rgb), 0.03); } } .sys-storage-legend li { display: grid; - grid-template-columns: 14px auto 1fr auto; + grid-template-columns: 14px minmax(0, auto) 1fr auto; align-items: center; gap: 10px; font-size: 0.85rem; @@ -1112,6 +1169,10 @@ table.sys-apps tr:hover td { background: rgba(var(--text-rgb), 0.03); } .sys-storage-leg-k { color: var(--text-primary); font-weight: 600; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .sys-storage-leg-v { color: rgba(var(--text-rgb), 0.65); diff --git a/containers/libreportal/frontend/components/admin/system/js/system-storage-page.js b/containers/libreportal/frontend/components/admin/system/js/system-storage-page.js index 5671122..b891a20 100644 --- a/containers/libreportal/frontend/components/admin/system/js/system-storage-page.js +++ b/containers/libreportal/frontend/components/admin/system/js/system-storage-page.js @@ -518,11 +518,17 @@ class SystemStoragePage {
${this._imageIconHtml(im)} - ${fmt.escape(this._imageName(im))} - ${fmt.escape(pill.label)} - ${fmt.bytes(im.size || 0)} - ${im.shared_size ? `${fmt.bytes(im.shared_size)} shared` : ''} - ${im.created ? `${fmt.timeAgo(im.created)}` : ''} +
+ + ${fmt.escape(this._imageName(im))} + ${fmt.escape(pill.label)} + + + ${fmt.bytes(im.size || 0)} + ${im.shared_size ? `${fmt.bytes(im.shared_size)} shared` : ''} + ${im.created ? `${fmt.timeAgo(im.created)}` : ''} + +