feat: implement platform-aware keyboard shortcuts and add IPC handler for platform detection

This commit is contained in:
Alessandro Spisso
2025-12-04 23:53:25 +01:00
parent 391938049b
commit f34bd19183
9 changed files with 108 additions and 22 deletions
@@ -1,7 +1,28 @@
import { HelpCircle } from "lucide-react";
import { useState, useEffect } from "react";
import { formatShortcut } from "@/utils/platformUtils";
export function KeyboardShortcutsHelp() {
const [shortcuts, setShortcuts] = useState({
delete: 'Ctrl + D',
pan: 'Shift + Ctrl + Scroll',
zoom: 'Ctrl + Scroll'
});
useEffect(() => {
Promise.all([
formatShortcut(['mod', 'D']),
formatShortcut(['shift', 'mod', 'Scroll']),
formatShortcut(['mod', 'Scroll'])
]).then(([deleteKey, panKey, zoomKey]) => {
setShortcuts({
delete: deleteKey,
pan: panKey,
zoom: zoomKey
});
});
}, []);
return (
<div className="relative group">
<HelpCircle className="w-4 h-4 text-slate-500 hover:text-[#34B27B] transition-colors cursor-help" />
@@ -22,15 +43,15 @@ export function KeyboardShortcutsHelp() {
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Delete Selected</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{formatShortcut(['mod', 'D'])}</kbd>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{shortcuts.delete}</kbd>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Pan Timeline</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{formatShortcut(['shift', 'mod', 'Scroll'])}</kbd>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{shortcuts.pan}</kbd>
</div>
<div className="flex items-center justify-between">
<span className="text-slate-400">Zoom Timeline</span>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{formatShortcut(['mod', 'Scroll'])}</kbd>
<kbd className="px-1 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-mono">{shortcuts.zoom}</kbd>
</div>
</div>
</div>
@@ -434,6 +434,18 @@ export default function TimelineEditor({
const [range, setRange] = useState<Range>(() => createInitialRange(totalMs));
const [keyframes, setKeyframes] = useState<{ id: string; time: number }[]>([]);
const [selectedKeyframeId, setSelectedKeyframeId] = useState<string | null>(null);
const [shortcuts, setShortcuts] = useState({
pan: 'Shift + Ctrl + Scroll',
zoom: 'Ctrl + Scroll'
});
useEffect(() => {
formatShortcut(['shift', 'mod', 'Scroll']).then(pan => {
formatShortcut(['mod', 'Scroll']).then(zoom => {
setShortcuts({ pan, zoom });
});
});
}, []);
// Add keyframe at current playhead position
const addKeyframe = useCallback(() => {
@@ -724,11 +736,11 @@ export default function TimelineEditor({
<div className="flex-1" />
<div className="flex items-center gap-4 text-[10px] text-slate-500 font-medium">
<span className="flex items-center gap-1.5">
<kbd className="px-1.5 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-sans"> + + Scroll</kbd>
<kbd className="px-1.5 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-sans">{shortcuts.pan}</kbd>
<span>Pan</span>
</span>
<span className="flex items-center gap-1.5">
<kbd className="px-1.5 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-sans"> + Scroll</kbd>
<kbd className="px-1.5 py-0.5 bg-white/5 border border-white/10 rounded text-[#34B27B] font-sans">{shortcuts.zoom}</kbd>
<span>Zoom</span>
</span>
</div>
+35 -10
View File
@@ -1,34 +1,59 @@
let cachedPlatform: string | null = null;
/**
* Gets the current platform from Electron
*/
const getPlatform = async (): Promise<string> => {
if (cachedPlatform) return cachedPlatform;
try {
const platform = await window.electronAPI.getPlatform();
cachedPlatform = platform;
return platform;
} catch (error) {
console.warn('Failed to get platform from Electron, falling back to navigator:', error);
// Fallback for development/testing
let fallbackPlatform = 'win32';
if (typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/.test(navigator.platform)) {
fallbackPlatform = 'darwin';
}
cachedPlatform = fallbackPlatform;
return fallbackPlatform;
}
};
/**
* Detects if the current platform is macOS
*/
export const isMac = (): boolean => {
if (typeof navigator === 'undefined') return false;
return /Mac|iPhone|iPad|iPod/.test(navigator.platform);
export const isMac = async (): Promise<boolean> => {
const platform = await getPlatform();
return platform === 'darwin';
};
/**
* Gets the modifier key symbol based on the platform
*/
export const getModifierKey = (): string => {
return isMac() ? '⌘' : 'Ctrl';
export const getModifierKey = async (): Promise<string> => {
return (await isMac()) ? '⌘' : 'Ctrl';
};
/**
* Gets the shift key symbol based on the platform
*/
export const getShiftKey = (): string => {
return isMac() ? '⇧' : 'Shift';
export const getShiftKey = async (): Promise<string> => {
return (await isMac()) ? '⇧' : 'Shift';
};
/**
* Formats a keyboard shortcut for display based on the platform
* @param keys Array of key combinations (e.g., ['mod', 'D'] or ['shift', 'mod', 'Scroll'])
*/
export const formatShortcut = (keys: string[]): string => {
export const formatShortcut = async (keys: string[]): Promise<string> => {
const isMacPlatform = await isMac();
return keys
.map(key => {
if (key.toLowerCase() === 'mod') return getModifierKey();
if (key.toLowerCase() === 'shift') return getShiftKey();
if (key.toLowerCase() === 'mod') return isMacPlatform ? '⌘' : 'Ctrl';
if (key.toLowerCase() === 'shift') return isMacPlatform ? '⇧' : 'Shift';
return key;
})
.join(' + ');