From 929359b4bce866092755ef25281cba257ee45fc0 Mon Sep 17 00:00:00 2001 From: dtoro Date: Sun, 15 Mar 2026 21:11:57 +0100 Subject: [PATCH] feat: refactor menus --- frontend/src/app/canvas/CanvasPage.tsx | 85 +++--- .../app/recollections/RecollectionLayout.tsx | 15 +- .../app/recollections/RecollectionMenubar.tsx | 20 +- .../RecollectionMenubarContext.tsx | 18 +- .../RecollectionTitleContent.tsx | 220 ++++++++++++++ .../RecollectionTitleDataContext.tsx | 44 +++ .../recollections/flux/FluxMenubarContent.tsx | 287 ++++-------------- .../src/app/recollections/logos/LogosPage.tsx | 7 +- 8 files changed, 406 insertions(+), 290 deletions(-) create mode 100644 frontend/src/app/recollections/RecollectionTitleContent.tsx create mode 100644 frontend/src/app/recollections/RecollectionTitleDataContext.tsx diff --git a/frontend/src/app/canvas/CanvasPage.tsx b/frontend/src/app/canvas/CanvasPage.tsx index 169aa15..353edec 100644 --- a/frontend/src/app/canvas/CanvasPage.tsx +++ b/frontend/src/app/canvas/CanvasPage.tsx @@ -4,6 +4,7 @@ */ import React, { useCallback, useEffect, useMemo, useRef } from 'react' +import { useLocation } from 'react-router-dom' import { ReactFlow, ReactFlowProvider, @@ -36,7 +37,7 @@ import { ContextMenu, ContextMenuTrigger } from '@/components/ui/context-menu' import { CanvasContextMenuContent } from '@/app/canvas/CanvasContextMenuContent' import { useCanvasConnectionPathFromStore } from '@/app/canvas/useCanvasConnectionPathFromStore' import { dispatchCanvasCommand } from '@/app/canvas/canvasStore' -import { useRecollectionMenubar } from '@/app/recollections/RecollectionMenubarContext' +import { useRecollectionTitleData } from '@/app/recollections/RecollectionTitleDataContext' import { FluxMenubarContent } from '@/app/recollections/flux/FluxMenubarContent' import { createContextualNode } from '@/app/canvas/ContextualZoomNode' import { ViewportDisplayProvider } from '@/app/canvas/ViewportDisplayContext' @@ -428,52 +429,57 @@ export function CanvasPage({ recollectionId }: CanvasPageProps) { flowActionsRef.current?.pasteAtViewportCenter?.() }, []) - const { setCustomContent } = useRecollectionMenubar() + const { pathname } = useLocation() + const isFluxActive = pathname.endsWith('/flux') + const { setTitleData } = useRecollectionTitleData() useEffect(() => { - setCustomContent( - { - save() - toast.success('Saved') - } - : undefined - } - canSave={Boolean(recollectionId)} - saveStatus={saveStatus} - undo={undo} - redo={redo} - canUndo={canUndo} - canRedo={canRedo} - onDuplicate={handleDuplicate} - onCopy={handleCopy} - onPaste={handlePaste} - canDuplicate={selectedNodes.length > 0} - canCopy={selectedNodes.length === 1} - onFitView={() => flowActionsRef.current?.fitView?.()} - /> - ) - return () => setCustomContent(null) + setTitleData({ + saveStatus, + onSave: recollectionId + ? () => { + save() + toast.success('Saved') + } + : undefined, + canSave: Boolean(recollectionId), + onImport: handleImportRecollection, + onExport: handleExportRecollection, + }) + return () => setTitleData(null) }, [ - setCustomContent, - recollectionId, + setTitleData, saveStatus, - canUndo, - canRedo, - selectedNodes.length, + recollectionId, save, - undo, - redo, - handleDuplicate, - handleCopy, - handlePaste, handleImportRecollection, handleExportRecollection, ]) + const fluxMenubarProps = useMemo( + () => ({ + undo, + redo, + canUndo, + canRedo, + onDuplicate: handleDuplicate, + onCopy: handleCopy, + onPaste: handlePaste, + canDuplicate: selectedNodes.length > 0, + canCopy: selectedNodes.length === 1, + onFitView: () => flowActionsRef.current?.fitView?.(), + }), + [ + canUndo, + canRedo, + selectedNodes.length, + undo, + redo, + handleDuplicate, + handleCopy, + handlePaste, + ] + ) + const graphContextValue = useMemo( () => ({ setNodes, setEdges, graphRef, edges }), [setNodes, setEdges, edges] @@ -682,6 +688,7 @@ export function CanvasPage({ recollectionId }: CanvasPageProps) { onContextMenu={onCanvasContextMenu} style={{ position: 'relative' }} > + {isFluxActive && }
{ariaAnnouncement}
diff --git a/frontend/src/app/recollections/RecollectionLayout.tsx b/frontend/src/app/recollections/RecollectionLayout.tsx index 34f9ef8..3e13370 100644 --- a/frontend/src/app/recollections/RecollectionLayout.tsx +++ b/frontend/src/app/recollections/RecollectionLayout.tsx @@ -6,6 +6,8 @@ import React, { useEffect } from 'react' import { Link, useParams } from 'react-router-dom' import { usePlatform } from '@/app/kosmos/KosmosContext' import { RecollectionMenubarProvider } from './RecollectionMenubarContext' +import { RecollectionTitleDataProvider } from './RecollectionTitleDataContext' +import { RecollectionTitleContent } from './RecollectionTitleContent' import { RecollectionMenubar } from './RecollectionMenubar' import { FlippingCardView } from './FlippingCardView' @@ -33,12 +35,15 @@ export function RecollectionLayout() { return ( -
- -
- + + +
+ +
+ +
-
+ ) } diff --git a/frontend/src/app/recollections/RecollectionMenubar.tsx b/frontend/src/app/recollections/RecollectionMenubar.tsx index 6629545..6e7dcfb 100644 --- a/frontend/src/app/recollections/RecollectionMenubar.tsx +++ b/frontend/src/app/recollections/RecollectionMenubar.tsx @@ -1,9 +1,9 @@ /** - * Shared base menubar for a recollection: Back, optional custom content (from context), Logos/Flux switcher. + * Recollection menubar: Back | title + save status | registered menus (middle) | Logos | Flux. */ import React from 'react' -import { Link, NavLink, useParams, useLocation } from 'react-router-dom' +import { Link, NavLink, useParams } from 'react-router-dom' import { usePlatform } from '@/app/kosmos/KosmosContext' import { ArrowLeft } from 'lucide-react' import { FluxIcon, LogosIcon } from '@/lib/icons' @@ -11,20 +11,17 @@ import { useRecollectionMenubar } from './RecollectionMenubarContext' export function RecollectionMenubar() { const { recollectionId } = useParams<{ recollectionId: string }>() - const { pathname } = useLocation() const { recollections } = usePlatform() - const { customContent } = useRecollectionMenubar() + const { titleContent, customContent } = useRecollectionMenubar() - const isFlux = pathname.endsWith('/flux') const recollection = recollectionId ? recollections.find((p) => p.id === recollectionId) : null const base = recollectionId ? `/recollections/${recollectionId}` : '' - const defaultMiddle = recollection ? ( + const defaultTitle = recollection ? ( {recollection.name} ) : null - const middleContent = isFlux ? (customContent ?? defaultMiddle) : defaultMiddle return ( -
+
-
- {middleContent} +
+ {titleContent ?? defaultTitle} +
+
+ {customContent}
{base && (