-
Notifications
You must be signed in to change notification settings - Fork 717
add grid view with thumbnail preview of videos #762
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -321,57 +321,91 @@ <h5 class="modal-title">Batch Import URLs</h5> | |||||||||||||||||||||||||||||||||
| </table> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| <div class="metube-section-header">Completed</div> | ||||||||||||||||||||||||||||||||||
| <div class="metube-section-header"> | ||||||||||||||||||||||||||||||||||
| <span>Completed</span> | ||||||||||||||||||||||||||||||||||
| <div class="btn-group metube-view-toggle" role="group" aria-label="View"> | ||||||||||||||||||||||||||||||||||
| <button type="button" class="btn" [ngClass]="viewMode==='list' ? 'btn-primary' : 'btn-outline-secondary'" (click)="setView ? setView('list') : (viewMode='list')"> | ||||||||||||||||||||||||||||||||||
| <fa-icon [icon]="faList" class="me-1"></fa-icon> List | ||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||
| <button type="button" class="btn" [ngClass]="viewMode==='grid' ? 'btn-primary' : 'btn-outline-secondary'" (click)="setView ? setView('grid') : (viewMode='grid')"> | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+327
to
+330
|
||||||||||||||||||||||||||||||||||
| <button type="button" class="btn" [ngClass]="viewMode==='list' ? 'btn-primary' : 'btn-outline-secondary'" (click)="setView ? setView('list') : (viewMode='list')"> | |
| <fa-icon [icon]="faList" class="me-1"></fa-icon> List | |
| </button> | |
| <button type="button" class="btn" [ngClass]="viewMode==='grid' ? 'btn-primary' : 'btn-outline-secondary'" (click)="setView ? setView('grid') : (viewMode='grid')"> | |
| <button type="button" class="btn" [ngClass]="viewMode==='list' ? 'btn-primary' : 'btn-outline-secondary'" (click)="setView('list')"> | |
| <fa-icon [icon]="faList" class="me-1"></fa-icon> List | |
| </button> | |
| <button type="button" class="btn" [ngClass]="viewMode==='grid' ? 'btn-primary' : 'btn-outline-secondary'" (click)="setView('grid')"> |
Copilot
AI
Sep 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The conditional check 'setView ?' is unnecessary since setView is always defined in the component. Simplify to just call setView('list') and setView('grid') directly.
| <button type="button" class="btn" [ngClass]="viewMode==='list' ? 'btn-primary' : 'btn-outline-secondary'" (click)="setView ? setView('list') : (viewMode='list')"> | |
| <fa-icon [icon]="faList" class="me-1"></fa-icon> List | |
| </button> | |
| <button type="button" class="btn" [ngClass]="viewMode==='grid' ? 'btn-primary' : 'btn-outline-secondary'" (click)="setView ? setView('grid') : (viewMode='grid')"> | |
| <button type="button" class="btn" [ngClass]="viewMode==='list' ? 'btn-primary' : 'btn-outline-secondary'" (click)="setView('list')"> | |
| <fa-icon [icon]="faList" class="me-1"></fa-icon> List | |
| </button> | |
| <button type="button" class="btn" [ngClass]="viewMode==='grid' ? 'btn-primary' : 'btn-outline-secondary'" (click)="setView('grid')"> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ import { MasterCheckboxComponent } from './master-checkbox.component'; | |
| import { Formats, Format, Quality } from './formats'; | ||
| import { Theme, Themes } from './theme'; | ||
| import {KeyValue} from "@angular/common"; | ||
| import { faList, faTableCells, faGrip } from '@fortawesome/free-solid-svg-icons'; | ||
|
|
||
| @Component({ | ||
| selector: 'app-root', | ||
|
|
@@ -43,6 +44,10 @@ export class AppComponent implements AfterViewInit { | |
| ytDlpVersion: string | null = null; | ||
| metubeVersion: string | null = null; | ||
| isAdvancedOpen = false; | ||
| viewMode: 'list' | 'grid' = 'list'; | ||
| faList = faList; | ||
| faTableCells = faTableCells; | ||
| faGrid = faGrip; | ||
|
|
||
| // Download metrics | ||
| activeDownloads = 0; | ||
|
|
@@ -504,6 +509,43 @@ export class AppComponent implements AfterViewInit { | |
| this.isAdvancedOpen = !this.isAdvancedOpen; | ||
| } | ||
|
|
||
| getThumbnailUrl(dl: Download): string | null { | ||
| if (this.isAudioDownload(dl)) { | ||
| // show your local placeholder for audio | ||
| return 'assets/audio-placeholder.png'; | ||
alexta69 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| // Build query: base (video/audio), folder (if any), file (the filename) | ||
| const params = new URLSearchParams(); | ||
| // Decide base by the same logic you use for buildDownloadLink | ||
| const isAudio = dl.quality === 'audio' || | ||
| (dl.format && ['mp3','m4a','opus','wav','flac'].includes(dl.format)); | ||
| params.set('base', isAudio ? 'audio' : 'video'); | ||
| if (dl.folder) params.set('folder', dl.folder); | ||
| if (dl.filename) params.set('file', dl.filename); | ||
| params.set('t', '1'); // second to seek; tweak if you like | ||
| return `thumb?${params.toString()}`; | ||
| } | ||
|
|
||
| onImgError(ev: Event) { | ||
| const img = ev.target as HTMLImageElement | null; | ||
| if (img) img.style.display = 'none'; | ||
| } | ||
|
|
||
| isAudioDownload(dl: Download): boolean { | ||
| const audioExts = ['.mp3','.m4a','.opus','.wav','.flac']; | ||
| if (dl.quality === 'audio') return true; | ||
| if (dl.format && ['mp3','m4a','opus','wav','flac'].includes(dl.format)) return true; | ||
| if (dl.filename) { | ||
| const lower = dl.filename.toLowerCase(); | ||
| if (audioExts.some(ext => lower.endsWith(ext))) return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| setView(mode: 'list' | 'grid') { | ||
| this.viewMode = mode; localStorage.setItem('completedView', mode); | ||
| } | ||
|
Comment on lines
+545
to
+547
|
||
|
|
||
| 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; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thumbnail endpoint lacks input validation for the 't' parameter. Malicious values could be passed to ffmpeg. Consider validating that 't' is a valid number within reasonable bounds.