|
import {createContext} from "@/utils/context" |
|
import {Topic} from "@/contexts/topics"; |
|
|
|
type Route = { |
|
name: keyof typeof routes, |
|
args: any[], |
|
location: string, |
|
} |
|
|
|
const matchers: {[key: string]: (location: string) => any[] | null} = {}; |
|
|
|
function constructRoute<A>( |
|
name: keyof typeof routes, |
|
generateLocation: (...args: A) => string, |
|
matcher: (location: string) => A | null, |
|
): (...args: A) => Route { |
|
matchers[name] = matcher; |
|
return (...args) => ({ |
|
name, |
|
args, |
|
location: generateLocation(...args), |
|
} satisfies Route); |
|
} |
|
|
|
export const routes = { |
|
home: constructRoute( |
|
"home", |
|
(page: number) => { |
|
|
|
if(page < 0) { |
|
throw new Error("Page not found"); |
|
} |
|
return page ? `/${page + 1}` : "/" |
|
}, |
|
(location) => { |
|
|
|
if(location === "/") { |
|
return [0]; |
|
} |
|
const match = location.match(/^\/(\d+)/); |
|
|
|
return match ? [Math.max(parseInt(match[1]) - 1, 0)] : null; |
|
}, |
|
|
|
), |
|
topic: constructRoute( |
|
"topic", |
|
(id: string, page: number) => `/topic/${id}/${page}`, |
|
(location) => { |
|
const match = location.match(/^\/topic\/(.+)\/(\d+)/); |
|
return match ? [match[1], Math.max(parseInt(match[2]) - 1, 1)] : null; |
|
}, |
|
), |
|
settings: constructRoute( |
|
"settings", |
|
() => "/settings", |
|
(location) => location === "/settings" ? [] : null, |
|
), |
|
} as const satisfies { |
|
[key: string]: (...args: any[]) => Route |
|
}; |
|
|
|
function matchBrowserLocation(): Route { |
|
for (const [name, matcher] of Object.entries(matchers)) { |
|
const location = window.location.pathname + window.location.search; |
|
const args = matcher(location); |
|
if (args !== null) { |
|
return routes[name as keyof typeof routes](...args); |
|
} |
|
} |
|
return routes.home(0); |
|
} |
|
|
|
const history: Route[] = []; |
|
let historyIndex = 0; |
|
|
|
export const routeCtx = createContext({ |
|
initialValue: matchBrowserLocation, |
|
controllers: (route: Route, setRoute) => ({ |
|
onMount: () => { |
|
|
|
|
|
history.push(route); |
|
historyIndex = history.length - 1; |
|
window.addEventListener('popstate', () => { |
|
setRoute(matchBrowserLocation()); |
|
}); |
|
}, |
|
effect: () => { |
|
const location = route.location; |
|
if (location !== `${window.location.pathname}${window.location.search}`) { |
|
history.push(route); |
|
historyIndex = history.length - 1; |
|
window.history.pushState({}, "", location); |
|
} |
|
}, |
|
actions: { |
|
goBack: () => { |
|
|
|
|
|
if (historyIndex > 0) { |
|
historyIndex--; |
|
setRoute(history[historyIndex]); |
|
} else { |
|
setRoute(routes.home(0)); |
|
} |
|
}, |
|
getHistoryIndex: () => historyIndex, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
}) |
|
}) |