Add clone buttons, fix card navigation highlight, UI polish
- Add clone buttons to Audio Source and Value Source cards - Fix command palette navigation destroying card highlight by skipping redundant data reload (skipLoad option on switchTab) - Convert value source modal sliders to value-in-label pattern - Change audio/value source modal footers to icon-only buttons - Remove separator lines between card sections - Add UI conventions to CLAUDE.md (card appearance, modal footer, sliders) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
32
CLAUDE.md
32
CLAUDE.md
@@ -149,6 +149,38 @@ Every editor modal **must** have a dirty check so closing with unsaved changes s
|
||||
|
||||
The base class handles: `isDirty()` comparison, confirmation dialog, backdrop click, ESC key, focus trapping, and body scroll lock.
|
||||
|
||||
### Card appearance
|
||||
|
||||
When creating or modifying entity cards (devices, targets, CSS sources, streams, audio/value sources, templates), **always reference existing cards** of the same or similar type for visual consistency. Cards should have:
|
||||
|
||||
- Clone (📋) and Edit (✏️) icon buttons in `.template-card-actions`
|
||||
- Delete (✕) button as `.card-remove-btn`
|
||||
- Property badges in `.stream-card-props` with emoji icons
|
||||
|
||||
### Modal footer buttons
|
||||
|
||||
Use **icon-only** buttons (✓ / ✕) matching the device settings modal pattern, **not** text buttons:
|
||||
|
||||
```html
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeMyModal()" title="Cancel" data-i18n-title="settings.button.cancel" data-i18n-aria-label="aria.cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveMyEntity()" title="Save" data-i18n-title="settings.button.save" data-i18n-aria-label="aria.save">✓</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Slider value display
|
||||
|
||||
For range sliders, display the current value **inside the label** (not in a separate wrapper). This keeps the value visible next to the property name:
|
||||
|
||||
```html
|
||||
<label for="my-slider"><span data-i18n="my.label">Speed:</span> <span id="my-slider-display">1.0</span></label>
|
||||
...
|
||||
<input type="range" id="my-slider" min="0" max="10" step="0.1" value="1.0"
|
||||
oninput="document.getElementById('my-slider-display').textContent = this.value">
|
||||
```
|
||||
|
||||
Do **not** use a `range-with-value` wrapper div.
|
||||
|
||||
## General Guidelines
|
||||
|
||||
- Always test changes before marking as complete
|
||||
|
||||
@@ -627,7 +627,6 @@
|
||||
color: var(--text-secondary);
|
||||
margin: 0 0 12px 0;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.subtab-section-header.cs-header {
|
||||
|
||||
@@ -107,13 +107,13 @@ import {
|
||||
// Layer 5: audio sources
|
||||
import {
|
||||
showAudioSourceModal, closeAudioSourceModal, saveAudioSource,
|
||||
editAudioSource, deleteAudioSource, onAudioSourceTypeChange,
|
||||
editAudioSource, cloneAudioSource, deleteAudioSource, onAudioSourceTypeChange,
|
||||
} from './features/audio-sources.js';
|
||||
|
||||
// Layer 5: value sources
|
||||
import {
|
||||
showValueSourceModal, closeValueSourceModal, saveValueSource,
|
||||
editValueSource, deleteValueSource, onValueSourceTypeChange,
|
||||
editValueSource, cloneValueSource, deleteValueSource, onValueSourceTypeChange,
|
||||
addSchedulePoint,
|
||||
} from './features/value-sources.js';
|
||||
|
||||
@@ -328,6 +328,7 @@ Object.assign(window, {
|
||||
closeAudioSourceModal,
|
||||
saveAudioSource,
|
||||
editAudioSource,
|
||||
cloneAudioSource,
|
||||
deleteAudioSource,
|
||||
onAudioSourceTypeChange,
|
||||
|
||||
@@ -336,6 +337,7 @@ Object.assign(window, {
|
||||
closeValueSourceModal,
|
||||
saveValueSource,
|
||||
editValueSource,
|
||||
cloneValueSource,
|
||||
deleteValueSource,
|
||||
onValueSourceTypeChange,
|
||||
addSchedulePoint,
|
||||
|
||||
@@ -16,7 +16,11 @@ import { switchTab } from '../features/tabs.js';
|
||||
export function navigateToCard(tab, subTab, sectionKey, cardAttr, cardValue) {
|
||||
// Push current location to history so browser back returns here
|
||||
history.pushState(null, '', location.hash || '#');
|
||||
switchTab(tab);
|
||||
|
||||
// Activate tab visually without triggering a data reload —
|
||||
// the command palette already fetched fresh data, and a reload
|
||||
// would re-render all cards, destroying the highlight.
|
||||
switchTab(tab, { skipLoad: true });
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (subTab) {
|
||||
@@ -41,17 +45,36 @@ export function navigateToCard(tab, subTab, sectionKey, cardAttr, cardValue) {
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for card to appear in DOM (tab data may load async)
|
||||
_waitForCard(cardAttr, cardValue, 3000).then(card => {
|
||||
if (!card) return;
|
||||
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
card.classList.add('card-highlight');
|
||||
_showDimOverlay(2000);
|
||||
setTimeout(() => card.classList.remove('card-highlight'), 2000);
|
||||
// Check if card already exists (data previously loaded)
|
||||
const existing = document.querySelector(`[${cardAttr}="${cardValue}"]`);
|
||||
if (existing) {
|
||||
_highlightCard(existing);
|
||||
return;
|
||||
}
|
||||
|
||||
// Card not in DOM — trigger data load and wait for it to appear
|
||||
_triggerTabLoad(tab);
|
||||
_waitForCard(cardAttr, cardValue, 5000).then(card => {
|
||||
if (card) _highlightCard(card);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _highlightCard(card) {
|
||||
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
card.classList.add('card-highlight');
|
||||
_showDimOverlay(2000);
|
||||
setTimeout(() => card.classList.remove('card-highlight'), 2000);
|
||||
}
|
||||
|
||||
/** Trigger the tab's data load function (used when card wasn't found in DOM). */
|
||||
function _triggerTabLoad(tab) {
|
||||
if (tab === 'dashboard' && typeof window.loadDashboard === 'function') window.loadDashboard();
|
||||
else if (tab === 'profiles' && typeof window.loadProfiles === 'function') window.loadProfiles();
|
||||
else if (tab === 'streams' && typeof window.loadPictureSources === 'function') window.loadPictureSources();
|
||||
else if (tab === 'targets' && typeof window.loadTargetsTab === 'function') window.loadTargetsTab();
|
||||
}
|
||||
|
||||
function _showDimOverlay(duration) {
|
||||
let overlay = document.getElementById('nav-dim-overlay');
|
||||
if (!overlay) {
|
||||
|
||||
@@ -149,6 +149,22 @@ export async function editAudioSource(sourceId) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Clone ─────────────────────────────────────────────────────
|
||||
|
||||
export async function cloneAudioSource(sourceId) {
|
||||
try {
|
||||
const resp = await fetchWithAuth(`/audio-sources/${sourceId}`);
|
||||
if (!resp.ok) throw new Error('fetch failed');
|
||||
const data = await resp.json();
|
||||
delete data.id;
|
||||
data.name = data.name + ' (copy)';
|
||||
await showAudioSourceModal(data.source_type, data);
|
||||
} catch (e) {
|
||||
if (e.isAuth) return;
|
||||
showToast(e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Delete ────────────────────────────────────────────────────
|
||||
|
||||
export async function deleteAudioSource(sourceId) {
|
||||
|
||||
@@ -748,6 +748,7 @@ function renderPictureSourcesList(streams) {
|
||||
<div class="stream-card-props">${propsHtml}</div>
|
||||
${src.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(src.description)}</div>` : ''}
|
||||
<div class="template-card-actions">
|
||||
<button class="btn btn-icon btn-secondary" onclick="cloneAudioSource('${src.id}')" title="${t('common.clone')}">📋</button>
|
||||
<button class="btn btn-icon btn-secondary" onclick="editAudioSource('${src.id}')" title="${t('common.edit')}">✏️</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ function _setHash(tab, subTab) {
|
||||
|
||||
let _suppressHashUpdate = false;
|
||||
|
||||
export function switchTab(name, { updateHash = true } = {}) {
|
||||
export function switchTab(name, { updateHash = true, skipLoad = false } = {}) {
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
const isActive = btn.dataset.tab === name;
|
||||
btn.classList.toggle('active', isActive);
|
||||
@@ -40,11 +40,11 @@ export function switchTab(name, { updateHash = true } = {}) {
|
||||
|
||||
if (name === 'dashboard') {
|
||||
// Use window.* to avoid circular imports with feature modules
|
||||
if (apiKey && typeof window.loadDashboard === 'function') window.loadDashboard();
|
||||
if (!skipLoad && apiKey && typeof window.loadDashboard === 'function') window.loadDashboard();
|
||||
} else {
|
||||
if (typeof window.stopPerfPolling === 'function') window.stopPerfPolling();
|
||||
if (typeof window.stopUptimeTimer === 'function') window.stopUptimeTimer();
|
||||
if (!apiKey) return;
|
||||
if (!apiKey || skipLoad) return;
|
||||
if (name === 'streams') {
|
||||
if (typeof window.loadPictureSources === 'function') window.loadPictureSources();
|
||||
} else if (name === 'targets') {
|
||||
|
||||
@@ -234,6 +234,22 @@ export async function editValueSource(sourceId) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Clone ─────────────────────────────────────────────────────
|
||||
|
||||
export async function cloneValueSource(sourceId) {
|
||||
try {
|
||||
const resp = await fetchWithAuth(`/value-sources/${sourceId}`);
|
||||
if (!resp.ok) throw new Error('fetch failed');
|
||||
const data = await resp.json();
|
||||
delete data.id;
|
||||
data.name = data.name + ' (copy)';
|
||||
await showValueSourceModal(data);
|
||||
} catch (e) {
|
||||
if (e.isAuth) return;
|
||||
showToast(e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Delete ────────────────────────────────────────────────────
|
||||
|
||||
export async function deleteValueSource(sourceId) {
|
||||
@@ -308,6 +324,7 @@ export function createValueSourceCard(src) {
|
||||
<div class="stream-card-props">${propsHtml}</div>
|
||||
${src.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(src.description)}</div>` : ''}
|
||||
<div class="template-card-actions">
|
||||
<button class="btn btn-icon btn-secondary" onclick="cloneValueSource('${src.id}')" title="${t('common.clone')}">📋</button>
|
||||
<button class="btn btn-icon btn-secondary" onclick="editValueSource('${src.id}')" title="${t('common.edit')}">✏️</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -87,8 +87,8 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="closeAudioSourceModal()" data-i18n="settings.button.cancel">× Cancel</button>
|
||||
<button class="btn btn-primary" onclick="saveAudioSource()" data-i18n="settings.button.save">✓ Save</button>
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeAudioSourceModal()" title="Cancel" data-i18n-title="settings.button.cancel" data-i18n-aria-label="aria.cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveAudioSource()" title="Save" data-i18n-title="settings.button.save" data-i18n-aria-label="aria.save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,15 +41,12 @@
|
||||
<div id="value-source-static-section">
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-value" data-i18n="value_source.value">Value:</label>
|
||||
<label for="value-source-value"><span data-i18n="value_source.value">Value:</span> <span id="value-source-value-display">1.0</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="value_source.value.hint">Constant output value (0.0 = off, 1.0 = full brightness)</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-value" min="0" max="1" step="0.01" value="1.0"
|
||||
oninput="document.getElementById('value-source-value-display').textContent = this.value">
|
||||
<span id="value-source-value-display">1.0</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-value" min="0" max="1" step="0.01" value="1.0"
|
||||
oninput="document.getElementById('value-source-value-display').textContent = this.value">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,41 +68,32 @@
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-speed" data-i18n="value_source.speed">Speed (cpm):</label>
|
||||
<label for="value-source-speed"><span data-i18n="value_source.speed">Speed (cpm):</span> <span id="value-source-speed-display">10</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="value_source.speed.hint">Cycles per minute — how fast the waveform repeats (1 = very slow, 120 = very fast)</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-speed" min="1" max="120" step="1" value="10"
|
||||
oninput="document.getElementById('value-source-speed-display').textContent = this.value">
|
||||
<span id="value-source-speed-display">10</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-speed" min="1" max="120" step="1" value="10"
|
||||
oninput="document.getElementById('value-source-speed-display').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-min-value" data-i18n="value_source.min_value">Min Value:</label>
|
||||
<label for="value-source-min-value"><span data-i18n="value_source.min_value">Min Value:</span> <span id="value-source-min-value-display">0</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="value_source.min_value.hint">Minimum output of the waveform cycle</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-min-value" min="0" max="1" step="0.01" value="0"
|
||||
oninput="document.getElementById('value-source-min-value-display').textContent = this.value">
|
||||
<span id="value-source-min-value-display">0</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-min-value" min="0" max="1" step="0.01" value="0"
|
||||
oninput="document.getElementById('value-source-min-value-display').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-max-value" data-i18n="value_source.max_value">Max Value:</label>
|
||||
<label for="value-source-max-value"><span data-i18n="value_source.max_value">Max Value:</span> <span id="value-source-max-value-display">1</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="value_source.max_value.hint">Maximum output of the waveform cycle</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-max-value" min="0" max="1" step="0.01" value="1"
|
||||
oninput="document.getElementById('value-source-max-value-display').textContent = this.value">
|
||||
<span id="value-source-max-value-display">1</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-max-value" min="0" max="1" step="0.01" value="1"
|
||||
oninput="document.getElementById('value-source-max-value-display').textContent = this.value">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -137,54 +125,42 @@
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-sensitivity" data-i18n="value_source.sensitivity">Sensitivity:</label>
|
||||
<label for="value-source-sensitivity"><span data-i18n="value_source.sensitivity">Sensitivity:</span> <span id="value-source-sensitivity-display">1.0</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="value_source.sensitivity.hint">Gain multiplier for the audio signal (higher = more reactive)</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-sensitivity" min="0.1" max="5" step="0.1" value="1.0"
|
||||
oninput="document.getElementById('value-source-sensitivity-display').textContent = this.value">
|
||||
<span id="value-source-sensitivity-display">1.0</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-sensitivity" min="0.1" max="5" step="0.1" value="1.0"
|
||||
oninput="document.getElementById('value-source-sensitivity-display').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-smoothing" data-i18n="value_source.smoothing">Smoothing:</label>
|
||||
<label for="value-source-smoothing"><span data-i18n="value_source.smoothing">Smoothing:</span> <span id="value-source-smoothing-display">0.3</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="value_source.smoothing.hint">Temporal smoothing (0 = instant response, 1 = very smooth/slow)</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-smoothing" min="0" max="1" step="0.05" value="0.3"
|
||||
oninput="document.getElementById('value-source-smoothing-display').textContent = this.value">
|
||||
<span id="value-source-smoothing-display">0.3</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-smoothing" min="0" max="1" step="0.05" value="0.3"
|
||||
oninput="document.getElementById('value-source-smoothing-display').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-audio-min-value" data-i18n="value_source.audio_min_value">Min Value:</label>
|
||||
<label for="value-source-audio-min-value"><span data-i18n="value_source.audio_min_value">Min Value:</span> <span id="value-source-audio-min-value-display">0</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="value_source.audio_min_value.hint">Output when audio is silent (e.g. 0.3 = 30% brightness floor)</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-audio-min-value" min="0" max="1" step="0.01" value="0"
|
||||
oninput="document.getElementById('value-source-audio-min-value-display').textContent = this.value">
|
||||
<span id="value-source-audio-min-value-display">0</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-audio-min-value" min="0" max="1" step="0.01" value="0"
|
||||
oninput="document.getElementById('value-source-audio-min-value-display').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-audio-max-value" data-i18n="value_source.audio_max_value">Max Value:</label>
|
||||
<label for="value-source-audio-max-value"><span data-i18n="value_source.audio_max_value">Max Value:</span> <span id="value-source-audio-max-value-display">1</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="value_source.audio_max_value.hint">Output at maximum audio level</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-audio-max-value" min="0" max="1" step="0.01" value="1"
|
||||
oninput="document.getElementById('value-source-audio-max-value-display').textContent = this.value">
|
||||
<span id="value-source-audio-max-value-display">1</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-audio-max-value" min="0" max="1" step="0.01" value="1"
|
||||
oninput="document.getElementById('value-source-audio-max-value-display').textContent = this.value">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -228,28 +204,22 @@
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-scene-sensitivity" data-i18n="value_source.sensitivity">Sensitivity:</label>
|
||||
<label for="value-source-scene-sensitivity"><span data-i18n="value_source.sensitivity">Sensitivity:</span> <span id="value-source-scene-sensitivity-display">1.0</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="value_source.scene_sensitivity.hint">Gain multiplier for the luminance signal (higher = more reactive to brightness changes)</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-scene-sensitivity" min="0.1" max="5" step="0.1" value="1.0"
|
||||
oninput="document.getElementById('value-source-scene-sensitivity-display').textContent = this.value">
|
||||
<span id="value-source-scene-sensitivity-display">1.0</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-scene-sensitivity" min="0.1" max="5" step="0.1" value="1.0"
|
||||
oninput="document.getElementById('value-source-scene-sensitivity-display').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-scene-smoothing" data-i18n="value_source.smoothing">Smoothing:</label>
|
||||
<label for="value-source-scene-smoothing"><span data-i18n="value_source.smoothing">Smoothing:</span> <span id="value-source-scene-smoothing-display">0.3</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="value_source.smoothing.hint">Temporal smoothing (0 = instant response, 1 = very smooth transitions)</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-scene-smoothing" min="0" max="1" step="0.05" value="0.3"
|
||||
oninput="document.getElementById('value-source-scene-smoothing-display').textContent = this.value">
|
||||
<span id="value-source-scene-smoothing-display">0.3</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-scene-smoothing" min="0" max="1" step="0.05" value="0.3"
|
||||
oninput="document.getElementById('value-source-scene-smoothing-display').textContent = this.value">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -257,28 +227,22 @@
|
||||
<div id="value-source-adaptive-range-section" style="display:none">
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-adaptive-min-value" data-i18n="value_source.adaptive_min_value">Min Value:</label>
|
||||
<label for="value-source-adaptive-min-value"><span data-i18n="value_source.adaptive_min_value">Min Value:</span> <span id="value-source-adaptive-min-value-display">0</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="value_source.adaptive_min_value.hint">Minimum output brightness</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-adaptive-min-value" min="0" max="1" step="0.01" value="0"
|
||||
oninput="document.getElementById('value-source-adaptive-min-value-display').textContent = this.value">
|
||||
<span id="value-source-adaptive-min-value-display">0</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-adaptive-min-value" min="0" max="1" step="0.01" value="0"
|
||||
oninput="document.getElementById('value-source-adaptive-min-value-display').textContent = this.value">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="label-row">
|
||||
<label for="value-source-adaptive-max-value" data-i18n="value_source.adaptive_max_value">Max Value:</label>
|
||||
<label for="value-source-adaptive-max-value"><span data-i18n="value_source.adaptive_max_value">Max Value:</span> <span id="value-source-adaptive-max-value-display">1</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="value_source.adaptive_max_value.hint">Maximum output brightness</small>
|
||||
<div class="range-with-value">
|
||||
<input type="range" id="value-source-adaptive-max-value" min="0" max="1" step="0.01" value="1"
|
||||
oninput="document.getElementById('value-source-adaptive-max-value-display').textContent = this.value">
|
||||
<span id="value-source-adaptive-max-value-display">1</span>
|
||||
</div>
|
||||
<input type="range" id="value-source-adaptive-max-value" min="0" max="1" step="0.01" value="1"
|
||||
oninput="document.getElementById('value-source-adaptive-max-value-display').textContent = this.value">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -294,8 +258,8 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="closeValueSourceModal()" data-i18n="settings.button.cancel">× Cancel</button>
|
||||
<button class="btn btn-primary" onclick="saveValueSource()" data-i18n="settings.button.save">✓ Save</button>
|
||||
<button class="btn btn-icon btn-secondary" onclick="closeValueSourceModal()" title="Cancel" data-i18n-title="settings.button.cancel" data-i18n-aria-label="aria.cancel">✕</button>
|
||||
<button class="btn btn-icon btn-primary" onclick="saveValueSource()" title="Save" data-i18n-title="settings.button.save" data-i18n-aria-label="aria.save">✓</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user