Files
eco_os/ecoos_daemon/ts_web/elements/ecoos-displays.ts
2026-01-12 14:34:56 +00:00

234 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* EcoOS Displays View
* Card-based display management
*/
import {
html,
DeesElement,
customElement,
property,
state,
css,
type TemplateResult,
} from '@design.estate/dees-element';
import { DeesButton, DeesPanel, DeesBadge } from '@design.estate/dees-catalog';
import { sharedStyles } from '../styles/shared.js';
import type { IDisplayInfo } from '../../ts_interfaces/display.js';
@customElement('ecoos-displays')
export class EcoosDisplays extends DeesElement {
@property({ type: Array })
public accessor displays: IDisplayInfo[] = [];
@state()
private accessor loading: boolean = false;
@state()
private accessor message: string = '';
@state()
private accessor messageError: boolean = false;
public static styles = [
sharedStyles,
css`
:host {
display: block;
padding: 16px;
}
.display-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
}
.display-card {
opacity: 1;
transition: opacity 0.2s ease;
}
.display-card.disabled {
opacity: 0.5;
}
.display-meta {
font-size: var(--text-xs);
color: var(--text-tertiary);
margin-bottom: 12px;
}
.badge-row {
margin-bottom: 12px;
}
.actions-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.message-bar {
margin-top: 16px;
padding: 8px 12px;
border-radius: 6px;
font-size: var(--text-sm);
}
.message-bar.success {
background: hsla(142.1, 76.2%, 36.3%, 0.15);
color: var(--success);
}
.message-bar.error {
background: hsla(0, 84.2%, 60.2%, 0.15);
color: var(--error);
}
.empty-state {
text-align: center;
padding: 32px;
color: var(--text-tertiary);
}
.disabled-section {
margin-top: 16px;
}
.disabled-header {
font-size: var(--text-sm);
color: var(--text-tertiary);
margin-bottom: 12px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
}
.disabled-header::before {
content: '▶';
font-size: 8px;
transition: transform 150ms ease;
}
.disabled-header.open::before {
transform: rotate(90deg);
}
`,
];
render(): TemplateResult {
const enabledDisplays = this.displays.filter(d => d.active);
const disabledDisplays = this.displays.filter(d => !d.active);
if (this.displays.length === 0) {
return html`
<div class="empty-state">
No displays detected
</div>
`;
}
return html`
<div class="display-grid">
${enabledDisplays.map(d => this.renderDisplayCard(d))}
</div>
${disabledDisplays.length > 0 ? html`
<details class="disabled-section">
<summary class="disabled-header">
Disabled Displays (${disabledDisplays.length})
</summary>
<div class="display-grid" style="margin-top: 12px;">
${disabledDisplays.map(d => this.renderDisplayCard(d))}
</div>
</details>
` : ''}
${this.message ? html`
<div class="message-bar ${this.messageError ? 'error' : 'success'}">${this.message}</div>
` : ''}
`;
}
private renderDisplayCard(display: IDisplayInfo): TemplateResult {
return html`
<dees-panel
class="display-card ${display.active ? '' : 'disabled'}"
.title=${display.name}
.subtitle=${`${display.width}×${display.height} @ ${display.refreshRate}Hz`}
.variant=${display.active ? 'default' : 'ghost'}
>
${display.make && display.make !== 'Unknown' ? html`
<div class="display-meta">${display.make}${display.model ? ` ${display.model}` : ''}</div>
` : ''}
${display.isPrimary ? html`
<div class="badge-row">
<dees-badge .type=${'primary'}>Primary</dees-badge>
</div>
` : ''}
<div class="actions-row">
${display.active && !display.isPrimary ? html`
<dees-button
.type=${'default'}
.text=${'Set Primary'}
.disabled=${this.loading}
@click=${() => this.setPrimary(display.name)}
></dees-button>
` : ''}
<dees-button
.type=${'default'}
.status=${display.active ? 'error' : 'success'}
.text=${display.active ? 'Disable' : 'Enable'}
.disabled=${this.loading}
@click=${() => this.toggleDisplay(display.name, !display.active)}
></dees-button>
</div>
</dees-panel>
`;
}
private async toggleDisplay(name: string, enable: boolean): Promise<void> {
this.loading = true;
this.message = '';
try {
const action = enable ? 'enable' : 'disable';
const response = await fetch(`/api/displays/${encodeURIComponent(name)}/${action}`, {
method: 'POST',
});
const result = await response.json();
this.message = result.message;
this.messageError = !result.success;
this.dispatchEvent(new CustomEvent('refresh-displays'));
} catch (error) {
this.message = `Error: ${error}`;
this.messageError = true;
} finally {
this.loading = false;
}
}
private async setPrimary(name: string): Promise<void> {
this.loading = true;
this.message = '';
try {
const response = await fetch(`/api/displays/${encodeURIComponent(name)}/primary`, {
method: 'POST',
});
const result = await response.json();
this.message = result.message;
this.messageError = !result.success;
this.dispatchEvent(new CustomEvent('refresh-displays'));
} catch (error) {
this.message = `Error: ${error}`;
this.messageError = true;
} finally {
this.loading = false;
}
}
}