mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 11:15:42 +00:00
Fix nodes re-render whene dragging
This commit is contained in:
+3
-1
@@ -19,8 +19,9 @@
|
||||
"@radix-ui/react-tooltip": "^1.2.3",
|
||||
"@react-aria/visually-hidden": "3.8.21",
|
||||
"@react-types/shared": "3.28.0",
|
||||
"@xyflow/react": "^12.6.0",
|
||||
"@xyflow/react": "^12.6.3",
|
||||
"drizzle-orm": "^0.43.1",
|
||||
"elkjs": "^0.10.0",
|
||||
"framer-motion": "11.15.0",
|
||||
"i18next-browser-languagedetector": "^8.0.5",
|
||||
"lucide-react": "^0.501.0",
|
||||
@@ -34,6 +35,7 @@
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dagre": "^0.7.52",
|
||||
"@types/node": "20.5.7",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
|
||||
+6
-3
@@ -5,6 +5,7 @@ import { ReactFlowProvider } from "@xyflow/react";
|
||||
import useAppRoutes from "./routes/app-route";
|
||||
import { SyncProvider } from "./providers/sync-provider/sync-provider";
|
||||
import DatabaseProvider from "./providers/database-provider/database-provider";
|
||||
import DiagramProvider from "./providers/diagram-provider/diagram-provider";
|
||||
|
||||
function App() {
|
||||
const appRoutes = useAppRoutes();
|
||||
@@ -12,9 +13,11 @@ function App() {
|
||||
<SyncProvider>
|
||||
<ReactFlowProvider>
|
||||
<DatabaseProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
{appRoutes}
|
||||
</TooltipProvider>
|
||||
<DiagramProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
{appRoutes}
|
||||
</TooltipProvider>
|
||||
</DiagramProvider>
|
||||
</DatabaseProvider>
|
||||
</ReactFlowProvider>
|
||||
</SyncProvider>
|
||||
|
||||
@@ -12,9 +12,9 @@ const sidebarItemClass : string = "text-gray-700 size-4 data-[active=true]:text-
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ }) => {
|
||||
const { t , i18n } = useTranslation() ;
|
||||
console.log ( i18n.language)
|
||||
|
||||
|
||||
console.log ()
|
||||
|
||||
const location = useLocation() ;
|
||||
|
||||
const sidebarItems: SidebarItemProps[] = useMemo(() => [
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useEffect } from "react";
|
||||
export const useTableToNode = (tables: TableType[]): void => {
|
||||
const { setNodes } = useReactFlow();
|
||||
useEffect(() => {
|
||||
|
||||
const nodes = tables.map((table: TableType) => {
|
||||
return {
|
||||
id: table.id,
|
||||
|
||||
+1
-18
@@ -15,24 +15,7 @@ export const colorOptions = [
|
||||
|
||||
];
|
||||
|
||||
/*
|
||||
|
||||
export const colorOptions = [
|
||||
'#ff6363', // A brighter red.
|
||||
'#ff6b8a', // A vibrant pink.
|
||||
'#c05dcf', // A rich purple.
|
||||
'#b067e9', // A lighter purple.
|
||||
'#8a61f5', // A bold indigo.
|
||||
'#7175fa', // A lighter indigo.
|
||||
'#8eb7ff', // A sky blue.
|
||||
'#42e0c0', // A fresh aqua.
|
||||
'#4dee8a', // A mint green.
|
||||
'#9ef07a', // A lime green.
|
||||
'#ffe374', // A warm yellow.
|
||||
'#ff9f74', // A peachy orange.
|
||||
];
|
||||
*/
|
||||
|
||||
|
||||
export const randomColor = () => {
|
||||
return colorOptions[Math.floor(Math.random() * colorOptions.length)];
|
||||
};
|
||||
@@ -1,39 +1,39 @@
|
||||
|
||||
import { addEdge, applyEdgeChanges, applyNodeChanges, Background, Connection, Controls, Edge, EdgeChange, EdgeRemoveChange, MiniMap, Node, NodeChange, NodePositionChange, NodeRemoveChange, NodeSelectionChange, OnEdgesChange, OnNodesChange, ReactFlow, useEdgesState, useNodesState, useReactFlow } from "@xyflow/react";
|
||||
import { SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { addEdge, applyNodeChanges, Background, Connection, Controls, Edge, EdgeChange, EdgeRemoveChange, MiniMap, Node, NodeChange, NodePositionChange, NodeRemoveChange, NodeSelectionChange, OnEdgesChange, OnNodesChange, ReactFlow, useEdgesState, useNodesState, useReactFlow } from "@xyflow/react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import Table from "./table/table";
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import Relationship from "./table/relationship";
|
||||
import DBController from "./db-controller/db-controller";
|
||||
import { TableInsertType, TableType } from "@/lib/schemas/table-schema";
|
||||
import { TableInsertType } from "@/lib/schemas/table-schema";
|
||||
import { useDatabase } from "@/providers/database-provider/database-provider";
|
||||
import { useTableToNode } from "@/hooks/use-table-to-node";
|
||||
import { useRelationshipToEdge } from "@/hooks/use-relationship-to-edge";
|
||||
import { RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { v4 } from "uuid";
|
||||
import { LEFT_PREFIX, TARGET_PREFIX } from "./table/field";
|
||||
import { TARGET_PREFIX } from "./table/field";
|
||||
import CardinalityMarker from "@/components/cardinality-marker/cardinality-marker";
|
||||
import { areArraysEqual } from "@/utils/utils";
|
||||
import { useDiagram } from "@/providers/diagram-provider/diagram-provider";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
|
||||
import { Button } from "@heroui/react";
|
||||
import { LayoutGrid } from "lucide-react";
|
||||
import { adjustTablesPositions } from "@/utils/tables";
|
||||
|
||||
|
||||
interface DatabaseProps {
|
||||
initialTables?: TableType
|
||||
}
|
||||
|
||||
const edgeTypes = {
|
||||
'relationship-edge': Relationship,
|
||||
};
|
||||
|
||||
const DatabasePage: React.FC<DatabaseProps> = ({ initialTables }) => {
|
||||
const DatabasePage: React.FC<never> = () => {
|
||||
|
||||
const { tables, relationships, updateTablePositions, deleteMultiTables, deleteMultiRelationships, createRelationship } = useDatabase();
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
|
||||
useTableToNode(tables);
|
||||
useRelationshipToEdge(relationships);
|
||||
const { setIsConnectionInProgress } = useDiagram();
|
||||
const [selectedNodeIds, setSelectedNodeIds] = useState<string[]>([]);
|
||||
const { fitView } = useReactFlow();
|
||||
|
||||
const nodeTypes = useMemo(() => ({ table: Table }), []);
|
||||
const edgeTypes = useMemo(() => ({ 'relationship-edge': Relationship }), []);
|
||||
|
||||
const onConnect = useCallback((connection: Connection) => {
|
||||
|
||||
createRelationship({
|
||||
@@ -45,8 +45,9 @@ const DatabasePage: React.FC<DatabaseProps> = ({ initialTables }) => {
|
||||
} as RelationshipInsertType)
|
||||
|
||||
setEdges((eds) => addEdge(connection, eds));
|
||||
setIsConnectionInProgress(false);
|
||||
|
||||
}, [setEdges]);
|
||||
}, []);
|
||||
|
||||
const handleNodesChanges: OnNodesChange<never> = useCallback((changes: NodeChange<never>[]) => {
|
||||
|
||||
@@ -60,75 +61,124 @@ const DatabasePage: React.FC<DatabaseProps> = ({ initialTables }) => {
|
||||
posY: change.position?.y
|
||||
} as TableInsertType)));
|
||||
|
||||
|
||||
if (nodeRemoveChanges.length > 0)
|
||||
deleteMultiTables(nodeRemoveChanges.map((change: NodeRemoveChange) => change.id));
|
||||
|
||||
onNodesChange(changes);
|
||||
return onNodesChange(changes);
|
||||
|
||||
}, [onNodesChange]);
|
||||
|
||||
|
||||
const handleEdgeChanges: OnEdgesChange<any> = useCallback((changes: EdgeChange<any>[]) => {
|
||||
|
||||
const edgeRemoveChanges: EdgeRemoveChange[] = changes.filter((change: EdgeChange) => change.type == "remove") as EdgeRemoveChange[];
|
||||
if (edgeRemoveChanges.length > 0) {
|
||||
deleteMultiRelationships(edgeRemoveChanges.map((change: EdgeRemoveChange) => change.id))
|
||||
}
|
||||
onEdgesChange(changes as EdgeChange<never>[]);
|
||||
return onEdgesChange(changes as EdgeChange<never>[]);
|
||||
}, [onEdgesChange]);
|
||||
|
||||
|
||||
const selectedTableIds: string[] = useMemo(() => {
|
||||
return nodes.filter((node: Node) => node.selected).map((node: Node) => node.id)
|
||||
useEffect(() => {
|
||||
const newSelectedNodesIds: string[] = nodes.filter((node: Node) => node.selected).map((node: Node) => node.id)
|
||||
if (areArraysEqual(newSelectedNodesIds, selectedNodeIds)) return; setSelectedNodeIds(newSelectedNodesIds);
|
||||
}, [nodes]);
|
||||
|
||||
const selectedRelationshipIds: string[] = useMemo(() => {
|
||||
return relationships.filter((relationship: RelationshipType) =>
|
||||
selectedTableIds.includes(relationship.sourceTableId) ||
|
||||
selectedTableIds.includes(relationship.targetTableId)
|
||||
).map((relationship: RelationshipType) => relationship.id);
|
||||
}, [selectedTableIds, relationships]);
|
||||
|
||||
|
||||
const selectedEdgeIds: string[] = useMemo(() => {
|
||||
return relationships.filter((relationship: RelationshipType) =>
|
||||
selectedNodeIds.includes(relationship.sourceTableId) ||
|
||||
selectedNodeIds.includes(relationship.targetTableId)
|
||||
).map((relationship: RelationshipType) => relationship.id);
|
||||
}, [selectedNodeIds, relationships]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setEdges((edges: any) => {
|
||||
return edges.map((edge: any) => {
|
||||
const selected: boolean = selectedRelationshipIds.includes(edge.id);
|
||||
const selected: boolean = selectedEdgeIds.includes(edge.id);
|
||||
return {
|
||||
...edge ,
|
||||
animated : selected ,
|
||||
|
||||
} as Edge
|
||||
...edge,
|
||||
animated: selected,
|
||||
} as Edge
|
||||
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
}, [setEdges, selectedRelationshipIds])
|
||||
return (
|
||||
<div className="w-full h-screen flex">
|
||||
}, [selectedEdgeIds]);
|
||||
|
||||
const onConnectStart = useCallback(() => {
|
||||
setIsConnectionInProgress(true);
|
||||
}, []);
|
||||
|
||||
|
||||
const onConnectEnd = useCallback(() => {
|
||||
setIsConnectionInProgress(false);
|
||||
}, []);
|
||||
|
||||
|
||||
const adjustPositions = useCallback(async () => {
|
||||
|
||||
updateTablePositions(await adjustTablesPositions(nodes, relationships));
|
||||
setTimeout(() => {
|
||||
fitView({
|
||||
duration: 500
|
||||
})
|
||||
}, 500)
|
||||
|
||||
}, [relationships, nodes])
|
||||
|
||||
useTableToNode(tables);
|
||||
useRelationshipToEdge(relationships);
|
||||
|
||||
return (
|
||||
|
||||
<div className="w-full h-screen flex relative">
|
||||
<DBController />
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
fitView
|
||||
className="w-full h-full nodes-animated"
|
||||
className="w-full h-full cursor-default"
|
||||
onNodesChange={handleNodesChanges}
|
||||
onEdgesChange={handleEdgeChanges}
|
||||
onConnect={onConnect}
|
||||
defaultEdgeOptions={{
|
||||
|
||||
type: 'relationship-edge',
|
||||
}}
|
||||
onlyRenderVisibleElements
|
||||
// onlyRenderVisibleElements
|
||||
panOnDrag={true}
|
||||
zoomOnScroll={true}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
snapGrid={[20, 20]}
|
||||
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectEnd={onConnectEnd}
|
||||
>
|
||||
<div className="absolute top-[160px]">
|
||||
|
||||
<Controls />
|
||||
</div>
|
||||
<Controls>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
variant="flat"
|
||||
className="size-8 p-1 shadow-none"
|
||||
onPressEnd={adjustPositions}
|
||||
|
||||
>
|
||||
<LayoutGrid className="size-4" />
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Adjust Positions
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
</Controls>
|
||||
<Background className="bg-default/30" />
|
||||
</ReactFlow>
|
||||
|
||||
@@ -149,6 +199,7 @@ const DatabasePage: React.FC<DatabaseProps> = ({ initialTables }) => {
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -1,6 +1,7 @@
|
||||
import { useRelationshipName } from "@/hooks/use-relationship-name";
|
||||
import { RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { useDatabase } from "@/providers/database-provider/database-provider";
|
||||
import { useDiagram } from "@/providers/diagram-provider/diagram-provider";
|
||||
import { Button, cn, Input, Listbox, ListboxItem, Popover, PopoverContent, PopoverTrigger } from "@heroui/react";
|
||||
import { Check, ChevronRight, EllipsisVertical, Focus, Pencil, Trash } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
@@ -21,7 +22,8 @@ const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> =
|
||||
const { editRelationship, deleteRelationship } = useDatabase();
|
||||
const { t } = useTranslation();
|
||||
const [popOverOpen, setPopOverOpen] = useState<boolean>(false);
|
||||
const [name, setName] = useState<string>(relationship.name ? relationship.name : defaultName)
|
||||
const [name, setName] = useState<string>(relationship.name ? relationship.name : defaultName);
|
||||
const { focusOnRelationship } = useDiagram();
|
||||
|
||||
|
||||
const editRelationshipName = () => {
|
||||
@@ -34,7 +36,7 @@ const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> =
|
||||
}
|
||||
|
||||
const onDeleteRelationship = () => {
|
||||
|
||||
|
||||
deleteRelationship(relationship.id);
|
||||
setPopOverOpen(false);
|
||||
}
|
||||
@@ -85,6 +87,7 @@ const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> =
|
||||
size="sm"
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPressEnd={() => focusOnRelationship(relationship.id, true)}
|
||||
>
|
||||
<Focus className="size-4 text-icon" />
|
||||
</Button>
|
||||
|
||||
+12
-2
@@ -1,7 +1,7 @@
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"
|
||||
import { Accordion, AccordionItem, Button, Input, useDisclosure } from "@heroui/react"
|
||||
import { Code, List, ListCollapse, Table, Workflow } from "lucide-react"
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import RelationshipAccordionHeader from "./relationship-accordion-item/relationship-accordion-header";
|
||||
import RelationshipAccordionBody from "./relationship-accordion-item/relationship-accordion-body";
|
||||
@@ -10,6 +10,7 @@ import CreateRelationshipForm from "./create-relationship-form/create-relationsh
|
||||
import { RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { useDatabase } from "@/providers/database-provider/database-provider";
|
||||
import { v4 } from "uuid";
|
||||
import { useDiagram } from "@/providers/diagram-provider/diagram-provider";
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +27,7 @@ const RelationshipController: React.FC<Props> = ({ }) => {
|
||||
const { createRelationship, relationships } = useDatabase();
|
||||
const { t } = useTranslation();
|
||||
const [selectedRelationship, setSelectedRelationship] = useState(new Set([]));
|
||||
const { focusedRelationshipId } = useDiagram();
|
||||
|
||||
const addRelationship = useCallback(() => {
|
||||
|
||||
@@ -41,6 +43,13 @@ const RelationshipController: React.FC<Props> = ({ }) => {
|
||||
}, [relationship]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (focusedRelationshipId) {
|
||||
setSelectedRelationship(new Set([focusedRelationshipId]) as any);
|
||||
}
|
||||
|
||||
}, [focusedRelationshipId])
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between gap-4 py-1">
|
||||
@@ -109,12 +118,13 @@ const RelationshipController: React.FC<Props> = ({ }) => {
|
||||
<AccordionItem
|
||||
key={relationship.id}
|
||||
aria-label={relationship.id}
|
||||
|
||||
classNames={{
|
||||
trigger: "w-full hover:bg-default transition-all duration-200 h-12",
|
||||
base: "rounded-md p-0 overflow-hidden",
|
||||
}}
|
||||
subtitle={
|
||||
<RelationshipAccordionHeader relationship={relationship} />
|
||||
<RelationshipAccordionHeader relationship={relationship} />
|
||||
}
|
||||
>
|
||||
<RelationshipAccordionBody relationship={relationship} />
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
|
||||
const { editTable, createField } = useDatabase();
|
||||
|
||||
const onColorChange = useCallback((color: string | undefined) => {
|
||||
console.log(color)
|
||||
|
||||
editTable({ id: table.id, color: color ? color : null } as TableType);
|
||||
}, [table]);
|
||||
|
||||
|
||||
+15
-12
@@ -9,6 +9,7 @@ import { TableType } from "@/lib/schemas/table-schema";
|
||||
import { useDatabase } from "@/providers/database-provider/database-provider";
|
||||
import { v4 } from "uuid";
|
||||
import { getNextSequence } from "@/utils/field";
|
||||
import { useDiagram } from "@/providers/diagram-provider/diagram-provider";
|
||||
|
||||
export interface TableAccordionHeaderProps {
|
||||
table: TableType,
|
||||
@@ -23,11 +24,11 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [editMode, setEditMode] = useState<boolean>(false);
|
||||
const { focusOnTable } = useDiagram();
|
||||
|
||||
|
||||
useEffect(()=> {
|
||||
setTableName(table.name) ;
|
||||
} , [table.name])
|
||||
useEffect(() => {
|
||||
setTableName(table.name);
|
||||
}, [table.name])
|
||||
|
||||
const saveTableName = async () => {
|
||||
await editTable({ id: table.id, name: tableName });
|
||||
@@ -51,7 +52,7 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
|
||||
nullable: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="group w-full flex h-12 gap-1 border-l-4 flex p-2 items-center border-l-[6px] "
|
||||
style={{
|
||||
@@ -114,13 +115,7 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
|
||||
!editMode && <>
|
||||
<div className="hidden shrink-0 flex-row group-hover:flex">
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
variant="light"
|
||||
>
|
||||
<Focus className="size-4 text-icon" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
@@ -129,6 +124,14 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
|
||||
>
|
||||
<Pencil className="size-4 text-icon" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPressEnd={() => focusOnTable(table.id , true)}
|
||||
>
|
||||
<Focus className="size-4 text-icon" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Popover placement="bottom" radius="sm" shadow="sm" showArrow isOpen={popOverOpen} onOpenChange={setPopOverOpen}>
|
||||
|
||||
@@ -7,8 +7,8 @@ import TableAccordionHeader from "./table-accordion-item/table-accordion-header"
|
||||
import TableAccordionBody from "./table-accordion-item/table-accordion-body";
|
||||
import { useDatabase } from "@/providers/database-provider/database-provider";
|
||||
import { TableType } from "@/lib/schemas/table-schema";
|
||||
import { v4 } from "uuid";
|
||||
import { randomColor } from "@/lib/colors";
|
||||
import { v4 } from "uuid";
|
||||
import { useDiagram } from "@/providers/diagram-provider/diagram-provider";
|
||||
|
||||
|
||||
interface Props { }
|
||||
@@ -20,6 +20,7 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
const { tables, createTable } = useDatabase();
|
||||
const { t } = useTranslation();
|
||||
const [selectedTable, setSelectedTable] = useState(new Set([]));
|
||||
const { focusedTableId } = useDiagram();
|
||||
|
||||
|
||||
const addNewTable = useCallback(async () => {
|
||||
@@ -35,7 +36,12 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
}, [tables]);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (focusedTableId) {
|
||||
setSelectedTable(new Set([focusedTableId]) as any);
|
||||
}
|
||||
} , [focusedTableId]) ;
|
||||
|
||||
const selectedTableId = selectedTable.values().next().value;
|
||||
|
||||
return (
|
||||
@@ -101,7 +107,7 @@ const TablesController: React.FC<Props> = ({ }) => {
|
||||
selectedKeys={selectedTable}
|
||||
onSelectionChange={setSelectedTable as any}
|
||||
isCompact
|
||||
|
||||
|
||||
>
|
||||
{tables.map((table: TableType) => (
|
||||
<AccordionItem
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
|
||||
import { FieldType } from "@/lib/schemas/field-schema";
|
||||
import { useDatabase } from "@/providers/database-provider/database-provider";
|
||||
import { useDiagram } 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";
|
||||
@@ -11,7 +12,8 @@ import React, { useEffect, useState } from "react";
|
||||
interface Props {
|
||||
field: FieldType,
|
||||
showHandles?: boolean,
|
||||
highlight?: boolean
|
||||
highlight?: boolean ,
|
||||
showTargetHandle? : boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +27,7 @@ const Field: React.FC<Props> = ({ field, showHandles, highlight }) => {
|
||||
const [editMode, setEditMode] = useState<boolean>(false);
|
||||
const { deleteField, editField } = useDatabase();
|
||||
const [fieldName, setFieldName] = useState<string>(field.name);
|
||||
|
||||
const {isConnectionInProgress} = useDiagram() ;
|
||||
|
||||
useEffect(() => {
|
||||
setFieldName(field.name);
|
||||
@@ -46,8 +48,8 @@ const Field: React.FC<Props> = ({ field, showHandles, highlight }) => {
|
||||
setEditMode(false);
|
||||
}
|
||||
|
||||
const connection = useConnection();
|
||||
console.log(highlight)
|
||||
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
"group relative flex h-8 items-center justify-between gap-1 border-t border-default px-3 text-sm last:rounded-b-[6px] hover:bg-slate-100 dark:hover:bg-slate-800 transition-all duration-200 ease-in-out" ,
|
||||
@@ -136,7 +138,7 @@ const Field: React.FC<Props> = ({ field, showHandles, highlight }) => {
|
||||
<div className={
|
||||
cn(
|
||||
"absolute w-full left-0 ",
|
||||
!connection.inProgress ? "invisible" : "visible"
|
||||
!isConnectionInProgress ? "invisible" : "visible"
|
||||
)} >
|
||||
<Handle
|
||||
id={`${TARGET_PREFIX}${field.id}`}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
|
||||
import { Cardinality, RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { useDiagram } 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";
|
||||
@@ -15,8 +16,9 @@ export type RelationshipProps = Edge<{
|
||||
|
||||
const Relationship: React.FC<EdgeProps<RelationshipProps>> = (props) => {
|
||||
|
||||
let { id, sourceX, sourceY, targetX, targetY, source, target, selected, data , animated } = props;
|
||||
let { id, sourceX, sourceY, targetX, targetY, source, target, selected, data, animated } = props;
|
||||
const { getInternalNode, getEdge } = useReactFlow();
|
||||
const { focusOnRelationship } = useDiagram();
|
||||
|
||||
|
||||
const sourceNode = getInternalNode(source);
|
||||
@@ -37,7 +39,7 @@ const Relationship: React.FC<EdgeProps<RelationshipProps>> = (props) => {
|
||||
const targetLeftX = targetX - 2;
|
||||
const targetRightX = targetX + targetWidth + 3;
|
||||
|
||||
|
||||
|
||||
|
||||
const { sourceSide, targetSide } = useMemo(() => {
|
||||
const distances = {
|
||||
@@ -122,14 +124,13 @@ const Relationship: React.FC<EdgeProps<RelationshipProps>> = (props) => {
|
||||
markerEnd={`url(#${endMarker})`}
|
||||
fill="none"
|
||||
className={cn([
|
||||
|
||||
`!stroke-2 ${selected ? '!stroke-primary' : 'stroke-slate-300'}`,
|
||||
|
||||
`!stroke-2 ${selected ? '!stroke-primary' : 'stroke-slate-300'}`,
|
||||
|
||||
])}
|
||||
onClick={(e) => {
|
||||
if (e.detail === 2) {
|
||||
console.log("hello world");
|
||||
// openRelationshipInEditor();
|
||||
focusOnRelationship(data?.relationship.id as string);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
@@ -147,7 +148,7 @@ const Relationship: React.FC<EdgeProps<RelationshipProps>> = (props) => {
|
||||
className="react-flow__edge-interaction"
|
||||
onClick={(e) => {
|
||||
if (e.detail === 2) {
|
||||
// openRelationshipInEditor();
|
||||
focusOnRelationship(data?.relationship.id as string);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -155,7 +156,7 @@ const Relationship: React.FC<EdgeProps<RelationshipProps>> = (props) => {
|
||||
</>)
|
||||
}
|
||||
|
||||
export default Relationship;
|
||||
export default React.memo(Relationship);
|
||||
|
||||
/*
|
||||
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
|
||||
|
||||
import { Edge, Handle, Node, NodeProps, NodeResizer, Position, useReactFlow, useStore } from "@xyflow/react";
|
||||
import { Edge, Node, NodeProps, useConnection, useStore } from "@xyflow/react";
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Card, CardBody, CardHeader, cn, Divider, Input } from "@heroui/react";
|
||||
import { Button, Card, cn, } from "@heroui/react";
|
||||
import {
|
||||
ChevronsLeftRight,
|
||||
ChevronsRightLeft,
|
||||
Table2,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Check,
|
||||
CircleDotDashed,
|
||||
SquareDot,
|
||||
SquarePlus,
|
||||
SquareMinus,
|
||||
Divide,
|
||||
Focus,
|
||||
} from 'lucide-react';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
|
||||
@@ -25,49 +16,63 @@ import { FieldType } from "@/lib/schemas/field-schema";
|
||||
import { TableType } from "@/lib/schemas/table-schema";
|
||||
import { useDatabase } from "@/providers/database-provider/database-provider";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { useDiagram } from "@/providers/diagram-provider/diagram-provider";
|
||||
|
||||
|
||||
export const MAX_TABLE_SIZE = 450;
|
||||
export const MID_TABLE_SIZE = 337;
|
||||
export const MIN_TABLE_SIZE = 224;
|
||||
export const TABLE_MINIMIZED_FIELDS = 10;
|
||||
|
||||
export type TableProps = Node<{
|
||||
table: TableType, isOverlapping?: boolean;
|
||||
highlightOverlappingTables?: boolean;
|
||||
table: TableType,
|
||||
}>
|
||||
|
||||
const Table: React.FC<NodeProps<TableProps>> = React.memo(({
|
||||
selected,
|
||||
dragging,
|
||||
id,
|
||||
data: { table, },
|
||||
}) => {
|
||||
const Table: React.FC<NodeProps<TableProps>> = ({ selected, data: { table } }) => {
|
||||
|
||||
const [editMode, setEditMode] = useState<boolean>(false);
|
||||
const [tableName, setTableName] = useState<string>(table.name);
|
||||
const { editTable } = useDatabase();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { focusOnTable } = useDiagram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setTableName(table.name);
|
||||
}, [table.name])
|
||||
|
||||
const saveTableName = async () => {
|
||||
const saveTableName = useCallback(async () => {
|
||||
await editTable({ id: table.id, name: tableName });
|
||||
setEditMode(false);
|
||||
}
|
||||
const edges = useStore((store) => Array.from(store.edges.values())) as Edge[];
|
||||
}, []);
|
||||
|
||||
const focus = useCallback(() => {
|
||||
focusOnTable(table.id, false);
|
||||
}, [])
|
||||
|
||||
const edges = useStore((store) => store.edges) as Edge[];
|
||||
|
||||
const highlightedEdges: Edge[] = useMemo(() => {
|
||||
return edges.filter((edge: Edge) => edge.animated || edge.selected);
|
||||
}, [edges]);
|
||||
|
||||
const fields: React.ReactNode[] = useMemo(() => {
|
||||
return table.fields.map((field: FieldType) => {
|
||||
const highlight: boolean = highlightedEdges.find((edge: any) =>
|
||||
(edge.data?.relationship as RelationshipType).sourceFieldId == field.id ||
|
||||
(edge.data?.relationship as RelationshipType).targetFieldId == field.id) != null;
|
||||
|
||||
return (<FieldComponent
|
||||
key={field.id}
|
||||
field={field}
|
||||
showHandles={selected}
|
||||
highlight={highlight}
|
||||
/>)
|
||||
})
|
||||
}, [table.fields, selected, highlightedEdges]);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
console.log(highlightedEdges)
|
||||
// console.log ("re-render" , table.name)
|
||||
|
||||
return (
|
||||
|
||||
<Card className={cn(
|
||||
@@ -132,61 +137,27 @@ const Table: React.FC<NodeProps<TableProps>> = React.memo(({
|
||||
size="sm"
|
||||
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
|
||||
isIconOnly
|
||||
onPressEnd={focus}
|
||||
>
|
||||
<Focus className="size-4 text-icon" />
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
variant="light"
|
||||
size="sm"
|
||||
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
|
||||
isIconOnly
|
||||
>
|
||||
{table.width !== MAX_TABLE_SIZE ? (
|
||||
<ChevronsLeftRight className="size-4" />
|
||||
) : (
|
||||
<ChevronsRightLeft className="size-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<div
|
||||
className="transition-[max-height] duration-200 ease-in-out"
|
||||
>
|
||||
{table.fields.map((field: FieldType) => {
|
||||
|
||||
const highlight: boolean = highlightedEdges.find((edge: any) =>
|
||||
(edge.data?.relationship as RelationshipType).sourceFieldId == field.id ||
|
||||
(edge.data?.relationship as RelationshipType).targetFieldId == field.id) != null;
|
||||
|
||||
|
||||
return (<FieldComponent
|
||||
key={field.id}
|
||||
field={field}
|
||||
showHandles={selected}
|
||||
highlight={highlight}
|
||||
|
||||
/>)
|
||||
})}
|
||||
<div className="transition-[max-height] duration-200 ease-in-out">
|
||||
{fields}
|
||||
</div>
|
||||
|
||||
</Card>
|
||||
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
export default React.memo(Table)
|
||||
//export default React.memo(Table ) ;
|
||||
|
||||
|
||||
|
||||
export default Table;
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
/*
|
||||
focused = { false}
|
||||
tableNodeId={id}
|
||||
field={field}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import DatabaseContext from "./database-context";
|
||||
import { useContext } from "react";
|
||||
import { useCallback, useContext } from "react";
|
||||
import { db, powerSyncDb } from "../sync-provider/sync-provider";
|
||||
import { TableInsertType, tables, TableType } from "@/lib/schemas/table-schema";
|
||||
import { useQuery } from "@powersync/react";
|
||||
@@ -47,85 +47,88 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
));
|
||||
|
||||
|
||||
const createTable = async (table: TableInsertType): Promise<QueryResult> => {
|
||||
return await db.insert(tables).values(table)
|
||||
}
|
||||
const editTable = async (table: TableInsertType): Promise<QueryResult> => {
|
||||
return await db.update(tables).set(table).where(eq(tables.id, table.id))
|
||||
}
|
||||
const deleteTable = async (id: string): Promise<QueryResult> => {
|
||||
const createTable = useCallback(async (table: TableInsertType): Promise<QueryResult> => {
|
||||
return await db.insert(tables).values(table);
|
||||
}, [db]);
|
||||
|
||||
const editTable = useCallback(async (table: TableInsertType): Promise<QueryResult> => {
|
||||
return await db.update(tables).set(table).where(eq(tables.id, table.id));
|
||||
}, [db]);
|
||||
|
||||
const deleteTable = useCallback(async (id: string): Promise<QueryResult> => {
|
||||
return await db.delete(tables).where(eq(tables.id, id));
|
||||
}
|
||||
const deleteMultiTables = async (ids: string[]): Promise<QueryResult> => {
|
||||
return await db.delete(tables).where(inArray(tables.id, ids ))
|
||||
}
|
||||
}, [db]);
|
||||
|
||||
const createField = async (field: FieldInsertType): Promise<QueryResult> => {
|
||||
const deleteMultiTables = useCallback(async (ids: string[]): Promise<QueryResult> => {
|
||||
return await db.delete(tables).where(inArray(tables.id, ids));
|
||||
}, [db]);
|
||||
|
||||
const createField = useCallback(async (field: FieldInsertType): Promise<QueryResult> => {
|
||||
return await db.insert(fields).values(field);
|
||||
}
|
||||
const editField = async (field: FieldInsertType): Promise<QueryResult> => {
|
||||
return await db.update(fields).set(field).where(eq(fields.id, field.id))
|
||||
}
|
||||
const deleteField = async (id: string): Promise<QueryResult> => {
|
||||
}, [db]);
|
||||
|
||||
const editField = useCallback(async (field: FieldInsertType): Promise<QueryResult> => {
|
||||
return await db.update(fields).set(field).where(eq(fields.id, field.id));
|
||||
}, [db]);
|
||||
|
||||
const deleteField = useCallback(async (id: string): Promise<QueryResult> => {
|
||||
return await db.delete(fields).where(eq(fields.id, id));
|
||||
}
|
||||
}, [db]);
|
||||
|
||||
const orderTableFields = async (fields: FieldType[]): Promise<QueryResult> => {
|
||||
const caseStatements = fields
|
||||
.map((field: FieldType, index: number) => `WHEN '${field.id}' THEN ${index}`)
|
||||
const orderTableFields = useCallback(async (fieldsList: FieldType[]): Promise<QueryResult> => {
|
||||
const caseStatements = fieldsList
|
||||
.map((field, index) => `WHEN '${field.id}' THEN ${index}`)
|
||||
.join('\n ');
|
||||
const ids = fields.map(u => `'${u.id}'`).join(',\n ');
|
||||
const ids = fieldsList.map(u => `'${u.id}'`).join(',\n ');
|
||||
const sql = `
|
||||
UPDATE fields
|
||||
SET sequence = CASE id
|
||||
${caseStatements}
|
||||
END
|
||||
WHERE id IN (
|
||||
${ids}
|
||||
);`;
|
||||
return await powerSyncDb.execute(sql)
|
||||
}
|
||||
|
||||
const createRelationship = async (relationship: RelationshipInsertType): Promise<QueryResult> => {
|
||||
return await db.insert(relationships).values(relationship);
|
||||
}
|
||||
const editRelationship = async (relationship: RelationshipInsertType): Promise<QueryResult> => {
|
||||
return await db.update(relationships).set(relationship).where(eq(relationships.id, relationship.id))
|
||||
}
|
||||
const deleteRelationship = async (id: string): Promise<QueryResult> => {
|
||||
return await db.delete(relationships).where(eq(relationships.id, id));
|
||||
}
|
||||
|
||||
const deleteMultiRelationships = async (ids: string[]): Promise<QueryResult> => {
|
||||
return await db.delete(relationships).where(inArray(relationships.id, ids ))
|
||||
}
|
||||
|
||||
const updateTablePositions = async (tables: TableInsertType[]): Promise<QueryResult> => {
|
||||
|
||||
const posXCases = tables
|
||||
.map((table: TableInsertType) => `WHEN '${table.id}' THEN ${table.posX}`)
|
||||
.join('\n ');
|
||||
|
||||
const posYCases = tables
|
||||
.map((table: TableInsertType) => `WHEN '${table.id}' THEN ${table.posY}`)
|
||||
.join('\n ');
|
||||
|
||||
const ids = tables.map((table: TableInsertType) => `'${table.id}'`).join(',\n ');
|
||||
|
||||
const sql = `
|
||||
UPDATE tables
|
||||
SET
|
||||
posX = CASE id
|
||||
${posXCases}
|
||||
END,
|
||||
posY = CASE id
|
||||
${posYCases}
|
||||
END
|
||||
WHERE id IN (
|
||||
${ids}
|
||||
UPDATE fields
|
||||
SET sequence = CASE id
|
||||
${caseStatements}
|
||||
END
|
||||
WHERE id IN (
|
||||
${ids}
|
||||
);`;
|
||||
return await powerSyncDb.execute(sql);
|
||||
}
|
||||
}, [powerSyncDb]);
|
||||
|
||||
const createRelationship = useCallback(async (relationship: RelationshipInsertType): Promise<QueryResult> => {
|
||||
return await db.insert(relationships).values(relationship);
|
||||
}, [db]);
|
||||
|
||||
const editRelationship = useCallback(async (relationship: RelationshipInsertType): Promise<QueryResult> => {
|
||||
return await db.update(relationships).set(relationship).where(eq(relationships.id, relationship.id));
|
||||
}, [db]);
|
||||
|
||||
const deleteRelationship = useCallback(async (id: string): Promise<QueryResult> => {
|
||||
return await db.delete(relationships).where(eq(relationships.id, id));
|
||||
}, [db]);
|
||||
|
||||
const deleteMultiRelationships = useCallback(async (ids: string[]): Promise<QueryResult> => {
|
||||
return await db.delete(relationships).where(inArray(relationships.id, ids));
|
||||
}, [db]);
|
||||
|
||||
const updateTablePositions = useCallback(async (tableList: TableInsertType[]): Promise<QueryResult> => {
|
||||
const posXCases = tableList
|
||||
.map(table => `WHEN '${table.id}' THEN ${table.posX}`)
|
||||
.join('\n ');
|
||||
const posYCases = tableList
|
||||
.map(table => `WHEN '${table.id}' THEN ${table.posY}`)
|
||||
.join('\n ');
|
||||
const ids = tableList.map(table => `'${table.id}'`).join(',\n ');
|
||||
const sql = `
|
||||
UPDATE tables
|
||||
SET
|
||||
posX = CASE id
|
||||
${posXCases}
|
||||
END,
|
||||
posY = CASE id
|
||||
${posYCases}
|
||||
END
|
||||
WHERE id IN (
|
||||
${ids}
|
||||
);`;
|
||||
return await powerSyncDb.execute(sql);
|
||||
}, [powerSyncDb]);
|
||||
return (
|
||||
|
||||
<DatabaseContext.Provider value={{
|
||||
@@ -133,7 +136,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
editTable,
|
||||
deleteTable,
|
||||
updateTablePositions,
|
||||
deleteMultiTables ,
|
||||
deleteMultiTables,
|
||||
|
||||
createField,
|
||||
editField,
|
||||
@@ -143,7 +146,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
createRelationship,
|
||||
editRelationship,
|
||||
deleteRelationship,
|
||||
deleteMultiRelationships ,
|
||||
deleteMultiRelationships,
|
||||
|
||||
tables: tablesList as TableType[],
|
||||
relationships: relationshipsList as RelationshipType[],
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createContext, Dispatch, SetStateAction } from "react";
|
||||
|
||||
|
||||
export interface DiagramContextType {
|
||||
|
||||
focusedTableId : string | undefined ;
|
||||
focusedRelationshipId : string | undefined;
|
||||
isConnectionInProgress : boolean
|
||||
|
||||
focusOnTable : ( id : string , transition? : boolean ) => void ,
|
||||
focusOnRelationship : ( id : string, transition? : boolean ) => void ,
|
||||
setIsConnectionInProgress : Dispatch<boolean>
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default createContext<DiagramContextType>({} as DiagramContextType);
|
||||
@@ -0,0 +1,98 @@
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { FitViewOptions, useReactFlow } from "@xyflow/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import DiagramContext from "./diagram-context";
|
||||
|
||||
|
||||
|
||||
interface Props { children: React.ReactNode }
|
||||
|
||||
const DiagramProvider: React.FC<Props> = ({ children }) => {
|
||||
|
||||
const { setNodes, fitView, setEdges } = useReactFlow();
|
||||
const navigate = useNavigate();
|
||||
const [focusedTableId, setFocusedTableId] = useState<string | undefined>(undefined)
|
||||
const [focusedRelationshipId, setFocusedRelationshipId] = useState<string | undefined>(undefined)
|
||||
const [isConnectionInProgress , setIsConnectionInProgress] = useState<boolean>( false) ;
|
||||
|
||||
const focusOnTable = useCallback((id: string, transition: boolean = false) => {
|
||||
navigate("/database/tables");
|
||||
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
const selected: boolean = node.id === id;
|
||||
if (selected && transition) {
|
||||
fitView({
|
||||
duration: 500,
|
||||
maxZoom: 1,
|
||||
minZoom: 1,
|
||||
nodes: [{
|
||||
id,
|
||||
}],
|
||||
});
|
||||
}
|
||||
return {
|
||||
...node,
|
||||
selected,
|
||||
}
|
||||
})
|
||||
);
|
||||
setFocusedTableId(id);
|
||||
}, [setFocusedTableId])
|
||||
|
||||
|
||||
const focusOnRelationship = useCallback((id: string, transition: boolean = false) => {
|
||||
navigate("/database/relationships");
|
||||
setFocusedRelationshipId(id);
|
||||
|
||||
setEdges((edges) =>
|
||||
edges.map((edge) => {
|
||||
const selected: boolean = edge.id === id;
|
||||
|
||||
if (selected && transition) {
|
||||
fitView({
|
||||
duration: 500,
|
||||
maxZoom: 1,
|
||||
minZoom: 1,
|
||||
nodes: [{
|
||||
id: (edge.data?.relationship as RelationshipType).sourceTableId
|
||||
}, {
|
||||
id: (edge.data?.relationship as RelationshipType).targetTableId
|
||||
}]
|
||||
});
|
||||
}
|
||||
return {
|
||||
...edge,
|
||||
selected
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
}, [setFocusedRelationshipId]);
|
||||
|
||||
|
||||
const contextValue = useMemo(() => ({
|
||||
focusedTableId,
|
||||
focusedRelationshipId,
|
||||
isConnectionInProgress ,
|
||||
focusOnTable,
|
||||
focusOnRelationship ,
|
||||
setIsConnectionInProgress
|
||||
}), [focusedTableId, focusedRelationshipId, focusOnTable, focusOnRelationship , isConnectionInProgress , setIsConnectionInProgress ]);
|
||||
return (
|
||||
<DiagramContext.Provider
|
||||
value={contextValue}
|
||||
>
|
||||
{children}
|
||||
</DiagramContext.Provider>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const useDiagram = () => useContext(DiagramContext);
|
||||
|
||||
|
||||
export default DiagramProvider;
|
||||
+36
-1
@@ -28,6 +28,10 @@
|
||||
|
||||
|
||||
|
||||
.react-flow__renderer {
|
||||
cursor: default !important; /* or pointer, grab, etc. */
|
||||
}
|
||||
|
||||
.sidebar-item[data-active="true"] svg {
|
||||
color : hsl(var(--heroui-primary-900));
|
||||
|
||||
@@ -56,4 +60,35 @@ div[data-slot="content"] hr[role="separator"] {
|
||||
to {
|
||||
stroke-dashoffset: -10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Target the scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 4px; /* Width of the scrollbar */
|
||||
|
||||
}
|
||||
|
||||
/* Track (background of the scrollbar) */
|
||||
::-webkit-scrollbar-track {
|
||||
background: hsl(210 40% 96.1%) ; /* Light gray background */
|
||||
|
||||
}
|
||||
|
||||
/* Handle (the scroll thumb) */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--heroui-default-300)); /* Darker gray */
|
||||
border-radius: 6px; /* Rounded corners */
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
|
||||
background: hsl(var(--nextui-default-400)); /* Darker on hover */
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { TableType } from "@/lib/schemas/table-schema";
|
||||
import { Node } from "@xyflow/react";
|
||||
|
||||
import ELK from "elkjs/lib/elk.bundled.js";
|
||||
|
||||
const elk = new ELK();
|
||||
|
||||
const adjustTablesPositions = async (
|
||||
nodes: Node[],
|
||||
relationships: RelationshipType[]
|
||||
): Promise<TableType[]> => {
|
||||
|
||||
// Extract tables (same as before)
|
||||
const tables: TableType[] = nodes.map((node: Node) => node.data.table) as TableType[];
|
||||
|
||||
// Build the ELK graph structure
|
||||
const graph = {
|
||||
id: "root",
|
||||
layoutOptions: {
|
||||
'elk.algorithm': 'layered',
|
||||
'elk.layered.spacing.nodeNodeBetweenLayers': '100',
|
||||
'elk.spacing.nodeNode': '80',
|
||||
},
|
||||
children: nodes.map((node) => ({
|
||||
id: node.id,
|
||||
width: node.measured?.width ?? 224,
|
||||
height: node.measured?.height ?? 150,
|
||||
})),
|
||||
edges: relationships.map((rel) => ({
|
||||
id: `${rel.sourceTableId}->${rel.targetTableId}`,
|
||||
sources: [rel.sourceTableId],
|
||||
targets: [rel.targetTableId],
|
||||
})),
|
||||
};
|
||||
|
||||
// Run ELK layout (async)
|
||||
const layoutedGraph = await elk.layout(graph);
|
||||
|
||||
// Map positions back to your tables
|
||||
tables.forEach((table) => {
|
||||
const node = layoutedGraph?.children?.find((n) => n.id === table.id);
|
||||
if (node) {
|
||||
// ELK positions are top-left, adjust to center like before
|
||||
table.posX = node.x || 0 + (node.width / 2) + 112; // 112 = half your fixed width
|
||||
table.posY = node.y || 0 + (node.height / 2) + 75; // 75 = half your fixed height
|
||||
}
|
||||
});
|
||||
|
||||
return tables;
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
adjustTablesPositions,
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
const areArraysEqual = (a: string[], b: string[]): boolean => {
|
||||
if (a.length !== b.length) return false;
|
||||
|
||||
const sortedA = [...a].sort();
|
||||
const sortedB = [...b].sort();
|
||||
|
||||
return sortedA.every((val, index) => val === sortedB[index]);
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
areArraysEqual
|
||||
}
|
||||
Reference in New Issue
Block a user