// Constants for log message types const LOG_TYPE = { SUCCESS: 'success', WARNING: 'warning', ERROR: 'error', INFO: 'info', DEBUG: 'debug' }; // Helper function to convert provider name to camelCase for ID lookup // e.g., "dw_leds" -> "DwLeds", "wled" -> "Wled", "none" -> "None" function providerToCamelCase(provider) { return provider.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(''); } // Constants for cache const CACHE_KEYS = { CONNECTION_STATUS: 'connection_status', LAST_UPDATE: 'last_status_update' }; const CACHE_DURATION = 5000; // 5 seconds cache duration // Function to log messages function logMessage(message, type = LOG_TYPE.DEBUG) { console.log(`[${type}] ${message}`); } // Function to get cached connection status function getCachedConnectionStatus() { const cachedData = localStorage.getItem(CACHE_KEYS.CONNECTION_STATUS); const lastUpdate = localStorage.getItem(CACHE_KEYS.LAST_UPDATE); if (cachedData && lastUpdate) { const now = Date.now(); const cacheAge = now - parseInt(lastUpdate); if (cacheAge < CACHE_DURATION) { return JSON.parse(cachedData); } } return null; } // Function to set cached connection status function setCachedConnectionStatus(data) { localStorage.setItem(CACHE_KEYS.CONNECTION_STATUS, JSON.stringify(data)); localStorage.setItem(CACHE_KEYS.LAST_UPDATE, Date.now().toString()); } // Function to update serial connection status async function updateSerialStatus(forceUpdate = false) { try { // Check cache first unless force update is requested if (!forceUpdate) { const cachedData = getCachedConnectionStatus(); if (cachedData) { updateConnectionUI(cachedData); return; } } const response = await fetch('/serial_status'); if (response.ok) { const data = await response.json(); setCachedConnectionStatus(data); updateConnectionUI(data); } } catch (error) { logMessage(`Error checking serial status: ${error.message}`, LOG_TYPE.ERROR); } } // Function to update UI based on connection status function updateConnectionUI(data) { const statusElement = document.getElementById('serialStatus'); const iconElement = document.querySelector('.material-icons.text-3xl'); const disconnectButton = document.getElementById('disconnectButton'); const portSelectionDiv = document.getElementById('portSelectionDiv'); if (statusElement && iconElement) { if (data.connected) { statusElement.textContent = `Connected to ${data.port || 'unknown port'}`; statusElement.className = 'text-green-500 text-sm font-medium leading-normal'; iconElement.textContent = 'usb'; if (disconnectButton) { disconnectButton.hidden = false; } if (portSelectionDiv) { portSelectionDiv.hidden = true; } } else { statusElement.textContent = 'Disconnected'; statusElement.className = 'text-red-500 text-sm font-medium leading-normal'; iconElement.textContent = 'usb_off'; if (disconnectButton) { disconnectButton.hidden = true; } if (portSelectionDiv) { portSelectionDiv.hidden = false; } } } } // Function to update available serial ports async function updateSerialPorts() { try { const response = await fetch('/list_serial_ports'); if (response.ok) { const ports = await response.json(); const portsElement = document.getElementById('availablePorts'); const portSelect = document.getElementById('portSelect'); if (portsElement) { portsElement.textContent = ports.length > 0 ? ports.join(', ') : 'No ports available'; } if (portSelect) { // Clear existing options except the first one while (portSelect.options.length > 1) { portSelect.remove(1); } // Add new options ports.forEach(port => { const option = document.createElement('option'); option.value = port; option.textContent = port; portSelect.appendChild(option); }); // If there's exactly one port available, select and connect to it if (ports.length === 1) { portSelect.value = ports[0]; // Trigger connect button click const connectButton = document.getElementById('connectButton'); if (connectButton) { connectButton.click(); } } } } } catch (error) { logMessage(`Error fetching serial ports: ${error.message}`, LOG_TYPE.ERROR); } } function setWledButtonState(isSet) { const saveWledConfig = document.getElementById('saveWledConfig'); if (!saveWledConfig) return; if (isSet) { saveWledConfig.className = 'flex items-center justify-center gap-2 min-w-[100px] max-w-[480px] cursor-pointer rounded-lg h-10 px-4 bg-red-600 hover:bg-red-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors'; saveWledConfig.innerHTML = 'Clear WLED IP'; } else { saveWledConfig.className = 'flex items-center justify-center gap-2 min-w-[100px] max-w-[480px] cursor-pointer rounded-lg h-10 px-4 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium leading-normal tracking-[0.015em] transition-colors'; saveWledConfig.innerHTML = 'Save Configuration'; } } // Handle LED provider selection and show/hide appropriate config sections function updateLedProviderUI() { const provider = document.querySelector('input[name="ledProvider"]:checked')?.value || 'none'; const wledConfig = document.getElementById('wledConfig'); const dwLedsConfig = document.getElementById('dwLedsConfig'); if (wledConfig && dwLedsConfig) { if (provider === 'wled') { wledConfig.classList.remove('hidden'); dwLedsConfig.classList.add('hidden'); } else if (provider === 'dw_leds') { wledConfig.classList.add('hidden'); dwLedsConfig.classList.remove('hidden'); } else { wledConfig.classList.add('hidden'); dwLedsConfig.classList.add('hidden'); } } } // Load LED configuration from server async function loadLedConfig() { try { const response = await fetch('/get_led_config'); if (response.ok) { const data = await response.json(); // Set provider radio button const providerRadio = document.getElementById(`ledProvider${providerToCamelCase(data.provider)}`); if (providerRadio) { providerRadio.checked = true; } else { document.getElementById('ledProviderNone').checked = true; } // Set WLED IP if configured if (data.wled_ip) { const wledIpInput = document.getElementById('wledIpInput'); if (wledIpInput) { wledIpInput.value = data.wled_ip; } } // Set DW LED configuration if configured if (data.dw_led_num_leds) { const numLedsInput = document.getElementById('dwLedNumLeds'); if (numLedsInput) { numLedsInput.value = data.dw_led_num_leds; } } if (data.dw_led_gpio_pin) { const gpioPinInput = document.getElementById('dwLedGpioPin'); if (gpioPinInput) { gpioPinInput.value = data.dw_led_gpio_pin; } } if (data.dw_led_pixel_order) { const pixelOrderInput = document.getElementById('dwLedPixelOrder'); if (pixelOrderInput) { pixelOrderInput.value = data.dw_led_pixel_order; } } // Update UI to show correct config section updateLedProviderUI(); } } catch (error) { logMessage(`Error loading LED config: ${error.message}`, LOG_TYPE.ERROR); } } // Initialize settings page document.addEventListener('DOMContentLoaded', async () => { // Initialize UI with default disconnected state updateConnectionUI({ connected: false }); // Load all data asynchronously Promise.all([ // Check connection status fetch('/serial_status').then(response => response.json()).catch(() => ({ connected: false })), // Load LED configuration (replaces old WLED-only loading) fetch('/get_led_config').then(response => response.json()).catch(() => ({ provider: 'none', wled_ip: null })), // Load current version and check for updates fetch('/api/version').then(response => response.json()).catch(() => ({ current: '1.0.0', latest: '1.0.0', update_available: false })), // Load available serial ports fetch('/list_serial_ports').then(response => response.json()).catch(() => []), // Load available pattern files for clear pattern selection getCachedPatternFiles().catch(() => []), // Load current custom clear patterns fetch('/api/custom_clear_patterns').then(response => response.json()).catch(() => ({ custom_clear_from_in: null, custom_clear_from_out: null })), // Load current clear pattern speed fetch('/api/clear_pattern_speed').then(response => response.json()).catch(() => ({ clear_pattern_speed: 200 })), // Load current app name fetch('/api/app-name').then(response => response.json()).catch(() => ({ app_name: 'Dune Weaver' })), // Load Still Sands settings fetch('/api/scheduled-pause').then(response => response.json()).catch(() => ({ enabled: false, time_slots: [] })) ]).then(([statusData, ledConfigData, updateData, ports, patterns, clearPatterns, clearSpeedData, appNameData, scheduledPauseData]) => { // Update connection status setCachedConnectionStatus(statusData); updateConnectionUI(statusData); // Update LED configuration const providerRadio = document.getElementById(`ledProvider${providerToCamelCase(ledConfigData.provider)}`); if (providerRadio) { providerRadio.checked = true; } else { document.getElementById('ledProviderNone').checked = true; } if (ledConfigData.wled_ip) { const wledIpInput = document.getElementById('wledIpInput'); if (wledIpInput) wledIpInput.value = ledConfigData.wled_ip; } // Load DW LED settings if (ledConfigData.dw_led_num_leds) { const numLedsInput = document.getElementById('dwLedNumLeds'); if (numLedsInput) numLedsInput.value = ledConfigData.dw_led_num_leds; } if (ledConfigData.dw_led_gpio_pin) { const gpioPinInput = document.getElementById('dwLedGpioPin'); if (gpioPinInput) gpioPinInput.value = ledConfigData.dw_led_gpio_pin; } if (ledConfigData.dw_led_pixel_order) { const pixelOrderInput = document.getElementById('dwLedPixelOrder'); if (pixelOrderInput) pixelOrderInput.value = ledConfigData.dw_led_pixel_order; } updateLedProviderUI() // Update version display const currentVersionText = document.getElementById('currentVersionText'); const latestVersionText = document.getElementById('latestVersionText'); const updateButton = document.getElementById('updateSoftware'); const updateIcon = document.getElementById('updateIcon'); const updateText = document.getElementById('updateText'); if (currentVersionText) { currentVersionText.textContent = updateData.current; } if (latestVersionText) { if (updateData.error) { latestVersionText.textContent = 'Error checking updates'; latestVersionText.className = 'text-red-500 text-sm font-normal leading-normal'; } else { latestVersionText.textContent = updateData.latest; latestVersionText.className = 'text-slate-500 text-sm font-normal leading-normal'; } } // Update button state if (updateButton && updateIcon && updateText) { if (updateData.update_available) { updateButton.disabled = false; updateButton.className = 'flex items-center justify-center gap-1.5 min-w-[84px] cursor-pointer rounded-lg h-9 px-3 bg-emerald-500 hover:bg-emerald-600 text-white text-xs font-medium leading-normal tracking-[0.015em] transition-colors'; updateIcon.textContent = 'download'; updateText.textContent = 'Update'; } else { updateButton.disabled = true; updateButton.className = 'flex items-center justify-center gap-1.5 min-w-[84px] cursor-pointer rounded-lg h-9 px-3 bg-gray-400 text-white text-xs font-medium leading-normal tracking-[0.015em] transition-colors disabled:opacity-50 disabled:cursor-not-allowed'; updateIcon.textContent = 'check'; updateText.textContent = 'Up to date'; } } // Update port selection const portSelect = document.getElementById('portSelect'); if (portSelect) { // Clear existing options except the first one while (portSelect.options.length > 1) { portSelect.remove(1); } // Add new options ports.forEach(port => { const option = document.createElement('option'); option.value = port; option.textContent = port; portSelect.appendChild(option); }); // If there's exactly one port available, select it if (ports.length === 1) { portSelect.value = ports[0]; } } // Initialize autocomplete for clear patterns const clearFromInInput = document.getElementById('customClearFromInInput'); const clearFromOutInput = document.getElementById('customClearFromOutInput'); if (clearFromInInput && clearFromOutInput && patterns && Array.isArray(patterns)) { // Store patterns globally for autocomplete window.availablePatterns = patterns; // Set current values if they exist if (clearPatterns && clearPatterns.custom_clear_from_in) { clearFromInInput.value = clearPatterns.custom_clear_from_in; } if (clearPatterns && clearPatterns.custom_clear_from_out) { clearFromOutInput.value = clearPatterns.custom_clear_from_out; } // Initialize autocomplete for both inputs initializeAutocomplete('customClearFromInInput', 'clearFromInSuggestions', 'clearFromInClear', patterns); initializeAutocomplete('customClearFromOutInput', 'clearFromOutSuggestions', 'clearFromOutClear', patterns); console.log('Autocomplete initialized with', patterns.length, 'patterns'); } // Set clear pattern speed const clearPatternSpeedInput = document.getElementById('clearPatternSpeedInput'); const effectiveClearSpeed = document.getElementById('effectiveClearSpeed'); if (clearPatternSpeedInput && clearSpeedData) { // Only set value if clear_pattern_speed is not null if (clearSpeedData.clear_pattern_speed !== null && clearSpeedData.clear_pattern_speed !== undefined) { clearPatternSpeedInput.value = clearSpeedData.clear_pattern_speed; if (effectiveClearSpeed) { effectiveClearSpeed.textContent = `Current: ${clearSpeedData.clear_pattern_speed} steps/min`; } } else { // Leave empty to show placeholder for default clearPatternSpeedInput.value = ''; if (effectiveClearSpeed && clearSpeedData.effective_speed) { effectiveClearSpeed.textContent = `Using default pattern speed: ${clearSpeedData.effective_speed} steps/min`; } } } // Update app name const appNameInput = document.getElementById('appNameInput'); if (appNameInput && appNameData.app_name) { appNameInput.value = appNameData.app_name; } // Store Still Sands data for later initialization window.initialStillSandsData = scheduledPauseData; }).catch(error => { logMessage(`Error initializing settings page: ${error.message}`, LOG_TYPE.ERROR); }); // Set up event listeners setupEventListeners(); }); // Setup event listeners function setupEventListeners() { // Save App Name const saveAppNameButton = document.getElementById('saveAppName'); const appNameInput = document.getElementById('appNameInput'); if (saveAppNameButton && appNameInput) { saveAppNameButton.addEventListener('click', async () => { const appName = appNameInput.value.trim() || 'Dune Weaver'; try { const response = await fetch('/api/app-name', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ app_name: appName }) }); if (response.ok) { const data = await response.json(); showStatusMessage('Application name updated successfully. Refresh the page to see changes.', 'success'); // Update the page title and header immediately document.title = `Settings - ${data.app_name}`; const headerTitle = document.querySelector('h1.text-gray-800'); if (headerTitle) { // Update just the text content, preserving the connection status dot const textNode = headerTitle.childNodes[0]; if (textNode && textNode.nodeType === Node.TEXT_NODE) { textNode.textContent = data.app_name; } } } else { throw new Error('Failed to save application name'); } } catch (error) { showStatusMessage(`Failed to save application name: ${error.message}`, 'error'); } }); // Handle Enter key in app name input appNameInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { saveAppNameButton.click(); } }); } // LED provider selection change handlers const ledProviderRadios = document.querySelectorAll('input[name="ledProvider"]'); ledProviderRadios.forEach(radio => { radio.addEventListener('change', updateLedProviderUI); }); // Save LED configuration const saveLedConfig = document.getElementById('saveLedConfig'); if (saveLedConfig) { saveLedConfig.addEventListener('click', async () => { const provider = document.querySelector('input[name="ledProvider"]:checked')?.value || 'none'; let requestBody = { provider }; if (provider === 'wled') { const wledIp = document.getElementById('wledIpInput')?.value; if (!wledIp) { showStatusMessage('Please enter a WLED IP address', 'error'); return; } requestBody.ip_address = wledIp; } else if (provider === 'dw_leds') { const numLeds = parseInt(document.getElementById('dwLedNumLeds')?.value) || 60; const gpioPin = parseInt(document.getElementById('dwLedGpioPin')?.value) || 12; const pixelOrder = document.getElementById('dwLedPixelOrder')?.value || 'GRB'; requestBody.num_leds = numLeds; requestBody.gpio_pin = gpioPin; requestBody.pixel_order = pixelOrder; } try { const response = await fetch('/set_led_config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) }); if (response.ok) { const data = await response.json(); if (provider === 'wled' && data.wled_ip) { localStorage.setItem('wled_ip', data.wled_ip); showStatusMessage('WLED configured successfully', 'success'); } else if (provider === 'dw_leds') { // Check if there's a warning (hardware not available but settings saved) if (data.warning) { showStatusMessage( `Settings saved for testing. Hardware issue: ${data.warning}`, 'warning' ); } else { showStatusMessage( `DW LEDs configured: ${data.dw_led_num_leds} LEDs on GPIO${data.dw_led_gpio_pin}`, 'success' ); } } else if (provider === 'none') { localStorage.removeItem('wled_ip'); showStatusMessage('LED controller disabled', 'success'); } } else { // Extract error detail from response const errorData = await response.json().catch(() => ({})); const errorMessage = errorData.detail || 'Failed to save LED configuration'; showStatusMessage(errorMessage, 'error'); } } catch (error) { showStatusMessage(`Failed to save LED configuration: ${error.message}`, 'error'); } }); } // Fetch and populate available versions async function loadAvailableVersions() { try { const response = await fetch('/api/versions'); const data = await response.json(); if (data.success && data.versions) { const versionSelect = document.getElementById('versionSelect'); if (versionSelect) { // Clear existing options except "Latest" versionSelect.innerHTML = ''; // Add optgroup for tags if (data.versions.tags && data.versions.tags.length > 0) { const tagsOptgroup = document.createElement('optgroup'); tagsOptgroup.label = 'Tags (Releases)'; data.versions.tags.forEach(tag => { const option = document.createElement('option'); option.value = tag; option.textContent = tag; tagsOptgroup.appendChild(option); }); versionSelect.appendChild(tagsOptgroup); } // Add optgroup for branches if (data.versions.branches && data.versions.branches.length > 0) { const branchesOptgroup = document.createElement('optgroup'); branchesOptgroup.label = 'Branches'; data.versions.branches.forEach(branch => { const option = document.createElement('option'); option.value = branch; option.textContent = branch; branchesOptgroup.appendChild(option); }); versionSelect.appendChild(branchesOptgroup); } // Enable update button const updateButton = document.getElementById('updateSoftware'); if (updateButton) { updateButton.disabled = false; updateButton.classList.remove('bg-gray-400'); updateButton.classList.add('bg-sky-600', 'hover:bg-sky-700'); } } } } catch (error) { console.error('Error loading versions:', error); showStatusMessage('Failed to load available versions', 'error'); } } // Load versions on page load loadAvailableVersions(); // Update confirmation modal logic let updateWebSocket = null; function showUpdateConfirmModal() { const modal = document.getElementById('updateConfirmModal'); const versionSelect = document.getElementById('versionSelect'); const targetVersionDisplay = document.getElementById('targetVersionDisplay'); if (modal && versionSelect && targetVersionDisplay) { const selectedVersion = versionSelect.value; targetVersionDisplay.textContent = selectedVersion; modal.classList.remove('hidden'); } } function hideUpdateConfirmModal() { const modal = document.getElementById('updateConfirmModal'); if (modal) { modal.classList.add('hidden'); } // Clean up WebSocket if exists if (updateWebSocket) { updateWebSocket.close(); updateWebSocket = null; } } function appendUpdateLog(message) { const logContainer = document.getElementById('updateProgressLog'); if (logContainer) { const logLine = document.createElement('div'); logLine.textContent = message; logContainer.appendChild(logLine); // Auto-scroll to bottom logContainer.parentElement.scrollTop = logContainer.parentElement.scrollHeight; } } function startUpdateProcess() { const versionSelect = document.getElementById('versionSelect'); const progressContainer = document.getElementById('updateProgressContainer'); const modalActions = document.getElementById('updateModalActions'); const completeActions = document.getElementById('updateCompleteActions'); const selectedVersion = versionSelect ? versionSelect.value : 'latest'; // Show progress container and hide action buttons if (progressContainer) progressContainer.classList.remove('hidden'); if (modalActions) modalActions.classList.add('hidden'); // Connect to WebSocket for live logs const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; updateWebSocket = new WebSocket(`${wsProtocol}//${window.location.host}/ws/update-progress`); updateWebSocket.onopen = () => { console.log('Update WebSocket connected'); appendUpdateLog('Connected to update stream...'); // Trigger the update fetch('/api/update_to_version', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ version: selectedVersion }) }) .then(response => response.json()) .then(data => { if (!data.success) { appendUpdateLog(`ERROR: ${data.message || 'Failed to start update'}`); } }) .catch(error => { appendUpdateLog(`ERROR: ${error.message}`); }); }; updateWebSocket.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'log') { appendUpdateLog(data.message); // Check for completion if (data.message.includes('Update completed successfully') || data.message.includes('Update failed')) { if (completeActions) completeActions.classList.remove('hidden'); } } }; updateWebSocket.onerror = (error) => { console.error('WebSocket error:', error); appendUpdateLog('ERROR: WebSocket connection error'); }; updateWebSocket.onclose = () => { console.log('Update WebSocket closed'); }; } // Update software button - show confirmation modal const updateSoftware = document.getElementById('updateSoftware'); if (updateSoftware) { updateSoftware.addEventListener('click', () => { if (updateSoftware.disabled) { return; } showUpdateConfirmModal(); }); } // Modal action buttons const closeUpdateModal = document.getElementById('closeUpdateModal'); const cancelUpdate = document.getElementById('cancelUpdate'); const confirmUpdate = document.getElementById('confirmUpdate'); const closeUpdateComplete = document.getElementById('closeUpdateComplete'); if (closeUpdateModal) { closeUpdateModal.addEventListener('click', hideUpdateConfirmModal); } if (cancelUpdate) { cancelUpdate.addEventListener('click', hideUpdateConfirmModal); } if (confirmUpdate) { confirmUpdate.addEventListener('click', () => { startUpdateProcess(); }); } if (closeUpdateComplete) { closeUpdateComplete.addEventListener('click', () => { hideUpdateConfirmModal(); // Optionally reload the page to show new version setTimeout(() => window.location.reload(), 1000); }); } // Connect button const connectButton = document.getElementById('connectButton'); if (connectButton) { connectButton.addEventListener('click', async () => { const portSelect = document.getElementById('portSelect'); if (!portSelect || !portSelect.value) { logMessage('Please select a port first', LOG_TYPE.WARNING); return; } try { const response = await fetch('/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ port: portSelect.value }) }); if (response.ok) { logMessage('Connected successfully', LOG_TYPE.SUCCESS); await updateSerialStatus(true); // Force update after connecting } else { throw new Error('Failed to connect'); } } catch (error) { logMessage(`Error connecting to device: ${error.message}`, LOG_TYPE.ERROR); } }); } // Disconnect button const disconnectButton = document.getElementById('disconnectButton'); if (disconnectButton) { disconnectButton.addEventListener('click', async () => { try { const response = await fetch('/disconnect', { method: 'POST' }); if (response.ok) { logMessage('Device disconnected successfully', LOG_TYPE.SUCCESS); await updateSerialStatus(true); // Force update after disconnecting } else { throw new Error('Failed to disconnect device'); } } catch (error) { logMessage(`Error disconnecting device: ${error.message}`, LOG_TYPE.ERROR); } }); } // Save custom clear patterns button const saveClearPatterns = document.getElementById('saveClearPatterns'); if (saveClearPatterns) { saveClearPatterns.addEventListener('click', async () => { const clearFromInInput = document.getElementById('customClearFromInInput'); const clearFromOutInput = document.getElementById('customClearFromOutInput'); if (!clearFromInInput || !clearFromOutInput) { return; } // Validate that the entered patterns exist (if not empty) const inValue = clearFromInInput.value.trim(); const outValue = clearFromOutInput.value.trim(); if (inValue && window.availablePatterns && !window.availablePatterns.includes(inValue)) { showStatusMessage(`Pattern not found: ${inValue}`, 'error'); return; } if (outValue && window.availablePatterns && !window.availablePatterns.includes(outValue)) { showStatusMessage(`Pattern not found: ${outValue}`, 'error'); return; } try { const response = await fetch('/api/custom_clear_patterns', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ custom_clear_from_in: inValue || null, custom_clear_from_out: outValue || null }) }); if (response.ok) { showStatusMessage('Clear patterns saved successfully', 'success'); } else { const error = await response.json(); throw new Error(error.detail || 'Failed to save clear patterns'); } } catch (error) { showStatusMessage(`Failed to save clear patterns: ${error.message}`, 'error'); } }); } // Save clear pattern speed button const saveClearSpeed = document.getElementById('saveClearSpeed'); if (saveClearSpeed) { saveClearSpeed.addEventListener('click', async () => { const clearPatternSpeedInput = document.getElementById('clearPatternSpeedInput'); if (!clearPatternSpeedInput) { return; } let speed; if (clearPatternSpeedInput.value === '' || clearPatternSpeedInput.value === null) { // Empty value means use default (None) speed = null; } else { speed = parseInt(clearPatternSpeedInput.value); // Validate speed only if it's not null if (isNaN(speed) || speed < 50 || speed > 2000) { showStatusMessage('Clear pattern speed must be between 50 and 2000, or leave empty for default', 'error'); return; } } try { const response = await fetch('/api/clear_pattern_speed', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ clear_pattern_speed: speed }) }); if (response.ok) { const data = await response.json(); if (speed === null) { showStatusMessage(`Clear pattern speed set to default (${data.effective_speed} steps/min)`, 'success'); } else { showStatusMessage(`Clear pattern speed set to ${speed} steps/min`, 'success'); } // Update the effective speed display const effectiveClearSpeed = document.getElementById('effectiveClearSpeed'); if (effectiveClearSpeed) { if (speed === null) { effectiveClearSpeed.textContent = `Using default pattern speed: ${data.effective_speed} steps/min`; } else { effectiveClearSpeed.textContent = `Current: ${speed} steps/min`; } } } else { const error = await response.json(); throw new Error(error.detail || 'Failed to save clear pattern speed'); } } catch (error) { showStatusMessage(`Failed to save clear pattern speed: ${error.message}`, 'error'); } }); } } // Button click handlers document.addEventListener('DOMContentLoaded', function() { // Home button const homeButton = document.getElementById('homeButton'); if (homeButton) { homeButton.addEventListener('click', async () => { try { const response = await fetch('/send_home', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); if (data.success) { updateStatus('Moving to home position...'); } } catch (error) { console.error('Error sending home command:', error); updateStatus('Error: Failed to move to home position'); } }); } // Stop button const stopButton = document.getElementById('stopButton'); if (stopButton) { stopButton.addEventListener('click', async () => { try { const response = await fetch('/stop_execution', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); if (data.success) { updateStatus('Execution stopped'); } } catch (error) { console.error('Error stopping execution:', error); updateStatus('Error: Failed to stop execution'); } }); } // Move to Center button const centerButton = document.getElementById('centerButton'); if (centerButton) { centerButton.addEventListener('click', async () => { try { const response = await fetch('/move_to_center', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); if (data.success) { updateStatus('Moving to center position...'); } } catch (error) { console.error('Error moving to center:', error); updateStatus('Error: Failed to move to center'); } }); } // Move to Perimeter button const perimeterButton = document.getElementById('perimeterButton'); if (perimeterButton) { perimeterButton.addEventListener('click', async () => { try { const response = await fetch('/move_to_perimeter', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); if (data.success) { updateStatus('Moving to perimeter position...'); } } catch (error) { console.error('Error moving to perimeter:', error); updateStatus('Error: Failed to move to perimeter'); } }); } }); // Function to update status function updateStatus(message) { const statusElement = document.querySelector('.text-slate-800.text-base.font-medium.leading-normal'); if (statusElement) { statusElement.textContent = message; // Reset status after 3 seconds if it's a temporary message if (message.includes('Moving') || message.includes('Execution')) { setTimeout(() => { statusElement.textContent = 'Status'; }, 3000); } } } // Function to show status messages (using existing base.js showStatusMessage if available) function showStatusMessage(message, type) { if (typeof window.showStatusMessage === 'function') { window.showStatusMessage(message, type); } else { // Fallback to console logging console.log(`[${type}] ${message}`); } } // Function to show update instructions modal function showUpdateInstructionsModal(data) { // Create modal HTML const modal = document.createElement('div'); modal.id = 'updateInstructionsModal'; modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4'; modal.innerHTML = `
${data.message}
${data.instructions}
No time slots configured
Click "Add Time Slot" to create a pause schedule