Improve property description hints for dialogs
This commit is contained in:
@@ -2,6 +2,16 @@ const API_BASE = '/api/v1';
|
||||
let refreshInterval = null;
|
||||
let apiKey = null;
|
||||
|
||||
// Toggle hint description visibility next to a label
|
||||
function toggleHint(btn) {
|
||||
const hint = btn.closest('.label-row').nextElementSibling;
|
||||
if (hint && hint.classList.contains('input-hint')) {
|
||||
const visible = hint.style.display !== 'none';
|
||||
hint.style.display = visible ? 'none' : 'block';
|
||||
btn.classList.toggle('active', !visible);
|
||||
}
|
||||
}
|
||||
|
||||
// Backdrop click helper: only closes modal if both mousedown and mouseup were on the backdrop itself.
|
||||
// Prevents accidental close when user drags text selection outside the dialog.
|
||||
function setupBackdropClose(modal, closeFn) {
|
||||
|
||||
@@ -203,15 +203,21 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="settings-device-url" data-i18n="device.url">WLED 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 your WLED device</small>
|
||||
<input type="url" id="settings-device-url" data-i18n-placeholder="device.url.placeholder" placeholder="http://192.168.1.100" required>
|
||||
<small class="input-hint" data-i18n="settings.url.hint">IP address or hostname of your WLED device</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 WLED device status (5-600 seconds)</small>
|
||||
<input type="number" id="settings-health-interval" min="5" max="600" value="30">
|
||||
<small class="input-hint" data-i18n="settings.health_interval.hint">How often to check the WLED device status (5-600 seconds)</small>
|
||||
</div>
|
||||
|
||||
<div id="settings-error" class="error-message" style="display: none;"></div>
|
||||
@@ -236,35 +242,47 @@
|
||||
<input type="hidden" id="stream-selector-device-id">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="stream-selector-stream" data-i18n="device.stream_selector.label">Stream:</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="device.stream_selector.hint">Select a stream that defines what this device captures and processes</small>
|
||||
<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 class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="stream-selector-border-width" data-i18n="device.stream_settings.border_width">Border Width (px):</label>
|
||||
<button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
|
||||
</div>
|
||||
<small class="input-hint" style="display:none" data-i18n="device.stream_settings.border_width_hint">How many pixels from the screen edge to sample for LED colors (1-100)</small>
|
||||
<input type="number" id="stream-selector-border-width" min="1" max="100" value="10">
|
||||
<small class="input-hint" data-i18n="device.stream_settings.border_width_hint">How many pixels from the screen edge to sample for LED colors (1-100)</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="stream-selector-interpolation" data-i18n="device.stream_settings.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="device.stream_settings.interpolation_hint">How to calculate LED color from sampled pixels</small>
|
||||
<select id="stream-selector-interpolation">
|
||||
<option value="average" data-i18n="device.stream_settings.interpolation.average">Average</option>
|
||||
<option value="median" data-i18n="device.stream_settings.interpolation.median">Median</option>
|
||||
<option value="dominant" data-i18n="device.stream_settings.interpolation.dominant">Dominant</option>
|
||||
</select>
|
||||
<small class="input-hint" data-i18n="device.stream_settings.interpolation_hint">How to calculate LED color from sampled pixels</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="stream-selector-smoothing">
|
||||
<span data-i18n="device.stream_settings.smoothing">Smoothing:</span>
|
||||
<span id="stream-selector-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="device.stream_settings.smoothing_hint">Temporal blending between frames (0=none, 1=full). Reduces flicker.</small>
|
||||
<input type="range" id="stream-selector-smoothing" min="0.0" max="1.0" step="0.05" value="0.3" oninput="document.getElementById('stream-selector-smoothing-value').textContent = this.value">
|
||||
<small class="input-hint" data-i18n="device.stream_settings.smoothing_hint">Temporal blending between frames (0=none, 1=full). Reduces flicker.</small>
|
||||
</div>
|
||||
|
||||
<div id="stream-selector-error" class="error-message" style="display: none;"></div>
|
||||
@@ -290,7 +308,11 @@
|
||||
Please enter your API key to authenticate and access the WLED 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"
|
||||
@@ -303,7 +325,6 @@
|
||||
👁️
|
||||
</button>
|
||||
</div>
|
||||
<small class="input-hint" data-i18n="auth.hint">Your API key will be stored securely in your browser's local storage.</small>
|
||||
</div>
|
||||
<div id="api-key-error" class="error-message" style="display: none;"></div>
|
||||
</div>
|
||||
@@ -380,7 +401,11 @@
|
||||
</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>
|
||||
<option value="" data-i18n="templates.engine.select">Select an engine...</option>
|
||||
</select>
|
||||
@@ -506,18 +531,30 @@
|
||||
<!-- Raw stream 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>
|
||||
@@ -528,11 +565,19 @@
|
||||
<!-- Processed stream 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 Stream:</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 stream 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 stream</small>
|
||||
<select id="stream-pp-template"></select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -540,9 +585,12 @@
|
||||
<!-- 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">
|
||||
<small class="form-hint" data-i18n="streams.image_source.hint">Enter a URL (http/https) or local file path to an image</small>
|
||||
</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">
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"templates.description.label": "Description (optional):",
|
||||
"templates.description.placeholder": "Describe this template...",
|
||||
"templates.engine": "Capture Engine:",
|
||||
"templates.engine.hint": "Select the screen capture technology to use",
|
||||
"templates.engine.select": "Select an engine...",
|
||||
"templates.engine.unavailable": "Unavailable",
|
||||
"templates.engine.unavailable.hint": "This engine is not available on your system",
|
||||
@@ -215,10 +216,15 @@
|
||||
"streams.type.raw": "Screen Capture",
|
||||
"streams.type.processed": "Processed",
|
||||
"streams.display": "Display:",
|
||||
"streams.display.hint": "Which screen to capture",
|
||||
"streams.capture_template": "Engine Template:",
|
||||
"streams.capture_template.hint": "Engine template defining how the screen is captured",
|
||||
"streams.target_fps": "Target FPS:",
|
||||
"streams.target_fps.hint": "Target frames per second for capture (10-90)",
|
||||
"streams.source": "Source Stream:",
|
||||
"streams.source.hint": "The stream to apply processing filters to",
|
||||
"streams.pp_template": "Filter Template:",
|
||||
"streams.pp_template.hint": "Filter template to apply to the source stream",
|
||||
"streams.description_label": "Description (optional):",
|
||||
"streams.description_placeholder": "Describe this stream...",
|
||||
"streams.created": "Stream created successfully",
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"templates.description.label": "Описание (необязательно):",
|
||||
"templates.description.placeholder": "Опишите этот шаблон...",
|
||||
"templates.engine": "Движок Захвата:",
|
||||
"templates.engine.hint": "Выберите технологию захвата экрана",
|
||||
"templates.engine.select": "Выберите движок...",
|
||||
"templates.engine.unavailable": "Недоступен",
|
||||
"templates.engine.unavailable.hint": "Этот движок недоступен в вашей системе",
|
||||
@@ -215,10 +216,15 @@
|
||||
"streams.type.raw": "Захват экрана",
|
||||
"streams.type.processed": "Обработанный",
|
||||
"streams.display": "Дисплей:",
|
||||
"streams.display.hint": "Какой экран захватывать",
|
||||
"streams.capture_template": "Шаблон Движка:",
|
||||
"streams.capture_template.hint": "Шаблон движка, определяющий способ захвата экрана",
|
||||
"streams.target_fps": "Целевой FPS:",
|
||||
"streams.target_fps.hint": "Целевое количество кадров в секунду (10-90)",
|
||||
"streams.source": "Исходный Поток:",
|
||||
"streams.source.hint": "Поток, к которому применяются фильтры обработки",
|
||||
"streams.pp_template": "Шаблон Фильтра:",
|
||||
"streams.pp_template.hint": "Шаблон фильтра для применения к исходному потоку",
|
||||
"streams.description_label": "Описание (необязательно):",
|
||||
"streams.description_placeholder": "Опишите этот поток...",
|
||||
"streams.created": "Поток успешно создан",
|
||||
|
||||
@@ -966,9 +966,49 @@ input:-webkit-autofill:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.label-row label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.hint-toggle {
|
||||
background: none;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 0.7rem;
|
||||
line-height: 1;
|
||||
color: var(--text-secondary, #888);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hint-toggle:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hint-toggle.active {
|
||||
opacity: 1;
|
||||
color: var(--primary-color, #4CAF50);
|
||||
border-color: var(--primary-color, #4CAF50);
|
||||
}
|
||||
|
||||
.input-hint {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
margin: 0 0 6px 0;
|
||||
color: #666;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user