@mytec: iter9 ready for test

This commit is contained in:
2026-01-30 14:47:32 +02:00
parent 7fe5f7068c
commit b932607521
7 changed files with 629 additions and 143 deletions

View File

@@ -1,61 +1,121 @@
import { useEffect } from 'react';
import { useSitesStore } from '@/store/sites.ts';
import { useCoverageStore } from '@/store/coverage.ts';
import { useSettingsStore } from '@/store/settings.ts';
import { useToastStore } from '@/components/ui/Toast.tsx';
interface ShortcutHandlers {
onCalculate: () => void;
onCloseForm: () => void;
onShowShortcuts?: () => void;
}
export function useKeyboardShortcuts({ onCalculate, onCloseForm }: ShortcutHandlers) {
function isInputActive(): boolean {
const el = document.activeElement;
if (!el) return false;
const tag = el.tagName;
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
}
export function useKeyboardShortcuts({
onCalculate,
onCloseForm,
onShowShortcuts,
}: ShortcutHandlers) {
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Ignore if typing in input or textarea
if (
e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement ||
e.target instanceof HTMLSelectElement
) {
return;
}
const isMac = navigator.platform.toLowerCase().includes('mac');
const modKey = isMac ? e.metaKey : e.ctrlKey;
if (modKey) {
switch (e.key.toLowerCase()) {
case 'enter': // Ctrl/Cmd+Enter: Calculate coverage
e.preventDefault();
onCalculate();
break;
// === Modifier shortcuts (work even in inputs) ===
if (modKey && e.key === 'Enter') {
e.preventDefault();
onCalculate();
return;
}
case 'n': // Ctrl/Cmd+N: New site (enter placement mode)
// Escape always works
if (e.key === 'Escape') {
useSitesStore.getState().selectSite(null);
useSitesStore.getState().setPlacingMode(false);
onCloseForm();
return;
}
// === Skip remaining shortcuts if typing in an input ===
if (isInputActive()) return;
// Shift combos (no browser conflicts)
if (e.shiftKey && !modKey && !e.altKey) {
switch (e.key.toUpperCase()) {
case 'S': // Shift+S: New site (place mode)
e.preventDefault();
useSitesStore.getState().setPlacingMode(true);
useToastStore.getState().addToast('Click on map to place new site', 'info');
break;
return;
case 'C': // Shift+C: Clear coverage
e.preventDefault();
useCoverageStore.getState().clearCoverage();
useToastStore.getState().addToast('Coverage cleared', 'info');
return;
}
}
// Non-modifier shortcuts
switch (e.key) {
case 'Escape': // Escape: Cancel/close
useSitesStore.getState().selectSite(null);
useSitesStore.getState().setPlacingMode(false);
onCloseForm();
break;
case 'h': // H: Toggle heatmap
case 'H':
if (!e.ctrlKey && !e.metaKey && !e.altKey) {
// Single letter shortcuts (no modifiers)
if (!modKey && !e.altKey && !e.shiftKey) {
switch (e.key.toLowerCase()) {
case 'h': // H: Toggle heatmap
useCoverageStore.getState().toggleHeatmap();
}
break;
return;
case 'g': // G: Toggle grid
useSettingsStore.getState().setShowGrid(
!useSettingsStore.getState().showGrid
);
return;
case 't': // T: Toggle terrain
useSettingsStore.getState().setShowElevationOverlay(
!useSettingsStore.getState().showElevationOverlay
);
return;
case 'r': // R: Toggle ruler / measurement
useSettingsStore.getState().setMeasurementMode(
!useSettingsStore.getState().measurementMode
);
return;
case 'f': // F: Fit to coverage — dispatch custom event for Map.tsx to handle
window.dispatchEvent(new CustomEvent('rfcp:fit-bounds'));
return;
case 'delete': // Delete key: delete selected sites
case 'backspace':
// Only if a site is selected (not batch)
{
const selectedId = useSitesStore.getState().selectedSiteId;
if (selectedId) {
const site = useSitesStore.getState().sites.find(s => s.id === selectedId);
if (site) {
useSitesStore.getState().deleteSite(selectedId);
useToastStore.getState().addToast(`"${site.name}" deleted`, 'info');
}
}
}
return;
}
// ? key for help (not shift on some layouts)
if (e.key === '?') {
onShowShortcuts?.();
return;
}
}
// ? with shift (most layouts: shift+/)
if (e.shiftKey && !modKey && !e.altKey && e.key === '?') {
onShowShortcuts?.();
return;
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [onCalculate, onCloseForm]);
}, [onCalculate, onCloseForm, onShowShortcuts]);
}