mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-17 11:25:48 +02:00
Fixed bug where external links were not respected by hotkeys
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { X, Keyboard } from 'lucide-react';
|
||||
import { X, Keyboard, ExternalLink } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent } from './Card';
|
||||
|
||||
@@ -9,41 +9,62 @@ interface NavItem {
|
||||
labelKey: string;
|
||||
}
|
||||
|
||||
interface SidebarItem {
|
||||
type: 'nav' | 'external';
|
||||
label: string;
|
||||
labelKey?: string;
|
||||
}
|
||||
|
||||
interface KeyboardShortcutsModalProps {
|
||||
onClose: () => void;
|
||||
navItems?: NavItem[];
|
||||
sidebarItems?: SidebarItem[];
|
||||
}
|
||||
|
||||
function getShortcuts(navItems: NavItem[] | undefined, t: (key: string) => string) {
|
||||
const navShortcuts = navItems
|
||||
function getShortcuts(
|
||||
sidebarItems: SidebarItem[] | undefined,
|
||||
navItems: NavItem[] | undefined,
|
||||
t: (key: string) => string
|
||||
) {
|
||||
// Use sidebarItems if provided (new format), otherwise fall back to navItems
|
||||
const navShortcuts = sidebarItems
|
||||
? sidebarItems.slice(0, 9).map((item, index) => ({
|
||||
keys: [String(index + 1)],
|
||||
description: item.type === 'external'
|
||||
? `Open ${item.label}`
|
||||
: `Go to ${item.labelKey ? t(item.labelKey) : item.label}`,
|
||||
isExternal: item.type === 'external',
|
||||
}))
|
||||
: navItems
|
||||
? navItems.map((item, index) => ({
|
||||
keys: [String(index + 1)],
|
||||
description: `Go to ${t(item.labelKey)}`,
|
||||
isExternal: false,
|
||||
}))
|
||||
: [
|
||||
{ keys: ['1'], description: 'Go to Printers' },
|
||||
{ keys: ['2'], description: 'Go to Archives' },
|
||||
{ keys: ['3'], description: 'Go to Queue' },
|
||||
{ keys: ['4'], description: 'Go to Statistics' },
|
||||
{ keys: ['5'], description: 'Go to Cloud Profiles' },
|
||||
{ keys: ['6'], description: 'Go to Settings' },
|
||||
{ keys: ['1'], description: 'Go to Printers', isExternal: false },
|
||||
{ keys: ['2'], description: 'Go to Archives', isExternal: false },
|
||||
{ keys: ['3'], description: 'Go to Queue', isExternal: false },
|
||||
{ keys: ['4'], description: 'Go to Statistics', isExternal: false },
|
||||
{ keys: ['5'], description: 'Go to Cloud Profiles', isExternal: false },
|
||||
{ keys: ['6'], description: 'Go to Settings', isExternal: false },
|
||||
];
|
||||
|
||||
return [
|
||||
{ category: 'Navigation', items: navShortcuts },
|
||||
{ category: 'Archives', items: [
|
||||
{ keys: ['/'], description: 'Focus search' },
|
||||
{ keys: ['U'], description: 'Open upload modal' },
|
||||
{ keys: ['Esc'], description: 'Clear selection / blur input' },
|
||||
{ keys: ['Right-click'], description: 'Context menu on cards' },
|
||||
{ keys: ['/'], description: 'Focus search', isExternal: false },
|
||||
{ keys: ['U'], description: 'Open upload modal', isExternal: false },
|
||||
{ keys: ['Esc'], description: 'Clear selection / blur input', isExternal: false },
|
||||
{ keys: ['Right-click'], description: 'Context menu on cards', isExternal: false },
|
||||
]},
|
||||
{ category: 'K-Profiles', items: [
|
||||
{ keys: ['R'], description: 'Refresh profiles' },
|
||||
{ keys: ['N'], description: 'New profile' },
|
||||
{ keys: ['Esc'], description: 'Exit selection mode' },
|
||||
{ keys: ['R'], description: 'Refresh profiles', isExternal: false },
|
||||
{ keys: ['N'], description: 'New profile', isExternal: false },
|
||||
{ keys: ['Esc'], description: 'Exit selection mode', isExternal: false },
|
||||
]},
|
||||
{ category: 'General', items: [
|
||||
{ keys: ['?'], description: 'Show this help' },
|
||||
{ keys: ['?'], description: 'Show this help', isExternal: false },
|
||||
]},
|
||||
];
|
||||
}
|
||||
@@ -56,9 +77,9 @@ function KeyBadge({ children }: { children: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function KeyboardShortcutsModal({ onClose, navItems }: KeyboardShortcutsModalProps) {
|
||||
export function KeyboardShortcutsModal({ onClose, navItems, sidebarItems }: KeyboardShortcutsModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const shortcuts = getShortcuts(navItems, t);
|
||||
const shortcuts = getShortcuts(sidebarItems, navItems, t);
|
||||
|
||||
// Close on Escape key
|
||||
useEffect(() => {
|
||||
@@ -95,7 +116,12 @@ export function KeyboardShortcutsModal({ onClose, navItems }: KeyboardShortcutsM
|
||||
<div className="space-y-2">
|
||||
{section.items.map((shortcut) => (
|
||||
<div key={shortcut.description} className="flex items-center justify-between">
|
||||
<span className="text-white text-sm">{shortcut.description}</span>
|
||||
<span className="text-white text-sm flex items-center gap-1.5">
|
||||
{shortcut.description}
|
||||
{shortcut.isExternal && (
|
||||
<ExternalLink className="w-3 h-3 text-bambu-gray" />
|
||||
)}
|
||||
</span>
|
||||
<div className="flex gap-1">
|
||||
{shortcut.keys.map((key) => (
|
||||
<KeyBadge key={key}>{key}</KeyBadge>
|
||||
|
||||
@@ -237,17 +237,25 @@ export function Layout() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Number keys for navigation (1-9) - follows sidebar order for internal nav items only
|
||||
// Number keys for navigation (1-9) - follows sidebar order including external links
|
||||
if (!e.metaKey && !e.ctrlKey && !e.altKey) {
|
||||
const keyNum = parseInt(e.key);
|
||||
const internalItems = orderedSidebarIds.filter(id => !isExternalLinkId(id));
|
||||
if (keyNum >= 1 && keyNum <= internalItems.length) {
|
||||
const navItem = navItemsMap.get(internalItems[keyNum - 1]);
|
||||
if (navItem) {
|
||||
e.preventDefault();
|
||||
navigate(navItem.to);
|
||||
return;
|
||||
if (keyNum >= 1 && keyNum <= orderedSidebarIds.length && keyNum <= 9) {
|
||||
const id = orderedSidebarIds[keyNum - 1];
|
||||
e.preventDefault();
|
||||
|
||||
if (isExternalLinkId(id)) {
|
||||
// External link - navigate to iframe page
|
||||
const linkId = id.replace('ext-', '');
|
||||
navigate(`/external/${linkId}`);
|
||||
} else {
|
||||
// Internal nav item
|
||||
const navItem = navItemsMap.get(id);
|
||||
if (navItem) {
|
||||
navigate(navItem.to);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
@@ -550,10 +558,15 @@ export function Layout() {
|
||||
{showShortcuts && (
|
||||
<KeyboardShortcutsModal
|
||||
onClose={() => setShowShortcuts(false)}
|
||||
navItems={orderedSidebarIds
|
||||
.filter(id => !isExternalLinkId(id))
|
||||
.map(id => navItemsMap.get(id)!)
|
||||
.filter(Boolean)}
|
||||
sidebarItems={orderedSidebarIds.map(id => {
|
||||
if (isExternalLinkId(id)) {
|
||||
const extLink = extLinksMap.get(id);
|
||||
return extLink ? { type: 'external' as const, label: extLink.name } : null;
|
||||
} else {
|
||||
const navItem = navItemsMap.get(id);
|
||||
return navItem ? { type: 'nav' as const, label: navItem.labelKey, labelKey: navItem.labelKey } : null;
|
||||
}
|
||||
}).filter(Boolean) as { type: 'nav' | 'external'; label: string; labelKey?: string }[]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -14,10 +14,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
const [theme, setThemeState] = useState<Theme>(() => {
|
||||
const stored = localStorage.getItem('theme') as Theme | null;
|
||||
if (stored) return stored;
|
||||
// Check system preference
|
||||
if (window.matchMedia('(prefers-color-scheme: light)').matches) {
|
||||
return 'light';
|
||||
}
|
||||
// Default to dark theme
|
||||
return 'dark';
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -7,7 +7,7 @@
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png" />
|
||||
<script type="module" crossorigin src="/assets/index-2RQZZHMw.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DAltyj1x.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BYZOEJWU.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user