2a8e2daefc
Introduces a new 'static' source type that fills all device LEDs with a single constant RGB color — no screen capture or processing required. - StaticColorStripSource storage model (color + led_count=0 auto-size) - StaticColorStripStream: no background thread, configure() sizes to device LED count at processor start; hot-updates preserve runtime size - ColorStripStreamManager dispatches static sources (no LiveStream needed) - WledTargetProcessor calls stream.configure(device_led_count) on start - API schemas/routes: source_type Literal["picture","static"]; color field; overlay/calibration-test endpoints return 400 for static - Frontend: type selector modal, color picker, type-aware card rendering (🎨 icon + color swatch), LED count field hidden for static type - Locale keys: color_strip.type, color_strip.static_color (en + ru) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
153 lines
11 KiB
HTML
153 lines
11 KiB
HTML
<!-- Color Strip Source Editor Modal -->
|
|
<div id="css-editor-modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="css-editor-title">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 id="css-editor-title" data-i18n="color_strip.add">🎞️ Add Color Strip Source</h2>
|
|
<button class="modal-close-btn" onclick="closeCSSEditorModal()" title="Close" data-i18n-aria-label="aria.close">✕</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="css-editor-form">
|
|
<input type="hidden" id="css-editor-id">
|
|
|
|
<div class="form-group">
|
|
<label for="css-editor-name" data-i18n="color_strip.name">Name:</label>
|
|
<input type="text" id="css-editor-name" data-i18n-placeholder="color_strip.name.placeholder" placeholder="Wall Strip" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="css-editor-type" data-i18n="color_strip.type">Type:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.type.hint">Picture Source derives colors from a screen capture. Static Color fills all LEDs with one constant color.</small>
|
|
<select id="css-editor-type" onchange="onCSSTypeChange()">
|
|
<option value="picture" data-i18n="color_strip.type.picture">Picture Source</option>
|
|
<option value="static" data-i18n="color_strip.type.static">Static Color</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Picture-source-specific fields -->
|
|
<div id="css-editor-picture-section">
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="css-editor-picture-source" data-i18n="color_strip.picture_source">Picture Source:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.picture_source.hint">Which screen capture source to use as input for LED color calculation</small>
|
|
<select id="css-editor-picture-source"></select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="css-editor-fps">
|
|
<span data-i18n="color_strip.fps">Target FPS:</span>
|
|
<span id="css-editor-fps-value">30</span>
|
|
</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.fps.hint">Target frames per second for LED color updates (10-90)</small>
|
|
<div class="slider-row">
|
|
<input type="range" id="css-editor-fps" min="10" max="90" value="30" oninput="document.getElementById('css-editor-fps-value').textContent = this.value">
|
|
<span class="slider-value">fps</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="css-editor-interpolation" data-i18n="color_strip.interpolation">Color Mode:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.interpolation.hint">How to calculate LED color from sampled border pixels</small>
|
|
<select id="css-editor-interpolation">
|
|
<option value="average" data-i18n="color_strip.interpolation.average">Average</option>
|
|
<option value="median" data-i18n="color_strip.interpolation.median">Median</option>
|
|
<option value="dominant" data-i18n="color_strip.interpolation.dominant">Dominant</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="css-editor-smoothing">
|
|
<span data-i18n="color_strip.smoothing">Smoothing:</span>
|
|
<span id="css-editor-smoothing-value">0.30</span>
|
|
</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.smoothing.hint">Temporal blending between frames (0=none, 1=full). Reduces flicker.</small>
|
|
<input type="range" id="css-editor-smoothing" min="0.0" max="1.0" step="0.05" value="0.3" oninput="document.getElementById('css-editor-smoothing-value').textContent = parseFloat(this.value).toFixed(2)">
|
|
</div>
|
|
|
|
<details class="form-collapse">
|
|
<summary data-i18n="color_strip.color_corrections">Color Corrections</summary>
|
|
<div class="form-collapse-body">
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="css-editor-brightness">
|
|
<span data-i18n="color_strip.brightness">Brightness:</span>
|
|
<span id="css-editor-brightness-value">1.00</span>
|
|
</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.brightness.hint">Output brightness multiplier (0=off, 1=unchanged, 2=double). Applied after color extraction.</small>
|
|
<input type="range" id="css-editor-brightness" min="0.0" max="2.0" step="0.05" value="1.0" oninput="document.getElementById('css-editor-brightness-value').textContent = parseFloat(this.value).toFixed(2)">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="css-editor-saturation">
|
|
<span data-i18n="color_strip.saturation">Saturation:</span>
|
|
<span id="css-editor-saturation-value">1.00</span>
|
|
</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.saturation.hint">Color saturation (0=grayscale, 1=unchanged, 2=double saturation)</small>
|
|
<input type="range" id="css-editor-saturation" min="0.0" max="2.0" step="0.05" value="1.0" oninput="document.getElementById('css-editor-saturation-value').textContent = parseFloat(this.value).toFixed(2)">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="css-editor-gamma">
|
|
<span data-i18n="color_strip.gamma">Gamma:</span>
|
|
<span id="css-editor-gamma-value">1.00</span>
|
|
</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.gamma.hint">Gamma correction (1=none, <1=brighter midtones, >1=darker midtones)</small>
|
|
<input type="range" id="css-editor-gamma" min="0.1" max="3.0" step="0.05" value="1.0" oninput="document.getElementById('css-editor-gamma-value').textContent = parseFloat(this.value).toFixed(2)">
|
|
</div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
|
|
<!-- Static-color-specific fields -->
|
|
<div id="css-editor-static-section" style="display:none">
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="css-editor-color" data-i18n="color_strip.static_color">Color:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.static_color.hint">The solid color that will be sent to all LEDs on the strip.</small>
|
|
<input type="color" id="css-editor-color" value="#ffffff">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- LED count — picture type only (auto-sized from device for static) -->
|
|
<div id="css-editor-led-count-group" class="form-group">
|
|
<div class="label-row">
|
|
<label for="css-editor-led-count" data-i18n="color_strip.led_count">LED Count:</label>
|
|
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?" data-i18n-aria-label="aria.hint">?</button>
|
|
</div>
|
|
<small class="input-hint" style="display:none" data-i18n="color_strip.led_count.hint">Total number of LEDs on the strip. Set to 0 to use the sum from calibration. If your strip has LEDs behind the TV that are not mapped to screen edges, set the exact count here and they will be filled with black.</small>
|
|
<input type="number" id="css-editor-led-count" min="0" max="1500" step="1" value="0">
|
|
</div>
|
|
|
|
<div id="css-editor-error" class="error-message" style="display: none;"></div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-icon btn-secondary" onclick="closeCSSEditorModal()" title="Cancel" data-i18n-aria-label="aria.cancel">✕</button>
|
|
<button class="btn btn-icon btn-primary" onclick="saveCSSEditor()" title="Save" data-i18n-aria-label="aria.save">✓</button>
|
|
</div>
|
|
</div>
|
|
</div>
|