feat: improvements
This commit is contained in:
145
frontend/src/app/recollections/RecollectionEditViewMenus.tsx
Normal file
145
frontend/src/app/recollections/RecollectionEditViewMenus.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
Reference in New Issue
Block a user