updated
This commit is contained in:
parent
f5deb65f75
commit
3052836f3b
@ -751,7 +751,7 @@ export default function App() {
|
|||||||
} finally {
|
} finally {
|
||||||
donePrefetchInFlightRef.current = false
|
donePrefetchInFlightRef.current = false
|
||||||
}
|
}
|
||||||
}, [doneSort, includeKeep])
|
}, [doneSort, includeKeep, donePageSize])
|
||||||
|
|
||||||
const loadDoneCount = useCallback(async () => {
|
const loadDoneCount = useCallback(async () => {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
@ -794,6 +794,11 @@ export default function App() {
|
|||||||
|
|
||||||
donePrefetchRef.current = null
|
donePrefetchRef.current = null
|
||||||
|
|
||||||
|
const totalPages = getDoneTotalPages(doneCount, donePageSize)
|
||||||
|
if (pageToLoad < totalPages) {
|
||||||
|
void prefetchDonePage(pageToLoad + 1)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -822,10 +827,15 @@ export default function App() {
|
|||||||
if (count !== doneCount) {
|
if (count !== doneCount) {
|
||||||
setDoneCount(count)
|
setDoneCount(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalPages = getDoneTotalPages(count, donePageSize)
|
||||||
|
if (pageToLoad < totalPages) {
|
||||||
|
void prefetchDonePage(pageToLoad + 1)
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}, [donePage, doneSort, prefetchDonePage, includeKeep, doneCount])
|
}, [donePage, doneSort, donePageSize, prefetchDonePage, includeKeep, doneCount])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
@ -2767,6 +2777,47 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fillDonePageFromPrefetch = useCallback((opts?: { removedFile?: string }) => {
|
||||||
|
const removedFile = String(opts?.removedFile ?? '').trim()
|
||||||
|
|
||||||
|
setDoneJobs((prev) => {
|
||||||
|
const filtered = removedFile
|
||||||
|
? prev.filter((j) => baseName(j.output || '') !== removedFile)
|
||||||
|
: [...prev]
|
||||||
|
|
||||||
|
const need = Math.max(0, donePageSize - filtered.length)
|
||||||
|
if (need <= 0) return filtered
|
||||||
|
|
||||||
|
const prefetchKey = makePrefetchKey(donePage + 1, doneSort)
|
||||||
|
const buf = donePrefetchRef.current
|
||||||
|
|
||||||
|
if (!buf || buf.key !== prefetchKey || !Array.isArray(buf.items) || buf.items.length === 0) {
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
const next: RecordJob[] = [...filtered]
|
||||||
|
const used = new Set(
|
||||||
|
next.map((x) => String(x.id || baseName(x.output || '')).trim())
|
||||||
|
)
|
||||||
|
|
||||||
|
while (next.length < donePageSize && buf.items.length > 0) {
|
||||||
|
const cand = buf.items.shift()!
|
||||||
|
const id = String(cand.id || baseName(cand.output || '')).trim()
|
||||||
|
if (!id || used.has(id)) continue
|
||||||
|
used.add(id)
|
||||||
|
next.push(cand)
|
||||||
|
}
|
||||||
|
|
||||||
|
donePrefetchRef.current = {
|
||||||
|
...buf,
|
||||||
|
items: buf.items,
|
||||||
|
ts: buf.ts,
|
||||||
|
}
|
||||||
|
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}, [donePage, donePageSize, doneSort])
|
||||||
|
|
||||||
const handleDeleteJobWithUndo = useCallback(
|
const handleDeleteJobWithUndo = useCallback(
|
||||||
async (job: RecordJob): Promise<void | { undoToken?: string }> => {
|
async (job: RecordJob): Promise<void | { undoToken?: string }> => {
|
||||||
const file = baseName(job.output || '')
|
const file = baseName(job.output || '')
|
||||||
@ -2783,45 +2834,14 @@ export default function App() {
|
|||||||
),
|
),
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
// ✅ Done-Liste lokal bereinigen + Seite direkt wieder auffüllen (aus Prefetch)
|
fillDonePageFromPrefetch({ removedFile: file })
|
||||||
setDoneJobs((prev) => {
|
|
||||||
const filtered = prev.filter((j) => baseName(j.output || '') !== file)
|
|
||||||
|
|
||||||
const need = donePageSize - filtered.length
|
|
||||||
if (need <= 0) return filtered
|
|
||||||
|
|
||||||
const prefetchKey = makePrefetchKey(donePage + 1, doneSort)
|
|
||||||
const buf = donePrefetchRef.current
|
|
||||||
|
|
||||||
if (!buf || buf.key !== prefetchKey || !Array.isArray(buf.items) || buf.items.length === 0) {
|
|
||||||
return filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
const next: RecordJob[] = [...filtered]
|
|
||||||
const used = new Set(next.map((x) => String(x.id || baseName(x.output || '')).trim()))
|
|
||||||
|
|
||||||
while (next.length < donePageSize && buf.items.length > 0) {
|
|
||||||
const cand = buf.items.shift()!
|
|
||||||
const id = String(cand.id || baseName(cand.output || '')).trim()
|
|
||||||
if (!id || used.has(id)) continue
|
|
||||||
used.add(id)
|
|
||||||
next.push(cand)
|
|
||||||
}
|
|
||||||
|
|
||||||
donePrefetchRef.current = { ...buf, items: buf.items, ts: buf.ts }
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
|
|
||||||
// ✅ Count sofort optimistisch runter
|
|
||||||
setDoneCount((c) => Math.max(0, c - 1))
|
setDoneCount((c) => Math.max(0, c - 1))
|
||||||
|
|
||||||
// ✅ Running-/Player-State bereinigen (falls offen)
|
|
||||||
setJobs((prev) => prev.filter((j) => baseName(j.output || '') !== file))
|
setJobs((prev) => prev.filter((j) => baseName(j.output || '') !== file))
|
||||||
setPlayerJob((prev) => (prev && baseName(prev.output || '') === file ? null : prev))
|
setPlayerJob((prev) => (prev && baseName(prev.output || '') === file ? null : prev))
|
||||||
|
|
||||||
// ✅ Prefetch wieder nachfüllen – aber nur wenn es noch eine nächste Seite gibt
|
|
||||||
const nextDoneCount = Math.max(0, doneCount - 1)
|
const nextDoneCount = Math.max(0, doneCount - 1)
|
||||||
const totalPagesAfterDelete = getDoneTotalPages(nextDoneCount)
|
const totalPagesAfterDelete = getDoneTotalPages(nextDoneCount, donePageSize)
|
||||||
|
|
||||||
if (donePage < totalPagesAfterDelete) {
|
if (donePage < totalPagesAfterDelete) {
|
||||||
void prefetchDonePage(donePage + 1)
|
void prefetchDonePage(donePage + 1)
|
||||||
@ -2841,7 +2861,15 @@ export default function App() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[runFinishedFileAction, notify, donePage, doneSort, prefetchDonePage, doneCount]
|
[
|
||||||
|
runFinishedFileAction,
|
||||||
|
notify,
|
||||||
|
fillDonePageFromPrefetch,
|
||||||
|
donePage,
|
||||||
|
prefetchDonePage,
|
||||||
|
doneCount,
|
||||||
|
loadDoneCount,
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleKeepJob = useCallback(
|
const handleKeepJob = useCallback(
|
||||||
@ -2856,45 +2884,14 @@ export default function App() {
|
|||||||
run: () => apiJSON(`/api/record/keep?file=${encodeURIComponent(file)}`, { method: 'POST' }),
|
run: () => apiJSON(`/api/record/keep?file=${encodeURIComponent(file)}`, { method: 'POST' }),
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
// ✅ Done-Liste lokal bereinigen + Seite direkt wieder auffüllen (aus Prefetch)
|
fillDonePageFromPrefetch({ removedFile: file })
|
||||||
setDoneJobs((prev) => {
|
|
||||||
const filtered = prev.filter((j) => baseName(j.output || '') !== file)
|
|
||||||
|
|
||||||
const need = donePageSize - filtered.length
|
|
||||||
if (need <= 0) return filtered
|
|
||||||
|
|
||||||
const prefetchKey = makePrefetchKey(donePage + 1, doneSort)
|
|
||||||
const buf = donePrefetchRef.current
|
|
||||||
|
|
||||||
if (!buf || buf.key !== prefetchKey || !Array.isArray(buf.items) || buf.items.length === 0) {
|
|
||||||
return filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
const next: RecordJob[] = [...filtered]
|
|
||||||
const used = new Set(next.map((x) => String(x.id || baseName(x.output || '')).trim()))
|
|
||||||
|
|
||||||
while (next.length < donePageSize && buf.items.length > 0) {
|
|
||||||
const cand = buf.items.shift()!
|
|
||||||
const id = String(cand.id || baseName(cand.output || '')).trim()
|
|
||||||
if (!id || used.has(id)) continue
|
|
||||||
used.add(id)
|
|
||||||
next.push(cand)
|
|
||||||
}
|
|
||||||
|
|
||||||
donePrefetchRef.current = { ...buf, items: buf.items, ts: buf.ts }
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
|
|
||||||
// ✅ Entfernt aus Running + Player
|
|
||||||
setJobs((prev) => prev.filter((j) => baseName(j.output || '') !== file))
|
setJobs((prev) => prev.filter((j) => baseName(j.output || '') !== file))
|
||||||
setPlayerJob((prev) => (prev && baseName(prev.output || '') === file ? null : prev))
|
setPlayerJob((prev) => (prev && baseName(prev.output || '') === file ? null : prev))
|
||||||
|
|
||||||
// ✅ Count runter
|
|
||||||
setDoneCount((c) => Math.max(0, c - 1))
|
setDoneCount((c) => Math.max(0, c - 1))
|
||||||
|
|
||||||
// ✅ Prefetch wieder nachfüllen – aber nur wenn es noch eine nächste Seite gibt
|
|
||||||
const nextDoneCount = Math.max(0, doneCount - 1)
|
const nextDoneCount = Math.max(0, doneCount - 1)
|
||||||
const totalPagesAfterKeep = getDoneTotalPages(nextDoneCount)
|
const totalPagesAfterKeep = getDoneTotalPages(nextDoneCount, donePageSize)
|
||||||
|
|
||||||
if (donePage < totalPagesAfterKeep) {
|
if (donePage < totalPagesAfterKeep) {
|
||||||
void prefetchDonePage(donePage + 1)
|
void prefetchDonePage(donePage + 1)
|
||||||
@ -2914,8 +2911,8 @@ export default function App() {
|
|||||||
[
|
[
|
||||||
runFinishedFileAction,
|
runFinishedFileAction,
|
||||||
notify,
|
notify,
|
||||||
|
fillDonePageFromPrefetch,
|
||||||
donePage,
|
donePage,
|
||||||
doneSort,
|
|
||||||
prefetchDonePage,
|
prefetchDonePage,
|
||||||
loadDoneCount,
|
loadDoneCount,
|
||||||
doneCount,
|
doneCount,
|
||||||
@ -3310,8 +3307,11 @@ export default function App() {
|
|||||||
}, ms)
|
}, ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
const kick = () => void checkClipboard()
|
const kick = () => {
|
||||||
window.addEventListener('hover', kick)
|
void checkClipboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('focus', kick)
|
||||||
document.addEventListener('visibilitychange', kick)
|
document.addEventListener('visibilitychange', kick)
|
||||||
|
|
||||||
schedule(0)
|
schedule(0)
|
||||||
@ -3319,7 +3319,7 @@ export default function App() {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
if (timer) window.clearTimeout(timer)
|
if (timer) window.clearTimeout(timer)
|
||||||
window.removeEventListener('hover', kick)
|
window.removeEventListener('focus', kick)
|
||||||
document.removeEventListener('visibilitychange', kick)
|
document.removeEventListener('visibilitychange', kick)
|
||||||
}
|
}
|
||||||
}, [autoAddEnabled, autoStartEnabled, enqueueStart])
|
}, [autoAddEnabled, autoStartEnabled, enqueueStart])
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useMemo, useState, useCallback, useEffect, useRef } from 'react'
|
import { useMemo, useState, useCallback, useEffect, useRef } from 'react'
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
import Table, { type Column } from './Table'
|
import Table, { type Column } from './Table'
|
||||||
import Card from './Card'
|
import Card from './Card'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
@ -563,6 +564,46 @@ function DiskEmergencyBadge() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LazyMountWhenVisible({
|
||||||
|
children,
|
||||||
|
placeholder,
|
||||||
|
rootMargin = '300px',
|
||||||
|
}: {
|
||||||
|
children: ReactNode
|
||||||
|
placeholder: ReactNode
|
||||||
|
rootMargin?: string
|
||||||
|
}) {
|
||||||
|
const hostRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) return
|
||||||
|
|
||||||
|
const el = hostRef.current
|
||||||
|
if (!el) return
|
||||||
|
|
||||||
|
const io = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
const entry = entries[0]
|
||||||
|
if (entry?.isIntersecting) {
|
||||||
|
setVisible(true)
|
||||||
|
io.disconnect()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ rootMargin, threshold: 0.01 }
|
||||||
|
)
|
||||||
|
|
||||||
|
io.observe(el)
|
||||||
|
return () => io.disconnect()
|
||||||
|
}, [visible, rootMargin])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={hostRef} className="w-full h-full">
|
||||||
|
{visible ? children : placeholder}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function Downloads({
|
export default function Downloads({
|
||||||
jobs,
|
jobs,
|
||||||
pending = [],
|
pending = [],
|
||||||
@ -952,6 +993,11 @@ export default function Downloads({
|
|||||||
const j = r.job
|
const j = r.job
|
||||||
return (
|
return (
|
||||||
<div className="grid w-[96px] h-[60px] overflow-hidden rounded-md">
|
<div className="grid w-[96px] h-[60px] overflow-hidden rounded-md">
|
||||||
|
<LazyMountWhenVisible
|
||||||
|
placeholder={
|
||||||
|
<div className="h-full w-full bg-gray-100 dark:bg-white/10" />
|
||||||
|
}
|
||||||
|
>
|
||||||
<ModelPreview
|
<ModelPreview
|
||||||
jobId={j.id}
|
jobId={j.id}
|
||||||
blur={blurPreviews}
|
blur={blurPreviews}
|
||||||
@ -970,6 +1016,7 @@ export default function Downloads({
|
|||||||
thumbsCandidates={jobThumbsJPGCandidates(j)}
|
thumbsCandidates={jobThumbsJPGCandidates(j)}
|
||||||
className="w-full h-full"
|
className="w-full h-full"
|
||||||
/>
|
/>
|
||||||
|
</LazyMountWhenVisible>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1289,7 +1289,6 @@ export default function FinishedDownloads({
|
|||||||
const GALLERY_CARD_SCALE_KEY = 'finishedDownloads_galleryCardScale_v1'
|
const GALLERY_CARD_SCALE_KEY = 'finishedDownloads_galleryCardScale_v1'
|
||||||
const LAST_UNDO_KEY = 'finishedDownloads_lastUndo_v1'
|
const LAST_UNDO_KEY = 'finishedDownloads_lastUndo_v1'
|
||||||
|
|
||||||
const allMode = loadMode === 'all'
|
|
||||||
const requestedTeaserPlaybackMode: TeaserPlaybackMode = teaserPlayback ?? 'hover'
|
const requestedTeaserPlaybackMode: TeaserPlaybackMode = teaserPlayback ?? 'hover'
|
||||||
const teaserPlaybackMode: TeaserPlaybackMode =
|
const teaserPlaybackMode: TeaserPlaybackMode =
|
||||||
pauseTeasers ? 'still' : requestedTeaserPlaybackMode
|
pauseTeasers ? 'still' : requestedTeaserPlaybackMode
|
||||||
@ -1327,7 +1326,6 @@ export default function FinishedDownloads({
|
|||||||
const [removingKeys, setRemovingKeys] = React.useState<Set<string>>(() => new Set())
|
const [removingKeys, setRemovingKeys] = React.useState<Set<string>>(() => new Set())
|
||||||
const [hiddenFiles, setHiddenFiles] = React.useState<Set<string>>(() => new Set())
|
const [hiddenFiles, setHiddenFiles] = React.useState<Set<string>>(() => new Set())
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = React.useState(false)
|
|
||||||
const [undoing, setUndoing] = React.useState(false)
|
const [undoing, setUndoing] = React.useState(false)
|
||||||
const [bulkBusy, setBulkBusy] = React.useState(false)
|
const [bulkBusy, setBulkBusy] = React.useState(false)
|
||||||
|
|
||||||
@ -1339,11 +1337,6 @@ export default function FinishedDownloads({
|
|||||||
|
|
||||||
const [renamedFiles, setRenamedFiles] = React.useState<Record<string, string>>({})
|
const [renamedFiles, setRenamedFiles] = React.useState<Record<string, string>>({})
|
||||||
|
|
||||||
const [overrideDoneJobs, setOverrideDoneJobs] = React.useState<RecordJob[] | null>(null)
|
|
||||||
const [overrideDoneJobsKey, setOverrideDoneJobsKey] = React.useState<string | null>(null)
|
|
||||||
const [overrideDoneTotal, setOverrideDoneTotal] = React.useState<number | null>(null)
|
|
||||||
const [refillTick, setRefillTick] = React.useState(0)
|
|
||||||
|
|
||||||
const [view, setView] = React.useState<ViewMode>('table')
|
const [view, setView] = React.useState<ViewMode>('table')
|
||||||
const [mobileOptionsOpen, setMobileOptionsOpen] = React.useState(false)
|
const [mobileOptionsOpen, setMobileOptionsOpen] = React.useState(false)
|
||||||
const [selectionActionsOpen, setSelectionActionsOpen] = React.useState(false)
|
const [selectionActionsOpen, setSelectionActionsOpen] = React.useState(false)
|
||||||
@ -1396,12 +1389,6 @@ export default function FinishedDownloads({
|
|||||||
const teaserKeyRafRef = React.useRef<number | null>(null)
|
const teaserKeyRafRef = React.useRef<number | null>(null)
|
||||||
const pendingTeaserKeyRef = React.useRef<string | null>(null)
|
const pendingTeaserKeyRef = React.useRef<string | null>(null)
|
||||||
|
|
||||||
const refillInFlightRef = React.useRef(false)
|
|
||||||
const refillQueuedWhileInFlightRef = React.useRef(false)
|
|
||||||
const refillSessionRef = React.useRef(0)
|
|
||||||
const refillTimerRef = React.useRef<number | null>(null)
|
|
||||||
const refillRetryRef = React.useRef(0)
|
|
||||||
|
|
||||||
const countHintRef = React.useRef({ pending: 0, t: 0, timer: 0 as any })
|
const countHintRef = React.useRef({ pending: 0, t: 0, timer: 0 as any })
|
||||||
|
|
||||||
const scaleLiveRafRef = React.useRef<number | null>(null)
|
const scaleLiveRafRef = React.useRef<number | null>(null)
|
||||||
@ -1418,8 +1405,6 @@ export default function FinishedDownloads({
|
|||||||
|
|
||||||
const removeTimersRef = React.useRef<Map<string, number>>(new Map())
|
const removeTimersRef = React.useRef<Map<string, number>>(new Map())
|
||||||
|
|
||||||
const galleryHydratedKeyRef = React.useRef<string | null>(null)
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// memoized derived data
|
// memoized derived data
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@ -1467,39 +1452,17 @@ export default function FinishedDownloads({
|
|||||||
activeTagSet.size > 0 || searchTokens.some((t) => t.length >= 2)
|
activeTagSet.size > 0 || searchTokens.some((t) => t.length >= 2)
|
||||||
|
|
||||||
const globalFilterActive = searchActiveForGlobalFetch
|
const globalFilterActive = searchActiveForGlobalFetch
|
||||||
const effectiveAllMode = globalFilterActive || allMode
|
const effectiveAllMode = globalFilterActive || loadMode === 'all'
|
||||||
|
|
||||||
const galleryPageSize = Math.max(2, galleryColumnCount * 2)
|
const galleryPageSize = Math.max(2, galleryColumnCount * 2)
|
||||||
|
|
||||||
const serverPageSize = pageSize
|
|
||||||
const effectivePageSize =
|
const effectivePageSize =
|
||||||
view === 'gallery'
|
view === 'gallery'
|
||||||
? galleryPageSize
|
? galleryPageSize
|
||||||
: pageSize
|
: pageSize
|
||||||
|
|
||||||
const galleryHydrationKey =
|
const doneJobsPage = doneJobs
|
||||||
view === 'gallery' && loadMode === 'paged' && !effectiveAllMode
|
const doneTotalPage = doneTotal
|
||||||
? `${page}__${sortMode}__${includeKeep ? 1 : 0}__${effectivePageSize}`
|
|
||||||
: null
|
|
||||||
|
|
||||||
const hasMatchingGalleryOverride =
|
|
||||||
view === 'gallery' &&
|
|
||||||
galleryHydrationKey != null &&
|
|
||||||
overrideDoneJobsKey === galleryHydrationKey
|
|
||||||
|
|
||||||
const doneJobsPage =
|
|
||||||
effectiveAllMode
|
|
||||||
? (overrideDoneJobs ?? doneJobs)
|
|
||||||
: hasMatchingGalleryOverride && overrideDoneJobs
|
|
||||||
? overrideDoneJobs
|
|
||||||
: doneJobs
|
|
||||||
|
|
||||||
const doneTotalPage =
|
|
||||||
effectiveAllMode
|
|
||||||
? (overrideDoneTotal ?? doneTotal)
|
|
||||||
: hasMatchingGalleryOverride && overrideDoneTotal != null
|
|
||||||
? overrideDoneTotal
|
|
||||||
: doneTotal
|
|
||||||
|
|
||||||
const applyRenamedOutputLocal = useCallback(
|
const applyRenamedOutputLocal = useCallback(
|
||||||
(job: RecordJob): RecordJob => {
|
(job: RecordJob): RecordJob => {
|
||||||
@ -1701,9 +1664,7 @@ export default function FinishedDownloads({
|
|||||||
|
|
||||||
const emptyFolder = !effectiveAllMode && totalItemsForPagination === 0
|
const emptyFolder = !effectiveAllMode && totalItemsForPagination === 0
|
||||||
const emptyByFilter = globalFilterActive && visibleRows.length === 0
|
const emptyByFilter = globalFilterActive && visibleRows.length === 0
|
||||||
const emptyAll = allMode && visibleRows.length === 0
|
const isEmptyState = emptyFolder || emptyByFilter
|
||||||
const isEmptyState = emptyFolder || emptyAll || emptyByFilter
|
|
||||||
const showLoadingCard = isLoading && !isEmptyState
|
|
||||||
|
|
||||||
const selectedCount = selectedFiles.length
|
const selectedCount = selectedFiles.length
|
||||||
const allPageSelected =
|
const allPageSelected =
|
||||||
@ -1837,42 +1798,6 @@ export default function FinishedDownloads({
|
|||||||
|
|
||||||
const clearTagFilter = useCallback(() => setTagFilter([]), [])
|
const clearTagFilter = useCallback(() => setTagFilter([]), [])
|
||||||
|
|
||||||
const fetchAllDoneJobs = useCallback(
|
|
||||||
async (signal?: AbortSignal) => {
|
|
||||||
setIsLoading(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(
|
|
||||||
`/api/record/done?all=1&sort=${encodeURIComponent(sortMode)}&withCount=1${includeKeep ? '&includeKeep=1' : ''}`,
|
|
||||||
{
|
|
||||||
cache: 'no-store' as any,
|
|
||||||
signal,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (!res.ok) return
|
|
||||||
|
|
||||||
const data = await res.json().catch(() => null)
|
|
||||||
const items = Array.isArray(data?.items) ? (data.items as RecordJob[]) : []
|
|
||||||
const count = Number(data?.count ?? data?.totalCount ?? items.length)
|
|
||||||
|
|
||||||
setOverrideDoneJobs(items)
|
|
||||||
setOverrideDoneTotal(Number.isFinite(count) ? count : items.length)
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[sortMode, includeKeep]
|
|
||||||
)
|
|
||||||
|
|
||||||
const queueRefill = useCallback(() => {
|
|
||||||
if (refillTimerRef.current != null) return
|
|
||||||
|
|
||||||
refillTimerRef.current = window.setTimeout(() => {
|
|
||||||
refillTimerRef.current = null
|
|
||||||
setRefillTick((n) => n + 1)
|
|
||||||
}, 80)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const flushResolutionsSoon = useCallback(() => {
|
const flushResolutionsSoon = useCallback(() => {
|
||||||
if (resolutionsFlushTimerRef.current != null) return
|
if (resolutionsFlushTimerRef.current != null) return
|
||||||
resolutionsFlushTimerRef.current = window.setTimeout(() => {
|
resolutionsFlushTimerRef.current = window.setTimeout(() => {
|
||||||
@ -2273,28 +2198,16 @@ export default function FinishedDownloads({
|
|||||||
return (await r.json().catch(() => null)) as any
|
return (await r.json().catch(() => null)) as any
|
||||||
},
|
},
|
||||||
onSuccess: async (result: any) => {
|
onSuccess: async (result: any) => {
|
||||||
if (onDeleteJob) {
|
|
||||||
const undoToken = typeof result?.undoToken === 'string' ? result.undoToken : ''
|
const undoToken = typeof result?.undoToken === 'string' ? result.undoToken : ''
|
||||||
|
|
||||||
if (undoToken) {
|
if (undoToken) {
|
||||||
setLastAction({ kind: 'delete', undoToken, originalFile: file, rowKey: key })
|
setLastAction({ kind: 'delete', undoToken, originalFile: file, rowKey: key })
|
||||||
} else {
|
} else {
|
||||||
setLastAction(null)
|
setLastAction(null)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const from = (result?.from === 'keep' ? 'keep' : 'done') as 'done' | 'keep'
|
|
||||||
const undoToken = typeof result?.undoToken === 'string' ? result.undoToken : ''
|
|
||||||
|
|
||||||
if (undoToken) {
|
|
||||||
setLastAction({ kind: 'delete', undoToken, originalFile: file, rowKey: key, from })
|
|
||||||
} else {
|
|
||||||
setLastAction(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
queueRefill()
|
|
||||||
emitCountHint(-1)
|
emitCountHint(-1)
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return res.ok
|
return res.ok
|
||||||
@ -2305,7 +2218,6 @@ export default function FinishedDownloads({
|
|||||||
onDeleteJob,
|
onDeleteJob,
|
||||||
withFileReleaseRetry,
|
withFileReleaseRetry,
|
||||||
runFileMutation,
|
runFileMutation,
|
||||||
queueRefill,
|
|
||||||
emitCountHint,
|
emitCountHint,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -2355,9 +2267,8 @@ export default function FinishedDownloads({
|
|||||||
|
|
||||||
setLastAction({ kind: 'keep', keptFile, originalFile: file, rowKey: key })
|
setLastAction({ kind: 'keep', keptFile, originalFile: file, rowKey: key })
|
||||||
|
|
||||||
queueRefill()
|
|
||||||
emitCountHint(includeKeep ? 0 : -1)
|
emitCountHint(includeKeep ? 0 : -1)
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return res.ok
|
return res.ok
|
||||||
@ -2368,7 +2279,6 @@ export default function FinishedDownloads({
|
|||||||
deletingKeys,
|
deletingKeys,
|
||||||
withFileReleaseRetry,
|
withFileReleaseRetry,
|
||||||
runFileMutation,
|
runFileMutation,
|
||||||
queueRefill,
|
|
||||||
emitCountHint,
|
emitCountHint,
|
||||||
includeKeep,
|
includeKeep,
|
||||||
]
|
]
|
||||||
@ -2468,7 +2378,6 @@ export default function FinishedDownloads({
|
|||||||
|
|
||||||
const visibleDelta = lastAction.from === 'keep' && !includeKeep ? 0 : +1
|
const visibleDelta = lastAction.from === 'keep' && !includeKeep ? 0 : +1
|
||||||
emitCountHint(visibleDelta)
|
emitCountHint(visibleDelta)
|
||||||
queueRefill()
|
|
||||||
setLastAction(null)
|
setLastAction(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2490,7 +2399,6 @@ export default function FinishedDownloads({
|
|||||||
unhideRow(restoredFile, restoredFile)
|
unhideRow(restoredFile, restoredFile)
|
||||||
|
|
||||||
emitCountHint(+1)
|
emitCountHint(+1)
|
||||||
queueRefill()
|
|
||||||
setLastAction(null)
|
setLastAction(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2513,7 +2421,6 @@ export default function FinishedDownloads({
|
|||||||
applyRename(oldFile, newFile)
|
applyRename(oldFile, newFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
queueRefill()
|
|
||||||
setLastAction(null)
|
setLastAction(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2526,7 +2433,6 @@ export default function FinishedDownloads({
|
|||||||
lastAction,
|
lastAction,
|
||||||
undoing,
|
undoing,
|
||||||
notify,
|
notify,
|
||||||
queueRefill,
|
|
||||||
includeKeep,
|
includeKeep,
|
||||||
emitCountHint,
|
emitCountHint,
|
||||||
applyRename,
|
applyRename,
|
||||||
@ -2594,10 +2500,6 @@ export default function FinishedDownloads({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLastAction({ kind: 'hot', currentFile: apiNew })
|
setLastAction({ kind: 'hot', currentFile: apiNew })
|
||||||
|
|
||||||
if (!onToggleHot || sortMode === 'file_asc' || sortMode === 'file_desc') {
|
|
||||||
queueRefill()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onError: async () => {
|
onError: async () => {
|
||||||
clearRenamePair(oldFile, optimisticNew)
|
clearRenamePair(oldFile, optimisticNew)
|
||||||
@ -2613,8 +2515,6 @@ export default function FinishedDownloads({
|
|||||||
clearRenamePair,
|
clearRenamePair,
|
||||||
onToggleHot,
|
onToggleHot,
|
||||||
withFileReleaseRetry,
|
withFileReleaseRetry,
|
||||||
queueRefill,
|
|
||||||
sortMode,
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2807,7 +2707,6 @@ export default function FinishedDownloads({
|
|||||||
const deletedCount = Number(data?.deleted ?? results.filter((x: any) => x?.ok).length ?? 0)
|
const deletedCount = Number(data?.deleted ?? results.filter((x: any) => x?.ok).length ?? 0)
|
||||||
|
|
||||||
clearSelection()
|
clearSelection()
|
||||||
queueRefill()
|
|
||||||
emitCountHint(-deletedCount)
|
emitCountHint(-deletedCount)
|
||||||
|
|
||||||
if (deletedCount > 0) {
|
if (deletedCount > 0) {
|
||||||
@ -2824,7 +2723,6 @@ export default function FinishedDownloads({
|
|||||||
selectedItems,
|
selectedItems,
|
||||||
animateRemove,
|
animateRemove,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
queueRefill,
|
|
||||||
emitCountHint,
|
emitCountHint,
|
||||||
notify,
|
notify,
|
||||||
])
|
])
|
||||||
@ -2869,7 +2767,6 @@ export default function FinishedDownloads({
|
|||||||
const keptCount = Number(data?.kept ?? results.filter((x: any) => x?.ok).length ?? 0)
|
const keptCount = Number(data?.kept ?? results.filter((x: any) => x?.ok).length ?? 0)
|
||||||
|
|
||||||
clearSelection()
|
clearSelection()
|
||||||
queueRefill()
|
|
||||||
emitCountHint(includeKeep ? 0 : -keptCount)
|
emitCountHint(includeKeep ? 0 : -keptCount)
|
||||||
|
|
||||||
if (keptCount > 0) {
|
if (keptCount > 0) {
|
||||||
@ -2886,7 +2783,6 @@ export default function FinishedDownloads({
|
|||||||
selectedItems,
|
selectedItems,
|
||||||
animateRemove,
|
animateRemove,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
queueRefill,
|
|
||||||
emitCountHint,
|
emitCountHint,
|
||||||
includeKeep,
|
includeKeep,
|
||||||
notify,
|
notify,
|
||||||
@ -2905,21 +2801,6 @@ export default function FinishedDownloads({
|
|||||||
}
|
}
|
||||||
}, [view, galleryColumnCount, pageSize, onPageSizeChange])
|
}, [view, galleryColumnCount, pageSize, onPageSizeChange])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (effectiveAllMode) return
|
|
||||||
setOverrideDoneJobs(null)
|
|
||||||
setOverrideDoneTotal(null)
|
|
||||||
setOverrideDoneJobsKey(null)
|
|
||||||
}, [effectiveAllMode])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (view === 'gallery') return
|
|
||||||
|
|
||||||
setOverrideDoneJobs(null)
|
|
||||||
setOverrideDoneTotal(null)
|
|
||||||
setOverrideDoneJobsKey(null)
|
|
||||||
}, [view])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(GALLERY_CARD_SCALE_KEY)
|
const raw = localStorage.getItem(GALLERY_CARD_SCALE_KEY)
|
||||||
@ -3250,73 +3131,6 @@ export default function FinishedDownloads({
|
|||||||
}, [lastAction])
|
}, [lastAction])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!effectiveAllMode) return
|
|
||||||
|
|
||||||
const ac = new AbortController()
|
|
||||||
|
|
||||||
const t = window.setTimeout(() => {
|
|
||||||
fetchAllDoneJobs(ac.signal).catch(() => {})
|
|
||||||
}, 250)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.clearTimeout(t)
|
|
||||||
ac.abort()
|
|
||||||
}
|
|
||||||
}, [effectiveAllMode, fetchAllDoneJobs])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (refillTick === 0) return
|
|
||||||
|
|
||||||
const ac = new AbortController()
|
|
||||||
let alive = true
|
|
||||||
let finished = false
|
|
||||||
const mySession = ++refillSessionRef.current
|
|
||||||
|
|
||||||
const finishRefill = () => {
|
|
||||||
if (finished) return
|
|
||||||
finished = true
|
|
||||||
|
|
||||||
if (refillSessionRef.current !== mySession) return
|
|
||||||
|
|
||||||
refillInFlightRef.current = false
|
|
||||||
|
|
||||||
if (refillQueuedWhileInFlightRef.current) {
|
|
||||||
refillQueuedWhileInFlightRef.current = false
|
|
||||||
queueRefill()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refillInFlightRef.current = true
|
|
||||||
|
|
||||||
// ✅ paged-mode hier NICHT mehr laden
|
|
||||||
if (!effectiveAllMode) {
|
|
||||||
finishRefill()
|
|
||||||
return () => {
|
|
||||||
alive = false
|
|
||||||
ac.abort()
|
|
||||||
finishRefill()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
;(async () => {
|
|
||||||
try {
|
|
||||||
await fetchAllDoneJobs(ac.signal)
|
|
||||||
if (alive) refillRetryRef.current = 0
|
|
||||||
} catch {
|
|
||||||
} finally {
|
|
||||||
if (alive) finishRefill()
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
alive = false
|
|
||||||
ac.abort()
|
|
||||||
finishRefill()
|
|
||||||
}
|
|
||||||
}, [refillTick, effectiveAllMode, fetchAllDoneJobs, queueRefill])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isLoading) return
|
|
||||||
if (effectiveAllMode) return
|
if (effectiveAllMode) return
|
||||||
if (totalItemsForPagination < 0) return
|
if (totalItemsForPagination < 0) return
|
||||||
|
|
||||||
@ -3326,7 +3140,6 @@ export default function FinishedDownloads({
|
|||||||
onPageChange(totalPages)
|
onPageChange(totalPages)
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isLoading,
|
|
||||||
effectiveAllMode,
|
effectiveAllMode,
|
||||||
totalItemsForPagination,
|
totalItemsForPagination,
|
||||||
effectivePageSize,
|
effectivePageSize,
|
||||||
@ -3334,103 +3147,6 @@ export default function FinishedDownloads({
|
|||||||
onPageChange,
|
onPageChange,
|
||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (loadMode !== 'paged') return
|
|
||||||
if (effectiveAllMode) return
|
|
||||||
if (view !== 'gallery') return
|
|
||||||
if (!galleryHydrationKey) return
|
|
||||||
|
|
||||||
const currentCount = hasMatchingGalleryOverride
|
|
||||||
? (overrideDoneJobs?.length ?? 0)
|
|
||||||
: doneJobs.length
|
|
||||||
|
|
||||||
const wantedCount = effectivePageSize
|
|
||||||
const totalCount = doneTotal
|
|
||||||
|
|
||||||
const pageStart = (page - 1) * wantedCount
|
|
||||||
const maxItemsOnThisPage = Math.max(0, totalCount - pageStart)
|
|
||||||
|
|
||||||
if (maxItemsOnThisPage <= 0) {
|
|
||||||
galleryHydratedKeyRef.current = galleryHydrationKey
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetCount = Math.min(wantedCount, maxItemsOnThisPage)
|
|
||||||
|
|
||||||
if (currentCount >= targetCount) {
|
|
||||||
galleryHydratedKeyRef.current = galleryHydrationKey
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (galleryHydratedKeyRef.current === galleryHydrationKey) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const ac = new AbortController()
|
|
||||||
let cancelled = false
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
|
|
||||||
;(async () => {
|
|
||||||
try {
|
|
||||||
const res = await fetch(
|
|
||||||
`/api/record/done?page=${page}&pageSize=${targetCount}&sort=${encodeURIComponent(sortMode)}${
|
|
||||||
includeKeep ? '&includeKeep=1' : ''
|
|
||||||
}`,
|
|
||||||
{ cache: 'no-store' as any, signal: ac.signal }
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!res.ok || cancelled || ac.signal.aborted) return
|
|
||||||
|
|
||||||
const data = await res.json().catch(() => null)
|
|
||||||
if (cancelled || ac.signal.aborted) return
|
|
||||||
|
|
||||||
const items = Array.isArray(data?.items)
|
|
||||||
? (data.items as RecordJob[])
|
|
||||||
: Array.isArray(data)
|
|
||||||
? data
|
|
||||||
: []
|
|
||||||
|
|
||||||
const countRaw = Number(data?.count ?? data?.totalCount ?? items.length)
|
|
||||||
const count = Number.isFinite(countRaw) && countRaw >= 0 ? countRaw : 0
|
|
||||||
|
|
||||||
setOverrideDoneJobs(items)
|
|
||||||
setOverrideDoneTotal(count)
|
|
||||||
setOverrideDoneJobsKey(galleryHydrationKey)
|
|
||||||
galleryHydratedKeyRef.current = galleryHydrationKey
|
|
||||||
} catch (err: any) {
|
|
||||||
if (err?.name !== 'AbortError') {
|
|
||||||
console.error('gallery hydration failed', err)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (!cancelled) {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
ac.abort()
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
loadMode,
|
|
||||||
effectiveAllMode,
|
|
||||||
view,
|
|
||||||
page,
|
|
||||||
sortMode,
|
|
||||||
includeKeep,
|
|
||||||
doneJobs,
|
|
||||||
doneTotal,
|
|
||||||
effectivePageSize,
|
|
||||||
serverPageSize,
|
|
||||||
galleryHydrationKey,
|
|
||||||
])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
galleryHydratedKeyRef.current = null
|
|
||||||
}, [page, sortMode, includeKeep, effectivePageSize, view])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const m = new Map<string, string>()
|
const m = new Map<string, string>()
|
||||||
for (const j of viewRows) {
|
for (const j of viewRows) {
|
||||||
@ -3516,37 +3232,12 @@ export default function FinishedDownloads({
|
|||||||
|
|
||||||
if (detail.phase === 'success') {
|
if (detail.phase === 'success') {
|
||||||
markDeleting(key, false)
|
markDeleting(key, false)
|
||||||
|
|
||||||
if (refillInFlightRef.current) {
|
|
||||||
refillQueuedWhileInFlightRef.current = true
|
|
||||||
} else {
|
|
||||||
queueRefill()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('finished-downloads:delete', onExternalDelete as EventListener)
|
window.addEventListener('finished-downloads:delete', onExternalDelete as EventListener)
|
||||||
return () => window.removeEventListener('finished-downloads:delete', onExternalDelete as EventListener)
|
return () => window.removeEventListener('finished-downloads:delete', onExternalDelete as EventListener)
|
||||||
}, [animateRemove, markDeleting, queueRefill, restoreRow])
|
}, [animateRemove, markDeleting, restoreRow])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onReload = () => {
|
|
||||||
if (document.hidden) return
|
|
||||||
|
|
||||||
if (refillInFlightRef.current) {
|
|
||||||
if (!refillQueuedWhileInFlightRef.current) {
|
|
||||||
refillQueuedWhileInFlightRef.current = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refillTimerRef.current != null) return
|
|
||||||
queueRefill()
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('finished-downloads:reload', onReload as EventListener)
|
|
||||||
return () => window.removeEventListener('finished-downloads:reload', onReload as EventListener)
|
|
||||||
}, [queueRefill])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onExternalRename = (ev: Event) => {
|
const onExternalRename = (ev: Event) => {
|
||||||
@ -3706,8 +3397,7 @@ export default function FinishedDownloads({
|
|||||||
<LoadingSpinner
|
<LoadingSpinner
|
||||||
size="lg"
|
size="lg"
|
||||||
className={[
|
className={[
|
||||||
'text-indigo-500 transition-opacity duration-150',
|
'text-indigo-500 transition-opacity duration-150 opacity-0 pointer-events-none',
|
||||||
isLoading ? 'opacity-100' : 'opacity-0 pointer-events-none',
|
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
srLabel="Lade Downloads…"
|
srLabel="Lade Downloads…"
|
||||||
/>
|
/>
|
||||||
@ -3866,8 +3556,7 @@ export default function FinishedDownloads({
|
|||||||
<LoadingSpinner
|
<LoadingSpinner
|
||||||
size="lg"
|
size="lg"
|
||||||
className={[
|
className={[
|
||||||
'text-indigo-500 transition-opacity duration-150',
|
'text-indigo-500 transition-opacity duration-150 opacity-0 pointer-events-none',
|
||||||
isLoading ? 'opacity-100' : 'opacity-0 pointer-events-none',
|
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
srLabel="Lade Downloads…"
|
srLabel="Lade Downloads…"
|
||||||
/>
|
/>
|
||||||
@ -4253,26 +3942,7 @@ export default function FinishedDownloads({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showLoadingCard ? (
|
{emptyFolder ? (
|
||||||
<Card grayBody>
|
|
||||||
<div className="flex items-center justify-between gap-3">
|
|
||||||
<div className="min-w-0">
|
|
||||||
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
|
||||||
Lade Downloads…
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-600 dark:text-gray-300">
|
|
||||||
Bitte warte einen Moment.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<LoadingSpinner
|
|
||||||
size="lg"
|
|
||||||
className="text-indigo-500"
|
|
||||||
srLabel="Lade Downloads…"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
) : (emptyFolder || emptyAll) ? (
|
|
||||||
<Card grayBody>
|
<Card grayBody>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="grid h-10 w-10 place-items-center rounded-xl bg-white/70 ring-1 ring-gray-200 dark:bg-white/5 dark:ring-white/10">
|
<div className="grid h-10 w-10 place-items-center rounded-xl bg-white/70 ring-1 ring-gray-200 dark:bg-white/5 dark:ring-white/10">
|
||||||
@ -4332,7 +4002,7 @@ export default function FinishedDownloads({
|
|||||||
}}
|
}}
|
||||||
isSmall={isSmall}
|
isSmall={isSmall}
|
||||||
jobForDetails={jobForDetails}
|
jobForDetails={jobForDetails}
|
||||||
isLoading={isLoading}
|
isLoading={false}
|
||||||
blurPreviews={blurPreviews}
|
blurPreviews={blurPreviews}
|
||||||
durations={durations}
|
durations={durations}
|
||||||
teaserState={teaserState}
|
teaserState={teaserState}
|
||||||
@ -4394,7 +4064,7 @@ export default function FinishedDownloads({
|
|||||||
{view === 'table' && (
|
{view === 'table' && (
|
||||||
<FinishedDownloadsTableView
|
<FinishedDownloadsTableView
|
||||||
rows={pageRows}
|
rows={pageRows}
|
||||||
isLoading={isLoading}
|
isLoading={false}
|
||||||
selectedKeys={selectedKeys}
|
selectedKeys={selectedKeys}
|
||||||
onToggleSelected={toggleSelected}
|
onToggleSelected={toggleSelected}
|
||||||
onToggleSelectAllPage={(checked) => {
|
onToggleSelectAllPage={(checked) => {
|
||||||
@ -4465,7 +4135,7 @@ export default function FinishedDownloads({
|
|||||||
if (checked) selectPageRows(pageRows)
|
if (checked) selectPageRows(pageRows)
|
||||||
else deselectPageRows(pageRows)
|
else deselectPageRows(pageRows)
|
||||||
}}
|
}}
|
||||||
isLoading={isLoading}
|
isLoading={false}
|
||||||
jobForDetails={jobForDetails}
|
jobForDetails={jobForDetails}
|
||||||
blurPreviews={blurPreviews}
|
blurPreviews={blurPreviews}
|
||||||
durations={durations}
|
durations={durations}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user