feat(ui): bump thumbnail sizes 1.5x, make Settings fluid, add subscription_count stat

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-22 23:18:22 -04:00
parent ec44c653fe
commit 6f68bf5fa7
6 changed files with 21 additions and 14 deletions
+5 -1
View File
@@ -6,7 +6,7 @@ from quart import Blueprint, jsonify, request
from sqlalchemy import func, select
from ..extensions import get_session
from ..models import AppSetting, ImageRecord, ImportBatch, ImportSettings, ImportTask, Tag
from ..models import AppSetting, Artist, ImageRecord, ImportBatch, ImportSettings, ImportTask, Tag
settings_bp = Blueprint("settings", __name__, url_prefix="/api")
@@ -118,6 +118,9 @@ async def system_stats():
storage_bytes = (
(await session.execute(select(func.coalesce(func.sum(ImageRecord.size_bytes), 0)))).scalar_one()
)
subscription_count = (await session.execute(
select(func.count(Artist.id)).where(Artist.is_subscription.is_(True))
)).scalar_one()
# Task counts grouped by status
status_rows = (
@@ -163,6 +166,7 @@ async def system_stats():
"total_images": total_images,
"total_tags": total_tags,
"storage_bytes": storage_bytes,
"subscription_count": int(subscription_count),
"tasks": {
"pending": status_counts.get("pending", 0),
"queued": status_counts.get("queued", 0),
@@ -86,7 +86,7 @@ function imagesForGroup(group) {
}
.fc-gallery-grid__items {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 8px;
}
.fc-gallery-grid__sentinel {
@@ -101,7 +101,7 @@ function imagesForGroup(group) {
}
.fc-gallery-grid__skeleton {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 8px;
}
.fc-gallery-grid__skeleton-item {
@@ -124,7 +124,7 @@ function imagesForGroup(group) {
@media (max-width: 600px) {
.fc-gallery-grid__items,
.fc-gallery-grid__skeleton {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 4px;
}
}
@@ -1,6 +1,6 @@
<template>
<v-row dense>
<v-col v-for="card in cards" :key="card.label" cols="12" sm="6" md="4" lg="2">
<v-col v-for="card in cards" :key="card.label" cols="12" sm="6" md="4" lg="3" xl="2">
<v-card class="fc-stat">
<v-card-text>
<div class="fc-stat__label text-caption">{{ card.label }}</div>
@@ -33,15 +33,17 @@ const cards = computed(() => {
{ label: 'Total images', value: '—' },
{ label: 'Total tags', value: '—' },
{ label: 'Storage used', value: '—' },
{ label: 'Subscriptions', value: '—' },
{ label: 'Pending tasks', value: '—' },
{ label: 'Failed tasks', value: '—' }
]
return [
{ label: 'Total images', value: s.total_images.toLocaleString() },
{ label: 'Total tags', value: s.total_tags.toLocaleString() },
{ label: 'Storage used', value: formatBytes(s.storage_bytes) },
{ label: 'Pending', value: (s.tasks.pending + s.tasks.queued).toLocaleString() },
{ label: 'Failed', value: s.tasks.failed.toLocaleString() }
{ label: 'Total images', value: s.total_images.toLocaleString() },
{ label: 'Total tags', value: s.total_tags.toLocaleString() },
{ label: 'Storage used', value: formatBytes(s.storage_bytes) },
{ label: 'Subscriptions', value: (s.subscription_count ?? 0).toLocaleString() },
{ label: 'Pending', value: (s.tasks.pending + s.tasks.queued).toLocaleString() },
{ label: 'Failed', value: s.tasks.failed.toLocaleString() }
]
})
</script>
+3 -2
View File
@@ -29,10 +29,11 @@ export function distributeIntoColumns(items, columnCount) {
}
// Reactive column count from container width. Breakpoints mirror the
// gallery grid intent: ~260px target column width, min 1 column.
// gallery grid intent: ~390px target column width, min 1 column. Bumped
// from 260px on 2026-05-23 per dogfood UX feedback (thumbs were too small).
export function columnCountForWidth(width) {
if (!width || width < 0) return 1
return Math.max(1, Math.floor(width / 260))
return Math.max(1, Math.floor(width / 390))
}
export function usePolyMasonry(containerRef) {
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<v-container class="py-6">
<v-container fluid class="py-6">
<v-tabs v-model="tab" color="accent" class="mb-4">
<v-tab value="overview">Overview</v-tab>
<v-tab value="import">Import</v-tab>
+1 -1
View File
@@ -76,7 +76,7 @@ async def test_system_stats_shape(client):
resp = await client.get("/api/system/stats")
assert resp.status_code == 200
body = await resp.get_json()
for key in ("total_images", "total_tags", "storage_bytes", "tasks", "active_batch"):
for key in ("total_images", "total_tags", "storage_bytes", "subscription_count", "tasks", "active_batch"):
assert key in body
for status in ("pending", "queued", "processing", "complete", "skipped", "failed"):
assert status in body["tasks"]