New source_type "notification" fires one-shot visual effects (flash, pulse, sweep) triggered via POST webhook. Designed as a composite layer for overlay on persistent sources. Includes app color mapping, whitelist/blacklist filtering, and auto-sizing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
549 lines
16 KiB
JavaScript
549 lines
16 KiB
JavaScript
/**
|
|
* 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);
|
|
}
|
|
});
|