feat(dees-appui-base): overhaul AppUI core: replace simple view rendering with a full-featured ViewRegistry (caching, hide/show lifecycle, async lazy-loading), introduce view lifecycle hooks and activation context, add activity log API/component, remove built-in router and state manager, and update configuration interfaces and demos

This commit is contained in:
2025-12-19 13:54:37 +00:00
parent e697419843
commit eeb863b197
13 changed files with 2578 additions and 1307 deletions

View File

@@ -2,6 +2,39 @@ import type { TemplateResult } from '@design.estate/dees-element';
import type { IAppBarMenuItem } from './appbarmenuitem.js';
import type { ITab } from './tab.js';
import type { ISecondaryMenuGroup } from './secondarymenu.js';
import type { IMenuGroup } from './menugroup.js';
// Forward declaration for circular reference
export type TDeesAppuiBase = HTMLElement & {
setAppBarMenus: (menus: IAppBarMenuItem[]) => void;
updateAppBarMenu: (name: string, update: Partial<IAppBarMenuItem>) => void;
setBreadcrumbs: (breadcrumbs: string | string[]) => void;
setUser: (user: IAppUser | undefined) => void;
setProfileMenuItems: (items: IAppBarMenuItem[]) => void;
setSearchVisible: (visible: boolean) => void;
setWindowControlsVisible: (visible: boolean) => void;
setMainMenu: (config: IMainMenuConfig) => void;
updateMainMenuGroup: (groupName: string, update: Partial<IMenuGroup>) => void;
addMainMenuItem: (groupName: string, tab: ITab) => void;
removeMainMenuItem: (groupName: string, tabKey: string) => void;
setMainMenuSelection: (tabKey: string) => void;
setMainMenuCollapsed: (collapsed: boolean) => void;
setMainMenuBadge: (tabKey: string, badge: string | number) => void;
clearMainMenuBadge: (tabKey: string) => void;
setSecondaryMenu: (config: { heading?: string; groups: ISecondaryMenuGroup[] }) => void;
updateSecondaryMenuGroup: (groupName: string, update: Partial<ISecondaryMenuGroup>) => void;
addSecondaryMenuItem: (groupName: string, item: ISecondaryMenuGroup['items'][0]) => void;
setSecondaryMenuSelection: (itemKey: string) => void;
clearSecondaryMenu: () => void;
setContentTabs: (tabs: ITab[]) => void;
addContentTab: (tab: ITab) => void;
removeContentTab: (tabKey: string) => void;
selectContentTab: (tabKey: string) => void;
getSelectedContentTab: () => ITab | undefined;
activityLog: IActivityLogAPI;
navigateToView: (viewId: string, params?: Record<string, string>) => Promise<boolean>;
getCurrentView: () => IViewDefinition | undefined;
};
/**
* User configuration for the app bar
@@ -13,6 +46,69 @@ export interface IAppUser {
status?: 'online' | 'offline' | 'busy' | 'away';
}
/**
* Activity entry for the activity log
*/
export interface IActivityEntry {
/** Unique identifier (auto-generated if not provided) */
id?: string;
/** Timestamp (auto-set to now if not provided) */
timestamp?: Date;
/** Activity type for icon styling */
type: 'login' | 'logout' | 'view' | 'create' | 'update' | 'delete' | 'custom';
/** User who performed the action */
user: string;
/** Activity message */
message: string;
/** Optional custom icon (overrides type-based icon) */
iconName?: string;
/** Optional additional data */
data?: Record<string, unknown>;
}
/**
* Activity log programmatic API
*/
export interface IActivityLogAPI {
/** Add a single activity entry */
add: (entry: IActivityEntry) => void;
/** Add multiple activity entries */
addMany: (entries: IActivityEntry[]) => void;
/** Clear all entries */
clear: () => void;
/** Get all entries */
getEntries: () => IActivityEntry[];
/** Filter entries */
filter: (criteria: { user?: string; type?: IActivityEntry['type'] }) => IActivityEntry[];
/** Search entries by message */
search: (query: string) => IActivityEntry[];
}
/**
* View activation context passed to onActivate lifecycle hook
*/
export interface IViewActivationContext {
/** Reference to the DeesAppuiBase instance */
appui: TDeesAppuiBase;
/** The view ID being activated */
viewId: string;
/** Route parameters if any */
params?: Record<string, string>;
}
/**
* View lifecycle hooks interface
* Views can implement these methods to receive lifecycle notifications
*/
export interface IViewLifecycle {
/** Called when view is activated (displayed) */
onActivate?: (context: IViewActivationContext) => void | Promise<void>;
/** Called when view is deactivated (hidden) */
onDeactivate?: () => void | Promise<void>;
/** Called before navigation away - return false or message to block */
canDeactivate?: () => boolean | string | Promise<boolean | string>;
}
/**
* View definition for the view registry
*/
@@ -23,17 +119,29 @@ export interface IViewDefinition {
name: string;
/** Optional icon */
iconName?: string;
/** The view content - can be a tag name, element class, or template function */
content: string | (new () => HTMLElement) | (() => TemplateResult);
/**
* The view content - can be:
* - Tag name string (e.g., 'my-dashboard')
* - Element class constructor
* - Template function returning TemplateResult
* - Async function returning any of the above (for lazy loading)
*/
content:
| string
| (new () => HTMLElement)
| (() => TemplateResult)
| (() => Promise<string | (new () => HTMLElement) | (() => TemplateResult)>);
/** Secondary menu items specific to this view */
secondaryMenu?: ISecondaryMenuGroup[];
/** Content tabs specific to this view */
contentTabs?: ITab[];
/** Optional route path (defaults to id) */
/** Optional route path (defaults to id). Supports params like 'settings/:section' */
route?: string;
/** Badge to show on menu item */
badge?: string | number;
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
/** Whether to cache this view instance (default: true) */
cache?: boolean;
}
/**
@@ -50,10 +158,18 @@ export interface IMainMenuSection {
* Main menu configuration
*/
export interface IMainMenuConfig {
/** Menu sections with view references */
/** Logo icon */
logoIcon?: string;
/** Logo text */
logoText?: string;
/** Menu groups with tabs */
groups?: IMenuGroup[];
/** Menu sections with view references (alternative to groups) */
sections?: IMainMenuSection[];
/** Bottom pinned items (view IDs) */
/** Bottom pinned items (view IDs or tabs) */
bottomItems?: string[];
/** Bottom tabs */
bottomTabs?: ITab[];
}
/**
@@ -77,47 +193,13 @@ export interface IBrandingConfig {
logoText?: string;
}
/**
* Routing configuration
*/
export interface IRoutingConfig {
/** Routing mode */
mode: 'hash' | 'history' | 'external' | 'none';
/** Base path for history mode */
basePath?: string;
/** Default view ID to show on startup */
defaultView?: string;
/** Sync URL on view change */
syncUrl?: boolean;
/** Handle 404s - show view ID or callback */
notFound?: string | (() => void);
}
/**
* State persistence configuration
*/
export interface IStatePersistenceConfig {
/** Enable state persistence */
enabled: boolean;
/** Storage key prefix */
storageKey?: string;
/** Storage type */
storage?: 'localStorage' | 'sessionStorage' | 'memory';
/** What to persist */
persist?: {
mainMenuCollapsed?: boolean;
secondaryMenuCollapsed?: boolean;
selectedView?: boolean;
secondaryMenuSelection?: boolean;
collapsedGroups?: boolean;
};
}
/**
* Activity log configuration
*/
export interface IActivityLogConfig {
enabled?: boolean;
/** Whether activity log is visible */
visible?: boolean;
/** Width of activity log panel */
width?: number;
}
@@ -137,46 +219,15 @@ export interface IAppConfig {
/** Main menu structure */
mainMenu?: IMainMenuConfig;
/** Routing configuration */
routing?: IRoutingConfig;
/** State persistence configuration */
statePersistence?: IStatePersistenceConfig;
/** Default view ID to show on startup */
defaultView?: string;
/** Activity log configuration */
activityLog?: IActivityLogConfig;
/** Event callbacks (optional shorthand) */
/** Event callbacks */
onViewChange?: (viewId: string, view: IViewDefinition) => void;
onSearch?: () => void;
}
/**
* Serialized UI state for persistence
*/
export interface IAppUIState {
/** Current view ID */
currentViewId?: string;
/** Main menu collapsed state */
mainMenuCollapsed?: boolean;
/** Secondary menu collapsed state */
secondaryMenuCollapsed?: boolean;
/** Selected secondary menu item key */
secondaryMenuSelectedKey?: string;
/** Collapsed group names in secondary menu */
collapsedGroups?: string[];
/** Timestamp of last save */
timestamp?: number;
}
/**
* Route change event detail
*/
export interface IRouteChangeEvent {
viewId: string;
previousViewId: string | null;
params?: Record<string, string>;
source: 'navigation' | 'popstate' | 'initial' | 'programmatic';
onSearch?: (query: string) => void;
}
/**
@@ -186,4 +237,16 @@ export interface IViewChangeEvent {
viewId: string;
view: IViewDefinition;
previousView?: IViewDefinition;
params?: Record<string, string>;
}
/**
* View lifecycle event (for rxjs Subject)
*/
export interface IViewLifecycleEvent {
type: 'activated' | 'deactivated' | 'loading' | 'loaded' | 'loadError';
viewId: string;
element?: HTMLElement;
params?: Record<string, string>;
error?: unknown;
}

View File

@@ -2,4 +2,6 @@ export interface ITab {
key: string;
iconName?: string;
action: () => void;
badge?: string | number;
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
}