feat(admin, admin-ui, medusa-js, medusa-react, medusa): Support Admin Extensions (#4761)
Co-authored-by: Rares Stefan <948623+StephixOne@users.noreply.github.com> Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
26c78bbc03
commit
f1a05f4725
@@ -0,0 +1,133 @@
|
||||
import { isForbiddenRoute } from "../constants/forbidden-routes"
|
||||
import { Link, Route, RouteExtension, RouteSegment } from "../types/extensions"
|
||||
|
||||
type RouteNode = {
|
||||
[pathSegment: string]: RouteNode | Route
|
||||
}
|
||||
|
||||
class RouteRegistry {
|
||||
private routes: RouteNode
|
||||
private links: Link[]
|
||||
|
||||
constructor() {
|
||||
this.routes = {}
|
||||
this.links = []
|
||||
}
|
||||
|
||||
register(origin: string, { Component, config }: RouteExtension) {
|
||||
const { path, link } = config
|
||||
|
||||
const pathSegments = path.split("/").filter(Boolean)
|
||||
let currentNode = this.routes
|
||||
let finalSegment = ""
|
||||
|
||||
for (const segment of pathSegments) {
|
||||
if (!currentNode[segment]) {
|
||||
currentNode[segment] = {}
|
||||
}
|
||||
currentNode = currentNode[segment] as RouteNode
|
||||
finalSegment = segment
|
||||
}
|
||||
|
||||
if (currentNode.__route) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.warn(
|
||||
`Route with path ${path} already registered by ${currentNode.__route.origin}.`
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
currentNode.__route = {
|
||||
origin,
|
||||
path: `/${finalSegment}`,
|
||||
Page: Component,
|
||||
}
|
||||
|
||||
if (link) {
|
||||
this.links.push({
|
||||
path,
|
||||
label: link.label,
|
||||
icon: link.icon,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getTopLevelRoutes(): (Route | RouteSegment)[] {
|
||||
const topLevelRoutes = Object.entries(this.routes)
|
||||
.filter(([key, _]) => !isForbiddenRoute(key))
|
||||
.map(([key, node]) => {
|
||||
return "__route" in node
|
||||
? ((node as RouteNode).__route as Route)
|
||||
: ({
|
||||
path: `/${key}`,
|
||||
} as RouteSegment)
|
||||
})
|
||||
|
||||
return topLevelRoutes
|
||||
}
|
||||
|
||||
getNestedRoutes(path: string) {
|
||||
const segments = path.split("/").filter((s) => !!s)
|
||||
const routes = this.routes
|
||||
|
||||
function getNode(segments: string[]) {
|
||||
let currentNode: RouteNode | null = routes
|
||||
|
||||
for (const segment of segments) {
|
||||
if (currentNode && currentNode[segment]) {
|
||||
currentNode = currentNode[segment] as RouteNode
|
||||
} else {
|
||||
currentNode = null
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return currentNode
|
||||
}
|
||||
|
||||
const lastKnownNode = getNode(segments)
|
||||
|
||||
if (!lastKnownNode) {
|
||||
return []
|
||||
}
|
||||
|
||||
const nestedRoutes: Route[] = []
|
||||
|
||||
function getChildren(node: RouteNode, parent: string = "") {
|
||||
const possiblePaths = Object.entries(node).filter(
|
||||
([key, _value]) => key !== "__route"
|
||||
)
|
||||
|
||||
for (const [key, value] of possiblePaths) {
|
||||
const currentPath = `${parent}/${key}`
|
||||
|
||||
if (value && "__route" in value) {
|
||||
const page = (value.__route as Route).Page
|
||||
const origin = (value.__route as Route).origin
|
||||
|
||||
nestedRoutes.push({
|
||||
origin,
|
||||
path: currentPath,
|
||||
Page: page,
|
||||
})
|
||||
}
|
||||
|
||||
if (value && typeof value === "object") {
|
||||
getChildren(value as RouteNode, currentPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getChildren(lastKnownNode)
|
||||
|
||||
return nestedRoutes
|
||||
}
|
||||
|
||||
getLinks(): Link[] {
|
||||
return this.links
|
||||
}
|
||||
}
|
||||
|
||||
export default RouteRegistry
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Card, Setting, SettingExtension } from "../types/extensions"
|
||||
|
||||
class SettingRegistry {
|
||||
private settings: Record<string, Setting>
|
||||
private cards: Card[]
|
||||
|
||||
constructor() {
|
||||
this.settings = {}
|
||||
this.cards = []
|
||||
}
|
||||
|
||||
register(origin: string, { Component, config }: SettingExtension) {
|
||||
const { path, card } = config
|
||||
|
||||
if (this.settings[path] !== undefined) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.warn(
|
||||
`Settings page with path ${path} already registered by ${this.settings[path].origin}.`
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.settings[path] = {
|
||||
origin,
|
||||
path: path,
|
||||
Page: Component,
|
||||
}
|
||||
|
||||
this.cards.push({
|
||||
path: path,
|
||||
label: card.label,
|
||||
description: card.description,
|
||||
icon: card.icon,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of settings sorted by path
|
||||
* @returns {Setting[]} An array of settings sorted by path
|
||||
*/
|
||||
getSettings(): Setting[] {
|
||||
return Object.values(this.settings).sort((a, b) => {
|
||||
return a.path.localeCompare(b.path)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of cards sorted by path
|
||||
* @returns {Card[]} An array of cards sorted by path
|
||||
*/
|
||||
getCards(): Card[] {
|
||||
return this.cards.sort((a, b) => {
|
||||
return a.path.localeCompare(b.path)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default SettingRegistry
|
||||
@@ -0,0 +1,26 @@
|
||||
import { InjectionZone, Widget, WidgetExtension } from "../types/extensions"
|
||||
|
||||
type Widgets = Map<InjectionZone, Widget[]>
|
||||
|
||||
class WidgetRegistry {
|
||||
private widgets: Widgets = new Map()
|
||||
|
||||
public register(origin: string, widget: WidgetExtension) {
|
||||
const { zone } = widget.config
|
||||
|
||||
const zones = Array.isArray(zone) ? zone : [zone]
|
||||
|
||||
for (const widgetZone of zones) {
|
||||
const widgets = this.widgets.get(widgetZone) || []
|
||||
widgets.push({ origin, Widget: widget.Component })
|
||||
|
||||
this.widgets.set(widgetZone, widgets)
|
||||
}
|
||||
}
|
||||
|
||||
public getWidgets(zone: InjectionZone) {
|
||||
return this.widgets.get(zone) || []
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetRegistry
|
||||
Reference in New Issue
Block a user