diff --git a/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverview.tsx b/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverview.tsx
index 9f99c9d7e..dd098c46b 100644
--- a/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverview.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverview.tsx
@@ -1,24 +1,32 @@
-import { useCallback } from 'react';
+import { useCallback, useState } from 'react';
import ReactFlow, {
applyEdgeChanges,
applyNodeChanges,
Background,
- Controls,
EdgeChange,
getIncomers,
getOutgoers,
NodeChange,
useEdgesState,
useNodesState,
+ useReactFlow,
} from 'reactflow';
import styled from '@emotion/styled';
-import { IconX } from 'twenty-ui';
+import {
+ IconLock,
+ IconLockOpen,
+ IconMaximize,
+ IconMinus,
+ IconPlus,
+ IconX,
+} from 'twenty-ui';
import { SettingsDataModelOverviewEffect } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewEffect';
import { SettingsDataModelOverviewObject } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewObject';
import { SettingsDataModelOverviewRelationMarkers } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewRelationMarkers';
import { calculateHandlePosition } from '@/settings/data-model/graph-overview/util/calculateHandlePosition';
import { Button } from '@/ui/input/button/components/Button';
+import { IconButtonGroup } from '@/ui/input/button/components/IconButtonGroup';
import { isDefined } from '~/utils/isDefined';
import 'reactflow/dist/style.css';
@@ -28,34 +36,6 @@ const NodeTypes = {
};
const StyledContainer = styled.div`
height: 100%;
- .has-many-edge {
- &.selected path.react-flow__edge-path {
- marker-end: url(#hasManySelected);
- stroke-width: 1.5;
- }
- }
- .has-many-edge--highlighted {
- path.react-flow__edge-path,
- path.react-flow__edge-interaction,
- path.react-flow__connection-path {
- stroke: ${({ theme }) => theme.tag.background.blue} !important;
- stroke-width: 1.5px;
- }
- }
- .has-many-edge-reversed {
- &.selected path.react-flow__edge-path {
- marker-end: url(#hasManyReversedSelected);
- stroke-width: 1.5;
- }
- }
- .has-many-edge-reversed--highlighted {
- path.react-flow__edge-path,
- path.react-flow__edge-interaction,
- path.react-flow__connection-path {
- stroke: ${({ theme }) => theme.tag.background.blue} !important;
- stroke-width: 1.5px;
- }
- }
.react-flow__handle {
border: 0 !important;
background: transparent !important;
@@ -75,27 +55,6 @@ const StyledContainer = styled.div`
top: 50%;
transform: translateX(50%) translateY(-50%);
}
- .top-handle {
- top: 0;
- left: 50%;
- transform: translateX(-50%) translateY(-50%);
- }
- .bottom-handle {
- bottom: 0;
- left: 50%;
- transform: translateX(-50%) translateY(50%);
- }
- .react-flow__panel {
- display: flex;
- border-radius: ${({ theme }) => theme.border.radius.md};
- box-shadow: unset;
-
- button {
- background: ${({ theme }) => theme.background.secondary};
- border-bottom: none;
- fill: ${({ theme }) => theme.font.color.secondary};
- }
- }
.react-flow__node {
z-index: -1 !important;
}
@@ -109,8 +68,11 @@ const StyledCloseButton = styled.div`
`;
export const SettingsDataModelOverview = () => {
+ const { fitView, zoomIn, zoomOut } = useReactFlow();
+
const [nodes, setNodes] = useNodesState([]);
const [edges, setEdges] = useEdgesState([]);
+ const [isInteractive, setInteractive] = useState(true);
const onNodesChange = useCallback(
(changes: NodeChange[]) =>
@@ -236,10 +198,34 @@ export const SettingsDataModelOverview = () => {
onEdgesChange={onEdgesChange}
nodeTypes={NodeTypes}
onNodesChange={handleNodesChange}
+ nodesDraggable={isInteractive}
+ elementsSelectable={isInteractive}
proOptions={{ hideAttribution: true }}
>
-
+
+ zoomIn(),
+ },
+ {
+ Icon: IconMinus,
+ onClick: () => zoomOut(),
+ },
+ {
+ Icon: IconMaximize,
+ onClick: () => fitView(),
+ },
+ {
+ Icon: isInteractive ? IconLockOpen : IconLock,
+ onClick: () => setInteractive(!isInteractive),
+ },
+ ]}
+ >
);
diff --git a/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewField.tsx b/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewField.tsx
index 960a0abfd..14e5db70c 100644
--- a/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewField.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewField.tsx
@@ -15,12 +15,16 @@ type ObjectFieldRowProps = {
const StyledRow = styled.div`
align-items: center;
display: flex;
- gap: ${({ theme }) => theme.spacing(1)};
+ gap: ${({ theme }) => theme.spacing(2)};
position: relative;
width: 100%;
padding: 0 ${({ theme }) => theme.spacing(2)};
`;
+const StyledFieldName = styled.div`
+ color: ${({ theme }) => theme.font.color.primary};
+`;
+
export const ObjectFieldRow = ({ field }: ObjectFieldRowProps) => {
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { getIcon } = useIcons();
@@ -37,7 +41,9 @@ export const ObjectFieldRow = ({ field }: ObjectFieldRowProps) => {
return (
{Icon && }
- {capitalize(relatedObject?.namePlural ?? '')}
+
+ {capitalize(relatedObject?.namePlural ?? '')}
+
;
const StyledNode = styled.div`
background-color: ${({ theme }) => theme.background.secondary};
- border-radius: ${({ theme }) => theme.border.radius.sm};
+ border-radius: ${({ theme }) => theme.border.radius.md};
display: flex;
flex-direction: column;
width: 220px;
padding: ${({ theme }) => theme.spacing(2)};
gap: ${({ theme }) => theme.spacing(2)};
border: 1px solid ${({ theme }) => theme.border.color.medium};
+ box-shadow: ${({ theme }) => theme.boxShadow.light};
`;
const StyledHeader = styled.div`
@@ -37,7 +38,7 @@ const StyledObjectName = styled.div`
border: 0;
border-radius: 4px 4px 0 0;
display: flex;
- font-weight: bold;
+ font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(1)};
position: relative;
text-align: center;
@@ -67,7 +68,7 @@ const StyledCardRowOther = styled.div`
display: flex;
height: 24px;
padding: 0 ${({ theme }) => theme.spacing(2)};
- gap: ${({ theme }) => theme.spacing(1)};
+ gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledCardRowText = styled.div``;
@@ -79,6 +80,7 @@ const StyledObjectInstanceCount = styled.div`
const StyledObjectLink = styled(Link)`
align-items: center;
display: flex;
+ gap: ${({ theme }) => theme.spacing(1)};
text-decoration: none;
color: ${({ theme }) => theme.font.color.primary};
@@ -130,10 +132,8 @@ export const SettingsDataModelOverviewObject = ({
))}
{countNonRelation > 0 && (
-
-
- {countNonRelation} other fields
-
+
+ {countNonRelation} fields
)}
diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx
index 68d52499c..265171d71 100644
--- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx
+++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx
@@ -1,3 +1,4 @@
+import { ReactFlowProvider } from 'reactflow';
import { IconSettings } from 'twenty-ui';
import { SettingsDataModelOverview } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverview';
@@ -6,7 +7,9 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'
export const SettingsObjectOverview = () => {
return (
-
+
+
+
);
};
diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
index c95a42ec2..2855731a3 100644
--- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
+++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
@@ -106,9 +106,11 @@ export {
IconList,
IconListNumbers,
IconLock,
+ IconLockOpen,
IconMail,
IconMailCog,
IconMap,
+ IconMaximize,
IconMinus,
IconMoneybag,
IconMouse2,