Proyecto App Windows

This commit is contained in:
2026-05-03 23:36:48 +02:00
commit 96793b2e1e
1248 changed files with 189750 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
# Workspace Dashboard
This document records the current dashboard implementation inside the workspace
shell.
Related:
- [Workspace Shell](./workspace-shell.md)
## Summary
- The dashboard is the default `/workspace` landing page.
- It is a **rail-based** content surface (Netflix / Apple TV pattern), not a
customizable widget grid.
- Layout is static and curated — there is no edit mode, drag-drop, size
stepper, show/hide toggle, or persisted layout. Rails auto-hide when empty.
- First-run users see the shared welcome empty-state with a single primary
CTA to add their first playlist.
Core implementation:
1. `libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts`
— the page-level facade.
2. `libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.ts`
— the reusable horizontal rail.
3. `libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts`
— data aggregation (recent items, favorites, playlist stats). Shared across
rails.
4. `libs/playlist/shared/ui/src/lib/recent-playlists/empty-state/empty-state.component.ts`
— reused welcome state with the primary "Add your first playlist" CTA.
## Page Structure
```
┌─────────────────────────────────────────────────────────────────────┐
│ Hero — Continue Watching (most recent item) │
├─────────────────────────────────────────────────────────────────────┤
│ Recently Watched · See all → │
│ [poster][poster][poster][poster] →→ │
├─────────────────────────────────────────────────────────────────────┤
│ Global Favorites · See all → │
│ [poster][poster][poster] →→ │
├─────────────────────────────────────────────────────────────────────┤
│ Recently Used Sources · See all → │
│ [tile][tile][tile][tile] →→ │
├─────────────────────────────────────────────────────────────────────┤
│ Recently Added on Xtream (aggregated across providers) │
│ [poster][poster][poster] →→ │
└─────────────────────────────────────────────────────────────────────┘
```
Render rules:
1. `dashboardReady() === false` → render the page-level skeleton rails/hero.
The first-load gate waits for playlist metadata plus the first global
recent/global favorites reloads and, when Xtream playlists exist, the first
Xtream recently-added reload.
2. `hasPlaylists() === false` → render `<app-empty-state type="welcome">`
full-bleed. All rails and the hero are skipped.
3. `hero()` = `globalRecentItems()[0]`. If present, render the hero panel.
4. Each rail is emitted via `@if (cards.length > 0)`. Empty rails are hidden
— there is no "empty widget" placeholder.
5. The continue-watching hero prefers a stored Xtream `backdrop_url`; when it
is missing the UI falls back to a blurred poster treatment instead of
showing a flat panel.
## Rail Contract
`DashboardRailComponent` is purely presentational:
1. Inputs: `label`, `items: DashboardRailCard[]`, optional `seeAllLink`,
optional `aspectRatio` (default `'2 / 3'`), optional `testId`.
2. Behavior: horizontal flex track with `scroll-snap-type: x mandatory`.
3. Chevron buttons fade in on hover (desktop only via `@media (hover: none)`).
4. Cards are keyboard-focusable router links; `scroll-snap-align: start`
means arrow-key nav lands on card boundaries.
5. Image handling: `loading="lazy"`, `decoding="async"`, fallback icon tile
when `imageUrl` is missing or `error` fires.
6. Dashboard hero, rail containers, rail cards, and "Manage all" links expose
stable `data-test-id` hooks. Treat these as the supported Electron E2E
selector surface; do not target internal CSS class names.
## Data Flow
1. `WorkspaceDashboardRailsComponent` injects `DashboardDataService`.
2. It derives five signals via `computed()`:
1. `hero` — first item of `globalRecentItems()`.
2. `recentlyWatchedCards` — maps `globalRecentItems()` to rail cards.
3. `xtreamRecentlyAddedCards` — maps `xtreamRecentlyAddedItems()` to rail
cards. Aggregates newly added VOD and series across *all* Xtream
playlists via `DashboardDataService.reloadXtreamRecentlyAddedItems()`,
which calls `getGlobalRecentlyAdded('all', limit, 'xtream')` with the
DB-level `playlists.type = 'xtream'` filter. The rail is Electron-only
(PWA returns `[]`) and auto-hides when empty, so users without Xtream
playlists never see it. Cards carry a `playlist_name · type` subtitle
so users can tell which provider each item came from. Driven by an
effect that re-runs whenever the Xtream playlist count changes.
4. `favoriteCards` — maps `globalFavoriteItems()` to rail cards.
5. `sourceCards` — maps `recentPlaylists()` to rail cards. `recentPlaylists()`
ranks M3U, Xtream, and Stalker sources by their latest recent activity
from `globalRecentItems()`, then falls back to playlist
`updateDate` / `importDate` for sources that have never been used.
3. `DashboardDataService` is passive on construction. The dashboard feature
owns the initial reloads for recent items, favorites, and Xtream recently
added rows on page entry.
4. No `Layout` state, no localStorage keys, no migrations.
5. Navigation state + deep-link targets come from the existing
`getRecentItemLink()` / `getGlobalFavoriteLink()` / `getPlaylistLink()`
helpers on `DashboardDataService` and reuse the workspace navigation
helpers in `@iptvnator/portal/shared/util`.
6. Xtream VOD and series detail pages opportunistically backfill
`content.backdrop_url` when metadata exposes a backdrop, but that write
must not refresh recently viewed ordering by itself.
7. The dashboard feature triggers a fresh reload of DB-backed recent/favorite
rows on dashboard entry so newly backfilled backdrop data is visible as soon
as the user returns from a detail page.
## Empty State
The welcome state is rendered via the existing
`EmptyStateComponent` (`type="welcome"`) from
`libs/playlist/shared/ui`:
1. Illustration + headline + description from the existing M3U welcome
strings (`HOME.PLAYLISTS.WELCOME_*`).
2. Primary button emits `addPlaylistClicked`. The dashboard page wires this
to `WORKSPACE_SHELL_ACTIONS.openAddPlaylistDialog()`.
3. Feature chips (M3U / Xtream / Stalker) are provided by the component.
## UX Rules
1. Rails represent content the user is likely to resume, not provider
internals. Never surface raw API objects.
2. Each rail must auto-hide when its data source is empty.
3. Image assets must degrade to a typed icon fallback — never show broken
images or empty tiles.
4. The page must never show "No widgets" style text. If there is no content
and no playlists, render the welcome state; otherwise render whatever
rails have data.
5. Navigation from a rail card must deep-link into the appropriate workspace
route without switching the active playlist in the header switcher.
6. `Recently Used Sources` reflects recent source usage across all provider
types, not just recent imports.
## Adding Or Changing Rails
Current workflow:
1. Add a new `computed()` signal for the card list in
`WorkspaceDashboardRailsComponent`, mapping your source data to
`DashboardRailCard`.
2. Drop a `<lib-dashboard-rail>` in the template, gated by
`@if (cards.length > 0)`.
3. If the data source is new, extend `DashboardDataService` rather than
reaching into DB services directly from the component.
4. Provide a `seeAllLink` only if there is a dedicated "manage all" route
for that content type.
## Deferred Work
Intentionally out of scope:
1. Customizable layout (drag/drop, resize, show/hide toggles, layout
persistence). Removed in favor of a curated, opinionated order.
2. Freeform widget grid with collision management.
3. External data rails such as RSS, sports, or news adapters.
4. Per-user A/B variants of rail ordering.