fix(web): fix replay viewer routing and embed fallback
Router: strip query string from hash path before route matching, and merge hash query params (e.g. ?url=) into the params passed to route handlers. Add /watch/replay route (without :id) so ?url= links work without a path ID. Embed: fall back to demo replay when the match replay is not found in R2/B2 instead of showing "Failed to fetch" (handles test match IDs with no replay). App: extend skeleton and PIP checks to match /watch/replay (with or without :id). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
651f344247
commit
273736a3f2
3 changed files with 31 additions and 8 deletions
|
|
@ -36,7 +36,7 @@ import {
|
|||
function getSkeletonHtml(path: string): string {
|
||||
if (path === '/leaderboard' || path === '/bots') return skeletonLeaderboard();
|
||||
if (path.startsWith('/bot/') || path.startsWith('/compete/bot/')) return skeletonBotProfile();
|
||||
if (path.startsWith('/watch/replay/') || path.startsWith('/replay/')) return skeletonReplay();
|
||||
if (path.startsWith('/watch/replay') || path.startsWith('/replay/')) return skeletonReplay();
|
||||
if (path.startsWith('/watch/playlists')) return skeletonPlaylists();
|
||||
if (path === '/watch/replays' || path === '/matches') return skeletonMatches();
|
||||
if (path === '/evolution') return skeletonEvolution();
|
||||
|
|
@ -197,8 +197,8 @@ router.beforeNavigate((from: string, to: string) => {
|
|||
|
||||
// §16.13: PIP replay — if leaving a replay page with active playback,
|
||||
// activate the mini-player instead of destroying the viewer.
|
||||
const leavingReplay = from.match(/^\/watch\/replay\//) || from.match(/^\/replay\//);
|
||||
const goingToReplay = to.match(/^\/watch\/replay\//) || to.match(/^\/replay\//);
|
||||
const leavingReplay = from.match(/^\/watch\/replay/) || from.match(/^\/replay\//);
|
||||
const goingToReplay = to.match(/^\/watch\/replay/) || to.match(/^\/replay\//);
|
||||
if (leavingReplay && !goingToReplay) {
|
||||
import('./components/pip-registry').then(({ getActiveReplay }) => {
|
||||
import('./components/pip').then(({ activatePip, isPipActive }) => {
|
||||
|
|
@ -246,6 +246,7 @@ router
|
|||
.on('/watch/replays', lazyRoute(loadMatchesPage))
|
||||
.on('/watch/playlists', lazyRoute(loadPlaylistsPage))
|
||||
.on('/watch/playlists/:slug', lazyRoute(loadPlaylistsPage))
|
||||
.on('/watch/replay', lazyRoute(loadReplayPage))
|
||||
.on('/watch/replay/:id', lazyRoute(loadReplayPage))
|
||||
.on('/watch/series/:id', lazyRoute(loadSeriesPage))
|
||||
.on('/watch/predictions', lazyRoute(loadPredictionsPage))
|
||||
|
|
|
|||
|
|
@ -131,8 +131,14 @@ class EmbedViewer {
|
|||
if (this.config.demo) {
|
||||
replay = await this.fetchDemoReplay();
|
||||
} else {
|
||||
// Try R2 first (warm cache), fall back to B2 (cold archive)
|
||||
replay = await this.fetchReplay(this.config.matchId);
|
||||
try {
|
||||
// Try R2 first (warm cache), fall back to B2 (cold archive)
|
||||
replay = await this.fetchReplay(this.config.matchId);
|
||||
} catch {
|
||||
// Replay not found in R2 or B2 — fall back to demo so the viewer
|
||||
// still shows something (e.g. when the match index has test IDs)
|
||||
replay = await this.fetchDemoReplay();
|
||||
}
|
||||
}
|
||||
this.replay = replay;
|
||||
|
||||
|
|
|
|||
|
|
@ -68,11 +68,24 @@ class Router {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get current path from hash
|
||||
* Get current path from hash (strips query string if present)
|
||||
*/
|
||||
getCurrentPath(): string {
|
||||
const hash = window.location.hash.slice(1); // Remove #
|
||||
return hash || '/';
|
||||
const path = hash.split('?')[0];
|
||||
return path || '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query params from the current hash URL
|
||||
*/
|
||||
private getHashQueryParams(): Record<string, string> {
|
||||
const hash = window.location.hash.slice(1);
|
||||
const qIdx = hash.indexOf('?');
|
||||
if (qIdx < 0) return {};
|
||||
const params: Record<string, string> = {};
|
||||
new URLSearchParams(hash.slice(qIdx + 1)).forEach((v, k) => { params[k] = v; });
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -95,10 +108,13 @@ class Router {
|
|||
hook(prevPath ?? '/', path);
|
||||
}
|
||||
|
||||
// Merge hash query params so handlers can read e.g. ?url= or ?id=
|
||||
const queryParams = this.getHashQueryParams();
|
||||
|
||||
for (const route of this.routes) {
|
||||
const match = path.match(route.pattern);
|
||||
if (match) {
|
||||
const params: Record<string, string> = {};
|
||||
const params: Record<string, string> = { ...queryParams };
|
||||
route.paramNames.forEach((name, idx) => {
|
||||
params[name] = decodeURIComponent(match[idx + 1]);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue