From 54b724921e6670b74d6b89d69a4ed1d959393fbe Mon Sep 17 00:00:00 2001 From: KarimTamani Date: Fri, 23 May 2025 15:12:28 +0100 Subject: [PATCH] Improved performence for database diagram --- src/hooks/use-relationship-to-edge.tsx | 19 ++--------- .../database/database-control-buttons.tsx | 2 +- src/pages/database/database-page.tsx | 22 ++++++------ .../relationship-controller.tsx | 1 + .../tables-controller/tables-controller.tsx | 2 -- src/pages/database/table/field.tsx | 34 ++++++++++--------- src/pages/database/table/relationship.tsx | 9 +++-- src/pages/database/table/table.tsx | 12 ++----- .../diagram-provider/diagram-context.tsx | 5 +-- .../diagram-provider/diagram-provider.tsx | 9 ++--- src/styles/globals.css | 3 +- 11 files changed, 49 insertions(+), 69 deletions(-) diff --git a/src/hooks/use-relationship-to-edge.tsx b/src/hooks/use-relationship-to-edge.tsx index 65e24d2..3a28afc 100644 --- a/src/hooks/use-relationship-to-edge.tsx +++ b/src/hooks/use-relationship-to-edge.tsx @@ -3,12 +3,12 @@ import { RelationshipType } from "@/lib/schemas/relationship-schema"; import { LEFT_PREFIX, TARGET_PREFIX } from "@/pages/database/table/field"; import { Edge, useReactFlow } from "@xyflow/react"; import { useEffect } from "react"; -import hash from 'object-hash'; + export const useRelationshipToEdge = (relationships: RelationshipType[]): void => { const { setEdges } = useReactFlow(); useEffect(() => { - const relationshipEdges = relationships.map((relationship: RelationshipType) => { + const edges = relationships.map((relationship: RelationshipType) => { return { id: relationship.id, source: relationship.sourceTableId, @@ -23,20 +23,7 @@ export const useRelationshipToEdge = (relationships: RelationshipType[]): void = } as Edge }) - - setEdges((edges: any) => { - - return relationshipEdges.map((relationshipEdge: any) => { - const edge = edges.find((edge: Edge) => edge.id == relationshipEdge.id); - if (!edge) - return relationshipEdge; - - const relationshipEdgeHash: string = hash(relationshipEdge); - const edgeHash: string = hash(edge); - - return relationshipEdgeHash == edgeHash ? edge : relationshipEdge; - }) - }); + setEdges(edges); }, [relationships]) } \ No newline at end of file diff --git a/src/pages/database/database-control-buttons.tsx b/src/pages/database/database-control-buttons.tsx index a0dbc6a..27a7a8c 100644 --- a/src/pages/database/database-control-buttons.tsx +++ b/src/pages/database/database-control-buttons.tsx @@ -52,7 +52,7 @@ const DatabaseControlButtons: React.FC = ({ adjustPositions }) }, []); return ( - = () => { @@ -78,7 +78,7 @@ const DatabasePage: React.FC = () => { change.type == "position" && !change.dragging ) as NodePositionChange[]; - + const nodeRemoveChanges: NodeRemoveChange[] = changes.filter((change: NodeChange) => change.type == "remove"); if (nodePositionChanges.length > 0) @@ -120,22 +120,22 @@ const DatabasePage: React.FC = () => { useEffect(() => { - setEdges((edges: any) => { return edges.map((edge: any) => { const selected: boolean = selectedEdgeIds.includes(edge.id); return (edge.animated == selected) ? edge : { ...edge, animated: selected } as Edge; }) }); + }, [selectedEdgeIds]); + useEffect(() => { setNodes((nodes: any) => { return nodes.map((node: any) => { - - const nodeHighlitedEdges: Edge[] = node.data.highlightedEdges; - const newHighlitedEdges: Edge[] = edges.filter((edge: Edge) => selectedEdgeIds.includes(edge.id) && (edge.source == node.id || edge.target == node.id)); - console.log(nodeHighlitedEdges, newHighlitedEdges); - + const newHighlitedEdges: Edge[] = edges.filter((edge: Edge) => + (edge.animated || edge.selected) + && (edge.source == node.id || edge.target == node.id) + ); return { ...node, data: { @@ -145,7 +145,7 @@ const DatabasePage: React.FC = () => { } }) }) - }, [selectedEdgeIds]); + }, [edges]); const onConnectStart = useCallback(() => { setIsConnectionInProgress(true); @@ -262,7 +262,7 @@ const DatabasePage: React.FC = () => { /> - +
= ({ }) => { = ({ }) => { - const { database } = useDatabase(); const {createTable} = useDatabaseOperations() ; const { tables } = database ; @@ -24,7 +23,6 @@ const TablesController: React.FC = ({ }) => { const [selectedTable, setSelectedTable] = useState(new Set([])); const { focusedTableId } = useDiagram(); - const addNewTable = useCallback(async () => { const newTableId: string = v4(); diff --git a/src/pages/database/table/field.tsx b/src/pages/database/table/field.tsx index e55d45c..fa2196e 100644 --- a/src/pages/database/table/field.tsx +++ b/src/pages/database/table/field.tsx @@ -1,19 +1,18 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"; import { FieldType } from "@/lib/schemas/field-schema"; import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider"; -import { useDiagram } from "@/providers/diagram-provider/diagram-provider"; +import { useDiagram, useDiagramOps } from "@/providers/diagram-provider/diagram-provider"; import { Button, cn, select } from "@heroui/react"; import { Handle, Position, useConnection } from "@xyflow/react"; import { Check, KeyRound, Trash, Trash2 } from "lucide-react"; -import React, { useEffect, useState } from "react"; - +import React, { useCallback, useEffect, useState } from "react"; +import hash from 'object-hash'; interface Props { field: FieldType, showHandles?: boolean, highlight?: boolean, - showTargetHandle?: boolean } @@ -22,30 +21,31 @@ export const RIGHT_PREFIX = "right_"; export const TARGET_PREFIX = "target_"; -const Field: React.FC = ({ field, showHandles, highlight }) => { - +const Field: React.FC = (props) => { + const { field, showHandles, highlight } = props; const [editMode, setEditMode] = useState(false); const { deleteField, editField } = useDatabaseOperations(); const [fieldName, setFieldName] = useState(field.name); - const { isConnectionInProgress } = useDiagram(); + const { isConnectionInProgress } = useDiagramOps(); + useEffect(() => { setFieldName(field.name); }, [field.name]); - const removeField = () => { + const removeField = useCallback(() => { deleteField(field.id) - } + }, []) - const saveFieldName = () => { + const saveFieldName = useCallback(() => { editField({ id: field.id, name: fieldName } as FieldType); setEditMode(false); - } + }, [fieldName]) - // console.log("render field ", field.name) + return (
= ({ field, showHandles, highlight }) => { )} onDoubleClick={() => setEditMode(true)} > - {field.name} + {fieldName} = ({ field, showHandles, highlight }) => { type="source" position={Position.Left} id={LEFT_PREFIX + field.id} - className="w-4 h-4 border-3 bg-primary dark:border-default-900" + className="w-4 h-4 border-4 bg-primary dark:border-default-900" />
@@ -154,4 +154,6 @@ const Field: React.FC = ({ field, showHandles, highlight }) => { } -export default React.memo(Field); \ No newline at end of file +export default React.memo(Field, (previousState, newState) => { + return hash(previousState) == hash(newState); +}); \ No newline at end of file diff --git a/src/pages/database/table/relationship.tsx b/src/pages/database/table/relationship.tsx index bdf940d..e6ee7db 100644 --- a/src/pages/database/table/relationship.tsx +++ b/src/pages/database/table/relationship.tsx @@ -4,9 +4,9 @@ import { Cardinality, RelationshipType } from "@/lib/schemas/relationship-schema import { useDiagram, useDiagramOps } from "@/providers/diagram-provider/diagram-provider"; import { card, cn } from "@heroui/react"; import { Edge, EdgeProps, getBezierPath, getSmoothStepPath, InternalNode, Node, Position, useReactFlow } from "@xyflow/react"; -import React, { useMemo } from "react"; - +import React, { useEffect, useMemo } from "react"; +import hash from 'object-hash'; export type RelationshipProps = Edge<{ @@ -113,7 +113,6 @@ const Relationship: React.FC> = (props) => { } }, [data?.relationship.cardinality, selected]); - // console.log ("render relationship " , props.data?.relationship.sourceTable.name , props.data?.relationship.targetTable.name) return ( <> @@ -125,9 +124,9 @@ const Relationship: React.FC> = (props) => { markerEnd={`url(#${endMarker})`} fill="none" className={cn([ - + `!stroke-2 ${selected ? '!stroke-primary' : 'stroke-slate-300 dark:stroke-default-500'}`, - + ])} onClick={(e) => { diff --git a/src/pages/database/table/table.tsx b/src/pages/database/table/table.tsx index 9f6d546..54b928b 100644 --- a/src/pages/database/table/table.tsx +++ b/src/pages/database/table/table.tsx @@ -35,7 +35,6 @@ const Table: React.FC> = ({ selected, data: { table, overl const [tableName, setTableName] = useState(table.name); const { editTable } = useDatabaseOperations(); - // const edges = useGetRelatedEdges(table.id as string); const { focusOnTable } = useDiagramOps(); const { t } = useTranslation(); @@ -51,11 +50,7 @@ const Table: React.FC> = ({ selected, data: { table, overl const focus = useCallback(() => { focusOnTable(table.id, false); }, [table]) - /* - const highlightedEdges: Edge[] = useMemo(() => { - return edges.filter((edge: Edge) => edge.animated || edge.selected); - }, [edges]); - */ + const fields: React.ReactNode[] = useMemo(() => { @@ -74,7 +69,7 @@ const Table: React.FC> = ({ selected, data: { table, overl }, [table.fields, selected, highlightedEdges]); - console.log ("re-render " , table.name) + return ( { const previousStateHash: string = hash(previousState.data); const newStateHash: string = hash(newState.data); - - - return previousStateHash == newStateHash && previousState.selected == newState.selected; }) diff --git a/src/providers/diagram-provider/diagram-context.tsx b/src/providers/diagram-provider/diagram-context.tsx index 9260818..532430c 100644 --- a/src/providers/diagram-provider/diagram-context.tsx +++ b/src/providers/diagram-provider/diagram-context.tsx @@ -4,13 +4,14 @@ import { createContext, Dispatch, SetStateAction } from "react"; interface DiagramDataContextType { focusedTableId: string | undefined; focusedRelationshipId: string | undefined; - isConnectionInProgress: boolean } interface DiagramOpsContextType { focusOnTable: (id: string, transition?: boolean) => void, focusOnRelationship: (id: string, transition?: boolean) => void, - setIsConnectionInProgress: Dispatch + setIsConnectionInProgress: Dispatch, + isConnectionInProgress: boolean + } diff --git a/src/providers/diagram-provider/diagram-provider.tsx b/src/providers/diagram-provider/diagram-provider.tsx index 72bdf89..6af0ef3 100644 --- a/src/providers/diagram-provider/diagram-provider.tsx +++ b/src/providers/diagram-provider/diagram-provider.tsx @@ -76,15 +76,16 @@ const DiagramProvider: React.FC = ({ children }) => { const contextDatatValue = useMemo(() => ({ focusedTableId, focusedRelationshipId, - isConnectionInProgress, - }), [focusedTableId, focusedRelationshipId, isConnectionInProgress,]); + + }), [focusedTableId, focusedRelationshipId,]); const contextOpsValues = useMemo(() => ({ focusOnTable, focusOnRelationship, - setIsConnectionInProgress + setIsConnectionInProgress, + isConnectionInProgress, - }), [focusOnTable, focusOnRelationship, setIsConnectionInProgress]) + }), [focusOnTable, focusOnRelationship, setIsConnectionInProgress, isConnectionInProgress]) return (