mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-24 06:58:48 -05:00
refactor: refactors both connector and node labels to be single component
This commit is contained in:
104
src/components/Label/Label.tsx
Normal file
104
src/components/Label/Label.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { ExpandButton } from './ExpandButton';
|
||||
|
||||
interface Props {
|
||||
labelHeight?: number;
|
||||
maxWidth: number;
|
||||
maxHeight?: number;
|
||||
expandDirection?: 'CENTER' | 'BOTTOM';
|
||||
children: React.ReactNode;
|
||||
connectorDotSize: number;
|
||||
}
|
||||
|
||||
export const Label = ({
|
||||
children,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
expandDirection = 'CENTER',
|
||||
labelHeight = 0,
|
||||
connectorDotSize
|
||||
}: Props) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const contentRef = useRef<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
contentRef.current?.scrollTo({ top: 0 });
|
||||
}, [isExpanded]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: maxWidth
|
||||
}}
|
||||
>
|
||||
{labelHeight > 0 && (
|
||||
<Box
|
||||
component="svg"
|
||||
viewBox={`0 0 ${connectorDotSize} ${labelHeight}`}
|
||||
width={connectorDotSize}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: -labelHeight,
|
||||
left: -connectorDotSize / 2
|
||||
}}
|
||||
>
|
||||
<line
|
||||
x1={connectorDotSize / 2}
|
||||
y1={0}
|
||||
x2={connectorDotSize / 2}
|
||||
y2={labelHeight}
|
||||
strokeDasharray={`0, ${connectorDotSize * 2}`}
|
||||
stroke="black"
|
||||
strokeWidth={connectorDotSize}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
ref={contentRef}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
display: 'inline-block',
|
||||
bgcolor: 'common.white',
|
||||
border: '1px solid',
|
||||
borderColor: 'grey.400',
|
||||
borderRadius: 2,
|
||||
py: 1,
|
||||
px: 1.5,
|
||||
transformOrigin: 'bottom center',
|
||||
transform: `translate(-50%, ${
|
||||
expandDirection === 'BOTTOM' ? '-100%' : '-50%'
|
||||
})`,
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
style={{
|
||||
maxHeight,
|
||||
top: -labelHeight
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0
|
||||
}}
|
||||
>
|
||||
{isExpanded && (
|
||||
<ExpandButton
|
||||
isExpanded={isExpanded}
|
||||
onClick={() => {
|
||||
setIsExpanded(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* </Box> */}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { useScene } from 'src/hooks/useScene';
|
||||
import { connectorPathTileToGlobal, getTilePosition } from 'src/utils';
|
||||
import { PROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { Label } from 'src/components/Label/Label';
|
||||
|
||||
interface Props {
|
||||
connector: ReturnType<typeof useScene>['connectors'][0];
|
||||
@@ -23,6 +24,23 @@ export const ConnectorLabel = ({ connector }: Props) => {
|
||||
});
|
||||
}, [connector.path]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{ position: 'absolute', pointerEvents: 'none' }}
|
||||
style={{
|
||||
maxWidth: PROJECTED_TILE_SIZE.width,
|
||||
left: labelPosition.x,
|
||||
top: labelPosition.y
|
||||
}}
|
||||
>
|
||||
<Label maxWidth={150} labelHeight={0} connectorDotSize={0}>
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
{connector.description}
|
||||
</Typography>
|
||||
</Label>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import React, { useEffect, useRef, useMemo, useState } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useResizeObserver } from 'src/hooks/useResizeObserver';
|
||||
import { PROJECTED_TILE_SIZE } from 'src/config';
|
||||
import { Gradient } from 'src/components/Gradient/Gradient';
|
||||
import { ExpandButton } from './ExpandButton';
|
||||
|
||||
const STANDARD_LABEL_HEIGHT = 80;
|
||||
const EXPANDED_LABEL_HEIGHT = 200;
|
||||
|
||||
interface Props {
|
||||
labelHeight: number;
|
||||
children: React.ReactNode;
|
||||
connectorDotSize: number;
|
||||
}
|
||||
|
||||
export const LabelContainer = ({
|
||||
children,
|
||||
labelHeight,
|
||||
connectorDotSize
|
||||
}: Props) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const contentRef = useRef<HTMLDivElement>();
|
||||
const { observe, size: contentSize } = useResizeObserver();
|
||||
const yOffset = useMemo(() => {
|
||||
return PROJECTED_TILE_SIZE.height / 2;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!contentRef.current) return;
|
||||
|
||||
observe(contentRef.current);
|
||||
}, [observe]);
|
||||
|
||||
const containerMaxHeight = useMemo(() => {
|
||||
return isExpanded ? EXPANDED_LABEL_HEIGHT : STANDARD_LABEL_HEIGHT;
|
||||
}, [isExpanded]);
|
||||
|
||||
const isContentTruncated = useMemo(() => {
|
||||
return !isExpanded && contentSize.height >= STANDARD_LABEL_HEIGHT - 10;
|
||||
}, [isExpanded, contentSize.height]);
|
||||
|
||||
useEffect(() => {
|
||||
contentRef.current?.scrollTo({ top: 0 });
|
||||
}, [isExpanded]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
transformOrigin: 'top center'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="svg"
|
||||
viewBox={`0 0 ${connectorDotSize} ${labelHeight}`}
|
||||
width={connectorDotSize}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: -(labelHeight + yOffset),
|
||||
left: -connectorDotSize / 2
|
||||
}}
|
||||
>
|
||||
<line
|
||||
x1={connectorDotSize / 2}
|
||||
y1={0}
|
||||
x2={connectorDotSize / 2}
|
||||
y2={labelHeight}
|
||||
strokeDasharray={`0, ${connectorDotSize * 2}`}
|
||||
stroke="black"
|
||||
strokeWidth={connectorDotSize}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: -contentSize.width * 0.5,
|
||||
top: -(contentSize.height + labelHeight + yOffset),
|
||||
overflow: 'hidden',
|
||||
width: 250
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
ref={contentRef}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
display: 'inline-block',
|
||||
bgcolor: 'common.white',
|
||||
border: '1px solid',
|
||||
borderColor: 'grey.400',
|
||||
borderRadius: 2,
|
||||
py: 1,
|
||||
px: 1.5
|
||||
}}
|
||||
style={{
|
||||
overflowY: isExpanded ? 'scroll' : 'hidden',
|
||||
maxHeight: containerMaxHeight
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{isContentTruncated && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
height: 60,
|
||||
width: '100%',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
<Gradient
|
||||
sx={{ position: 'absolute', width: '100%', height: '100%' }}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0
|
||||
}}
|
||||
>
|
||||
{isContentTruncated && (
|
||||
<ExpandButton
|
||||
isExpanded={isExpanded}
|
||||
onClick={() => {
|
||||
setIsExpanded(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isExpanded && (
|
||||
<ExpandButton
|
||||
isExpanded={isExpanded}
|
||||
onClick={() => {
|
||||
setIsExpanded(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Typography, useTheme } from '@mui/material';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { PROJECTED_TILE_SIZE, DEFAULT_LABEL_HEIGHT } from 'src/config';
|
||||
import { getTilePosition } from 'src/utils';
|
||||
import { useIcon } from 'src/hooks/useIcon';
|
||||
import { ViewItem } from 'src/types';
|
||||
import { MarkdownEditor } from 'src/components/MarkdownEditor/MarkdownEditor';
|
||||
import { useModelItem } from 'src/hooks/useModelItem';
|
||||
import { LabelContainer } from './LabelContainer/LabelContainer';
|
||||
import { Label } from 'src/components/Label/Label';
|
||||
|
||||
interface Props {
|
||||
node: ViewItem;
|
||||
@@ -14,7 +13,6 @@ interface Props {
|
||||
}
|
||||
|
||||
export const Node = ({ node, order }: Props) => {
|
||||
const theme = useTheme();
|
||||
const modelItem = useModelItem(node.id);
|
||||
const { iconComponent } = useIcon(modelItem.icon);
|
||||
|
||||
@@ -50,33 +48,22 @@ export const Node = ({ node, order }: Props) => {
|
||||
}}
|
||||
>
|
||||
{(modelItem.name || description) && (
|
||||
<>
|
||||
<Box
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: -PROJECTED_TILE_SIZE.height
|
||||
}}
|
||||
/>
|
||||
<LabelContainer
|
||||
<Box
|
||||
sx={{ position: 'absolute' }}
|
||||
style={{ bottom: PROJECTED_TILE_SIZE.height / 2 }}
|
||||
>
|
||||
<Label
|
||||
maxWidth={250}
|
||||
maxHeight={100}
|
||||
expandDirection="BOTTOM"
|
||||
labelHeight={node.labelHeight ?? DEFAULT_LABEL_HEIGHT}
|
||||
connectorDotSize={3}
|
||||
>
|
||||
{modelItem.name && (
|
||||
<Typography fontWeight={600}>{modelItem.name}</Typography>
|
||||
)}
|
||||
{description && (
|
||||
<Box sx={{ pt: 0.2, width: 200 }}>
|
||||
<MarkdownEditor
|
||||
readOnly
|
||||
value={modelItem.description}
|
||||
styles={{
|
||||
color: theme.palette.text.secondary
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</LabelContainer>
|
||||
</>
|
||||
</Label>
|
||||
</Box>
|
||||
)}
|
||||
{iconComponent && (
|
||||
<Box
|
||||
|
||||
Reference in New Issue
Block a user