diff --git a/contexts/frontend.md b/contexts/frontend.md index c1d07f8..46224b4 100644 --- a/contexts/frontend.md +++ b/contexts/frontend.md @@ -70,6 +70,8 @@ For `EntitySelect` with `allowNone: true`, pass the same i18n string as `noneLab ### Enhanced selectors (IconSelect & EntitySelect) +**IMPORTANT:** Always use icon grid or entity pickers instead of plain `` dropdowns should be enhanced with visual selectors depending on the data type: - **Predefined options** (source types, effect types, palettes, waveforms, viz modes) → use `IconSelect` from `js/core/icon-select.ts`. This replaces the ` ${CONDITION_TYPE_KEYS.map(k => ``).join('')} - +
`; diff --git a/server/src/wled_controller/static/js/features/pattern-templates.ts b/server/src/wled_controller/static/js/features/pattern-templates.ts index 25a1efa..5944496 100644 --- a/server/src/wled_controller/static/js/features/pattern-templates.ts +++ b/server/src/wled_controller/static/js/features/pattern-templates.ts @@ -29,6 +29,20 @@ import type { PatternTemplate } from '../types.ts'; let _patternBgEntitySelect: EntitySelect | null = null; let _patternTagsInput: TagInput | null = null; +// ── Auto-name ── + +let _ptNameManuallyEdited = false; + +function _autoGeneratePatternName() { + if (_ptNameManuallyEdited) return; + if ((document.getElementById('pattern-template-id') as HTMLInputElement).value) return; + const count = patternEditorRects.length; + const label = count > 0 + ? `${t('targets.section.pattern_templates')} · ${count} ${count === 1 ? 'rect' : 'rects'}` + : t('targets.section.pattern_templates'); + (document.getElementById('pattern-template-name') as HTMLInputElement).value = label; +} + class PatternTemplateModal extends Modal { constructor() { super('pattern-template-modal'); @@ -149,6 +163,11 @@ export async function showPatternTemplateEditor(templateId: string | null = null _patternTagsInput = new TagInput(document.getElementById('pattern-tags-container'), { placeholder: t('tags.placeholder') }); _patternTagsInput.setValue(_editorTags); + // Auto-name wiring + _ptNameManuallyEdited = !!(templateId || cloneData); + (document.getElementById('pattern-template-name') as HTMLElement).oninput = () => { _ptNameManuallyEdited = true; }; + if (!templateId && !cloneData) _autoGeneratePatternName(); + patternModal.snapshot(); renderPatternRectList(); @@ -317,6 +336,7 @@ export function addPatternRect(): void { setPatternEditorSelectedIdx(patternEditorRects.length - 1); renderPatternRectList(); renderPatternCanvas(); + _autoGeneratePatternName(); } export function deleteSelectedPatternRect(): void { @@ -325,6 +345,7 @@ export function deleteSelectedPatternRect(): void { setPatternEditorSelectedIdx(-1); renderPatternRectList(); renderPatternCanvas(); + _autoGeneratePatternName(); } export function removePatternRect(index: number): void { @@ -333,6 +354,7 @@ export function removePatternRect(index: number): void { else if (patternEditorSelectedIdx > index) setPatternEditorSelectedIdx(patternEditorSelectedIdx - 1); renderPatternRectList(); renderPatternCanvas(); + _autoGeneratePatternName(); } // ----- Pattern Canvas Visual Editor ----- diff --git a/server/src/wled_controller/static/js/features/scene-presets.ts b/server/src/wled_controller/static/js/features/scene-presets.ts index f2574cf..49bc19c 100644 --- a/server/src/wled_controller/static/js/features/scene-presets.ts +++ b/server/src/wled_controller/static/js/features/scene-presets.ts @@ -22,6 +22,19 @@ let _editingId: string | null = null; let _allTargets: any[] = []; // fetched on capture open let _sceneTagsInput: TagInput | null = null; +// ── Auto-name ── + +let _spNameManuallyEdited = false; + +function _autoGenerateScenePresetName() { + if (_spNameManuallyEdited) return; + if ((document.getElementById('scene-preset-editor-id') as HTMLInputElement).value) return; + const items = document.querySelectorAll('#scene-target-list .scene-target-item'); + const count = items.length; + const label = count > 0 ? `${t('scenes.title')} · ${count} ${count === 1 ? 'target' : 'targets'}` : t('scenes.title'); + (document.getElementById('scene-preset-editor-name') as HTMLInputElement).value = label; +} + class ScenePresetEditorModal extends Modal { constructor() { super('scene-preset-editor-modal'); } onForceClose() { @@ -165,6 +178,11 @@ export async function openScenePresetCapture(): Promise { _sceneTagsInput = new TagInput(document.getElementById('scene-tags-container'), { placeholder: t('tags.placeholder') }); _sceneTagsInput.setValue([]); + // Auto-name wiring + _spNameManuallyEdited = false; + (document.getElementById('scene-preset-editor-name') as HTMLElement).oninput = () => { _spNameManuallyEdited = true; }; + _autoGenerateScenePresetName(); + scenePresetModal.open(); scenePresetModal.snapshot(); } @@ -319,7 +337,10 @@ export async function addSceneTarget(): Promise { if (!picked) return; const tgt = _allTargets.find(t => t.id === picked); - if (tgt) _addTargetToList(tgt.id, tgt.name); + if (tgt) { + _addTargetToList(tgt.id, tgt.name); + _autoGenerateScenePresetName(); + } } // removeSceneTarget is now handled via event delegation on the modal @@ -492,6 +513,7 @@ export function initScenePresetDelegation(container: HTMLElement): void { if (item) { item.remove(); _refreshTargetSelect(); + _autoGenerateScenePresetName(); } return; } @@ -528,6 +550,7 @@ if (_sceneEditorModal) { if (item) { item.remove(); _refreshTargetSelect(); + _autoGenerateScenePresetName(); } }); } diff --git a/server/src/wled_controller/static/js/features/sync-clocks.ts b/server/src/wled_controller/static/js/features/sync-clocks.ts index d1b4985..7c8973a 100644 --- a/server/src/wled_controller/static/js/features/sync-clocks.ts +++ b/server/src/wled_controller/static/js/features/sync-clocks.ts @@ -13,6 +13,17 @@ import { TagInput, renderTagChips } from '../core/tag-input.ts'; import { loadPictureSources } from './streams.ts'; import type { SyncClock } from '../types.ts'; +// ── Auto-name ── + +let _scNameManuallyEdited = false; + +function _autoGenerateSyncClockName() { + if (_scNameManuallyEdited) return; + if ((document.getElementById('sync-clock-id') as HTMLInputElement).value) return; + const speed = parseFloat((document.getElementById('sync-clock-speed') as HTMLInputElement).value) || 1.0; + (document.getElementById('sync-clock-name') as HTMLInputElement).value = `${t('sync_clock.group.title')} · ${speed}x`; +} + // ── Modal ── let _syncClockTagsInput: TagInput | null = null; @@ -62,6 +73,12 @@ export async function showSyncClockModal(editData: SyncClock | null): Promise { _scNameManuallyEdited = true; }; + (document.getElementById('sync-clock-speed') as HTMLElement).oninput = () => _autoGenerateSyncClockName(); + if (!isEdit) _autoGenerateSyncClockName(); + syncClockModal.open(); syncClockModal.snapshot(); } diff --git a/server/src/wled_controller/static/js/features/weather-sources.ts b/server/src/wled_controller/static/js/features/weather-sources.ts index 3e9299c..5072090 100644 --- a/server/src/wled_controller/static/js/features/weather-sources.ts +++ b/server/src/wled_controller/static/js/features/weather-sources.ts @@ -24,6 +24,20 @@ function _getProviderItems() { ]; } +// ── Auto-name ── + +let _wsNameManuallyEdited = false; + +function _autoGenerateWeatherSourceName() { + if (_wsNameManuallyEdited) return; + if ((document.getElementById('weather-source-id') as HTMLInputElement).value) return; + const provider = (document.getElementById('weather-source-provider') as HTMLSelectElement).value; + const lat = (document.getElementById('weather-source-latitude') as HTMLInputElement).value; + const lon = (document.getElementById('weather-source-longitude') as HTMLInputElement).value; + const providerLabel = provider === 'open_meteo' ? 'Open-Meteo' : provider; + (document.getElementById('weather-source-name') as HTMLInputElement).value = `${providerLabel} · ${lat}, ${lon}`; +} + // ── Modal ── let _weatherSourceTagsInput: TagInput | null = null; @@ -92,6 +106,13 @@ export async function showWeatherSourceModal(editData: WeatherSource | null = nu columns: 1, }); + // Auto-name wiring + _wsNameManuallyEdited = isEdit; + (document.getElementById('weather-source-name') as HTMLElement).oninput = () => { _wsNameManuallyEdited = true; }; + (document.getElementById('weather-source-latitude') as HTMLElement).oninput = () => _autoGenerateWeatherSourceName(); + (document.getElementById('weather-source-longitude') as HTMLElement).oninput = () => _autoGenerateWeatherSourceName(); + if (!isEdit) _autoGenerateWeatherSourceName(); + // Show/hide test button based on edit mode const testBtn = document.getElementById('weather-source-test-btn'); if (testBtn) testBtn.style.display = isEdit ? '' : 'none'; @@ -230,6 +251,7 @@ export function weatherSourceGeolocate(): void { (pos) => { (document.getElementById('weather-source-latitude') as HTMLInputElement).value = pos.coords.latitude.toFixed(4); (document.getElementById('weather-source-longitude') as HTMLInputElement).value = pos.coords.longitude.toFixed(4); + _autoGenerateWeatherSourceName(); if (btn) btn.classList.remove('loading'); showToast(t('weather_source.geo.success'), 'success'); },