feat(ui): cap Dashboard Active and Recent Activity lists at 5 items

Both lists could grow long enough to dominate the viewport (21 queued
downloads was common). Limit each to 5 entries with a "+N more"
affordance: Active shows a "+N more queued" button that deep-links to
/downloads?status=queued, and Recent Activity folds the overflow count
into the existing "View all downloads" button. Seed Downloads.vue's
status filter from the URL query param so the deep-link actually filters
on arrival.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 15:47:38 -04:00
parent 97864efacb
commit e9edaeb66d
2 changed files with 24 additions and 4 deletions
+20 -3
View File
@@ -163,7 +163,7 @@
<v-card-text> <v-card-text>
<v-list v-if="activeDownloads.length" density="compact"> <v-list v-if="activeDownloads.length" density="compact">
<v-list-item <v-list-item
v-for="dl in activeDownloads" v-for="dl in visibleActiveDownloads"
:key="dl.id" :key="dl.id"
:class="{ 'download-running': dl.status === 'running' }" :class="{ 'download-running': dl.status === 'running' }"
> >
@@ -181,6 +181,11 @@
</v-chip> </v-chip>
</v-list-item-subtitle> </v-list-item-subtitle>
</v-list-item> </v-list-item>
<v-list-item v-if="hiddenActiveCount" class="text-caption text-medium-emphasis">
<v-btn variant="text" size="small" to="/downloads?status=queued">
+{{ hiddenActiveCount }} more queued
</v-btn>
</v-list-item>
</v-list> </v-list>
<div v-else class="text-center py-8"> <div v-else class="text-center py-8">
<v-icon size="48" color="secondary" :style="{ opacity: 0.5 }">mdi-check-circle-outline</v-icon> <v-icon size="48" color="secondary" :style="{ opacity: 0.5 }">mdi-check-circle-outline</v-icon>
@@ -202,7 +207,7 @@
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<v-list v-if="recentActivityGrouped.length"> <v-list v-if="recentActivityGrouped.length">
<template v-for="entry in recentActivityGrouped" :key="entry.key"> <template v-for="entry in visibleRecentActivity" :key="entry.key">
<v-list-item v-if="entry.kind === 'single'"> <v-list-item v-if="entry.kind === 'single'">
<template v-slot:prepend> <template v-slot:prepend>
<v-icon :color="getStatusColor(entry.download.status)"> <v-icon :color="getStatusColor(entry.download.status)">
@@ -251,7 +256,9 @@
</div> </div>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-btn variant="text" to="/downloads">View All Downloads</v-btn> <v-btn variant="text" to="/downloads">
{{ hiddenRecentCount ? `View all downloads (+${hiddenRecentCount} more)` : 'View all downloads' }}
</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-col> </v-col>
@@ -415,6 +422,16 @@ const recentActivityGrouped = computed(() => {
return out return out
}) })
const DASHBOARD_LIST_LIMIT = 5
const visibleActiveDownloads = computed(() => activeDownloads.value.slice(0, DASHBOARD_LIST_LIMIT))
const hiddenActiveCount = computed(() =>
Math.max(0, activeDownloads.value.length - DASHBOARD_LIST_LIMIT)
)
const visibleRecentActivity = computed(() => recentActivityGrouped.value.slice(0, DASHBOARD_LIST_LIMIT))
const hiddenRecentCount = computed(() =>
Math.max(0, recentActivityGrouped.value.length - DASHBOARD_LIST_LIMIT)
)
// Read threshold from settings, default 5 if unset or invalid // Read threshold from settings, default 5 if unset or invalid
const failureThreshold = computed(() => { const failureThreshold = computed(() => {
const n = Number(settingsStore.settings['dashboard.failure_threshold']) const n = Number(settingsStore.settings['dashboard.failure_threshold'])
+4 -1
View File
@@ -360,15 +360,18 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue' import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useDownloadsStore } from '../stores/downloads' import { useDownloadsStore } from '../stores/downloads'
import { useSourcesStore } from '../stores/sources' import { useSourcesStore } from '../stores/sources'
import { useNotificationStore } from '../stores/notifications' import { useNotificationStore } from '../stores/notifications'
const route = useRoute()
const downloadsStore = useDownloadsStore() const downloadsStore = useDownloadsStore()
const sourcesStore = useSourcesStore() const sourcesStore = useSourcesStore()
const notifications = useNotificationStore() const notifications = useNotificationStore()
const filterStatus = ref(null) const VALID_STATUSES = ['queued', 'running', 'completed', 'failed', 'skipped']
const filterStatus = ref(VALID_STATUSES.includes(route.query.status) ? route.query.status : null)
const filterSourceId = ref(null) const filterSourceId = ref(null)
const filterFromDate = ref(null) const filterFromDate = ref(null)
const filterToDate = ref(null) const filterToDate = ref(null)