feat: expandable error details with copy-to-clipboard (closes #143)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7427cbb0c0
commit
0104add945
2 changed files with 51 additions and 9 deletions
|
|
@ -456,20 +456,42 @@
|
|||
<fa-icon [icon]="faCheckCircle" class="text-success" />
|
||||
}
|
||||
@if (download.value.status === 'error') {
|
||||
<fa-icon [icon]="faTimesCircle" class="text-danger" />
|
||||
<fa-icon [icon]="faTimesCircle" class="text-danger" style="cursor: pointer;" (click)="toggleErrorDetail(download.key)" />
|
||||
}
|
||||
</div>
|
||||
<span ngbTooltip="{{buildResultItemTooltip(download.value)}}">@if (!!download.value.filename) {
|
||||
<a href="{{buildDownloadLink(download.value)}}" target="_blank">{{ download.value.title }}</a>
|
||||
} @else {
|
||||
{{download.value.title}}
|
||||
@if (download.value.msg) {
|
||||
<span><br>{{download.value.msg}}</span>
|
||||
}
|
||||
@if (download.value.error) {
|
||||
<span><br>Error: {{download.value.error}}</span>
|
||||
}
|
||||
<span [style.cursor]="download.value.status === 'error' ? 'pointer' : 'default'"
|
||||
(click)="download.value.status === 'error' ? toggleErrorDetail(download.key) : null">
|
||||
{{download.value.title}}
|
||||
@if (download.value.status === 'error' && !isErrorExpanded(download.key)) {
|
||||
<small class="text-danger ms-2">
|
||||
<fa-icon [icon]="faChevronRight" size="xs" class="me-1" />Click for details
|
||||
</small>
|
||||
}
|
||||
</span>
|
||||
}</span>
|
||||
@if (download.value.status === 'error' && isErrorExpanded(download.key)) {
|
||||
<div class="alert alert-danger py-2 px-3 mt-2 mb-0 small" style="border-left: 4px solid var(--bs-danger);">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
@if (download.value.msg) {
|
||||
<div class="mb-1"><strong>Message:</strong> {{download.value.msg}}</div>
|
||||
}
|
||||
@if (download.value.error) {
|
||||
<div class="mb-1"><strong>Error:</strong> {{download.value.error}}</div>
|
||||
}
|
||||
<div class="text-muted" style="word-break: break-all;"><strong>URL:</strong> {{download.value.url}}</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger ms-2 flex-shrink-0"
|
||||
(click)="copyErrorMessage(download.value); $event.stopPropagation()"
|
||||
ngbTooltip="Copy error details to clipboard">
|
||||
<fa-icon [icon]="faCopy" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (download.value.size) {
|
||||
|
|
|
|||
|
|
@ -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 } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faTrashAlt, faCheckCircle, faTimesCircle, faRedoAlt, faSun, faMoon, faCheck, faCircleHalfStroke, faDownload, faExternalLinkAlt, faFileImport, faFileExport, faCopy, faClock, faTachometerAlt, 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';
|
||||
|
|
@ -66,6 +66,7 @@ export class App implements AfterViewInit, OnInit {
|
|||
ytDlpVersion: string | null = null;
|
||||
metubeVersion: string | null = null;
|
||||
isAdvancedOpen = false;
|
||||
expandedErrors: Set<string> = new Set();
|
||||
|
||||
// Download metrics
|
||||
activeDownloads = 0;
|
||||
|
|
@ -100,6 +101,7 @@ export class App implements AfterViewInit, OnInit {
|
|||
faGithub = faGithub;
|
||||
faClock = faClock;
|
||||
faTachometerAlt = faTachometerAlt;
|
||||
faChevronRight = faChevronRight;
|
||||
subtitleFormats = [
|
||||
{ id: 'srt', text: 'SRT' },
|
||||
{ id: 'txt', text: 'TXT (Text only)' },
|
||||
|
|
@ -699,6 +701,24 @@ export class App implements AfterViewInit, OnInit {
|
|||
this.isAdvancedOpen = !this.isAdvancedOpen;
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue