Add BetterCam engine, UI polish, and bug fixes

- Add BetterCam capture engine (DXGI Desktop Duplication, priority 4)
- Fix missing picture_stream_id in get_device endpoint
- Fix template delete validation to check streams instead of devices
- Add description field to capture engine template UI
- Default template name changed to "Default" with descriptive text
- Display picker highlights selected display instead of primary
- Fix modals closing when dragging text selection outside dialog
- Rename "Engine Configuration" to "Configuration", hide when empty
- Rename "Run Test" to "Run" across all test buttons
- Always reserve space for vertical scrollbar
- Redesign Stream Settings info panel with pill-style props
- Fix processed stream showing internal ID instead of stream name
- Update en/ru locale files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 23:28:35 +03:00
parent 9ae93497a6
commit ebec1bd16e
13 changed files with 417 additions and 100 deletions

View File

@@ -238,11 +238,10 @@
<div class="form-group">
<label for="stream-selector-stream" data-i18n="device.stream_selector.label">Stream:</label>
<select id="stream-selector-stream"></select>
<div id="stream-selector-info" class="stream-info-panel" style="display: none;"></div>
<small class="input-hint" data-i18n="device.stream_selector.hint">Select a stream that defines what this device captures and processes</small>
</div>
<div id="stream-selector-info" class="stream-info-panel" style="display: none;"></div>
<div class="form-group">
<label for="stream-selector-border-width" data-i18n="device.stream_settings.border_width">Border Width (px):</label>
<input type="number" id="stream-selector-border-width" min="1" max="100" value="10">
@@ -375,6 +374,11 @@
<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">
<label for="template-engine" data-i18n="templates.engine">Capture Engine:</label>
<select id="template-engine" onchange="onEngineChange()" required>
@@ -384,7 +388,7 @@
</div>
<div id="engine-config-section" style="display: none;">
<h3 data-i18n="templates.config">Engine Configuration</h3>
<h3 data-i18n="templates.config">Configuration</h3>
<div id="engine-config-fields"></div>
</div>
@@ -409,7 +413,7 @@
<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)">
<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>
@@ -423,7 +427,7 @@
</div>
<button type="button" class="btn btn-primary" onclick="runTemplateTest()" style="margin-top: 16px;">
<span data-i18n="templates.test.run">🧪 Run Test</span>
<span data-i18n="templates.test.run">🧪 Run</span>
</button>
</div>
@@ -447,7 +451,7 @@
</div>
<button type="button" class="btn btn-primary" onclick="runStreamTest()" style="margin-top: 16px;">
<span data-i18n="streams.test.run">🧪 Run Test</span>
<span data-i18n="streams.test.run">🧪 Run</span>
</button>
</div>
@@ -475,7 +479,7 @@
</div>
<button type="button" class="btn btn-primary" onclick="runPPTemplateTest()" style="margin-top: 16px;">
<span data-i18n="streams.test.run">🧪 Run Test</span>
<span data-i18n="streams.test.run">🧪 Run</span>
</button>
</div>
@@ -504,7 +508,7 @@
<div class="form-group">
<label data-i18n="streams.display">Display:</label>
<input type="hidden" id="stream-display-index" value="">
<button type="button" class="btn btn-display-picker" id="stream-display-picker-btn" onclick="openDisplayPicker(onStreamDisplaySelected)">
<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>