fix(plex): a Restart does not replay on a reload or a Forward onto the player
fromStart rode in history.state._sub, so reloading the player entry restarted the item from 0 again and the progress checkpoint overwrote its resume point. A sub-view config can now say what the history entry keeps of a view.
This commit is contained in:
@@ -138,3 +138,35 @@ describe("useHistorySubview popstate", () => {
|
||||
expect(result.current.view).toBe("root");
|
||||
});
|
||||
});
|
||||
|
||||
// E28 review E1: a one-shot flag the view is OPENED with (the Plex player's `fromStart`, a card's
|
||||
// Restart) rode in history.state._sub, so a reload or a Forward onto that entry restarted from 0
|
||||
// again — and the player's checkpoint then overwrote the resume point. `entry` says what the
|
||||
// history entry keeps of a view; the view itself still opens with the flag.
|
||||
describe("useHistorySubview entry", () => {
|
||||
type V = { kind: string; once?: boolean };
|
||||
const withEntry: SubviewUrl<V> = {
|
||||
key: "v",
|
||||
encode: () => null,
|
||||
decode: () => null,
|
||||
entry: (v) => ({ kind: v.kind }),
|
||||
};
|
||||
|
||||
it("opens the view with the flag, but the history entry keeps it without", () => {
|
||||
const { result } = renderHook(() => useHistorySubview<V>({ kind: "root" }, withEntry));
|
||||
act(() => result.current.open({ kind: "player", once: true }));
|
||||
expect(result.current.view).toEqual({ kind: "player", once: true });
|
||||
expect(window.history.state._sub).toEqual({ kind: "player" });
|
||||
// A reload of that entry reads what the entry kept.
|
||||
const { result: reloaded } = renderHook(() =>
|
||||
useHistorySubview<V>({ kind: "root" }, withEntry),
|
||||
);
|
||||
expect(reloaded.current.view).toEqual({ kind: "player" });
|
||||
});
|
||||
|
||||
it("keeps it out of a replaced entry too", () => {
|
||||
const { result } = renderHook(() => useHistorySubview<V>({ kind: "root" }, withEntry));
|
||||
act(() => result.current.open({ kind: "player", once: true }, { replace: true }));
|
||||
expect(window.history.state._sub).toEqual({ kind: "player" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,6 +106,9 @@ export interface SubviewUrl<T> {
|
||||
encode: (view: T) => string | null;
|
||||
/** Parse a param value back to a sub-view, or null if it isn't a valid one. */
|
||||
decode: (raw: string) => T | null;
|
||||
/** What the history ENTRY keeps of a view, when that is less than the view opens with: a one-shot
|
||||
* flag (the Plex player's `fromStart`) must not replay on a reload or a Forward onto the entry. */
|
||||
entry?: (view: T) => T;
|
||||
}
|
||||
|
||||
export function useHistorySubview<T>(
|
||||
@@ -152,19 +155,20 @@ export function useHistorySubview<T>(
|
||||
const open = useCallback(
|
||||
(next: T, opts?: { replace?: boolean }) => {
|
||||
setView(next);
|
||||
const kept = url?.entry ? url.entry(next) : next;
|
||||
const href = url ? urlWithParams({ [url.key]: url.encode(next) }) : "";
|
||||
if (opts?.replace) {
|
||||
// Same ENTRY, so it keeps its sfScroll id: PageScroller keys offsets per (entry, view), and
|
||||
// the view key has changed, so the sub-view still opens at the top without minting an id
|
||||
// that would orphan the one this entry already carries.
|
||||
window.history.replaceState({ ...window.history.state, _sub: next }, "", href);
|
||||
window.history.replaceState({ ...window.history.state, _sub: kept }, "", href);
|
||||
notifyNav();
|
||||
return;
|
||||
}
|
||||
// A fresh sfScroll so the opened sub-view is its own scroll entry (starts at the top, and Back
|
||||
// restores the view we came from) — then notify PageScroller, which this push doesn't re-render.
|
||||
window.history.pushState(
|
||||
{ ...rememberNavPosition(), _sub: next, sfScroll: nextNavId() },
|
||||
{ ...rememberNavPosition(), _sub: kept, sfScroll: nextNavId() },
|
||||
"",
|
||||
href,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user