diff --git a/server/src/wled_controller/static/js/core/command-palette.js b/server/src/wled_controller/static/js/core/command-palette.js index 1415c65..2500153 100644 --- a/server/src/wled_controller/static/js/core/command-palette.js +++ b/server/src/wled_controller/static/js/core/command-palette.js @@ -5,6 +5,11 @@ import { fetchWithAuth } from './api.js'; import { t } from './i18n.js'; import { navigateToCard } from './navigation.js'; +import { + getTargetTypeIcon, getPictureSourceIcon, getColorStripIcon, getAudioSourceIcon, + ICON_DEVICE, ICON_TARGET, ICON_PROFILE, ICON_VALUE_SOURCE, + ICON_CAPTURE_TEMPLATE, ICON_PP_TEMPLATE, ICON_PATTERN_TEMPLATE, +} from './icons.js'; let _isOpen = false; let _items = []; @@ -30,67 +35,66 @@ function _buildItems(results) { const items = []; _mapEntities(devices, d => items.push({ - name: d.name, detail: d.device_type, group: 'devices', icon: '๐Ÿ–ฅ๏ธ', + name: d.name, detail: d.device_type, group: 'devices', icon: ICON_DEVICE, nav: ['targets', 'led', 'led-devices', 'data-device-id', d.id], })); _mapEntities(targets, tgt => { if (tgt.target_type === 'key_colors') { items.push({ - name: tgt.name, detail: 'key_colors', group: 'kc_targets', icon: '๐ŸŽจ', + name: tgt.name, detail: 'key_colors', group: 'kc_targets', icon: getTargetTypeIcon('key_colors'), nav: ['targets', 'key_colors', 'kc-targets', 'data-kc-target-id', tgt.id], }); } else { items.push({ - name: tgt.name, detail: tgt.target_type, group: 'targets', icon: 'โšก', + name: tgt.name, detail: tgt.target_type, group: 'targets', icon: ICON_TARGET, nav: ['targets', 'led', 'led-targets', 'data-target-id', tgt.id], }); } }); _mapEntities(css, c => items.push({ - name: c.name, detail: c.css_type || c.source_type, group: 'css', icon: '๐ŸŽจ', + name: c.name, detail: c.css_type || c.source_type, group: 'css', icon: getColorStripIcon(c.css_type || c.source_type), nav: ['targets', 'led', 'led-css', 'data-css-id', c.id], })); _mapEntities(profiles, p => items.push({ - name: p.name, detail: p.enabled ? 'enabled' : '', group: 'profiles', icon: '๐Ÿ“‹', + name: p.name, detail: p.enabled ? 'enabled' : '', group: 'profiles', icon: ICON_PROFILE, nav: ['profiles', null, 'profiles', 'data-profile-id', p.id], })); _mapEntities(capTempl, ct => items.push({ - name: ct.name, detail: ct.engine_type, group: 'capture_templates', icon: '๐Ÿ“ท', + name: ct.name, detail: ct.engine_type, group: 'capture_templates', icon: ICON_CAPTURE_TEMPLATE, nav: ['streams', 'raw', 'raw-templates', 'data-template-id', ct.id], })); _mapEntities(ppTempl, pp => items.push({ - name: pp.name, detail: '', group: 'pp_templates', icon: '๐Ÿ”ง', + name: pp.name, detail: '', group: 'pp_templates', icon: ICON_PP_TEMPLATE, nav: ['streams', 'processed', 'proc-templates', 'data-pp-template-id', pp.id], })); _mapEntities(patTempl, pt => items.push({ - name: pt.name, detail: '', group: 'pattern_templates', icon: '๐Ÿงฉ', + name: pt.name, detail: '', group: 'pattern_templates', icon: ICON_PATTERN_TEMPLATE, nav: ['targets', 'key_colors', 'kc-patterns', 'data-pattern-template-id', pt.id], })); _mapEntities(audioSrc, a => { const section = a.source_type === 'mono' ? 'audio-mono' : 'audio-multi'; items.push({ - name: a.name, detail: a.source_type, group: 'audio', icon: '๐ŸŽต', + name: a.name, detail: a.source_type, group: 'audio', icon: getAudioSourceIcon(a.source_type), nav: ['streams', 'audio', section, 'data-id', a.id], }); }); _mapEntities(valSrc, v => items.push({ - name: v.name, detail: v.source_type, group: 'value', icon: '๐Ÿ”ข', + name: v.name, detail: v.source_type, group: 'value', icon: ICON_VALUE_SOURCE, nav: ['streams', 'value', 'value-sources', 'data-id', v.id], })); _mapEntities(streams, s => { const mapping = _streamSubTab[s.stream_type] || _streamSubTab.raw; - const icon = s.stream_type === 'processed' ? '๐ŸŽž๏ธ' : s.stream_type === 'static_image' ? '๐Ÿ–ผ๏ธ' : '๐Ÿ–ฅ๏ธ'; items.push({ - name: s.name, detail: s.stream_type, group: 'streams', icon, + name: s.name, detail: s.stream_type, group: 'streams', icon: getPictureSourceIcon(s.stream_type), nav: ['streams', mapping.sub, mapping.section, 'data-stream-id', s.id], }); }); diff --git a/server/src/wled_controller/static/js/core/icons.js b/server/src/wled_controller/static/js/core/icons.js new file mode 100644 index 0000000..882e19e --- /dev/null +++ b/server/src/wled_controller/static/js/core/icons.js @@ -0,0 +1,96 @@ +/** + * Centralized emoji icon maps and getter functions. + * + * Import icons from this module instead of using inline emoji literals. + */ + +// โ”€โ”€ Type-resolution maps (private) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +const _targetTypeIcons = { led: '\uD83D\uDCA1', wled: '\uD83D\uDCA1', key_colors: '\uD83C\uDFA8' }; +const _pictureSourceTypeIcons = { raw: '\uD83D\uDDA5\uFE0F', processed: '\uD83C\uDFA8', static_image: '\uD83D\uDDBC\uFE0F' }; +const _colorStripTypeIcons = { + static: '\uD83C\uDFA8', color_cycle: '\uD83D\uDD04', gradient: '\uD83C\uDF08', + effect: '\u26A1', composite: '\uD83D\uDD17', + mapped: '\uD83D\uDCCD', mapped_zones: '\uD83D\uDCCD', + audio: '\uD83C\uDFB5', audio_visualization: '\uD83C\uDFB5', + api_input: '\uD83D\uDCE1', +}; +const _valueSourceTypeIcons = { + static: '\uD83D\uDCCA', animated: '\uD83D\uDD04', audio: '\uD83C\uDFB5', + adaptive_time: '\uD83D\uDD50', adaptive_scene: '\uD83C\uDF24\uFE0F', +}; +const _audioSourceTypeIcons = { mono: '\uD83C\uDFA4', multichannel: '\uD83D\uDD0A' }; +const _engineTypeIcons = { scrcpy: '\uD83D\uDCF1' }; + +// โ”€โ”€ Type-resolution getters โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +/** Target type โ†’ emoji (fallback: โšก) */ +export function getTargetTypeIcon(targetType) { + return _targetTypeIcons[targetType] || '\u26A1'; +} + +/** Picture source / stream type โ†’ emoji (fallback: ๐Ÿ“บ) */ +export function getPictureSourceIcon(streamType) { + return _pictureSourceTypeIcons[streamType] || '\uD83D\uDCFA'; +} + +/** Color strip source type โ†’ emoji (fallback: ๐ŸŽž๏ธ) */ +export function getColorStripIcon(sourceType) { + return _colorStripTypeIcons[sourceType] || '\uD83C\uDF9E\uFE0F'; +} + +/** Value source type โ†’ emoji (fallback: ๐ŸŽš๏ธ) */ +export function getValueSourceIcon(sourceType) { + return _valueSourceTypeIcons[sourceType] || '\uD83C\uDF9A\uFE0F'; +} + +/** Audio source type โ†’ emoji (fallback: ๐ŸŽต) */ +export function getAudioSourceIcon(sourceType) { + return _audioSourceTypeIcons[sourceType] || '\uD83C\uDFB5'; +} + +/** Capture engine type โ†’ emoji (fallback: ๐Ÿš€) */ +export function getEngineIcon(engineType) { + return _engineTypeIcons[engineType] || '\uD83D\uDE80'; +} + +// โ”€โ”€ Entity-kind constants โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export const ICON_PROFILE = '\uD83D\uDCCB'; // ๐Ÿ“‹ +export const ICON_DEVICE = '\uD83D\uDDA5\uFE0F'; // ๐Ÿ–ฅ๏ธ +export const ICON_TARGET = '\u26A1'; // โšก +export const ICON_VALUE_SOURCE = '\uD83D\uDD22'; // ๐Ÿ”ข + +// โ”€โ”€ Template-kind constants โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export const ICON_TEMPLATE = '\uD83D\uDCCB'; // ๐Ÿ“‹ (generic card header) +export const ICON_CAPTURE_TEMPLATE = '\uD83D\uDCF7'; // ๐Ÿ“ท +export const ICON_PP_TEMPLATE = '\uD83D\uDD27'; // ๐Ÿ”ง +export const ICON_PATTERN_TEMPLATE = '\uD83D\uDCC4'; // ๐Ÿ“„ + +// โ”€โ”€ Action constants โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export const ICON_CLONE = '\uD83D\uDCCB'; // ๐Ÿ“‹ +export const ICON_EDIT = '\u270F\uFE0F'; // โœ๏ธ +export const ICON_TEST = '\uD83E\uDDEA'; // ๐Ÿงช +export const ICON_START = '\u25B6\uFE0F'; // โ–ถ๏ธ +export const ICON_STOP = '\u23F9\uFE0F'; // โน๏ธ +export const ICON_STOP_PLAIN = '\u23F9'; // โน +export const ICON_PAUSE = '\u23F8'; // โธ +export const ICON_SETTINGS = '\u2699\uFE0F'; // โš™๏ธ +export const ICON_CALIBRATION = '\uD83D\uDCD0'; // ๐Ÿ“ + +// โ”€โ”€ Misc badge constants โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +export const ICON_AUDIO_LOOPBACK = '\uD83D\uDD0A'; // ๐Ÿ”Š +export const ICON_AUDIO_INPUT = '\uD83C\uDFA4'; // ๐ŸŽค +export const ICON_CLOCK = '\uD83D\uDD50'; // ๐Ÿ• +export const ICON_WARNING = '\u26A0\uFE0F'; // โš ๏ธ +export const ICON_OK = '\u2705'; // โœ… +export const ICON_LINK_SOURCE = '\uD83D\uDCFA'; // ๐Ÿ“บ +export const ICON_LED = '\uD83D\uDCA1'; // ๐Ÿ’ก +export const ICON_FPS = '\u26A1'; // โšก +export const ICON_WEB = '\uD83C\uDF10'; // ๐ŸŒ +export const ICON_OVERLAY = '\uD83D\uDC41\uFE0F'; // ๐Ÿ‘๏ธ +export const ICON_LED_PREVIEW = '\uD83D\uDCCA'; // ๐Ÿ“Š +export const ICON_AUTOSTART = '\u2B50'; // โญ diff --git a/server/src/wled_controller/static/js/features/color-strips.js b/server/src/wled_controller/static/js/features/color-strips.js index 1e54f6d..ecef2d3 100644 --- a/server/src/wled_controller/static/js/features/color-strips.js +++ b/server/src/wled_controller/static/js/features/color-strips.js @@ -6,6 +6,10 @@ import { fetchWithAuth, escapeHtml } from '../core/api.js'; import { t } from '../core/i18n.js'; import { showToast, showConfirm } from '../core/ui.js'; import { Modal } from '../core/modal.js'; +import { + getColorStripIcon, getPictureSourceIcon, + ICON_CLONE, ICON_EDIT, ICON_CALIBRATION, +} from '../core/icons.js'; class CSSEditorModal extends Modal { constructor() { @@ -686,9 +690,9 @@ export function createColorStripCard(source, pictureSourceMap) { `; } - const icon = isStatic ? '๐ŸŽจ' : isColorCycle ? '๐Ÿ”„' : isGradient ? '๐ŸŒˆ' : isEffect ? 'โšก' : isComposite ? '๐Ÿ”—' : isMapped ? '๐Ÿ“' : isAudio ? '๐ŸŽต' : isApiInput ? '๐Ÿ“ก' : '๐ŸŽž๏ธ'; + const icon = getColorStripIcon(source.source_type); const calibrationBtn = (!isStatic && !isGradient && !isColorCycle && !isEffect && !isComposite && !isMapped && !isAudio && !isApiInput) - ? `` + ? `` : ''; return ` @@ -703,8 +707,8 @@ export function createColorStripCard(source, pictureSourceMap) { ${propsHtml}
- - + + ${calibrationBtn}
@@ -734,8 +738,7 @@ export async function showCSSEditor(cssId = null, cloneData = null) { const opt = document.createElement('option'); opt.value = s.id; opt.dataset.name = s.name; - const typeIcon = s.stream_type === 'raw' ? '๐Ÿ–ฅ๏ธ' : s.stream_type === 'static_image' ? '๐Ÿ–ผ๏ธ' : '๐ŸŽจ'; - opt.textContent = `${typeIcon} ${s.name}`; + opt.textContent = `${getPictureSourceIcon(s.stream_type)} ${s.name}`; sourceSelect.appendChild(opt); }); diff --git a/server/src/wled_controller/static/js/features/dashboard.js b/server/src/wled_controller/static/js/features/dashboard.js index f1ec917..654b452 100644 --- a/server/src/wled_controller/static/js/features/dashboard.js +++ b/server/src/wled_controller/static/js/features/dashboard.js @@ -8,6 +8,11 @@ import { t } from '../core/i18n.js'; import { showToast, formatUptime } from '../core/ui.js'; import { renderPerfSection, initPerfCharts, startPerfPolling, stopPerfPolling } from './perf-charts.js'; import { startAutoRefresh, updateTabBadge } from './tabs.js'; +import { + getTargetTypeIcon, + ICON_TARGET, ICON_PROFILE, ICON_CLOCK, ICON_WARNING, ICON_OK, + ICON_STOP, ICON_STOP_PLAIN, ICON_AUTOSTART, +} from '../core/icons.js'; const DASHBOARD_COLLAPSED_KEY = 'dashboard_collapsed'; const MAX_FPS_SAMPLES = 120; @@ -53,7 +58,7 @@ function _startUptimeTimer() { if (!el) continue; const seconds = _getInterpolatedUptime(id); if (seconds != null) { - el.textContent = `๐Ÿ• ${formatUptime(seconds)}`; + el.textContent = `${ICON_CLOCK} ${formatUptime(seconds)}`; } } }, 1000); @@ -184,7 +189,7 @@ function _updateRunningMetrics(enrichedRunning) { if (fpsEl) fpsEl.innerHTML = `${fpsActual}/${fpsTarget}`; const errorsEl = cached?.errors || document.querySelector(`[data-errors-text="${target.id}"]`); - if (errorsEl) errorsEl.textContent = `${errors > 0 ? 'โš ๏ธ' : 'โœ…'} ${errors}`; + if (errorsEl) errorsEl.textContent = `${errors > 0 ? ICON_WARNING : ICON_OK} ${errors}`; // Update health dot const isLed = target.target_type === 'led' || target.target_type === 'wled'; @@ -228,7 +233,7 @@ function _updateProfilesInPlace(profiles) { if (btn) { btn.className = `btn btn-icon ${p.enabled ? 'btn-warning' : 'btn-success'}`; btn.setAttribute('onclick', `dashboardToggleProfile('${p.id}', ${!p.enabled})`); - btn.textContent = p.enabled ? 'โน' : 'โ–ถ'; + btn.textContent = p.enabled ? ICON_STOP_PLAIN : 'โ–ถ'; } } } @@ -369,21 +374,22 @@ export async function loadDashboard(forceFullRender = false) { const isRunning = !!(target.state && target.state.processing); const device = devicesMap[target.device_id]; const deviceName = device ? device.name : ''; - const typeIcon = target.target_type === 'key_colors' ? '๐ŸŽจ' : '๐Ÿ’ก'; + const typeIcon = getTargetTypeIcon(target.target_type); const statusBadge = isRunning ? `${t('profiles.status.active')}` : `${t('profiles.status.inactive')}`; return `
- โญ + ${ICON_AUTOSTART}
${escapeHtml(target.name)} ${statusBadge}
${deviceName ? `
${typeIcon} ${escapeHtml(deviceName)}
` : `
${typeIcon}
`}
+
`; @@ -412,7 +418,7 @@ export async function loadDashboard(forceFullRender = false) { if (running.length > 0) { runningIds = running.map(t => t.id); - const stopAllBtn = ``; + const stopAllBtn = ``; const runningItems = running.map(target => renderDashboardTarget(target, true, devicesMap, cssSourceMap)).join(''); targetsInner += `
@@ -472,7 +478,7 @@ function renderDashboardTarget(target, isRunning, devicesMap = {}, cssSourceMap const state = target.state || {}; const metrics = target.metrics || {}; const isLed = target.target_type === 'led' || target.target_type === 'wled'; - const icon = 'โšก'; + const icon = ICON_TARGET; const typeLabel = isLed ? t('dashboard.type.led') : t('dashboard.type.kc'); let subtitleParts = [typeLabel]; @@ -530,14 +536,14 @@ function renderDashboardTarget(target, isRunning, devicesMap = {}, cssSourceMap
-
๐Ÿ• ${uptime}
+
${ICON_CLOCK} ${uptime}
-
${errors > 0 ? 'โš ๏ธ' : 'โœ…'} ${errors}
+
${errors > 0 ? ICON_WARNING : ICON_OK} ${errors}
- +
`; } else { @@ -587,7 +593,7 @@ function renderDashboardProfile(profile) { return `
- ๐Ÿ“‹ + ${ICON_PROFILE}
${escapeHtml(profile.name)}
${condSummary ? `
${escapeHtml(condSummary)}
` : ''} @@ -602,7 +608,7 @@ function renderDashboardProfile(profile) {
`; diff --git a/server/src/wled_controller/static/js/features/devices.js b/server/src/wled_controller/static/js/features/devices.js index 3809b2d..e5864c5 100644 --- a/server/src/wled_controller/static/js/features/devices.js +++ b/server/src/wled_controller/static/js/features/devices.js @@ -9,6 +9,7 @@ import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, isSerialDevice, isMock import { t } from '../core/i18n.js'; import { showToast, showConfirm } from '../core/ui.js'; import { Modal } from '../core/modal.js'; +import { ICON_SETTINGS, ICON_STOP_PLAIN, ICON_LED, ICON_WEB } from '../core/icons.js'; class DeviceSettingsModal extends Modal { constructor() { super('device-settings-modal'); } @@ -75,20 +76,20 @@ export function createDeviceCard(device) { return `
- ${(device.capabilities || []).includes('power_control') ? `` : ''} + ${(device.capabilities || []).includes('power_control') ? `` : ''}
${device.name || device.id} - ${device.url && device.url.startsWith('http') ? `${escapeHtml(device.url.replace(/^https?:\/\//, ''))}๐ŸŒ` : (device.url && !device.url.startsWith('http') ? `${escapeHtml(device.url)}` : '')} + ${device.url && device.url.startsWith('http') ? `${escapeHtml(device.url.replace(/^https?:\/\//, ''))}${ICON_WEB}` : (device.url && !device.url.startsWith('http') ? `${escapeHtml(device.url)}` : '')} ${healthLabel}
${(device.device_type || 'wled').toUpperCase()} - ${ledCount ? `๐Ÿ’ก ${ledCount}` : ''} + ${ledCount ? `${ICON_LED} ${ledCount}` : ''} ${state.device_led_type ? `๐Ÿ”Œ ${state.device_led_type.replace(/ RGBW$/, '')}` : ''} ${state.device_rgbw ? '' : ''}
@@ -103,7 +104,7 @@ export function createDeviceCard(device) {
` : ''}
diff --git a/server/src/wled_controller/static/js/features/kc-targets.js b/server/src/wled_controller/static/js/features/kc-targets.js index c6884f2..ba786a5 100644 --- a/server/src/wled_controller/static/js/features/kc-targets.js +++ b/server/src/wled_controller/static/js/features/kc-targets.js @@ -14,7 +14,10 @@ import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js' import { t } from '../core/i18n.js'; import { lockBody, showToast, showConfirm, formatUptime } from '../core/ui.js'; import { Modal } from '../core/modal.js'; -import { getValueSourceIcon } from './value-sources.js'; +import { + getValueSourceIcon, getPictureSourceIcon, + ICON_CLONE, ICON_EDIT, ICON_TEST, ICON_START, ICON_STOP, +} from '../core/icons.js'; class KCEditorModal extends Modal { constructor() { @@ -152,21 +155,21 @@ export function createKCTargetCard(target, sourceMap, patternTemplateMap, valueS
${isProcessing ? ` ` : ` `}
@@ -396,8 +399,7 @@ export async function showKCEditor(targetId = null, cloneData = null) { const opt = document.createElement('option'); opt.value = s.id; opt.dataset.name = s.name; - const typeIcon = s.stream_type === 'raw' ? '\uD83D\uDDA5\uFE0F' : s.stream_type === 'static_image' ? '\uD83D\uDDBC\uFE0F' : '\uD83C\uDFA8'; - opt.textContent = `${typeIcon} ${s.name}`; + opt.textContent = `${getPictureSourceIcon(s.stream_type)} ${s.name}`; sourceSelect.appendChild(opt); }); diff --git a/server/src/wled_controller/static/js/features/pattern-templates.js b/server/src/wled_controller/static/js/features/pattern-templates.js index ad494cf..e1e2c6a 100644 --- a/server/src/wled_controller/static/js/features/pattern-templates.js +++ b/server/src/wled_controller/static/js/features/pattern-templates.js @@ -18,6 +18,7 @@ import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js' import { t } from '../core/i18n.js'; import { showToast, showConfirm } from '../core/ui.js'; import { Modal } from '../core/modal.js'; +import { getPictureSourceIcon, ICON_PATTERN_TEMPLATE, ICON_CLONE, ICON_EDIT } from '../core/icons.js'; class PatternTemplateModal extends Modal { constructor() { @@ -55,15 +56,15 @@ export function createPatternTemplateCard(pt) {
- ๐Ÿ“„ ${escapeHtml(pt.name)} + ${ICON_PATTERN_TEMPLATE} ${escapeHtml(pt.name)}
${desc}
โ–ญ ${rectCount} rect${rectCount !== 1 ? 's' : ''}
- - + +
`; @@ -80,8 +81,7 @@ export async function showPatternTemplateEditor(templateId = null, cloneData = n sources.forEach(s => { const opt = document.createElement('option'); opt.value = s.id; - const typeIcon = s.stream_type === 'raw' ? '\uD83D\uDDA5\uFE0F' : s.stream_type === 'static_image' ? '\uD83D\uDDBC\uFE0F' : '\uD83C\uDFA8'; - opt.textContent = `${typeIcon} ${s.name}`; + opt.textContent = `${getPictureSourceIcon(s.stream_type)} ${s.name}`; bgSelect.appendChild(opt); }); diff --git a/server/src/wled_controller/static/js/features/profiles.js b/server/src/wled_controller/static/js/features/profiles.js index e402cfb..7394a54 100644 --- a/server/src/wled_controller/static/js/features/profiles.js +++ b/server/src/wled_controller/static/js/features/profiles.js @@ -9,6 +9,7 @@ import { showToast, showConfirm } from '../core/ui.js'; import { Modal } from '../core/modal.js'; import { CardSection } from '../core/card-sections.js'; import { updateTabBadge } from './tabs.js'; +import { ICON_SETTINGS, ICON_STOP_PLAIN, ICON_START, ICON_PAUSE } from '../core/icons.js'; class ProfileEditorModal extends Modal { constructor() { super('profile-editor-modal'); } @@ -132,17 +133,17 @@ function createProfileCard(profile, runningTargetIds = new Set()) {
${condPills}
- + ${profile.target_ids.length > 0 ? (() => { const anyRunning = profile.target_ids.some(id => runningTargetIds.has(id)); return ``; })() : ''}
`; diff --git a/server/src/wled_controller/static/js/features/streams.js b/server/src/wled_controller/static/js/features/streams.js index 18ae935..a334361 100644 --- a/server/src/wled_controller/static/js/features/streams.js +++ b/server/src/wled_controller/static/js/features/streams.js @@ -30,6 +30,11 @@ import { openDisplayPicker, formatDisplayLabel } from './displays.js'; import { CardSection } from '../core/card-sections.js'; import { updateSubTabHash } from './tabs.js'; import { createValueSourceCard } from './value-sources.js'; +import { + getEngineIcon, getPictureSourceIcon, getAudioSourceIcon, + ICON_TEMPLATE, ICON_CLONE, ICON_EDIT, ICON_TEST, ICON_LINK_SOURCE, + ICON_FPS, ICON_WEB, ICON_VALUE_SOURCE, ICON_AUDIO_LOOPBACK, ICON_AUDIO_INPUT, +} from '../core/icons.js'; // โ”€โ”€ Card section instances โ”€โ”€ const csRawStreams = new CardSection('raw-streams', { titleKey: 'streams.section.streams', gridClass: 'templates-grid', addCardOnclick: "showAddStreamModal('raw')" }); @@ -128,11 +133,6 @@ async function loadCaptureTemplates() { } } -function getEngineIcon(engineType) { - if (engineType === 'scrcpy') return '๐Ÿ“ฑ'; - return '๐Ÿš€'; -} - export async function showAddTemplateModal(cloneData = null) { setCurrentEditingTemplateId(null); document.getElementById('template-modal-title').textContent = t('templates.add'); @@ -587,8 +587,7 @@ function renderPictureSourcesList(streams) { const activeTab = localStorage.getItem('activeStreamTab') || 'raw'; const renderStreamCard = (stream) => { - const typeIcons = { raw: '๐Ÿ–ฅ๏ธ', processed: '๐ŸŽจ', static_image: '๐Ÿ–ผ๏ธ' }; - const typeIcon = typeIcons[stream.stream_type] || '๐Ÿ“บ'; + const typeIcon = getPictureSourceIcon(stream.stream_type); let detailsHtml = ''; if (stream.stream_type === 'raw') { @@ -599,8 +598,8 @@ function renderPictureSourcesList(streams) { } detailsHtml = `
๐Ÿ–ฅ๏ธ ${stream.display_index ?? 0} - โšก ${stream.target_fps ?? 30} - ${capTmplName ? `๐Ÿ“‹ ${capTmplName}` : ''} + ${ICON_FPS} ${stream.target_fps ?? 30} + ${capTmplName ? `${ICON_TEMPLATE} ${capTmplName}` : ''}
`; } else if (stream.stream_type === 'processed') { const sourceStream = _cachedStreams.find(s => s.id === stream.source_stream_id); @@ -613,13 +612,13 @@ function renderPictureSourcesList(streams) { if (ppTmpl) ppTmplName = escapeHtml(ppTmpl.name); } detailsHtml = `
- ๐Ÿ“บ ${sourceName} - ${ppTmplName ? `๐Ÿ“‹ ${ppTmplName}` : ''} + ${ICON_LINK_SOURCE} ${sourceName} + ${ppTmplName ? `${ICON_TEMPLATE} ${ppTmplName}` : ''}
`; } else if (stream.stream_type === 'static_image') { const src = stream.image_source || ''; detailsHtml = `
- ๐ŸŒ ${escapeHtml(src)} + ${ICON_WEB} ${escapeHtml(src)}
`; } @@ -632,9 +631,9 @@ function renderPictureSourcesList(streams) { ${detailsHtml} ${stream.description ? `
${escapeHtml(stream.description)}
` : ''}
- - - + + +
`; @@ -647,11 +646,11 @@ function renderPictureSourcesList(streams) {
-
๐Ÿ“‹ ${escapeHtml(template.name)}
+
${ICON_TEMPLATE} ${escapeHtml(template.name)}
${template.description ? `
${escapeHtml(template.description)}
` : ''}
- ๐Ÿš€ ${template.engine_type.toUpperCase()} + ${getEngineIcon(template.engine_type)} ${template.engine_type.toUpperCase()} ${configEntries.length > 0 ? `๐Ÿ”ง ${configEntries.length}` : ''}
${configEntries.length > 0 ? ` @@ -668,9 +667,9 @@ function renderPictureSourcesList(streams) { ` : ''}
- - - + + +
`; @@ -686,14 +685,14 @@ function renderPictureSourcesList(streams) {
-
๐Ÿ“‹ ${escapeHtml(tmpl.name)}
+
${ICON_TEMPLATE} ${escapeHtml(tmpl.name)}
${tmpl.description ? `
${escapeHtml(tmpl.description)}
` : ''} ${filterChainHtml}
- - - + + +
`; @@ -708,11 +707,11 @@ function renderPictureSourcesList(streams) { const tabs = [ - { key: 'raw', icon: '๐Ÿ–ฅ๏ธ', titleKey: 'streams.group.raw', count: rawStreams.length }, - { key: 'static_image', icon: '๐Ÿ–ผ๏ธ', titleKey: 'streams.group.static_image', count: staticImageStreams.length }, - { key: 'processed', icon: '๐ŸŽจ', titleKey: 'streams.group.processed', count: processedStreams.length }, - { key: 'audio', icon: '๐Ÿ”Š', titleKey: 'streams.group.audio', count: _cachedAudioSources.length }, - { key: 'value', icon: '๐Ÿ”ข', titleKey: 'streams.group.value', count: _cachedValueSources.length }, + { key: 'raw', icon: getPictureSourceIcon('raw'), titleKey: 'streams.group.raw', count: rawStreams.length }, + { key: 'static_image', icon: getPictureSourceIcon('static_image'), titleKey: 'streams.group.static_image', count: staticImageStreams.length }, + { key: 'processed', icon: getPictureSourceIcon('processed'), titleKey: 'streams.group.processed', count: processedStreams.length }, + { key: 'audio', icon: getAudioSourceIcon('multichannel'), titleKey: 'streams.group.audio', count: _cachedAudioSources.length }, + { key: 'value', icon: ICON_VALUE_SOURCE, titleKey: 'streams.group.value', count: _cachedValueSources.length }, ]; const tabBar = `
${tabs.map(tab => @@ -721,7 +720,7 @@ function renderPictureSourcesList(streams) { const renderAudioSourceCard = (src) => { const isMono = src.source_type === 'mono'; - const icon = isMono ? '๐ŸŽค' : '๐Ÿ”Š'; + const icon = getAudioSourceIcon(src.source_type); let propsHtml = ''; if (isMono) { @@ -729,13 +728,13 @@ function renderPictureSourcesList(streams) { const parentName = parent ? parent.name : src.audio_source_id; const chLabel = src.channel === 'left' ? 'L' : src.channel === 'right' ? 'R' : 'M'; propsHtml = ` - ๐Ÿ”Š ${escapeHtml(parentName)} + ${ICON_AUDIO_LOOPBACK} ${escapeHtml(parentName)} ๐Ÿ“ป ${chLabel} `; } else { const devIdx = src.device_index ?? -1; const loopback = src.is_loopback !== false; - const devLabel = loopback ? '๐Ÿ”Š Loopback' : '๐ŸŽค Input'; + const devLabel = loopback ? `${ICON_AUDIO_LOOPBACK} Loopback` : `${ICON_AUDIO_INPUT} Input`; propsHtml = `${devLabel} #${devIdx}`; } @@ -748,8 +747,8 @@ function renderPictureSourcesList(streams) {
${propsHtml}
${src.description ? `
${escapeHtml(src.description)}
` : ''}
- - + +
`; @@ -978,9 +977,7 @@ async function populateStreamModalDropdowns() { const opt = document.createElement('option'); opt.value = s.id; opt.dataset.name = s.name; - const typeLabels = { raw: '๐Ÿ–ฅ๏ธ', processed: '๐ŸŽจ', static_image: '๐Ÿ–ผ๏ธ' }; - const typeLabel = typeLabels[s.stream_type] || '๐Ÿ“บ'; - opt.textContent = `${typeLabel} ${s.name}`; + opt.textContent = `${getPictureSourceIcon(s.stream_type)} ${s.name}`; sourceSelect.appendChild(opt); }); } diff --git a/server/src/wled_controller/static/js/features/targets.js b/server/src/wled_controller/static/js/features/targets.js index d393ecc..dff9311 100644 --- a/server/src/wled_controller/static/js/features/targets.js +++ b/server/src/wled_controller/static/js/features/targets.js @@ -17,7 +17,11 @@ import { Modal } from '../core/modal.js'; import { createDeviceCard, attachDeviceListeners, fetchDeviceBrightness, _computeMaxFps } from './devices.js'; import { createKCTargetCard, connectKCWebSocket, disconnectKCWebSocket } from './kc-targets.js'; import { createColorStripCard } from './color-strips.js'; -import { getValueSourceIcon } from './value-sources.js'; +import { + getValueSourceIcon, getTargetTypeIcon, + ICON_CLONE, ICON_EDIT, ICON_START, ICON_STOP, + ICON_LED, ICON_FPS, ICON_OVERLAY, ICON_LED_PREVIEW, +} from '../core/icons.js'; import { CardSection } from '../core/card-sections.js'; import { updateSubTabHash, updateTabBadge } from './tabs.js'; @@ -493,8 +497,8 @@ export async function loadTargetsTab() { if (activeSubTab === 'wled') activeSubTab = 'led'; const subTabs = [ - { key: 'led', icon: '\uD83D\uDCA1', titleKey: 'targets.subtab.led', count: ledDevices.length + Object.keys(colorStripSourceMap).length + ledTargets.length }, - { key: 'key_colors', icon: '\uD83C\uDFA8', titleKey: 'targets.subtab.key_colors', count: kcTargets.length + patternTemplates.length }, + { key: 'led', icon: getTargetTypeIcon('led'), titleKey: 'targets.subtab.led', count: ledDevices.length + Object.keys(colorStripSourceMap).length + ledTargets.length }, + { key: 'key_colors', icon: getTargetTypeIcon('key_colors'), titleKey: 'targets.subtab.key_colors', count: kcTargets.length + patternTemplates.length }, ]; const tabBar = `
${subTabs.map(tab => @@ -701,8 +705,8 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSo
- ๐Ÿ’ก ${escapeHtml(deviceName)} - โšก ${target.fps || 30} + ${ICON_LED} ${escapeHtml(deviceName)} + ${ICON_FPS} ${target.fps || 30} ๐ŸŽž๏ธ ${cssSummary} ${bvs ? `${getValueSourceIcon(bvs.source_type)} ${escapeHtml(bvs.name)}` : ''}
@@ -776,31 +780,31 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSo
${isProcessing ? ` ` : ` `} ${isProcessing ? ` ` : ''} ${overlayAvailable ? (state.overlay_active ? ` ` : ` `) : ''}
diff --git a/server/src/wled_controller/static/js/features/value-sources.js b/server/src/wled_controller/static/js/features/value-sources.js index 13d0b3a..cfd3a74 100644 --- a/server/src/wled_controller/static/js/features/value-sources.js +++ b/server/src/wled_controller/static/js/features/value-sources.js @@ -15,8 +15,11 @@ import { fetchWithAuth, escapeHtml } from '../core/api.js'; import { t } from '../core/i18n.js'; import { showToast, showConfirm } from '../core/ui.js'; import { Modal } from '../core/modal.js'; +import { getValueSourceIcon, ICON_CLONE, ICON_EDIT } from '../core/icons.js'; import { loadPictureSources } from './streams.js'; +export { getValueSourceIcon }; + class ValueSourceModal extends Modal { constructor() { super('value-source-modal'); } @@ -271,13 +274,6 @@ export async function deleteValueSource(sourceId) { // โ”€โ”€ Card rendering (used by streams.js) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -const _vsTypeIcons = { static: '๐Ÿ“Š', animated: '๐Ÿ”„', audio: '๐ŸŽต', adaptive_time: '๐Ÿ•', adaptive_scene: '๐ŸŒค๏ธ' }; - -/** Return the emoji icon for a given value source type. */ -export function getValueSourceIcon(sourceType) { - return _vsTypeIcons[sourceType] || '๐ŸŽš๏ธ'; -} - export function createValueSourceCard(src) { const icon = getValueSourceIcon(src.source_type); @@ -324,8 +320,8 @@ export function createValueSourceCard(src) {
${propsHtml}
${src.description ? `
${escapeHtml(src.description)}
` : ''}
- - + +
`;