This commit is contained in:
2026-01-12 10:57:54 +00:00
parent c55cd25a88
commit 72900086cd
63 changed files with 3963 additions and 5078 deletions

View File

@@ -14,6 +14,9 @@ import { EcoApplauncherWifimenu, type IWifiNetwork } from '../eco-applauncher-wi
import { EcoApplauncherBatterymenu } from '../eco-applauncher-batterymenu/index.js';
import { EcoApplauncherSoundmenu, type IAudioDevice } from '../eco-applauncher-soundmenu/index.js';
import { EcoApplauncherKeyboard } from '../eco-applauncher-keyboard/index.js';
import { EcoApplauncherPowermenu, type TPowerAction } from '../eco-applauncher-powermenu/index.js';
import { EcoViewHome } from '../../../views/eco-view-home/index.js';
import { EcoViewLogin, type ILoginConfig, type ILoginCredentials } from '../../../views/eco-view-login/index.js';
// Ensure components are registered
DeesIcon;
@@ -21,6 +24,9 @@ EcoApplauncherWifimenu;
EcoApplauncherBatterymenu;
EcoApplauncherSoundmenu;
EcoApplauncherKeyboard;
EcoApplauncherPowermenu;
EcoViewHome;
EcoViewLogin;
declare global {
interface HTMLElementTagNameMap {
@@ -28,12 +34,18 @@ declare global {
}
}
export type TApplauncherMode = 'login' | 'home';
export interface IAppIcon {
name: string;
icon: string;
action?: () => void;
view?: TemplateResult;
}
export type { ILoginConfig, ILoginCredentials } from '../../../views/eco-view-login/index.js';
export type { TPowerAction } from '../eco-applauncher-powermenu/index.js';
export interface IStatusBarConfig {
showTime?: boolean;
showNetwork?: boolean;
@@ -153,6 +165,10 @@ export class EcoApplauncher extends DeesElement {
background: ${cssManager.bdTheme('hsl(220 15% 88%)', 'hsl(240 5% 15%)')};
}
.top-icon-button.active {
background: ${cssManager.bdTheme('hsl(220 15% 85%)', 'hsl(240 5% 18%)')};
}
.user-avatar {
width: 32px;
height: 32px;
@@ -195,9 +211,6 @@ export class EcoApplauncher extends DeesElement {
.apps-area {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 48px;
overflow-y: auto;
}
@@ -206,7 +219,6 @@ export class EcoApplauncher extends DeesElement {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 32px;
max-width: 800px;
width: 100%;
}
@@ -415,6 +427,18 @@ export class EcoApplauncher extends DeesElement {
pointer-events: none;
}
.topbar-menu-wrapper {
position: relative;
}
.topbar-menu-popup {
position: absolute;
top: calc(100% + 8px);
right: 0;
z-index: 100;
pointer-events: none;
}
.keyboard-area {
flex-shrink: 0;
height: 0;
@@ -430,6 +454,53 @@ export class EcoApplauncher extends DeesElement {
overflow: visible;
}
.view-area {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.view-header {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 24px;
background: ${cssManager.bdTheme('hsl(220 15% 94%)', 'hsl(240 6% 8%)')};
border-bottom: 1px solid ${cssManager.bdTheme('hsl(220 15% 88%)', 'hsl(240 5% 15%)')};
flex-shrink: 0;
}
.back-button {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 8px 14px;
background: ${cssManager.bdTheme('hsl(220 15% 88%)', 'hsl(240 5% 14%)')};
border-radius: 8px;
cursor: pointer;
transition: background 0.15s ease;
color: ${cssManager.bdTheme('hsl(0 0% 30%)', 'hsl(0 0% 80%)')};
font-size: 13px;
font-weight: 500;
}
.back-button:hover {
background: ${cssManager.bdTheme('hsl(220 15% 82%)', 'hsl(240 5% 18%)')};
}
.view-title {
font-size: 16px;
font-weight: 600;
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
}
.view-content {
flex: 1;
overflow: auto;
}
@media (max-width: 600px) {
.apps-area {
padding: 24px;
@@ -462,6 +533,16 @@ export class EcoApplauncher extends DeesElement {
`,
];
@property({ type: String })
accessor mode: TApplauncherMode = 'home';
@property({ type: Object })
accessor loginConfig: ILoginConfig = {
allowedMethods: ['pin', 'password', 'qr'],
pinLength: 4,
welcomeMessage: 'Welcome',
};
@property({ type: Array })
accessor apps: IAppIcon[] = [];
@@ -512,9 +593,18 @@ export class EcoApplauncher extends DeesElement {
@state()
accessor soundMenuOpen = false;
@state()
accessor powerMenuOpen = false;
@state()
accessor keyboardVisible = false;
@state()
accessor activeView: TemplateResult | null = null;
@state()
accessor activeViewName: string | null = null;
@property({ type: Array })
accessor networks: IWifiNetwork[] = [];
@@ -552,12 +642,8 @@ export class EcoApplauncher extends DeesElement {
public render(): TemplateResult {
return html`
<div class="launcher-container">
${this.renderTopBar()}
<div class="apps-area">
<div class="apps-grid">
${this.apps.map((app) => this.renderAppIcon(app))}
</div>
</div>
${this.mode === 'login' ? '' : this.renderTopBar()}
${this.renderMainContent()}
<div class="keyboard-area ${this.keyboardVisible ? 'visible' : ''}">
<eco-applauncher-keyboard
?visible=${this.keyboardVisible}
@@ -585,6 +671,58 @@ export class EcoApplauncher extends DeesElement {
`;
}
private renderMainContent(): TemplateResult {
if (this.mode === 'login') {
return this.renderLoginView();
}
// Home mode
if (this.activeView) {
return this.renderActiveView();
}
return this.renderHomeView();
}
private renderLoginView(): TemplateResult {
return html`
<eco-view-login
.config=${this.loginConfig}
@login-attempt=${this.handleLoginAttempt}
@key-press=${this.handleKeyboardKeyPress}
@backspace=${this.handleKeyboardBackspace}
style="flex: 1;"
></eco-view-login>
`;
}
private renderHomeView(): TemplateResult {
return html`
<eco-view-home
.apps=${this.apps}
@app-click=${this.handleHomeAppClick}
></eco-view-home>
`;
}
private renderAppsArea(): TemplateResult {
return html`
<div class="apps-area">
<div class="apps-grid">
${this.apps.map((app) => this.renderAppIcon(app))}
</div>
</div>
`;
}
private renderActiveView(): TemplateResult {
return html`
<div class="view-area">
<div class="view-content">
${this.activeView}
</div>
</div>
`;
}
private renderAppIcon(app: IAppIcon): TemplateResult {
return html`
<div class="app-icon" @click=${() => this.handleAppClick(app)}>
@@ -607,7 +745,13 @@ export class EcoApplauncher extends DeesElement {
return html`
<div class="top-bar">
<div class="top-left">
${this.topBarConfig.showDate ? html`
${this.activeView ? html`
<div class="back-button" @click=${this.handleBackClick}>
<dees-icon .icon=${'lucide:arrowLeft'} .iconSize=${16}></dees-icon>
<span>Back</span>
</div>
<span class="view-title">${this.activeViewName}</span>
` : this.topBarConfig.showDate ? html`
<span class="top-date">${this.currentDate}</span>
` : ''}
</div>
@@ -636,6 +780,21 @@ export class EcoApplauncher extends DeesElement {
${userInitials}
</div>
` : ''}
<div class="topbar-menu-wrapper">
<div
class="top-icon-button ${this.powerMenuOpen ? 'active' : ''}"
@click=${this.handlePowerClick}
>
<dees-icon .icon=${'lucide:power'} .iconSize=${18}></dees-icon>
</div>
<div class="topbar-menu-popup">
<eco-applauncher-powermenu
?open=${this.powerMenuOpen}
@menu-close=${this.handlePowerMenuClose}
@power-action=${this.handlePowerAction}
></eco-applauncher-powermenu>
</div>
</div>
</div>
</div>
`;
@@ -831,15 +990,91 @@ export class EcoApplauncher extends DeesElement {
composed: true,
})
);
// If app has a view, open it inside the applauncher
if (app.view) {
this.activeView = app.view;
this.activeViewName = app.name;
return;
}
// Otherwise execute the action
if (app.action) {
app.action();
}
}
private handleHomeAppClick(e: CustomEvent): void {
const app = e.detail.app as IAppIcon;
this.handleAppClick(app);
}
private handleLoginAttempt(e: CustomEvent): void {
const credentials = e.detail as ILoginCredentials;
// Dispatch event for parent to handle validation
this.dispatchEvent(new CustomEvent('login-attempt', {
detail: credentials,
bubbles: true,
composed: true,
}));
}
/**
* Set the login result after validation
* @param success Whether login was successful
* @param errorMessage Optional error message to display
*/
public setLoginResult(success: boolean, errorMessage?: string): void {
const loginView = this.shadowRoot?.querySelector('eco-view-login') as EcoViewLogin | null;
if (success) {
this.mode = 'home';
this.dispatchEvent(new CustomEvent('login-success', {
bubbles: true,
composed: true,
}));
} else {
if (loginView && errorMessage) {
loginView.showErrorMessage(errorMessage);
}
this.dispatchEvent(new CustomEvent('login-failure', {
detail: { error: errorMessage },
bubbles: true,
composed: true,
}));
}
}
/**
* Switch to login mode
*/
public showLogin(): void {
this.mode = 'login';
this.activeView = null;
this.activeViewName = null;
}
/**
* Switch to home mode
*/
public showHome(): void {
this.mode = 'home';
}
private handleBackClick(): void {
this.activeView = null;
this.activeViewName = null;
this.dispatchEvent(new CustomEvent('view-close', {
bubbles: true,
composed: true,
}));
}
private handleNetworkClick(e: MouseEvent): void {
e.stopPropagation();
this.batteryMenuOpen = false;
this.soundMenuOpen = false;
this.powerMenuOpen = false;
this.wifiMenuOpen = !this.wifiMenuOpen;
}
@@ -847,6 +1082,7 @@ export class EcoApplauncher extends DeesElement {
e.stopPropagation();
this.wifiMenuOpen = false;
this.soundMenuOpen = false;
this.powerMenuOpen = false;
this.batteryMenuOpen = !this.batteryMenuOpen;
}
@@ -854,9 +1090,18 @@ export class EcoApplauncher extends DeesElement {
e.stopPropagation();
this.wifiMenuOpen = false;
this.batteryMenuOpen = false;
this.powerMenuOpen = false;
this.soundMenuOpen = !this.soundMenuOpen;
}
private handlePowerClick(e: MouseEvent): void {
e.stopPropagation();
this.wifiMenuOpen = false;
this.batteryMenuOpen = false;
this.soundMenuOpen = false;
this.powerMenuOpen = !this.powerMenuOpen;
}
private handleWifiMenuClose(): void {
this.wifiMenuOpen = false;
}
@@ -869,6 +1114,19 @@ export class EcoApplauncher extends DeesElement {
this.soundMenuOpen = false;
}
private handlePowerMenuClose(): void {
this.powerMenuOpen = false;
}
private handlePowerAction(e: CustomEvent): void {
const action = e.detail.action as TPowerAction;
this.dispatchEvent(new CustomEvent('power-action', {
detail: { action },
bubbles: true,
composed: true,
}));
}
private handleVolumeChange(e: CustomEvent): void {
this.soundLevel = e.detail.volume;
this.dispatchEvent(new CustomEvent('volume-change', {
@@ -953,6 +1211,7 @@ export class EcoApplauncher extends DeesElement {
this.wifiMenuOpen = false;
this.batteryMenuOpen = false;
this.soundMenuOpen = false;
this.powerMenuOpen = false;
}
this.dispatchEvent(new CustomEvent('keyboard-toggle', {
detail: { visible: this.keyboardVisible },