Add visual IconSelect selectors for effect, palette, gradient, waveform dropdowns

Replace plain <select> dropdowns with rich visual selectors:
- Effect type: icon grid with descriptions
- Effect/audio palette: gradient strip previews from color data
- Gradient preset: gradient strip previews (13 presets)
- Audio visualization: icon grid with descriptions
- Notification effect: icon grid with descriptions
- Waveform (value source): inline SVG shape previews

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 00:41:05 +03:00
parent dc4495a117
commit a728c75113
6 changed files with 168 additions and 3 deletions

View File

@@ -78,6 +78,27 @@ function _buildVSTypeItems() {
}
let _vsTypeIconSelect = null;
let _waveformIconSelect = null;
const _WAVEFORM_SVG = {
sine: '<svg viewBox="0 0 60 24" width="60" height="24" fill="none" stroke="currentColor" stroke-width="2"><path d="M0 12 Q15 -4 30 12 Q45 28 60 12"/></svg>',
triangle: '<svg viewBox="0 0 60 24" width="60" height="24" fill="none" stroke="currentColor" stroke-width="2"><path d="M0 12 L15 2 L45 22 L60 12"/></svg>',
square: '<svg viewBox="0 0 60 24" width="60" height="24" fill="none" stroke="currentColor" stroke-width="2"><path d="M0 20 L0 4 L30 4 L30 20 L60 20 L60 4"/></svg>',
sawtooth: '<svg viewBox="0 0 60 24" width="60" height="24" fill="none" stroke="currentColor" stroke-width="2"><path d="M0 20 L30 4 L30 20 L60 4"/></svg>',
};
function _ensureWaveformIconSelect() {
const sel = document.getElementById('value-source-waveform');
if (!sel) return;
const items = [
{ value: 'sine', icon: _WAVEFORM_SVG.sine, label: t('value_source.waveform.sine') },
{ value: 'triangle', icon: _WAVEFORM_SVG.triangle, label: t('value_source.waveform.triangle') },
{ value: 'square', icon: _WAVEFORM_SVG.square, label: t('value_source.waveform.square') },
{ value: 'sawtooth', icon: _WAVEFORM_SVG.sawtooth, label: t('value_source.waveform.sawtooth') },
];
if (_waveformIconSelect) { _waveformIconSelect.updateItems(items); return; }
_waveformIconSelect = new IconSelect({ target: sel, items, columns: 4 });
}
function _ensureVSTypeIconSelect() {
const sel = document.getElementById('value-source-type');
@@ -111,6 +132,7 @@ export async function showValueSourceModal(editData) {
_setSlider('value-source-value', editData.value ?? 1.0);
} else if (editData.source_type === 'animated') {
document.getElementById('value-source-waveform').value = editData.waveform || 'sine';
if (_waveformIconSelect) _waveformIconSelect.setValue(editData.waveform || 'sine');
_setSlider('value-source-speed', editData.speed ?? 10);
_setSlider('value-source-min-value', editData.min_value ?? 0);
_setSlider('value-source-max-value', editData.max_value ?? 1);
@@ -174,6 +196,7 @@ export function onValueSourceTypeChange() {
if (_vsTypeIconSelect) _vsTypeIconSelect.setValue(type);
document.getElementById('value-source-static-section').style.display = type === 'static' ? '' : 'none';
document.getElementById('value-source-animated-section').style.display = type === 'animated' ? '' : 'none';
if (type === 'animated') _ensureWaveformIconSelect();
document.getElementById('value-source-audio-section').style.display = type === 'audio' ? '' : 'none';
document.getElementById('value-source-adaptive-time-section').style.display = type === 'adaptive_time' ? '' : 'none';
document.getElementById('value-source-adaptive-scene-section').style.display = type === 'adaptive_scene' ? '' : 'none';