Apply IconSelect to all type selectors across the app
- Value source type (5 types, static icons) - Device type (7 types, new wifi/usb icon paths + device icon map) - Capture engine (dynamic from API, uses getEngineIcon) - Audio engine (dynamic from API, new getAudioEngineIcon) - Add i18n description keys for value source and device types - Fix trigger button styling to match native input height Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -71,3 +71,5 @@ export const rotateCcw = '<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2
|
|||||||
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 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"/>';
|
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"/>';
|
||||||
export const power = '<path d="M18.36 6.64a9 9 0 1 1-12.73 0"/><line x1="12" x2="12" y1="2" y2="12"/>';
|
export const power = '<path d="M18.36 6.64a9 9 0 1 1-12.73 0"/><line x1="12" x2="12" y1="2" y2="12"/>';
|
||||||
|
export const wifi = '<path d="M12 20h.01"/><path d="M2 8.82a15 15 0 0 1 20 0"/><path d="M5 12.859a10 10 0 0 1 14 0"/><path d="M8.5 16.429a5 5 0 0 1 7 0"/>';
|
||||||
|
export const usb = '<circle cx="10" cy="7" r="1"/><circle cx="4" cy="20" r="1"/><path d="M4.7 19.3 19 5"/><path d="m21 3-3 1 2 2Z"/><path d="M10 8v3a1 1 0 0 1-1 1H4"/><path d="M14 12v2a1 1 0 0 0 1 1h3"/><circle cx="20" cy="15" r="1"/>';
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ const _valueSourceTypeIcons = {
|
|||||||
adaptive_time: _svg(P.clock), adaptive_scene: _svg(P.cloudSun),
|
adaptive_time: _svg(P.clock), adaptive_scene: _svg(P.cloudSun),
|
||||||
};
|
};
|
||||||
const _audioSourceTypeIcons = { mono: _svg(P.mic), multichannel: _svg(P.volume2) };
|
const _audioSourceTypeIcons = { mono: _svg(P.mic), multichannel: _svg(P.volume2) };
|
||||||
|
const _deviceTypeIcons = {
|
||||||
|
wled: _svg(P.wifi), adalight: _svg(P.usb), ambiled: _svg(P.usb),
|
||||||
|
mqtt: _svg(P.send), ws: _svg(P.globe), openrgb: _svg(P.palette),
|
||||||
|
mock: _svg(P.wrench),
|
||||||
|
};
|
||||||
const _engineTypeIcons = { scrcpy: _svg(P.smartphone) };
|
const _engineTypeIcons = { scrcpy: _svg(P.smartphone) };
|
||||||
|
const _audioEngineTypeIcons = { wasapi: _svg(P.volume2), sounddevice: _svg(P.mic) };
|
||||||
|
|
||||||
// ── Type-resolution getters ─────────────────────────────────
|
// ── Type-resolution getters ─────────────────────────────────
|
||||||
|
|
||||||
@@ -58,11 +64,21 @@ export function getAudioSourceIcon(sourceType) {
|
|||||||
return _audioSourceTypeIcons[sourceType] || _svg(P.music);
|
return _audioSourceTypeIcons[sourceType] || _svg(P.music);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Device type → icon (fallback: lightbulb) */
|
||||||
|
export function getDeviceTypeIcon(deviceType) {
|
||||||
|
return _deviceTypeIcons[deviceType] || _svg(P.lightbulb);
|
||||||
|
}
|
||||||
|
|
||||||
/** Capture engine type → icon (fallback: rocket) */
|
/** Capture engine type → icon (fallback: rocket) */
|
||||||
export function getEngineIcon(engineType) {
|
export function getEngineIcon(engineType) {
|
||||||
return _engineTypeIcons[engineType] || _svg(P.rocket);
|
return _engineTypeIcons[engineType] || _svg(P.rocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Audio engine type → icon (fallback: music) */
|
||||||
|
export function getAudioEngineIcon(engineType) {
|
||||||
|
return _audioEngineTypeIcons[engineType] || _svg(P.music);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Entity-kind constants ───────────────────────────────────
|
// ── Entity-kind constants ───────────────────────────────────
|
||||||
|
|
||||||
export const ICON_AUTOMATION = _svg(P.clipboardList);
|
export const ICON_AUTOMATION = _svg(P.clipboardList);
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { t } from '../core/i18n.js';
|
|||||||
import { showToast } from '../core/ui.js';
|
import { showToast } from '../core/ui.js';
|
||||||
import { Modal } from '../core/modal.js';
|
import { Modal } from '../core/modal.js';
|
||||||
import { _computeMaxFps, _renderFpsHint } from './devices.js';
|
import { _computeMaxFps, _renderFpsHint } from './devices.js';
|
||||||
|
import { getDeviceTypeIcon } from '../core/icons.js';
|
||||||
|
import { IconSelect } from '../core/icon-select.js';
|
||||||
|
|
||||||
class AddDeviceModal extends Modal {
|
class AddDeviceModal extends Modal {
|
||||||
constructor() { super('add-device-modal'); }
|
constructor() { super('add-device-modal'); }
|
||||||
@@ -33,8 +35,31 @@ class AddDeviceModal extends Modal {
|
|||||||
|
|
||||||
const addDeviceModal = new AddDeviceModal();
|
const addDeviceModal = new AddDeviceModal();
|
||||||
|
|
||||||
|
/* ── Icon-grid type selector ──────────────────────────────────── */
|
||||||
|
|
||||||
|
const DEVICE_TYPE_KEYS = ['wled', 'adalight', 'ambiled', 'mqtt', 'ws', 'openrgb', 'mock'];
|
||||||
|
|
||||||
|
function _buildDeviceTypeItems() {
|
||||||
|
return DEVICE_TYPE_KEYS.map(key => ({
|
||||||
|
value: key,
|
||||||
|
icon: getDeviceTypeIcon(key),
|
||||||
|
label: t(`device.type.${key}`),
|
||||||
|
desc: t(`device.type.${key}.desc`),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _deviceTypeIconSelect = null;
|
||||||
|
|
||||||
|
function _ensureDeviceTypeIconSelect() {
|
||||||
|
const sel = document.getElementById('device-type');
|
||||||
|
if (!sel) return;
|
||||||
|
if (_deviceTypeIconSelect) { _deviceTypeIconSelect.updateItems(_buildDeviceTypeItems()); return; }
|
||||||
|
_deviceTypeIconSelect = new IconSelect({ target: sel, items: _buildDeviceTypeItems(), columns: 3 });
|
||||||
|
}
|
||||||
|
|
||||||
export function onDeviceTypeChanged() {
|
export function onDeviceTypeChanged() {
|
||||||
const deviceType = document.getElementById('device-type').value;
|
const deviceType = document.getElementById('device-type').value;
|
||||||
|
if (_deviceTypeIconSelect) _deviceTypeIconSelect.setValue(deviceType);
|
||||||
const urlGroup = document.getElementById('device-url-group');
|
const urlGroup = document.getElementById('device-url-group');
|
||||||
const urlInput = document.getElementById('device-url');
|
const urlInput = document.getElementById('device-url');
|
||||||
const serialGroup = document.getElementById('device-serial-port-group');
|
const serialGroup = document.getElementById('device-serial-port-group');
|
||||||
@@ -272,6 +297,7 @@ export function showAddDevice() {
|
|||||||
document.getElementById('device-serial-port').innerHTML = '';
|
document.getElementById('device-serial-port').innerHTML = '';
|
||||||
const scanBtn = document.getElementById('scan-network-btn');
|
const scanBtn = document.getElementById('scan-network-btn');
|
||||||
if (scanBtn) scanBtn.disabled = false;
|
if (scanBtn) scanBtn.disabled = false;
|
||||||
|
_ensureDeviceTypeIconSelect();
|
||||||
addDeviceModal.open();
|
addDeviceModal.open();
|
||||||
onDeviceTypeChanged();
|
onDeviceTypeChanged();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -41,13 +41,14 @@ import { updateSubTabHash } from './tabs.js';
|
|||||||
import { createValueSourceCard } from './value-sources.js';
|
import { createValueSourceCard } from './value-sources.js';
|
||||||
import { createSyncClockCard } from './sync-clocks.js';
|
import { createSyncClockCard } from './sync-clocks.js';
|
||||||
import {
|
import {
|
||||||
getEngineIcon, getPictureSourceIcon, getAudioSourceIcon,
|
getEngineIcon, getAudioEngineIcon, getPictureSourceIcon, getAudioSourceIcon,
|
||||||
ICON_TEMPLATE, ICON_CLONE, ICON_EDIT, ICON_TEST, ICON_LINK_SOURCE,
|
ICON_TEMPLATE, ICON_CLONE, ICON_EDIT, ICON_TEST, ICON_LINK_SOURCE,
|
||||||
ICON_FPS, ICON_WEB, ICON_VALUE_SOURCE, ICON_CLOCK, ICON_AUDIO_LOOPBACK, ICON_AUDIO_INPUT,
|
ICON_FPS, ICON_WEB, ICON_VALUE_SOURCE, ICON_CLOCK, ICON_AUDIO_LOOPBACK, ICON_AUDIO_INPUT,
|
||||||
ICON_AUDIO_TEMPLATE, ICON_MONITOR, ICON_WRENCH, ICON_RADIO,
|
ICON_AUDIO_TEMPLATE, ICON_MONITOR, ICON_WRENCH, ICON_RADIO,
|
||||||
ICON_CAPTURE_TEMPLATE, ICON_PP_TEMPLATE, ICON_HELP,
|
ICON_CAPTURE_TEMPLATE, ICON_PP_TEMPLATE, ICON_HELP,
|
||||||
} from '../core/icons.js';
|
} from '../core/icons.js';
|
||||||
import { wrapCard } from '../core/card-colors.js';
|
import { wrapCard } from '../core/card-colors.js';
|
||||||
|
import { IconSelect } from '../core/icon-select.js';
|
||||||
|
|
||||||
// ── Card section instances ──
|
// ── Card section instances ──
|
||||||
const csRawStreams = new CardSection('raw-streams', { titleKey: 'streams.section.streams', gridClass: 'templates-grid', addCardOnclick: "showAddStreamModal('raw')", keyAttr: 'data-stream-id' });
|
const csRawStreams = new CardSection('raw-streams', { titleKey: 'streams.section.streams', gridClass: 'templates-grid', addCardOnclick: "showAddStreamModal('raw')", keyAttr: 'data-stream-id' });
|
||||||
@@ -296,14 +297,25 @@ async function loadAvailableEngines() {
|
|||||||
const firstAvailable = availableEngines.find(e => e.available);
|
const firstAvailable = availableEngines.find(e => e.available);
|
||||||
if (firstAvailable) select.value = firstAvailable.type;
|
if (firstAvailable) select.value = firstAvailable.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update icon-grid selector with dynamic engine list
|
||||||
|
const items = availableEngines
|
||||||
|
.filter(e => e.available)
|
||||||
|
.map(e => ({ value: e.type, icon: getEngineIcon(e.type), label: e.name, desc: '' }));
|
||||||
|
if (_engineIconSelect) { _engineIconSelect.updateItems(items); }
|
||||||
|
else { _engineIconSelect = new IconSelect({ target: select, items, columns: 2 }); }
|
||||||
|
_engineIconSelect.setValue(select.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading engines:', error);
|
console.error('Error loading engines:', error);
|
||||||
showToast(t('templates.error.engines') + ': ' + error.message, 'error');
|
showToast(t('templates.error.engines') + ': ' + error.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _engineIconSelect = null;
|
||||||
|
|
||||||
export async function onEngineChange() {
|
export async function onEngineChange() {
|
||||||
const engineType = document.getElementById('template-engine').value;
|
const engineType = document.getElementById('template-engine').value;
|
||||||
|
if (_engineIconSelect) _engineIconSelect.setValue(engineType);
|
||||||
const configSection = document.getElementById('engine-config-section');
|
const configSection = document.getElementById('engine-config-section');
|
||||||
const configFields = document.getElementById('engine-config-fields');
|
const configFields = document.getElementById('engine-config-fields');
|
||||||
|
|
||||||
@@ -667,14 +679,25 @@ async function loadAvailableAudioEngines() {
|
|||||||
const firstAvailable = availableAudioEngines.find(e => e.available);
|
const firstAvailable = availableAudioEngines.find(e => e.available);
|
||||||
if (firstAvailable) select.value = firstAvailable.type;
|
if (firstAvailable) select.value = firstAvailable.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update icon-grid selector with dynamic engine list
|
||||||
|
const items = availableAudioEngines
|
||||||
|
.filter(e => e.available)
|
||||||
|
.map(e => ({ value: e.type, icon: getAudioEngineIcon(e.type), label: e.type.toUpperCase(), desc: '' }));
|
||||||
|
if (_audioEngineIconSelect) { _audioEngineIconSelect.updateItems(items); }
|
||||||
|
else { _audioEngineIconSelect = new IconSelect({ target: select, items, columns: 2 }); }
|
||||||
|
_audioEngineIconSelect.setValue(select.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading audio engines:', error);
|
console.error('Error loading audio engines:', error);
|
||||||
showToast(t('audio_template.error.engines') + ': ' + error.message, 'error');
|
showToast(t('audio_template.error.engines') + ': ' + error.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _audioEngineIconSelect = null;
|
||||||
|
|
||||||
export async function onAudioEngineChange() {
|
export async function onAudioEngineChange() {
|
||||||
const engineType = document.getElementById('audio-template-engine').value;
|
const engineType = document.getElementById('audio-template-engine').value;
|
||||||
|
if (_audioEngineIconSelect) _audioEngineIconSelect.setValue(engineType);
|
||||||
const configSection = document.getElementById('audio-engine-config-section');
|
const configSection = document.getElementById('audio-engine-config-section');
|
||||||
const configFields = document.getElementById('audio-engine-config-fields');
|
const configFields = document.getElementById('audio-engine-config-fields');
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
ICON_MUSIC, ICON_TRENDING_UP, ICON_MAP_PIN, ICON_MONITOR, ICON_REFRESH,
|
ICON_MUSIC, ICON_TRENDING_UP, ICON_MAP_PIN, ICON_MONITOR, ICON_REFRESH,
|
||||||
} from '../core/icons.js';
|
} from '../core/icons.js';
|
||||||
import { wrapCard } from '../core/card-colors.js';
|
import { wrapCard } from '../core/card-colors.js';
|
||||||
|
import { IconSelect } from '../core/icon-select.js';
|
||||||
import { loadPictureSources } from './streams.js';
|
import { loadPictureSources } from './streams.js';
|
||||||
|
|
||||||
export { getValueSourceIcon };
|
export { getValueSourceIcon };
|
||||||
@@ -58,6 +59,28 @@ class ValueSourceModal extends Modal {
|
|||||||
|
|
||||||
const valueSourceModal = new ValueSourceModal();
|
const valueSourceModal = new ValueSourceModal();
|
||||||
|
|
||||||
|
/* ── Icon-grid type selector ──────────────────────────────────── */
|
||||||
|
|
||||||
|
const VS_TYPE_KEYS = ['static', 'animated', 'audio', 'adaptive_time', 'adaptive_scene'];
|
||||||
|
|
||||||
|
function _buildVSTypeItems() {
|
||||||
|
return VS_TYPE_KEYS.map(key => ({
|
||||||
|
value: key,
|
||||||
|
icon: getValueSourceIcon(key),
|
||||||
|
label: t(`value_source.type.${key}`),
|
||||||
|
desc: t(`value_source.type.${key}.desc`),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let _vsTypeIconSelect = null;
|
||||||
|
|
||||||
|
function _ensureVSTypeIconSelect() {
|
||||||
|
const sel = document.getElementById('value-source-type');
|
||||||
|
if (!sel) return;
|
||||||
|
if (_vsTypeIconSelect) { _vsTypeIconSelect.updateItems(_buildVSTypeItems()); return; }
|
||||||
|
_vsTypeIconSelect = new IconSelect({ target: sel, items: _buildVSTypeItems(), columns: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
// ── Modal ─────────────────────────────────────────────────────
|
// ── Modal ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function showValueSourceModal(editData) {
|
export async function showValueSourceModal(editData) {
|
||||||
@@ -69,6 +92,7 @@ export async function showValueSourceModal(editData) {
|
|||||||
document.getElementById('value-source-id').value = isEdit ? editData.id : '';
|
document.getElementById('value-source-id').value = isEdit ? editData.id : '';
|
||||||
document.getElementById('value-source-error').style.display = 'none';
|
document.getElementById('value-source-error').style.display = 'none';
|
||||||
|
|
||||||
|
_ensureVSTypeIconSelect();
|
||||||
const typeSelect = document.getElementById('value-source-type');
|
const typeSelect = document.getElementById('value-source-type');
|
||||||
document.getElementById('value-source-type-group').style.display = isEdit ? 'none' : '';
|
document.getElementById('value-source-type-group').style.display = isEdit ? 'none' : '';
|
||||||
|
|
||||||
@@ -142,6 +166,7 @@ export async function closeValueSourceModal() {
|
|||||||
|
|
||||||
export function onValueSourceTypeChange() {
|
export function onValueSourceTypeChange() {
|
||||||
const type = document.getElementById('value-source-type').value;
|
const type = document.getElementById('value-source-type').value;
|
||||||
|
if (_vsTypeIconSelect) _vsTypeIconSelect.setValue(type);
|
||||||
document.getElementById('value-source-static-section').style.display = type === 'static' ? '' : 'none';
|
document.getElementById('value-source-static-section').style.display = type === 'static' ? '' : 'none';
|
||||||
document.getElementById('value-source-animated-section').style.display = type === 'animated' ? '' : 'none';
|
document.getElementById('value-source-animated-section').style.display = type === 'animated' ? '' : 'none';
|
||||||
document.getElementById('value-source-audio-section').style.display = type === 'audio' ? '' : 'none';
|
document.getElementById('value-source-audio-section').style.display = type === 'audio' ? '' : 'none';
|
||||||
|
|||||||
@@ -120,6 +120,20 @@
|
|||||||
"device.scan.selected": "Device selected",
|
"device.scan.selected": "Device selected",
|
||||||
"device.type": "Device Type:",
|
"device.type": "Device Type:",
|
||||||
"device.type.hint": "Select the type of LED controller",
|
"device.type.hint": "Select the type of LED controller",
|
||||||
|
"device.type.wled": "WLED",
|
||||||
|
"device.type.wled.desc": "WiFi LED controller over HTTP/UDP",
|
||||||
|
"device.type.adalight": "Adalight",
|
||||||
|
"device.type.adalight.desc": "Serial LED protocol for Arduino",
|
||||||
|
"device.type.ambiled": "AmbiLED",
|
||||||
|
"device.type.ambiled.desc": "Serial protocol for AmbiLED devices",
|
||||||
|
"device.type.mqtt": "MQTT",
|
||||||
|
"device.type.mqtt.desc": "Publish LED data via MQTT broker",
|
||||||
|
"device.type.ws": "WebSocket",
|
||||||
|
"device.type.ws.desc": "Stream LED data to WebSocket clients",
|
||||||
|
"device.type.openrgb": "OpenRGB",
|
||||||
|
"device.type.openrgb.desc": "Control RGB peripherals via OpenRGB",
|
||||||
|
"device.type.mock": "Mock",
|
||||||
|
"device.type.mock.desc": "Virtual device for testing",
|
||||||
"device.serial_port": "Serial Port:",
|
"device.serial_port": "Serial Port:",
|
||||||
"device.serial_port.hint": "Select the COM port of the Adalight device",
|
"device.serial_port.hint": "Select the COM port of the Adalight device",
|
||||||
"device.serial_port.none": "No serial ports found",
|
"device.serial_port.none": "No serial ports found",
|
||||||
@@ -999,10 +1013,15 @@
|
|||||||
"value_source.type": "Type:",
|
"value_source.type": "Type:",
|
||||||
"value_source.type.hint": "Static outputs a constant value. Animated cycles through a waveform. Audio reacts to sound input. Adaptive types adjust brightness automatically based on time of day or scene content.",
|
"value_source.type.hint": "Static outputs a constant value. Animated cycles through a waveform. Audio reacts to sound input. Adaptive types adjust brightness automatically based on time of day or scene content.",
|
||||||
"value_source.type.static": "Static",
|
"value_source.type.static": "Static",
|
||||||
|
"value_source.type.static.desc": "Constant output value",
|
||||||
"value_source.type.animated": "Animated",
|
"value_source.type.animated": "Animated",
|
||||||
|
"value_source.type.animated.desc": "Cycles through a waveform",
|
||||||
"value_source.type.audio": "Audio",
|
"value_source.type.audio": "Audio",
|
||||||
"value_source.type.adaptive_time": "Adaptive (Time of Day)",
|
"value_source.type.audio.desc": "Reacts to sound input",
|
||||||
|
"value_source.type.adaptive_time": "Adaptive (Time)",
|
||||||
|
"value_source.type.adaptive_time.desc": "Adjusts by time of day",
|
||||||
"value_source.type.adaptive_scene": "Adaptive (Scene)",
|
"value_source.type.adaptive_scene": "Adaptive (Scene)",
|
||||||
|
"value_source.type.adaptive_scene.desc": "Adjusts by scene content",
|
||||||
"value_source.value": "Value:",
|
"value_source.value": "Value:",
|
||||||
"value_source.value.hint": "Constant output value (0.0 = off, 1.0 = full brightness)",
|
"value_source.value.hint": "Constant output value (0.0 = off, 1.0 = full brightness)",
|
||||||
"value_source.waveform": "Waveform:",
|
"value_source.waveform": "Waveform:",
|
||||||
|
|||||||
@@ -120,6 +120,20 @@
|
|||||||
"device.scan.selected": "Устройство выбрано",
|
"device.scan.selected": "Устройство выбрано",
|
||||||
"device.type": "Тип устройства:",
|
"device.type": "Тип устройства:",
|
||||||
"device.type.hint": "Выберите тип LED контроллера",
|
"device.type.hint": "Выберите тип LED контроллера",
|
||||||
|
"device.type.wled": "WLED",
|
||||||
|
"device.type.wled.desc": "WiFi LED контроллер по HTTP/UDP",
|
||||||
|
"device.type.adalight": "Adalight",
|
||||||
|
"device.type.adalight.desc": "Серийный протокол для Arduino",
|
||||||
|
"device.type.ambiled": "AmbiLED",
|
||||||
|
"device.type.ambiled.desc": "Серийный протокол AmbiLED",
|
||||||
|
"device.type.mqtt": "MQTT",
|
||||||
|
"device.type.mqtt.desc": "Отправка LED данных через MQTT брокер",
|
||||||
|
"device.type.ws": "WebSocket",
|
||||||
|
"device.type.ws.desc": "Стриминг LED данных через WebSocket",
|
||||||
|
"device.type.openrgb": "OpenRGB",
|
||||||
|
"device.type.openrgb.desc": "Управление RGB через OpenRGB",
|
||||||
|
"device.type.mock": "Mock",
|
||||||
|
"device.type.mock.desc": "Виртуальное устройство для тестов",
|
||||||
"device.serial_port": "Серийный порт:",
|
"device.serial_port": "Серийный порт:",
|
||||||
"device.serial_port.hint": "Выберите COM порт устройства Adalight",
|
"device.serial_port.hint": "Выберите COM порт устройства Adalight",
|
||||||
"device.serial_port.none": "Серийные порты не найдены",
|
"device.serial_port.none": "Серийные порты не найдены",
|
||||||
@@ -999,10 +1013,15 @@
|
|||||||
"value_source.type": "Тип:",
|
"value_source.type": "Тип:",
|
||||||
"value_source.type.hint": "Статический выдаёт постоянное значение. Анимированный циклически меняет форму волны. Аудио реагирует на звук. Адаптивные типы автоматически подстраивают яркость по времени суток или содержимому сцены.",
|
"value_source.type.hint": "Статический выдаёт постоянное значение. Анимированный циклически меняет форму волны. Аудио реагирует на звук. Адаптивные типы автоматически подстраивают яркость по времени суток или содержимому сцены.",
|
||||||
"value_source.type.static": "Статический",
|
"value_source.type.static": "Статический",
|
||||||
|
"value_source.type.static.desc": "Постоянное выходное значение",
|
||||||
"value_source.type.animated": "Анимированный",
|
"value_source.type.animated": "Анимированный",
|
||||||
|
"value_source.type.animated.desc": "Циклическая смена по форме волны",
|
||||||
"value_source.type.audio": "Аудио",
|
"value_source.type.audio": "Аудио",
|
||||||
"value_source.type.adaptive_time": "Адаптивный (Время суток)",
|
"value_source.type.audio.desc": "Реагирует на звуковой сигнал",
|
||||||
|
"value_source.type.adaptive_time": "Адаптивный (Время)",
|
||||||
|
"value_source.type.adaptive_time.desc": "Подстройка по времени суток",
|
||||||
"value_source.type.adaptive_scene": "Адаптивный (Сцена)",
|
"value_source.type.adaptive_scene": "Адаптивный (Сцена)",
|
||||||
|
"value_source.type.adaptive_scene.desc": "Подстройка по содержимому сцены",
|
||||||
"value_source.value": "Значение:",
|
"value_source.value": "Значение:",
|
||||||
"value_source.value.hint": "Постоянное выходное значение (0.0 = выкл, 1.0 = полная яркость)",
|
"value_source.value.hint": "Постоянное выходное значение (0.0 = выкл, 1.0 = полная яркость)",
|
||||||
"value_source.waveform": "Форма волны:",
|
"value_source.waveform": "Форма волны:",
|
||||||
|
|||||||
@@ -120,6 +120,20 @@
|
|||||||
"device.scan.selected": "设备已选择",
|
"device.scan.selected": "设备已选择",
|
||||||
"device.type": "设备类型:",
|
"device.type": "设备类型:",
|
||||||
"device.type.hint": "选择 LED 控制器的类型",
|
"device.type.hint": "选择 LED 控制器的类型",
|
||||||
|
"device.type.wled": "WLED",
|
||||||
|
"device.type.wled.desc": "通过HTTP/UDP控制的WiFi LED",
|
||||||
|
"device.type.adalight": "Adalight",
|
||||||
|
"device.type.adalight.desc": "Arduino串口LED协议",
|
||||||
|
"device.type.ambiled": "AmbiLED",
|
||||||
|
"device.type.ambiled.desc": "AmbiLED串口协议",
|
||||||
|
"device.type.mqtt": "MQTT",
|
||||||
|
"device.type.mqtt.desc": "通过MQTT代理发布LED数据",
|
||||||
|
"device.type.ws": "WebSocket",
|
||||||
|
"device.type.ws.desc": "通过WebSocket流式传输LED数据",
|
||||||
|
"device.type.openrgb": "OpenRGB",
|
||||||
|
"device.type.openrgb.desc": "通过OpenRGB控制RGB外设",
|
||||||
|
"device.type.mock": "Mock",
|
||||||
|
"device.type.mock.desc": "用于测试的虚拟设备",
|
||||||
"device.serial_port": "串口:",
|
"device.serial_port": "串口:",
|
||||||
"device.serial_port.hint": "选择 Adalight 设备的 COM 端口",
|
"device.serial_port.hint": "选择 Adalight 设备的 COM 端口",
|
||||||
"device.serial_port.none": "未找到串口",
|
"device.serial_port.none": "未找到串口",
|
||||||
@@ -999,10 +1013,15 @@
|
|||||||
"value_source.type": "类型:",
|
"value_source.type": "类型:",
|
||||||
"value_source.type.hint": "静态输出固定值。动画循环波形。音频响应声音输入。自适应类型根据时间或场景内容自动调节亮度。",
|
"value_source.type.hint": "静态输出固定值。动画循环波形。音频响应声音输入。自适应类型根据时间或场景内容自动调节亮度。",
|
||||||
"value_source.type.static": "静态",
|
"value_source.type.static": "静态",
|
||||||
|
"value_source.type.static.desc": "固定输出值",
|
||||||
"value_source.type.animated": "动画",
|
"value_source.type.animated": "动画",
|
||||||
|
"value_source.type.animated.desc": "循环波形变化",
|
||||||
"value_source.type.audio": "音频",
|
"value_source.type.audio": "音频",
|
||||||
|
"value_source.type.audio.desc": "响应声音输入",
|
||||||
"value_source.type.adaptive_time": "自适应(时间)",
|
"value_source.type.adaptive_time": "自适应(时间)",
|
||||||
|
"value_source.type.adaptive_time.desc": "按时间自动调节",
|
||||||
"value_source.type.adaptive_scene": "自适应(场景)",
|
"value_source.type.adaptive_scene": "自适应(场景)",
|
||||||
|
"value_source.type.adaptive_scene.desc": "按场景内容调节",
|
||||||
"value_source.value": "值:",
|
"value_source.value": "值:",
|
||||||
"value_source.value.hint": "固定输出值(0.0 = 关闭,1.0 = 最大亮度)",
|
"value_source.value.hint": "固定输出值(0.0 = 关闭,1.0 = 最大亮度)",
|
||||||
"value_source.waveform": "波形:",
|
"value_source.waveform": "波形:",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* - Navigation: network-first with offline fallback
|
* - Navigation: network-first with offline fallback
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CACHE_NAME = 'ledgrab-v15';
|
const CACHE_NAME = 'ledgrab-v16';
|
||||||
|
|
||||||
// Only pre-cache static assets (no auth required).
|
// Only pre-cache static assets (no auth required).
|
||||||
// Do NOT pre-cache '/' — it requires API key auth and would cache an error page.
|
// Do NOT pre-cache '/' — it requires API key auth and would cache an error page.
|
||||||
|
|||||||
Reference in New Issue
Block a user