Datamodel overview improvements (#5771)

closes #5586
<img width="707" alt="Bildschirmfoto 2024-06-07 um 14 05 39"
src="https://github.com/twentyhq/twenty/assets/48770548/af5fa200-d71b-41ed-9478-35becfc306a3">
This commit is contained in:
brendanlaschke
2024-06-07 14:53:12 +02:00
committed by GitHub
parent 1208fed7b3
commit 908990315d
5 changed files with 61 additions and 64 deletions

View File

@ -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 }}
>
<Background />
<Controls />
<IconButtonGroup
className="react-flow__panel react-flow__controls bottom left"
size="small"
iconButtons={[
{
Icon: IconPlus,
onClick: () => zoomIn(),
},
{
Icon: IconMinus,
onClick: () => zoomOut(),
},
{
Icon: IconMaximize,
onClick: () => fitView(),
},
{
Icon: isInteractive ? IconLockOpen : IconLock,
onClick: () => setInteractive(!isInteractive),
},
]}
></IconButtonGroup>
</ReactFlow>
</StyledContainer>
);

View File

@ -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 (
<StyledRow>
{Icon && <Icon size={theme.icon.size.md} />}
{capitalize(relatedObject?.namePlural ?? '')}
<StyledFieldName>
{capitalize(relatedObject?.namePlural ?? '')}
</StyledFieldName>
<Handle
type={field.toRelationMetadata ? 'source' : 'target'}
position={Position.Right}

View File

@ -2,7 +2,7 @@ import { Link } from 'react-router-dom';
import { NodeProps } from 'reactflow';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconTag, useIcons } from 'twenty-ui';
import { IconChevronDown, useIcons } from 'twenty-ui';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
@ -18,13 +18,14 @@ type SettingsDataModelOverviewObjectProps = NodeProps<ObjectMetadataItem>;
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 && (
<StyledCardRowOther>
<IconTag size={theme.icon.size.md} />
<StyledCardRowText>
{countNonRelation} other fields
</StyledCardRowText>
<IconChevronDown size={theme.icon.size.md} />
<StyledCardRowText>{countNonRelation} fields</StyledCardRowText>
</StyledCardRowOther>
)}
</StyledInnerCard>

View File

@ -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 (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsDataModelOverview />
<ReactFlowProvider>
<SettingsDataModelOverview />
</ReactFlowProvider>
</SubMenuTopBarContainer>
);
};

View File

@ -106,9 +106,11 @@ export {
IconList,
IconListNumbers,
IconLock,
IconLockOpen,
IconMail,
IconMailCog,
IconMap,
IconMaximize,
IconMinus,
IconMoneybag,
IconMouse2,