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:
@@ -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'])
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user