From 7f0c0fd70e08ff833c2992189a12a2236e5bc15a Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Wed, 30 Apr 2025 01:03:54 +0300 Subject: [PATCH] F: Improve diagram flow management --- .../src/components/flow/diagram-flow.tsx | 100 ++++++++++++++++++ .../editor-oss-graph/graph/block-node.tsx | 18 ++-- .../editor-oss-graph/graph/node-core.tsx | 4 +- .../editor-oss-graph/oss-flow-state.tsx | 7 +- .../oss-page/editor-oss-graph/oss-flow.tsx | 94 ++++++---------- .../editor-oss-graph/use-dragging.tsx | 3 + .../rsform/dialogs/dlg-show-ast/ast-flow.tsx | 25 +++-- .../dialogs/dlg-show-ast/graph/ast-node.tsx | 4 +- .../dlg-show-type-graph/mgraph-flow.tsx | 26 +++-- .../editor-term-graph/graph/tg-node.tsx | 7 +- .../rsform-page/editor-term-graph/tg-flow.tsx | 55 ++++------ .../editor-term-graph/toolbar-term-graph.tsx | 4 +- 12 files changed, 213 insertions(+), 134 deletions(-) create mode 100644 rsconcept/frontend/src/components/flow/diagram-flow.tsx diff --git a/rsconcept/frontend/src/components/flow/diagram-flow.tsx b/rsconcept/frontend/src/components/flow/diagram-flow.tsx new file mode 100644 index 00000000..830baad4 --- /dev/null +++ b/rsconcept/frontend/src/components/flow/diagram-flow.tsx @@ -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) => void; + onKeyUp?: (event: React.KeyboardEvent) => 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) { + if (event.code === 'Space') { + event.preventDefault(); + event.stopPropagation(); + setSpaceMode(true); + } + onKeyDown?.(event); + } + + function handleKeyUp(event: React.KeyboardEvent) { + if (event.code === 'Space') { + setSpaceMode(false); + } + onKeyUp?.(event); + } + + function handleContextMenu(event: React.MouseEvent) { + event.preventDefault(); + onContextMenu?.(event); + } + + return ( +
+ + {showGrid ? : null} + {children} + +
+ ); +} diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx index 272f7a7e..be20a380 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx @@ -28,12 +28,12 @@ export function BlockNode(node: BlockInternalNode) { return ( <> - + {showCoordinates ? (
-
-
-
-
+
+
+
+
setHover(node.data.operation)} > -
+
{ setNodes(newNodes); setEdges(newEdges); - setTimeout(() => fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }), PARAMETER.refreshTimeout); + setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout); }, [schema, setNodes, setEdges, edgeAnimate, edgeStraight, fitView]); useEffect(() => { @@ -94,7 +93,7 @@ export const OssFlowState = ({ children }: React.PropsWithChildren) => { }, [schema, edgeAnimate, edgeStraight, resetGraph]); function resetView() { - setTimeout(() => fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }), PARAMETER.refreshTimeout); + setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.refreshTimeout); } return ( diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx index 42a6e359..c7d9b66e 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx @@ -1,11 +1,12 @@ 'use client'; import { useState } from 'react'; -import { Background, ReactFlow, useReactFlow, useStoreApi } from 'reactflow'; import clsx from 'clsx'; +import { DiagramFlow, useReactFlow, useStoreApi } from '@/components/flow/diagram-flow'; import { useMainHeight } from '@/stores/app-layout'; import { useDialogsStore } from '@/stores/dialogs'; +import { PARAMETER } from '@/utils/constants'; import { promptText } from '@/utils/labels'; import { useDeleteBlock } from '../../../backend/use-delete-block'; @@ -25,8 +26,18 @@ import { ToolbarOssGraph } from './toolbar-oss-graph'; import { useDragging } from './use-dragging'; import { useGetLayout } from './use-get-layout'; -const ZOOM_MAX = 2; -const ZOOM_MIN = 0.5; +export const flowOptions = { + 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() { const mainHeight = useMainHeight(); @@ -46,7 +57,6 @@ export function OssFlow() { const { deleteBlock } = useDeleteBlock(); const [mouseCoords, setMouseCoords] = useState({ x: 0, y: 0 }); - const [spacePressed, setSpacePressed] = useState(false); const showCreateOperation = useDialogsStore(state => state.showCreateOperation); const showCreateBlock = useDialogsStore(state => state.showCreateBlock); @@ -131,12 +141,6 @@ export function OssFlow() { } function handleKeyDown(event: React.KeyboardEvent) { - if (event.code === 'Space') { - event.preventDefault(); - event.stopPropagation(); - setSpacePressed(true); - return; - } if (isProcessing) { return; } @@ -173,25 +177,13 @@ export function OssFlow() { } } - function handleKeyUp(event: React.KeyboardEvent) { - if (event.code === 'Space') { - setSpacePressed(false); - } - } - function handleMouseMove(event: React.MouseEvent) { const targetPosition = screenToFlowPosition({ x: event.clientX, y: event.clientY }); setMouseCoords(targetPosition); } return ( -
+
{showCoordinates ? : null} -
- { - event.preventDefault(); - hideContextMenu(); - }} - nodesDraggable={!spacePressed} - onNodeDragStart={spacePressed ? undefined : handleDragStart} - onNodeDrag={spacePressed ? undefined : handleDrag} - onNodeDragStop={spacePressed ? undefined : handleDragStop} - > - {showGrid ? : null} - -
+
); } diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-dragging.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-dragging.tsx index aae0110d..4d8e73be 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-dragging.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/use-dragging.tsx @@ -63,6 +63,9 @@ export function useDragging({ hideContextMenu }: DraggingProps) { if (containMovement) { applyContainMovement([target.id, ...selected.map(id => String(id))], false); } else { + event.preventDefault(); + event.stopPropagation(); + const new_parent = dropTarget.evaluate(event); const allSelected = [...selected.filter(id => id != Number(target.id)), Number(target.id)]; const operations = allSelected diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-ast/ast-flow.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-ast/ast-flow.tsx index f1854526..903048bb 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-ast/ast-flow.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-ast/ast-flow.tsx @@ -1,7 +1,9 @@ 'use client'; 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'; @@ -9,6 +11,16 @@ import { ASTEdgeTypes } from './graph/ast-edge-types'; import { applyLayout } from './graph/ast-layout'; 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 { data: SyntaxTree; onNodeEnter: (node: Node) => void; @@ -53,11 +65,11 @@ export function ASTFlow({ data, onNodeEnter, onNodeLeave, onChangeDragging }: AS }, [data, setNodes, setEdges]); return ( - onNodeEnter(node)} onNodeMouseLeave={(_, node) => onNodeLeave(node)} onNodeDragStart={() => onChangeDragging(true)} @@ -65,11 +77,6 @@ export function ASTFlow({ data, onNodeEnter, onNodeLeave, onChangeDragging }: AS onNodesChange={onNodesChange} nodeTypes={ASTNodeTypes} edgeTypes={ASTEdgeTypes} - fitView - maxZoom={2} - minZoom={0.5} - nodesConnectable={false} - onContextMenu={event => event.preventDefault()} /> ); } diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-ast/graph/ast-node.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-ast/graph/ast-node.tsx index 073f645e..f3917696 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-ast/graph/ast-node.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-ast/graph/ast-node.tsx @@ -34,7 +34,9 @@ export function ASTNode(node: ASTNodeInternal) {
LABEL_THRESHOLD ? 'text-[12px]/[16px]' : 'text-[14px]/[20px]' )} > diff --git a/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-type-graph/mgraph-flow.tsx b/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-type-graph/mgraph-flow.tsx index 84d26b09..1fec85c1 100644 --- a/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-type-graph/mgraph-flow.tsx +++ b/rsconcept/frontend/src/features/rsform/dialogs/dlg-show-type-graph/mgraph-flow.tsx @@ -1,7 +1,9 @@ 'use client'; 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'; @@ -9,8 +11,15 @@ import { TMGraphEdgeTypes } from './graph/mgraph-edge-types'; import { applyLayout } from './graph/mgraph-layout'; import { TMGraphNodeTypes } from './graph/mgraph-node-types'; -const ZOOM_MAX = 2; -const ZOOM_MIN = 0.5; +const flowOptions = { + fitView: true, + fitViewOptions: { padding: 0.25 }, + edgesFocusable: false, + nodesFocusable: false, + nodesConnectable: false, + maxZoom: 2, + minZoom: 0.5 +} as const; interface MGraphFlowProps { data: TypificationGraph; @@ -57,19 +66,14 @@ export function MGraphFlow({ data }: MGraphFlowProps) { }, [data, setNodes, setEdges]); return ( - event.preventDefault()} /> ); } diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-node.tsx b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-node.tsx index 689d97a1..9db73316 100644 --- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-node.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/graph/tg-node.tsx @@ -54,7 +54,7 @@ export function TGNode(node: TGNodeInternal) {
LABEL_THRESHOLD ? 'text-[12px]/[16px]' : 'text-[14px]/[20px]' )} style={{ @@ -76,13 +76,14 @@ export function TGNode(node: TGNodeInternal) { {description ? (
DESCRIPTION_THRESHOLD ? 'text-[10px]/[12px]' : 'text-[12px]/[16px]' )} onContextMenu={handleContextMenu} onDoubleClick={handleDoubleClick} > -
+
{description}
diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/tg-flow.tsx b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/tg-flow.tsx index 86de93ec..faeb6421 100644 --- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/tg-flow.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/tg-flow.tsx @@ -1,18 +1,9 @@ 'use client'; import { useEffect, useRef } from 'react'; -import { - type Edge, - MarkerType, - type Node, - ReactFlow, - useEdgesState, - useNodesState, - useOnSelectionChange, - useReactFlow, - useStoreApi -} from 'reactflow'; +import { type Edge, MarkerType, type Node, useEdgesState, useNodesState, useOnSelectionChange } from 'reactflow'; +import { DiagramFlow, useReactFlow, useStoreApi } from '@/components/flow/diagram-flow'; import { useMainHeight } from '@/stores/app-layout'; import { PARAMETER } from '@/utils/constants'; @@ -32,9 +23,15 @@ import { ToolbarTermGraph } from './toolbar-term-graph'; import { useFilteredGraph } from './use-filtered-graph'; import { ViewHidden } from './view-hidden'; -const ZOOM_MAX = 3; -const ZOOM_MIN = 0.25; -export const VIEW_PADDING = 0.3; +export const flowOptions = { + fitView: true, + fitViewOptions: { padding: 0.3, duration: PARAMETER.zoomDuration }, + edgesFocusable: false, + nodesFocusable: false, + nodesConnectable: false, + maxZoom: 3, + minZoom: 0.25 +} as const; export function TGFlow() { const mainHeight = useMainHeight(); @@ -116,9 +113,7 @@ export function TGFlow() { setNodes(newNodes); setEdges(newEdges); - setTimeout(() => { - fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }); - }, PARAMETER.minimalTimeout); + setTimeout(() => fitView(flowOptions.fitViewOptions), PARAMETER.minimalTimeout); }, [schema, filteredGraph, setNodes, setEdges, filter.noText, fitView, viewportInitialized, focusCst]); const prevSelected = useRef([]); @@ -190,22 +185,16 @@ export function TGFlow() {
-
- event.preventDefault()} - /> -
+ event.preventDefault()} + />
); } diff --git a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/toolbar-term-graph.tsx b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/toolbar-term-graph.tsx index 74c81352..dfd6a9bc 100644 --- a/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/toolbar-term-graph.tsx +++ b/rsconcept/frontend/src/features/rsform/pages/rsform-page/editor-term-graph/toolbar-term-graph.tsx @@ -25,7 +25,7 @@ import { useMutatingRSForm } from '../../../backend/use-mutating-rsform'; import { useTermGraphStore } from '../../../stores/term-graph'; import { useRSEdit } from '../rsedit-context'; -import { VIEW_PADDING } from './tg-flow'; +import { flowOptions } from './tg-flow'; export function ToolbarTermGraph() { const isProcessing = useMutatingRSForm(); @@ -76,7 +76,7 @@ export function ToolbarTermGraph() { function handleFitView() { setTimeout(() => { - fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }); + fitView(flowOptions.fitViewOptions); }, PARAMETER.minimalTimeout); }