Separate tree nodes into independent panels, remove graph local search, UI improvements
- Split Sources tab: raw/raw_templates, processed/proc_templates each get own panel - Split Targets tab: led-devices, led-targets, kc-targets, kc-patterns each get own panel - Remove graph local search — search button and / key open global command palette - Add graphNavigateToNode for command palette → graph node navigation - Add tree group expand/collapse animation (max-height + opacity transition) - Make tree group headers visually distinct (smaller, uppercase, left border on children) - Make CardSection collapse opt-in via collapsible flag (disabled by default) - Move filter textbox next to section title (remove margin-left: auto) - Fix notification bell button vertical centering in test preview - Fix clipboard copy on non-HTTPS with execCommand fallback - Add overlay toggle button on picture-based CSS cards - Add CSPT to graph add-entity picker and global search - Update all cross-link navigation paths for new panel keys - Add i18n keys for new tree groups and search groups (en/ru/zh) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -535,18 +535,10 @@ export async function saveTargetEditor() {
|
||||
let _treeTriggered = false;
|
||||
|
||||
const _targetsTree = new TreeNav('targets-tree-nav', {
|
||||
onSelect: (key, leaf) => {
|
||||
const subTab = leaf?.subTab || key;
|
||||
onSelect: (key) => {
|
||||
_treeTriggered = true;
|
||||
switchTargetSubTab(subTab);
|
||||
switchTargetSubTab(key);
|
||||
_treeTriggered = false;
|
||||
// Scroll to specific section
|
||||
if (leaf?.sectionKey) {
|
||||
requestAnimationFrame(() => {
|
||||
const section = document.querySelector(`[data-card-section="${leaf.sectionKey}"]`);
|
||||
if (section) section.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -558,25 +550,25 @@ export function switchTargetSubTab(tabKey) {
|
||||
updateSubTabHash('targets', tabKey);
|
||||
// Update tree active state (unless the tree triggered this switch)
|
||||
if (!_treeTriggered) {
|
||||
const leafKey = _targetsTree.getLeafForSubTab(tabKey);
|
||||
if (leafKey) _targetsTree.setActive(leafKey);
|
||||
_targetsTree.setActive(tabKey);
|
||||
}
|
||||
}
|
||||
|
||||
const _targetSectionMap = {
|
||||
'led-devices': [csDevices],
|
||||
'led-targets': [csLedTargets],
|
||||
'kc-targets': [csKCTargets],
|
||||
'kc-patterns': [csPatternTemplates],
|
||||
};
|
||||
|
||||
export function expandAllTargetSections() {
|
||||
const activeSubTab = localStorage.getItem('activeTargetSubTab') || 'led';
|
||||
const sections = activeSubTab === 'key_colors'
|
||||
? [csKCTargets, csPatternTemplates]
|
||||
: [csDevices, csLedTargets];
|
||||
CardSection.expandAll(sections);
|
||||
const activeSubTab = localStorage.getItem('activeTargetSubTab') || 'led-devices';
|
||||
CardSection.expandAll(_targetSectionMap[activeSubTab] || []);
|
||||
}
|
||||
|
||||
export function collapseAllTargetSections() {
|
||||
const activeSubTab = localStorage.getItem('activeTargetSubTab') || 'led';
|
||||
const sections = activeSubTab === 'key_colors'
|
||||
? [csKCTargets, csPatternTemplates]
|
||||
: [csDevices, csLedTargets];
|
||||
CardSection.collapseAll(sections);
|
||||
const activeSubTab = localStorage.getItem('activeTargetSubTab') || 'led-devices';
|
||||
CardSection.collapseAll(_targetSectionMap[activeSubTab] || []);
|
||||
}
|
||||
|
||||
let _loadTargetsLock = false;
|
||||
@@ -666,20 +658,22 @@ export async function loadTargetsTab() {
|
||||
{
|
||||
key: 'led_group', icon: getTargetTypeIcon('led'), titleKey: 'targets.subtab.led',
|
||||
children: [
|
||||
{ key: 'led-devices', titleKey: 'targets.section.devices', icon: getDeviceTypeIcon('wled'), count: ledDevices.length, subTab: 'led', sectionKey: 'led-devices' },
|
||||
{ key: 'led-targets', titleKey: 'targets.section.targets', icon: getTargetTypeIcon('led'), count: ledTargets.length, subTab: 'led', sectionKey: 'led-targets' },
|
||||
{ key: 'led-devices', titleKey: 'targets.section.devices', icon: getDeviceTypeIcon('wled'), count: ledDevices.length },
|
||||
{ key: 'led-targets', titleKey: 'targets.section.targets', icon: getTargetTypeIcon('led'), count: ledTargets.length },
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'kc_group', icon: getTargetTypeIcon('key_colors'), titleKey: 'targets.subtab.key_colors',
|
||||
children: [
|
||||
{ key: 'kc-targets', titleKey: 'targets.section.key_colors', icon: getTargetTypeIcon('key_colors'), count: kcTargets.length, subTab: 'key_colors', sectionKey: 'kc-targets' },
|
||||
{ key: 'kc-patterns', titleKey: 'targets.section.pattern_templates', icon: ICON_TEMPLATE, count: patternTemplates.length, subTab: 'key_colors', sectionKey: 'kc-patterns' },
|
||||
{ key: 'kc-targets', titleKey: 'targets.section.key_colors', icon: getTargetTypeIcon('key_colors'), count: kcTargets.length },
|
||||
{ key: 'kc-patterns', titleKey: 'targets.section.pattern_templates', icon: ICON_TEMPLATE, count: patternTemplates.length },
|
||||
]
|
||||
}
|
||||
];
|
||||
// Determine which tree leaf is active
|
||||
const activeLeaf = activeSubTab === 'key_colors' ? 'kc-targets' : 'led-devices';
|
||||
// Determine which tree leaf is active — migrate old values
|
||||
const validLeaves = ['led-devices', 'led-targets', 'kc-targets', 'kc-patterns'];
|
||||
const activeLeaf = validLeaves.includes(activeSubTab) ? activeSubTab
|
||||
: activeSubTab === 'key_colors' ? 'kc-targets' : 'led-devices';
|
||||
|
||||
// Use window.createPatternTemplateCard to avoid circular import
|
||||
const createPatternTemplateCard = window.createPatternTemplateCard || (() => '');
|
||||
@@ -718,17 +712,13 @@ export async function loadTargetsTab() {
|
||||
}
|
||||
} else {
|
||||
// ── First render: build full HTML ──
|
||||
const ledPanel = `
|
||||
<div class="target-sub-tab-panel stream-tab-panel${activeSubTab === 'led' ? ' active' : ''}" id="target-sub-tab-led">
|
||||
${csDevices.render(deviceItems)}
|
||||
${csLedTargets.render(ledTargetItems)}
|
||||
</div>`;
|
||||
const kcPanel = `
|
||||
<div class="target-sub-tab-panel stream-tab-panel${activeSubTab === 'key_colors' ? ' active' : ''}" id="target-sub-tab-key_colors">
|
||||
${csKCTargets.render(kcTargetItems)}
|
||||
${csPatternTemplates.render(patternItems)}
|
||||
</div>`;
|
||||
container.innerHTML = ledPanel + kcPanel;
|
||||
const panels = [
|
||||
{ key: 'led-devices', html: csDevices.render(deviceItems) },
|
||||
{ key: 'led-targets', html: csLedTargets.render(ledTargetItems) },
|
||||
{ key: 'kc-targets', html: csKCTargets.render(kcTargetItems) },
|
||||
{ key: 'kc-patterns', html: csPatternTemplates.render(patternItems) },
|
||||
].map(p => `<div class="target-sub-tab-panel stream-tab-panel${p.key === activeLeaf ? ' active' : ''}" id="target-sub-tab-${p.key}">${p.html}</div>`).join('');
|
||||
container.innerHTML = panels;
|
||||
CardSection.bindAll([csDevices, csLedTargets, csKCTargets, csPatternTemplates]);
|
||||
|
||||
// Render tree sidebar with expand/collapse buttons
|
||||
@@ -1001,7 +991,7 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSo
|
||||
</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 stream-card-link" title="${t('targets.device')}" onclick="event.stopPropagation(); navigateToCard('targets','led-devices','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>
|
||||
<span class="stream-card-prop" title="${t('targets.protocol')}">${_protocolBadge(device, target)}</span>
|
||||
<span class="stream-card-prop${cssId ? ' stream-card-link' : ''}" title="${t('targets.color_strip_source')}"${cssId ? ` onclick="event.stopPropagation(); navigateToCard('streams','color_strip','color-strips','data-css-id','${cssId}')"` : ''}>${ICON_FILM} ${cssSummary}</span>
|
||||
|
||||
Reference in New Issue
Block a user