262 lines
7.1 KiB
TypeScript
262 lines
7.1 KiB
TypeScript
import {
|
|
customElement,
|
|
DeesElement,
|
|
type TemplateResult,
|
|
html,
|
|
property,
|
|
css,
|
|
cssManager,
|
|
} from '@design.estate/dees-element';
|
|
import { DeesIcon } from '@design.estate/dees-catalog';
|
|
import { demo } from './eco-applauncher-powermenu.demo.js';
|
|
|
|
// Ensure dees-icon is registered
|
|
DeesIcon;
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'eco-applauncher-powermenu': EcoApplauncherPowermenu;
|
|
}
|
|
}
|
|
|
|
export type TPowerAction = 'lock' | 'lock-sleep' | 'reboot';
|
|
|
|
@customElement('eco-applauncher-powermenu')
|
|
export class EcoApplauncherPowermenu extends DeesElement {
|
|
public static demo = demo;
|
|
public static demoGroup = 'App Launcher';
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
position: relative;
|
|
pointer-events: none;
|
|
}
|
|
|
|
:host([open]) {
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.menu-container {
|
|
background: ${cssManager.bdTheme('#ffffff', 'hsl(240 6% 10%)')};
|
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(240 5% 20%)')};
|
|
border-radius: 12px;
|
|
box-shadow: ${cssManager.bdTheme(
|
|
'0 8px 32px rgba(0, 0, 0, 0.15)',
|
|
'0 8px 32px rgba(0, 0, 0, 0.4)'
|
|
)};
|
|
min-width: 200px;
|
|
overflow: hidden;
|
|
opacity: 0;
|
|
transform: scale(0.95) translateY(-8px);
|
|
transition: all 0.2s ease-out;
|
|
pointer-events: none;
|
|
}
|
|
|
|
:host([open]) .menu-container {
|
|
opacity: 1;
|
|
transform: scale(1) translateY(0);
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.menu-header {
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(240 5% 15%)')};
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 60%)')};
|
|
}
|
|
|
|
.menu-options {
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.menu-option {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 12px 16px;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease;
|
|
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
|
|
}
|
|
|
|
.menu-option:hover {
|
|
background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(240 5% 15%)')};
|
|
}
|
|
|
|
.menu-option:active {
|
|
background: ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(240 5% 18%)')};
|
|
}
|
|
|
|
.menu-option.danger {
|
|
color: ${cssManager.bdTheme('hsl(0 72% 45%)', 'hsl(0 72% 60%)')};
|
|
}
|
|
|
|
.menu-option.danger:hover {
|
|
background: ${cssManager.bdTheme('hsl(0 72% 97%)', 'hsl(0 50% 15%)')};
|
|
}
|
|
|
|
.option-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 6px;
|
|
background: ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(240 5% 18%)')};
|
|
}
|
|
|
|
.menu-option.danger .option-icon {
|
|
background: ${cssManager.bdTheme('hsl(0 72% 94%)', 'hsl(0 50% 18%)')};
|
|
}
|
|
|
|
.option-text {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
.option-label {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.option-description {
|
|
font-size: 11px;
|
|
color: ${cssManager.bdTheme('hsl(0 0% 55%)', 'hsl(0 0% 50%)')};
|
|
}
|
|
|
|
.menu-divider {
|
|
height: 1px;
|
|
background: ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(240 5% 15%)')};
|
|
margin: 4px 0;
|
|
}
|
|
`,
|
|
];
|
|
|
|
@property({ type: Boolean, reflect: true })
|
|
accessor open = false;
|
|
|
|
private boundHandleClickOutside = this.handleClickOutside.bind(this);
|
|
private inactivityTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
private readonly INACTIVITY_TIMEOUT = 60000; // 1 minute
|
|
private lastActivityTime = 0;
|
|
|
|
public render(): TemplateResult {
|
|
return html`
|
|
<div class="menu-container"
|
|
@click=${(e: MouseEvent) => e.stopPropagation()}
|
|
@mousedown=${this.resetInactivityTimer}
|
|
>
|
|
<div class="menu-header">Power</div>
|
|
<div class="menu-options">
|
|
<div class="menu-option" @click=${() => this.handleAction('lock')}>
|
|
<div class="option-icon">
|
|
<dees-icon .icon=${'lucide:lock'} .iconSize=${16}></dees-icon>
|
|
</div>
|
|
<div class="option-text">
|
|
<span class="option-label">Lock</span>
|
|
<span class="option-description">Lock the screen</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="menu-option" @click=${() => this.handleAction('lock-sleep')}>
|
|
<div class="option-icon">
|
|
<dees-icon .icon=${'lucide:moon'} .iconSize=${16}></dees-icon>
|
|
</div>
|
|
<div class="option-text">
|
|
<span class="option-label">Lock + Sleep</span>
|
|
<span class="option-description">Lock and turn off display</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="menu-divider"></div>
|
|
|
|
<div class="menu-option danger" @click=${() => this.handleAction('reboot')}>
|
|
<div class="option-icon">
|
|
<dees-icon .icon=${'lucide:refreshCw'} .iconSize=${16}></dees-icon>
|
|
</div>
|
|
<div class="option-text">
|
|
<span class="option-label">Reboot</span>
|
|
<span class="option-description">Restart the system</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private handleAction(action: TPowerAction): void {
|
|
this.dispatchEvent(new CustomEvent('power-action', {
|
|
detail: { action },
|
|
bubbles: true,
|
|
composed: true,
|
|
}));
|
|
this.closeMenu();
|
|
}
|
|
|
|
private handleClickOutside(e: MouseEvent): void {
|
|
if (this.open && !this.contains(e.target as Node)) {
|
|
this.closeMenu();
|
|
}
|
|
}
|
|
|
|
private resetInactivityTimer(): void {
|
|
const now = Date.now();
|
|
// Throttle: only reset if 5+ seconds since last reset
|
|
if (now - this.lastActivityTime < 5000) {
|
|
return;
|
|
}
|
|
this.lastActivityTime = now;
|
|
this.clearInactivityTimer();
|
|
if (this.open) {
|
|
this.inactivityTimeout = setTimeout(() => {
|
|
this.closeMenu();
|
|
}, this.INACTIVITY_TIMEOUT);
|
|
}
|
|
}
|
|
|
|
private clearInactivityTimer(): void {
|
|
if (this.inactivityTimeout) {
|
|
clearTimeout(this.inactivityTimeout);
|
|
this.inactivityTimeout = null;
|
|
}
|
|
}
|
|
|
|
private closeMenu(): void {
|
|
this.open = false;
|
|
this.dispatchEvent(new CustomEvent('menu-close', {
|
|
bubbles: true,
|
|
composed: true,
|
|
}));
|
|
}
|
|
|
|
protected updated(changedProperties: Map<string, unknown>): void {
|
|
if (changedProperties.has('open')) {
|
|
if (this.open) {
|
|
this.resetInactivityTimer();
|
|
} else {
|
|
this.clearInactivityTimer();
|
|
}
|
|
}
|
|
}
|
|
|
|
async connectedCallback(): Promise<void> {
|
|
await super.connectedCallback();
|
|
setTimeout(() => {
|
|
document.addEventListener('click', this.boundHandleClickOutside);
|
|
}, 0);
|
|
}
|
|
|
|
async disconnectedCallback(): Promise<void> {
|
|
await super.disconnectedCallback();
|
|
document.removeEventListener('click', this.boundHandleClickOutside);
|
|
this.clearInactivityTimer();
|
|
}
|
|
}
|