Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import { SettingsPanel, DXFilterManager, PSKFilterManager } from './components';
import { SettingsPanel, DXFilterManager, PSKFilterManager, KeybindingsPanel } from './components';

import DockableLayout from './layouts/DockableLayout.jsx';
import ClassicLayout from './layouts/ClassicLayout.jsx';
Expand Down Expand Up @@ -47,6 +47,7 @@ import useLocalInstall from './hooks/app/useLocalInstall';
import useVersionCheck from './hooks/app/useVersionCheck';
import WhatsNew from './components/WhatsNew.jsx';
import { initCtyLookup } from './utils/ctyLookup.js';
import { getAllLayers } from './plugins/layerRegistry.js';
import ActivateFilterManager from './components/ActivateFilterManager.jsx';

// Load DXCC entity database on app startup (non-blocking)
Expand All @@ -61,6 +62,7 @@ const App = () => {
const [showSettings, setShowSettings] = useState(false);
const [showDXFilters, setShowDXFilters] = useState(false);
const [showPSKFilters, setShowPSKFilters] = useState(false);
const [showKeybindings, setShowKeybindings] = useState(false);
const [showPotaFilters, setShowPotaFilters] = useState(false);
const [showSotaFilters, setShowSotaFilters] = useState(false);
const [showWwffFilters, setShowWwffFilters] = useState(false);
Expand Down Expand Up @@ -98,6 +100,68 @@ const App = () => {
}
}, [configLoaded, config.callsign]);


const layerShortcuts = useMemo(() => {
const layers = getAllLayers();
const map = {};
const used = new Set();

for (const layer of layers) {
const name = (layer.name || layer.id || '').toLowerCase();
for (const char of name) {
if (/[a-z]/.test(char) && !used.has(char)) {
map[char] = layer.id;
used.add(char);
break;
}
}
}
return map;
}, []);

const keybindingsList = useMemo(() => {
return Object.entries(layerShortcuts)
.map(([key, id]) => {
const layer = getAllLayers().find(l => l.id === id);
let name = layer?.name || layer?.id || id;
if (name?.startsWith('plugins.layers.')) {
name = t(name, name);
}
return { key: key.toUpperCase(), description: `Toggle ${name}` };
})
.sort((a, b) => a.key.localeCompare(b.key));
}, [layerShortcuts, t]);

useEffect(() => {
const handleKey = (e) => {
if (
showSettings || showDXFilters || showPSKFilters || showKeybindings ||
document.activeElement?.tagName === 'INPUT' ||
document.activeElement?.tagName === 'TEXTAREA' ||
document.activeElement?.tagName === 'SELECT'
) return;

if (e.key === '?') {
setShowKeybindings(v => !v);
e.preventDefault();
return;
}

const layerId = layerShortcuts[e.key.toLowerCase()];
if (layerId && window.hamclockLayerControls) {
const isEnabled = window.hamclockLayerControls.layers?.find(l => l.id === layerId)?.enabled ?? false;
window.hamclockLayerControls.toggleLayer(layerId, !isEnabled);
e.preventDefault();
}
};

document.addEventListener('keydown', handleKey);
return () => document.removeEventListener('keydown', handleKey);
}, [
showSettings, showDXFilters, showPSKFilters, showKeybindings,
layerShortcuts // only real dependency
]);

const handleResetLayout = useCallback(() => {
resetLayout();
setLayoutResetKey((prev) => prev + 1);
Expand Down Expand Up @@ -411,6 +475,7 @@ const App = () => {
rightSidebarVisible,
getGridTemplateColumns,
scale,
keybindingsList,
};

return (
Expand Down Expand Up @@ -461,6 +526,11 @@ const App = () => {
isOpen={showPSKFilters}
onClose={() => setShowPSKFilters(false)}
/>
<KeybindingsPanel
isOpen={showKeybindings}
onClose={() => setShowKeybindings(false)}
keybindings={keybindingsList}
/>
<ActivateFilterManager
name="POTA"
filters={potaFilters}
Expand Down
10 changes: 10 additions & 0 deletions src/DockableApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
RigControlPanel,
OnAirPanel,
IDTimerPanel,
KeybindingsPanel,
DXLocalTime,
} from './components';

Expand Down Expand Up @@ -160,6 +161,9 @@ export const DockableApp = ({
handleUpdateClick,
updateInProgress,
isLocalInstall,

// Keybindings
keybindingsList,
}) => {
const layoutRef = useRef(null);
const [model, setModel] = useState(() => Model.fromJson(loadLayout()));
Expand Down Expand Up @@ -377,6 +381,7 @@ export const DockableApp = ({
'rig-control': { name: 'Rig Control', icon: '📻' },
'on-air': { name: 'On Air', icon: '🔴' },
'id-timer': { name: 'ID Timer', icon: '📢' },
keybindings: { name: 'Keyboard Shortcuts', icon: '⌨️' },
};
}, [isLocalInstall]);

Expand Down Expand Up @@ -866,6 +871,10 @@ export const DockableApp = ({
content = <IDTimerPanel callsign={config.callsign} />;
break;

case 'keybindings':
content = <KeybindingsPanel keybindings={keybindingsList} nodeId={nodeId} />;
break;

default:
content = (
<div style={{ padding: '20px', color: '#ff6b6b', textAlign: 'center' }}>
Expand Down Expand Up @@ -930,6 +939,7 @@ export const DockableApp = ({
dxLocked,
handleToggleDxLock,
panelZoom,
keybindingsList,
],
);

Expand Down
Loading