324 lines
17 KiB
Markdown
324 lines
17 KiB
Markdown
# 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.5–3 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 200–500-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 1–3 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.5–1 day |
|
||
| 2. List redesign | 1–1.5 days |
|
||
| 3. Command bar + toasts | 0.5–1 day |
|
||
| 4. Thumbnails (front + back) | 0.5 day |
|
||
| 5. Polish | 0.5 day |
|
||
| **Total** | **~3–4 days** |
|
||
|
||
(For reference, the rejected full rewrite in React was estimated at 3–5 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.
|