Add shell registry and profile-driven app config support
This commit is contained in:
@@ -84,6 +84,7 @@ export const CONFIG_KEYS = {
|
||||
BRAND_LOGO: 'BRAND_LOGO',
|
||||
THEME_COLOR: 'THEME_COLOR',
|
||||
UI_SHELL: 'UI_SHELL',
|
||||
INITIAL_ROUTE: 'INITIAL_ROUTE',
|
||||
STORAGE_BACKEND: 'STORAGE_BACKEND',
|
||||
API_BASE_URL: 'API_BASE_URL',
|
||||
MODULES: 'MODULES',
|
||||
@@ -152,6 +153,7 @@ export function initEnv(appConfig) {
|
||||
BRAND_LOGO: appConfig.brand_logo || appConfig.brandLogo || appConfig.icons?.[0]?.src || '/favicon.svg',
|
||||
THEME_COLOR: appConfig.theme_color || appConfig.themeColor || '#000000',
|
||||
UI_SHELL: appConfig.ui_shell || appConfig.uiShell || 'EmptyShell',
|
||||
INITIAL_ROUTE: appConfig.initial_route || appConfig.initialRoute || appConfig.ui?.initial_route || appConfig.ui?.initialRoute || '/home',
|
||||
STORAGE_BACKEND: appConfig.storage?.backend || 'localStorage',
|
||||
API_BASE_URL: resolvedApiBaseURL,
|
||||
MODULES: appConfig.modules || [],
|
||||
|
||||
+22
-15
@@ -6,7 +6,13 @@
|
||||
|
||||
import React, { createContext, useContext, useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
import { TamaguiProvider, Theme, createTamagui, YStack } from 'tamagui';
|
||||
import { clearPWACache, getServiceWorkerStatus } from '../platform/sw-register.js';
|
||||
import {
|
||||
clearPWACache,
|
||||
clearAllCaches,
|
||||
clearAllStorage,
|
||||
getServiceWorkerStatus,
|
||||
unregisterAllServiceWorkers
|
||||
} from '../platform/sw-register.js';
|
||||
import { getProvider } from '../platform/storage.js';
|
||||
import * as apiClient from '../platform/api.js';
|
||||
import * as storageModuleRef from '../platform/storage.js';
|
||||
@@ -14,6 +20,7 @@ import * as menuRef from '../platform/menu.js';
|
||||
import * as envModuleRef from '../platform/env.js';
|
||||
import { getConfig, setConfig, CONFIG_KEYS, createLogger, startTrace, isDevelopment } from '../platform/env.js';
|
||||
import { EmptyShell, LandingShell, DashboardShell, AppInfo, Router } from './components/index.js';
|
||||
import { resolveRegisteredShell } from './components/shell-registry.js';
|
||||
import { LoginPage } from '../security/pages/LoginPage.jsx';
|
||||
import { getStyleTheme, DEFAULT_STYLE_THEME, normalizeStyleThemeName, setActiveStyleThemeName } from './styles/index.js';
|
||||
import { THEME_MODE_CONFIG_KEY, THEME_NAME_CONFIG_KEY, THEME_MODES, themeManager } from './theme-controller.js';
|
||||
@@ -48,8 +55,11 @@ function resolveShellComponent(shellName = 'EmptyShell') {
|
||||
case 'dashboardshell':
|
||||
return DashboardShell;
|
||||
case 'emptyshell':
|
||||
default:
|
||||
return EmptyShell;
|
||||
default: {
|
||||
const registeredShell = resolveRegisteredShell(shellName);
|
||||
return registeredShell || EmptyShell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +94,9 @@ function App({
|
||||
const [menuItems, setMenuItems] = useState([]);
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const [ShellComponent, setShellComponent] = useState(() => resolveShellComponent(initialProfile?.ui_shell ?? 'EmptyShell'));
|
||||
const [initialRoute, setInitialRoute] = useState(
|
||||
initialProfile?.initial_route ?? initialProfile?.initialRoute ?? initialProfile?.ui?.initial_route ?? initialProfile?.ui?.initialRoute ?? '/home'
|
||||
);
|
||||
const [bootResult, setBootResult] = useState(null);
|
||||
const [bootModeOverride, setBootModeOverride] = useState(null);
|
||||
|
||||
@@ -282,6 +295,9 @@ function App({
|
||||
const Shell = resolveShellComponent(shellName);
|
||||
setShellComponent(() => Shell);
|
||||
appLogger.log(`Using shell: ${shellName}`);
|
||||
|
||||
const configuredInitialRoute = await services.env.getConfig(CONFIG_KEYS.INITIAL_ROUTE, '/home');
|
||||
setInitialRoute(configuredInitialRoute || '/home');
|
||||
|
||||
// Get menu items from primary menu
|
||||
if (services.menu) {
|
||||
@@ -405,7 +421,7 @@ function App({
|
||||
appContent = <LoginPage />;
|
||||
} else if (!shouldRenderBootScreen && !shouldHoldDuringInit) {
|
||||
appContent = (
|
||||
<Router initialPath="/home">
|
||||
<Router initialPath={initialRoute}>
|
||||
{/* Declarative route registration (commented out - routes now registered programmatically via modules)
|
||||
<Router.Endpoint path="/home" component={HomePage} />
|
||||
|
||||
@@ -520,18 +536,9 @@ if (typeof window !== 'undefined') {
|
||||
window.clearPWACache = clearPWACache;
|
||||
window.__PWA_UTILS__ = {
|
||||
clearPWACache,
|
||||
clearAllCaches: async () => {
|
||||
const { clearAllCaches } = await import('../platform/sw-register.js');
|
||||
return clearAllCaches();
|
||||
},
|
||||
unregisterAllServiceWorkers: async () => {
|
||||
const { unregisterAllServiceWorkers } = await import('../platform/sw-register.js');
|
||||
return unregisterAllServiceWorkers();
|
||||
},
|
||||
clearAllStorage: async () => {
|
||||
const { clearAllStorage } = await import('../platform/sw-register.js');
|
||||
return clearAllStorage();
|
||||
}
|
||||
clearAllCaches,
|
||||
unregisterAllServiceWorkers,
|
||||
clearAllStorage
|
||||
};
|
||||
|
||||
console.log('💡 PWA Utilities available:');
|
||||
|
||||
@@ -394,6 +394,7 @@ const iconMap = {
|
||||
'wifi': wrap(WifiHigh, 'WifiHigh'),
|
||||
'wifi-off': wrap(WifiSlash, 'WifiSlash'),
|
||||
'lightning': wrap(Lightning, 'Lightning'),
|
||||
'game': wrap(Lightning, 'Lightning'),
|
||||
|
||||
// ── Volume ─────────────────────────────────────────────────────────────
|
||||
'volume-up': wrap(SpeakerHigh, 'SpeakerHigh'),
|
||||
|
||||
@@ -25,6 +25,7 @@ export { Panel, default as PanelDefault } from './Panel.jsx';
|
||||
export { SettingsPanel, default as SettingsPanelDefault } from './SettingsPanel.jsx';
|
||||
export { GeneralConfig, default as GeneralConfigDefault } from './GeneralConfig.jsx';
|
||||
export { IdentityConfig, default as IdentityConfigDefault } from './IdentityConfig.jsx';
|
||||
export { registerShell, unregisterShell, resolveRegisteredShell, listRegisteredShells, clearRegisteredShells } from './shell-registry.js';
|
||||
export * from './grid/index.js';
|
||||
export { getTypographyRoleProps, getStyleTypography, TYPOGRAPHY_ROLE_KEYS } from '../styles/index.js';
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
const SHELL_REGISTRY_KEY = '__bface_shell_registry__';
|
||||
|
||||
function getShellRegistry() {
|
||||
const scope = typeof globalThis !== 'undefined' ? globalThis : window;
|
||||
if (!scope[SHELL_REGISTRY_KEY]) {
|
||||
scope[SHELL_REGISTRY_KEY] = new Map();
|
||||
}
|
||||
return scope[SHELL_REGISTRY_KEY];
|
||||
}
|
||||
|
||||
function normalizeShellName(name = '') {
|
||||
return String(name).trim().toLowerCase();
|
||||
}
|
||||
|
||||
export function registerShell(name, component) {
|
||||
const key = normalizeShellName(name);
|
||||
if (!key || typeof component !== 'function') {
|
||||
return false;
|
||||
}
|
||||
|
||||
getShellRegistry().set(key, component);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function unregisterShell(name) {
|
||||
const key = normalizeShellName(name);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getShellRegistry().delete(key);
|
||||
}
|
||||
|
||||
export function resolveRegisteredShell(name) {
|
||||
const key = normalizeShellName(name);
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getShellRegistry().get(key) || null;
|
||||
}
|
||||
|
||||
export function listRegisteredShells() {
|
||||
return Array.from(getShellRegistry().keys());
|
||||
}
|
||||
|
||||
export function clearRegisteredShells() {
|
||||
getShellRegistry().clear();
|
||||
}
|
||||
Reference in New Issue
Block a user