mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 19:25:44 +00:00
Improved performence for database diagram
This commit is contained in:
@@ -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",
|
||||
}}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,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) => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -62,8 +62,7 @@ div[data-slot="content"] hr[role="separator"] {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Target the scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
|
||||
Reference in New Issue
Block a user