F: Improve diagram flow management

This commit is contained in:
Ivan 2025-04-30 01:04:04 +03:00
parent caf11aa329
commit 6bc7993797
12 changed files with 213 additions and 134 deletions

View File

@ -0,0 +1,100 @@
'use client';
import { type ReactNode, useState } from 'react';
import { Background, ReactFlow, type ReactFlowProps } from 'reactflow';
export { useReactFlow, useStoreApi } from 'reactflow';
import { cn } from '../utils';
type DiagramFlowProps = {
children?: ReactNode;
height?: number | string;
showGrid?: boolean;
gridSize?: number;
onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
onKeyUp?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
} & ReactFlowProps;
export function DiagramFlow({
children,
className,
style,
height,
showGrid = false,
gridSize = 20,
onKeyDown,
onKeyUp,
nodesDraggable = true,
nodesFocusable,
edgesFocusable,
onNodesChange,
onEdgesChange,
onNodeClick,
onNodeDoubleClick,
onNodeContextMenu,
onNodeMouseEnter,
onNodeMouseLeave,
onNodeDragStart,
onNodeDrag,
onNodeDragStop,
onContextMenu,
...restProps
}: DiagramFlowProps) {
const [spaceMode, setSpaceMode] = useState(false);
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.code === 'Space') {
event.preventDefault();
event.stopPropagation();
setSpaceMode(true);
}
onKeyDown?.(event);
}
function handleKeyUp(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.code === 'Space') {
setSpaceMode(false);
}
onKeyUp?.(event);
}
function handleContextMenu(event: React.MouseEvent<HTMLDivElement>) {
event.preventDefault();
onContextMenu?.(event);
}
return (
<div
tabIndex={-1}
className={cn('relative cc-mask-sides w-[100dvw]', spaceMode && 'space-mode', className)}
style={{ ...style, height: height }}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
>
<ReactFlow
{...restProps}
onNodesChange={spaceMode ? undefined : onNodesChange}
onEdgesChange={spaceMode ? undefined : onEdgesChange}
onNodeClick={spaceMode ? undefined : onNodeClick}
onNodeDoubleClick={spaceMode ? undefined : onNodeDoubleClick}
nodesDraggable={!spaceMode && nodesDraggable}
nodesFocusable={!spaceMode && nodesFocusable}
edgesFocusable={!spaceMode && edgesFocusable}
onNodeDragStart={spaceMode ? undefined : onNodeDragStart}
onNodeDrag={spaceMode ? undefined : onNodeDrag}
onNodeDragStop={spaceMode ? undefined : onNodeDragStop}
onNodeContextMenu={spaceMode ? undefined : onNodeContextMenu}
onNodeMouseEnter={spaceMode ? undefined : onNodeMouseEnter}
onNodeMouseLeave={spaceMode ? undefined : onNodeMouseLeave}
onContextMenu={handleContextMenu}
>
{showGrid ? <Background gap={gridSize} /> : null}
{children}
</ReactFlow>
</div>
);
}

View File

@ -28,12 +28,12 @@ export function BlockNode(node: BlockInternalNode) {
return ( return (
<> <>
<NodeResizeControl minWidth={BLOCK_NODE_MIN_WIDTH} minHeight={BLOCK_NODE_MIN_HEIGHT}> <NodeResizeControl minWidth={BLOCK_NODE_MIN_WIDTH} minHeight={BLOCK_NODE_MIN_HEIGHT}>
<IconResize size={8} className='absolute bottom-[2px] right-[2px] cc-graph-interactive' /> <IconResize size={12} className='absolute bottom-[3px] right-[3px] cc-graph-interactive' />
</NodeResizeControl> </NodeResizeControl>
{showCoordinates ? ( {showCoordinates ? (
<div <div
className={clsx( className={clsx(
'absolute top-full mt-1 right-[1px]', 'absolute top-full mt-[4px] right-[1px]',
'text-[7px]/[8px] font-math', 'text-[7px]/[8px] font-math',
'text-muted-foreground hover:text-foreground' 'text-muted-foreground hover:text-foreground'
)} )}
@ -43,24 +43,24 @@ export function BlockNode(node: BlockInternalNode) {
) : null} ) : null}
<div <div
className={clsx( className={clsx(
'cc-node-block h-full w-full', 'cc-node-block h-full w-full cursor-pointer',
isDragging && isParent && dropTarget !== node.data.block.id && 'border-destructive', isDragging && isParent && dropTarget !== node.data.block.id && 'border-destructive',
((isParent && !isDragging) || dropTarget === node.data.block.id) && 'border-primary', ((isParent && !isDragging) || dropTarget === node.data.block.id) && 'border-primary',
isChild && 'border-accent-orange' isChild && 'border-accent-orange'
)} )}
> >
<div className='absolute top-0 left-0 w-full h-2 cc-graph-interactive cursor-pointer' /> <div className='absolute -top-[8px] left-0 w-full h-[16px] cc-graph-interactive' />
<div className='absolute top-0 right-0 h-full w-2 cc-graph-interactive cursor-pointer' /> <div className='absolute top-0 -right-[8px] h-full w-[16px] cc-graph-interactive' />
<div className='absolute bottom-0 right-0 w-full h-2 cc-graph-interactive cursor-pointer' /> <div className='absolute -bottom-[8px] right-0 w-full h-[16px] cc-graph-interactive' />
<div className='absolute bottom-0 left-0 h-full w-2 cc-graph-interactive cursor-pointer' /> <div className='absolute bottom-0 -left-[8px] h-full w-[16px] cc-graph-interactive' />
<div <div
className={clsx( className={clsx(
'w-fit mx-auto -translate-y-1/2 -mt-[8px]', 'w-fit mx-auto -translate-y-1/2 -mt-[8px]',
'px-2', 'px-[8px]',
'bg-background rounded-lg', 'bg-background rounded-lg',
'text-[18px]/[20px] line-clamp-2 text-center text-ellipsis', 'text-[18px]/[20px] line-clamp-2 text-center text-ellipsis',
'cc-graph-interactive cursor-pointer' 'cc-graph-interactive'
)} )}
data-tooltip-id={globalIDs.operation_tooltip} data-tooltip-id={globalIDs.operation_tooltip}
data-tooltip-hidden={node.dragging} data-tooltip-hidden={node.dragging}

View File

@ -42,7 +42,7 @@ export function NodeCore({ node }: NodeCoreProps) {
data-tooltip-hidden={node.dragging} data-tooltip-hidden={node.dragging}
onMouseEnter={() => setHover(node.data.operation)} onMouseEnter={() => setHover(node.data.operation)}
> >
<div className='absolute z-pop top-0 right-0 flex flex-col gap-1 p-[2px]'> <div className='absolute z-pop top-0 right-0 flex flex-col gap-[4px] p-[2px]'>
<Indicator <Indicator
noPadding noPadding
title={hasFile ? 'Связанная КС' : 'Нет связанной КС'} title={hasFile ? 'Связанная КС' : 'Нет связанной КС'}
@ -59,7 +59,7 @@ export function NodeCore({ node }: NodeCoreProps) {
{showCoordinates ? ( {showCoordinates ? (
<div <div
className={clsx( className={clsx(
'absolute top-full mt-1 right-[1px]', 'absolute top-full mt-[4px] right-[1px]',
'text-[7px]/[8px] font-math', 'text-[7px]/[8px] font-math',
'text-muted-foreground hover:text-foreground', 'text-muted-foreground hover:text-foreground',
node.selected && 'translate-y-[6px]' node.selected && 'translate-y-[6px]'

View File

@ -11,10 +11,9 @@ import { PARAMETER } from '@/utils/constants';
import { useOssEdit } from '../oss-edit-context'; import { useOssEdit } from '../oss-edit-context';
import { flowOptions } from './oss-flow';
import { OssFlowContext } from './oss-flow-context'; import { OssFlowContext } from './oss-flow-context';
const VIEW_PADDING = 0.2;
const Z_BLOCK = 1; const Z_BLOCK = 1;
const Z_SCHEMA = 10; const Z_SCHEMA = 10;
@ -86,7 +85,7 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => {
setNodes(newNodes); setNodes(newNodes);
setEdges(newEdges); setEdges(newEdges);
setTimeout(() => fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }), PARAMETER.refreshTimeout); setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout);
}, [schema, setNodes, setEdges, edgeAnimate, edgeStraight, fitView]); }, [schema, setNodes, setEdges, edgeAnimate, edgeStraight, fitView]);
useEffect(() => { useEffect(() => {
@ -94,7 +93,7 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => {
}, [schema, edgeAnimate, edgeStraight, resetGraph]); }, [schema, edgeAnimate, edgeStraight, resetGraph]);
function resetView() { function resetView() {
setTimeout(() => fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }), PARAMETER.refreshTimeout); setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout);
} }
return ( return (

View File

@ -1,11 +1,12 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState } from 'react';
import { Background, ReactFlow, useReactFlow, useStoreApi } from 'reactflow';
import clsx from 'clsx'; import clsx from 'clsx';
import { DiagramFlow, useReactFlow, useStoreApi } from '@/components/flow/diagram-flow';
import { useMainHeight } from '@/stores/app-layout'; import { useMainHeight } from '@/stores/app-layout';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { promptText } from '@/utils/labels'; import { promptText } from '@/utils/labels';
import { useDeleteBlock } from '../../../backend/use-delete-block'; import { useDeleteBlock } from '../../../backend/use-delete-block';
@ -25,8 +26,18 @@ import { ToolbarOssGraph } from './toolbar-oss-graph';
import { useDragging } from './use-dragging'; import { useDragging } from './use-dragging';
import { useGetLayout } from './use-get-layout'; import { useGetLayout } from './use-get-layout';
const ZOOM_MAX = 2; export const flowOptions = {
const ZOOM_MIN = 0.5; fitView: true,
fitViewOptions: { padding: 0.3, duration: PARAMETER.zoomDuration },
edgesFocusable: false,
nodesFocusable: false,
nodesConnectable: false,
maxZoom: 2,
minZoom: 0.5,
gridSize: GRID_SIZE,
snapToGrid: true,
snapGrid: [GRID_SIZE, GRID_SIZE] as [number, number]
} as const;
export function OssFlow() { export function OssFlow() {
const mainHeight = useMainHeight(); const mainHeight = useMainHeight();
@ -46,7 +57,6 @@ export function OssFlow() {
const { deleteBlock } = useDeleteBlock(); const { deleteBlock } = useDeleteBlock();
const [mouseCoords, setMouseCoords] = useState<Position2D>({ x: 0, y: 0 }); const [mouseCoords, setMouseCoords] = useState<Position2D>({ x: 0, y: 0 });
const [spacePressed, setSpacePressed] = useState(false);
const showCreateOperation = useDialogsStore(state => state.showCreateOperation); const showCreateOperation = useDialogsStore(state => state.showCreateOperation);
const showCreateBlock = useDialogsStore(state => state.showCreateBlock); const showCreateBlock = useDialogsStore(state => state.showCreateBlock);
@ -131,12 +141,6 @@ export function OssFlow() {
} }
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) { function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.code === 'Space') {
event.preventDefault();
event.stopPropagation();
setSpacePressed(true);
return;
}
if (isProcessing) { if (isProcessing) {
return; return;
} }
@ -173,25 +177,13 @@ export function OssFlow() {
} }
} }
function handleKeyUp(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.code === 'Space') {
setSpacePressed(false);
}
}
function handleMouseMove(event: React.MouseEvent<HTMLDivElement>) { function handleMouseMove(event: React.MouseEvent<HTMLDivElement>) {
const targetPosition = screenToFlowPosition({ x: event.clientX, y: event.clientY }); const targetPosition = screenToFlowPosition({ x: event.clientX, y: event.clientY });
setMouseCoords(targetPosition); setMouseCoords(targetPosition);
} }
return ( return (
<div <div tabIndex={-1} className='relative' onMouseMove={showCoordinates ? handleMouseMove : undefined}>
tabIndex={-1}
className='relative'
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onMouseMove={showCoordinates ? handleMouseMove : undefined}
>
{showCoordinates ? <CoordinateDisplay mouseCoords={mouseCoords} className='absolute top-1 right-2' /> : null} {showCoordinates ? <CoordinateDisplay mouseCoords={mouseCoords} className='absolute top-1 right-2' /> : null}
<ToolbarOssGraph <ToolbarOssGraph
className='absolute z-pop top-8 right-1/2 translate-x-1/2' className='absolute z-pop top-8 right-1/2 translate-x-1/2'
@ -203,43 +195,25 @@ export function OssFlow() {
<ContextMenu isOpen={isContextMenuOpen} onHide={hideContextMenu} {...menuProps} /> <ContextMenu isOpen={isContextMenuOpen} onHide={hideContextMenu} {...menuProps} />
<div <DiagramFlow
className={clsx( {...flowOptions}
'relative w-[100vw] cc-mask-sides', className={clsx(!containMovement && 'cursor-relocate')}
spacePressed ? 'space-mode' : '', height={mainHeight}
!containMovement && 'cursor-relocate' onKeyDown={handleKeyDown}
)}
style={{ height: mainHeight, fontFamily: 'Rubik' }}
>
<ReactFlow
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
onNodesChange={spacePressed ? undefined : onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={spacePressed ? undefined : onEdgesChange} onEdgesChange={onEdgesChange}
edgesFocusable={false}
nodesFocusable={false}
fitView
nodeTypes={OssNodeTypes} nodeTypes={OssNodeTypes}
maxZoom={ZOOM_MAX} showGrid={showGrid}
minZoom={ZOOM_MIN}
nodesConnectable={false}
snapToGrid={true}
snapGrid={[GRID_SIZE, GRID_SIZE]}
onClick={hideContextMenu} onClick={hideContextMenu}
onNodeDoubleClick={spacePressed ? undefined : handleNodeDoubleClick} onNodeDoubleClick={handleNodeDoubleClick}
onNodeContextMenu={handleContextMenu} onNodeContextMenu={handleContextMenu}
onContextMenu={event => { onContextMenu={hideContextMenu}
event.preventDefault(); onNodeDragStart={handleDragStart}
hideContextMenu(); onNodeDrag={handleDrag}
}} onNodeDragStop={handleDragStop}
nodesDraggable={!spacePressed} />
onNodeDragStart={spacePressed ? undefined : handleDragStart}
onNodeDrag={spacePressed ? undefined : handleDrag}
onNodeDragStop={spacePressed ? undefined : handleDragStop}
>
{showGrid ? <Background gap={GRID_SIZE} /> : null}
</ReactFlow>
</div>
</div> </div>
); );
} }

View File

@ -63,6 +63,9 @@ export function useDragging({ hideContextMenu }: DraggingProps) {
if (containMovement) { if (containMovement) {
applyContainMovement([target.id, ...selected.map(id => String(id))], false); applyContainMovement([target.id, ...selected.map(id => String(id))], false);
} else { } else {
event.preventDefault();
event.stopPropagation();
const new_parent = dropTarget.evaluate(event); const new_parent = dropTarget.evaluate(event);
const allSelected = [...selected.filter(id => id != Number(target.id)), Number(target.id)]; const allSelected = [...selected.filter(id => id != Number(target.id)), Number(target.id)];
const operations = allSelected const operations = allSelected

View File

@ -1,7 +1,9 @@
'use client'; 'use client';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { type Edge, MarkerType, type Node, ReactFlow, useEdgesState, useNodesState } from 'reactflow'; import { type Edge, MarkerType, type Node, useEdgesState, useNodesState } from 'reactflow';
import { DiagramFlow } from '@/components/flow/diagram-flow';
import { type SyntaxTree } from '../../models/rslang'; import { type SyntaxTree } from '../../models/rslang';
@ -9,6 +11,16 @@ import { ASTEdgeTypes } from './graph/ast-edge-types';
import { applyLayout } from './graph/ast-layout'; import { applyLayout } from './graph/ast-layout';
import { ASTNodeTypes } from './graph/ast-node-types'; import { ASTNodeTypes } from './graph/ast-node-types';
const flowOptions = {
fitView: true,
fitViewOptions: { padding: 0.25 },
edgesFocusable: false,
nodesFocusable: false,
nodesConnectable: false,
maxZoom: 2,
minZoom: 0.5
} as const;
interface ASTFlowProps { interface ASTFlowProps {
data: SyntaxTree; data: SyntaxTree;
onNodeEnter: (node: Node) => void; onNodeEnter: (node: Node) => void;
@ -53,11 +65,11 @@ export function ASTFlow({ data, onNodeEnter, onNodeLeave, onChangeDragging }: AS
}, [data, setNodes, setEdges]); }, [data, setNodes, setEdges]);
return ( return (
<ReactFlow <DiagramFlow
{...flowOptions}
className='h-full w-full'
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
edgesFocusable={false}
nodesFocusable={false}
onNodeMouseEnter={(_, node) => onNodeEnter(node)} onNodeMouseEnter={(_, node) => onNodeEnter(node)}
onNodeMouseLeave={(_, node) => onNodeLeave(node)} onNodeMouseLeave={(_, node) => onNodeLeave(node)}
onNodeDragStart={() => onChangeDragging(true)} onNodeDragStart={() => onChangeDragging(true)}
@ -65,11 +77,6 @@ export function ASTFlow({ data, onNodeEnter, onNodeLeave, onChangeDragging }: AS
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
nodeTypes={ASTNodeTypes} nodeTypes={ASTNodeTypes}
edgeTypes={ASTEdgeTypes} edgeTypes={ASTEdgeTypes}
fitView
maxZoom={2}
minZoom={0.5}
nodesConnectable={false}
onContextMenu={event => event.preventDefault()}
/> />
); );
} }

View File

@ -34,7 +34,9 @@ export function ASTNode(node: ASTNodeInternal) {
<Handle type='source' position={Position.Bottom} className='opacity-0' /> <Handle type='source' position={Position.Bottom} className='opacity-0' />
<div <div
className={clsx( className={clsx(
'font-math mt-1 w-fit text-center translate-x-[calc(-50%+20px)]', 'mt-[4px] w-fit translate-x-[calc(-50%+20px)]',
'font-math text-center ',
'pointer-events-none',
label.length > LABEL_THRESHOLD ? 'text-[12px]/[16px]' : 'text-[14px]/[20px]' label.length > LABEL_THRESHOLD ? 'text-[12px]/[16px]' : 'text-[14px]/[20px]'
)} )}
> >

View File

@ -1,7 +1,9 @@
'use client'; 'use client';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { type Edge, ReactFlow, useEdgesState, useNodesState } from 'reactflow'; import { type Edge, useEdgesState, useNodesState } from 'reactflow';
import { DiagramFlow } from '@/components/flow/diagram-flow';
import { type TypificationGraph } from '../../models/typification-graph'; import { type TypificationGraph } from '../../models/typification-graph';
@ -9,8 +11,15 @@ import { TMGraphEdgeTypes } from './graph/mgraph-edge-types';
import { applyLayout } from './graph/mgraph-layout'; import { applyLayout } from './graph/mgraph-layout';
import { TMGraphNodeTypes } from './graph/mgraph-node-types'; import { TMGraphNodeTypes } from './graph/mgraph-node-types';
const ZOOM_MAX = 2; const flowOptions = {
const ZOOM_MIN = 0.5; fitView: true,
fitViewOptions: { padding: 0.25 },
edgesFocusable: false,
nodesFocusable: false,
nodesConnectable: false,
maxZoom: 2,
minZoom: 0.5
} as const;
interface MGraphFlowProps { interface MGraphFlowProps {
data: TypificationGraph; data: TypificationGraph;
@ -57,19 +66,14 @@ export function MGraphFlow({ data }: MGraphFlowProps) {
}, [data, setNodes, setEdges]); }, [data, setNodes, setEdges]);
return ( return (
<ReactFlow <DiagramFlow
{...flowOptions}
className='h-full w-full'
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
edgesFocusable={false}
nodesFocusable={false}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
nodeTypes={TMGraphNodeTypes} nodeTypes={TMGraphNodeTypes}
edgeTypes={TMGraphEdgeTypes} edgeTypes={TMGraphEdgeTypes}
fitView
maxZoom={ZOOM_MAX}
minZoom={ZOOM_MIN}
nodesConnectable={false}
onContextMenu={event => event.preventDefault()}
/> />
); );
} }

View File

@ -54,7 +54,7 @@ export function TGNode(node: TGNodeInternal) {
<div <div
className={clsx( className={clsx(
'w-full h-full cursor-default flex items-center justify-center rounded-full', 'w-full h-full cursor-default flex items-center justify-center rounded-full',
isFocused && 'border-2 border-selected', isFocused && 'border-[2px] border-selected',
label.length > LABEL_THRESHOLD ? 'text-[12px]/[16px]' : 'text-[14px]/[20px]' label.length > LABEL_THRESHOLD ? 'text-[12px]/[16px]' : 'text-[14px]/[20px]'
)} )}
style={{ style={{
@ -76,13 +76,14 @@ export function TGNode(node: TGNodeInternal) {
{description ? ( {description ? (
<div <div
className={clsx( className={clsx(
'mt-1 w-[150px] px-1 text-center translate-x-[calc(-50%+20px)]', 'mt-[4px] w-[150px] px-[4px] text-center translate-x-[calc(-50%+20px)]',
'pointer-events-none',
description.length > DESCRIPTION_THRESHOLD ? 'text-[10px]/[12px]' : 'text-[12px]/[16px]' description.length > DESCRIPTION_THRESHOLD ? 'text-[10px]/[12px]' : 'text-[12px]/[16px]'
)} )}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
onDoubleClick={handleDoubleClick} onDoubleClick={handleDoubleClick}
> >
<div className='absolute top-0 px-1 left-0 text-center w-full line-clamp-3 hover:line-clamp-none'> <div className='absolute top-0 px-[4px] left-0 text-center w-full line-clamp-3 hover:line-clamp-none'>
{description} {description}
</div> </div>
<div aria-hidden className='line-clamp-3 hover:line-clamp-none cc-text-outline'> <div aria-hidden className='line-clamp-3 hover:line-clamp-none cc-text-outline'>

View File

@ -1,18 +1,9 @@
'use client'; 'use client';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { import { type Edge, MarkerType, type Node, useEdgesState, useNodesState, useOnSelectionChange } from 'reactflow';
type Edge,
MarkerType,
type Node,
ReactFlow,
useEdgesState,
useNodesState,
useOnSelectionChange,
useReactFlow,
useStoreApi
} from 'reactflow';
import { DiagramFlow, useReactFlow, useStoreApi } from '@/components/flow/diagram-flow';
import { useMainHeight } from '@/stores/app-layout'; import { useMainHeight } from '@/stores/app-layout';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
@ -32,9 +23,15 @@ import { ToolbarTermGraph } from './toolbar-term-graph';
import { useFilteredGraph } from './use-filtered-graph'; import { useFilteredGraph } from './use-filtered-graph';
import { ViewHidden } from './view-hidden'; import { ViewHidden } from './view-hidden';
const ZOOM_MAX = 3; export const flowOptions = {
const ZOOM_MIN = 0.25; fitView: true,
export const VIEW_PADDING = 0.3; fitViewOptions: { padding: 0.3, duration: PARAMETER.zoomDuration },
edgesFocusable: false,
nodesFocusable: false,
nodesConnectable: false,
maxZoom: 3,
minZoom: 0.25
} as const;
export function TGFlow() { export function TGFlow() {
const mainHeight = useMainHeight(); const mainHeight = useMainHeight();
@ -116,9 +113,7 @@ export function TGFlow() {
setNodes(newNodes); setNodes(newNodes);
setEdges(newEdges); setEdges(newEdges);
setTimeout(() => { setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.minimalTimeout);
fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING });
}, PARAMETER.minimalTimeout);
}, [schema, filteredGraph, setNodes, setEdges, filter.noText, fitView, viewportInitialized, focusCst]); }, [schema, filteredGraph, setNodes, setEdges, filter.noText, fitView, viewportInitialized, focusCst]);
const prevSelected = useRef<number[]>([]); const prevSelected = useRef<number[]>([]);
@ -190,22 +185,16 @@ export function TGFlow() {
<ViewHidden items={hidden} /> <ViewHidden items={hidden} />
</div> </div>
<div className='relative outline-hidden w-[100dvw] cc-mask-sides' style={{ height: mainHeight }}> <DiagramFlow
<ReactFlow {...flowOptions}
height={mainHeight}
nodes={nodes} nodes={nodes}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
edges={edges} edges={edges}
fitView
edgesFocusable={false}
nodesFocusable={false}
nodesConnectable={false}
nodeTypes={TGNodeTypes} nodeTypes={TGNodeTypes}
edgeTypes={TGEdgeTypes} edgeTypes={TGEdgeTypes}
maxZoom={ZOOM_MAX}
minZoom={ZOOM_MIN}
onContextMenu={event => event.preventDefault()} onContextMenu={event => event.preventDefault()}
/> />
</div> </div>
</div>
); );
} }

View File

@ -25,7 +25,7 @@ import { useMutatingRSForm } from '../../../backend/use-mutating-rsform';
import { useTermGraphStore } from '../../../stores/term-graph'; import { useTermGraphStore } from '../../../stores/term-graph';
import { useRSEdit } from '../rsedit-context'; import { useRSEdit } from '../rsedit-context';
import { VIEW_PADDING } from './tg-flow'; import { flowOptions } from './tg-flow';
export function ToolbarTermGraph() { export function ToolbarTermGraph() {
const isProcessing = useMutatingRSForm(); const isProcessing = useMutatingRSForm();
@ -76,7 +76,7 @@ export function ToolbarTermGraph() {
function handleFitView() { function handleFitView() {
setTimeout(() => { setTimeout(() => {
fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }); fitView(flowOptions.fitViewOptions);
}, PARAMETER.minimalTimeout); }, PARAMETER.minimalTimeout);
} }