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:
@ -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>
|
||||
);
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -106,9 +106,11 @@ export {
|
||||
IconList,
|
||||
IconListNumbers,
|
||||
IconLock,
|
||||
IconLockOpen,
|
||||
IconMail,
|
||||
IconMailCog,
|
||||
IconMap,
|
||||
IconMaximize,
|
||||
IconMinus,
|
||||
IconMoneybag,
|
||||
IconMouse2,
|
||||
|
||||
Reference in New Issue
Block a user