import { customElement, DeesElement, type TemplateResult, html, property, css, cssManager, state, } from '@design.estate/dees-element'; import { DeesAppuiSecondarymenu, DeesIcon } from '@design.estate/dees-catalog'; import type { ISecondaryMenuGroup, ISecondaryMenuItem } from '../../elements/interfaces/secondarymenu.js'; import { demo } from './eco-view-peripherals.demo.js'; // Ensure components are registered DeesAppuiSecondarymenu; DeesIcon; declare global { interface HTMLElementTagNameMap { 'eco-view-peripherals': EcoViewPeripherals; } } export type TPeripheralCategory = | 'all' | 'printers' | 'scanners' | 'speakers' | 'storage' | 'power' | 'cameras' | 'streaming' | 'usb'; export type TConnectionType = 'network' | 'usb' | 'bluetooth'; export interface IPeripheralDevice { id: string; name: string; type: TPeripheralCategory; connectionType: TConnectionType; status: 'online' | 'offline' | 'busy' | 'error'; ip?: string; manufacturer?: string; model?: string; isDefault?: boolean; } @customElement('eco-view-peripherals') export class EcoViewPeripherals extends DeesElement { public static demo = demo; public static demoGroup = 'Views'; public static styles = [ cssManager.defaultStyles, css` :host { display: block; width: 100%; height: 100%; background: ${cssManager.bdTheme('#f5f5f7', 'hsl(240 6% 10%)')}; color: ${cssManager.bdTheme('hsl(0 0% 10%)', 'hsl(0 0% 98%)')}; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } .peripherals-container { display: flex; height: 100%; } dees-appui-secondarymenu { flex-shrink: 0; background: ${cssManager.bdTheme('#ffffff', 'hsl(240 6% 8%)')}; border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(240 5% 15%)')}; } .content { flex: 1; overflow-y: auto; padding: 32px 48px; } .panel-header { margin-bottom: 24px; display: flex; align-items: center; justify-content: space-between; } .panel-header-left { display: flex; flex-direction: column; gap: 4px; } .panel-title { font-size: 28px; font-weight: 600; color: ${cssManager.bdTheme('hsl(0 0% 10%)', 'hsl(0 0% 98%)')}; } .panel-description { font-size: 14px; color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 60%)')}; } .scan-button { display: flex; align-items: center; gap: 8px; padding: 10px 20px; background: hsl(217 91% 60%); color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: background 0.15s ease; } .scan-button:hover { background: hsl(217 91% 55%); } .scan-button.scanning { background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(240 5% 25%)')}; cursor: not-allowed; } .scan-button.scanning dees-icon { animation: spin 1s linear infinite; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .device-section { background: ${cssManager.bdTheme('#ffffff', 'hsl(240 6% 12%)')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(240 5% 18%)')}; border-radius: 12px; margin-bottom: 24px; overflow: hidden; } .section-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(240 5% 15%)')}; } .section-title { font-size: 13px; font-weight: 600; color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 50%)')}; text-transform: uppercase; letter-spacing: 0.5px; } .device-count { font-size: 12px; color: ${cssManager.bdTheme('hsl(0 0% 60%)', 'hsl(0 0% 50%)')}; background: ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(240 5% 20%)')}; padding: 2px 8px; border-radius: 10px; } .device-list { padding: 8px 0; } .device-item { display: flex; align-items: center; gap: 16px; padding: 14px 20px; cursor: pointer; transition: background 0.15s ease; } .device-item:hover { background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(240 5% 14%)')}; } .device-icon-wrapper { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; background: ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(240 5% 18%)')}; color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 70%)')}; } .device-icon-wrapper.online { background: ${cssManager.bdTheme('hsl(142 71% 93%)', 'hsl(142 71% 45% / 0.15)')}; color: hsl(142 71% 35%); } .device-icon-wrapper.busy { background: ${cssManager.bdTheme('hsl(47 100% 93%)', 'hsl(47 100% 50% / 0.15)')}; color: hsl(47 100% 40%); } .device-icon-wrapper.error { background: ${cssManager.bdTheme('hsl(0 72% 93%)', 'hsl(0 72% 51% / 0.15)')}; color: hsl(0 72% 45%); } .device-info { flex: 1; min-width: 0; } .device-name { font-size: 15px; font-weight: 500; color: ${cssManager.bdTheme('hsl(0 0% 10%)', 'hsl(0 0% 98%)')}; display: flex; align-items: center; gap: 8px; } .default-badge { font-size: 10px; font-weight: 600; color: hsl(217 91% 60%); background: ${cssManager.bdTheme('hsl(217 91% 95%)', 'hsl(217 91% 60% / 0.15)')}; padding: 2px 6px; border-radius: 4px; text-transform: uppercase; } .device-details { font-size: 13px; color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 55%)')}; margin-top: 2px; display: flex; align-items: center; gap: 12px; } .device-detail { display: flex; align-items: center; gap: 4px; } .status-indicator { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } .status-indicator.online { background: hsl(142 71% 45%); } .status-indicator.offline { background: ${cssManager.bdTheme('hsl(0 0% 70%)', 'hsl(0 0% 40%)')}; } .status-indicator.busy { background: hsl(47 100% 50%); } .status-indicator.error { background: hsl(0 72% 51%); } .device-actions { display: flex; align-items: center; gap: 8px; } .action-button { padding: 8px 12px; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(240 5% 25%)')}; border-radius: 6px; background: transparent; color: ${cssManager.bdTheme('hsl(0 0% 30%)', 'hsl(0 0% 80%)')}; font-size: 13px; cursor: pointer; transition: all 0.15s ease; } .action-button:hover { background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(240 5% 18%)')}; border-color: ${cssManager.bdTheme('hsl(0 0% 75%)', 'hsl(240 5% 35%)')}; } .action-button.primary { background: hsl(217 91% 60%); border-color: hsl(217 91% 60%); color: white; } .action-button.primary:hover { background: hsl(217 91% 55%); } .empty-state { padding: 48px 20px; text-align: center; } .empty-icon { color: ${cssManager.bdTheme('hsl(0 0% 75%)', 'hsl(0 0% 35%)')}; margin-bottom: 16px; } .empty-title { font-size: 16px; font-weight: 500; color: ${cssManager.bdTheme('hsl(0 0% 30%)', 'hsl(0 0% 80%)')}; margin-bottom: 8px; } .empty-description { font-size: 14px; color: ${cssManager.bdTheme('hsl(0 0% 55%)', 'hsl(0 0% 50%)')}; } .connection-type { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; padding: 2px 6px; border-radius: 4px; background: ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(240 5% 20%)')}; color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 60%)')}; } `, ]; @property({ type: String }) accessor activeCategory: TPeripheralCategory = 'all'; @state() accessor isScanning = false; @state() accessor devices: IPeripheralDevice[] = [ // Mock printers { id: 'printer-1', name: 'HP LaserJet Pro', type: 'printers', connectionType: 'network', status: 'online', ip: '192.168.1.50', manufacturer: 'HP', model: 'LaserJet Pro M404n', isDefault: true, }, { id: 'printer-2', name: 'Brother MFC-L2750DW', type: 'printers', connectionType: 'network', status: 'online', ip: '192.168.1.51', manufacturer: 'Brother', model: 'MFC-L2750DW', }, { id: 'printer-3', name: 'Canon PIXMA', type: 'printers', connectionType: 'usb', status: 'offline', manufacturer: 'Canon', model: 'PIXMA TR8620', }, // Mock scanners { id: 'scanner-1', name: 'Epson Perfection V600', type: 'scanners', connectionType: 'usb', status: 'online', manufacturer: 'Epson', model: 'Perfection V600', }, // Mock speakers { id: 'speaker-1', name: 'Sonos One', type: 'speakers', connectionType: 'network', status: 'online', ip: '192.168.1.60', manufacturer: 'Sonos', model: 'One (Gen 2)', }, { id: 'speaker-2', name: 'HomePod mini', type: 'speakers', connectionType: 'network', status: 'online', ip: '192.168.1.61', manufacturer: 'Apple', model: 'HomePod mini', }, // Mock NAS { id: 'nas-1', name: 'Synology DS920+', type: 'storage', connectionType: 'network', status: 'online', ip: '192.168.1.100', manufacturer: 'Synology', model: 'DS920+', }, // Mock UPS { id: 'ups-1', name: 'APC Back-UPS Pro', type: 'power', connectionType: 'usb', status: 'online', manufacturer: 'APC', model: 'Back-UPS Pro 1500', }, // Mock cameras { id: 'camera-1', name: 'Logitech C920', type: 'cameras', connectionType: 'usb', status: 'online', manufacturer: 'Logitech', model: 'C920 HD Pro', isDefault: true, }, { id: 'camera-2', name: 'Ring Indoor Cam', type: 'cameras', connectionType: 'network', status: 'online', ip: '192.168.1.70', manufacturer: 'Ring', model: 'Indoor Cam', }, // Mock streaming devices { id: 'streaming-1', name: 'Living Room Apple TV', type: 'streaming', connectionType: 'network', status: 'online', ip: '192.168.1.80', manufacturer: 'Apple', model: 'Apple TV 4K', }, { id: 'streaming-2', name: 'Bedroom Chromecast', type: 'streaming', connectionType: 'network', status: 'online', ip: '192.168.1.81', manufacturer: 'Google', model: 'Chromecast with Google TV', }, // Mock USB devices { id: 'usb-1', name: 'SanDisk Ultra', type: 'usb', connectionType: 'usb', status: 'online', manufacturer: 'SanDisk', model: 'Ultra USB 3.0 128GB', }, ]; private getMenuGroups(): ISecondaryMenuGroup[] { const allCount = this.devices.length; const getCount = (type: TPeripheralCategory) => this.devices.filter(d => d.type === type).length; return [ { name: 'Devices', iconName: 'lucide:monitor', items: [ { key: 'all', iconName: 'lucide:layoutGrid', action: () => this.activeCategory = 'all', badge: allCount, }, ], }, { name: 'Output', iconName: 'lucide:printer', items: [ { key: 'printers', iconName: 'lucide:printer', action: () => this.activeCategory = 'printers', badge: getCount('printers') || undefined, }, { key: 'speakers', iconName: 'lucide:speaker', action: () => this.activeCategory = 'speakers', badge: getCount('speakers') || undefined, }, ], }, { name: 'Input', iconName: 'lucide:scan', items: [ { key: 'scanners', iconName: 'lucide:scan', action: () => this.activeCategory = 'scanners', badge: getCount('scanners') || undefined, }, { key: 'cameras', iconName: 'lucide:camera', action: () => this.activeCategory = 'cameras', badge: getCount('cameras') || undefined, }, ], }, { name: 'Network', iconName: 'lucide:network', items: [ { key: 'storage', iconName: 'lucide:hardDrive', action: () => this.activeCategory = 'storage', badge: getCount('storage') || undefined, }, { key: 'streaming', iconName: 'lucide:cast', action: () => this.activeCategory = 'streaming', badge: getCount('streaming') || undefined, }, ], }, { name: 'Other', iconName: 'lucide:plug', items: [ { key: 'power', iconName: 'lucide:batteryCharging', action: () => this.activeCategory = 'power', badge: getCount('power') || undefined, }, { key: 'usb', iconName: 'lucide:usb', action: () => this.activeCategory = 'usb', badge: getCount('usb') || undefined, }, ], }, ]; } private getSelectedItem(): ISecondaryMenuItem | null { for (const group of this.getMenuGroups()) { for (const item of group.items) { if ('key' in item && item.key === this.activeCategory) { return item; } } } return null; } private getFilteredDevices(): IPeripheralDevice[] { if (this.activeCategory === 'all') { return this.devices; } return this.devices.filter(d => d.type === this.activeCategory); } private getCategoryTitle(): string { const titles: Record = { all: 'All Devices', printers: 'Printers', scanners: 'Scanners', speakers: 'Speakers', storage: 'Network Storage', power: 'Power Devices', cameras: 'Cameras', streaming: 'Streaming Devices', usb: 'USB Devices', }; return titles[this.activeCategory]; } private getCategoryDescription(): string { const descriptions: Record = { all: 'View and manage all connected peripherals', printers: 'Manage printers and print queues', scanners: 'Configure scanners and scanning options', speakers: 'Network speakers and audio devices', storage: 'NAS devices and network storage', power: 'UPS and power management devices', cameras: 'Webcams and security cameras', streaming: 'Apple TV, Chromecast, and streaming devices', usb: 'USB storage and connected devices', }; return descriptions[this.activeCategory]; } private getDeviceIcon(device: IPeripheralDevice): string { const icons: Record = { all: 'lucide:monitor', printers: 'lucide:printer', scanners: 'lucide:scan', speakers: 'lucide:speaker', storage: 'lucide:hardDrive', power: 'lucide:batteryCharging', cameras: 'lucide:camera', streaming: 'lucide:cast', usb: 'lucide:usb', }; return icons[device.type]; } private getConnectionIcon(type: TConnectionType): string { const icons: Record = { network: 'lucide:wifi', usb: 'lucide:usb', bluetooth: 'lucide:bluetooth', }; return icons[type]; } private async handleScan(): Promise { if (this.isScanning) return; this.isScanning = true; this.dispatchEvent(new CustomEvent('scan-start', { bubbles: true, composed: true, })); // Simulate scanning await new Promise(resolve => setTimeout(resolve, 3000)); this.isScanning = false; this.dispatchEvent(new CustomEvent('scan-complete', { bubbles: true, composed: true, })); } private handleDeviceClick(device: IPeripheralDevice): void { this.dispatchEvent(new CustomEvent('device-select', { detail: { device }, bubbles: true, composed: true, })); } private handleSetDefault(device: IPeripheralDevice, e: Event): void { e.stopPropagation(); this.devices = this.devices.map(d => ({ ...d, isDefault: d.type === device.type ? d.id === device.id : d.isDefault, })); this.dispatchEvent(new CustomEvent('device-set-default', { detail: { device }, bubbles: true, composed: true, })); } public render(): TemplateResult { return html`
${this.renderContent()}
`; } private renderContent(): TemplateResult { const devices = this.getFilteredDevices(); return html`
${this.getCategoryTitle()}
${this.getCategoryDescription()}
${this.activeCategory === 'all' ? this.renderGroupedDevices(devices) : this.renderDeviceList(devices) } `; } private renderGroupedDevices(devices: IPeripheralDevice[]): TemplateResult { const groups = new Map(); for (const device of devices) { const existing = groups.get(device.type) || []; existing.push(device); groups.set(device.type, existing); } const categoryLabels: Record = { all: 'All', printers: 'Printers', scanners: 'Scanners', speakers: 'Speakers', storage: 'Network Storage', power: 'Power Devices', cameras: 'Cameras', streaming: 'Streaming', usb: 'USB Devices', }; return html` ${Array.from(groups.entries()).map(([category, categoryDevices]) => html`
${categoryLabels[category]} ${categoryDevices.length}
${categoryDevices.map(device => this.renderDeviceItem(device))}
`)} `; } private renderDeviceList(devices: IPeripheralDevice[]): TemplateResult { if (devices.length === 0) { return this.renderEmptyState(); } return html`
Discovered Devices ${devices.length}
${devices.map(device => this.renderDeviceItem(device))}
`; } private renderDeviceItem(device: IPeripheralDevice): TemplateResult { return html`
this.handleDeviceClick(device)}>
${device.name} ${device.isDefault ? html`Default` : ''}
${device.status.charAt(0).toUpperCase() + device.status.slice(1)}
${device.ip ? html`
${device.ip}
` : ''} ${device.connectionType.toUpperCase()}
${!device.isDefault && (device.type === 'printers' || device.type === 'cameras') ? html` ` : ''}
`; } private renderEmptyState(): TemplateResult { return html`
No devices found
Click "Scan for Devices" to discover ${this.getCategoryTitle().toLowerCase()} on your network or connected via USB.
`; } }