/** * Entry point — imports all modules, registers globals, initializes app. */ // Layer 0: state import { apiKey, setApiKey, refreshInterval } from './core/state.js'; import { Modal } from './core/modal.js'; // Layer 1: api, i18n import { loadServerInfo, loadDisplays, configureApiKey } from './core/api.js'; import { t, initLocale, changeLocale } from './core/i18n.js'; // Layer 2: ui import { toggleHint, lockBody, unlockBody, closeLightbox, showToast, showConfirm, closeConfirmModal, openFullImageLightbox, showOverlaySpinner, hideOverlaySpinner, } from './core/ui.js'; // Layer 3: displays, tutorials import { openDisplayPicker, closeDisplayPicker, selectDisplay, formatDisplayLabel, } from './features/displays.js'; import { startCalibrationTutorial, startDeviceTutorial, startGettingStartedTutorial, startDashboardTutorial, startTargetsTutorial, startSourcesTutorial, startAutomationsTutorial, closeTutorial, tutorialNext, tutorialPrev, } from './features/tutorials.js'; // Layer 4: devices, dashboard, streams, kc-targets, pattern-templates, automations import { showSettings, closeDeviceSettingsModal, forceCloseDeviceSettingsModal, saveDeviceSettings, updateBrightnessLabel, saveCardBrightness, turnOffDevice, removeDevice, loadDevices, updateSettingsBaudFpsHint, copyWsUrl, } from './features/devices.js'; import { loadDashboard, stopUptimeTimer, dashboardToggleAutomation, dashboardStartTarget, dashboardStopTarget, dashboardStopAll, dashboardPauseClock, dashboardResumeClock, dashboardResetClock, toggleDashboardSection, changeDashboardPollInterval, } from './features/dashboard.js'; import { startEventsWS, stopEventsWS } from './core/events-ws.js'; import { startPerfPolling, stopPerfPolling, } from './features/perf-charts.js'; import { loadPictureSources, switchStreamTab, showAddTemplateModal, editTemplate, closeTemplateModal, saveTemplate, deleteTemplate, showTestTemplateModal, closeTestTemplateModal, onEngineChange, runTemplateTest, showAddAudioTemplateModal, editAudioTemplate, closeAudioTemplateModal, saveAudioTemplate, deleteAudioTemplate, cloneAudioTemplate, onAudioEngineChange, showTestAudioTemplateModal, closeTestAudioTemplateModal, startAudioTemplateTest, showAddStreamModal, editStream, closeStreamModal, saveStream, deleteStream, onStreamTypeChange, onStreamDisplaySelected, onTestDisplaySelected, showTestStreamModal, closeTestStreamModal, updateStreamTestDuration, runStreamTest, showTestPPTemplateModal, closeTestPPTemplateModal, updatePPTestDuration, runPPTemplateTest, showAddPPTemplateModal, editPPTemplate, closePPTemplateModal, savePPTemplate, deletePPTemplate, addFilterFromSelect, toggleFilterExpand, removeFilter, moveFilter, updateFilterOption, renderModalFilterList, updateCaptureDuration, cloneStream, cloneCaptureTemplate, clonePPTemplate, expandAllStreamSections, collapseAllStreamSections, } from './features/streams.js'; import { createKCTargetCard, testKCTarget, toggleKCTestAutoRefresh, showKCEditor, closeKCEditorModal, forceCloseKCEditorModal, saveKCEditor, deleteKCTarget, disconnectAllKCWebSockets, updateKCBrightnessLabel, saveKCBrightness, cloneKCTarget, } from './features/kc-targets.js'; import { createPatternTemplateCard, showPatternTemplateEditor, closePatternTemplateModal, forceClosePatternTemplateModal, savePatternTemplate, deletePatternTemplate, renderPatternRectList, selectPatternRect, updatePatternRect, addPatternRect, deleteSelectedPatternRect, removePatternRect, capturePatternBackground, clonePatternTemplate, } from './features/pattern-templates.js'; import { loadAutomations, openAutomationEditor, closeAutomationEditorModal, saveAutomationEditor, addAutomationCondition, toggleAutomationEnabled, deleteAutomation, copyWebhookUrl, expandAllAutomationSections, collapseAllAutomationSections, } from './features/automations.js'; import { openScenePresetCapture, editScenePreset, saveScenePreset, closeScenePresetEditor, activateScenePreset, recaptureScenePreset, cloneScenePreset, deleteScenePreset, addSceneTarget, removeSceneTarget, } from './features/scene-presets.js'; // Layer 5: device-discovery, targets import { onDeviceTypeChanged, updateBaudFpsHint, onSerialPortFocus, showAddDevice, closeAddDeviceModal, scanForDevices, handleAddDevice, } from './features/device-discovery.js'; import { loadTargetsTab, switchTargetSubTab, showTargetEditor, closeTargetEditorModal, forceCloseTargetEditorModal, saveTargetEditor, startTargetProcessing, stopTargetProcessing, stopAllLedTargets, stopAllKCTargets, startTargetOverlay, stopTargetOverlay, deleteTarget, cloneTarget, toggleLedPreview, expandAllTargetSections, collapseAllTargetSections, disconnectAllLedPreviewWS, } from './features/targets.js'; // Layer 5: color-strip sources import { showCSSEditor, closeCSSEditorModal, forceCSSEditorClose, saveCSSEditor, deleteColorStrip, onCSSTypeChange, onEffectTypeChange, onAnimationTypeChange, onCSSClockChange, updateEffectPreview, colorCycleAddColor, colorCycleRemoveColor, compositeAddLayer, compositeRemoveLayer, mappedAddZone, mappedRemoveZone, onAudioVizChange, applyGradientPreset, cloneColorStrip, copyEndpointUrl, onNotificationFilterModeChange, notificationAddAppColor, notificationRemoveAppColor, } from './features/color-strips.js'; // Layer 5: audio sources import { showAudioSourceModal, closeAudioSourceModal, saveAudioSource, editAudioSource, cloneAudioSource, deleteAudioSource, testAudioSource, closeTestAudioSourceModal, } from './features/audio-sources.js'; // Layer 5: value sources import { showValueSourceModal, closeValueSourceModal, saveValueSource, editValueSource, cloneValueSource, deleteValueSource, onValueSourceTypeChange, addSchedulePoint, testValueSource, closeTestValueSourceModal, } from './features/value-sources.js'; // Layer 5: calibration import { showCalibration, closeCalibrationModal, forceCloseCalibrationModal, saveCalibration, updateOffsetSkipLock, updateCalibrationPreview, setStartPosition, toggleEdgeInputs, toggleDirection, toggleTestEdge, showCSSCalibration, toggleCalibrationOverlay, } from './features/calibration.js'; // Layer 6: tabs, navigation, command palette, settings import { switchTab, initTabs, startAutoRefresh, handlePopState } from './features/tabs.js'; import { navigateToCard } from './core/navigation.js'; import { openCommandPalette, closeCommandPalette, initCommandPalette } from './core/command-palette.js'; import { openSettingsModal, closeSettingsModal, downloadBackup, handleRestoreFileSelected, saveAutoBackupSettings, restoreSavedBackup, downloadSavedBackup, deleteSavedBackup, } from './features/settings.js'; // ─── Register all HTML onclick / onchange / onfocus globals ─── Object.assign(window, { // core / state (for inline script) setApiKey, // core / ui toggleHint, lockBody, unlockBody, closeLightbox, showToast, showConfirm, closeConfirmModal, openFullImageLightbox, showOverlaySpinner, hideOverlaySpinner, // core / api + i18n t, configureApiKey, loadServerInfo, loadDisplays, changeLocale, // displays openDisplayPicker, closeDisplayPicker, selectDisplay, formatDisplayLabel, // tutorials startCalibrationTutorial, startDeviceTutorial, startGettingStartedTutorial, startDashboardTutorial, startTargetsTutorial, startSourcesTutorial, startAutomationsTutorial, closeTutorial, tutorialNext, tutorialPrev, // devices showSettings, closeDeviceSettingsModal, forceCloseDeviceSettingsModal, saveDeviceSettings, updateBrightnessLabel, saveCardBrightness, turnOffDevice, removeDevice, loadDevices, updateSettingsBaudFpsHint, copyWsUrl, // dashboard loadDashboard, dashboardToggleAutomation, dashboardStartTarget, dashboardStopTarget, dashboardStopAll, dashboardPauseClock, dashboardResumeClock, dashboardResetClock, toggleDashboardSection, changeDashboardPollInterval, stopUptimeTimer, startPerfPolling, stopPerfPolling, // streams / capture templates / PP templates loadPictureSources, switchStreamTab, expandAllStreamSections, collapseAllStreamSections, showAddTemplateModal, editTemplate, closeTemplateModal, saveTemplate, deleteTemplate, showTestTemplateModal, closeTestTemplateModal, onEngineChange, runTemplateTest, updateCaptureDuration, showAddStreamModal, editStream, closeStreamModal, saveStream, deleteStream, onStreamTypeChange, onStreamDisplaySelected, onTestDisplaySelected, showTestStreamModal, closeTestStreamModal, updateStreamTestDuration, runStreamTest, showTestPPTemplateModal, closeTestPPTemplateModal, updatePPTestDuration, runPPTemplateTest, showAddPPTemplateModal, editPPTemplate, closePPTemplateModal, savePPTemplate, deletePPTemplate, addFilterFromSelect, toggleFilterExpand, removeFilter, moveFilter, updateFilterOption, renderModalFilterList, cloneStream, cloneCaptureTemplate, clonePPTemplate, showAddAudioTemplateModal, editAudioTemplate, closeAudioTemplateModal, saveAudioTemplate, deleteAudioTemplate, cloneAudioTemplate, onAudioEngineChange, showTestAudioTemplateModal, closeTestAudioTemplateModal, startAudioTemplateTest, // kc-targets createKCTargetCard, testKCTarget, toggleKCTestAutoRefresh, showKCEditor, closeKCEditorModal, forceCloseKCEditorModal, saveKCEditor, deleteKCTarget, disconnectAllKCWebSockets, updateKCBrightnessLabel, saveKCBrightness, cloneKCTarget, // pattern-templates createPatternTemplateCard, showPatternTemplateEditor, closePatternTemplateModal, forceClosePatternTemplateModal, savePatternTemplate, deletePatternTemplate, renderPatternRectList, selectPatternRect, updatePatternRect, addPatternRect, deleteSelectedPatternRect, removePatternRect, capturePatternBackground, clonePatternTemplate, // automations loadAutomations, openAutomationEditor, closeAutomationEditorModal, saveAutomationEditor, addAutomationCondition, toggleAutomationEnabled, deleteAutomation, copyWebhookUrl, expandAllAutomationSections, collapseAllAutomationSections, // scene presets openScenePresetCapture, editScenePreset, saveScenePreset, closeScenePresetEditor, activateScenePreset, recaptureScenePreset, cloneScenePreset, deleteScenePreset, addSceneTarget, removeSceneTarget, // device-discovery onDeviceTypeChanged, updateBaudFpsHint, onSerialPortFocus, showAddDevice, closeAddDeviceModal, scanForDevices, handleAddDevice, // targets loadTargetsTab, switchTargetSubTab, expandAllTargetSections, collapseAllTargetSections, showTargetEditor, closeTargetEditorModal, forceCloseTargetEditorModal, saveTargetEditor, startTargetProcessing, stopTargetProcessing, stopAllLedTargets, stopAllKCTargets, startTargetOverlay, stopTargetOverlay, deleteTarget, cloneTarget, toggleLedPreview, disconnectAllLedPreviewWS, // color-strip sources showCSSEditor, closeCSSEditorModal, forceCSSEditorClose, saveCSSEditor, deleteColorStrip, onCSSTypeChange, onEffectTypeChange, onCSSClockChange, onAnimationTypeChange, updateEffectPreview, colorCycleAddColor, colorCycleRemoveColor, compositeAddLayer, compositeRemoveLayer, mappedAddZone, mappedRemoveZone, onAudioVizChange, applyGradientPreset, cloneColorStrip, copyEndpointUrl, onNotificationFilterModeChange, notificationAddAppColor, notificationRemoveAppColor, // audio sources showAudioSourceModal, closeAudioSourceModal, saveAudioSource, editAudioSource, cloneAudioSource, deleteAudioSource, testAudioSource, closeTestAudioSourceModal, // value sources showValueSourceModal, closeValueSourceModal, saveValueSource, editValueSource, cloneValueSource, deleteValueSource, onValueSourceTypeChange, addSchedulePoint, testValueSource, closeTestValueSourceModal, // calibration showCalibration, closeCalibrationModal, forceCloseCalibrationModal, saveCalibration, updateOffsetSkipLock, updateCalibrationPreview, setStartPosition, toggleEdgeInputs, toggleDirection, toggleTestEdge, showCSSCalibration, toggleCalibrationOverlay, // tabs / navigation / command palette switchTab, startAutoRefresh, navigateToCard, openCommandPalette, closeCommandPalette, // settings (backup / restore / auto-backup) openSettingsModal, closeSettingsModal, downloadBackup, handleRestoreFileSelected, saveAutoBackupSettings, restoreSavedBackup, downloadSavedBackup, deleteSavedBackup, }); // ─── Global keyboard shortcuts ─── document.addEventListener('keydown', (e) => { const tag = document.activeElement?.tagName; const inInput = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT'; // Command palette: Ctrl+K / Cmd+K (works even in inputs) if ((e.ctrlKey || e.metaKey) && e.key === 'k' && !e.altKey && !e.shiftKey) { e.preventDefault(); openCommandPalette(); return; } // Tab shortcuts: Ctrl+1..4 (skip when typing in inputs) if (!inInput && e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { const tabMap = { '1': 'dashboard', '2': 'automations', '3': 'targets', '4': 'streams' }; const tab = tabMap[e.key]; if (tab) { e.preventDefault(); switchTab(tab); return; } } if (e.key === 'Escape') { // Close in order: overlay lightboxes first, then modals via stack if (document.getElementById('display-picker-lightbox').classList.contains('active')) { closeDisplayPicker(); } else if (document.getElementById('image-lightbox').classList.contains('active')) { closeLightbox(); } else { Modal.closeTopmost(); } } }); // ─── Browser back/forward via hash routing ─── window.addEventListener('popstate', handlePopState); // ─── Cleanup on page unload ─── window.addEventListener('beforeunload', () => { if (refreshInterval) { clearInterval(refreshInterval); } stopEventsWS(); disconnectAllKCWebSockets(); disconnectAllLedPreviewWS(); }); // ─── Initialization ─── document.addEventListener('DOMContentLoaded', async () => { // Load API key from localStorage before anything that triggers API calls setApiKey(localStorage.getItem('wled_api_key')); // Initialize locale (dispatches languageChanged which may trigger API calls) await initLocale(); // Restore active tab before showing content to avoid visible jump initTabs(); // Show content now that translations are loaded and tabs are set document.body.style.visibility = 'visible'; // Set CSS variable for sticky header height so section headers stack below it const headerEl = document.querySelector('header'); if (headerEl) { const updateHeaderHeight = () => { document.documentElement.style.setProperty( '--header-height', headerEl.offsetHeight + 'px' ); }; updateHeaderHeight(); window.addEventListener('resize', updateHeaderHeight); } // Initialize command palette initCommandPalette(); // Setup form handler document.getElementById('add-device-form').addEventListener('submit', handleAddDevice); // Show modal if no API key is stored if (!apiKey) { setTimeout(() => { if (typeof window.showApiKeyModal === 'function') { window.showApiKeyModal(null, true); } }, 100); return; } // User is logged in, load data loadServerInfo(); loadDisplays(); loadTargetsTab(); // Start global events WebSocket and auto-refresh startEventsWS(); startAutoRefresh(); // Show getting-started tutorial on first visit if (!localStorage.getItem('tour_completed')) { setTimeout(() => startGettingStartedTutorial(), 600); } });