// Inject the HTML before Alpine initializes its data components if (!document.querySelector('.container')) { document.body.innerHTML = `

🎮 Handheld Emulation Devices

Manage your collection of retro handheld devices

Device Name Manufacturer Year CPU RAM Display Battery Actions

No devices found. Try adding one!

`; } document.addEventListener('alpine:init', () => { Alpine.data('deviceManager', () => ({ devices: [], searchQuery: '', showCreateForm: false, showDetailsModal: false, showToast: false, toastMessage: '', editingDevice: null, selectedDevice: null, // Use config from server or default apiBase: window.APP_CONFIG?.api_url || '', init() { this.$watch('showCreateForm', value => { const popover = document.getElementById('createFormPopover'); if (value) { try { popover.showPopover(); } catch(e) {} } else { try { popover.hidePopover(); } catch(e) {} } }); this.$watch('showDetailsModal', value => { const popover = document.getElementById('detailsModalPopover'); if (value) { try { popover.showPopover(); } catch(e) {} } else { try { popover.hidePopover(); } catch(e) {} } }); this.$watch('showToast', value => { const popover = document.getElementById('toastPopover'); if (value) { try { popover.showPopover(); } catch(e) {} } else { try { popover.hidePopover(); } catch(e) {} } }); }, form: { name: '', manufacturer: '', release_year: null, cpu: '', ram_mb: null, storage_mb: null, display_size: '', battery_hours: null }, get filteredDevices() { return this.devices.filter(device => device.name.toLowerCase().includes(this.searchQuery.toLowerCase()) || device.manufacturer.toLowerCase().includes(this.searchQuery.toLowerCase()) ); }, async loadDevices() { try { const response = await fetch(this.apiBase + '/devices'); const data = await response.json(); console.log('Devices data:', data); this.devices = Array.isArray(data.data) ? data.data : (Array.isArray(data) ? data : []); } catch (error) { this.showNotification('Failed to load devices'); console.error(error); } }, connectWebSocket() { const wsUrl = this.apiBase.replace('http', 'ws'); console.log('Connecting to WebSocket:', wsUrl); const socket = new WebSocket(wsUrl); socket.onmessage = (event) => { const data = JSON.parse(event.data); console.log('WS Message received:', data); if (data.event_type === 'DevicePublished') { this.showNotification(`New device: ${data.device_name}`); this.loadDevices(); // Refresh list } else if (data.event_type === 'DeviceUpdated') { this.showNotification(`Device updated: ${data.device_name}`); this.loadDevices(); // Refresh list } else if (data.event_type === 'DeviceDeleted') { this.showNotification(`Device removed: ${data.device_name}`); // Optimistically remove from local list or just refresh this.devices = this.devices.filter(d => d.id != data.device_id); if (this.selectedDevice && this.selectedDevice.id == data.device_id) { this.showDetailsModal = false; } } }; socket.onclose = () => { console.log('WS disconnected, retrying in 5s...'); setTimeout(() => this.connectWebSocket(), 5000); }; socket.onerror = (error) => { console.error('WS Error:', error); }; }, async saveDevice() { try { const url = this.editingDevice ? `${this.apiBase}/devices/${this.editingDevice.id}` : `${this.apiBase}/devices`; const method = this.editingDevice ? 'PUT' : 'POST'; // Filter out null/undefined values and only send necessary data const payload = { name: this.form.name, manufacturer: this.form.manufacturer, release_year: this.form.release_year ? parseInt(this.form.release_year) : null, cpu: this.form.cpu || null, ram_mb: this.form.ram_mb ? parseInt(this.form.ram_mb) : null, storage_mb: this.form.storage_mb ? parseInt(this.form.storage_mb) : null, display_size: this.form.display_size || null, battery_hours: this.form.battery_hours ? parseFloat(this.form.battery_hours) : null }; const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (response.ok) { await this.loadDevices(); this.resetForm(); this.showCreateForm = false; this.showNotification( this.editingDevice ? 'Device updated successfully!' : 'Device created successfully!' ); } else { const errorData = await response.json().catch(() => ({})); this.showNotification(`Failed to save device: ${errorData.error || response.statusText}`); } } catch (error) { this.showNotification('Failed to save device: Network error'); console.error(error); } }, editDevice(device) { this.editingDevice = device; this.form = { ...device }; this.showCreateForm = true; this.showDetailsModal = false; }, async deleteDevice(id) { if (confirm('Are you sure you want to delete this device?')) { try { const response = await fetch(`${this.apiBase}/devices/${id}`, { method: 'DELETE' }); if (response.ok) { await this.loadDevices(); this.showNotification('Device deleted successfully!'); } else { this.showNotification('Failed to delete device'); } } catch (error) { this.showNotification('Failed to delete device'); console.error(error); } } }, viewDevice(device) { this.selectedDevice = device; this.showDetailsModal = true; }, resetForm() { this.form = { name: '', manufacturer: '', release_year: null, cpu: '', ram_mb: null, storage_mb: null, display_size: '', battery_hours: null }; this.editingDevice = null; }, showNotification(message) { this.toastMessage = message; this.showToast = true; setTimeout(() => { this.showToast = false; }, 3000); }, addNewDevice() { this.resetForm(); this.showCreateForm = true; } })); });