27cae28e12
describeDownloadError condenses a postprocessing failure to one line for the queue/notification, discarding the rest of yt-dlp's stderr. The persisted error log now also keeps the fuller non-noise transcript (stderrDetail) as an optional `detail` field, and the Diagnostics card gets a collapsible "Show details" expander per row so a failure like "Postprocessing: Conversion failed!" can be inspected -- and copied into a bug report -- without re-running the download from a terminal.
134 lines
4.5 KiB
TypeScript
134 lines
4.5 KiB
TypeScript
import { useState } from 'react'
|
|
import { Button, Card, Subtitle2, Caption1, Text } from '@fluentui/react-components'
|
|
import {
|
|
BugRegular,
|
|
ChevronDownRegular,
|
|
ChevronRightRegular,
|
|
CopyRegular,
|
|
DeleteRegular
|
|
} from '@fluentui/react-icons'
|
|
import { useErrorLog } from '../../store/errorlog'
|
|
import { useErrorTextStyles } from '../../components/ui/errorText'
|
|
import { EmptyState } from '../../components/ui/EmptyState'
|
|
import { useSettingsStyles } from './settingsStyles'
|
|
|
|
export function DiagnosticsCard(): React.JSX.Element {
|
|
const styles = useSettingsStyles()
|
|
const errText = useErrorTextStyles()
|
|
const errorEntries = useErrorLog((s) => s.entries)
|
|
const clearErrorLog = useErrorLog((s) => s.clear)
|
|
|
|
const [confirmClearLog, setConfirmClearLog] = useState(false)
|
|
// Which rows have their "Show details" transcript expanded; keyed by entry id.
|
|
const [expanded, setExpanded] = useState<Set<string>>(new Set())
|
|
function toggleExpanded(id: string): void {
|
|
setExpanded((s) => {
|
|
const next = new Set(s)
|
|
if (next.has(id)) next.delete(id)
|
|
else next.add(id)
|
|
return next
|
|
})
|
|
}
|
|
|
|
function copyErrorReport(): void {
|
|
// The button is disabled when there are no entries, so `report` is always
|
|
// non-empty here -- no need for an unreachable empty-report fallback (L159).
|
|
const report = errorEntries
|
|
.map((e) =>
|
|
[new Date(e.occurredAt).toLocaleString(), e.title ?? e.url, e.url, e.error, e.detail]
|
|
.filter(Boolean)
|
|
.join('\n')
|
|
)
|
|
.join('\n\n---\n\n')
|
|
navigator.clipboard.writeText(report).catch(() => {})
|
|
}
|
|
|
|
return (
|
|
<Card className={styles.card}>
|
|
<div className={styles.sectionHeader}>
|
|
<BugRegular className={styles.sectionIcon} />
|
|
<Subtitle2 as="h3">Diagnostics</Subtitle2>
|
|
</div>
|
|
<Caption1 className={styles.hint}>
|
|
Failed downloads are logged here even after you clear the queue, so you can copy the details
|
|
into a bug report.
|
|
</Caption1>
|
|
|
|
<div className={styles.folderRow}>
|
|
<Button
|
|
icon={<CopyRegular />}
|
|
onClick={copyErrorReport}
|
|
disabled={errorEntries.length === 0}
|
|
>
|
|
Copy full report
|
|
</Button>
|
|
{confirmClearLog ? (
|
|
<>
|
|
<Caption1>
|
|
Clear all {errorEntries.length} {errorEntries.length === 1 ? 'error' : 'errors'}?
|
|
</Caption1>
|
|
<Button
|
|
icon={<DeleteRegular />}
|
|
appearance="primary"
|
|
onClick={() => {
|
|
clearErrorLog()
|
|
setConfirmClearLog(false)
|
|
}}
|
|
>
|
|
Clear log
|
|
</Button>
|
|
<Button appearance="subtle" onClick={() => setConfirmClearLog(false)}>
|
|
Cancel
|
|
</Button>
|
|
</>
|
|
) : (
|
|
<Button
|
|
icon={<DeleteRegular />}
|
|
onClick={() => setConfirmClearLog(true)}
|
|
disabled={errorEntries.length === 0}
|
|
>
|
|
Clear log
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{errorEntries.length === 0 ? (
|
|
<EmptyState compact message="No errors yet." />
|
|
) : (
|
|
<div className={styles.errorList}>
|
|
{errorEntries.slice(0, 20).map((e) => {
|
|
const isExpanded = expanded.has(e.id)
|
|
return (
|
|
<div key={e.id} className={styles.errorRow}>
|
|
<div className={styles.errorRowHeader}>
|
|
<Text className={styles.errorRowTitle}>{e.title ?? e.url}</Text>
|
|
<Caption1 className={styles.hint}>
|
|
{new Date(e.occurredAt).toLocaleString()}
|
|
</Caption1>
|
|
</div>
|
|
<Caption1 className={errText.errorPre}>{e.error}</Caption1>
|
|
{e.detail && (
|
|
<>
|
|
<Button
|
|
appearance="transparent"
|
|
size="small"
|
|
icon={isExpanded ? <ChevronDownRegular /> : <ChevronRightRegular />}
|
|
onClick={() => toggleExpanded(e.id)}
|
|
>
|
|
{isExpanded ? 'Hide details' : 'Show details'}
|
|
</Button>
|
|
{isExpanded && <pre className={styles.errorDetail}>{e.detail}</pre>}
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
{errorEntries.length > 20 && (
|
|
<Caption1 className={styles.hint}>Showing 20 of {errorEntries.length} errors.</Caption1>
|
|
)}
|
|
</div>
|
|
)}
|
|
</Card>
|
|
)
|
|
}
|