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
`;
}
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
}));
}
}