export type KeyedDocumentRoute = { immediately: boolean; key: string; routeKey?: string; }; declare const documentRouteIdentityBrand: unique symbol; export type DocumentRouteIdentity = string & { readonly [documentRouteIdentityBrand]: true; }; export type DocumentRouteIdentitySource = | { id: string; page: "channel" } | { owner: string; page: "document"; repository: string; slug: string } | { childSlug: string; owner: string; page: "child"; parentSlug: string; repository: string; }; export function documentRouteIdentity(source: DocumentRouteIdentitySource): DocumentRouteIdentity { switch (source.page) { case "channel": return `channel:${source.id}` as DocumentRouteIdentity; case "document": return `document:${source.owner}/${source.repository}/${source.slug}` as DocumentRouteIdentity; case "child": return ( `child:${source.owner}/${source.repository}/${source.parentSlug}/${source.childSlug}` ) as DocumentRouteIdentity; } } type ReadyDocumentRoute = T & { resolution?: R }; export type DocumentRouteSwap = { current: ReadyDocumentRoute; pending?: ReadyDocumentRoute; previous?: ReadyDocumentRoute; }; export type DocumentRouteAction = | { route: T; type: "requested" } | { key: string; resolution?: R; type: "ready" } | { key: string; type: "closed" }; export function transitionDocumentRoute( state: DocumentRouteSwap, action: DocumentRouteAction, ): DocumentRouteSwap { if (action.type === "requested") { let requested = action.route; let retarget = ( route: ReadyDocumentRoute, ): ReadyDocumentRoute => requested.routeKey !== undefined && requested.routeKey === route.routeKey ? { ...route, immediately: requested.immediately } : { ...requested, resolution: route.resolution }; if (requested.key === state.current.key) { return { current: retarget(state.current), previous: state.previous, }; } // While a route is still leaving, the current one has never been shown, so a // return to the leaving route simply keeps it. if (requested.key === state.previous?.key) { return { current: retarget(state.previous) }; } return { ...state, pending: requested }; } if (action.type === "ready") { let ready = (route: ReadyDocumentRoute): ReadyDocumentRoute => action.resolution === undefined ? route : { ...route, resolution: action.resolution }; if (state.pending?.key === action.key) { // A route that never reached the screen is dropped, not shown on its way out. return { current: ready(state.pending), previous: state.previous ?? state.current }; } if (state.current.key === action.key && action.resolution !== undefined) { if (state.current.resolution === action.resolution) return state; return { ...state, current: ready(state.current) }; } if (state.previous?.key === action.key && action.resolution !== undefined) { if (state.previous.resolution === action.resolution) return state; return { ...state, previous: ready(state.previous) }; } return state; } return state.previous?.key === action.key ? { current: state.current, pending: state.pending } : state; }