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.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) {
${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) {
${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) {
${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)}
` : ''}
-
-
+
+
`;