Split monolithic app.js into native ES modules
Replace the single 7034-line app.js with 17 ES module files organized into core/ (state, api, i18n, ui) and features/ (calibration, dashboard, device-discovery, devices, displays, kc-targets, pattern-templates, profiles, streams, tabs, targets, tutorials) with an app.js entry point that registers ~90 onclick globals on window. No bundler needed — FastAPI serves modules directly via <script type="module">. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
112
server/src/wled_controller/static/js/features/displays.js
Normal file
112
server/src/wled_controller/static/js/features/displays.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Display picker lightbox — display selection for streams and tests.
|
||||
*/
|
||||
|
||||
import {
|
||||
_cachedDisplays, _displayPickerCallback, _displayPickerSelectedIndex,
|
||||
set_displayPickerCallback, set_displayPickerSelectedIndex,
|
||||
} from '../core/state.js';
|
||||
import { t } from '../core/i18n.js';
|
||||
import { loadDisplays } from '../core/api.js';
|
||||
|
||||
export function openDisplayPicker(callback, selectedIndex) {
|
||||
set_displayPickerCallback(callback);
|
||||
set_displayPickerSelectedIndex((selectedIndex !== undefined && selectedIndex !== null && selectedIndex !== '') ? Number(selectedIndex) : null);
|
||||
const lightbox = document.getElementById('display-picker-lightbox');
|
||||
|
||||
lightbox.classList.add('active');
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (_cachedDisplays && _cachedDisplays.length > 0) {
|
||||
renderDisplayPickerLayout(_cachedDisplays);
|
||||
} else {
|
||||
const canvas = document.getElementById('display-picker-canvas');
|
||||
canvas.innerHTML = '<div class="loading-spinner"></div>';
|
||||
loadDisplays().then(() => {
|
||||
// Re-import to get updated value
|
||||
import('../core/state.js').then(({ _cachedDisplays: displays }) => {
|
||||
if (displays && displays.length > 0) {
|
||||
renderDisplayPickerLayout(displays);
|
||||
} else {
|
||||
canvas.innerHTML = `<div class="loading">${t('displays.none')}</div>`;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function closeDisplayPicker(event) {
|
||||
if (event && event.target && event.target.closest('.display-picker-content')) return;
|
||||
const lightbox = document.getElementById('display-picker-lightbox');
|
||||
lightbox.classList.remove('active');
|
||||
set_displayPickerCallback(null);
|
||||
}
|
||||
|
||||
export function selectDisplay(displayIndex) {
|
||||
// Re-read live bindings
|
||||
import('../core/state.js').then(({ _displayPickerCallback: cb, _cachedDisplays: displays }) => {
|
||||
if (cb) {
|
||||
const display = displays ? displays.find(d => d.index === displayIndex) : null;
|
||||
cb(displayIndex, display);
|
||||
}
|
||||
closeDisplayPicker();
|
||||
});
|
||||
}
|
||||
|
||||
export function renderDisplayPickerLayout(displays) {
|
||||
const canvas = document.getElementById('display-picker-canvas');
|
||||
|
||||
if (!displays || displays.length === 0) {
|
||||
canvas.innerHTML = `<div class="loading">${t('displays.none')}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||
displays.forEach(display => {
|
||||
minX = Math.min(minX, display.x);
|
||||
minY = Math.min(minY, display.y);
|
||||
maxX = Math.max(maxX, display.x + display.width);
|
||||
maxY = Math.max(maxY, display.y + display.height);
|
||||
});
|
||||
|
||||
const totalWidth = maxX - minX;
|
||||
const totalHeight = maxY - minY;
|
||||
const aspect = totalHeight / totalWidth;
|
||||
|
||||
const displayElements = displays.map(display => {
|
||||
const leftPct = ((display.x - minX) / totalWidth) * 100;
|
||||
const topPct = ((display.y - minY) / totalHeight) * 100;
|
||||
const widthPct = (display.width / totalWidth) * 100;
|
||||
const heightPct = (display.height / totalHeight) * 100;
|
||||
|
||||
const isSelected = _displayPickerSelectedIndex !== null && display.index === _displayPickerSelectedIndex;
|
||||
return `
|
||||
<div class="layout-display layout-display-pickable${isSelected ? ' selected' : ''}"
|
||||
style="left: ${leftPct}%; top: ${topPct}%; width: ${widthPct}%; height: ${heightPct}%;"
|
||||
onclick="selectDisplay(${display.index})"
|
||||
title="${t('displays.picker.click_to_select')}">
|
||||
<div class="layout-position-label">(${display.x}, ${display.y})</div>
|
||||
<div class="layout-index-label">#${display.index}</div>
|
||||
<div class="layout-display-label">
|
||||
<strong>${display.name}</strong>
|
||||
<small>${display.width}×${display.height}</small>
|
||||
<small>${display.refresh_rate}Hz</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
canvas.innerHTML = `
|
||||
<div class="layout-container" style="width: 100%; padding-bottom: ${aspect * 100}%; position: relative;">
|
||||
${displayElements}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
export function formatDisplayLabel(displayIndex, display) {
|
||||
if (display) {
|
||||
return `#${display.index}: ${display.width}×${display.height}${display.is_primary ? ' (' + t('displays.badge.primary') + ')' : ''}`;
|
||||
}
|
||||
return `Display ${displayIndex}`;
|
||||
}
|
||||
Reference in New Issue
Block a user