Skip to content
11 changes: 10 additions & 1 deletion src/components/GraphViewer/GraphViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,16 @@ const GraphViewer = (props) => {
linkCanvasObjectMode={'replace'}
onLinkHover={handleLinkHover}
// Override drawing of canvas objects, draw an image as a node
nodeCanvasObject={(node, ctx) => paintNode(node, ctx, hoverNode, selectedNode, nodeSelected, previouslySelectedNodes)}
nodeCanvasObject={(node, ctx) =>
paintNode(
node,
ctx,
hoverNode,
selectedNode,
nodeSelected,
previouslySelectedNodes,
selectedLayout.layout === LEFT_RIGHT.layout
)}
nodeCanvasObjectMode={node => 'replace'}
nodeVal = { node => {
if ( selectedLayout.layout === TOP_DOWN.layout ){
Expand Down
2 changes: 1 addition & 1 deletion src/components/NodeDetailView/Details/SubjectDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const SubjectDetails = (props) => {
if ( property.isGroup ){
return (<Box className="tab-content-row">
<Typography component="label">{property.label}</Typography>
<SimpleLinkedChip chips={[{ value : node.graph_node.attributes[property.property]}]} node={getGroupNode(node.graph_node.attributes[property.property]?.[0], node)} />
<SimpleLinkedChip chips={[node.graph_node.attributes[property.property]]} node={getGroupNode(node.graph_node.attributes[property.property]?.[0], node)} />
</Box>)
}

Expand Down
82 changes: 73 additions & 9 deletions src/components/NodeDetailView/Details/Views/SimpleChip.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,78 @@
import React from "react";
import {
Box,
Chip
} from "@material-ui/core";
import React, { useState } from "react";
import { Box, Chip, Menu, MenuItem } from "@material-ui/core";

const SimpleChip = ({ chips }) => {
const [anchorEl, setAnchorEl] = useState(null);
const [selectedItem, setSelectedItem] = useState(null);

const isUrl = (value) => {
if (!value) return false;
try {
new URL(value);
return true;
} catch {
return false;
}
};

const normalize = (item) =>
typeof item === "string" ? { value: item } : item;

const handleClick = (item) => {
const url = item?.link || (isUrl(item?.value) ? item?.value : null);
if (url) {
window.open(url, "_blank");
}
};

const handleContextMenu = (event, item) => {
event.preventDefault();
setAnchorEl(event.currentTarget);
setSelectedItem(item);
};

const handleMenuClose = () => {
setAnchorEl(null);
setSelectedItem(null);
};

const handleCopyId = () => {
const copyValue = selectedItem?.id || selectedItem?.link || selectedItem?.value;
if (copyValue) {
navigator.clipboard.writeText(copyValue);
}
handleMenuClose();
};

const handleOpenNewTab = () => {
const url = selectedItem?.link || selectedItem?.id || selectedItem?.value;
if (isUrl(url)) {
window.open(url, "_blank");
}
handleMenuClose();
};

const SimpleChip = ({chips}) => {
return (
<Box className="chip-overflow noscrollbar">
{ chips?.map((item, index) => <Chip key={`${item}_${index}`} label={item} /> ) }
{chips?.map((rawItem, index) => {
const item = normalize(rawItem);
return (
<Chip
key={`${item?.value}_${index}`}
label={item?.value}
onClick={() => handleClick(item)}
onContextMenu={(e) => handleContextMenu(e, item)}
/>
);
})}
<Menu anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleMenuClose}>
<MenuItem onClick={handleCopyId}>Copy ID</MenuItem>
{(selectedItem?.link || isUrl(selectedItem?.id) || isUrl(selectedItem?.value)) && (
<MenuItem onClick={handleOpenNewTab}>Open in new tab</MenuItem>
)}
</Menu>
</Box>
)
}
);
};

export default SimpleChip;
111 changes: 87 additions & 24 deletions src/components/NodeDetailView/Details/Views/SimpleLinkedChip.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,108 @@
import React from "react";
import React, { useState } from "react";
import {
Box,
Chip
Chip,
Menu,
MenuItem
} from "@material-ui/core";
import { useDispatch } from 'react-redux';
import { selectGroup } from '../../../../redux/actions';
import { GRAPH_SOURCE } from '../../../../constants';


const SimpleChip = ({chips, node}) => {
const SimpleChip = ({ chips, node }) => {
const dispatch = useDispatch();

const [anchorEl, setAnchorEl] = useState(null);
const [selectedItem, setSelectedItem] = useState(null);

const isUrl = (value) => {
if (!value) return false;
try {
new URL(value);
return true;
} catch {
return false;
}
};

const handleClick = (item, node) => {
if ( item.link ){
window.open(item.link, '_blank')
} else if ( item.value ) {
let urlCheck = new RegExp("([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+@)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?");
if(urlCheck.test(item.value)) {
window.open(item.value, '_blank')
} else {
if ( node ) {
dispatch(selectGroup({
if (item.link) {
window.open(item.link, '_blank');
} else if (item.value) {
if (isUrl(item.value)) {
window.open(item.value, '_blank');
} else if (node) {
dispatch(
selectGroup({
dataset_id: node.dataset_id,
graph_node: node?.id,
tree_node: node?.tree_reference?.id,
source: GRAPH_SOURCE
}));
}
source: GRAPH_SOURCE,
})
);
}
}
}
};

const handleContextMenu = (event, item) => {
event.preventDefault();
setAnchorEl(event.currentTarget);
setSelectedItem(item);
};

const handleMenuClose = () => {
setAnchorEl(null);
setSelectedItem(null);
};

const handleCopyId = () => {
const copyValue = selectedItem?.id || selectedItem?.link || selectedItem?.value;
if (copyValue) {
navigator.clipboard.writeText(copyValue);
}
handleMenuClose();
};

const handleOpenNewTab = () => {
const url = selectedItem?.link || selectedItem?.id || selectedItem?.value;
if (url) {
window.open(url, '_blank');
}
handleMenuClose();
};

return (
<Box className="chip-overflow noscrollbar">
{ chips?.map((item, index) => ( node === undefined
? item.link ?
(<Chip label={item?.value} onClick={() => handleClick(item, null)}/>)
: (<Chip label={item?.value} />)
: (<Chip label={item?.value} onClick={() => handleClick(item, node)}/>)))
}
{chips?.map((item, index) =>
node === undefined ? (
item.link ? (
<Chip
label={item?.value}
onClick={() => handleClick(item, null)}
onContextMenu={(e) => handleContextMenu(e, item)}
/>
) : (
<Chip
label={item?.value}
onContextMenu={(e) => handleContextMenu(e, item)}
/>
)
) : (
<Chip
label={item?.value}
onClick={() => handleClick(item, node)}
onContextMenu={(e) => handleContextMenu(e, item)}
/>
)
)}
<Menu anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleMenuClose}>
{(selectedItem?.link || isUrl(selectedItem?.id) || isUrl(selectedItem?.value)) && (
<MenuItem onClick={handleOpenNewTab}>Open in new tab</MenuItem>
)}
</Menu>
</Box>
)
}
);
};

export default SimpleChip;
35 changes: 33 additions & 2 deletions src/components/Sidebar/Sidebar.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import { Box } from '@material-ui/core';
import SidebarHeader from './Header';
import SidebarContent from './List';
Expand All @@ -7,16 +7,47 @@ import SidebarFooter from './Footer';
const Sidebar = (props) => {
const [expand, setExpand] = useState(true);
const [searchTerm, setSearchTerm] = useState('');
const [width, setWidth] = useState(300);
const [isResizing, setIsResizing] = useState(false);
const sidebarRef = useRef(null);

const startResizing = (e) => {
setIsResizing(true);
e.preventDefault();
};

useEffect(() => {
const handleMouseMove = (e) => {
if (!isResizing || !expand) return;
const sidebarLeft = sidebarRef.current?.getBoundingClientRect().left || 0;
const newWidth = e.clientX - sidebarLeft;
if (newWidth > 200) {
setWidth(newWidth);
}
};
const stopResizing = () => setIsResizing(false);
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', stopResizing);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', stopResizing);
};
}, [isResizing, expand]);

return (
<Box className={'sidebar' + (!expand ? ' shrink' : '')}>
<Box
ref={sidebarRef}
className={'sidebar' + (!expand ? ' shrink' : '')}
style={{ width: expand ? width : 66 }}
>
<SidebarHeader setExpand={setExpand} expand={expand} setSearchTerm={setSearchTerm} searchTerm={searchTerm} />
<SidebarContent setExpand={setExpand} expand={expand} searchTerm={searchTerm} />
<SidebarFooter
setExpand={setExpand}
expand={expand}
setOpenDatasetsListDialog={props.setOpenDatasetsListDialog}
/>
{expand && <Box className="sidebar-resizer" onMouseDown={startResizing} />}
</Box>
);
};
Expand Down
17 changes: 13 additions & 4 deletions src/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ const theme = createTheme({
display: 'flex',
flexDirection: 'column',
transition: primaryTransition,
position: 'relative',
'&.shrink': {
width: '4.125rem',
transition: primaryTransition,
Expand Down Expand Up @@ -664,14 +665,14 @@ const theme = createTheme({
marginRight: '0.625rem',
flexShrink: 0,
},
'& .labelText': {
'& .labelText': {
fontWeight: 'normal',
flexGrow: 1,
fontSize: '0.8125rem',
lineHeight: '1rem',
color: whiteColor,
},
'& .MuiTreeItem-group': {
},
'& .MuiTreeItem-group': {
paddingLeft: '1.4375rem',
margin: 0,
},
Expand Down Expand Up @@ -830,7 +831,7 @@ const theme = createTheme({
},
},

'& .no-instance': {
'& .no-instance': {
fontSize: '0.75rem',
display: 'flex',
alignItems: 'center',
Expand All @@ -842,6 +843,14 @@ const theme = createTheme({
textAlign: 'center',
},
},
'&-resizer': {
width: '0.3125rem',
cursor: 'col-resize',
position: 'absolute',
right: 0,
top: 0,
bottom: 0,
},
'&-footer': {
boxShadow: `0 -4.75rem 3.0625rem -2.5625rem ${secondaryColor}`,
borderTop: `0.0625rem solid ${lightBorderColor}`,
Expand Down
17 changes: 13 additions & 4 deletions src/utils/GraphViewerHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,15 @@ const roundRect = (ctx, x, y, width, height, radius, color, alpha) => {
ctx.fill();
};

export const paintNode = (node, ctx, hoverNode, selectedNode, nodeSelected, previouslySelectedNodes) => {
export const paintNode = (
node,
ctx,
hoverNode,
selectedNode,
nodeSelected,
previouslySelectedNodes,
showFullName = false
) => {
const size = 7.5;
const nodeImageSize = [size * 2.4, size * 2.4];
const hoverRectDimensions = [size * 4.2, size * 4.2];
Expand Down Expand Up @@ -105,10 +113,11 @@ export const paintNode = (node, ctx, hoverNode, selectedNode, nodeSelected, prev
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
let nodeName = node.name;
if (nodeName.length > 10) {
if (Array.isArray(nodeName)) {
nodeName = nodeName[0];
}
if (!showFullName && nodeName?.length > 10) {
nodeName = nodeName.substr(0, 9).concat('...');
} else if ( Array.isArray(nodeName) ){
nodeName = nodeName[0]?.substr(0, 9).concat('...');
}
const textProps = [nodeName, node.x, textHoverPosition[1]];
if (node === hoverNode || node?.id === selectedNode?.id || node?.id === nodeSelected?.id ) {
Expand Down
Loading