Split monolithic index.html and style.css for maintainability
- Extract 15 modals and 3 partials from index.html into Jinja2 templates (templates/modals/*.html, templates/partials/*.html) - Split style.css (3,712 lines) into 11 feature-scoped CSS files under static/css/ (base, layout, components, cards, modal, calibration, dashboard, streams, patterns, profiles, tutorials) - Switch root route from FileResponse to Jinja2Templates - Add jinja2 dependency - Consolidate duplicate @keyframes spin definition - Browser receives identical assembled HTML — zero JS changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
89
server/src/wled_controller/templates/modals/add-device.html
Normal file
89
server/src/wled_controller/templates/modals/add-device.html
Normal file
@@ -0,0 +1,89 @@
|
||||
<!-- Add Device Modal -->
|
||||
<div id="add-device-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 data-i18n="devices.add">Add New Device</h2>
|
||||
<div class="modal-header-actions">
|
||||
<button type="button" class="modal-header-btn" id="scan-network-btn" onclick="scanForDevices()" data-i18n-title="device.scan" title="Auto Discovery">🔍</button>
|
||||
<button class="modal-close-btn" onclick="closeAddDeviceModal()" title="Close">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="discovery-section" class="discovery-section" style="display: none;">
|
||||
<div id="discovery-loading" class="discovery-loading" style="display: none;">
|
||||
<span class="discovery-spinner"></span>
|
||||
</div>
|
||||
<div id="discovery-list" class="discovery-list"></div>
|
||||
<div id="discovery-empty" style="display: none;">
|
||||
<small data-i18n="device.scan.empty">No devices found</small>
|
||||
</div>
|
||||
<hr class="modal-divider">
|
||||
</div>
|
||||
<form id="add-device-form">
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="device-type" data-i18n="device.type">Device Type:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="device.type.hint">Select the type of LED controller</small>
|
||||
<select id="device-type" onchange="onDeviceTypeChanged()">
|
||||
<option value="wled">WLED</option>
|
||||
<option value="adalight">Adalight</option>
|
||||
<option value="ambiled">AmbiLED</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="device-name" data-i18n="device.name">Device Name:</label>
|
||||
<input type="text" id="device-name" data-i18n-placeholder="device.name.placeholder" placeholder="Living Room TV" required>
|
||||
</div>
|
||||
<div class="form-group" id="device-url-group">
|
||||
<div class="label-row">
|
||||
<label for="device-url" id="device-url-label" data-i18n="device.url">URL:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" id="device-url-hint" data-i18n="device.url.hint">IP address or hostname of the device (e.g. http://192.168.1.100)</small>
|
||||
<input type="text" id="device-url" data-i18n-placeholder="device.url.placeholder" placeholder="http://192.168.1.100" required>
|
||||
</div>
|
||||
<div class="form-group" id="device-serial-port-group" style="display: none;">
|
||||
<div class="label-row">
|
||||
<label for="device-serial-port" id="device-serial-port-label" data-i18n="device.serial_port">Serial Port:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="device.serial_port.hint">Select the COM port of the Adalight device</small>
|
||||
<select id="device-serial-port" onfocus="onSerialPortFocus()"></select>
|
||||
</div>
|
||||
<div class="form-group" id="device-led-count-group" style="display: none;">
|
||||
<div class="label-row">
|
||||
<label for="device-led-count" data-i18n="device.led_count">LED Count:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="device.led_count_manual.hint">Number of LEDs on the strip (must match your Arduino sketch)</small>
|
||||
<input type="number" id="device-led-count" min="1" max="10000" placeholder="60" oninput="updateBaudFpsHint()">
|
||||
</div>
|
||||
<div class="form-group" id="device-baud-rate-group" style="display: none;">
|
||||
<div class="label-row">
|
||||
<label for="device-baud-rate" data-i18n="device.baud_rate">Baud Rate:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="device.baud_rate.hint">Serial communication speed. Higher = more FPS but requires matching Arduino sketch.</small>
|
||||
<select id="device-baud-rate" onchange="updateBaudFpsHint()">
|
||||
<option value="115200">115200</option>
|
||||
<option value="230400">230400</option>
|
||||
<option value="460800">460800</option>
|
||||
<option value="500000">500000</option>
|
||||
<option value="921600">921600</option>
|
||||
<option value="1000000">1000000</option>
|
||||
<option value="1500000">1500000</option>
|
||||
<option value="2000000">2000000</option>
|
||||
</select>
|
||||
<small id="baud-fps-hint" class="fps-hint" style="display:none"></small>
|
||||
</div>
|
||||
<div id="add-device-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeAddDeviceModal()" title="Cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="document.getElementById('add-device-form').requestSubmit()" title="Add Device">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
40
server/src/wled_controller/templates/modals/api-key.html
Normal file
40
server/src/wled_controller/templates/modals/api-key.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!-- Login Modal -->
|
||||
<div id="api-key-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 data-i18n="auth.title">🔑 Login to LED Grab</h2>
|
||||
<button class="modal-close-btn" id="modal-close-x-btn" onclick="closeApiKeyModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<form id="api-key-form" onsubmit="submitApiKey(event)">
|
||||
<div class="modal-body">
|
||||
<p class="modal-description" data-i18n="auth.message">
|
||||
Please enter your API key to authenticate and access the LED Grab.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="api-key-input" data-i18n="auth.label">API Key:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="auth.hint">Your API key will be stored securely in your browser's local storage.</small>
|
||||
<div class="password-input-wrapper">
|
||||
<input
|
||||
type="password"
|
||||
id="api-key-input"
|
||||
data-i18n-placeholder="auth.placeholder"
|
||||
placeholder="Enter your API key..."
|
||||
autocomplete="off"
|
||||
>
|
||||
<button type="button" class="password-toggle" onclick="togglePasswordVisibility()">
|
||||
👁️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="api-key-error" class="error-message" style="display: none;"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-icon btn-secondary" onclick="closeApiKeyModal()" id="modal-cancel-btn" title="Cancel">✕</button>
|
||||
<button type="submit" class="btn btn-icon btn-primary" title="Login">✓</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
144
server/src/wled_controller/templates/modals/calibration.html
Normal file
144
server/src/wled_controller/templates/modals/calibration.html
Normal file
@@ -0,0 +1,144 @@
|
||||
<!-- Calibration Modal -->
|
||||
<div id="calibration-modal" class="modal">
|
||||
<div class="modal-content" style="max-width: 700px;">
|
||||
<div class="modal-header">
|
||||
<h2 data-i18n="calibration.title">📐 LED Calibration</h2>
|
||||
<button class="tutorial-trigger-btn" onclick="startCalibrationTutorial()" data-i18n-title="calibration.tutorial.start" title="Start tutorial">?</button>
|
||||
<button class="modal-close-btn" onclick="closeCalibrationModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="calibration-device-id">
|
||||
<!-- Interactive Preview with integrated LED inputs and test toggles -->
|
||||
<div style="margin-bottom: 12px; padding: 0 24px;">
|
||||
<div class="calibration-preview">
|
||||
<!-- Screen with direction toggle, total LEDs, and offset -->
|
||||
<div class="preview-screen">
|
||||
<button type="button" class="direction-toggle" onclick="toggleDirection()" title="Toggle direction">
|
||||
<span id="direction-icon">↻</span> <span id="direction-label">CW</span>
|
||||
</button>
|
||||
<div class="preview-screen-total" onclick="toggleEdgeInputs()" title="Toggle edge LED inputs"><span id="cal-total-leds-inline">0</span> / <span id="cal-device-led-count-inline">0</span></div>
|
||||
<div class="preview-screen-border-width">
|
||||
<label for="cal-border-width" data-i18n="calibration.border_width">Border (px):</label>
|
||||
<input type="number" id="cal-border-width" min="1" max="100" value="10">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edge bars with span controls and LED count inputs -->
|
||||
<div class="preview-edge edge-top">
|
||||
<div class="edge-span-bar" data-edge="top">
|
||||
<div class="edge-span-handle edge-span-handle-start" data-edge="top" data-handle="start"></div>
|
||||
<div class="edge-span-handle edge-span-handle-end" data-edge="top" data-handle="end"></div>
|
||||
</div>
|
||||
<input type="number" id="cal-top-leds" class="edge-led-input" min="0" value="0"
|
||||
oninput="updateCalibrationPreview()">
|
||||
</div>
|
||||
<div class="preview-edge edge-right">
|
||||
<div class="edge-span-bar" data-edge="right">
|
||||
<div class="edge-span-handle edge-span-handle-start" data-edge="right" data-handle="start"></div>
|
||||
<div class="edge-span-handle edge-span-handle-end" data-edge="right" data-handle="end"></div>
|
||||
</div>
|
||||
<input type="number" id="cal-right-leds" class="edge-led-input" min="0" value="0"
|
||||
oninput="updateCalibrationPreview()">
|
||||
</div>
|
||||
<div class="preview-edge edge-bottom">
|
||||
<div class="edge-span-bar" data-edge="bottom">
|
||||
<div class="edge-span-handle edge-span-handle-start" data-edge="bottom" data-handle="start"></div>
|
||||
<div class="edge-span-handle edge-span-handle-end" data-edge="bottom" data-handle="end"></div>
|
||||
</div>
|
||||
<input type="number" id="cal-bottom-leds" class="edge-led-input" min="0" value="0"
|
||||
oninput="updateCalibrationPreview()">
|
||||
</div>
|
||||
<div class="preview-edge edge-left">
|
||||
<div class="edge-span-bar" data-edge="left">
|
||||
<div class="edge-span-handle edge-span-handle-start" data-edge="left" data-handle="start"></div>
|
||||
<div class="edge-span-handle edge-span-handle-end" data-edge="left" data-handle="end"></div>
|
||||
</div>
|
||||
<input type="number" id="cal-left-leds" class="edge-led-input" min="0" value="0"
|
||||
oninput="updateCalibrationPreview()">
|
||||
</div>
|
||||
|
||||
<!-- Edge test toggle zones (outside container border, in tick area) -->
|
||||
<div class="edge-toggle toggle-top" onclick="toggleTestEdge('top')"></div>
|
||||
<div class="edge-toggle toggle-right" onclick="toggleTestEdge('right')"></div>
|
||||
<div class="edge-toggle toggle-bottom" onclick="toggleTestEdge('bottom')"></div>
|
||||
<div class="edge-toggle toggle-left" onclick="toggleTestEdge('left')"></div>
|
||||
|
||||
<!-- Corner start position buttons -->
|
||||
<div class="preview-corner corner-top-left" onclick="setStartPosition('top_left')">●</div>
|
||||
<div class="preview-corner corner-top-right" onclick="setStartPosition('top_right')">●</div>
|
||||
<div class="preview-corner corner-bottom-left" onclick="setStartPosition('bottom_left')">●</div>
|
||||
<div class="preview-corner corner-bottom-right" onclick="setStartPosition('bottom_right')">●</div>
|
||||
|
||||
<!-- Canvas overlay for ticks, arrows, start label -->
|
||||
<canvas id="calibration-preview-canvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden selects (used by saveCalibration) -->
|
||||
<div style="display: none;">
|
||||
<select id="cal-start-position">
|
||||
<option value="bottom_left">Bottom Left</option>
|
||||
<option value="bottom_right">Bottom Right</option>
|
||||
<option value="top_left">Top Left</option>
|
||||
<option value="top_right">Top Right</option>
|
||||
</select>
|
||||
<select id="cal-layout">
|
||||
<option value="clockwise">Clockwise</option>
|
||||
<option value="counterclockwise">Counterclockwise</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Offset & Skip LEDs -->
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; padding: 0 24px;">
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="cal-offset" data-i18n="calibration.offset">LED Offset:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="calibration.offset.hint">Distance from physical LED 0 to the start corner (along strip direction)</small>
|
||||
<input type="number" id="cal-offset" min="0" value="0" oninput="updateOffsetSkipLock(); updateCalibrationPreview()">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="cal-skip-start" data-i18n="calibration.skip_start">Skip LEDs (Start):</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="calibration.skip_start.hint">Number of LEDs to turn off at the beginning of the strip (0 = none)</small>
|
||||
<input type="number" id="cal-skip-start" min="0" value="0" oninput="updateOffsetSkipLock(); updateCalibrationPreview()">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="cal-skip-end" data-i18n="calibration.skip_end">Skip LEDs (End):</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="calibration.skip_end.hint">Number of LEDs to turn off at the end of the strip (0 = none)</small>
|
||||
<input type="number" id="cal-skip-end" min="0" value="0" oninput="updateOffsetSkipLock(); updateCalibrationPreview()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tutorial Overlay -->
|
||||
<div id="tutorial-overlay" class="tutorial-overlay">
|
||||
<div class="tutorial-backdrop"></div>
|
||||
<div class="tutorial-ring"></div>
|
||||
<div class="tutorial-tooltip">
|
||||
<div class="tutorial-tooltip-header">
|
||||
<span class="tutorial-step-counter"></span>
|
||||
<button class="tutorial-close-btn" onclick="closeTutorial()">×</button>
|
||||
</div>
|
||||
<p class="tutorial-tooltip-text"></p>
|
||||
<div class="tutorial-tooltip-nav">
|
||||
<button class="tutorial-prev-btn" onclick="tutorialPrev()">←</button>
|
||||
<button class="tutorial-next-btn" onclick="tutorialNext()">→</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calibration-error" class="error-message" style="display: none;"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeCalibrationModal()" title="Cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveCalibration()" title="Save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- Template Modal -->
|
||||
<div id="template-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="template-modal-title" data-i18n="templates.add">Add Capture Template</h2>
|
||||
<button class="modal-close-btn" onclick="closeTemplateModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="template-id">
|
||||
<form id="template-form">
|
||||
<div class="form-group">
|
||||
<label for="template-name" data-i18n="templates.name">Template Name:</label>
|
||||
<input type="text" id="template-name" data-i18n-placeholder="templates.name.placeholder" placeholder="My Custom Template" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="template-description" data-i18n="templates.description.label">Description (optional):</label>
|
||||
<input type="text" id="template-description" data-i18n-placeholder="templates.description.placeholder" placeholder="Describe this template..." maxlength="500">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="template-engine" data-i18n="templates.engine">Capture Engine:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="templates.engine.hint">Select the screen capture technology to use</small>
|
||||
<select id="template-engine" onchange="onEngineChange()" required>
|
||||
</select>
|
||||
<small id="engine-availability-hint" class="form-hint" style="display: none;"></small>
|
||||
</div>
|
||||
|
||||
<div id="engine-config-section" style="display: none;">
|
||||
<h3 data-i18n="templates.config">Configuration</h3>
|
||||
<div id="engine-config-fields"></div>
|
||||
</div>
|
||||
|
||||
<div id="template-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeTemplateModal()" title="Cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveTemplate()" title="Save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
16
server/src/wled_controller/templates/modals/confirm.html
Normal file
16
server/src/wled_controller/templates/modals/confirm.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- Confirmation Modal -->
|
||||
<div id="confirm-modal" class="modal">
|
||||
<div class="modal-content" style="max-width: 450px;">
|
||||
<div class="modal-header">
|
||||
<h2 id="confirm-title">Confirm Action</h2>
|
||||
<button class="modal-close-btn" onclick="closeConfirmModal(false)" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="confirm-message" class="modal-description"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-danger" id="confirm-no-btn" onclick="closeConfirmModal(false)">No</button>
|
||||
<button class="btn btn-secondary" id="confirm-yes-btn" onclick="closeConfirmModal(true)">Yes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,90 @@
|
||||
<!-- General Settings Modal -->
|
||||
<div id="device-settings-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 data-i18n="settings.general.title">⚙️ General Settings</h2>
|
||||
<button class="modal-close-btn" onclick="closeDeviceSettingsModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="device-settings-form">
|
||||
<input type="hidden" id="settings-device-id">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="settings-device-name" data-i18n="device.name">Device Name:</label>
|
||||
<input type="text" id="settings-device-name" data-i18n-placeholder="device.name.placeholder" placeholder="Living Room TV" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="settings-url-group">
|
||||
<div class="label-row">
|
||||
<label for="settings-device-url" data-i18n="device.url">URL:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.url.hint">IP address or hostname of the device</small>
|
||||
<input type="text" id="settings-device-url" data-i18n-placeholder="device.url.placeholder" placeholder="http://192.168.1.100" required>
|
||||
</div>
|
||||
<div class="form-group" id="settings-serial-port-group" style="display: none;">
|
||||
<div class="label-row">
|
||||
<label for="settings-serial-port" data-i18n="device.serial_port">Serial Port:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="device.serial_port.hint">Select the COM port of the Adalight device</small>
|
||||
<select id="settings-serial-port"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="settings-led-count-group" style="display: none;">
|
||||
<div class="label-row">
|
||||
<label for="settings-led-count" data-i18n="device.led_count">LED Count:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="device.led_count_manual.hint">Number of LEDs on the strip (must match your Arduino sketch)</small>
|
||||
<input type="number" id="settings-led-count" min="1" max="10000" oninput="updateSettingsBaudFpsHint()">
|
||||
</div>
|
||||
<div class="form-group" id="settings-baud-rate-group" style="display: none;">
|
||||
<div class="label-row">
|
||||
<label for="settings-baud-rate" data-i18n="device.baud_rate">Baud Rate:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="device.baud_rate.hint">Serial communication speed. Higher = more FPS but requires matching Arduino sketch.</small>
|
||||
<select id="settings-baud-rate" onchange="updateSettingsBaudFpsHint()">
|
||||
<option value="115200">115200</option>
|
||||
<option value="230400">230400</option>
|
||||
<option value="460800">460800</option>
|
||||
<option value="500000">500000</option>
|
||||
<option value="921600">921600</option>
|
||||
<option value="1000000">1000000</option>
|
||||
<option value="1500000">1500000</option>
|
||||
<option value="2000000">2000000</option>
|
||||
</select>
|
||||
<small id="settings-baud-fps-hint" class="fps-hint" style="display:none"></small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="settings-health-interval" data-i18n="settings.health_interval">Health Check Interval (s):</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.health_interval.hint">How often to check the device status (5-600 seconds)</small>
|
||||
<input type="number" id="settings-health-interval" min="5" max="600" value="30">
|
||||
</div>
|
||||
|
||||
<div class="form-group settings-toggle-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="settings.auto_shutdown">Auto Restore:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="settings.auto_shutdown.hint">Restore device to idle state when targets stop or server shuts down</small>
|
||||
<label class="settings-toggle">
|
||||
<input type="checkbox" id="settings-auto-shutdown">
|
||||
<span class="settings-toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="settings-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeDeviceSettingsModal()" title="Cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveDeviceSettings()" title="Save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
80
server/src/wled_controller/templates/modals/kc-editor.html
Normal file
80
server/src/wled_controller/templates/modals/kc-editor.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<!-- Key Colors Editor Modal -->
|
||||
<div id="kc-editor-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="kc-editor-title" data-i18n="kc.add">🎨 Add Key Colors Target</h2>
|
||||
<button class="modal-close-btn" onclick="closeKCEditorModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="kc-editor-form">
|
||||
<input type="hidden" id="kc-editor-id">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="kc-editor-name" data-i18n="kc.name">Target Name:</label>
|
||||
<input type="text" id="kc-editor-name" data-i18n-placeholder="kc.name.placeholder" placeholder="My Key Colors Target" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="kc-editor-source" data-i18n="kc.source">Picture Source:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="kc.source.hint">Which picture source to extract colors from</small>
|
||||
<select id="kc-editor-source"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="kc-editor-pattern-template" data-i18n="kc.pattern_template">Pattern Template:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="kc.pattern_template.hint">Select the rectangle pattern to use for color extraction</small>
|
||||
<select id="kc-editor-pattern-template"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="kc-editor-fps" data-i18n="kc.fps">Extraction FPS:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="kc.fps.hint">How many times per second to extract colors (1-60)</small>
|
||||
<div class="slider-row">
|
||||
<input type="range" id="kc-editor-fps" min="1" max="60" value="10" oninput="document.getElementById('kc-editor-fps-value').textContent = this.value">
|
||||
<span id="kc-editor-fps-value" class="slider-value">10</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="kc-editor-interpolation" data-i18n="kc.interpolation">Color Mode:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="kc.interpolation.hint">How to compute the key color from pixels in each rectangle</small>
|
||||
<select id="kc-editor-interpolation">
|
||||
<option value="average">Average</option>
|
||||
<option value="median">Median</option>
|
||||
<option value="dominant">Dominant</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="kc-editor-smoothing">
|
||||
<span data-i18n="kc.smoothing">Smoothing:</span>
|
||||
<span id="kc-editor-smoothing-value">0.3</span>
|
||||
</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="kc.smoothing.hint">Temporal blending between extractions (0=none, 1=full)</small>
|
||||
<input type="range" id="kc-editor-smoothing" min="0.0" max="1.0" step="0.05" value="0.3" oninput="document.getElementById('kc-editor-smoothing-value').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div id="kc-editor-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeKCEditorModal()" title="Cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveKCEditor()" title="Save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,67 @@
|
||||
<!-- Pattern Template Editor Modal -->
|
||||
<div id="pattern-template-modal" class="modal">
|
||||
<div class="modal-content modal-content-wide">
|
||||
<div class="modal-header">
|
||||
<h2 id="pattern-template-title" data-i18n="pattern.add">📄 Add Pattern Template</h2>
|
||||
<button class="modal-close-btn" onclick="closePatternTemplateModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="pattern-template-form">
|
||||
<input type="hidden" id="pattern-template-id">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="pattern-template-name" data-i18n="pattern.name">Template Name:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="pattern.name.hint">A descriptive name for this rectangle layout</small>
|
||||
<input type="text" id="pattern-template-name" data-i18n-placeholder="pattern.name.placeholder" placeholder="My Pattern Template" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="pattern-template-description" data-i18n="pattern.description_label">Description (optional):</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="pattern.description.hint">Optional notes about where or how this pattern is used</small>
|
||||
<input type="text" id="pattern-template-description" data-i18n-placeholder="pattern.description_placeholder" placeholder="Describe this pattern...">
|
||||
</div>
|
||||
|
||||
<!-- Visual Editor -->
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="pattern.visual_editor">Visual Editor</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="pattern.visual_editor.hint">Click + buttons to add rectangles. Drag edges to resize, drag inside to move.</small>
|
||||
<div class="pattern-bg-row">
|
||||
<select id="pattern-bg-source"></select>
|
||||
<button type="button" class="btn btn-icon btn-secondary pattern-capture-btn" onclick="capturePatternBackground()" title="Capture Background" data-i18n-title="pattern.capture_bg">📷</button>
|
||||
</div>
|
||||
<div class="pattern-canvas-container">
|
||||
<canvas id="pattern-canvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Precise coordinate list -->
|
||||
<div class="form-group">
|
||||
<div id="pattern-rect-labels" class="pattern-rect-labels">
|
||||
<span data-i18n="pattern.rect.name">Name</span>
|
||||
<span data-i18n="pattern.rect.x">X</span>
|
||||
<span data-i18n="pattern.rect.y">Y</span>
|
||||
<span data-i18n="pattern.rect.width">W</span>
|
||||
<span data-i18n="pattern.rect.height">H</span>
|
||||
<span></span>
|
||||
</div>
|
||||
<div id="pattern-rect-list" class="pattern-rect-list"></div>
|
||||
</div>
|
||||
|
||||
<div id="pattern-template-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closePatternTemplateModal()" title="Cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="savePatternTemplate()" title="Save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
40
server/src/wled_controller/templates/modals/pp-template.html
Normal file
40
server/src/wled_controller/templates/modals/pp-template.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!-- Processing Template Modal -->
|
||||
<div id="pp-template-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="pp-template-modal-title" data-i18n="postprocessing.add">Add Processing Template</h2>
|
||||
<button class="modal-close-btn" onclick="closePPTemplateModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="pp-template-id">
|
||||
<form id="pp-template-form">
|
||||
<div class="form-group">
|
||||
<label for="pp-template-name" data-i18n="postprocessing.name">Template Name:</label>
|
||||
<input type="text" id="pp-template-name" data-i18n-placeholder="postprocessing.name.placeholder" placeholder="My Processing Template" required>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic filter list -->
|
||||
<div id="pp-filter-list" class="pp-filter-list"></div>
|
||||
|
||||
<!-- Add filter control -->
|
||||
<div class="pp-add-filter-row">
|
||||
<select id="pp-add-filter-select" class="pp-add-filter-select">
|
||||
<option value="" data-i18n="filters.select_type">Select filter type...</option>
|
||||
</select>
|
||||
<button type="button" class="pp-add-filter-btn" onclick="addFilterFromSelect()" title="Add Filter">+</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="pp-template-description" data-i18n="postprocessing.description_label">Description (optional):</label>
|
||||
<input type="text" id="pp-template-description" data-i18n-placeholder="postprocessing.description_placeholder" placeholder="Describe this template...">
|
||||
</div>
|
||||
|
||||
<div id="pp-template-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closePPTemplateModal()" title="Cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="savePPTemplate()" title="Save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,74 @@
|
||||
<!-- Profile Editor Modal -->
|
||||
<div id="profile-editor-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="profile-editor-title" data-i18n="profiles.add">📋 Add Profile</h2>
|
||||
<button class="modal-close-btn" onclick="closeProfileEditorModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="profile-editor-form">
|
||||
<input type="hidden" id="profile-editor-id">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="profile-editor-name" data-i18n="profiles.name">Name:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="profiles.name.hint">A descriptive name for this profile</small>
|
||||
<input type="text" id="profile-editor-name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group settings-toggle-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="profiles.enabled">Enabled:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="profiles.enabled.hint">Disabled profiles won't activate even when conditions are met</small>
|
||||
<label class="settings-toggle">
|
||||
<input type="checkbox" id="profile-editor-enabled" checked>
|
||||
<span class="settings-toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="profile-editor-logic" data-i18n="profiles.condition_logic">Condition Logic:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="profiles.condition_logic.hint">How multiple conditions are combined: ANY (OR) or ALL (AND)</small>
|
||||
<select id="profile-editor-logic">
|
||||
<option value="or" data-i18n="profiles.condition_logic.or">Any condition (OR)</option>
|
||||
<option value="and" data-i18n="profiles.condition_logic.and">All conditions (AND)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="profiles.conditions">Conditions:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="profiles.conditions.hint">Rules that determine when this profile activates</small>
|
||||
<div id="profile-conditions-list"></div>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="addProfileCondition()" style="margin-top: 6px;">
|
||||
+ <span data-i18n="profiles.conditions.add">Add Condition</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="profiles.targets">Targets:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="profiles.targets.hint">Targets to start when this profile activates</small>
|
||||
<div id="profile-targets-list" class="profile-targets-checklist"></div>
|
||||
</div>
|
||||
|
||||
<div id="profile-editor-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeProfileEditorModal()" title="Cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveProfileEditor()" title="Save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
102
server/src/wled_controller/templates/modals/stream.html
Normal file
102
server/src/wled_controller/templates/modals/stream.html
Normal file
@@ -0,0 +1,102 @@
|
||||
<!-- Source Modal -->
|
||||
<div id="stream-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="stream-modal-title" data-i18n="streams.add">Add Source</h2>
|
||||
<button class="modal-close-btn" onclick="closeStreamModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="stream-id">
|
||||
<form id="stream-form">
|
||||
<div class="form-group">
|
||||
<label for="stream-name" data-i18n="streams.name">Source Name:</label>
|
||||
<input type="text" id="stream-name" data-i18n-placeholder="streams.name.placeholder" placeholder="My Source" required>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="stream-type" value="raw">
|
||||
|
||||
<!-- Raw source fields -->
|
||||
<div id="stream-raw-fields">
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label data-i18n="streams.display">Display:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="streams.display.hint">Which screen to capture</small>
|
||||
<input type="hidden" id="stream-display-index" value="">
|
||||
<button type="button" class="btn btn-display-picker" id="stream-display-picker-btn" onclick="openDisplayPicker(onStreamDisplaySelected, document.getElementById('stream-display-index').value)">
|
||||
<span id="stream-display-picker-label" data-i18n="displays.picker.select">Select display...</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="stream-capture-template" data-i18n="streams.capture_template">Capture Template:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="streams.capture_template.hint">Engine template defining how the screen is captured</small>
|
||||
<select id="stream-capture-template"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="stream-target-fps" data-i18n="streams.target_fps">Target FPS:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="streams.target_fps.hint">Target frames per second for capture (10-90)</small>
|
||||
<div class="slider-row">
|
||||
<input type="range" id="stream-target-fps" min="10" max="90" value="30" oninput="document.getElementById('stream-target-fps-value').textContent = this.value">
|
||||
<span id="stream-target-fps-value" class="slider-value">30</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Processed source fields -->
|
||||
<div id="stream-processed-fields" style="display: none;">
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="stream-source" data-i18n="streams.source">Source:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="streams.source.hint">The source to apply processing filters to</small>
|
||||
<select id="stream-source"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="stream-pp-template" data-i18n="streams.pp_template">Processing Template:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="streams.pp_template.hint">Filter template to apply to the source</small>
|
||||
<select id="stream-pp-template"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Static image fields -->
|
||||
<div id="stream-static-image-fields" style="display: none;">
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="stream-image-source" data-i18n="streams.image_source">Image Source:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="streams.image_source.hint">Enter a URL (http/https) or local file path to an image</small>
|
||||
<input type="text" id="stream-image-source" data-i18n-placeholder="streams.image_source.placeholder" placeholder="https://example.com/image.jpg or C:\path\to\image.png">
|
||||
</div>
|
||||
<div id="stream-image-preview-container" class="image-preview-container" style="display: none;">
|
||||
<img id="stream-image-preview" class="stream-image-preview" src="" alt="Preview">
|
||||
<div id="stream-image-info" class="stream-image-info"></div>
|
||||
</div>
|
||||
<div id="stream-image-validation-status" class="validation-status" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="stream-description" data-i18n="streams.description_label">Description (optional):</label>
|
||||
<input type="text" id="stream-description" data-i18n-placeholder="streams.description_placeholder" placeholder="Describe this source...">
|
||||
</div>
|
||||
|
||||
<div id="stream-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeStreamModal()" title="Cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveStream()" title="Save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,92 @@
|
||||
<!-- Target Editor Modal (name, device, source, settings) -->
|
||||
<div id="target-editor-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 id="target-editor-title" data-i18n="targets.add">🎯 Add Target</h2>
|
||||
<button class="modal-close-btn" onclick="closeTargetEditorModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="target-editor-form">
|
||||
<input type="hidden" id="target-editor-id">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="target-editor-name" data-i18n="targets.name">Target Name:</label>
|
||||
<input type="text" id="target-editor-name" data-i18n-placeholder="targets.name.placeholder" placeholder="My Target" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="target-editor-device" data-i18n="targets.device">Device:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="targets.device.hint">Select the LED device to send data to</small>
|
||||
<select id="target-editor-device"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="target-editor-source" data-i18n="targets.source">Picture Source:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="targets.source.hint">Select a source that defines what to capture</small>
|
||||
<select id="target-editor-source"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="target-editor-fps" data-i18n="targets.fps">Target FPS:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="targets.fps.hint">Target frames per second for capture and LED updates (10-90)</small>
|
||||
<div class="slider-row">
|
||||
<input type="range" id="target-editor-fps" min="10" max="90" value="30" oninput="document.getElementById('target-editor-fps-value').textContent = this.value">
|
||||
<span id="target-editor-fps-value" class="slider-value">30</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="target-editor-interpolation" data-i18n="targets.interpolation">Interpolation Mode:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="targets.interpolation.hint">How to calculate LED color from sampled pixels</small>
|
||||
<select id="target-editor-interpolation">
|
||||
<option value="average">Average</option>
|
||||
<option value="median">Median</option>
|
||||
<option value="dominant">Dominant</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="target-editor-smoothing">
|
||||
<span data-i18n="targets.smoothing">Smoothing:</span>
|
||||
<span id="target-editor-smoothing-value">0.3</span>
|
||||
</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="targets.smoothing.hint">Temporal blending between frames (0=none, 1=full). Reduces flicker.</small>
|
||||
<input type="range" id="target-editor-smoothing" min="0.0" max="1.0" step="0.05" value="0.3" oninput="document.getElementById('target-editor-smoothing-value').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="target-editor-standby-group">
|
||||
<div class="label-row">
|
||||
<label for="target-editor-standby-interval">
|
||||
<span data-i18n="targets.standby_interval">Standby Interval:</span>
|
||||
<span id="target-editor-standby-interval-value">1.0</span><span>s</span>
|
||||
</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="targets.standby_interval.hint">How often to resend the last frame when the screen is static, to keep the device in live mode (0.5-5.0s)</small>
|
||||
<input type="range" id="target-editor-standby-interval" min="0.5" max="5.0" step="0.5" value="1.0" oninput="document.getElementById('target-editor-standby-interval-value').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div id="target-editor-error" class="error-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeTargetEditorModal()" title="Cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveTargetEditor()" title="Save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
<!-- Test PP Template Modal -->
|
||||
<div id="test-pp-template-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 data-i18n="postprocessing.test.title">Test Processing Template</h2>
|
||||
<button class="modal-close-btn" onclick="closeTestPPTemplateModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label data-i18n="postprocessing.test.source_stream">Source:</label>
|
||||
<select id="test-pp-source-stream"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="test-pp-duration">
|
||||
<span data-i18n="streams.test.duration">Capture Duration (s):</span>
|
||||
<span id="test-pp-duration-value">5</span>
|
||||
</label>
|
||||
<input type="range" id="test-pp-duration" min="1" max="10" step="1" value="5" oninput="updatePPTestDuration(this.value)" />
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary" onclick="runPPTemplateTest()" style="margin-top: 16px;">
|
||||
<span data-i18n="streams.test.run">🧪 Run</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
23
server/src/wled_controller/templates/modals/test-stream.html
Normal file
23
server/src/wled_controller/templates/modals/test-stream.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!-- Test Source Modal -->
|
||||
<div id="test-stream-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 data-i18n="streams.test.title">Test Source</h2>
|
||||
<button class="modal-close-btn" onclick="closeTestStreamModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="test-stream-duration">
|
||||
<span data-i18n="streams.test.duration">Capture Duration (s):</span>
|
||||
<span id="test-stream-duration-value">5</span>
|
||||
</label>
|
||||
<input type="range" id="test-stream-duration" min="1" max="10" step="1" value="5" oninput="updateStreamTestDuration(this.value)" />
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary" onclick="runStreamTest()" style="margin-top: 16px;">
|
||||
<span data-i18n="streams.test.run">🧪 Run</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
<!-- Test Template Modal -->
|
||||
<div id="test-template-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 data-i18n="templates.test.title">Test Capture Template</h2>
|
||||
<button class="modal-close-btn" onclick="closeTestTemplateModal()" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label data-i18n="templates.test.display">Display:</label>
|
||||
<input type="hidden" id="test-template-display" value="">
|
||||
<button type="button" class="btn btn-display-picker" id="test-display-picker-btn" onclick="openDisplayPicker(onTestDisplaySelected, document.getElementById('test-template-display').value)">
|
||||
<span id="test-display-picker-label" data-i18n="displays.picker.select">Select display...</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="test-template-duration">
|
||||
<span data-i18n="templates.test.duration">Capture Duration (s):</span>
|
||||
<span id="test-template-duration-value">5</span>
|
||||
</label>
|
||||
<input type="range" id="test-template-duration" min="1" max="10" step="1" value="5" oninput="updateCaptureDuration(this.value)" />
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary" onclick="runTemplateTest()" style="margin-top: 16px;">
|
||||
<span data-i18n="templates.test.run">🧪 Run</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user