146 lines
5.0 KiB
TypeScript
146 lines
5.0 KiB
TypeScript
/**
|
|
* 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 } from 'lucide-react'
|
|
|
|
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 fluxSlot = isFluxActive ? flux : null
|
|
|
|
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 menus = useMemo(() => {
|
|
if (!activeSlot) return null
|
|
const hasFluxOnly =
|
|
fluxSlot &&
|
|
(fluxSlot.onDuplicate != null || fluxSlot.onCopy != null || fluxSlot.onPaste != null)
|
|
return (
|
|
<Menubar className="shrink-0 rounded-none border-0 bg-transparent p-0 shadow-none">
|
|
<MenubarMenu>
|
|
<MenubarTrigger className="font-normal text-muted-foreground">Edit</MenubarTrigger>
|
|
<MenubarContent>
|
|
<MenubarItem onClick={activeSlot.undo} disabled={!activeSlot.canUndo} className="gap-2">
|
|
<Undo2 className="h-4 w-4" />
|
|
Undo
|
|
<span className="ml-auto pl-4">
|
|
<KbdGroup>
|
|
<Kbd>⌘ + Z</Kbd>
|
|
</KbdGroup>
|
|
</span>
|
|
</MenubarItem>
|
|
<MenubarItem onClick={activeSlot.redo} disabled={!activeSlot.canRedo} className="gap-2">
|
|
<Redo2 className="h-4 w-4" />
|
|
Redo
|
|
<span className="ml-auto pl-4">
|
|
<KbdGroup>
|
|
<Kbd>⌘ + ⇧ + Z</Kbd>
|
|
</KbdGroup>
|
|
</span>
|
|
</MenubarItem>
|
|
{hasFluxOnly && (
|
|
<>
|
|
<MenubarSeparator />
|
|
{fluxSlot!.onDuplicate != null && (
|
|
<MenubarItem
|
|
onClick={fluxSlot!.onDuplicate}
|
|
disabled={!fluxSlot!.canDuplicate}
|
|
className="gap-2"
|
|
>
|
|
<CopyPlus className="h-4 w-4" />
|
|
Duplicate
|
|
<span className="ml-auto pl-4">
|
|
<KbdGroup>
|
|
<Kbd>⌘D</Kbd>
|
|
</KbdGroup>
|
|
</span>
|
|
</MenubarItem>
|
|
)}
|
|
{fluxSlot!.onCopy != null && (
|
|
<MenubarItem
|
|
onClick={fluxSlot!.onCopy}
|
|
disabled={!fluxSlot!.canCopy}
|
|
className="gap-2"
|
|
>
|
|
<Copy className="h-4 w-4" />
|
|
Copy
|
|
<span className="ml-auto pl-4">
|
|
<KbdGroup>
|
|
<Kbd>⌘C</Kbd>
|
|
</KbdGroup>
|
|
</span>
|
|
</MenubarItem>
|
|
)}
|
|
{fluxSlot!.onPaste != null && (
|
|
<MenubarItem onClick={fluxSlot!.onPaste} className="gap-2">
|
|
<ClipboardPaste className="h-4 w-4" />
|
|
Paste
|
|
<span className="ml-auto pl-4">
|
|
<KbdGroup>
|
|
<Kbd>⌘V</Kbd>
|
|
</KbdGroup>
|
|
</span>
|
|
</MenubarItem>
|
|
)}
|
|
</>
|
|
)}
|
|
</MenubarContent>
|
|
</MenubarMenu>
|
|
<MenubarMenu>
|
|
<MenubarTrigger className="font-normal text-muted-foreground">View</MenubarTrigger>
|
|
<MenubarContent>
|
|
{fluxSlot?.onFitView && (
|
|
<MenubarItem onClick={fluxSlot.onFitView} className="gap-2">
|
|
Fit View
|
|
<span className="ml-auto pl-4">
|
|
<KbdGroup>
|
|
<Kbd>⌘0</Kbd>
|
|
</KbdGroup>
|
|
</span>
|
|
</MenubarItem>
|
|
)}
|
|
</MenubarContent>
|
|
</MenubarMenu>
|
|
</Menubar>
|
|
)
|
|
}, [activeSlot, fluxSlot])
|
|
|
|
return menus
|
|
}
|