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',
|
BRAND_LOGO: 'BRAND_LOGO',
|
||||||
THEME_COLOR: 'THEME_COLOR',
|
THEME_COLOR: 'THEME_COLOR',
|
||||||
UI_SHELL: 'UI_SHELL',
|
UI_SHELL: 'UI_SHELL',
|
||||||
|
INITIAL_ROUTE: 'INITIAL_ROUTE',
|
||||||
STORAGE_BACKEND: 'STORAGE_BACKEND',
|
STORAGE_BACKEND: 'STORAGE_BACKEND',
|
||||||
API_BASE_URL: 'API_BASE_URL',
|
API_BASE_URL: 'API_BASE_URL',
|
||||||
MODULES: 'MODULES',
|
MODULES: 'MODULES',
|
||||||
@@ -152,6 +153,7 @@ export function initEnv(appConfig) {
|
|||||||
BRAND_LOGO: appConfig.brand_logo || appConfig.brandLogo || appConfig.icons?.[0]?.src || '/favicon.svg',
|
BRAND_LOGO: appConfig.brand_logo || appConfig.brandLogo || appConfig.icons?.[0]?.src || '/favicon.svg',
|
||||||
THEME_COLOR: appConfig.theme_color || appConfig.themeColor || '#000000',
|
THEME_COLOR: appConfig.theme_color || appConfig.themeColor || '#000000',
|
||||||
UI_SHELL: appConfig.ui_shell || appConfig.uiShell || 'EmptyShell',
|
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',
|
STORAGE_BACKEND: appConfig.storage?.backend || 'localStorage',
|
||||||
API_BASE_URL: resolvedApiBaseURL,
|
API_BASE_URL: resolvedApiBaseURL,
|
||||||
MODULES: appConfig.modules || [],
|
MODULES: appConfig.modules || [],
|
||||||
|
|||||||
+22
-15
@@ -6,7 +6,13 @@
|
|||||||
|
|
||||||
import React, { createContext, useContext, useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
import React, { createContext, useContext, useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||||
import { TamaguiProvider, Theme, createTamagui, YStack } from 'tamagui';
|
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 { getProvider } from '../platform/storage.js';
|
||||||
import * as apiClient from '../platform/api.js';
|
import * as apiClient from '../platform/api.js';
|
||||||
import * as storageModuleRef from '../platform/storage.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 * as envModuleRef from '../platform/env.js';
|
||||||
import { getConfig, setConfig, CONFIG_KEYS, createLogger, startTrace, isDevelopment } 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 { EmptyShell, LandingShell, DashboardShell, AppInfo, Router } from './components/index.js';
|
||||||
|
import { resolveRegisteredShell } from './components/shell-registry.js';
|
||||||
import { LoginPage } from '../security/pages/LoginPage.jsx';
|
import { LoginPage } from '../security/pages/LoginPage.jsx';
|
||||||
import { getStyleTheme, DEFAULT_STYLE_THEME, normalizeStyleThemeName, setActiveStyleThemeName } from './styles/index.js';
|
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';
|
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':
|
case 'dashboardshell':
|
||||||
return DashboardShell;
|
return DashboardShell;
|
||||||
case 'emptyshell':
|
case 'emptyshell':
|
||||||
default:
|
|
||||||
return EmptyShell;
|
return EmptyShell;
|
||||||
|
default: {
|
||||||
|
const registeredShell = resolveRegisteredShell(shellName);
|
||||||
|
return registeredShell || EmptyShell;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +94,9 @@ function App({
|
|||||||
const [menuItems, setMenuItems] = useState([]);
|
const [menuItems, setMenuItems] = useState([]);
|
||||||
const [initialized, setInitialized] = useState(false);
|
const [initialized, setInitialized] = useState(false);
|
||||||
const [ShellComponent, setShellComponent] = useState(() => resolveShellComponent(initialProfile?.ui_shell ?? 'EmptyShell'));
|
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 [bootResult, setBootResult] = useState(null);
|
||||||
const [bootModeOverride, setBootModeOverride] = useState(null);
|
const [bootModeOverride, setBootModeOverride] = useState(null);
|
||||||
|
|
||||||
@@ -282,6 +295,9 @@ function App({
|
|||||||
const Shell = resolveShellComponent(shellName);
|
const Shell = resolveShellComponent(shellName);
|
||||||
setShellComponent(() => Shell);
|
setShellComponent(() => Shell);
|
||||||
appLogger.log(`Using shell: ${shellName}`);
|
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
|
// Get menu items from primary menu
|
||||||
if (services.menu) {
|
if (services.menu) {
|
||||||
@@ -405,7 +421,7 @@ function App({
|
|||||||
appContent = <LoginPage />;
|
appContent = <LoginPage />;
|
||||||
} else if (!shouldRenderBootScreen && !shouldHoldDuringInit) {
|
} else if (!shouldRenderBootScreen && !shouldHoldDuringInit) {
|
||||||
appContent = (
|
appContent = (
|
||||||
<Router initialPath="/home">
|
<Router initialPath={initialRoute}>
|
||||||
{/* Declarative route registration (commented out - routes now registered programmatically via modules)
|
{/* Declarative route registration (commented out - routes now registered programmatically via modules)
|
||||||
<Router.Endpoint path="/home" component={HomePage} />
|
<Router.Endpoint path="/home" component={HomePage} />
|
||||||
|
|
||||||
@@ -520,18 +536,9 @@ if (typeof window !== 'undefined') {
|
|||||||
window.clearPWACache = clearPWACache;
|
window.clearPWACache = clearPWACache;
|
||||||
window.__PWA_UTILS__ = {
|
window.__PWA_UTILS__ = {
|
||||||
clearPWACache,
|
clearPWACache,
|
||||||
clearAllCaches: async () => {
|
clearAllCaches,
|
||||||
const { clearAllCaches } = await import('../platform/sw-register.js');
|
unregisterAllServiceWorkers,
|
||||||
return clearAllCaches();
|
clearAllStorage
|
||||||
},
|
|
||||||
unregisterAllServiceWorkers: async () => {
|
|
||||||
const { unregisterAllServiceWorkers } = await import('../platform/sw-register.js');
|
|
||||||
return unregisterAllServiceWorkers();
|
|
||||||
},
|
|
||||||
clearAllStorage: async () => {
|
|
||||||
const { clearAllStorage } = await import('../platform/sw-register.js');
|
|
||||||
return clearAllStorage();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('💡 PWA Utilities available:');
|
console.log('💡 PWA Utilities available:');
|
||||||
|
|||||||
@@ -394,6 +394,7 @@ const iconMap = {
|
|||||||
'wifi': wrap(WifiHigh, 'WifiHigh'),
|
'wifi': wrap(WifiHigh, 'WifiHigh'),
|
||||||
'wifi-off': wrap(WifiSlash, 'WifiSlash'),
|
'wifi-off': wrap(WifiSlash, 'WifiSlash'),
|
||||||
'lightning': wrap(Lightning, 'Lightning'),
|
'lightning': wrap(Lightning, 'Lightning'),
|
||||||
|
'game': wrap(Lightning, 'Lightning'),
|
||||||
|
|
||||||
// ── Volume ─────────────────────────────────────────────────────────────
|
// ── Volume ─────────────────────────────────────────────────────────────
|
||||||
'volume-up': wrap(SpeakerHigh, 'SpeakerHigh'),
|
'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 { SettingsPanel, default as SettingsPanelDefault } from './SettingsPanel.jsx';
|
||||||
export { GeneralConfig, default as GeneralConfigDefault } from './GeneralConfig.jsx';
|
export { GeneralConfig, default as GeneralConfigDefault } from './GeneralConfig.jsx';
|
||||||
export { IdentityConfig, default as IdentityConfigDefault } from './IdentityConfig.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 * from './grid/index.js';
|
||||||
export { getTypographyRoleProps, getStyleTypography, TYPOGRAPHY_ROLE_KEYS } from '../styles/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