import React, { useState, useEffect, useRef, useCallback } from "react" import { useEditor } from "tldraw" // Command Palette that shows when holding Ctrl+Shift or via global event // Displays available keyboard shortcuts for custom tools and actions interface ShortcutItem { id: string label: string kbd: string key: string // The actual key to press (e.g., 'V', 'C', etc.) icon?: string category: 'tool' | 'action' } // Global function to open the command palette export function openCommandPalette() { window.dispatchEvent(new CustomEvent('open-command-palette')) } export function CommandPalette() { const editor = useEditor() const [isVisible, setIsVisible] = useState(false) const [isManuallyOpened, setIsManuallyOpened] = useState(false) const holdTimeoutRef = useRef(null) const keysHeldRef = useRef({ ctrl: false, shift: false }) // Custom tools with Ctrl+Shift shortcuts (matching overrides.tsx) const customToolShortcuts: ShortcutItem[] = [ { id: 'VideoChat', label: 'Video Chat', kbd: '⌃⇧V', key: 'V', icon: '📹', category: 'tool' }, { id: 'ChatBox', label: 'Chat Box', kbd: '⌃⇧C', key: 'C', icon: '💬', category: 'tool' }, { id: 'Embed', label: 'Embed', kbd: '⌃⇧E', key: 'E', icon: '🔗', category: 'tool' }, { id: 'Slide', label: 'Slide', kbd: '⌃⇧S', key: 'S', icon: '📊', category: 'tool' }, { id: 'Markdown', label: 'Markdown', kbd: '⌃⇧M', key: 'M', icon: '📝', category: 'tool' }, { id: 'MycrozineTemplate', label: 'Mycrozine', kbd: '⌃⇧Z', key: 'Z', icon: '📰', category: 'tool' }, { id: 'Prompt', label: 'LLM Prompt', kbd: '⌃⇧L', key: 'L', icon: '🤖', category: 'tool' }, { id: 'ObsidianNote', label: 'Obsidian Note', kbd: '⌃⇧O', key: 'O', icon: '📓', category: 'tool' }, { id: 'Transcription', label: 'Transcription', kbd: '⌃⇧T', key: 'T', icon: '🎤', category: 'tool' }, // { id: 'Holon', label: 'Holon', kbd: '⌃⇧H', key: 'H', icon: '⭕', category: 'tool' }, // Temporarily hidden { id: 'FathomMeetings', label: 'Fathom Meetings', kbd: '⌃⇧F', key: 'F', icon: '📅', category: 'tool' }, { id: 'ImageGen', label: 'Image Gen', kbd: '⌃⇧I', key: 'I', icon: '🖼️', category: 'tool' }, // { id: 'VideoGen', label: 'Video Gen', kbd: '⌃⇧G', key: 'G', icon: '🎬', category: 'tool' }, // Temporarily hidden // { id: 'Multmux', label: 'Terminal', kbd: '⌃⇧K', key: 'K', icon: '💻', category: 'tool' }, // Temporarily hidden ] // Custom actions with shortcuts (matching overrides.tsx) const customActionShortcuts: ShortcutItem[] = [ { id: 'zoom-to-selection', label: 'Zoom to Selection', kbd: 'Z', key: 'Z', icon: '🔍', category: 'action' }, { id: 'copy-link', label: 'Copy Link', kbd: '⌃⌥C', key: 'C', icon: '🔗', category: 'action' }, { id: 'lock-element', label: 'Lock Element', kbd: '⇧L', key: 'L', icon: '🔒', category: 'action' }, { id: 'search-shapes', label: 'Search Shapes', kbd: 'S', key: 'S', icon: '🔎', category: 'action' }, { id: 'semantic-search', label: 'Semantic Search', kbd: '⇧S', key: 'S', icon: '🧠', category: 'action' }, { id: 'ask-ai', label: 'Ask AI About Canvas', kbd: '⇧A', key: 'A', icon: '✨', category: 'action' }, { id: 'export-pdf', label: 'Export to PDF', kbd: '⌃⌥P', key: 'P', icon: '📄', category: 'action' }, { id: 'run-llm', label: 'Run LLM on Arrow', kbd: '⌃⌥R', key: 'R', icon: '⚡', category: 'action' }, ] // Handle clicking on a tool/action const handleItemClick = useCallback((item: ShortcutItem) => { setIsVisible(false) setIsManuallyOpened(false) if (item.category === 'tool') { // Set the current tool editor.setCurrentTool(item.id) } else { // Dispatch keyboard event to trigger the action // Simulate the keyboard shortcut const event = new KeyboardEvent('keydown', { key: item.key, code: `Key${item.key}`, ctrlKey: item.kbd.includes('⌃'), shiftKey: item.kbd.includes('⇧'), altKey: item.kbd.includes('⌥'), bubbles: true, }) window.dispatchEvent(event) } }, [editor]) // Handle manual open via custom event useEffect(() => { const handleOpenEvent = () => { setIsManuallyOpened(true) setIsVisible(true) } window.addEventListener('open-command-palette', handleOpenEvent) return () => window.removeEventListener('open-command-palette', handleOpenEvent) }, []) // Handle Escape key and click outside to close when manually opened useEffect(() => { if (!isManuallyOpened) return const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') { setIsVisible(false) setIsManuallyOpened(false) } } const handleClickOutside = (e: MouseEvent) => { const target = e.target as HTMLElement if (target.closest('.command-palette')) return setIsVisible(false) setIsManuallyOpened(false) } window.addEventListener('keydown', handleEscape) window.addEventListener('mousedown', handleClickOutside) return () => { window.removeEventListener('keydown', handleEscape) window.removeEventListener('mousedown', handleClickOutside) } }, [isManuallyOpened]) // Handle Ctrl+Shift key press/release useEffect(() => { const checkAndShowPalette = () => { if (keysHeldRef.current.ctrl && keysHeldRef.current.shift) { // Clear any existing timeout if (holdTimeoutRef.current) { clearTimeout(holdTimeoutRef.current) } // Set a small delay before showing (to avoid flashing on quick combos) holdTimeoutRef.current = setTimeout(() => { setIsVisible(true) }, 300) // 300ms hold to show } } const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Control') { keysHeldRef.current.ctrl = true checkAndShowPalette() } else if (e.key === 'Shift') { keysHeldRef.current.shift = true checkAndShowPalette() } else if (isVisible && !isManuallyOpened) { // Hide on any other key press (they're using a shortcut) - only if not manually opened setIsVisible(false) } } const handleKeyUp = (e: KeyboardEvent) => { if (e.key === 'Control') { keysHeldRef.current.ctrl = false } else if (e.key === 'Shift') { keysHeldRef.current.shift = false } // Hide palette if either key is released - only if not manually opened if (!isManuallyOpened && (!keysHeldRef.current.ctrl || !keysHeldRef.current.shift)) { if (holdTimeoutRef.current) { clearTimeout(holdTimeoutRef.current) holdTimeoutRef.current = null } setIsVisible(false) } } window.addEventListener('keydown', handleKeyDown) window.addEventListener('keyup', handleKeyUp) return () => { window.removeEventListener('keydown', handleKeyDown) window.removeEventListener('keyup', handleKeyUp) if (holdTimeoutRef.current) { clearTimeout(holdTimeoutRef.current) } } }, [isVisible, isManuallyOpened]) if (!isVisible) return null return (
{/* Header */}

⌨️ Command Palette

Click a button or use Ctrl+Shift + Key to activate

{/* Tools Section */}

Tools (Ctrl+Shift + Key)

{customToolShortcuts.map(item => ( ))}
{/* Actions Section */}

Actions

{customActionShortcuts.map(item => ( ))}
{/* Footer hint */}

Press ? for full keyboard shortcuts dialog

{/* CSS Animations */}
) }