Improved performence for database diagram

This commit is contained in:
KarimTamani
2025-05-23 15:12:28 +01:00
parent 33c8047ea1
commit 54b724921e
11 changed files with 49 additions and 69 deletions
+3 -16
View File
@@ -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])
}
@@ -52,7 +52,7 @@ const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions })
}, []);
return (
<Navbar className="flex rounded-md border-1 border-default-200 bg-transparent dark:border-default-800 " isBlurred
<Navbar className="flex rounded-md border-1 border-default-200 bg-background dark:bg-transparent dark:border-default-800 " isBlurred
classNames={{
wrapper: "h-14 p-2 gap-1",
}}
+11 -11
View File
@@ -19,11 +19,11 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/to
import { addToast, Button, Image } from "@heroui/react";
import { AlertTriangle, LayoutGrid } from "lucide-react";
import { adjustTablesPositions } from "@/utils/tables";
import { useTheme } from "next-themes";
import DatabaseControlButtons from "./database-control-buttons";
import useGetOverlappingNodes from "@/hooks/use-get-overlapping-nodes";
import { FieldType } from "@/lib/schemas/field-schema";
import hash from "object-hash";
const DatabasePage: React.FC<never> = () => {
@@ -78,7 +78,7 @@ const DatabasePage: React.FC<never> = () => {
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<never> = () => {
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<never> = () => {
}
})
})
}, [selectedEdgeIds]);
}, [edges]);
const onConnectStart = useCallback(() => {
setIsConnectionInProgress(true);
@@ -262,7 +262,7 @@ const DatabasePage: React.FC<never> = () => {
/>
</Controls >
<Background />
<Background className="bg-default/40 dark:bg-black"/>
</ReactFlow>
<div
className="absolute left-[12px] top-[64px] "
@@ -82,6 +82,7 @@ const RelationshipController: React.FC<Props> = ({ }) => {
<Input
//ref={filterInputRef}
type="text"
size="sm"
radius="sm"
variant="bordered"
@@ -16,7 +16,6 @@ interface Props { }
const TablesController: React.FC<Props> = ({ }) => {
const { database } = useDatabase();
const {createTable} = useDatabaseOperations() ;
const { tables } = database ;
@@ -24,7 +23,6 @@ const TablesController: React.FC<Props> = ({ }) => {
const [selectedTable, setSelectedTable] = useState(new Set([]));
const { focusedTableId } = useDiagram();
const addNewTable = useCallback(async () => {
const newTableId: string = v4();
+18 -16
View File
@@ -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<Props> = ({ field, showHandles, highlight }) => {
const Field: React.FC<Props> = (props) => {
const { field, showHandles, highlight } = props;
const [editMode, setEditMode] = useState<boolean>(false);
const { deleteField, editField } = useDatabaseOperations();
const [fieldName, setFieldName] = useState<string>(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 (
<div className={cn(
@@ -62,7 +62,7 @@ const Field: React.FC<Props> = ({ field, showHandles, highlight }) => {
)}
onDoubleClick={() => setEditMode(true)}
>
{field.name}
{fieldName}
</label>
<span className={cn("content-center truncate flex items-center h-full gap-1 text-right text-xs text-default-600 group-hover:hidden font-semibold text-icon dark:text-default-200",
field.isPrimary ? "font-semibold text-slate-700" : ""
@@ -122,12 +122,12 @@ const Field: React.FC<Props> = ({ 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"
/>
<Handle
type="source"
position={Position.Right}
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"
id={RIGHT_PREFIX + field.id}
/>
</div>
@@ -154,4 +154,6 @@ const Field: React.FC<Props> = ({ field, showHandles, highlight }) => {
}
export default React.memo(Field);
export default React.memo(Field, (previousState, newState) => {
return hash(previousState) == hash(newState);
});
+4 -5
View File
@@ -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<EdgeProps<RelationshipProps>> = (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<EdgeProps<RelationshipProps>> = (props) => {
markerEnd={`url(#${endMarker})`}
fill="none"
className={cn([
`!stroke-2 ${selected ? '!stroke-primary' : 'stroke-slate-300 dark:stroke-default-500'}`,
])}
onClick={(e) => {
+2 -10
View File
@@ -35,7 +35,6 @@ const Table: React.FC<NodeProps<TableProps>> = ({ selected, data: { table, overl
const [tableName, setTableName] = useState<string>(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<NodeProps<TableProps>> = ({ 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<NodeProps<TableProps>> = ({ selected, data: { table, overl
}, [table.fields, selected, highlightedEdges]);
console.log ("re-render " , table.name)
return (
<Card className={cn(
@@ -170,8 +165,5 @@ export default React.memo(Table, (previousState: any, newState: any) => {
const previousStateHash: string = hash(previousState.data);
const newStateHash: string = hash(newState.data);
return previousStateHash == newStateHash && previousState.selected == newState.selected;
})
@@ -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<boolean>
setIsConnectionInProgress: Dispatch<boolean>,
isConnectionInProgress: boolean
}
@@ -76,15 +76,16 @@ const DiagramProvider: React.FC<Props> = ({ 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 (
<DiagramDataContext.Provider
value={contextDatatValue}
+1 -2
View File
@@ -62,8 +62,7 @@ div[data-slot="content"] hr[role="separator"] {
}
}
/* Target the scrollbar */
::-webkit-scrollbar {