feat: full screen node

This commit is contained in:
2026-03-11 20:22:32 +01:00
parent 0b8c18a43f
commit 86d264e83a
11 changed files with 151 additions and 12 deletions

View File

@@ -51,6 +51,11 @@ import {
EmptyTitle,
} from '@/components/ui/empty'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogTitle,
} from '@/components/ui/dialog'
import { ClipboardPaste, FolderOpen, FileStack, CircleDotDashed } from 'lucide-react'
import { DEFAULT_NODE_STYLE, getDefaultDataForType, getNextNodeId } from '@/lib/flowUtils'
import {
@@ -58,6 +63,7 @@ import {
getRegisteredNodeTypesGroupedByClassification,
getRegisteredNodeTypeIds,
getDefaultStyle,
getNodeType,
isConnectionAllowed,
} from '@/lib/nodeRegistry'
import type { AppNode, AppEdge } from '@/lib/nodeTypes'
@@ -131,6 +137,70 @@ function FlowFitViewOnLoad() {
return null
}
type FullscreenNodeOverlayProps = {
nodeId: string
nodes: AppNode[]
onClose: () => void
}
function FullscreenNodeOverlay({ nodeId, nodes, onClose }: FullscreenNodeOverlayProps) {
const open = nodeId != null
const node = nodes.find((n) => n.id === nodeId)
if (!nodeId) return null
return (
<Dialog open={open} onOpenChange={(isOpen) => { if (!isOpen) onClose() }}>
<DialogContent
className="max-w-[94vw] w-[94vw] max-h-[90vh] h-[90vh] p-0 gap-0 overflow-hidden flex flex-col border rounded-lg shadow-xl bg-card"
aria-describedby={undefined}
hideCloseButton
>
<DialogTitle className="sr-only">Node: {node?.id ?? nodeId}</DialogTitle>
{node && node.type && (
<FullscreenNodeContent node={node} />
)}
</DialogContent>
</Dialog>
)
}
function FullscreenNodeContent({ node }: { node: AppNode }) {
const descriptor = getNodeType(node.type ?? '')
const NodeComponent = descriptor?.component as React.ComponentType<{
id: string
data: Record<string, unknown>
type?: string
selected?: boolean
width?: number
height?: number
}> | undefined
if (!NodeComponent) return null
const w = typeof window !== 'undefined' ? Math.round(window.innerWidth * 0.94) : 1200
const h = typeof window !== 'undefined' ? Math.round(window.innerHeight * 0.9) : 800
const fullscreenNodeForStore: AppNode = {
...node,
position: node.position ?? { x: 0, y: 0 },
style: { ...(node.style as object), width: w, height: h },
}
return (
<div className="flex-1 min-h-0 overflow-hidden flex">
<ReactFlowProvider initialNodes={[fullscreenNodeForStore]} initialEdges={[]}>
<NodeComponent
id={node.id}
data={(node.data as Record<string, unknown>) ?? {}}
type={node.type}
selected={false}
width={w}
height={h}
/>
</ReactFlowProvider>
</div>
)
}
export type CanvasPageProps = {
/** Optional project id for future per-project graph loading */
projectId?: string
@@ -193,6 +263,7 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
const [isPanning, setIsPanning] = React.useState(false)
const [isSelecting, setIsSelecting] = React.useState(false)
const [ariaAnnouncement, setAriaAnnouncement] = React.useState<string | null>(null)
const [fullscreenNodeId, setFullscreenNodeId] = React.useState<string | null>(null)
const nodesRef = useRef(nodes)
nodesRef.current = nodes
@@ -412,6 +483,8 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
setConnectionFrom,
isValidConnection,
flowActionsRef,
fullscreenNodeId,
setFullscreenNodeId,
}),
[
nodes,
@@ -424,6 +497,8 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
setConnectionFrom,
isValidConnection,
flowActionsRef,
fullscreenNodeId,
setFullscreenNodeId,
]
)
@@ -707,6 +782,13 @@ export function CanvasPage({ projectId }: CanvasPageProps) {
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
{fullscreenNodeId && (
<FullscreenNodeOverlay
nodeId={fullscreenNodeId}
nodes={nodes}
onClose={() => setFullscreenNodeId(null)}
/>
)}
</FlowContext.Provider>
</div>
</div>