forked from tldraw/tldraw
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[botcom] fancypants routes (tldraw#5078)
Some self-indulgent typescript shenanigans, as an end-of-week palette cleanser πββοΈ I found the route definitions kinda hard to read and add to, with all the prefixes string interpolation and helper functions stuff. I wanted to make it obvious how to add a new route and whether/how to add a helper function for creating paths or urls for that route. I remembered gary bernhardt came up with some clever typesafe router thingy a few years ago after TS added template string types, and figured I could do something similar that lets us specify the routes as normal (uninterpolated, easy to scan) strings and then extract the param types, and also to compile helper functions automatically. That's what this PR does. it adds a routeDefs.ts file that lets you quickly scan the list of routes on the site, and if you want to add a new route you put it there and it automatically compiles a helper fn. Then in routes.tsx we reference the route paths when constructing the route component hierarchy. I don't feel super strongly about whether or not to merge this, tbh it was fine how it was π€·πΌ ### Change type - [x] `other`
- Loading branch information
Showing
18 changed files
with
151 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { assert } from 'tldraw' | ||
|
||
export const ROUTES = { | ||
legacyRoot: '/', | ||
legacyNewPage: '/new', | ||
legacyNewPage2: '/r', | ||
legacyRoom: '/r/:roomId', | ||
touchscreenSidePanel: '/ts-side', | ||
legacyRoomHistory: '/r/:boardId/history', | ||
legacyRoomHistorySnapshot: '/r/:boardId/history/:timestamp', | ||
legacySnapshot: '/s/:roomId', | ||
legacyReadonly: '/ro/:roomId', | ||
legacyReadonlyOld: '/v/:roomId', | ||
|
||
tlaRoot: `/q`, | ||
tlaFile: `/q/f/:fileSlug`, | ||
tlaLocalFile: `/q/lf/:fileSlug`, | ||
tlaPlayground: `/q/playground`, | ||
tlaPublish: `/q/p/:fileSlug`, | ||
} as const | ||
|
||
export const routes: { | ||
[key in keyof typeof ROUTES]: PathFn<(typeof ROUTES)[key]> | ||
} = Object.fromEntries( | ||
Object.entries(ROUTES).map(([key, path]) => [ | ||
key, | ||
((routeParamsOrOptions: any, options: any) => { | ||
if (path.includes('/:')) { | ||
return compilePath(path, routeParamsOrOptions, options) | ||
} else { | ||
return compilePath(path, null, routeParamsOrOptions) | ||
} | ||
}) satisfies PathFn<any>, | ||
]) | ||
) as any | ||
|
||
type ExtractParamNamesFromPath<route extends string> = route extends `/${infer path}` | ||
? ExtractParamNamesFromPathSegments<SplitPath<path>> | ||
: never | ||
|
||
type SplitPath<path extends string> = path extends `${infer segment}/${infer rest}` | ||
? segment | SplitPath<rest> | ||
: path | ||
|
||
type ExtractParamNamesFromPathSegments<segments extends string> = segments extends `:${infer param}` | ||
? param | ||
: never | ||
|
||
interface PathOptions { | ||
searchParams?: ConstructorParameters<typeof URLSearchParams>[0] | ||
asUrl?: boolean | ||
} | ||
|
||
type PathFn<path extends `/${string}`> = path extends `${string}:${string}:${string}` | ||
? // has at least two params | ||
( | ||
routeParams: Record<ExtractParamNamesFromPath<path>, string>, | ||
searchParams?: PathOptions | ||
) => string | ||
: path extends `${string}:${string}` | ||
? // only has one param, so we can have a single string | ||
(param: string, searchParams?: PathOptions) => string | ||
: (searchParams?: PathOptions) => string | ||
|
||
function compilePath( | ||
path: string, | ||
routeParams: string | Record<string, string> | null, | ||
options?: PathOptions | ||
) { | ||
const search = new URLSearchParams(options?.searchParams).toString() | ||
if (!path.includes(':')) { | ||
assert( | ||
routeParams === null || Object.keys(routeParams).length === 0, | ||
`Route params are not allowed for path ${path}` | ||
) | ||
return path + (search ? `?${search}` : '') | ||
} | ||
assert(routeParams !== null, `Route params are required for path ${path}`) | ||
|
||
path = | ||
path.replace(/:\w+/g, (match) => | ||
// if there's only one param, routeParams will be a string | ||
typeof routeParams === 'string' ? routeParams : routeParams[match.slice(1)] | ||
) + (search ? `?${search}` : '') | ||
|
||
if (options?.asUrl) { | ||
return `${window.location.origin}${path}` | ||
} | ||
|
||
return path | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.