Full-stack implementation of DMX output for stage lighting and LED controllers: - DMXClient with Art-Net and sACN packet builders, multi-universe splitting - DMXDeviceProvider with manual_led_count capability and URL parsing - Device store, API schemas, routes wired with dmx_protocol/start_universe/start_channel - Frontend: add/settings modals with DMX fields, IconSelect protocol picker - Fix add device modal dirty check on type change (re-snapshot after switch) - i18n keys for DMX in en/ru/zh locales Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
166 lines
12 KiB
HTML
166 lines
12 KiB
HTML
<!-- Add Device Modal -->
|
|
<div id="add-device-modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="add-device-modal-title">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 id="add-device-modal-title" 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"><svg class="icon" viewBox="0 0 24 24"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg></button>
|
|
<button class="modal-close-btn" onclick="closeAddDeviceModal()" title="Close" data-i18n-aria-label="aria.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>
|
|
<option value="mqtt">MQTT</option>
|
|
<option value="ws">WebSocket</option>
|
|
<option value="openrgb">OpenRGB</option>
|
|
<option value="dmx">DMX</option>
|
|
<option value="mock">Mock</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-zone-group" style="display: none;">
|
|
<div class="label-row">
|
|
<label data-i18n="device.openrgb.zone">Zones:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="device.openrgb.zone.hint">Select which LED zones to control (leave all unchecked for all zones)</small>
|
|
<div id="device-zone-list" class="zone-checkbox-list"></div>
|
|
</div>
|
|
<div class="form-group" id="device-zone-mode-group" style="display: none;">
|
|
<div class="label-row">
|
|
<label data-i18n="device.openrgb.mode">Zone mode:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="device.openrgb.mode.hint">Combined treats all zones as one continuous LED strip. Separate renders each zone independently with the full effect.</small>
|
|
<div class="zone-mode-radios">
|
|
<label class="zone-mode-option">
|
|
<input type="radio" name="device-zone-mode" value="combined" checked>
|
|
<span data-i18n="device.openrgb.mode.combined">Combined strip</span>
|
|
</label>
|
|
<label class="zone-mode-option">
|
|
<input type="radio" name="device-zone-mode" value="separate">
|
|
<span data-i18n="device.openrgb.mode.separate">Independent zones</span>
|
|
</label>
|
|
</div>
|
|
</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 class="form-group" id="device-led-type-group" style="display: none;">
|
|
<div class="label-row">
|
|
<label for="device-led-type" data-i18n="device.led_type">LED Type:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="device.led_type.hint">RGB (3 channels) or RGBW (4 channels with dedicated white)</small>
|
|
<select id="device-led-type">
|
|
<option value="rgb">RGB</option>
|
|
<option value="rgbw">RGBW</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group" id="device-send-latency-group" style="display: none;">
|
|
<div class="label-row">
|
|
<label for="device-send-latency" data-i18n="device.send_latency">Send Latency (ms):</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="device.send_latency.hint">Simulated network/serial delay per frame in milliseconds</small>
|
|
<input type="number" id="device-send-latency" min="0" max="5000" value="0">
|
|
</div>
|
|
<div class="form-group" id="device-dmx-protocol-group" style="display: none;">
|
|
<div class="label-row">
|
|
<label for="device-dmx-protocol" data-i18n="device.dmx_protocol">DMX Protocol:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="device.dmx_protocol.hint">Art-Net uses UDP port 6454, sACN (E1.31) uses UDP port 5568</small>
|
|
<select id="device-dmx-protocol">
|
|
<option value="artnet">Art-Net</option>
|
|
<option value="sacn">sACN (E1.31)</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group" id="device-dmx-start-universe-group" style="display: none;">
|
|
<div class="label-row">
|
|
<label for="device-dmx-start-universe" data-i18n="device.dmx_start_universe">Start Universe:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="device.dmx_start_universe.hint">First DMX universe (0-32767). Multiple universes are used automatically for >170 LEDs.</small>
|
|
<input type="number" id="device-dmx-start-universe" min="0" max="32767" value="0">
|
|
</div>
|
|
<div class="form-group" id="device-dmx-start-channel-group" style="display: none;">
|
|
<div class="label-row">
|
|
<label for="device-dmx-start-channel" data-i18n="device.dmx_start_channel">Start Channel:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="device.dmx_start_channel.hint">First DMX channel within the universe (1-512)</small>
|
|
<input type="number" id="device-dmx-start-channel" min="1" max="512" value="1">
|
|
</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" data-i18n-aria-label="aria.cancel">✕</button>
|
|
<button class="btn btn-icon btn-primary" onclick="document.getElementById('add-device-form').requestSubmit()" title="Add Device" data-i18n-aria-label="aria.save">✓</button>
|
|
</div>
|
|
</div>
|
|
</div>
|