Files
XCDesktop/src/lib/module-registry.ts

159 lines
4.7 KiB
TypeScript
Raw Normal View History

2026-03-08 01:34:54 +08:00
import React from 'react'
import type { LucideIcon } from 'lucide-react'
import type { FileItem } from '@/lib/api'
import type { TOCItem } from '@/lib/utils'
import type { FrontendModuleConfig, InternalModuleConfig, ModuleEndpoints } from '@shared/types/module'
import type { ModuleDefinition } from '@shared/modules/types'
import { moduleApiRegistry } from './api/modules/ModuleApiRegistry'
import { createModuleApi, ModuleApiInstance } from './api/createModuleApi'
export type { FrontendModuleConfig, InternalModuleConfig }
export interface FrontendModule extends InternalModuleConfig {
match: (file: FileItem) => boolean
render: (file: FileItem, isActive: boolean, onTocUpdated?: (toc: TOCItem[]) => void) => React.ReactNode
}
const createFileItem = (name: string, path: string): FileItem => ({
name,
path,
type: 'file',
size: 0,
modified: new Date().toISOString(),
})
class ModuleRegistryImpl {
private modules = new Map<string, InternalModuleConfig>()
register(config: FrontendModuleConfig): void {
if (this.modules.has(config.id)) {
throw new Error(`Module with ID '${config.id}' is already registered`)
}
const tabId = `${config.id}-tab`
const fileItem = createFileItem(config.name, tabId)
const internalConfig: InternalModuleConfig = {
...config,
tabId,
fileItem,
}
this.modules.set(config.id, internalConfig)
console.log(`[ModuleRegistry] Registering module: ${config.id}, basePath: ${config.basePath}, hasEndpoints: ${!!config.endpoints}, endpoints:`, config.endpoints)
if (config.endpoints && config.basePath) {
const apiInstance = createModuleApi({
id: config.id,
name: config.name,
basePath: config.basePath,
order: config.order,
endpoints: config.endpoints,
})
moduleApiRegistry.register(apiInstance)
console.log(`[ModuleRegistry] API registered for: ${config.id}`)
} else {
console.warn(`[ModuleRegistry] Skipping API registration for ${config.id}: endpoints=${!!config.endpoints}, basePath=${config.basePath}`)
}
}
get(id: string): InternalModuleConfig | undefined {
return this.modules.get(id)
}
getAll(): InternalModuleConfig[] {
return Array.from(this.modules.values())
.sort((a, b) => a.order - b.order)
}
has(id: string): boolean {
return this.modules.has(id)
}
getFileItem(id: string): FileItem | undefined {
return this.modules.get(id)?.fileItem
}
getTabId(id: string): string | undefined {
return this.modules.get(id)?.tabId
}
getDefault(): InternalModuleConfig | undefined {
const all = this.getAll()
return all.length > 0 ? all[0] : undefined
}
}
export const registry = new ModuleRegistryImpl()
export function defineModule<
TEndpoints extends ModuleEndpoints
>(config: FrontendModuleConfig<TEndpoints>): FrontendModuleConfig<TEndpoints> {
return config
}
export interface FrontendModuleOptions<
TEndpoints extends ModuleEndpoints = ModuleEndpoints
> {
icon: LucideIcon
component: React.ComponentType
}
export function createFrontendModule<
TEndpoints extends ModuleEndpoints
>(
definition: ModuleDefinition<string, TEndpoints>,
options: FrontendModuleOptions<TEndpoints>
): FrontendModuleConfig<TEndpoints> {
return {
id: definition.id,
name: definition.name,
order: definition.order,
basePath: definition.basePath,
endpoints: definition.endpoints,
icon: options.icon,
component: options.component,
}
}
export function getModuleLegacy(file: FileItem): InternalModuleConfig | undefined {
return registry.getAll().find(m => m.fileItem?.path === file.path)
}
export function matchModule(file: FileItem): InternalModuleConfig | undefined {
return getModuleLegacy(file)
}
export const getAllModules = (): InternalModuleConfig[] => {
return registry.getAll()
}
export const getFileItem = (moduleId: string): FileItem | undefined => {
return registry.getFileItem(moduleId)
}
export const getModuleFileItem = getFileItem
export const getModuleTabId = (moduleId: string): string | undefined => {
return registry.getTabId(moduleId)
}
export const getModule = (moduleId: string): InternalModuleConfig | undefined => {
return registry.get(moduleId)
}
export const getDefaultModule = (): InternalModuleConfig | undefined => {
return registry.getDefault()
}
export function getModuleApi<TEndpoints extends ModuleEndpoints>(
moduleId: string
): ModuleApiInstance<TEndpoints> | undefined {
const api = moduleApiRegistry.get<ModuleApiInstance<TEndpoints>>(moduleId)
if (!api) {
console.error(`[getModuleApi] Failed to get API for module: ${moduleId}. Available APIs:`, Array.from((moduleApiRegistry as any).apis?.keys?.() || []))
}
return api
}