import { demoFunc } from './dees-simple-appdash.demo.js'; import { customElement, html, DeesElement, property, type TemplateResult, cssManager, css, unsafeCSS, type CSSResult, state, domtools, } from '@design.estate/dees-element'; import './dees-icon.js'; import type { DeesTerminal } from './dees-terminal.js'; declare global { interface HTMLElementTagNameMap { 'dees-simple-appdash': DeesSimpleAppDash; } } export interface IView { name: string; iconName?: string; element: DeesElement['constructor']['prototype']; } @customElement('dees-simple-appdash') export class DeesSimpleAppDash extends DeesElement { // STATIC public static demo = demoFunc; // INSTANCE @property() public name: string = 'Application Dashboard'; @property({ type: Array }) public viewTabs: IView[] = []; @property({ type: String }) public terminalSetupCommand: string = `echo "Terminal ready"`; @state() private selectedView: IView; public static styles = [ cssManager.defaultStyles, css` :host { color: ${cssManager.bdTheme('#333', '#ccc')}; user-select: none; display: block; overflow: hidden; position: relative; height: 100%; width: 100%; } .maincontainer { position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px; overflow: hidden; } .appbar { position: absolute; top: 0px; left: 0px; height: calc(100% - 24px); width: 240px; background: ${cssManager.bdTheme('#fafafa', '#000')}; border-right: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')}; font-size: 12px; font-family: 'Geist Sans', sans-serif; z-index: 2; display: grid; grid-template-rows: auto min-content; overflow: hidden; } .sidebar-header { padding: 16px 12px; border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')}; display: flex; align-items: center; gap: 8px; } .appName { font-size: 14px; font-weight: 600; color: ${cssManager.bdTheme('#000', '#fff')}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .viewTabs-container { flex: 1; overflow-y: auto; padding: 4px 0; } .viewTabs { display: flex; flex-direction: column; } .viewTab { display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: default; transition: background 0.1s; color: ${cssManager.bdTheme('#333', '#ccc')}; user-select: none; position: relative; } .viewTab:hover { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.08)')}; } .viewTab:active { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')}; } .viewTab.selected { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')}; color: ${cssManager.bdTheme('#000', '#fff')}; font-weight: 500; } .viewTab.selected::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: ${cssManager.bdTheme('#26a69a', '#26a69a')}; } .viewTab dees-icon { font-size: 14px; opacity: 0.7; } .appActions { padding: 12px; border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')}; } .action { display: flex; align-items: center; gap: 8px; padding: 8px; border-radius: 4px; cursor: default; transition: background 0.1s; color: ${cssManager.bdTheme('#333', '#ccc')}; } .action:hover { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.08)')}; } .action dees-icon { font-size: 14px; opacity: 0.7; } .appcontent { z-index: 1; position: absolute; top: 0px; right: 0px; height: calc(100% - 24px); bottom: 24px; width: calc(100% - 240px); overflow: auto; background: ${cssManager.bdTheme('#f5f5f5', '#000')}; overscroll-behavior: contain; } .controlbar { color: #fff; position: absolute; bottom: 0px; left: 0px; width: 100%; border-top: 1px solid ${cssManager.bdTheme('#00000020', '#ffffff20')}; height: 24px; background: ${cssManager.bdTheme('#2196f3', '#1565c0')}; z-index: 2; display: flex; justify-content: flex-end; align-items: center; flex-direction: row; font-size: 12px; } .control { display: flex; align-items: center; gap: 4px; margin-right: 16px; white-space: nowrap; cursor: default; opacity: 0.8; transition: opacity 0.2s; } .control:hover { opacity: 1; } .control dees-icon { font-size: 14px; } `, ]; public render(): TemplateResult { return html`
${this.viewTabs.map( (view) => html`
this.loadView(view)} > ${view.iconName ? html` ` : ''} ${view.name}
` )}
{ this.dispatchEvent(new CustomEvent('logout', { bubbles: true, composed: true })); }}> Logout
Connected
Terminal
`; } public async firstUpdated(_changedProperties): Promise { const domtools = await this.domtoolsPromise; super.firstUpdated(_changedProperties); if (this.viewTabs && this.viewTabs.length > 0) { await this.loadView(this.viewTabs[0]); } } public currentTerminal: DeesTerminal; public async launchTerminal() { const domtools = await this.domtoolsPromise; if (this.currentTerminal) { // If terminal already exists, remove it await this.closeTerminal(); return; } const maincontainer = this.shadowRoot.querySelector('.maincontainer'); const { DeesTerminal } = await import('./dees-terminal.js'); const terminal = new DeesTerminal(); terminal.setupCommand = this.terminalSetupCommand; this.currentTerminal = terminal; maincontainer.appendChild(terminal); terminal.style.position = 'absolute'; terminal.style.zIndex = '10'; terminal.style.top = '0px'; terminal.style.left = '240px'; terminal.style.right = '0px'; terminal.style.bottom = '24px'; terminal.style.opacity = '0'; terminal.style.transform = 'translateY(20px)'; terminal.style.transition = 'all 0.2s'; terminal.style.background = '#000'; terminal.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)'; // Add close button to terminal terminal.addEventListener('close', () => this.closeTerminal()); await domtools.convenience.smartdelay.delayFor(0); terminal.style.opacity = '1'; terminal.style.transform = 'translateY(0px)'; return terminal; } private async closeTerminal() { const domtools = await this.domtoolsPromise; if (this.currentTerminal) { this.currentTerminal.style.opacity = '0'; this.currentTerminal.style.transform = 'translateY(20px)'; await domtools.convenience.smartdelay.delayFor(200); this.currentTerminal.remove(); this.currentTerminal = null; } } private currentView: DeesElement; public async loadView(viewArg: IView) { const appcontent = this.shadowRoot.querySelector('.appcontent'); const view = new viewArg.element(); if (this.currentView) { this.currentView.remove(); } appcontent.appendChild(view); this.currentView = view; this.selectedView = viewArg; // Emit view-select event this.dispatchEvent(new CustomEvent('view-select', { detail: { view: viewArg }, bubbles: true, composed: true })); } }