mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -08:00
Merge remote-tracking branch 'origin/master' into claude/iptvnator-vod-hero-redesign-2c8950
# Conflicts: # apps/electron-backend-e2e/src/detail-back-lane.e2e.ts # apps/web-e2e/src/m3u-movie-details.e2e.ts # docs/architecture/iptvnator-ui-guidelines.md # libs/ui/components/src/lib/content-hero/content-hero.component.scss # libs/ui/components/src/lib/portal-detail-shell/portal-detail-shell.component.scss
This commit is contained in:
commit
87d7e1d46c
122 files changed
+2080
-1040
No files matched your search
@@ -162,8 +162,9 @@ Contracts:
|
||||
player subtree, so shell state changes cannot recreate the `<video>`.
|
||||
- **External MPV/VLC sessions do not flip the layout to watch** — browse
|
||||
layout stays, and the primary CTA keeps its "Stop <player>" behavior.
|
||||
- The shell's sticky arrow emits the host-owned `backClicked` in browse and
|
||||
watch alike (unless `backAvailable=false`, when it is not rendered at all):
|
||||
- The shell's Back, rendered by the workspace header, emits the host-owned
|
||||
`backClicked` in browse and watch alike (unless `backAvailable=false`, when
|
||||
the shell registers none):
|
||||
hosts wire it to their route-level `goBack()`, straight back to the list —
|
||||
everything browse offers is also visible in watch, so a two-step unwind
|
||||
would be ceremony. Escape alone unwinds one level: in watch it emits
|
||||
@@ -174,7 +175,7 @@ Contracts:
|
||||
[Portal Detail Navigation](./portal-detail-navigation.md).
|
||||
- The now-playing bar has one exit of its own: the "Close player" button
|
||||
emits `closed` and returns to browse without navigating. It carries no back
|
||||
arrow — a second arrow beside the sticky one, with a different meaning,
|
||||
arrow — a second arrow beside the header's, with a different meaning,
|
||||
was the duplicate this contract removes.
|
||||
- Entering watch scrolls the shell to the top; leaving keeps the scroll
|
||||
position.
|
||||
|
||||
@@ -725,6 +725,27 @@ Settings use the same system but are flatter than content-heavy views.
|
||||
`EpgProgrammeDialogService` opens the programme dialog at 540px from the
|
||||
timeline, list, guide and channel rows, with a panel class that scopes its
|
||||
surface overrides.
|
||||
- **Destructive actions.** Material only emits `warn` button colors for M2
|
||||
themes, so the `color` input is a no-op here. A button that removes or
|
||||
discards user data uses the global `.app-destructive-button` class from
|
||||
`m3-theme.scss` (error/on-error tokens per theme, for filled, text,
|
||||
outlined and icon buttons), as the unsaved-changes dialog's Discard does.
|
||||
Confirmations go through `DialogService.openConfirmDialog` with a
|
||||
translated verb as the required `confirmLabel` ("Remove playlist",
|
||||
"Clear") and `tone: 'destructive'` when the action loses data; the dismiss
|
||||
defaults to "Cancel". Never confirm with "Yes"/"No". When the verb itself
|
||||
is "Cancel …", pass `cancelLabel` "Close" so the two buttons do not read
|
||||
alike. `theme-tokens.e2e.ts` checks the label and the error fill in both
|
||||
themes.
|
||||
|
||||
## Source Type Icons
|
||||
|
||||
`SOURCE_TYPE_ICONS` in `@iptvnator/shared/interfaces` is the only source of
|
||||
provider icons: Xtream `cloud`, Stalker `cast`, the M3U family
|
||||
`playlist_play`, and per playlist `link` (URL), `description` (local file or
|
||||
text) and `subject` (pasted text in the add flow). Use
|
||||
`getPlaylistSourceIcon()` for a stored playlist. An icon never stands for two
|
||||
providers, and the Dashboard rail icon is never a provider icon.
|
||||
|
||||
## Phone Layout
|
||||
|
||||
|
||||
@@ -23,30 +23,29 @@ do not reach global player shortcuts. Descendant controls retain their native
|
||||
keys and Tab order. Entering watch still scrolls to the top; Back and saved
|
||||
catalog scroll positions retain the existing navigation contract below.
|
||||
|
||||
The shell owns a single sticky Back control, outside the collapsing hero. Its
|
||||
zero-height wrapper is a direct child of the scroll owner, so the control stays
|
||||
16 px from the top throughout long episode lists without shifting the hero.
|
||||
The button has an opaque app-themed surface, visible keyboard focus, an Escape
|
||||
shortcut hint via native `title` and Electron `no-drag` hit testing. The hint
|
||||
does not create an overlay that could consume the first Escape press.
|
||||
The shell owns the page's Back action, but the workspace header renders it.
|
||||
While `backAvailable()` is true, the shell registers a target with
|
||||
`WorkspaceBackNavigationService` (`@iptvnator/portal/shared/data-access`). The
|
||||
target, the `WorkspaceBackTarget` contract in `@iptvnator/portal/shared/util`,
|
||||
carries a label (the host's `backLabel`, else the translated "Back"), whether
|
||||
Escape currently runs it, and `run()`, which emits `backClicked`. The service
|
||||
keeps a stack in which the newest registration wins, and each release removes
|
||||
only its own target. A loading shell replaced by the loaded one therefore
|
||||
cannot clear its successor, whichever is destroyed first. The header shows the target as an `arrow_back`
|
||||
icon button in its leading slot (`data-test-id="workspace-header-back"`), to
|
||||
the right of the macOS traffic lights. That is where desktop apps and Material's
|
||||
top app bar keep navigation. The header never scrolls, so the control stays
|
||||
visible over long episode lists, and nothing floats over the scroll owner:
|
||||
detail columns keep symmetric insets and their full width. The button has
|
||||
Electron `no-drag` hit testing. In browse its tooltip and `aria-keyshortcuts`
|
||||
advertise Escape, and an Escape pressed on the focused button runs Back
|
||||
itself, because the shell's browse Escape requires focus inside the page. At
|
||||
≤640 px Back takes the context drawer toggle's slot (one navigation icon); the
|
||||
list it returns to shows the toggle again. This replaced #1763's 72 px lane
|
||||
reserved beside a sticky in-page arrow, along with its phone bar. Electron E2E
|
||||
`detail-header-back.e2e.ts` covers 1280, 780 and 375 px in browse and watch.
|
||||
|
||||
The control floats, so the content makes room for it: while it is shown, every
|
||||
content column (hero content, watch-state player, episodes, About and extras)
|
||||
starts after a 72 px lane — 16 px inset, 40 px button, 16 px gap — and no scroll
|
||||
position puts the arrow over a heading or the player. The shell derives the lane
|
||||
from the button's geometry and publishes it as `--detail-back-lane`, which
|
||||
`content-hero` and `content-about` read too; hosts with `backAvailable=false`
|
||||
keep their own inset. The lane is horizontal because watch state is short of
|
||||
height, not width. A pane narrower than 400 px is the exception — a phone, or a
|
||||
desktop pane beside the context panel — because a 72 px column there would clip
|
||||
the player's controls. The pane decides, not the viewport: a `ResizeObserver`
|
||||
on the shell's border box (scrollbar-independent) sets `shell-host--compact`,
|
||||
and the wrapper becomes an in-flow 56 px sticky bar on `--app-header-bg` that
|
||||
content scrolls beneath and that takes the taps over it. Electron E2E
|
||||
(`detail-back-lane.e2e.ts`) sweeps both states with the lane at 1280 and
|
||||
780 px and with the bar at 700 and 375 px.
|
||||
|
||||
The sticky control is route-level Back in both states: it emits `backClicked`
|
||||
The header Back is route-level in both states: it emits `backClicked`
|
||||
whether or not inline playback is active, so the arrow keeps one meaning and
|
||||
the list is one click away while watching. Only Escape unwinds one level: watch
|
||||
emits `closePlayerRequested`, browse emits `backClicked`. Hosts retain their
|
||||
@@ -61,13 +60,13 @@ Escape bubbles through the shell before Material's body-level tooltip dispatcher
|
||||
so focused detail actions return with one press even while their tooltip is open.
|
||||
The document listener remains the outside-shell watch fallback; `defaultPrevented`
|
||||
prevents duplicate actions and preserves descendant handlers' priority.
|
||||
After Escape closes a player, lost focus moves to the sticky control (or the
|
||||
shell when there is no browse Back), without scrolling or stealing existing
|
||||
focus.
|
||||
After Escape closes a player, lost focus moves to the shell itself, without
|
||||
scrolling or stealing existing focus, so the next Escape and the scroll keys
|
||||
keep working on the page.
|
||||
|
||||
Hosts without browse navigation set `backAvailable=false`: M3U uses its channel
|
||||
sidebar, and collection bootstrap placeholders have no return handler. They
|
||||
render no sticky arrow in either state and have no browse Escape action; their
|
||||
register no header Back in either state and have no browse Escape action; their
|
||||
watch exits are the bar's Close player button and Escape. Loading/error shells
|
||||
with a return handler keep Back available.
|
||||
|
||||
@@ -77,8 +76,8 @@ with a return handler keep Back available.
|
||||
- Stalker uses an inline/store-state detail model.
|
||||
- Detail pages themselves are two-state (browse ↔ watch) inside
|
||||
`PortalDetailShellComponent`; entering/leaving watch is a layout state,
|
||||
not a navigation. Route-level back semantics are unchanged; the one
|
||||
sticky arrow returns to the list from either state, while Escape and the
|
||||
not a navigation. Route-level back semantics are unchanged; the header's
|
||||
one Back arrow returns to the list from either state, while Escape and the
|
||||
now-playing bar's Close button close the inline player. See
|
||||
[Embedded Inline Playback](./embedded-inline-playback.md).
|
||||
- Favorites and recently viewed collections now use collection-owned inline detail
|
||||
|
||||
@@ -172,11 +172,18 @@ and that pause holds the slide.
|
||||
hover lift are not clipped; the fades are offset by the same variable so
|
||||
they reach the track's clipping edge and no card strip shows beyond them.
|
||||
4. Cards are keyboard-focusable router links; `scroll-snap-align: start`
|
||||
means arrow-key nav lands on card boundaries.
|
||||
means arrow-key nav lands on card boundaries. A card that receives
|
||||
keyboard or script focus scrolls fully into the viewport: Chromium skips
|
||||
its own focus scroll once 32px of an element shows, so the track's
|
||||
`focusin` handler moves to the first card-start snap position revealing
|
||||
the whole card (a card wider than the viewport aligns at its own start).
|
||||
Focus caused by a press inside the track (within 100ms of `pointerdown`,
|
||||
650ms for touch) leaves the rail still, so the card does not slide from
|
||||
under the pointer before the click.
|
||||
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
|
||||
6. Dashboard hero, rail containers, rail viewports and tracks, rail cards and
|
||||
their links, 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
|
||||
|
||||
@@ -90,12 +90,18 @@ The shell is intentionally split into four persistent regions:
|
||||
because its data source is the SQLite worker bridge.
|
||||
2. Provider-aware context links derived from the active or current playlist.
|
||||
3. Settings remains a persistent footer shortcut in the rail.
|
||||
4. No brand mark: it only repeated the first workspace link (Dashboard,
|
||||
or Sources when the dashboard is off).
|
||||
2. Top header:
|
||||
1. Playlist switcher.
|
||||
2. Route-aware search input and command palette trigger.
|
||||
3. Add source action.
|
||||
4. Optional playlist refresh and route-specific shortcut actions.
|
||||
5. Downloads shortcut in Electron.
|
||||
1. Leading Back slot, shown while the current page registers a target
|
||||
with `WorkspaceBackNavigationService` (detail pages today). At
|
||||
≤640 px it takes the context drawer toggle's place. See
|
||||
[Portal Detail Navigation](./portal-detail-navigation.md).
|
||||
2. Playlist switcher.
|
||||
3. Route-aware search input and command palette trigger.
|
||||
4. Add source action.
|
||||
5. Optional playlist refresh and route-specific shortcut actions.
|
||||
6. Downloads shortcut in Electron.
|
||||
3. Main body:
|
||||
1. Optional left context panel.
|
||||
2. Main router outlet content.
|
||||
|
||||
Reference in new issue
Block a user