Replace all emoji icons with Lucide SVGs, add accent color picker
- Replace all emoji characters across WebUI with inline Lucide SVG icons for cross-platform consistency (icon paths in icon-paths.js) - Add accent color picker popover with 9 preset colors + custom picker, persisted to localStorage, updates all CSS custom properties - Remove subtab separator line for cleaner look - Color badge icons with accent color for visual pop - Remove processing badge from target cards - Fix hardcoded #4CAF50 in FPS labels and active badges to use CSS vars - Replace CSS content emoji (▶) with pure CSS triangle Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
71
server/src/wled_controller/static/js/core/icon-paths.js
Normal file
71
server/src/wled_controller/static/js/core/icon-paths.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Lucide icon SVG path data (https://lucide.dev) — MIT license.
|
||||
*
|
||||
* Each export is the inner SVG markup (paths, circles, rects, lines)
|
||||
* for a 24×24 viewBox icon. These are consumed by icons.js via the
|
||||
* _svg() wrapper which adds the outer <svg> tag with consistent attributes.
|
||||
*
|
||||
* To add a new icon: copy the inner elements from the Lucide source SVG
|
||||
* and export as a single string constant here.
|
||||
*/
|
||||
|
||||
export const lightbulb = '<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>';
|
||||
export const zap = '<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>';
|
||||
export const palette = '<path d="M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z"/><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/>';
|
||||
export const monitor = '<rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/>';
|
||||
export const layoutDashboard = '<rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/>';
|
||||
export const clipboardList = '<rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/>';
|
||||
export const copy = '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>';
|
||||
export const tv = '<path d="m17 2-5 5-5-5"/><rect width="20" height="15" x="2" y="7" rx="2"/>';
|
||||
export const film = '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M7 3v18"/><path d="M3 7.5h4"/><path d="M3 12h18"/><path d="M3 16.5h4"/><path d="M17 3v18"/><path d="M17 7.5h4"/><path d="M17 16.5h4"/>';
|
||||
export const fileText = '<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>';
|
||||
export const flaskConical = '<path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"/><path d="M6.453 15h11.094"/><path d="M8.5 2h7"/>';
|
||||
export const pencil = '<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/>';
|
||||
export const play = '<path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"/>';
|
||||
export const square = '<rect width="18" height="18" x="3" y="3" rx="2"/>';
|
||||
export const pause = '<rect x="14" y="3" width="5" height="18" rx="1"/><rect x="5" y="3" width="5" height="18" rx="1"/>';
|
||||
export const settings = '<path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/><circle cx="12" cy="12" r="3"/>';
|
||||
export const ruler = '<path d="M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z"/><path d="m14.5 12.5 2-2"/><path d="m11.5 9.5 2-2"/><path d="m8.5 6.5 2-2"/><path d="m17.5 15.5 2-2"/>';
|
||||
export const volume2 = '<path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/><path d="M16 9a5 5 0 0 1 0 6"/><path d="M19.364 18.364a9 9 0 0 0 0-12.728"/>';
|
||||
export const mic = '<path d="M12 19v3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><rect x="9" y="2" width="6" height="13" rx="3"/>';
|
||||
export const clock = '<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>';
|
||||
export const triangleAlert = '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/>';
|
||||
export const circleCheck = '<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>';
|
||||
export const globe = '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>';
|
||||
export const eye = '<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/>';
|
||||
export const eyeOff = '<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/>';
|
||||
export const star = '<path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"/>';
|
||||
export const hash = '<line x1="4" x2="20" y1="9" y2="9"/><line x1="4" x2="20" y1="15" y2="15"/><line x1="10" x2="8" y1="3" y2="21"/><line x1="16" x2="14" y1="3" y2="21"/>';
|
||||
export const camera = '<path d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"/><circle cx="12" cy="13" r="3"/>';
|
||||
export const wrench = '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z"/>';
|
||||
export const music = '<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>';
|
||||
export const search = '<path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/>';
|
||||
export const moon = '<path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/>';
|
||||
export const sun = '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>';
|
||||
export const keyRound = '<path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"/><circle cx="16.5" cy="7.5" r=".5" fill="currentColor"/>';
|
||||
export const logOut = '<path d="m16 17 5-5-5-5"/><path d="M21 12H9"/><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>';
|
||||
export const rainbow = '<path d="M22 17a10 10 0 0 0-20 0"/><path d="M6 17a6 6 0 0 1 12 0"/><path d="M10 17a2 2 0 0 1 4 0"/>';
|
||||
export const refreshCw = '<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/>';
|
||||
export const link = '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>';
|
||||
export const mapPin = '<path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/>';
|
||||
export const plug = '<path d="M12 22v-5"/><path d="M15 8V2"/><path d="M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z"/><path d="M9 8V2"/>';
|
||||
export const smartphone = '<rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/>';
|
||||
export const rocket = '<path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09"/><path d="M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05"/>';
|
||||
export const image = '<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>';
|
||||
export const target = '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>';
|
||||
export const trendingUp = '<path d="M16 7h6v6"/><path d="m22 7-8.5 8.5-5-5L2 17"/>';
|
||||
export const activity = '<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"/>';
|
||||
export const timer = '<line x1="10" x2="14" y1="2" y2="2"/><line x1="12" x2="15" y1="14" y2="11"/><circle cx="12" cy="14" r="8"/>';
|
||||
export const moveVertical = '<path d="M12 2v20"/><path d="m8 18 4 4 4-4"/><path d="m8 6 4-4 4 4"/>';
|
||||
export const cloudSun = '<path d="M12 2v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="M20 12h2"/><path d="m19.07 4.93-1.41 1.41"/><path d="M15.947 12.65a4 4 0 0 0-5.925-4.128"/><path d="M13 22H7a5 5 0 1 1 4.9-6H13a3 3 0 0 1 0 6Z"/>';
|
||||
export const sunDim = '<circle cx="12" cy="12" r="4"/><path d="M12 4h.01"/><path d="M20 12h.01"/><path d="M12 20h.01"/><path d="M4 12h.01"/><path d="M17.657 6.343h.01"/><path d="M17.657 17.657h.01"/><path d="M6.343 17.657h.01"/><path d="M6.343 6.343h.01"/>';
|
||||
export const slidersHorizontal = '<path d="M10 5H3"/><path d="M12 19H3"/><path d="M14 3v4"/><path d="M16 17v4"/><path d="M21 12h-9"/><path d="M21 19h-5"/><path d="M21 5h-7"/><path d="M8 10v4"/><path d="M8 12H3"/>';
|
||||
export const circleHelp = '<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/>';
|
||||
export const radio = '<path d="M16.247 7.761a6 6 0 0 1 0 8.478"/><path d="M19.075 4.933a10 10 0 0 1 0 14.134"/><path d="M4.925 19.067a10 10 0 0 1 0-14.134"/><path d="M7.753 16.239a6 6 0 0 1 0-8.478"/><circle cx="12" cy="12" r="2"/>';
|
||||
export const send = '<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/>';
|
||||
export const sparkles = '<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/>';
|
||||
export const fastForward = '<path d="M12 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 12 18z"/><path d="M2 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 2 18z"/>';
|
||||
export const rotateCw = '<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/>';
|
||||
export const rotateCcw = '<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/>';
|
||||
export const download = '<path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/>';
|
||||
export const undo2 = '<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5 5.5 5.5 0 0 1-5.5 5.5H11"/>';
|
||||
@@ -1,97 +1,146 @@
|
||||
/**
|
||||
* Centralized emoji icon maps and getter functions.
|
||||
* Centralized SVG icon constants and getter functions.
|
||||
*
|
||||
* Uses Lucide icons (https://lucide.dev) — MIT-licensed, 24×24 stroke icons.
|
||||
* SVG path data lives in icon-paths.js; this module wraps them with the
|
||||
* <svg> tag and exports named constants for use across the app.
|
||||
*
|
||||
* Import icons from this module instead of using inline emoji literals.
|
||||
*/
|
||||
|
||||
// ── Type-resolution maps (private) ──────────────────────────
|
||||
import * as P from './icon-paths.js';
|
||||
|
||||
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',
|
||||
// ── SVG wrapper ────────────────────────────────────────────
|
||||
const _svg = (d) => `<svg class="icon" viewBox="0 0 24 24">${d}</svg>`;
|
||||
|
||||
// ── Type-resolution maps (private) ──────────────────────────
|
||||
const _targetTypeIcons = { led: _svg(P.lightbulb), wled: _svg(P.lightbulb), key_colors: _svg(P.palette) };
|
||||
const _pictureSourceTypeIcons = { raw: _svg(P.monitor), processed: _svg(P.palette), static_image: _svg(P.image) };
|
||||
const _colorStripTypeIcons = {
|
||||
static: _svg(P.palette), color_cycle: _svg(P.refreshCw), gradient: _svg(P.rainbow),
|
||||
effect: _svg(P.zap), composite: _svg(P.link),
|
||||
mapped: _svg(P.mapPin), mapped_zones: _svg(P.mapPin),
|
||||
audio: _svg(P.music), audio_visualization: _svg(P.music),
|
||||
api_input: _svg(P.send),
|
||||
};
|
||||
const _valueSourceTypeIcons = {
|
||||
static: '\uD83D\uDCCA', animated: '\uD83D\uDD04', audio: '\uD83C\uDFB5',
|
||||
adaptive_time: '\uD83D\uDD50', adaptive_scene: '\uD83C\uDF24\uFE0F',
|
||||
const _valueSourceTypeIcons = {
|
||||
static: _svg(P.layoutDashboard), animated: _svg(P.refreshCw), audio: _svg(P.music),
|
||||
adaptive_time: _svg(P.clock), adaptive_scene: _svg(P.cloudSun),
|
||||
};
|
||||
const _audioSourceTypeIcons = { mono: '\uD83C\uDFA4', multichannel: '\uD83D\uDD0A' };
|
||||
const _engineTypeIcons = { scrcpy: '\uD83D\uDCF1' };
|
||||
const _audioSourceTypeIcons = { mono: _svg(P.mic), multichannel: _svg(P.volume2) };
|
||||
const _engineTypeIcons = { scrcpy: _svg(P.smartphone) };
|
||||
|
||||
// ── Type-resolution getters ─────────────────────────────────
|
||||
|
||||
/** Target type → emoji (fallback: ⚡) */
|
||||
/** Target type → icon (fallback: zap) */
|
||||
export function getTargetTypeIcon(targetType) {
|
||||
return _targetTypeIcons[targetType] || '\u26A1';
|
||||
return _targetTypeIcons[targetType] || _svg(P.zap);
|
||||
}
|
||||
|
||||
/** Picture source / stream type → emoji (fallback: 📺) */
|
||||
/** Picture source / stream type → icon (fallback: tv) */
|
||||
export function getPictureSourceIcon(streamType) {
|
||||
return _pictureSourceTypeIcons[streamType] || '\uD83D\uDCFA';
|
||||
return _pictureSourceTypeIcons[streamType] || _svg(P.tv);
|
||||
}
|
||||
|
||||
/** Color strip source type → emoji (fallback: 🎞️) */
|
||||
/** Color strip source type → icon (fallback: film) */
|
||||
export function getColorStripIcon(sourceType) {
|
||||
return _colorStripTypeIcons[sourceType] || '\uD83C\uDF9E\uFE0F';
|
||||
return _colorStripTypeIcons[sourceType] || _svg(P.film);
|
||||
}
|
||||
|
||||
/** Value source type → emoji (fallback: 🎚️) */
|
||||
/** Value source type → icon (fallback: sliders) */
|
||||
export function getValueSourceIcon(sourceType) {
|
||||
return _valueSourceTypeIcons[sourceType] || '\uD83C\uDF9A\uFE0F';
|
||||
return _valueSourceTypeIcons[sourceType] || _svg(P.slidersHorizontal);
|
||||
}
|
||||
|
||||
/** Audio source type → emoji (fallback: 🎵) */
|
||||
/** Audio source type → icon (fallback: music) */
|
||||
export function getAudioSourceIcon(sourceType) {
|
||||
return _audioSourceTypeIcons[sourceType] || '\uD83C\uDFB5';
|
||||
return _audioSourceTypeIcons[sourceType] || _svg(P.music);
|
||||
}
|
||||
|
||||
/** Capture engine type → emoji (fallback: 🚀) */
|
||||
/** Capture engine type → icon (fallback: rocket) */
|
||||
export function getEngineIcon(engineType) {
|
||||
return _engineTypeIcons[engineType] || '\uD83D\uDE80';
|
||||
return _engineTypeIcons[engineType] || _svg(P.rocket);
|
||||
}
|
||||
|
||||
// ── 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'; // 🔢
|
||||
export const ICON_PROFILE = _svg(P.clipboardList);
|
||||
export const ICON_DEVICE = _svg(P.monitor);
|
||||
export const ICON_TARGET = _svg(P.zap);
|
||||
export const ICON_VALUE_SOURCE = _svg(P.hash);
|
||||
|
||||
// ── 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'; // 📄
|
||||
export const ICON_AUDIO_TEMPLATE = '\uD83C\uDFB5'; // 🎵
|
||||
export const ICON_TEMPLATE = _svg(P.clipboardList);
|
||||
export const ICON_CAPTURE_TEMPLATE = _svg(P.camera);
|
||||
export const ICON_PP_TEMPLATE = _svg(P.wrench);
|
||||
export const ICON_PATTERN_TEMPLATE = _svg(P.fileText);
|
||||
export const ICON_AUDIO_TEMPLATE = _svg(P.music);
|
||||
|
||||
// ── 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'; // 📐
|
||||
export const ICON_CLONE = _svg(P.copy);
|
||||
export const ICON_EDIT = _svg(P.pencil);
|
||||
export const ICON_TEST = _svg(P.flaskConical);
|
||||
export const ICON_START = _svg(P.play);
|
||||
export const ICON_STOP = _svg(P.square);
|
||||
export const ICON_STOP_PLAIN = _svg(P.square);
|
||||
export const ICON_PAUSE = _svg(P.pause);
|
||||
export const ICON_SETTINGS = _svg(P.settings);
|
||||
export const ICON_CALIBRATION = _svg(P.ruler);
|
||||
|
||||
// ── 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'; // ⭐
|
||||
export const ICON_AUDIO_LOOPBACK = _svg(P.volume2);
|
||||
export const ICON_AUDIO_INPUT = _svg(P.mic);
|
||||
export const ICON_CLOCK = _svg(P.clock);
|
||||
export const ICON_WARNING = _svg(P.triangleAlert);
|
||||
export const ICON_OK = _svg(P.circleCheck);
|
||||
export const ICON_LINK_SOURCE = _svg(P.tv);
|
||||
export const ICON_LED = _svg(P.lightbulb);
|
||||
export const ICON_FPS = _svg(P.zap);
|
||||
export const ICON_WEB = _svg(P.globe);
|
||||
export const ICON_OVERLAY = _svg(P.eye);
|
||||
export const ICON_LED_PREVIEW = _svg(P.layoutDashboard);
|
||||
export const ICON_AUTOSTART = _svg(P.star);
|
||||
|
||||
// ── UI / header / modal icons ───────────────────────────────
|
||||
|
||||
export const ICON_SEARCH = _svg(P.search);
|
||||
export const ICON_MOON = _svg(P.moon);
|
||||
export const ICON_SUN = _svg(P.sun);
|
||||
export const ICON_KEY = _svg(P.keyRound);
|
||||
export const ICON_LOGOUT = _svg(P.logOut);
|
||||
export const ICON_EYE = _svg(P.eye);
|
||||
export const ICON_EYE_OFF = _svg(P.eyeOff);
|
||||
export const ICON_HELP = _svg(P.circleHelp);
|
||||
export const ICON_DASHBOARD = _svg(P.layoutDashboard);
|
||||
|
||||
// ── Card badge icons ────────────────────────────────────────
|
||||
|
||||
export const ICON_PALETTE = _svg(P.palette);
|
||||
export const ICON_RAINBOW = _svg(P.rainbow);
|
||||
export const ICON_REFRESH = _svg(P.refreshCw);
|
||||
export const ICON_LINK = _svg(P.link);
|
||||
export const ICON_MAP_PIN = _svg(P.mapPin);
|
||||
export const ICON_MUSIC = _svg(P.music);
|
||||
export const ICON_TIMER = _svg(P.timer);
|
||||
export const ICON_MONITOR = _svg(P.monitor);
|
||||
export const ICON_GLOBE = _svg(P.globe);
|
||||
export const ICON_RADIO = _svg(P.radio);
|
||||
export const ICON_PLUG = _svg(P.plug);
|
||||
export const ICON_FILM = _svg(P.film);
|
||||
export const ICON_FILE_TEXT = _svg(P.fileText);
|
||||
export const ICON_TARGET_ICON = _svg(P.target);
|
||||
export const ICON_TRENDING_UP = _svg(P.trendingUp);
|
||||
export const ICON_ACTIVITY = _svg(P.activity);
|
||||
export const ICON_MOVE_VERTICAL = _svg(P.moveVertical);
|
||||
export const ICON_SUN_DIM = _svg(P.sunDim);
|
||||
export const ICON_CAMERA = _svg(P.camera);
|
||||
export const ICON_WRENCH = _svg(P.wrench);
|
||||
export const ICON_SPARKLES = _svg(P.sparkles);
|
||||
export const ICON_FAST_FORWARD = _svg(P.fastForward);
|
||||
export const ICON_ROTATE_CW = _svg(P.rotateCw);
|
||||
export const ICON_ROTATE_CCW = _svg(P.rotateCcw);
|
||||
export const ICON_DOWNLOAD = _svg(P.download);
|
||||
export const ICON_UNDO = _svg(P.undo2);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import { kcTestAutoRefresh, setKcTestAutoRefresh, setKcTestTargetId, confirmResolve, setConfirmResolve } from './state.js';
|
||||
import { t } from './i18n.js';
|
||||
import { ICON_PAUSE, ICON_START } from './icons.js';
|
||||
|
||||
export function toggleHint(btn) {
|
||||
const hint = btn.closest('.label-row').nextElementSibling;
|
||||
@@ -125,10 +126,10 @@ export function updateAutoRefreshButton(active) {
|
||||
if (!btn) return;
|
||||
if (active) {
|
||||
btn.classList.add('active');
|
||||
btn.innerHTML = '⏸';
|
||||
btn.innerHTML = ICON_PAUSE;
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
btn.innerHTML = '▶';
|
||||
btn.innerHTML = ICON_START;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { API_BASE, fetchWithAuth, escapeHtml } from '../core/api.js';
|
||||
import { t } from '../core/i18n.js';
|
||||
import { showToast, showConfirm, lockBody, unlockBody } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
import { ICON_MUSIC } from '../core/icons.js';
|
||||
import { loadPictureSources } from './streams.js';
|
||||
|
||||
class AudioSourceModal extends Modal {
|
||||
@@ -43,7 +44,7 @@ export async function showAudioSourceModal(sourceType, editData) {
|
||||
? (editData.source_type === 'mono' ? 'audio_source.edit.mono' : 'audio_source.edit.multichannel')
|
||||
: (sourceType === 'mono' ? 'audio_source.add.mono' : 'audio_source.add.multichannel');
|
||||
|
||||
document.getElementById('audio-source-modal-title').textContent = t(titleKey);
|
||||
document.getElementById('audio-source-modal-title').innerHTML = `${ICON_MUSIC} ${t(titleKey)}`;
|
||||
document.getElementById('audio-source-id').value = isEdit ? editData.id : '';
|
||||
document.getElementById('audio-source-error').style.display = 'none';
|
||||
|
||||
@@ -199,7 +200,7 @@ async function _loadAudioDevices() {
|
||||
const data = await resp.json();
|
||||
const devices = data.devices || [];
|
||||
select.innerHTML = devices.map(d => {
|
||||
const label = d.is_loopback ? `🔊 ${d.name}` : `🎤 ${d.name}`;
|
||||
const label = d.name;
|
||||
const val = `${d.index}:${d.is_loopback ? '1' : '0'}`;
|
||||
return `<option value="${val}">${escapeHtml(label)}</option>`;
|
||||
}).join('');
|
||||
|
||||
@@ -10,6 +10,7 @@ import { showToast } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
import { closeTutorial, startCalibrationTutorial } from './tutorials.js';
|
||||
import { startCSSOverlay, stopCSSOverlay } from './color-strips.js';
|
||||
import { ICON_WARNING, ICON_ROTATE_CW, ICON_ROTATE_CCW } from '../core/icons.js';
|
||||
|
||||
/* ── CalibrationModal subclass ────────────────────────────────── */
|
||||
|
||||
@@ -371,7 +372,7 @@ export function updateCalibrationPreview() {
|
||||
const mismatch = inCSS
|
||||
? (declaredCount > 0 && total > declaredCount)
|
||||
: (total !== declaredCount);
|
||||
document.getElementById('cal-total-leds-inline').textContent = (mismatch ? '\u26A0 ' : '') + total;
|
||||
document.getElementById('cal-total-leds-inline').innerHTML = (mismatch ? ICON_WARNING + ' ' : '') + total;
|
||||
if (totalEl) totalEl.classList.toggle('mismatch', mismatch);
|
||||
|
||||
const startPos = document.getElementById('cal-start-position').value;
|
||||
@@ -386,7 +387,7 @@ export function updateCalibrationPreview() {
|
||||
const direction = document.getElementById('cal-layout').value;
|
||||
const dirIcon = document.getElementById('direction-icon');
|
||||
const dirLabel = document.getElementById('direction-label');
|
||||
if (dirIcon) dirIcon.textContent = direction === 'clockwise' ? '↻' : '↺';
|
||||
if (dirIcon) dirIcon.innerHTML = direction === 'clockwise' ? ICON_ROTATE_CW : ICON_ROTATE_CCW;
|
||||
if (dirLabel) dirLabel.textContent = direction === 'clockwise' ? 'CW' : 'CCW';
|
||||
|
||||
const deviceId = document.getElementById('calibration-device-id').value;
|
||||
|
||||
@@ -9,6 +9,9 @@ import { Modal } from '../core/modal.js';
|
||||
import {
|
||||
getColorStripIcon, getPictureSourceIcon,
|
||||
ICON_CLONE, ICON_EDIT, ICON_CALIBRATION,
|
||||
ICON_LED, ICON_PALETTE, ICON_FPS, ICON_MAP_PIN, ICON_MUSIC,
|
||||
ICON_AUDIO_LOOPBACK, ICON_TIMER, ICON_LINK_SOURCE, ICON_FILM,
|
||||
ICON_LINK, ICON_SPARKLES, ICON_FAST_FORWARD, ICON_ACTIVITY,
|
||||
} from '../core/icons.js';
|
||||
|
||||
class CSSEditorModal extends Modal {
|
||||
@@ -587,8 +590,8 @@ export function createColorStripCard(source, pictureSourceMap, audioSourceMap) {
|
||||
|
||||
const anim = (isStatic || isGradient) && source.animation && source.animation.enabled ? source.animation : null;
|
||||
const animBadge = anim
|
||||
? `<span class="stream-card-prop" title="${t('color_strip.animation')}">✨ ${t('color_strip.animation.type.' + anim.type) || anim.type}</span>`
|
||||
+ `<span class="stream-card-prop" title="${t('color_strip.animation.speed')}">⏩ ${(anim.speed || 1.0).toFixed(1)}×</span>`
|
||||
? `<span class="stream-card-prop" title="${t('color_strip.animation')}">${ICON_SPARKLES} ${t('color_strip.animation.type.' + anim.type) || anim.type}</span>`
|
||||
+ `<span class="stream-card-prop" title="${t('color_strip.animation.speed')}">${ICON_FAST_FORWARD} ${(anim.speed || 1.0).toFixed(1)}×</span>`
|
||||
: '';
|
||||
|
||||
let propsHtml;
|
||||
@@ -598,7 +601,7 @@ export function createColorStripCard(source, pictureSourceMap, audioSourceMap) {
|
||||
<span class="stream-card-prop" title="${t('color_strip.static_color')}">
|
||||
<span style="display:inline-block;width:14px;height:14px;background:${hexColor};border:1px solid #888;border-radius:2px;vertical-align:middle;margin-right:4px"></span>${hexColor.toUpperCase()}
|
||||
</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">💡 ${source.led_count}</span>` : ''}
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">${ICON_LED} ${source.led_count}</span>` : ''}
|
||||
${animBadge}
|
||||
`;
|
||||
} else if (isColorCycle) {
|
||||
@@ -608,8 +611,8 @@ export function createColorStripCard(source, pictureSourceMap, audioSourceMap) {
|
||||
).join('');
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">${swatches}</span>
|
||||
<span class="stream-card-prop" title="${t('color_strip.color_cycle.speed')}">⏩ ${(source.cycle_speed || 1.0).toFixed(1)}×</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">💡 ${source.led_count}</span>` : ''}
|
||||
<span class="stream-card-prop" title="${t('color_strip.color_cycle.speed')}">${ICON_FAST_FORWARD} ${(source.cycle_speed || 1.0).toFixed(1)}×</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">${ICON_LED} ${source.led_count}</span>` : ''}
|
||||
`;
|
||||
} else if (isGradient) {
|
||||
const stops = source.stops || [];
|
||||
@@ -629,31 +632,31 @@ export function createColorStripCard(source, pictureSourceMap, audioSourceMap) {
|
||||
}
|
||||
propsHtml = `
|
||||
${cssGradient ? `<span style="flex:1 1 100%;height:12px;background:${cssGradient};border-radius:3px;border:1px solid rgba(128,128,128,0.3)"></span>` : ''}
|
||||
<span class="stream-card-prop">🎨 ${stops.length} ${t('color_strip.gradient.stops_count')}</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">💡 ${source.led_count}</span>` : ''}
|
||||
<span class="stream-card-prop">${ICON_PALETTE} ${stops.length} ${t('color_strip.gradient.stops_count')}</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">${ICON_LED} ${source.led_count}</span>` : ''}
|
||||
${animBadge}
|
||||
`;
|
||||
} else if (isEffect) {
|
||||
const effectLabel = t('color_strip.effect.' + (source.effect_type || 'fire')) || source.effect_type || 'fire';
|
||||
const paletteLabel = source.palette ? (t('color_strip.palette.' + source.palette) || source.palette) : '';
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">⚡ ${escapeHtml(effectLabel)}</span>
|
||||
${paletteLabel ? `<span class="stream-card-prop" title="${t('color_strip.effect.palette')}">🎨 ${escapeHtml(paletteLabel)}</span>` : ''}
|
||||
<span class="stream-card-prop" title="${t('color_strip.effect.speed')}">⏩ ${(source.speed || 1.0).toFixed(1)}×</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">💡 ${source.led_count}</span>` : ''}
|
||||
<span class="stream-card-prop">${ICON_FPS} ${escapeHtml(effectLabel)}</span>
|
||||
${paletteLabel ? `<span class="stream-card-prop" title="${t('color_strip.effect.palette')}">${ICON_PALETTE} ${escapeHtml(paletteLabel)}</span>` : ''}
|
||||
<span class="stream-card-prop" title="${t('color_strip.effect.speed')}">${ICON_FAST_FORWARD} ${(source.speed || 1.0).toFixed(1)}×</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">${ICON_LED} ${source.led_count}</span>` : ''}
|
||||
`;
|
||||
} else if (isComposite) {
|
||||
const layerCount = (source.layers || []).length;
|
||||
const enabledCount = (source.layers || []).filter(l => l.enabled !== false).length;
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">🔗 ${enabledCount}/${layerCount} ${t('color_strip.composite.layers_count')}</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">💡 ${source.led_count}</span>` : ''}
|
||||
<span class="stream-card-prop">${ICON_LINK} ${enabledCount}/${layerCount} ${t('color_strip.composite.layers_count')}</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">${ICON_LED} ${source.led_count}</span>` : ''}
|
||||
`;
|
||||
} else if (isMapped) {
|
||||
const zoneCount = (source.zones || []).length;
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">📍 ${zoneCount} ${t('color_strip.mapped.zones_count')}</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">💡 ${source.led_count}</span>` : ''}
|
||||
<span class="stream-card-prop">${ICON_MAP_PIN} ${zoneCount} ${t('color_strip.mapped.zones_count')}</span>
|
||||
${source.led_count ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">${ICON_LED} ${source.led_count}</span>` : ''}
|
||||
`;
|
||||
} else if (isAudio) {
|
||||
const vizLabel = t('color_strip.audio.viz.' + (source.visualization_mode || 'spectrum')) || source.visualization_mode || 'spectrum';
|
||||
@@ -662,14 +665,14 @@ export function createColorStripCard(source, pictureSourceMap, audioSourceMap) {
|
||||
const showPalette = (vizMode === 'spectrum' || vizMode === 'beat_pulse') && source.palette;
|
||||
const audioPaletteLabel = showPalette ? (t('color_strip.palette.' + source.palette) || source.palette) : '';
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">🎵 ${escapeHtml(vizLabel)}</span>
|
||||
${audioPaletteLabel ? `<span class="stream-card-prop" title="${t('color_strip.audio.palette')}">🎨 ${escapeHtml(audioPaletteLabel)}</span>` : ''}
|
||||
<span class="stream-card-prop" title="${t('color_strip.audio.sensitivity')}">📶 ${sensitivityVal}</span>
|
||||
<span class="stream-card-prop">${ICON_MUSIC} ${escapeHtml(vizLabel)}</span>
|
||||
${audioPaletteLabel ? `<span class="stream-card-prop" title="${t('color_strip.audio.palette')}">${ICON_PALETTE} ${escapeHtml(audioPaletteLabel)}</span>` : ''}
|
||||
<span class="stream-card-prop" title="${t('color_strip.audio.sensitivity')}">${ICON_ACTIVITY} ${sensitivityVal}</span>
|
||||
${source.audio_source_id ? (() => {
|
||||
const as = audioSourceMap && audioSourceMap[source.audio_source_id];
|
||||
const asName = as ? as.name : source.audio_source_id;
|
||||
const asSection = as && as.source_type === 'mono' ? 'audio-mono' : 'audio-multi';
|
||||
return `<span class="stream-card-prop${as ? ' stream-card-link' : ''}" title="${t('color_strip.audio.source')}"${as ? ` onclick="event.stopPropagation(); navigateToCard('streams','audio','${asSection}','data-id','${source.audio_source_id}')"` : ''}>🔊 ${escapeHtml(asName)}</span>`;
|
||||
return `<span class="stream-card-prop${as ? ' stream-card-link' : ''}" title="${t('color_strip.audio.source')}"${as ? ` onclick="event.stopPropagation(); navigateToCard('streams','audio','${asSection}','data-id','${source.audio_source_id}')"` : ''}>${ICON_AUDIO_LOOPBACK} ${escapeHtml(asName)}</span>`;
|
||||
})() : ''}
|
||||
${source.mirror ? `<span class="stream-card-prop">🪞</span>` : ''}
|
||||
`;
|
||||
@@ -680,7 +683,7 @@ export function createColorStripCard(source, pictureSourceMap, audioSourceMap) {
|
||||
<span class="stream-card-prop" title="${t('color_strip.api_input.fallback_color')}">
|
||||
<span style="display:inline-block;width:14px;height:14px;background:${fbColor};border:1px solid #888;border-radius:2px;vertical-align:middle;margin-right:4px"></span>${fbColor.toUpperCase()}
|
||||
</span>
|
||||
<span class="stream-card-prop" title="${t('color_strip.api_input.timeout')}">⏱️ ${timeoutVal}s</span>
|
||||
<span class="stream-card-prop" title="${t('color_strip.api_input.timeout')}">${ICON_TIMER} ${timeoutVal}s</span>
|
||||
`;
|
||||
} else {
|
||||
const ps = pictureSourceMap && pictureSourceMap[source.picture_source_id];
|
||||
@@ -694,8 +697,8 @@ export function createColorStripCard(source, pictureSourceMap, audioSourceMap) {
|
||||
else if (ps.stream_type === 'processed') { psSubTab = 'processed'; psSection = 'proc-streams'; }
|
||||
}
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop stream-card-prop-full${ps ? ' stream-card-link' : ''}" title="${t('color_strip.picture_source')}"${ps ? ` onclick="event.stopPropagation(); navigateToCard('streams','${psSubTab}','${psSection}','data-stream-id','${source.picture_source_id}')"` : ''}>📺 ${escapeHtml(srcName)}</span>
|
||||
${ledCount ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">💡 ${ledCount}</span>` : ''}
|
||||
<span class="stream-card-prop stream-card-prop-full${ps ? ' stream-card-link' : ''}" title="${t('color_strip.picture_source')}"${ps ? ` onclick="event.stopPropagation(); navigateToCard('streams','${psSubTab}','${psSection}','data-stream-id','${source.picture_source_id}')"` : ''}>${ICON_LINK_SOURCE} ${escapeHtml(srcName)}</span>
|
||||
${ledCount ? `<span class="stream-card-prop" title="${t('color_strip.leds')}">${ICON_LED} ${ledCount}</span>` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -845,12 +848,12 @@ export async function showCSSEditor(cssId = null, cloneData = null) {
|
||||
}
|
||||
|
||||
await _populateFromCSS(css);
|
||||
document.getElementById('css-editor-title').textContent = t('color_strip.edit');
|
||||
document.getElementById('css-editor-title').innerHTML = `${ICON_FILM} ${t('color_strip.edit')}`;
|
||||
} else if (cloneData) {
|
||||
document.getElementById('css-editor-id').value = '';
|
||||
document.getElementById('css-editor-name').value = (cloneData.name || '') + ' (Copy)';
|
||||
await _populateFromCSS(cloneData);
|
||||
document.getElementById('css-editor-title').textContent = t('color_strip.add');
|
||||
document.getElementById('css-editor-title').innerHTML = `${ICON_FILM} ${t('color_strip.add')}`;
|
||||
} else {
|
||||
document.getElementById('css-editor-id').value = '';
|
||||
document.getElementById('css-editor-name').value = '';
|
||||
@@ -887,7 +890,7 @@ export async function showCSSEditor(cssId = null, cloneData = null) {
|
||||
document.getElementById('css-editor-api-input-timeout').value = 5.0;
|
||||
document.getElementById('css-editor-api-input-timeout-val').textContent = '5.0';
|
||||
_showApiInputEndpoints(null);
|
||||
document.getElementById('css-editor-title').textContent = t('color_strip.add');
|
||||
document.getElementById('css-editor-title').innerHTML = `${ICON_FILM} ${t('color_strip.add')}`;
|
||||
document.getElementById('css-editor-gradient-preset').value = '';
|
||||
gradientInit([
|
||||
{ position: 0.0, color: [255, 0, 0] },
|
||||
|
||||
@@ -11,7 +11,7 @@ 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,
|
||||
ICON_STOP, ICON_STOP_PLAIN, ICON_START, ICON_AUTOSTART, ICON_HELP,
|
||||
} from '../core/icons.js';
|
||||
|
||||
const DASHBOARD_COLLAPSED_KEY = 'dashboard_collapsed';
|
||||
@@ -64,7 +64,7 @@ function _startUptimeTimer() {
|
||||
if (!el) continue;
|
||||
const seconds = _getInterpolatedUptime(id);
|
||||
if (seconds != null) {
|
||||
el.textContent = `${ICON_CLOCK} ${formatUptime(seconds)}`;
|
||||
el.innerHTML = `${ICON_CLOCK} ${formatUptime(seconds)}`;
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
@@ -218,7 +218,7 @@ function _updateRunningMetrics(enrichedRunning) {
|
||||
}
|
||||
|
||||
const errorsEl = cached?.errors || document.querySelector(`[data-errors-text="${target.id}"]`);
|
||||
if (errorsEl) errorsEl.textContent = `${errors > 0 ? ICON_WARNING : ICON_OK} ${errors}`;
|
||||
if (errorsEl) errorsEl.innerHTML = `${errors > 0 ? ICON_WARNING : ICON_OK} ${errors}`;
|
||||
|
||||
// Update health dot — prefer streaming reachability when processing
|
||||
const isLed = target.target_type === 'led' || target.target_type === 'wled';
|
||||
@@ -267,7 +267,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 ? ICON_STOP_PLAIN : '▶';
|
||||
btn.innerHTML = p.enabled ? ICON_STOP_PLAIN : ICON_START;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -460,7 +460,7 @@ export async function loadDashboard(forceFullRender = false) {
|
||||
</div>
|
||||
<div class="dashboard-target-actions">
|
||||
<button class="btn btn-icon ${isRunning ? 'btn-warning' : 'btn-success'}" onclick="${isRunning ? `dashboardStopTarget('${target.id}')` : `dashboardStartTarget('${target.id}')`}" title="${isRunning ? t('device.stop') : t('device.start')}">
|
||||
${isRunning ? ICON_STOP_PLAIN : '▶'}
|
||||
${isRunning ? ICON_STOP_PLAIN : ICON_START}
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -518,7 +518,7 @@ export async function loadDashboard(forceFullRender = false) {
|
||||
// First load: build everything in one innerHTML to avoid flicker
|
||||
const isFirstLoad = !container.querySelector('.dashboard-perf-persistent');
|
||||
const pollSelect = _renderPollIntervalSelect();
|
||||
const toolbar = `<div class="stream-tab-bar"><span class="cs-expand-collapse-group">${pollSelect}<button class="tutorial-trigger-btn" onclick="startDashboardTutorial()" title="${t('tour.restart')}">?</button></span></div>`;
|
||||
const toolbar = `<div class="stream-tab-bar"><span class="cs-expand-collapse-group">${pollSelect}<button class="tutorial-trigger-btn" onclick="startDashboardTutorial()" title="${t('tour.restart')}">${ICON_HELP}</button></span></div>`;
|
||||
if (isFirstLoad) {
|
||||
container.innerHTML = `${toolbar}<div class="dashboard-perf-persistent dashboard-section">
|
||||
${_sectionHeader('perf', t('dashboard.section.performance'), '')}
|
||||
@@ -639,7 +639,7 @@ function renderDashboardTarget(target, isRunning, devicesMap = {}, cssSourceMap
|
||||
<div class="dashboard-target-metrics"></div>
|
||||
<div class="dashboard-target-actions">
|
||||
<button class="dashboard-autostart-btn${target.auto_start ? ' active' : ''}" onclick="dashboardToggleAutoStart('${target.id}', ${!target.auto_start})" title="${target.auto_start ? t('autostart.toggle.enabled') : t('autostart.toggle.disabled')}">★</button>
|
||||
<button class="btn btn-icon btn-success" onclick="dashboardStartTarget('${target.id}')" title="${t('device.button.start')}">▶</button>
|
||||
<button class="btn btn-icon btn-success" onclick="dashboardStartTarget('${target.id}')" title="${t('device.button.start')}">${ICON_START}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -690,7 +690,7 @@ function renderDashboardProfile(profile) {
|
||||
</div>
|
||||
<div class="dashboard-target-actions">
|
||||
<button class="btn btn-icon ${profile.enabled ? 'btn-warning' : 'btn-success'}" onclick="dashboardToggleProfile('${profile.id}', ${!profile.enabled})" title="${profile.enabled ? t('profiles.action.disable') : t('profiles.status.active')}">
|
||||
${profile.enabled ? ICON_STOP_PLAIN : '▶'}
|
||||
${profile.enabled ? ICON_STOP_PLAIN : ICON_START}
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@@ -9,7 +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';
|
||||
import { ICON_SETTINGS, ICON_STOP_PLAIN, ICON_LED, ICON_WEB, ICON_PLUG } from '../core/icons.js';
|
||||
|
||||
class DeviceSettingsModal extends Modal {
|
||||
constructor() { super('device-settings-modal'); }
|
||||
@@ -90,7 +90,7 @@ export function createDeviceCard(device) {
|
||||
<div class="card-subtitle">
|
||||
<span class="card-meta device-type-badge">${(device.device_type || 'wled').toUpperCase()}</span>
|
||||
${ledCount ? `<span class="card-meta" title="${t('device.led_count')}">${ICON_LED} ${ledCount}</span>` : ''}
|
||||
${state.device_led_type ? `<span class="card-meta">🔌 ${state.device_led_type.replace(/ RGBW$/, '')}</span>` : ''}
|
||||
${state.device_led_type ? `<span class="card-meta">${ICON_PLUG} ${state.device_led_type.replace(/ RGBW$/, '')}</span>` : ''}
|
||||
<span class="card-meta" title="${state.device_rgbw ? 'RGBW' : 'RGB'}"><span class="channel-indicator"><span class="ch" style="background:#e53935"></span><span class="ch" style="background:#43a047"></span><span class="ch" style="background:#1e88e5"></span>${state.device_rgbw ? '<span class="ch" style="background:#eee"></span>' : ''}</span></span>
|
||||
</div>
|
||||
${(device.capabilities || []).includes('brightness_control') ? `
|
||||
|
||||
@@ -16,7 +16,8 @@ import { lockBody, showToast, showConfirm, formatUptime } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
import {
|
||||
getValueSourceIcon, getPictureSourceIcon,
|
||||
ICON_CLONE, ICON_EDIT, ICON_TEST, ICON_START, ICON_STOP,
|
||||
ICON_CLONE, ICON_EDIT, ICON_TEST, ICON_START, ICON_STOP, ICON_PAUSE,
|
||||
ICON_LINK_SOURCE, ICON_PATTERN_TEMPLATE, ICON_FPS, ICON_PALETTE,
|
||||
} from '../core/icons.js';
|
||||
|
||||
class KCEditorModal extends Modal {
|
||||
@@ -126,14 +127,13 @@ export function createKCTargetCard(target, sourceMap, patternTemplateMap, valueS
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
${escapeHtml(target.name)}
|
||||
${isProcessing ? `<span class="badge processing">${t('targets.status.processing')}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stream-card-props">
|
||||
<span class="stream-card-prop${source ? ' stream-card-link' : ''}" title="${t('kc.source')}"${source ? ` onclick="event.stopPropagation(); navigateToCard('streams','${source.stream_type === 'static_image' ? 'static_image' : source.stream_type === 'processed' ? 'processed' : 'raw'}','${source.stream_type === 'static_image' ? 'static-streams' : source.stream_type === 'processed' ? 'proc-streams' : 'raw-streams'}','data-stream-id','${target.picture_source_id}')"` : ''}>📺 ${escapeHtml(sourceName)}</span>
|
||||
<span class="stream-card-prop${patTmpl ? ' stream-card-link' : ''}" title="${t('kc.pattern_template')}"${patTmpl ? ` onclick="event.stopPropagation(); navigateToCard('targets','key_colors','kc-patterns','data-pattern-template-id','${kcSettings.pattern_template_id}')"` : ''}>📄 ${escapeHtml(patternName)}</span>
|
||||
<span class="stream-card-prop${source ? ' stream-card-link' : ''}" title="${t('kc.source')}"${source ? ` onclick="event.stopPropagation(); navigateToCard('streams','${source.stream_type === 'static_image' ? 'static_image' : source.stream_type === 'processed' ? 'processed' : 'raw'}','${source.stream_type === 'static_image' ? 'static-streams' : source.stream_type === 'processed' ? 'proc-streams' : 'raw-streams'}','data-stream-id','${target.picture_source_id}')"` : ''}>${ICON_LINK_SOURCE} ${escapeHtml(sourceName)}</span>
|
||||
<span class="stream-card-prop${patTmpl ? ' stream-card-link' : ''}" title="${t('kc.pattern_template')}"${patTmpl ? ` onclick="event.stopPropagation(); navigateToCard('targets','key_colors','kc-patterns','data-pattern-template-id','${kcSettings.pattern_template_id}')"` : ''}>${ICON_PATTERN_TEMPLATE} ${escapeHtml(patternName)}</span>
|
||||
<span class="stream-card-prop">▭ ${rectCount} rect${rectCount !== 1 ? 's' : ''}</span>
|
||||
<span class="stream-card-prop" title="${t('kc.fps')}">⚡ ${kcSettings.fps ?? 10}</span>
|
||||
<span class="stream-card-prop" title="${t('kc.fps')}">${ICON_FPS} ${kcSettings.fps ?? 10}</span>
|
||||
${bvs ? `<span class="stream-card-prop stream-card-prop-full stream-card-link" title="${t('targets.brightness_vs')}" onclick="event.stopPropagation(); navigateToCard('streams','value','value-sources','data-id','${bvsId}')">${getValueSourceIcon(bvs.source_type)} ${escapeHtml(bvs.name)}</span>` : ''}
|
||||
</div>
|
||||
<div class="brightness-control" data-kc-brightness-wrap="${target.id}">
|
||||
@@ -289,10 +289,10 @@ export function updateAutoRefreshButton(active) {
|
||||
if (!btn) return;
|
||||
if (active) {
|
||||
btn.classList.add('active');
|
||||
btn.innerHTML = '⏸'; // pause symbol
|
||||
btn.innerHTML = ICON_PAUSE;
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
btn.innerHTML = '▶'; // play symbol
|
||||
btn.innerHTML = ICON_START;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ function _populateKCBrightnessVsDropdown(selectedId = '') {
|
||||
const icon = getValueSourceIcon(vs.source_type);
|
||||
const opt = document.createElement('option');
|
||||
opt.value = vs.id;
|
||||
opt.textContent = `${icon} ${vs.name}`;
|
||||
opt.textContent = vs.name;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
sel.value = selectedId || '';
|
||||
@@ -462,7 +462,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
|
||||
document.getElementById('kc-editor-smoothing-value').textContent = kcSettings.smoothing ?? 0.3;
|
||||
patSelect.value = kcSettings.pattern_template_id || '';
|
||||
_populateKCBrightnessVsDropdown(kcSettings.brightness_value_source_id || '');
|
||||
document.getElementById('kc-editor-title').textContent = t('kc.edit');
|
||||
document.getElementById('kc-editor-title').innerHTML = `${ICON_PALETTE} ${t('kc.edit')}`;
|
||||
} else if (cloneData) {
|
||||
const kcSettings = cloneData.key_colors_settings || {};
|
||||
document.getElementById('kc-editor-id').value = '';
|
||||
@@ -475,7 +475,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
|
||||
document.getElementById('kc-editor-smoothing-value').textContent = kcSettings.smoothing ?? 0.3;
|
||||
patSelect.value = kcSettings.pattern_template_id || '';
|
||||
_populateKCBrightnessVsDropdown(kcSettings.brightness_value_source_id || '');
|
||||
document.getElementById('kc-editor-title').textContent = t('kc.add');
|
||||
document.getElementById('kc-editor-title').innerHTML = `${ICON_PALETTE} ${t('kc.add')}`;
|
||||
} else {
|
||||
document.getElementById('kc-editor-id').value = '';
|
||||
document.getElementById('kc-editor-name').value = '';
|
||||
@@ -487,7 +487,7 @@ export async function showKCEditor(targetId = null, cloneData = null) {
|
||||
document.getElementById('kc-editor-smoothing-value').textContent = '0.3';
|
||||
if (patTemplates.length > 0) patSelect.value = patTemplates[0].id;
|
||||
_populateKCBrightnessVsDropdown('');
|
||||
document.getElementById('kc-editor-title').textContent = t('kc.add');
|
||||
document.getElementById('kc-editor-title').innerHTML = `${ICON_PALETTE} ${t('kc.add')}`;
|
||||
}
|
||||
|
||||
// Auto-name
|
||||
|
||||
@@ -97,19 +97,19 @@ export async function showPatternTemplateEditor(templateId = null, cloneData = n
|
||||
document.getElementById('pattern-template-id').value = tmpl.id;
|
||||
document.getElementById('pattern-template-name').value = tmpl.name;
|
||||
document.getElementById('pattern-template-description').value = tmpl.description || '';
|
||||
document.getElementById('pattern-template-modal-title').textContent = t('pattern.edit');
|
||||
document.getElementById('pattern-template-modal-title').innerHTML = `${ICON_PATTERN_TEMPLATE} ${t('pattern.edit')}`;
|
||||
setPatternEditorRects((tmpl.rectangles || []).map(r => ({ ...r })));
|
||||
} else if (cloneData) {
|
||||
document.getElementById('pattern-template-id').value = '';
|
||||
document.getElementById('pattern-template-name').value = (cloneData.name || '') + ' (Copy)';
|
||||
document.getElementById('pattern-template-description').value = cloneData.description || '';
|
||||
document.getElementById('pattern-template-modal-title').textContent = t('pattern.add');
|
||||
document.getElementById('pattern-template-modal-title').innerHTML = `${ICON_PATTERN_TEMPLATE} ${t('pattern.add')}`;
|
||||
setPatternEditorRects((cloneData.rectangles || []).map(r => ({ ...r })));
|
||||
} else {
|
||||
document.getElementById('pattern-template-id').value = '';
|
||||
document.getElementById('pattern-template-name').value = '';
|
||||
document.getElementById('pattern-template-description').value = '';
|
||||
document.getElementById('pattern-template-modal-title').textContent = t('pattern.add');
|
||||
document.getElementById('pattern-template-modal-title').innerHTML = `${ICON_PATTERN_TEMPLATE} ${t('pattern.add')}`;
|
||||
setPatternEditorRects([]);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { showToast, showConfirm, setTabRefreshing } 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';
|
||||
import { ICON_SETTINGS, ICON_STOP_PLAIN, ICON_START, ICON_PAUSE, ICON_CLOCK, ICON_TARGET, ICON_PROFILE, ICON_HELP, ICON_OK } from '../core/icons.js';
|
||||
|
||||
class ProfileEditorModal extends Modal {
|
||||
constructor() { super('profile-editor-modal'); }
|
||||
@@ -88,7 +88,7 @@ function renderProfiles(profiles, runningTargetIds = new Set()) {
|
||||
const container = document.getElementById('profiles-content');
|
||||
|
||||
const items = csProfiles.applySortOrder(profiles.map(p => ({ key: p.id, html: createProfileCard(p, runningTargetIds) })));
|
||||
const toolbar = `<div class="stream-tab-bar"><span class="cs-expand-collapse-group"><button class="btn-expand-collapse" onclick="expandAllProfileSections()" title="${t('section.expand_all')}">⊞</button><button class="btn-expand-collapse" onclick="collapseAllProfileSections()" title="${t('section.collapse_all')}">⊟</button><button class="tutorial-trigger-btn" onclick="startProfilesTutorial()" title="${t('tour.restart')}">?</button></span></div>`;
|
||||
const toolbar = `<div class="stream-tab-bar"><span class="cs-expand-collapse-group"><button class="btn-expand-collapse" onclick="expandAllProfileSections()" title="${t('section.expand_all')}">⊞</button><button class="btn-expand-collapse" onclick="collapseAllProfileSections()" title="${t('section.collapse_all')}">⊟</button><button class="tutorial-trigger-btn" onclick="startProfilesTutorial()" title="${t('tour.restart')}">${ICON_HELP}</button></span></div>`;
|
||||
container.innerHTML = toolbar + csProfiles.render(items);
|
||||
csProfiles.bind();
|
||||
|
||||
@@ -109,7 +109,7 @@ function createProfileCard(profile, runningTargetIds = new Set()) {
|
||||
} else {
|
||||
const parts = profile.conditions.map(c => {
|
||||
if (c.condition_type === 'always') {
|
||||
return `<span class="stream-card-prop">✅ ${t('profiles.condition.always')}</span>`;
|
||||
return `<span class="stream-card-prop">${ICON_OK} ${t('profiles.condition.always')}</span>`;
|
||||
}
|
||||
if (c.condition_type === 'application') {
|
||||
const apps = (c.apps || []).join(', ');
|
||||
@@ -127,7 +127,7 @@ function createProfileCard(profile, runningTargetIds = new Set()) {
|
||||
let lastActivityMeta = '';
|
||||
if (profile.last_activated_at) {
|
||||
const ts = new Date(profile.last_activated_at);
|
||||
lastActivityMeta = `<span class="card-meta" title="${t('profiles.last_activated')}">🕐 ${ts.toLocaleString()}</span>`;
|
||||
lastActivityMeta = `<span class="card-meta" title="${t('profiles.last_activated')}">${ICON_CLOCK} ${ts.toLocaleString()}</span>`;
|
||||
}
|
||||
|
||||
return `
|
||||
@@ -143,7 +143,7 @@ function createProfileCard(profile, runningTargetIds = new Set()) {
|
||||
</div>
|
||||
<div class="card-subtitle">
|
||||
<span class="card-meta">${profile.condition_logic === 'and' ? t('profiles.logic.all') : t('profiles.logic.any')}</span>
|
||||
<span class="card-meta">⚡ ${targetCountText}</span>
|
||||
<span class="card-meta">${ICON_TARGET} ${targetCountText}</span>
|
||||
${lastActivityMeta}
|
||||
</div>
|
||||
<div class="stream-card-props">${condPills}</div>
|
||||
@@ -158,7 +158,7 @@ function createProfileCard(profile, runningTargetIds = new Set()) {
|
||||
</button>`;
|
||||
})() : ''}
|
||||
<button class="btn btn-icon ${profile.enabled ? 'btn-warning' : 'btn-success'}" onclick="toggleProfileEnabled('${profile.id}', ${!profile.enabled})" title="${profile.enabled ? t('profiles.action.disable') : t('profiles.status.active')}">
|
||||
${profile.enabled ? ICON_PAUSE : '▶'}
|
||||
${profile.enabled ? ICON_PAUSE : ICON_START}
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -180,7 +180,7 @@ export async function openProfileEditor(profileId) {
|
||||
await loadProfileTargetChecklist([]);
|
||||
|
||||
if (profileId) {
|
||||
titleEl.textContent = t('profiles.edit');
|
||||
titleEl.innerHTML = `${ICON_PROFILE} ${t('profiles.edit')}`;
|
||||
try {
|
||||
const resp = await fetchWithAuth(`/profiles/${profileId}`);
|
||||
if (!resp.ok) throw new Error('Failed to load profile');
|
||||
@@ -201,7 +201,7 @@ export async function openProfileEditor(profileId) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
titleEl.textContent = t('profiles.add');
|
||||
titleEl.innerHTML = `${ICON_PROFILE} ${t('profiles.add')}`;
|
||||
idInput.value = '';
|
||||
nameInput.value = '';
|
||||
enabledInput.checked = true;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { API_BASE, fetchWithAuth } from '../core/api.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
import { showToast, showConfirm } from '../core/ui.js';
|
||||
import { t } from '../core/i18n.js';
|
||||
import { ICON_UNDO, ICON_DOWNLOAD } from '../core/icons.js';
|
||||
|
||||
// Simple modal (no form / no dirty check needed)
|
||||
const settingsModal = new Modal('settings-modal');
|
||||
@@ -207,8 +208,8 @@ export async function loadBackupList() {
|
||||
<span>${date}</span>
|
||||
<span style="color:var(--text-muted);margin-left:0.3rem;">${sizeKB} KB</span>
|
||||
</div>
|
||||
<button class="btn btn-icon btn-secondary" onclick="restoreSavedBackup('${b.filename}')" title="${t('settings.saved_backups.restore')}" style="padding:2px 6px;font-size:0.8rem;">↺</button>
|
||||
<button class="btn btn-icon btn-secondary" onclick="downloadSavedBackup('${b.filename}')" title="${t('settings.saved_backups.download')}" style="padding:2px 6px;font-size:0.8rem;">⬇</button>
|
||||
<button class="btn btn-icon btn-secondary" onclick="restoreSavedBackup('${b.filename}')" title="${t('settings.saved_backups.restore')}" style="padding:2px 6px;font-size:0.8rem;">${ICON_UNDO}</button>
|
||||
<button class="btn btn-icon btn-secondary" onclick="downloadSavedBackup('${b.filename}')" title="${t('settings.saved_backups.download')}" style="padding:2px 6px;font-size:0.8rem;">${ICON_DOWNLOAD}</button>
|
||||
<button class="btn btn-icon btn-secondary" onclick="deleteSavedBackup('${b.filename}')" title="${t('settings.saved_backups.delete')}" style="padding:2px 6px;font-size:0.8rem;">✕</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
@@ -39,7 +39,8 @@ 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,
|
||||
ICON_AUDIO_TEMPLATE,
|
||||
ICON_AUDIO_TEMPLATE, ICON_MONITOR, ICON_WRENCH, ICON_RADIO,
|
||||
ICON_CAPTURE_TEMPLATE, ICON_PP_TEMPLATE, ICON_HELP,
|
||||
} from '../core/icons.js';
|
||||
|
||||
// ── Card section instances ──
|
||||
@@ -166,7 +167,7 @@ async function loadCaptureTemplates() {
|
||||
|
||||
export async function showAddTemplateModal(cloneData = null) {
|
||||
setCurrentEditingTemplateId(null);
|
||||
document.getElementById('template-modal-title').textContent = t('templates.add');
|
||||
document.getElementById('template-modal-title').innerHTML = `${ICON_CAPTURE_TEMPLATE} ${t('templates.add')}`;
|
||||
document.getElementById('template-form').reset();
|
||||
document.getElementById('template-id').value = '';
|
||||
document.getElementById('engine-config-section').style.display = 'none';
|
||||
@@ -197,7 +198,7 @@ export async function editTemplate(templateId) {
|
||||
const template = await response.json();
|
||||
|
||||
setCurrentEditingTemplateId(templateId);
|
||||
document.getElementById('template-modal-title').textContent = t('templates.edit');
|
||||
document.getElementById('template-modal-title').innerHTML = `${ICON_CAPTURE_TEMPLATE} ${t('templates.edit')}`;
|
||||
document.getElementById('template-id').value = templateId;
|
||||
document.getElementById('template-name').value = template.name;
|
||||
document.getElementById('template-description').value = template.description || '';
|
||||
@@ -751,7 +752,7 @@ async function loadAudioTemplates() {
|
||||
|
||||
export async function showAddAudioTemplateModal(cloneData = null) {
|
||||
setCurrentEditingAudioTemplateId(null);
|
||||
document.getElementById('audio-template-modal-title').textContent = t('audio_template.add');
|
||||
document.getElementById('audio-template-modal-title').innerHTML = `${ICON_AUDIO_TEMPLATE} ${t('audio_template.add')}`;
|
||||
document.getElementById('audio-template-form').reset();
|
||||
document.getElementById('audio-template-id').value = '';
|
||||
document.getElementById('audio-engine-config-section').style.display = 'none';
|
||||
@@ -781,7 +782,7 @@ export async function editAudioTemplate(templateId) {
|
||||
const template = await response.json();
|
||||
|
||||
setCurrentEditingAudioTemplateId(templateId);
|
||||
document.getElementById('audio-template-modal-title').textContent = t('audio_template.edit');
|
||||
document.getElementById('audio-template-modal-title').innerHTML = `${ICON_AUDIO_TEMPLATE} ${t('audio_template.edit')}`;
|
||||
document.getElementById('audio-template-id').value = templateId;
|
||||
document.getElementById('audio-template-name').value = template.name;
|
||||
document.getElementById('audio-template-description').value = template.description || '';
|
||||
@@ -900,7 +901,7 @@ export async function showTestAudioTemplateModal(templateId) {
|
||||
const data = await resp.json();
|
||||
const devices = data.devices || [];
|
||||
deviceSelect.innerHTML = devices.map(d => {
|
||||
const label = d.is_loopback ? `🔊 ${d.name}` : `🎤 ${d.name}`;
|
||||
const label = d.name;
|
||||
const val = `${d.index}:${d.is_loopback ? '1' : '0'}`;
|
||||
return `<option value="${val}">${escapeHtml(label)}</option>`;
|
||||
}).join('');
|
||||
@@ -1182,7 +1183,7 @@ function renderPictureSourcesList(streams) {
|
||||
if (capTmpl) capTmplName = escapeHtml(capTmpl.name);
|
||||
}
|
||||
detailsHtml = `<div class="stream-card-props">
|
||||
<span class="stream-card-prop" title="${t('streams.display')}">🖥️ ${stream.display_index ?? 0}</span>
|
||||
<span class="stream-card-prop" title="${t('streams.display')}">${ICON_MONITOR} ${stream.display_index ?? 0}</span>
|
||||
<span class="stream-card-prop" title="${t('streams.target_fps')}">${ICON_FPS} ${stream.target_fps ?? 30}</span>
|
||||
${capTmplName ? `<span class="stream-card-prop stream-card-link" title="${t('streams.capture_template')}" onclick="event.stopPropagation(); navigateToCard('streams','raw','raw-templates','data-id','${stream.capture_template_id}')">${ICON_TEMPLATE} ${capTmplName}</span>` : ''}
|
||||
</div>`;
|
||||
@@ -1236,7 +1237,7 @@ function renderPictureSourcesList(streams) {
|
||||
${template.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(template.description)}</div>` : ''}
|
||||
<div class="stream-card-props">
|
||||
<span class="stream-card-prop" title="${t('templates.engine')}">${getEngineIcon(template.engine_type)} ${template.engine_type.toUpperCase()}</span>
|
||||
${configEntries.length > 0 ? `<span class="stream-card-prop" title="${t('templates.config.show')}">🔧 ${configEntries.length}</span>` : ''}
|
||||
${configEntries.length > 0 ? `<span class="stream-card-prop" title="${t('templates.config.show')}">${ICON_WRENCH} ${configEntries.length}</span>` : ''}
|
||||
</div>
|
||||
${configEntries.length > 0 ? `
|
||||
<details class="template-config-details">
|
||||
@@ -1301,7 +1302,7 @@ function renderPictureSourcesList(streams) {
|
||||
|
||||
const tabBar = `<div class="stream-tab-bar">${tabs.map(tab =>
|
||||
`<button class="stream-tab-btn${tab.key === activeTab ? ' active' : ''}" data-stream-tab="${tab.key}" onclick="switchStreamTab('${tab.key}')">${tab.icon} ${t(tab.titleKey)} <span class="stream-tab-count">${tab.count}</span></button>`
|
||||
).join('')}<span class="cs-expand-collapse-group"><button class="btn-expand-collapse" onclick="expandAllStreamSections()" title="${t('section.expand_all')}">⊞</button><button class="btn-expand-collapse" onclick="collapseAllStreamSections()" title="${t('section.collapse_all')}">⊟</button><button class="tutorial-trigger-btn" onclick="startSourcesTutorial()" title="${t('tour.restart')}">?</button></span></div>`;
|
||||
).join('')}<span class="cs-expand-collapse-group"><button class="btn-expand-collapse" onclick="expandAllStreamSections()" title="${t('section.expand_all')}">⊞</button><button class="btn-expand-collapse" onclick="collapseAllStreamSections()" title="${t('section.collapse_all')}">⊟</button><button class="tutorial-trigger-btn" onclick="startSourcesTutorial()" title="${t('tour.restart')}">${ICON_HELP}</button></span></div>`;
|
||||
|
||||
const renderAudioSourceCard = (src) => {
|
||||
const isMono = src.source_type === 'mono';
|
||||
@@ -1317,7 +1318,7 @@ function renderPictureSourcesList(streams) {
|
||||
: `<span class="stream-card-prop" title="${escapeHtml(t('audio_source.parent'))}">${ICON_AUDIO_LOOPBACK} ${escapeHtml(parentName)}</span>`;
|
||||
propsHtml = `
|
||||
${parentBadge}
|
||||
<span class="stream-card-prop" title="${escapeHtml(t('audio_source.channel'))}">📻 ${chLabel}</span>
|
||||
<span class="stream-card-prop" title="${escapeHtml(t('audio_source.channel'))}">${ICON_RADIO} ${chLabel}</span>
|
||||
`;
|
||||
} else {
|
||||
const devIdx = src.device_index ?? -1;
|
||||
@@ -1356,7 +1357,7 @@ function renderPictureSourcesList(streams) {
|
||||
${template.description ? `<div class="template-config" style="opacity:0.7;">${escapeHtml(template.description)}</div>` : ''}
|
||||
<div class="stream-card-props">
|
||||
<span class="stream-card-prop" title="${t('audio_template.engine')}">${ICON_AUDIO_TEMPLATE} ${template.engine_type.toUpperCase()}</span>
|
||||
${configEntries.length > 0 ? `<span class="stream-card-prop" title="${t('audio_template.config.show')}">🔧 ${configEntries.length}</span>` : ''}
|
||||
${configEntries.length > 0 ? `<span class="stream-card-prop" title="${t('audio_template.config.show')}">${ICON_WRENCH} ${configEntries.length}</span>` : ''}
|
||||
</div>
|
||||
${configEntries.length > 0 ? `
|
||||
<details class="template-config-details">
|
||||
@@ -1456,7 +1457,7 @@ function _autoGenerateStreamName() {
|
||||
export async function showAddStreamModal(presetType, cloneData = null) {
|
||||
const streamType = (cloneData && cloneData.stream_type) || presetType || 'raw';
|
||||
const titleKeys = { raw: 'streams.add.raw', processed: 'streams.add.processed', static_image: 'streams.add.static_image' };
|
||||
document.getElementById('stream-modal-title').textContent = t(titleKeys[streamType] || 'streams.add');
|
||||
document.getElementById('stream-modal-title').innerHTML = `${getPictureSourceIcon(streamType)} ${t(titleKeys[streamType] || 'streams.add')}`;
|
||||
document.getElementById('stream-form').reset();
|
||||
document.getElementById('stream-id').value = '';
|
||||
document.getElementById('stream-display-index').value = '';
|
||||
@@ -1513,7 +1514,7 @@ export async function editStream(streamId) {
|
||||
const stream = await response.json();
|
||||
|
||||
const editTitleKeys = { raw: 'streams.edit.raw', processed: 'streams.edit.processed', static_image: 'streams.edit.static_image' };
|
||||
document.getElementById('stream-modal-title').textContent = t(editTitleKeys[stream.stream_type] || 'streams.edit');
|
||||
document.getElementById('stream-modal-title').innerHTML = `${getPictureSourceIcon(stream.stream_type)} ${t(editTitleKeys[stream.stream_type] || 'streams.edit')}`;
|
||||
document.getElementById('stream-id').value = streamId;
|
||||
document.getElementById('stream-name').value = stream.name;
|
||||
document.getElementById('stream-description').value = stream.description || '';
|
||||
@@ -2108,7 +2109,7 @@ function _autoGeneratePPTemplateName() {
|
||||
export async function showAddPPTemplateModal(cloneData = null) {
|
||||
if (_availableFilters.length === 0) await loadAvailableFilters();
|
||||
|
||||
document.getElementById('pp-template-modal-title').textContent = t('postprocessing.add');
|
||||
document.getElementById('pp-template-modal-title').innerHTML = `${ICON_PP_TEMPLATE} ${t('postprocessing.add')}`;
|
||||
document.getElementById('pp-template-form').reset();
|
||||
document.getElementById('pp-template-id').value = '';
|
||||
document.getElementById('pp-template-error').style.display = 'none';
|
||||
@@ -2146,7 +2147,7 @@ export async function editPPTemplate(templateId) {
|
||||
if (!response.ok) throw new Error(`Failed to load template: ${response.status}`);
|
||||
const tmpl = await response.json();
|
||||
|
||||
document.getElementById('pp-template-modal-title').textContent = t('postprocessing.edit');
|
||||
document.getElementById('pp-template-modal-title').innerHTML = `${ICON_PP_TEMPLATE} ${t('postprocessing.edit')}`;
|
||||
document.getElementById('pp-template-id').value = templateId;
|
||||
document.getElementById('pp-template-name').value = tmpl.name;
|
||||
document.getElementById('pp-template-description').value = tmpl.description || '';
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
getValueSourceIcon, getTargetTypeIcon,
|
||||
ICON_CLONE, ICON_EDIT, ICON_START, ICON_STOP,
|
||||
ICON_LED, ICON_FPS, ICON_OVERLAY, ICON_LED_PREVIEW,
|
||||
ICON_GLOBE, ICON_RADIO, ICON_PLUG, ICON_FILM, ICON_SUN_DIM, ICON_TARGET_ICON, ICON_HELP,
|
||||
} from '../core/icons.js';
|
||||
import { CardSection } from '../core/card-sections.js';
|
||||
import { updateSubTabHash, updateTabBadge } from './tabs.js';
|
||||
@@ -276,7 +277,7 @@ export async function showTargetEditor(targetId = null, cloneData = null) {
|
||||
document.getElementById('target-editor-fps-value').textContent = fps;
|
||||
document.getElementById('target-editor-keepalive-interval').value = target.keepalive_interval ?? 1.0;
|
||||
document.getElementById('target-editor-keepalive-interval-value').textContent = target.keepalive_interval ?? 1.0;
|
||||
document.getElementById('target-editor-title').textContent = t('targets.edit');
|
||||
document.getElementById('target-editor-title').innerHTML = `${ICON_TARGET_ICON} ${t('targets.edit')}`;
|
||||
|
||||
const thresh = target.min_brightness_threshold ?? 0;
|
||||
document.getElementById('target-editor-brightness-threshold').value = thresh;
|
||||
@@ -297,7 +298,7 @@ export async function showTargetEditor(targetId = null, cloneData = null) {
|
||||
document.getElementById('target-editor-fps-value').textContent = fps;
|
||||
document.getElementById('target-editor-keepalive-interval').value = cloneData.keepalive_interval ?? 1.0;
|
||||
document.getElementById('target-editor-keepalive-interval-value').textContent = cloneData.keepalive_interval ?? 1.0;
|
||||
document.getElementById('target-editor-title').textContent = t('targets.add');
|
||||
document.getElementById('target-editor-title').innerHTML = `${ICON_TARGET_ICON} ${t('targets.add')}`;
|
||||
|
||||
const cloneThresh = cloneData.min_brightness_threshold ?? 0;
|
||||
document.getElementById('target-editor-brightness-threshold').value = cloneThresh;
|
||||
@@ -316,7 +317,7 @@ export async function showTargetEditor(targetId = null, cloneData = null) {
|
||||
document.getElementById('target-editor-fps-value').textContent = '30';
|
||||
document.getElementById('target-editor-keepalive-interval').value = 1.0;
|
||||
document.getElementById('target-editor-keepalive-interval-value').textContent = '1.0';
|
||||
document.getElementById('target-editor-title').textContent = t('targets.add');
|
||||
document.getElementById('target-editor-title').innerHTML = `${ICON_TARGET_ICON} ${t('targets.add')}`;
|
||||
|
||||
document.getElementById('target-editor-brightness-threshold').value = 0;
|
||||
document.getElementById('target-editor-brightness-threshold-value').textContent = '0';
|
||||
@@ -576,7 +577,7 @@ export async function loadTargetsTab() {
|
||||
|
||||
const tabBar = `<div class="stream-tab-bar">${subTabs.map(tab =>
|
||||
`<button class="target-sub-tab-btn stream-tab-btn${tab.key === activeSubTab ? ' active' : ''}" data-target-sub-tab="${tab.key}" onclick="switchTargetSubTab('${tab.key}')">${tab.icon} ${t(tab.titleKey)} <span class="stream-tab-count">${tab.count}</span></button>`
|
||||
).join('')}<span class="cs-expand-collapse-group"><button class="btn-expand-collapse" onclick="expandAllTargetSections()" title="${t('section.expand_all')}">⊞</button><button class="btn-expand-collapse" onclick="collapseAllTargetSections()" title="${t('section.collapse_all')}">⊟</button><button class="tutorial-trigger-btn" onclick="startTargetsTutorial()" title="${t('tour.restart')}">?</button></span></div>`;
|
||||
).join('')}<span class="cs-expand-collapse-group"><button class="btn-expand-collapse" onclick="expandAllTargetSections()" title="${t('section.expand_all')}">⊞</button><button class="btn-expand-collapse" onclick="collapseAllTargetSections()" title="${t('section.collapse_all')}">⊟</button><button class="tutorial-trigger-btn" onclick="startTargetsTutorial()" title="${t('tour.restart')}">${ICON_HELP}</button></span></div>`;
|
||||
|
||||
// Use window.createPatternTemplateCard to avoid circular import
|
||||
const createPatternTemplateCard = window.createPatternTemplateCard || (() => '');
|
||||
@@ -867,16 +868,15 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSo
|
||||
<div class="card-title">
|
||||
<span class="health-dot ${healthClass}" title="${healthTitle}"></span>
|
||||
${escapeHtml(target.name)}
|
||||
${isProcessing ? `<span class="badge processing">${t('device.status.processing')}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stream-card-props">
|
||||
<span class="stream-card-prop stream-card-link" title="${t('targets.device')}" onclick="event.stopPropagation(); navigateToCard('targets','led','led-devices','data-device-id','${target.device_id}')">${ICON_LED} ${escapeHtml(deviceName)}</span>
|
||||
<span class="stream-card-prop" title="${t('targets.fps')}">${ICON_FPS} ${target.fps || 30}</span>
|
||||
${device?.device_type === 'wled' || !device ? `<span class="stream-card-prop" title="${t('targets.protocol')}">${target.protocol === 'http' ? '🌐' : '📡'} ${(target.protocol || 'ddp').toUpperCase()}</span>` : `<span class="stream-card-prop" title="${t('targets.protocol')}">🔌 ${t('targets.protocol.serial')}</span>`}
|
||||
<span class="stream-card-prop stream-card-prop-full${cssId ? ' stream-card-link' : ''}" title="${t('targets.color_strip_source')}"${cssId ? ` onclick="event.stopPropagation(); navigateToCard('targets','led','led-css','data-css-id','${cssId}')"` : ''}>🎞️ ${cssSummary}</span>
|
||||
${device?.device_type === 'wled' || !device ? `<span class="stream-card-prop" title="${t('targets.protocol')}">${target.protocol === 'http' ? ICON_GLOBE : ICON_RADIO} ${(target.protocol || 'ddp').toUpperCase()}</span>` : `<span class="stream-card-prop" title="${t('targets.protocol')}">${ICON_PLUG} ${t('targets.protocol.serial')}</span>`}
|
||||
<span class="stream-card-prop stream-card-prop-full${cssId ? ' stream-card-link' : ''}" title="${t('targets.color_strip_source')}"${cssId ? ` onclick="event.stopPropagation(); navigateToCard('targets','led','led-css','data-css-id','${cssId}')"` : ''}>${ICON_FILM} ${cssSummary}</span>
|
||||
${bvs ? `<span class="stream-card-prop stream-card-prop-full stream-card-link" title="${t('targets.brightness_vs')}" onclick="event.stopPropagation(); navigateToCard('streams','value','value-sources','data-id','${bvsId}')">${getValueSourceIcon(bvs.source_type)} ${escapeHtml(bvs.name)}</span>` : ''}
|
||||
${target.min_brightness_threshold > 0 ? `<span class="stream-card-prop" title="${t('targets.min_brightness_threshold')}">🔅 <${target.min_brightness_threshold} → off</span>` : ''}
|
||||
${target.min_brightness_threshold > 0 ? `<span class="stream-card-prop" title="${t('targets.min_brightness_threshold')}">${ICON_SUN_DIM} <${target.min_brightness_threshold} → off</span>` : ''}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
${isProcessing ? `
|
||||
@@ -1123,7 +1123,7 @@ function connectLedPreviewWS(targetId) {
|
||||
if (bLabel) {
|
||||
const pct = Math.round(brightness / 255 * 100);
|
||||
if (pct < 100 || bLabel.dataset.hasBvs) {
|
||||
bLabel.textContent = `☀ ${pct}%`;
|
||||
bLabel.innerHTML = `${ICON_SUN_DIM} ${pct}%`;
|
||||
bLabel.style.display = '';
|
||||
} else {
|
||||
bLabel.style.display = 'none';
|
||||
|
||||
@@ -15,7 +15,12 @@ import { API_BASE, 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, ICON_TEST } from '../core/icons.js';
|
||||
import {
|
||||
getValueSourceIcon,
|
||||
ICON_CLONE, ICON_EDIT, ICON_TEST,
|
||||
ICON_LED_PREVIEW, ICON_ACTIVITY, ICON_TIMER, ICON_MOVE_VERTICAL,
|
||||
ICON_MUSIC, ICON_TRENDING_UP, ICON_MAP_PIN, ICON_MONITOR, ICON_REFRESH,
|
||||
} from '../core/icons.js';
|
||||
import { loadPictureSources } from './streams.js';
|
||||
|
||||
export { getValueSourceIcon };
|
||||
@@ -58,7 +63,8 @@ export async function showValueSourceModal(editData) {
|
||||
const isEdit = !!editData;
|
||||
const titleKey = isEdit ? 'value_source.edit' : 'value_source.add';
|
||||
|
||||
document.getElementById('value-source-modal-title').textContent = t(titleKey);
|
||||
const titleIcon = isEdit ? getValueSourceIcon(editData.source_type) : getValueSourceIcon('static');
|
||||
document.getElementById('value-source-modal-title').innerHTML = `${titleIcon} ${t(titleKey)}`;
|
||||
document.getElementById('value-source-id').value = isEdit ? editData.id : '';
|
||||
document.getElementById('value-source-error').style.display = 'none';
|
||||
|
||||
@@ -471,13 +477,13 @@ export function createValueSourceCard(src) {
|
||||
|
||||
let propsHtml = '';
|
||||
if (src.source_type === 'static') {
|
||||
propsHtml = `<span class="stream-card-prop">📊 ${t('value_source.type.static')}: ${src.value ?? 1.0}</span>`;
|
||||
propsHtml = `<span class="stream-card-prop">${ICON_LED_PREVIEW} ${t('value_source.type.static')}: ${src.value ?? 1.0}</span>`;
|
||||
} else if (src.source_type === 'animated') {
|
||||
const waveLabel = src.waveform || 'sine';
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">〰️ ${escapeHtml(waveLabel)}</span>
|
||||
<span class="stream-card-prop">⏱️ ${src.speed ?? 10} cpm</span>
|
||||
<span class="stream-card-prop">↕️ ${src.min_value ?? 0}–${src.max_value ?? 1}</span>
|
||||
<span class="stream-card-prop">${ICON_ACTIVITY} ${escapeHtml(waveLabel)}</span>
|
||||
<span class="stream-card-prop">${ICON_TIMER} ${src.speed ?? 10} cpm</span>
|
||||
<span class="stream-card-prop">${ICON_MOVE_VERTICAL} ${src.min_value ?? 0}–${src.max_value ?? 1}</span>
|
||||
`;
|
||||
} else if (src.source_type === 'audio') {
|
||||
const audioSrc = _cachedAudioSources.find(a => a.id === src.audio_source_id);
|
||||
@@ -485,18 +491,18 @@ export function createValueSourceCard(src) {
|
||||
const audioSection = audioSrc ? (audioSrc.source_type === 'mono' ? 'audio-mono' : 'audio-multi') : 'audio-multi';
|
||||
const modeLabel = src.mode || 'rms';
|
||||
const audioBadge = audioSrc
|
||||
? `<span class="stream-card-prop stream-card-link" title="${escapeHtml(t('value_source.audio_source'))}" onclick="event.stopPropagation(); navigateToCard('streams','audio','${audioSection}','data-id','${src.audio_source_id}')">🎵 ${escapeHtml(audioName)}</span>`
|
||||
: `<span class="stream-card-prop" title="${escapeHtml(t('value_source.audio_source'))}">🎵 ${escapeHtml(audioName)}</span>`;
|
||||
? `<span class="stream-card-prop stream-card-link" title="${escapeHtml(t('value_source.audio_source'))}" onclick="event.stopPropagation(); navigateToCard('streams','audio','${audioSection}','data-id','${src.audio_source_id}')">${ICON_MUSIC} ${escapeHtml(audioName)}</span>`
|
||||
: `<span class="stream-card-prop" title="${escapeHtml(t('value_source.audio_source'))}">${ICON_MUSIC} ${escapeHtml(audioName)}</span>`;
|
||||
propsHtml = `
|
||||
${audioBadge}
|
||||
<span class="stream-card-prop">📈 ${modeLabel.toUpperCase()}</span>
|
||||
<span class="stream-card-prop">↕️ ${src.min_value ?? 0}–${src.max_value ?? 1}</span>
|
||||
<span class="stream-card-prop">${ICON_TRENDING_UP} ${modeLabel.toUpperCase()}</span>
|
||||
<span class="stream-card-prop">${ICON_MOVE_VERTICAL} ${src.min_value ?? 0}–${src.max_value ?? 1}</span>
|
||||
`;
|
||||
} else if (src.source_type === 'adaptive_time') {
|
||||
const pts = (src.schedule || []).length;
|
||||
propsHtml = `
|
||||
<span class="stream-card-prop">📍 ${pts} ${t('value_source.schedule.points')}</span>
|
||||
<span class="stream-card-prop">↕️ ${src.min_value ?? 0}–${src.max_value ?? 1}</span>
|
||||
<span class="stream-card-prop">${ICON_MAP_PIN} ${pts} ${t('value_source.schedule.points')}</span>
|
||||
<span class="stream-card-prop">${ICON_MOVE_VERTICAL} ${src.min_value ?? 0}–${src.max_value ?? 1}</span>
|
||||
`;
|
||||
} else if (src.source_type === 'adaptive_scene') {
|
||||
const ps = _cachedStreams.find(s => s.id === src.picture_source_id);
|
||||
@@ -507,11 +513,11 @@ export function createValueSourceCard(src) {
|
||||
else if (ps.stream_type === 'processed') { psSubTab = 'processed'; psSection = 'proc-streams'; }
|
||||
}
|
||||
const psBadge = ps
|
||||
? `<span class="stream-card-prop stream-card-link" onclick="event.stopPropagation(); navigateToCard('streams','${psSubTab}','${psSection}','data-stream-id','${src.picture_source_id}')" title="${escapeHtml(t('value_source.picture_source'))}">🖥️ ${escapeHtml(psName)}</span>`
|
||||
: `<span class="stream-card-prop">🖥️ ${escapeHtml(psName)}</span>`;
|
||||
? `<span class="stream-card-prop stream-card-link" onclick="event.stopPropagation(); navigateToCard('streams','${psSubTab}','${psSection}','data-stream-id','${src.picture_source_id}')" title="${escapeHtml(t('value_source.picture_source'))}">${ICON_MONITOR} ${escapeHtml(psName)}</span>`
|
||||
: `<span class="stream-card-prop">${ICON_MONITOR} ${escapeHtml(psName)}</span>`;
|
||||
propsHtml = `
|
||||
${psBadge}
|
||||
<span class="stream-card-prop">🔄 ${src.scene_behavior || 'complement'}</span>
|
||||
<span class="stream-card-prop">${ICON_REFRESH} ${src.scene_behavior || 'complement'}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user