841 lines
22 KiB
TypeScript
841 lines
22 KiB
TypeScript
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 '../../interfaces/secondarymenu.js';
|
|
import { demo } from './eco-peripherals.demo.js';
|
|
|
|
// Ensure components are registered
|
|
DeesAppuiSecondarymenu;
|
|
DeesIcon;
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'eco-peripherals': EcoPeripherals;
|
|
}
|
|
}
|
|
|
|
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-peripherals')
|
|
export class EcoPeripherals extends DeesElement {
|
|
public static demo = demo;
|
|
public static demoGroup = 'App Launcher';
|
|
|
|
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<TPeripheralCategory, string> = {
|
|
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<TPeripheralCategory, string> = {
|
|
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<TPeripheralCategory, string> = {
|
|
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<TConnectionType, string> = {
|
|
network: 'lucide:wifi',
|
|
usb: 'lucide:usb',
|
|
bluetooth: 'lucide:bluetooth',
|
|
};
|
|
return icons[type];
|
|
}
|
|
|
|
private async handleScan(): Promise<void> {
|
|
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`
|
|
<div class="peripherals-container">
|
|
<dees-appui-secondarymenu
|
|
.heading=${'Peripherals'}
|
|
.groups=${this.getMenuGroups()}
|
|
.selectedItem=${this.getSelectedItem()}
|
|
></dees-appui-secondarymenu>
|
|
<div class="content">
|
|
${this.renderContent()}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderContent(): TemplateResult {
|
|
const devices = this.getFilteredDevices();
|
|
|
|
return html`
|
|
<div class="panel-header">
|
|
<div class="panel-header-left">
|
|
<div class="panel-title">${this.getCategoryTitle()}</div>
|
|
<div class="panel-description">${this.getCategoryDescription()}</div>
|
|
</div>
|
|
<button
|
|
class="scan-button ${this.isScanning ? 'scanning' : ''}"
|
|
@click=${this.handleScan}
|
|
?disabled=${this.isScanning}
|
|
>
|
|
<dees-icon .icon=${this.isScanning ? 'lucide:loader2' : 'lucide:radar'} .iconSize=${16}></dees-icon>
|
|
${this.isScanning ? 'Scanning...' : 'Scan for Devices'}
|
|
</button>
|
|
</div>
|
|
|
|
${this.activeCategory === 'all'
|
|
? this.renderGroupedDevices(devices)
|
|
: this.renderDeviceList(devices)
|
|
}
|
|
`;
|
|
}
|
|
|
|
private renderGroupedDevices(devices: IPeripheralDevice[]): TemplateResult {
|
|
const groups = new Map<TPeripheralCategory, IPeripheralDevice[]>();
|
|
|
|
for (const device of devices) {
|
|
const existing = groups.get(device.type) || [];
|
|
existing.push(device);
|
|
groups.set(device.type, existing);
|
|
}
|
|
|
|
const categoryLabels: Record<TPeripheralCategory, string> = {
|
|
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`
|
|
<div class="device-section">
|
|
<div class="section-header">
|
|
<span class="section-title">${categoryLabels[category]}</span>
|
|
<span class="device-count">${categoryDevices.length}</span>
|
|
</div>
|
|
<div class="device-list">
|
|
${categoryDevices.map(device => this.renderDeviceItem(device))}
|
|
</div>
|
|
</div>
|
|
`)}
|
|
`;
|
|
}
|
|
|
|
private renderDeviceList(devices: IPeripheralDevice[]): TemplateResult {
|
|
if (devices.length === 0) {
|
|
return this.renderEmptyState();
|
|
}
|
|
|
|
return html`
|
|
<div class="device-section">
|
|
<div class="section-header">
|
|
<span class="section-title">Discovered Devices</span>
|
|
<span class="device-count">${devices.length}</span>
|
|
</div>
|
|
<div class="device-list">
|
|
${devices.map(device => this.renderDeviceItem(device))}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderDeviceItem(device: IPeripheralDevice): TemplateResult {
|
|
return html`
|
|
<div class="device-item" @click=${() => this.handleDeviceClick(device)}>
|
|
<div class="device-icon-wrapper ${device.status}">
|
|
<dees-icon .icon=${this.getDeviceIcon(device)} .iconSize=${24}></dees-icon>
|
|
</div>
|
|
<div class="device-info">
|
|
<div class="device-name">
|
|
${device.name}
|
|
${device.isDefault ? html`<span class="default-badge">Default</span>` : ''}
|
|
</div>
|
|
<div class="device-details">
|
|
<div class="device-detail">
|
|
<span class="status-indicator ${device.status}"></span>
|
|
${device.status.charAt(0).toUpperCase() + device.status.slice(1)}
|
|
</div>
|
|
${device.ip ? html`
|
|
<div class="device-detail">${device.ip}</div>
|
|
` : ''}
|
|
<span class="connection-type">
|
|
<dees-icon .icon=${this.getConnectionIcon(device.connectionType)} .iconSize=${10}></dees-icon>
|
|
${device.connectionType.toUpperCase()}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="device-actions">
|
|
${!device.isDefault && (device.type === 'printers' || device.type === 'cameras') ? html`
|
|
<button class="action-button" @click=${(e: Event) => this.handleSetDefault(device, e)}>
|
|
Set Default
|
|
</button>
|
|
` : ''}
|
|
<dees-icon .icon=${'lucide:chevronRight'} .iconSize=${16}></dees-icon>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderEmptyState(): TemplateResult {
|
|
return html`
|
|
<div class="device-section">
|
|
<div class="empty-state">
|
|
<dees-icon class="empty-icon" .icon=${'lucide:searchX'} .iconSize=${48}></dees-icon>
|
|
<div class="empty-title">No devices found</div>
|
|
<div class="empty-description">
|
|
Click "Scan for Devices" to discover ${this.getCategoryTitle().toLowerCase()} on your network or connected via USB.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|