From fd3aaea9d9de1f28cf166e9622d809b70c0ab863 Mon Sep 17 00:00:00 2001 From: ddmoney420 Date: Sun, 1 Mar 2026 19:06:58 -0700 Subject: [PATCH] feat: expandable error details with copy-to-clipboard (closes #143) --- ui/src/app/app.html | 38 ++++++++++++++++++++++++++++++-------- ui/src/app/app.ts | 22 +++++++++++++++++++++- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/ui/src/app/app.html b/ui/src/app/app.html index db035b8..6244667 100644 --- a/ui/src/app/app.html +++ b/ui/src/app/app.html @@ -463,20 +463,42 @@ } @if (entry[1].status === 'error') { - + } @if (!!entry[1].filename) { {{ entry[1].title }} } @else { - {{entry[1].title}} - @if (entry[1].msg) { -
{{entry[1].msg}}
- } - @if (entry[1].error) { -
Error: {{entry[1].error}}
- } + + {{entry[1].title}} + @if (entry[1].status === 'error' && !isErrorExpanded(entry[0])) { + + Click for details + + } + }
+ @if (entry[1].status === 'error' && isErrorExpanded(entry[0])) { +
+
+
+ @if (entry[1].msg) { +
Message: {{entry[1].msg}}
+ } + @if (entry[1].error) { +
Error: {{entry[1].error}}
+ } +
URL: {{entry[1].url}}
+
+ +
+
+ } @if (entry[1].size) { diff --git a/ui/src/app/app.ts b/ui/src/app/app.ts index 0933347..17281c6 100644 --- a/ui/src/app/app.ts +++ b/ui/src/app/app.ts @@ -6,7 +6,7 @@ import { FormsModule } from '@angular/forms'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgSelectModule } from '@ng-select/ng-select'; -import { faTrashAlt, faCheckCircle, faTimesCircle, faRedoAlt, faSun, faMoon, faCheck, faCircleHalfStroke, faDownload, faExternalLinkAlt, faFileImport, faFileExport, faCopy, faClock, faTachometerAlt, faSortAmountDown, faSortAmountUp } from '@fortawesome/free-solid-svg-icons'; +import { faTrashAlt, faCheckCircle, faTimesCircle, faRedoAlt, faSun, faMoon, faCheck, faCircleHalfStroke, faDownload, faExternalLinkAlt, faFileImport, faFileExport, faCopy, faClock, faTachometerAlt, faSortAmountDown, faSortAmountUp, faChevronRight } from '@fortawesome/free-solid-svg-icons'; import { faGithub } from '@fortawesome/free-brands-svg-icons'; import { CookieService } from 'ngx-cookie-service'; import { DownloadsService } from './services/downloads.service'; @@ -67,6 +67,7 @@ export class App implements AfterViewInit, OnInit { metubeVersion: string | null = null; isAdvancedOpen = false; sortAscending = false; + expandedErrors: Set = new Set(); // Download metrics activeDownloads = 0; @@ -103,6 +104,7 @@ export class App implements AfterViewInit, OnInit { faTachometerAlt = faTachometerAlt; faSortAmountDown = faSortAmountDown; faSortAmountUp = faSortAmountUp; + faChevronRight = faChevronRight; subtitleFormats = [ { id: 'srt', text: 'SRT' }, { id: 'txt', text: 'TXT (Text only)' }, @@ -722,6 +724,24 @@ export class App implements AfterViewInit, OnInit { return result; } + toggleErrorDetail(id: string) { + if (this.expandedErrors.has(id)) this.expandedErrors.delete(id); + else this.expandedErrors.add(id); + } + + copyErrorMessage(download: Download) { + const parts: string[] = []; + if (download.title) parts.push(`Title: ${download.title}`); + if (download.url) parts.push(`URL: ${download.url}`); + if (download.msg) parts.push(`Message: ${download.msg}`); + if (download.error) parts.push(`Error: ${download.error}`); + navigator.clipboard.writeText(parts.join('\n')).catch(() => {}); + } + + isErrorExpanded(id: string): boolean { + return this.expandedErrors.has(id); + } + private updateMetrics() { this.activeDownloads = Array.from(this.downloads.queue.values()).filter(d => d.status === 'downloading' || d.status === 'preparing').length; this.queuedDownloads = Array.from(this.downloads.queue.values()).filter(d => d.status === 'pending').length;