/** * Shared Edit and View menus for recollections. Always rendered in the menubar; * uses the active slot (flux or logos by pathname) for undo, redo, and Flux-only actions. */ import React, { useEffect, useMemo } from 'react' import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarTrigger, } from '@/components/ui/menubar' import { Kbd, KbdGroup } from '@/components/ui/kbd' import { useRecollectionActions } from './RecollectionActionsContext' import { ClipboardPaste, Copy, CopyPlus, Redo2, Undo2, Play, Square } from 'lucide-react' import { useRunStore } from '@/lib/graph/runStore' const UNDO_KEYS = { key: 'z', shiftKey: false } const REDO_KEYS = { key: 'z', shiftKey: true } function matchKey(ev: KeyboardEvent, want: { key: string; shiftKey: boolean }) { const mod = ev.ctrlKey || ev.metaKey return ev.key.toLowerCase() === want.key && !!mod && !!ev.shiftKey === want.shiftKey } export function RecollectionEditViewMenus() { const { activeSlot, flux, isFluxActive } = useRecollectionActions() const runStatus = useRunStore((s) => s.status) const resetRun = useRunStore((s) => s.reset) const isDirty = useRunStore((s) => s.dirty) const fluxSlot = isFluxActive ? flux : null const isRunning = runStatus === 'running' || runStatus === 'pending' const hasFinished = runStatus === 'completed' || runStatus === 'failed' // Keyboard shortcut: Cmd+Enter to run useEffect(() => { if (!fluxSlot?.onRun) return const onKeyDown = (ev: KeyboardEvent) => { const mod = ev.ctrlKey || ev.metaKey if (mod && ev.key === 'Enter' && !isRunning) { ev.preventDefault() ev.stopPropagation() fluxSlot.onRun?.() } } window.addEventListener('keydown', onKeyDown, true) return () => window.removeEventListener('keydown', onKeyDown, true) }, [fluxSlot?.onRun, isRunning]) useEffect(() => { if (!activeSlot) return const onKeyDown = (ev: KeyboardEvent) => { if (matchKey(ev, UNDO_KEYS) && activeSlot.canUndo) { ev.preventDefault() ev.stopPropagation() activeSlot.undo() } else if (matchKey(ev, REDO_KEYS) && activeSlot.canRedo) { ev.preventDefault() ev.stopPropagation() activeSlot.redo() } } window.addEventListener('keydown', onKeyDown, true) return () => window.removeEventListener('keydown', onKeyDown, true) }, [activeSlot]) const hasFluxOnly = fluxSlot && (fluxSlot.onDuplicate != null || fluxSlot.onCopy != null || fluxSlot.onPaste != null) const menus = useMemo( () => ( Edit activeSlot?.undo()} disabled={!activeSlot?.canUndo} className="gap-2" > Undo ⌘ + Z activeSlot?.redo()} disabled={!activeSlot?.canRedo} className="gap-2" > Redo ⌘ + ⇧ + Z {hasFluxOnly && fluxSlot && ( <> {fluxSlot.onDuplicate != null && ( Duplicate ⌘D )} {fluxSlot.onCopy != null && ( Copy ⌘C )} {fluxSlot.onPaste != null && ( Paste ⌘V )} )} View {fluxSlot?.onFitView && ( Fit View ⌘0 )} ), [activeSlot, fluxSlot, hasFluxOnly] ) return (
{menus} {fluxSlot?.onRun && ( )}
) }