513 lines
13 KiB
Markdown
513 lines
13 KiB
Markdown
![]() |
# Building Applications with dees-appui Architecture
|
||
|
|
||
|
## Overview
|
||
|
|
||
|
The dees-appui system provides a comprehensive framework for building desktop-style web applications with a consistent layout, navigation, and view management system. This document outlines the architecture and best practices for building applications using these components.
|
||
|
|
||
|
## Core Architecture
|
||
|
|
||
|
### Component Hierarchy
|
||
|
|
||
|
```
|
||
|
dees-appui-base
|
||
|
├── dees-appui-appbar (top menu bar)
|
||
|
├── dees-appui-mainmenu (left sidebar - primary navigation)
|
||
|
├── dees-appui-mainselector (second sidebar - contextual navigation)
|
||
|
├── dees-appui-maincontent (main content area)
|
||
|
│ └── dees-appui-view (view container)
|
||
|
│ └── dees-appui-tabs (tab navigation within views)
|
||
|
└── dees-appui-activitylog (right sidebar - optional)
|
||
|
```
|
||
|
|
||
|
### View-Based Architecture
|
||
|
|
||
|
The system is built around the concept of **Views** - self-contained modules that represent different sections of your application. Each view can have:
|
||
|
|
||
|
- Its own tabs for sub-navigation
|
||
|
- Menu items for the selector (contextual navigation)
|
||
|
- Content areas with dynamic loading
|
||
|
- State management
|
||
|
- Event handling
|
||
|
|
||
|
## Implementation Plan
|
||
|
|
||
|
### Phase 1: Application Shell Setup
|
||
|
|
||
|
```typescript
|
||
|
// app-shell.ts
|
||
|
import { LitElement, html, css } from 'lit';
|
||
|
import { customElement, property } from 'lit/decorators.js';
|
||
|
import type { IAppView } from '@design.estate/dees-catalog';
|
||
|
|
||
|
@customElement('my-app-shell')
|
||
|
export class MyAppShell extends LitElement {
|
||
|
@property({ type: Array })
|
||
|
views: IAppView[] = [];
|
||
|
|
||
|
@property({ type: String })
|
||
|
activeViewId: string = '';
|
||
|
|
||
|
render() {
|
||
|
const activeView = this.views.find(v => v.id === this.activeViewId);
|
||
|
|
||
|
return html`
|
||
|
<dees-appui-base
|
||
|
.appbarMenuItems=${this.getAppBarMenuItems()}
|
||
|
.appbarBreadcrumbs=${this.getBreadcrumbs()}
|
||
|
.appbarTheme=${'dark'}
|
||
|
.appbarUser=${{ name: 'User', status: 'online' }}
|
||
|
.mainmenuTabs=${this.getMainMenuTabs()}
|
||
|
.mainselectorOptions=${activeView?.menuItems || []}
|
||
|
@mainmenu-tab-select=${this.handleMainMenuSelect}
|
||
|
@mainselector-option-select=${this.handleSelectorSelect}
|
||
|
>
|
||
|
<dees-appui-view
|
||
|
slot="maincontent"
|
||
|
.viewConfig=${activeView}
|
||
|
@view-tab-select=${this.handleViewTabSelect}
|
||
|
></dees-appui-view>
|
||
|
</dees-appui-base>
|
||
|
`;
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Phase 2: View Definition
|
||
|
|
||
|
```typescript
|
||
|
// views/dashboard-view.ts
|
||
|
export const dashboardView: IAppView = {
|
||
|
id: 'dashboard',
|
||
|
name: 'Dashboard',
|
||
|
description: 'System overview and metrics',
|
||
|
iconName: 'home',
|
||
|
tabs: [
|
||
|
{
|
||
|
key: 'overview',
|
||
|
iconName: 'chart-line',
|
||
|
action: () => console.log('Overview selected'),
|
||
|
content: () => html`
|
||
|
<dashboard-overview></dashboard-overview>
|
||
|
`
|
||
|
},
|
||
|
{
|
||
|
key: 'metrics',
|
||
|
iconName: 'tachometer-alt',
|
||
|
action: () => console.log('Metrics selected'),
|
||
|
content: () => html`
|
||
|
<dashboard-metrics></dashboard-metrics>
|
||
|
`
|
||
|
},
|
||
|
{
|
||
|
key: 'alerts',
|
||
|
iconName: 'bell',
|
||
|
action: () => console.log('Alerts selected'),
|
||
|
content: () => html`
|
||
|
<dashboard-alerts></dashboard-alerts>
|
||
|
`
|
||
|
}
|
||
|
],
|
||
|
menuItems: [
|
||
|
{ key: 'Time Range', action: () => showTimeRangeSelector() },
|
||
|
{ key: 'Refresh Rate', action: () => showRefreshSettings() },
|
||
|
{ key: 'Export Data', action: () => exportDashboardData() }
|
||
|
]
|
||
|
};
|
||
|
```
|
||
|
|
||
|
### Phase 3: View Management System
|
||
|
|
||
|
```typescript
|
||
|
// services/view-manager.ts
|
||
|
export class ViewManager {
|
||
|
private views: Map<string, IAppView> = new Map();
|
||
|
private activeView: IAppView | null = null;
|
||
|
private viewCache: Map<string, any> = new Map();
|
||
|
|
||
|
registerView(view: IAppView) {
|
||
|
this.views.set(view.id, view);
|
||
|
}
|
||
|
|
||
|
async activateView(viewId: string) {
|
||
|
const view = this.views.get(viewId);
|
||
|
if (!view) throw new Error(`View ${viewId} not found`);
|
||
|
|
||
|
// Deactivate current view
|
||
|
if (this.activeView) {
|
||
|
await this.deactivateView(this.activeView.id);
|
||
|
}
|
||
|
|
||
|
// Activate new view
|
||
|
this.activeView = view;
|
||
|
|
||
|
// Update navigation
|
||
|
this.updateMainSelector(view.menuItems);
|
||
|
this.updateBreadcrumbs(view);
|
||
|
|
||
|
// Load view data if needed
|
||
|
if (!this.viewCache.has(viewId)) {
|
||
|
await this.loadViewData(view);
|
||
|
}
|
||
|
|
||
|
return view;
|
||
|
}
|
||
|
|
||
|
private async loadViewData(view: IAppView) {
|
||
|
// Implement lazy loading of view data
|
||
|
const viewData = await import(`./views/${view.id}/data.js`);
|
||
|
this.viewCache.set(view.id, viewData);
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Phase 4: Navigation Integration
|
||
|
|
||
|
```typescript
|
||
|
// navigation/app-navigation.ts
|
||
|
export class AppNavigation {
|
||
|
constructor(
|
||
|
private viewManager: ViewManager,
|
||
|
private appShell: MyAppShell
|
||
|
) {}
|
||
|
|
||
|
setupMainMenu(): ITab[] {
|
||
|
return [
|
||
|
{
|
||
|
key: 'dashboard',
|
||
|
iconName: 'home',
|
||
|
action: () => this.navigateToView('dashboard')
|
||
|
},
|
||
|
{
|
||
|
key: 'projects',
|
||
|
iconName: 'folder',
|
||
|
action: () => this.navigateToView('projects')
|
||
|
},
|
||
|
{
|
||
|
key: 'analytics',
|
||
|
iconName: 'chart-bar',
|
||
|
action: () => this.navigateToView('analytics')
|
||
|
},
|
||
|
{
|
||
|
key: 'settings',
|
||
|
iconName: 'cog',
|
||
|
action: () => this.navigateToView('settings')
|
||
|
}
|
||
|
];
|
||
|
}
|
||
|
|
||
|
async navigateToView(viewId: string) {
|
||
|
const view = await this.viewManager.activateView(viewId);
|
||
|
this.appShell.activeViewId = viewId;
|
||
|
|
||
|
// Update URL
|
||
|
window.history.pushState(
|
||
|
{ viewId },
|
||
|
view.name,
|
||
|
`/${viewId}`
|
||
|
);
|
||
|
}
|
||
|
|
||
|
handleBrowserNavigation() {
|
||
|
window.addEventListener('popstate', (event) => {
|
||
|
if (event.state?.viewId) {
|
||
|
this.navigateToView(event.state.viewId);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Phase 5: Dynamic View Loading
|
||
|
|
||
|
```typescript
|
||
|
// views/view-loader.ts
|
||
|
export class ViewLoader {
|
||
|
private loadedViews: Set<string> = new Set();
|
||
|
|
||
|
async loadView(viewId: string): Promise<IAppView> {
|
||
|
if (this.loadedViews.has(viewId)) {
|
||
|
return this.getViewConfig(viewId);
|
||
|
}
|
||
|
|
||
|
// Dynamic import
|
||
|
const viewModule = await import(`./views/${viewId}/index.js`);
|
||
|
const viewConfig = viewModule.default as IAppView;
|
||
|
|
||
|
// Register custom elements if needed
|
||
|
if (viewModule.registerElements) {
|
||
|
await viewModule.registerElements();
|
||
|
}
|
||
|
|
||
|
this.loadedViews.add(viewId);
|
||
|
return viewConfig;
|
||
|
}
|
||
|
|
||
|
async preloadViews(viewIds: string[]) {
|
||
|
const promises = viewIds.map(id => this.loadView(id));
|
||
|
await Promise.all(promises);
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Best Practices
|
||
|
|
||
|
### 1. View Organization
|
||
|
|
||
|
```
|
||
|
src/
|
||
|
├── views/
|
||
|
│ ├── dashboard/
|
||
|
│ │ ├── index.ts # View configuration
|
||
|
│ │ ├── data.ts # Data fetching/management
|
||
|
│ │ ├── components/ # View-specific components
|
||
|
│ │ │ ├── dashboard-overview.ts
|
||
|
│ │ │ ├── dashboard-metrics.ts
|
||
|
│ │ │ └── dashboard-alerts.ts
|
||
|
│ │ └── styles.ts # View-specific styles
|
||
|
│ ├── projects/
|
||
|
│ │ └── ...
|
||
|
│ └── settings/
|
||
|
│ └── ...
|
||
|
├── services/
|
||
|
│ ├── view-manager.ts
|
||
|
│ ├── navigation.ts
|
||
|
│ └── state-manager.ts
|
||
|
└── app-shell.ts
|
||
|
```
|
||
|
|
||
|
### 2. State Management
|
||
|
|
||
|
```typescript
|
||
|
// services/state-manager.ts
|
||
|
export class StateManager {
|
||
|
private viewStates: Map<string, any> = new Map();
|
||
|
|
||
|
saveViewState(viewId: string, state: any) {
|
||
|
this.viewStates.set(viewId, {
|
||
|
...this.getViewState(viewId),
|
||
|
...state,
|
||
|
lastUpdated: Date.now()
|
||
|
});
|
||
|
}
|
||
|
|
||
|
getViewState(viewId: string): any {
|
||
|
return this.viewStates.get(viewId) || {};
|
||
|
}
|
||
|
|
||
|
// Persist to localStorage
|
||
|
persistState() {
|
||
|
const serialized = JSON.stringify(
|
||
|
Array.from(this.viewStates.entries())
|
||
|
);
|
||
|
localStorage.setItem('app-state', serialized);
|
||
|
}
|
||
|
|
||
|
restoreState() {
|
||
|
const saved = localStorage.getItem('app-state');
|
||
|
if (saved) {
|
||
|
const entries = JSON.parse(saved);
|
||
|
this.viewStates = new Map(entries);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 3. View Communication
|
||
|
|
||
|
```typescript
|
||
|
// events/view-events.ts
|
||
|
export class ViewEventBus {
|
||
|
private eventTarget = new EventTarget();
|
||
|
|
||
|
emit(eventName: string, detail: any) {
|
||
|
this.eventTarget.dispatchEvent(
|
||
|
new CustomEvent(eventName, { detail })
|
||
|
);
|
||
|
}
|
||
|
|
||
|
on(eventName: string, handler: (detail: any) => void) {
|
||
|
this.eventTarget.addEventListener(eventName, (e: CustomEvent) => {
|
||
|
handler(e.detail);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Cross-view communication
|
||
|
sendMessage(fromView: string, toView: string, message: any) {
|
||
|
this.emit('view-message', {
|
||
|
from: fromView,
|
||
|
to: toView,
|
||
|
message
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 4. Responsive Design
|
||
|
|
||
|
```typescript
|
||
|
// views/responsive-view.ts
|
||
|
export const createResponsiveView = (config: IAppView): IAppView => {
|
||
|
return {
|
||
|
...config,
|
||
|
tabs: config.tabs.map(tab => ({
|
||
|
...tab,
|
||
|
content: () => html`
|
||
|
<div class="view-content ${getDeviceClass()}">
|
||
|
${tab.content()}
|
||
|
</div>
|
||
|
`
|
||
|
}))
|
||
|
};
|
||
|
};
|
||
|
|
||
|
function getDeviceClass(): string {
|
||
|
const width = window.innerWidth;
|
||
|
if (width < 768) return 'mobile';
|
||
|
if (width < 1024) return 'tablet';
|
||
|
return 'desktop';
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 5. Performance Optimization
|
||
|
|
||
|
```typescript
|
||
|
// optimization/lazy-components.ts
|
||
|
export const lazyComponent = (
|
||
|
importFn: () => Promise<any>,
|
||
|
componentName: string
|
||
|
) => {
|
||
|
let loaded = false;
|
||
|
|
||
|
return () => {
|
||
|
if (!loaded) {
|
||
|
importFn().then(() => {
|
||
|
loaded = true;
|
||
|
});
|
||
|
return html`<dees-spinner></dees-spinner>`;
|
||
|
}
|
||
|
|
||
|
return html`<${componentName}></${componentName}>`;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// Usage in view
|
||
|
tabs: [
|
||
|
{
|
||
|
key: 'heavy-component',
|
||
|
content: lazyComponent(
|
||
|
() => import('./components/heavy-component.js'),
|
||
|
'heavy-component'
|
||
|
)
|
||
|
}
|
||
|
]
|
||
|
```
|
||
|
|
||
|
## Advanced Features
|
||
|
|
||
|
### 1. View Permissions
|
||
|
|
||
|
```typescript
|
||
|
interface IAppViewWithPermissions extends IAppView {
|
||
|
requiredPermissions?: string[];
|
||
|
visibleTo?: (user: User) => boolean;
|
||
|
}
|
||
|
|
||
|
class PermissionManager {
|
||
|
canAccessView(view: IAppViewWithPermissions, user: User): boolean {
|
||
|
if (view.visibleTo) {
|
||
|
return view.visibleTo(user);
|
||
|
}
|
||
|
|
||
|
if (view.requiredPermissions) {
|
||
|
return view.requiredPermissions.every(
|
||
|
perm => user.permissions.includes(perm)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 2. View Lifecycle Hooks
|
||
|
|
||
|
```typescript
|
||
|
interface IAppViewLifecycle extends IAppView {
|
||
|
onActivate?: () => Promise<void>;
|
||
|
onDeactivate?: () => Promise<void>;
|
||
|
onTabChange?: (oldTab: string, newTab: string) => void;
|
||
|
onDestroy?: () => void;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 3. Dynamic Menu Generation
|
||
|
|
||
|
```typescript
|
||
|
class DynamicMenuBuilder {
|
||
|
buildMainMenu(views: IAppView[], user: User): ITab[] {
|
||
|
return views
|
||
|
.filter(view => this.canShowInMenu(view, user))
|
||
|
.map(view => ({
|
||
|
key: view.id,
|
||
|
iconName: view.iconName || 'file',
|
||
|
action: () => this.navigation.navigateToView(view.id)
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
buildSelectorMenu(view: IAppView, context: any): ISelectionOption[] {
|
||
|
const baseItems = view.menuItems || [];
|
||
|
const contextItems = this.getContextualItems(view, context);
|
||
|
|
||
|
return [...baseItems, ...contextItems];
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Migration Strategy
|
||
|
|
||
|
For existing applications:
|
||
|
|
||
|
1. **Identify Views**: Map existing routes/pages to views
|
||
|
2. **Extract Components**: Move page-specific components into view folders
|
||
|
3. **Define View Configs**: Create IAppView configurations
|
||
|
4. **Update Navigation**: Replace existing routing with view navigation
|
||
|
5. **Migrate State**: Move page state to ViewManager
|
||
|
6. **Test & Optimize**: Ensure smooth transitions and performance
|
||
|
|
||
|
## Example Application Structure
|
||
|
|
||
|
```typescript
|
||
|
// main.ts
|
||
|
import { ViewManager } from './services/view-manager.js';
|
||
|
import { AppNavigation } from './services/navigation.js';
|
||
|
import { dashboardView } from './views/dashboard/index.js';
|
||
|
import { projectsView } from './views/projects/index.js';
|
||
|
import { settingsView } from './views/settings/index.js';
|
||
|
|
||
|
const app = new MyAppShell();
|
||
|
const viewManager = new ViewManager();
|
||
|
const navigation = new AppNavigation(viewManager, app);
|
||
|
|
||
|
// Register views
|
||
|
viewManager.registerView(dashboardView);
|
||
|
viewManager.registerView(projectsView);
|
||
|
viewManager.registerView(settingsView);
|
||
|
|
||
|
// Setup navigation
|
||
|
app.views = [dashboardView, projectsView, settingsView];
|
||
|
navigation.setupMainMenu();
|
||
|
navigation.handleBrowserNavigation();
|
||
|
|
||
|
// Initial navigation
|
||
|
navigation.navigateToView('dashboard');
|
||
|
|
||
|
document.body.appendChild(app);
|
||
|
```
|
||
|
|
||
|
This architecture provides:
|
||
|
- **Modularity**: Each view is self-contained
|
||
|
- **Scalability**: Easy to add new views
|
||
|
- **Performance**: Lazy loading and caching
|
||
|
- **Consistency**: Unified navigation and layout
|
||
|
- **Flexibility**: Customizable per view
|
||
|
- **Maintainability**: Clear separation of concerns
|