mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 05:06:16 -08:00
feat(workspace): move page Back buttons into the header and add a history fallback (#1814)
This commit is contained in:
1 parent
6f247fb538
commit
84aef83a6c
40 files changed
+902
-216
No files matched your search
@@ -189,6 +189,18 @@ scrim after three idle seconds, with a 32px mute toggle in the corner. It
|
||||
never starts under `prefers-reduced-motion` or with `saveData`, and stops
|
||||
while the hero is off screen or the window is unfocused.
|
||||
|
||||
## Back Navigation
|
||||
|
||||
Page-level Back lives only in the workspace header's leading slot (see
|
||||
[Header Back](./workspace-shell.md#header-back)). A routed page, or the shell
|
||||
it renders in, registers it with `registerWorkspaceBack()` instead of drawing
|
||||
an arrow, so Back keeps one position and one look on every page and never
|
||||
floats over a scroll owner. Without a registration the header falls back to
|
||||
browser history while an in-app previous page exists, and shows nothing
|
||||
otherwise. An arrow that returns within a menu, dialog or player panel is not
|
||||
page navigation and stays in that surface; an error state may repeat the
|
||||
header's Back as a labelled recovery button beside its other actions.
|
||||
|
||||
## Electron Drag Regions
|
||||
|
||||
Every interactive descendant of a drag region—including buttons, links,
|
||||
@@ -890,7 +902,10 @@ Prefer removing a control over shrinking everything around it:
|
||||
- Counts and subtitles that a neighbouring control already states.
|
||||
|
||||
Never drop the only way back to a hidden surface. A collapse toggle that is
|
||||
reachable by touch needs its restore affordance to be reachable too.
|
||||
reachable by touch needs its restore affordance to be reachable too. For the
|
||||
same reason the header's history Back yields to the context drawer toggle,
|
||||
and Settings keeps the toggle beside its Back; only a detail page's Back,
|
||||
whose list shows the toggle again, takes the toggle's slot.
|
||||
|
||||
## Typography
|
||||
|
||||
|
||||
@@ -23,27 +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 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`
|
||||
The shell owns the page's Back action, but the workspace header renders it
|
||||
(the general contract, other pages and the history fallback are in
|
||||
[Header Back](./workspace-shell.md#header-back)). While `backAvailable()` is
|
||||
true, the shell registers a target through `registerWorkspaceBack()`
|
||||
(`@iptvnator/portal/shared/data-access`). It carries the host's `backLabel`
|
||||
(else the translated "Back"), whether Escape currently runs it, and `run()`,
|
||||
which emits `backClicked`. The newest registration wins and each release
|
||||
removes only its own target, so a loading shell replaced by the loaded one
|
||||
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. 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.
|
||||
list it returns to shows the toggle again, and there the history fallback
|
||||
yields to it. 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,
|
||||
and the history fallback on the list Back returns to.
|
||||
|
||||
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
|
||||
@@ -67,8 +69,13 @@ 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
|
||||
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.
|
||||
watch exits are the bar's Close player button and Escape. The header may still
|
||||
show the history fallback there (a generic Back to the previous page, without
|
||||
Escape) when the page was reached by in-app navigation. Loading/error shells
|
||||
with a return handler keep Back available. The downloads offline and recording
|
||||
error states additionally keep a labelled "Back to Downloads" button beside
|
||||
Retry or Remove: it is the error state's recovery action and runs the same
|
||||
handler as the header Back.
|
||||
|
||||
## Summary
|
||||
|
||||
|
||||
@@ -93,10 +93,9 @@ The shell is intentionally split into four persistent regions:
|
||||
4. No brand mark: it only repeated the first workspace link (Dashboard,
|
||||
or Sources when the dashboard is off).
|
||||
2. Top header:
|
||||
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).
|
||||
1. Leading Back slot: the current page's registered Back, else browser
|
||||
history while an in-app previous page exists, else nothing. See
|
||||
[Header Back](#header-back).
|
||||
2. Playlist switcher.
|
||||
3. Route-aware search input and command palette trigger.
|
||||
4. Add source action.
|
||||
@@ -130,6 +129,71 @@ When adding shell behavior, prefer placing it in the service that owns the
|
||||
nearest existing state. Keep `WorkspaceShellFacade` as a stable re-export layer
|
||||
for the template unless the template contract itself intentionally changes.
|
||||
|
||||
## Header Back
|
||||
|
||||
The header's leading slot is the workspace's one page-level Back. Pages do not
|
||||
render an arrow of their own: they register a `WorkspaceBackTarget`
|
||||
(`@iptvnator/portal/shared/util`) with `WorkspaceBackNavigationService`
|
||||
(`@iptvnator/portal/shared/data-access`), normally through
|
||||
`registerWorkspaceBack()`, which registers for the calling component's
|
||||
lifetime while its optional `available` predicate holds. The newest
|
||||
registration wins, and each release removes only its own target. The button
|
||||
(`data-test-id="workspace-header-back"`) sits beside the macOS traffic lights,
|
||||
never scrolls, and has Electron `no-drag` hit testing. A target supplies its
|
||||
label (else the translated "Back"), whether Escape on the page runs it, and
|
||||
`run()`.
|
||||
|
||||
| Page | Registered by | Back runs | ≤640 px |
|
||||
| --- | --- | --- | --- |
|
||||
| Portal, collection, offline and recording details | `PortalDetailShellComponent` while `backAvailable()` | the host's `backClicked` | replaces the drawer toggle |
|
||||
| Xtream and Stalker Discover and actor pages | `DiscoverViewComponent`, `ActorViewComponent` | the route's `Location.back()` | (no drawer) |
|
||||
| In-portal search, Xtream and Stalker | `SearchLayoutComponent` while `backAvailable()` and no inline detail replaces the results | `Location.back()` | (no drawer) |
|
||||
| Settings | `WorkspaceSettingsContextPanelComponent`, which exists exactly while the settings route shows | `Location.back()` | beside the drawer toggle |
|
||||
|
||||
Detail-page semantics (Escape, browse and watch) are in
|
||||
[Portal Detail Navigation](./portal-detail-navigation.md#detail-scroll-and-focus).
|
||||
|
||||
**History fallback.** Without a registration, the header shows Back while the
|
||||
previous history entry is an in-app one, and runs `Location.back()`. The
|
||||
service reads that from the Navigation API: the previous entry must be
|
||||
same-document (`NavigationHistoryEntry.sameDocument`), so the router pushed it
|
||||
after this document loaded. Entries from before a reload or from another page
|
||||
of the origin never count, and the fallback can neither leave nor reload the
|
||||
app. `currententrychange` keeps it current through pushes, replacements,
|
||||
traversals and guard-cancelled Back navigations that the router rewrites.
|
||||
Without the Navigation API (older Safari and Firefox, jsdom) there is no
|
||||
fallback; registered pages are unaffected. The fallback reads "Back" and
|
||||
advertises no Escape, because no page handles one for it. Pages that set
|
||||
`backAvailable=false`, such as M3U details, therefore show it too when they
|
||||
were reached by navigation.
|
||||
|
||||
When there is nowhere to go, the slot is empty rather than a disabled arrow.
|
||||
Sessions often start on a page that never navigates (an M3U playlist or live
|
||||
TV), where a disabled arrow would stay for the whole session. The cost is one
|
||||
shift of the switcher and search when Back first appears or leaves, which
|
||||
happens only at the start of the history and together with a route change.
|
||||
There is no Forward button: Stalker inline details are store state, not
|
||||
history entries, so Forward would skip them.
|
||||
|
||||
**Phone width.** `phoneDrawerToggle` sets how Back shares the leading slot with
|
||||
the context drawer toggle. `replace` (the default) takes the toggle's slot: a
|
||||
detail page's drawer belongs to the list that Back returns to. `beside` keeps
|
||||
both: the settings drawer holds the page's own sections. `yield` hides Back
|
||||
while the toggle shows: the history fallback must not cost a category list its
|
||||
only way into the drawer, and two navigation icons do not fit beside the
|
||||
switcher. System and browser Back still work there.
|
||||
|
||||
**Left in place.** These controls stay inside their surface on purpose:
|
||||
|
||||
1. Downloads offline and recording detail error states keep their labelled
|
||||
"Back to Downloads" button beside Retry or Remove. It is the error state's
|
||||
recovery action, not page chrome; the header shows the same Back.
|
||||
2. Back controls internal to a surface, which leave a panel rather than the
|
||||
page: the Embedded MPV dock panel and the alternative-sources panel inside
|
||||
the VOD "…" menu.
|
||||
3. The M3U player sidebar's Home button, which renders only outside the
|
||||
workspace shell.
|
||||
|
||||
## Context Panel Rules
|
||||
|
||||
The shell decides which secondary panel to show from the current route:
|
||||
|
||||
Reference in new issue
Block a user