Add type picker for entity creation, icon grid filter, and serial port placeholder
- Replace inline type selectors with pre-modal type picker grid for devices, color strip sources, and value sources - Add filterable search to icon grid when items > 9 (no auto-focus on touch) - Show disabled (grayed-out) filtered items instead of hiding them - Responsive grid columns (2-5 cols based on viewport width) - Add "Select a port..." placeholder to serial port dropdown - Update en/ru/zh locales with new keys Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@ import * as P from '../core/icon-paths.js';
|
||||
import { wrapCard } from '../core/card-colors.js';
|
||||
import { TagInput, renderTagChips } from '../core/tag-input.js';
|
||||
import { attachProcessPicker } from '../core/process-picker.js';
|
||||
import { IconSelect } from '../core/icon-select.js';
|
||||
import { IconSelect, showTypePicker } from '../core/icon-select.js';
|
||||
import { EntitySelect } from '../core/entity-palette.js';
|
||||
import {
|
||||
rgbArrayToHex, hexToRgbArray,
|
||||
@@ -1315,7 +1315,16 @@ function _autoGenerateCSSName() {
|
||||
|
||||
/* ── Editor open/close ────────────────────────────────────────── */
|
||||
|
||||
export async function showCSSEditor(cssId = null, cloneData = null) {
|
||||
export async function showCSSEditor(cssId = null, cloneData = null, presetType = null) {
|
||||
// When creating new: show type picker first, then re-enter with presetType
|
||||
if (!cssId && !cloneData && !presetType) {
|
||||
showTypePicker({
|
||||
title: t('color_strip.select_type'),
|
||||
items: _buildCSSTypeItems(),
|
||||
onPick: (type) => showCSSEditor(null, null, type),
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const sources = await streamsCache.fetch();
|
||||
|
||||
@@ -1449,8 +1458,8 @@ export async function showCSSEditor(cssId = null, cloneData = null) {
|
||||
// Initialize icon-grid type selector (idempotent)
|
||||
_ensureCSSTypeIconSelect();
|
||||
|
||||
// Hide type selector in edit mode (type is immutable)
|
||||
document.getElementById('css-editor-type-group').style.display = cssId ? 'none' : '';
|
||||
// Hide type selector — type is chosen before the modal opens (or immutable in edit)
|
||||
document.getElementById('css-editor-type-group').style.display = 'none';
|
||||
|
||||
if (cssId) {
|
||||
const cssSources = await colorStripSourcesCache.fetch();
|
||||
@@ -1481,7 +1490,7 @@ export async function showCSSEditor(cssId = null, cloneData = null) {
|
||||
} else {
|
||||
document.getElementById('css-editor-id').value = '';
|
||||
document.getElementById('css-editor-name').value = '';
|
||||
document.getElementById('css-editor-type').value = 'picture';
|
||||
document.getElementById('css-editor-type').value = presetType || 'picture';
|
||||
onCSSTypeChange();
|
||||
document.getElementById('css-editor-interpolation').value = 'average';
|
||||
if (_interpolationIconSelect) _interpolationIconSelect.setValue('average');
|
||||
@@ -1527,7 +1536,8 @@ export async function showCSSEditor(cssId = null, cloneData = null) {
|
||||
document.getElementById('css-editor-candlelight-num-candles').value = 3;
|
||||
document.getElementById('css-editor-candlelight-speed').value = 1.0;
|
||||
document.getElementById('css-editor-candlelight-speed-val').textContent = '1.0';
|
||||
document.getElementById('css-editor-title').innerHTML = `${ICON_FILM} ${t('color_strip.add')}`;
|
||||
const typeIcon = getColorStripIcon(presetType || 'picture');
|
||||
document.getElementById('css-editor-title').innerHTML = `${typeIcon} ${t('color_strip.add')}: ${t(`color_strip.type.${presetType || 'picture'}`)}`;
|
||||
document.getElementById('css-editor-gradient-preset').value = '';
|
||||
gradientInit([
|
||||
{ position: 0.0, color: [255, 0, 0] },
|
||||
|
||||
@@ -13,7 +13,7 @@ import { showToast, desktopFocus } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
import { _computeMaxFps, _renderFpsHint } from './devices.js';
|
||||
import { getDeviceTypeIcon, ICON_RADIO, ICON_GLOBE, ICON_CPU, ICON_KEYBOARD, ICON_MOUSE, ICON_HEADPHONES, ICON_PLUG, ICON_TARGET_ICON, ICON_ACTIVITY } from '../core/icons.js';
|
||||
import { IconSelect } from '../core/icon-select.js';
|
||||
import { IconSelect, showTypePicker } from '../core/icon-select.js';
|
||||
|
||||
class AddDeviceModal extends Modal {
|
||||
constructor() { super('add-device-modal'); }
|
||||
@@ -262,6 +262,7 @@ export function onDeviceTypeChanged() {
|
||||
opt.value = '';
|
||||
opt.textContent = t('device.serial_port.hint') || 'Click to discover ports...';
|
||||
opt.disabled = true;
|
||||
opt.selected = true;
|
||||
serialSelect.appendChild(opt);
|
||||
}
|
||||
updateBaudFpsHint();
|
||||
@@ -328,6 +329,7 @@ export function onDeviceTypeChanged() {
|
||||
opt.value = '';
|
||||
opt.textContent = t('device.serial_port.hint') || 'Click to discover ports...';
|
||||
opt.disabled = true;
|
||||
opt.selected = true;
|
||||
serialSelect.appendChild(opt);
|
||||
}
|
||||
} else if (isHueDevice(deviceType)) {
|
||||
@@ -525,6 +527,14 @@ function _populateSerialPortDropdown(devices) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Default placeholder
|
||||
const placeholder = document.createElement('option');
|
||||
placeholder.value = '';
|
||||
placeholder.textContent = t('device.serial_port.select') || 'Select a port...';
|
||||
placeholder.disabled = true;
|
||||
placeholder.selected = true;
|
||||
select.appendChild(placeholder);
|
||||
|
||||
devices.forEach(device => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = device.url;
|
||||
@@ -544,7 +554,17 @@ export function onSerialPortFocus() {
|
||||
}
|
||||
}
|
||||
|
||||
export function showAddDevice() {
|
||||
export function showAddDevice(presetType = null) {
|
||||
// When no type specified: show type picker first
|
||||
if (!presetType) {
|
||||
showTypePicker({
|
||||
title: t('device.select_type'),
|
||||
items: _buildDeviceTypeItems(),
|
||||
onPick: (type) => showAddDevice(type),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const form = document.getElementById('add-device-form');
|
||||
const error = document.getElementById('add-device-error');
|
||||
form.reset();
|
||||
@@ -563,6 +583,14 @@ export function showAddDevice() {
|
||||
const scanBtn = document.getElementById('scan-network-btn');
|
||||
if (scanBtn) scanBtn.disabled = false;
|
||||
_ensureDeviceTypeIconSelect();
|
||||
|
||||
// Pre-select type and hide the type selector (already chosen)
|
||||
document.getElementById('device-type').value = presetType;
|
||||
document.getElementById('device-type-group').style.display = 'none';
|
||||
const typeIcon = getDeviceTypeIcon(presetType);
|
||||
const typeName = t(`device.type.${presetType}`);
|
||||
document.getElementById('add-device-modal-title').innerHTML = `${typeIcon} ${t('devices.add')}: ${typeName}`;
|
||||
|
||||
addDeviceModal.open();
|
||||
onDeviceTypeChanged();
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
} from '../core/icons.js';
|
||||
import { wrapCard } from '../core/card-colors.js';
|
||||
import { TagInput, renderTagChips } from '../core/tag-input.js';
|
||||
import { IconSelect } from '../core/icon-select.js';
|
||||
import { IconSelect, showTypePicker } from '../core/icon-select.js';
|
||||
import { EntitySelect } from '../core/entity-palette.js';
|
||||
import { loadPictureSources } from './streams.js';
|
||||
|
||||
@@ -165,13 +165,27 @@ function _ensureVSTypeIconSelect() {
|
||||
|
||||
// ── Modal ─────────────────────────────────────────────────────
|
||||
|
||||
export async function showValueSourceModal(editData) {
|
||||
export async function showValueSourceModal(editData, presetType = null) {
|
||||
// When creating new: show type picker first, then re-enter with presetType
|
||||
if (!editData && !presetType) {
|
||||
showTypePicker({
|
||||
title: t('value_source.select_type'),
|
||||
items: _buildVSTypeItems(),
|
||||
onPick: (type) => showValueSourceModal(null, type),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const hasId = editData?.id;
|
||||
const isEdit = !!hasId;
|
||||
const titleKey = isEdit ? 'value_source.edit' : 'value_source.add';
|
||||
|
||||
const titleIcon = editData ? getValueSourceIcon(editData.source_type) : getValueSourceIcon('static');
|
||||
document.getElementById('value-source-modal-title').innerHTML = `${titleIcon} ${t(titleKey)}`;
|
||||
const sourceType = editData?.source_type || presetType || 'static';
|
||||
const titleIcon = getValueSourceIcon(sourceType);
|
||||
const titleKey = isEdit ? 'value_source.edit' : 'value_source.add';
|
||||
const typeName = t(`value_source.type.${sourceType}`);
|
||||
document.getElementById('value-source-modal-title').innerHTML = isEdit
|
||||
? `${titleIcon} ${t(titleKey)}`
|
||||
: `${titleIcon} ${t(titleKey)}: ${typeName}`;
|
||||
document.getElementById('value-source-id').value = isEdit ? editData.id : '';
|
||||
document.getElementById('value-source-error').style.display = 'none';
|
||||
|
||||
@@ -180,7 +194,8 @@ export async function showValueSourceModal(editData) {
|
||||
|
||||
_ensureVSTypeIconSelect();
|
||||
const typeSelect = document.getElementById('value-source-type');
|
||||
document.getElementById('value-source-type-group').style.display = isEdit ? 'none' : '';
|
||||
// Type is chosen before the modal opens — always hide selector
|
||||
document.getElementById('value-source-type-group').style.display = 'none';
|
||||
|
||||
if (editData) {
|
||||
document.getElementById('value-source-name').value = editData.name || '';
|
||||
@@ -227,7 +242,7 @@ export async function showValueSourceModal(editData) {
|
||||
} else {
|
||||
document.getElementById('value-source-name').value = '';
|
||||
document.getElementById('value-source-description').value = '';
|
||||
typeSelect.value = 'static';
|
||||
typeSelect.value = presetType || 'static';
|
||||
onValueSourceTypeChange();
|
||||
_setSlider('value-source-value', 1.0);
|
||||
_setSlider('value-source-speed', 10);
|
||||
|
||||
Reference in New Issue
Block a user