Add WebUI navigation improvements: keyboard shortcuts, hash routing, command palette, cross-entity links

- Keyboard shortcuts: Ctrl+1-4 for tab switching
- URL hash routing: #tab/subtab format with browser back/forward support
- Tab count badges: running targets and active profiles counts
- Cross-entity quick links: clickable references navigate to related cards
- Command palette (Ctrl+K): global search across all entities with keyboard navigation
- Expand/collapse all sections: buttons in sub-tab bars
- Sticky section headers: headers pin while scrolling long card grids
- Improved section filter: better styling with reset button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 02:40:24 +03:00
parent a82eec7a06
commit f67936c977
16 changed files with 917 additions and 34 deletions

View File

@@ -54,6 +54,7 @@ import {
addFilterFromSelect, toggleFilterExpand, removeFilter, moveFilter, updateFilterOption,
renderModalFilterList, updateCaptureDuration,
cloneStream, cloneCaptureTemplate, clonePPTemplate,
expandAllStreamSections, collapseAllStreamSections,
} from './features/streams.js';
import {
createKCTargetCard, testKCTarget, toggleKCTestAutoRefresh,
@@ -88,6 +89,7 @@ import {
startTargetProcessing, stopTargetProcessing,
startTargetOverlay, stopTargetOverlay, deleteTarget,
cloneTarget, toggleLedPreview,
expandAllTargetSections, collapseAllTargetSections,
} from './features/targets.js';
// Layer 5: color-strip sources
@@ -123,8 +125,10 @@ import {
showCSSCalibration, toggleCalibrationOverlay,
} from './features/calibration.js';
// Layer 6: tabs
import { switchTab, initTabs, startAutoRefresh } from './features/tabs.js';
// Layer 6: tabs, navigation, command palette
import { switchTab, initTabs, startAutoRefresh, handlePopState } from './features/tabs.js';
import { navigateToCard } from './core/navigation.js';
import { openCommandPalette, closeCommandPalette, initCommandPalette } from './core/command-palette.js';
// ─── Register all HTML onclick / onchange / onfocus globals ───
@@ -191,6 +195,7 @@ Object.assign(window, {
// streams / capture templates / PP templates
loadPictureSources,
switchStreamTab,
expandAllStreamSections, collapseAllStreamSections,
showAddTemplateModal,
editTemplate,
closeTemplateModal,
@@ -285,6 +290,7 @@ Object.assign(window, {
loadTargetsTab,
loadTargets,
switchTargetSubTab,
expandAllTargetSections, collapseAllTargetSections,
showTargetEditor,
closeTargetEditorModal,
forceCloseTargetEditorModal,
@@ -348,14 +354,38 @@ Object.assign(window, {
showCSSCalibration,
toggleCalibrationOverlay,
// tabs
// tabs / navigation / command palette
switchTab,
startAutoRefresh,
navigateToCard,
openCommandPalette,
closeCommandPalette,
});
// ─── Global Escape key handler ───
// ─── Global keyboard shortcuts ───
document.addEventListener('keydown', (e) => {
const tag = document.activeElement?.tagName;
const inInput = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
// Command palette: Ctrl+K / Cmd+K (works even in inputs)
if ((e.ctrlKey || e.metaKey) && e.key === 'k' && !e.altKey && !e.shiftKey) {
e.preventDefault();
openCommandPalette();
return;
}
// Tab shortcuts: Ctrl+1..4 (skip when typing in inputs)
if (!inInput && e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) {
const tabMap = { '1': 'dashboard', '2': 'profiles', '3': 'targets', '4': 'streams' };
const tab = tabMap[e.key];
if (tab) {
e.preventDefault();
switchTab(tab);
return;
}
}
if (e.key === 'Escape') {
// Close in order: overlay lightboxes first, then modals via stack
if (document.getElementById('display-picker-lightbox').classList.contains('active')) {
@@ -368,6 +398,10 @@ document.addEventListener('keydown', (e) => {
}
});
// ─── Browser back/forward via hash routing ───
window.addEventListener('popstate', handlePopState);
// ─── Cleanup on page unload ───
window.addEventListener('beforeunload', () => {
@@ -393,6 +427,9 @@ document.addEventListener('DOMContentLoaded', async () => {
// Show content now that translations are loaded and tabs are set
document.body.style.visibility = 'visible';
// Initialize command palette
initCommandPalette();
// Setup form handler
document.getElementById('add-device-form').addEventListener('submit', handleAddDevice);