Files
eco_os/ecoos_daemon/ts_web/elements/ecoos-displays.ts

203 lines
5.1 KiB
TypeScript
Raw Normal View History

/**
* EcoOS Displays View
* Display management with enable/disable/primary controls
*/
import {
html,
DeesElement,
customElement,
property,
state,
css,
type TemplateResult,
} from '@design.estate/dees-element';
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;
public static styles = [
sharedStyles,
css`
:host {
display: block;
padding: 20px;
}
.display-item {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px 0;
border-bottom: 1px solid var(--ecoos-border);
}
.display-item:last-child {
border-bottom: none;
}
.display-info {
flex: 1;
min-width: 150px;
}
.display-name {
font-weight: 500;
font-size: 14px;
}
.display-details {
font-size: 11px;
color: var(--ecoos-text-dim);
}
.display-actions {
display: flex;
gap: 4px;
align-items: center;
}
.display-actions .btn {
padding: 4px 12px;
margin: 0;
font-size: 11px;
}
.section-header {
font-size: 12px;
color: var(--ecoos-text-dim);
margin: 16px 0 8px 0;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.section-header:hover {
color: var(--ecoos-text);
}
.collapsed-content {
display: none;
}
.collapsed-content.expanded {
display: block;
}
`,
];
render(): TemplateResult {
const enabledDisplays = this.displays.filter(d => d.active);
const disabledDisplays = this.displays.filter(d => !d.active);
return html`
<div class="card">
<div class="card-title">Displays</div>
${this.displays.length === 0
? html`<div style="color: var(--ecoos-text-dim)">No displays detected</div>`
: html`
<!-- Enabled Displays -->
${enabledDisplays.map(d => this.renderDisplayItem(d))}
<!-- Disabled Displays -->
${disabledDisplays.length > 0 ? html`
<details style="margin-top: 12px;">
<summary class="section-header">
Disabled Displays (${disabledDisplays.length})
</summary>
<div style="margin-top: 8px;">
${disabledDisplays.map(d => this.renderDisplayItem(d))}
</div>
</details>
` : ''}
`
}
</div>
`;
}
private renderDisplayItem(display: IDisplayInfo): TemplateResult {
return html`
<div class="display-item">
<div class="display-info">
<div class="display-name">${display.name}</div>
<div class="display-details">
${display.width}x${display.height} @ ${display.refreshRate}Hz
${display.make !== 'Unknown' ? `${display.make}` : ''}
</div>
</div>
<div class="display-actions">
${display.isPrimary
? html`<span class="device-default">Primary</span>`
: display.active
? html`
<button
class="btn btn-primary"
@click=${() => this.setKioskDisplay(display.name)}
?disabled=${this.loading}
>
Set Primary
</button>
`
: ''
}
<button
class="btn ${display.active ? 'btn-danger' : 'btn-primary'}"
@click=${() => this.toggleDisplay(display.name, !display.active)}
?disabled=${this.loading}
>
${display.active ? 'Disable' : 'Enable'}
</button>
</div>
</div>
`;
}
private async toggleDisplay(name: string, enable: boolean): Promise<void> {
this.loading = true;
try {
const action = enable ? 'enable' : 'disable';
const response = await fetch(`/api/displays/${encodeURIComponent(name)}/${action}`, {
method: 'POST',
});
const result = await response.json();
if (!result.success) {
alert(result.message);
}
this.dispatchEvent(new CustomEvent('refresh-displays'));
} catch (error) {
alert(`Error: ${error}`);
} finally {
this.loading = false;
}
}
private async setKioskDisplay(name: string): Promise<void> {
this.loading = true;
try {
const response = await fetch(`/api/displays/${encodeURIComponent(name)}/primary`, {
method: 'POST',
});
const result = await response.json();
if (!result.success) {
alert(result.message);
}
this.dispatchEvent(new CustomEvent('refresh-displays'));
} catch (error) {
alert(`Error: ${error}`);
} finally {
this.loading = false;
}
}
}