optional splat routes (#13547)
Co-authored-by: SteelRazor47 <36779933+SteelRazor47@users.noreply.github.com>
This commit is contained in:
6
.changeset/mean-mugs-sparkle.md
Normal file
6
.changeset/mean-mugs-sparkle.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/admin-vite-plugin": patch
|
||||||
|
"@medusajs/dashboard": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(admin-vite-plugin,dashboard): support for react-router's splat and optional segments
|
||||||
@@ -4,7 +4,11 @@ export function getRoute(file: string): string {
|
|||||||
const importPath = normalizePath(file)
|
const importPath = normalizePath(file)
|
||||||
return importPath
|
return importPath
|
||||||
.replace(/.*\/admin\/(routes)/, "")
|
.replace(/.*\/admin\/(routes)/, "")
|
||||||
.replace(/\[([^\]]+)\]/g, ":$1")
|
.replace("[[*]]", "*?") // optional splat
|
||||||
|
.replace("[*]", "*") // splat
|
||||||
|
.replace(/\(([^\[\]\)]+)\)/g, "$1?") // optional static, (foo)
|
||||||
|
.replace(/\[\[([^\]]+)\]\]/g, ":$1?") // optional dynamic, [[foo]]
|
||||||
|
.replace(/\[([^\]]+)\]/g, ":$1") // dynamic, [foo]
|
||||||
.replace(
|
.replace(
|
||||||
new RegExp(
|
new RegExp(
|
||||||
`/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join(
|
`/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join(
|
||||||
|
|||||||
@@ -42,6 +42,13 @@ type DashboardAppProps = {
|
|||||||
plugins: DashboardPlugin[]
|
plugins: DashboardPlugin[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches segments that are optional and at the end of the path.
|
||||||
|
* Example: /path/to/:id?
|
||||||
|
* Such paths can be added to the menu items without the optional segment.
|
||||||
|
*/
|
||||||
|
const OPTIONAL_LAST_SEGMENT_MATCH = /\/([^\/])+\?$/
|
||||||
|
|
||||||
export class DashboardApp {
|
export class DashboardApp {
|
||||||
private widgets: WidgetMap
|
private widgets: WidgetMap
|
||||||
private menus: MenuMap
|
private menus: MenuMap
|
||||||
@@ -129,10 +136,11 @@ export class DashboardApp {
|
|||||||
allMenuItems.sort((a, b) => a.path.length - b.path.length)
|
allMenuItems.sort((a, b) => a.path.length - b.path.length)
|
||||||
|
|
||||||
allMenuItems.forEach((item) => {
|
allMenuItems.forEach((item) => {
|
||||||
if (item.path.includes("/:")) {
|
item.path = item.path.replace(OPTIONAL_LAST_SEGMENT_MATCH, "")
|
||||||
|
if (item.path.includes("/:") || item.path.endsWith("/*")) {
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
console.warn(
|
console.warn(
|
||||||
`[@medusajs/dashboard] Menu item for path "${item.path}" can't be added to the sidebar as it contains a parameter.`
|
`[@medusajs/dashboard] Menu item for path "${item.path}" can't be added to the sidebar as it contains a mandatory parameter.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -37,9 +37,10 @@ const createBranchRoute = (segment: string): RouteObject => ({
|
|||||||
const createLeafRoute = (
|
const createLeafRoute = (
|
||||||
Component: ComponentType,
|
Component: ComponentType,
|
||||||
loader?: LoaderFunction,
|
loader?: LoaderFunction,
|
||||||
handle?: object
|
handle?: object,
|
||||||
|
path: string = ""
|
||||||
): RouteObject => ({
|
): RouteObject => ({
|
||||||
path: "",
|
path,
|
||||||
ErrorBoundary: ErrorBoundary,
|
ErrorBoundary: ErrorBoundary,
|
||||||
async lazy() {
|
async lazy() {
|
||||||
const result: {
|
const result: {
|
||||||
@@ -149,7 +150,6 @@ const addRoute = (
|
|||||||
|
|
||||||
if (isComponentSegment || remainingSegments.length === 0) {
|
if (isComponentSegment || remainingSegments.length === 0) {
|
||||||
route.children ||= []
|
route.children ||= []
|
||||||
const leaf = createLeafRoute(Component, loader)
|
|
||||||
|
|
||||||
if (handle) {
|
if (handle) {
|
||||||
route.handle = handle
|
route.handle = handle
|
||||||
@@ -159,9 +159,17 @@ const addRoute = (
|
|||||||
route.loader = loader
|
route.loader = loader
|
||||||
}
|
}
|
||||||
|
|
||||||
leaf.children = processParallelRoutes(parallelRoutes, currentFullPath)
|
// Since splat segments must occur at the end of a route, react-router enforces the segment to be a leaf.
|
||||||
route.children.push(leaf)
|
// Therefore we can't create a child leaf route with `path: ""` and must instead modify the route itself
|
||||||
|
if (currentSegment === "*?" || currentSegment === "*") {
|
||||||
|
const leaf = createLeafRoute(Component, loader, handle, currentSegment)
|
||||||
|
leaf.children = processParallelRoutes(parallelRoutes, currentFullPath)
|
||||||
|
Object.assign(route, leaf)
|
||||||
|
} else {
|
||||||
|
const leaf = createLeafRoute(Component, loader)
|
||||||
|
leaf.children = processParallelRoutes(parallelRoutes, currentFullPath)
|
||||||
|
route.children.push(leaf)
|
||||||
|
}
|
||||||
if (remainingSegments.length > 0) {
|
if (remainingSegments.length > 0) {
|
||||||
addRoute(
|
addRoute(
|
||||||
remainingSegments,
|
remainingSegments,
|
||||||
|
|||||||
Reference in New Issue
Block a user