mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
10 KiB
10 KiB
Workspace Dashboard
This document records the current dashboard implementation inside the workspace shell.
Related:
Summary
- The dashboard is the default
/workspacelanding 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:
libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts— the page-level facade.libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.ts— the reusable horizontal rail.libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts— data aggregation (recent items, favorites, playlist stats). Shared across rails.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) │
├─────────────────────────────────────────────────────────────────────┤
│ Continue Watching · See all → │
│ [poster][poster][poster][poster] →→ │
├─────────────────────────────────────────────────────────────────────┤
│ Live now on your favorites / Continue with live TV · See all → │
│ [channel][channel][channel][channel] →→ │
├─────────────────────────────────────────────────────────────────────┤
│ Recently Used Sources · See all → │
│ [tile][tile][tile][tile] →→ │
├─────────────────────────────────────────────────────────────────────┤
│ Recently Added on Xtream (aggregated across providers) │
│ [poster][poster][poster] →→ │
└─────────────────────────────────────────────────────────────────────┘
Render rules:
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.hasPlaylists() === false→ render<app-empty-state type="welcome">full-bleed. All rails and the hero are skipped.hero()=globalRecentItems()[0]. If present, render the hero panel.- Each rail is emitted via
@if (cards.length > 0). Empty rails are hidden — there is no "empty widget" placeholder. - 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. - The mixed global favorites rail is not rendered on the dashboard. Live
favorites are promoted into the live rail, while mixed favorites stay on
/workspace/global-favorites.
Rail Contract
DashboardRailComponent is purely presentational:
- Inputs:
label,items: DashboardRailCard[], optionalseeAllLink, optionalaspectRatio(default'2 / 3'), optionaltestId. - Behavior: horizontal flex track with
scroll-snap-type: x mandatory. - Chevron buttons fade in on hover (desktop only via
@media (hover: none)). - Cards are keyboard-focusable router links;
scroll-snap-align: startmeans arrow-key nav lands on card boundaries. - Image handling:
loading="lazy",decoding="async", fallback icon tile whenimageUrlis missing orerrorfires. - Dashboard hero, rail containers, rail cards, and "Manage all" links expose
stable
data-test-idhooks. Treat these as the supported Electron E2E selector surface; do not target internal CSS class names.
Data Flow
WorkspaceDashboardRailsComponentinjectsDashboardDataService.- It derives the dashboard surface via
computed():hero— first item ofglobalRecentItems().continueWatchingCards— mapsglobalRecentVodItems()to movie/series cover cards. Xtream playback positions are bulk-loaded per playlist so hero and cards can show progress, remaining time, and series season/ episode badges. Series lookup uses keyed maps for both direct episode ids and series ids; card renders must not scan the full playback-position map.liveOnFavoritesCardsEnriched— maps favorited live channels first, falling back to recently watched live channels when no live favorites exist. M3U cards carry anepg_lookup_keyusing the app-wide XMLTV fallback order (tvg-id->tvg-name-> channel name); EPG enrichment must use that key before falling back to the card title.xtreamRecentlyAddedCards— mapsxtreamRecentlyAddedItems()to rail cards. Aggregates newly added VOD and series across all Xtream playlists viaDashboardDataService.reloadXtreamRecentlyAddedItems(), which callsgetGlobalRecentlyAdded('all', limit, 'xtream')with the DB-levelplaylists.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 aplaylist_name · typesubtitle so users can tell which provider each item came from. Driven by an effect that re-runs whenever the Xtream playlist count changes.sourceCards— mapsrecentPlaylists()to rail cards.recentPlaylists()ranks M3U, Xtream, and Stalker sources by their latest recent activity fromglobalRecentItems(), then falls back to playlistupdateDate/importDatefor sources that have never been used.
DashboardDataServiceis passive on construction. The dashboard feature owns the initial reloads for recent items, favorites, and Xtream recently added rows on page entry.- No
Layoutstate, no localStorage keys, no migrations. - Navigation state + deep-link targets come from the existing
getRecentItemLink()/getGlobalFavoriteLink()/getPlaylistLink()helpers onDashboardDataServiceand reuse the workspace navigation helpers in@iptvnator/portal/shared/util. - Xtream VOD and series detail pages opportunistically backfill
content.backdrop_urlwhen metadata exposes a backdrop, but that write must not refresh recently viewed ordering by itself. - 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.
- Playback-position reloads are keyed by the VOD/series recent set and should
call
reloadPlaybackPositions()throughuntracked()so live-only recent changes do not trigger unnecessary IPC round-trips.
Empty State
The welcome state is rendered via the existing
EmptyStateComponent (type="welcome") from
libs/playlist/shared/ui:
- Illustration + headline + description from the existing M3U welcome
strings (
HOME.PLAYLISTS.WELCOME_*). - Primary button emits
addPlaylistClicked. The dashboard page wires this toWORKSPACE_SHELL_ACTIONS.openAddPlaylistDialog(). - Feature chips (M3U / Xtream / Stalker) are provided by the component.
UX Rules
- Rails represent content the user is likely to resume, not provider internals. Never surface raw API objects.
- Each rail must auto-hide when its data source is empty.
- Image assets must degrade to a typed icon fallback — never show broken images or empty tiles.
- 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.
- Navigation from a rail card must deep-link into the appropriate workspace route without switching the active playlist in the header switcher.
Recently Used Sourcesreflects recent source usage across all provider types, not just recent imports.- The live rail title key must match the rendered source: favorites use
WORKSPACE.DASHBOARD.LIVE_FAVORITES; recently watched fallback usesWORKSPACE.DASHBOARD.LIVE_RECENT.
Adding Or Changing Rails
Current workflow:
- Add a new
computed()signal for the card list inWorkspaceDashboardRailsComponent, mapping your source data toDashboardRailCard. - Drop a
<lib-dashboard-rail>in the template, gated by@if (cards.length > 0). - If the data source is new, extend
DashboardDataServicerather than reaching into DB services directly from the component. - Provide a
seeAllLinkonly if there is a dedicated "manage all" route for that content type.
Deferred Work
Intentionally out of scope:
- Customizable layout (drag/drop, resize, show/hide toggles, layout persistence). Removed in favor of a curated, opinionated order.
- Freeform widget grid with collision management.
- External data rails such as RSS, sports, or news adapters.
- Per-user A/B variants of rail ordering.