metube/UI_REDESIGN.md

17 KiB
Raw Blame History

MeTube UI Redesign — Design Doc

Date: 2026-08-09 Status: Proposal (not yet implemented)

1. Background

MeTube (this fork) is a self-hosted web GUI for yt-dlp: Python 3.13 backend (aiohttp + python-socketio, ~2,000 LOC) and an Angular 19 frontend (Bootstrap 5.3 + ng-bootstrap, ~2,400 LOC, PWA).

The frontend is a single-screen app built around one monolithic component:

  • ui/src/app/app.component.ts — 744 LOC
  • ui/src/app/app.component.html — 595 LOC, with the same table markup triplicated across three tabs
  • ui/src/app/app.component.sass — 341 LOC
  • Supporting: downloads.service.ts (socket/HTTP state), 2 checkbox components, 4 pipes, theme/format helpers

The UI is stock Bootstrap: three tabs (Downloading / Pending / Completed), each a 7-column <table>; an inline add-form; a collapsible "Advanced Options" card; alert() for errors; cookie-persisted preferences.

2. Problems with the current UI

  1. Table-based lists: 7 text columns, horizontal overflow-auto scroll on mobile, no thumbnails — recognizing "which video is this" from text is slow.
  2. No design language: default Bootstrap chrome, no empty states, no skeleton loaders, "Connecting to server..." as the loading experience.
  3. Errors via alert(): blocking, non-actionable, disappears.
  4. Monolithic component: imperative @ViewChild(...).nativeElement.disabled manipulation (7+ instances), triplicated template markup, dead tooling (tslint/codelyzer/protractor). Every change is slower and riskier than it should be.
  5. List tooling doesn't match real data volumes: the primary user runs ~230 queued / ~300 completed items. Plain scrolling tables are unusable at that scale — no filter, no sort, weak batch selection, no virtualization.

3. Decision: redesign in place, do NOT rewrite the stack

Evaluated and rejected: rewriting the frontend in Next.js (or Vite + React), and rewriting the backend in Node.

Rationale:

  • ~90% of the user-visible benefit comes from the design, not the framework. Cards, thumbnails, filter/sort, batch actions, virtualization are all achievable in the existing Angular + Bootstrap stack (Angular CDK has virtual scrolling; Bootstrap renders cards fine).
  • The rewrite's only unique benefit is code health / developer velocity, which doesn't pay off for what is largely a one-off redesign.
  • Regression risk: cookie-persisted prefs, folder tag-creation, socket string-parsing quirks, PWA/service-worker setup would all need to be rediscovered and re-tested.
  • Next.js specifically is the wrong shape for this app: a LAN SPA with no SEO/SSR needs. If a rewrite ever happens, Vite + React is the right-sized tool — but it is not worth doing now.
  • The backend must stay Python regardless: yt-dlp is Python-native, and the subtle, battle-tested logic (precheck worker, filename-conflict resolution, multiprocess lifecycle, playlist expansion, cookie wiring) lives in app/ytdl.py. A Node port is 1.53 weeks of risk for zero user-visible gain.

If the frontend keeps needing substantial work after this redesign and Angular becomes a friction point, revisit a rewrite then — with a proven design to port, it becomes a mechanical ~2-day job.

4. Goals / Non-goals

Goals:

  • Modern card-based, thumbnail-first UI for all three lists.
  • Handle 200500-item lists comfortably: filter, sort, folder grouping, multi-select batch actions, virtualized rendering.
  • Keep "now downloading" always visible.
  • Replace alert() with toasts; add empty states and skeleton loading.
  • Mobile-friendly (cards stack; no horizontal table scroll).
  • Refactor the monolith into components as part of the work (only as much as the redesign requires — no gratuitous re-architecture).

Non-goals:

  • No framework/stack change (stays Angular 19 + Bootstrap 5.3).
  • No backend queue/precheck/persistence changes, except the additive thumbnail endpoint (§6.3).
  • No new features beyond what the design requires (no new download options, no playlist monitor — that's a separate planned effort).

5. Proposed design

5.1 Layout (desktop)

┌──────────────────────────────────────────────────────────────────────────┐
│ ▶ MeTube          ⬇ 2 active · 12.4 MB/s                    🌙  ⚙       │
├──────────────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────────────┐     │
│ │ 🔗 Paste video / playlist URL…                             [Add] │     │
│ └──────────────────────────────────────────────────────────────────┘     │
│  [Best][1080p][720p]  [MP4▾][MP3][M4A]   📁 movies    ⋯ Advanced        │
├──────────────────────────────────────────────────────────────────────────┤
│ ── NOW DOWNLOADING (2) ───────────────────────────── 12.4 MB/s total ──  │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ [thumb] Title ███████████████░░░░░ 62% 12.4MB/s ETA 0:43          ✕ │ │
│ │ [thumb] Title ████░░░░░░░░░░░░░░░ 18%  3.1MB/s ETA 4:12           ✕ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────┤
│  [ Queued 230 ] [ Completed 303 ]              📁 All folders ▾  ▦ | ☰  │
│  ┌───────────────────────────────────────────────────────────────────┐   │
│  │ ☑ Select    🔍 Filter titles…      Sort: Date added ▾   Group: 📁 │   │
│  ├───────────────────────────────────────────────────────────────────┤   │
│  │ ▸ 📁 shows (86)                                            ☐ all  │   │
│  │ ▸ 📁 music (54)                                            ☐ all  │   │
│  │ ▾ 📁 movies (90)                                           ☐ all  │   │
│  │   ☐ [thumb] Movie title one          720p · queued 2d ago      ✕ │   │
│  │   ☑ [thumb] Movie title two          720p · queued 2d ago      ✕ │   │
│  │   ☐ [thumb] Movie title three        720p · queued 3d ago      ✕ │   │
│  │                    …(virtualized scroll)…                         │   │
│  └───────────────────────────────────────────────────────────────────┘   │
│  2 selected ── [ ▶ Start ] [ ✕ Cancel ] [ 📁 Move to… ]                  │
└──────────────────────────────────────────────────────────────────────────┘

5.2 Advanced options slide-over

Replaces the inline collapsible card. Rarely-changed controls move here;

cookie-persisted defaults keep the main bar to one row.

┌────────────────────────────┐
│ Advanced options        ✕  │
│                            │
│ Auto-start        [ toggle ]│
│ Playlist strict   [ toggle ]│
│ Playlist limit    [  10  ] │
│                            │
│ ── Batch ──────────────    │
│ [Import URLs] [Export]     │
│ [Copy all URLs]            │
│                            │
│ ── Cookies ────────────    │
│ domain.com   ✓ saved       │
│ [Paste cookie…]            │
└────────────────────────────┘

5.3 Mobile

Cards stack vertically; the active panel, tabs, filter bar, and list reflow to single-column. No horizontal scrolling anywhere. The ⋮ menu and slide-over become bottom sheets.

┌─────────────────────────┐
│ ▶ MeTube      🌙    ⚙  │
│ ⬇2 · 12MB/s            │
├─────────────────────────┤
│ ┌─────────────────────┐ │
│ │🔗 Paste URL…   [Add]│ │
│ └─────────────────────┘ │
│ [Best][1080p][720p]     │
│ 📁 movies      ⋯ More   │
│                         │
│ NOW DOWNLOADING (2)     │
│ ┌─────────────────────┐ │
│ │[thumb]              │ │
│ │ Title goes here     │ │
│ │ ████████░░░ 62%     │ │
│ │ 12MB/s · ETA 0:43 ✕ │ │
│ └─────────────────────┘ │
│                         │
│ [Queued 230][Done 303]  │
│ ┌────────┐  ┌────────┐  │
│ │ thumb  │  │ thumb  │  │
│ │ title… │  │ title… │  │
│ │ 384MB ⋮│  │ 1.2GB ⋮│  │
│ └────────┘  └────────┘  │
└─────────────────────────┘

5.4 Key design decisions and rationale

  1. Tabs for the archives, pinned panel for active downloads. Stacked sections (all three lists on one scrolling page) were considered and rejected: at 230/303 items the lists never fit on a screen, so the "pipeline at a glance" argument fails. Tabs hide state, so each tab carries a count badge. The active-downloads panel stays pinned above the tabs because its size is bounded by the concurrency limit (~3) — it costs nothing and keeps live progress always visible.

  2. Tables → thumbnail-first cards/rows. Recognition-by-image is far faster than recognition-by-text; it also eliminates the 7-column mobile squeeze. Completed items get a grid/list toggle (▦|☰) — the completed tab is a media library (browse, play, move, delete), not an audit log.

  3. Command bar instead of a form. Usage is 95% two actions: paste URL, watch progress. Quality/format become one-tap chips with cookie-persisted defaults; folder picker stays; prefix and everything else moves to the slide-over.

  4. List tooling matched to volume (the main volume-driven change):

    • Per-tab filter box (title substring), sort (date added, title, size, folder), and folder grouping.
    • Folder groups are collapsible with per-group select-all.
    • Multi-select with a batch action bar (start / cancel / delete / move to folder). With 230 queued items the real operations are plural.
    • Virtualized scrolling (Angular CDK cdk-virtual-scroll-viewport) — render ~30 rows instead of 533 DOM subtrees. Concrete perf win over the current tables.
  5. Toasts replace alert() — non-blocking, can carry actions (e.g. "Duplicate skipped — view item"). The recent-events panel becomes a collapsible activity drawer (diagnostic info: available, not permanently occupying screen space).

  6. Theme: keep the existing dark/light/auto Bootstrap data-bs-theme mechanism; refine palette to one accent color. Header gets slim stat chips (active count, total speed) replacing the current navbar counters.

5.5 Open question: per-folder navigation

If queue work is frequently per-folder ("start all the music ones"), folder groups may deserve first-class status — e.g. a qBittorrent-style left sidebar of folders with counts instead of group headers inside the list. Decide based on actual usage before implementation; default is the group-header design above (simpler, keeps single-column layout).

6. Implementation plan

6.1 Component refactor (Angular, in place)

Break the monolith into:

  • CommandBarComponent — URL input, quality/format chips, folder picker, slide-over trigger. Owns the add-form state and cookie-persisted defaults.
  • AdvancedOptionsComponent — slide-over (auto-start, playlist options, import/export/copy, cookie paste).
  • ActiveDownloadsComponent — pinned now-downloading panel (rows + total speed).
  • DownloadListComponent — reusable list for Queued and Completed tabs: filter, sort, folder grouping, multi-select, batch action bar, cdk-virtual-scroll-viewport. Grid/list toggle applies to Completed.
  • DownloadRowComponent — thumbnail, title, progress/status line, folder badge, actions (cancel / start / move / delete / reveal).
  • ToastsComponent / toast service — replaces alert().
  • Keep: downloads.service.ts (socket + HTTP state) largely as-is; downloads.pipe.ts (eta/speed/fileSize); theme helpers. The triplicated tab markup collapses into two instances of DownloadListComponent.

6.2 Frontend behaviors to preserve exactly

  • Cookie-persisted prefs: quality, format, theme, active tab, sort, grouping.
  • Folder picker with tag-creation (currently ng-select) and per-row folder editing (calls POST /set_folder; moves completed files on disk).
  • Socket.IO event handling: events arrive as JSON strings and are parsed client-side; full state re-sync on connect via the all event.
  • PWA/service-worker setup (custom-service-worker.js, ngsw-config.json).
  • Import/Export/Copy URLs batch modal.
  • Existing HTTP API usage: POST /add, POST /delete, POST /start, POST /set_folder, POST /cookie, GET /history, GET|POST /events, GET /version.

6.3 Backend addition: thumbnail endpoint (additive only)

Cards need thumbnails. yt-dlp metadata already contains thumbnail URLs, but hot-linking remote thumbnails from a LAN app leaks requests to third-party hosts and breaks for expired URLs. Add one endpoint:

  • GET /thumbnail?id=<download_id> — backend fetches/caches the thumbnail (cache under STATE_DIR/thumbnails/, keyed by download id), returns the image with long-lived cache headers. Fall back to a placeholder icon when no thumbnail exists (e.g. audio-only downloads).

No other backend changes. Queue, precheck, conflict resolution, persistence, socket events are untouched.

6.4 Phasing

  1. Phase 1 — component split (no visual change): extract components from the monolith, keep the tables. Establishes the structure the redesign builds on; low regression risk, easily reviewable.
  2. Phase 2 — list redesign: DownloadListComponent with cards, filter, sort, folder groups, multi-select, virtualization; pinned active panel; tabs with count badges.
  3. Phase 3 — command bar + slide-over: new add flow, toasts, activity drawer, empty states, skeletons.
  4. Phase 4 — thumbnails: backend endpoint + wire into rows/grid.
  5. Phase 5 — polish: palette/accent, mobile bottom sheets, cleanup of dead tooling (tslint/codelyzer/protractor configs) if desired.

Phases 13 deliver the bulk of the UX gain; Phase 4 is the biggest visual upgrade but is separable if backend changes need to wait.

6.5 Effort estimate

Phase Estimate
1. Component split 0.51 day
2. List redesign 11.5 days
3. Command bar + toasts 0.51 day
4. Thumbnails (front + back) 0.5 day
5. Polish 0.5 day
Total ~34 days

(For reference, the rejected full rewrite in React was estimated at 35 days for the frontend alone, plus regression risk on working behaviors.)

7. Risks

  • Virtual scroll + folder groups: cdk-virtual-scroll-viewport works best with flat lists; collapsible groups need flattening logic in the component. Mitigation: flatten grouped data into a single array with group-header rows (standard pattern).
  • Thumbnail fetching load: 300 completed items loading thumbnails at once. Mitigation: backend cache + lazy loading (loading="lazy" / viewport-based).
  • Bootstrap theming limits: the refined palette may fight Bootstrap defaults. Mitigation: CSS custom properties on top of data-bs-theme, not a Bootstrap rebuild.
  • Regression in preserved behaviors (§6.2): mitigate with manual test checklist per phase; the project has no real frontend test suite, and adding one is out of scope.