SERVER-52576 Added libdeps graph visualizer web service with basic features.
This commit is contained in:
committed by
Evergreen Agent
parent
dac6c3ae77
commit
27aecefac7
57
buildscripts/libdeps/graph_visualizer_web_stack/src/App.js
Normal file
57
buildscripts/libdeps/graph_visualizer_web_stack/src/App.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from "react";
|
||||
import SplitPane from "react-split-pane";
|
||||
|
||||
import theme from "./theme";
|
||||
|
||||
import GraphCommitDisplay from "./GraphCommitDisplay";
|
||||
import GraphInfoTabs from "./GraphInfoTabs";
|
||||
import DrawGraph from "./DrawGraph";
|
||||
|
||||
const resizerStyle = {
|
||||
background: theme.palette.text.secondary,
|
||||
width: "1px",
|
||||
cursor: "col-resize",
|
||||
margin: "1px",
|
||||
padding: "1px",
|
||||
height: "100%",
|
||||
};
|
||||
|
||||
const topPaneStyle = {
|
||||
height: "100vh",
|
||||
overflow: "visible",
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
const [infosize, setInfosize] = React.useState(450);
|
||||
const [drawsize, setDrawsize] = React.useState(
|
||||
window.screen.width - infosize
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
setInfosize(window.screen.width - drawsize);
|
||||
}, [drawsize]);
|
||||
|
||||
return (
|
||||
<SplitPane
|
||||
pane1Style={{ height: "12%" }}
|
||||
pane2Style={{ height: "88%" }}
|
||||
split="horizontal"
|
||||
style={topPaneStyle}
|
||||
>
|
||||
<GraphCommitDisplay />
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
minSize={100}
|
||||
style={{ position: "relative" }}
|
||||
defaultSize={infosize}
|
||||
pane1Style={{ height: "100%" }}
|
||||
pane2Style={{ height: "100%", width: "100%" }}
|
||||
resizerStyle={resizerStyle}
|
||||
onChange={(size) => setDrawsize(window.screen.width - size)}
|
||||
>
|
||||
<GraphInfoTabs width={infosize} />
|
||||
<DrawGraph size={drawsize} />
|
||||
</SplitPane>
|
||||
</SplitPane>
|
||||
);
|
||||
}
|
||||
186
buildscripts/libdeps/graph_visualizer_web_stack/src/DataGrid.js
Normal file
186
buildscripts/libdeps/graph_visualizer_web_stack/src/DataGrid.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import clsx from "clsx";
|
||||
import { AutoSizer, Column, Table } from "react-virtualized";
|
||||
import "react-virtualized/styles.css"; // only needs to be imported once
|
||||
import { withStyles } from "@material-ui/core/styles";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import { Checkbox } from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
import { getRows } from "./redux/store";
|
||||
import { updateSelected } from "./redux/nodes";
|
||||
import { socket } from "./connect";
|
||||
|
||||
const styles = (theme) => ({
|
||||
flexContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
table: {
|
||||
// temporary right-to-left patch, waiting for
|
||||
// https://github.com/bvaughn/react-virtualized/issues/454
|
||||
"& .ReactVirtualized__Table__headerRow": {
|
||||
flip: false,
|
||||
paddingRight: theme.direction === "rtl" ? "0 !important" : undefined,
|
||||
},
|
||||
},
|
||||
tableRowOdd: {
|
||||
backgroundColor: "#4d4d4d",
|
||||
},
|
||||
tableRowEven: {},
|
||||
tableRowHover: {
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.grey[700],
|
||||
},
|
||||
},
|
||||
tableCell: {
|
||||
flex: 1,
|
||||
},
|
||||
noClick: {
|
||||
cursor: "initial",
|
||||
},
|
||||
});
|
||||
|
||||
const DataGrid = ({
|
||||
rowGetter,
|
||||
rowCount,
|
||||
nodes,
|
||||
rowHeight,
|
||||
headerHeight,
|
||||
columns,
|
||||
onNodeClicked,
|
||||
updateSelected,
|
||||
classes,
|
||||
}) => {
|
||||
const [checkBoxes, setCheckBoxes] = React.useState([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setCheckBoxes(nodes);
|
||||
}, [nodes]);
|
||||
|
||||
const getRowClassName = ({ index }) => {
|
||||
return clsx(
|
||||
index % 2 == 0 ? classes.tableRowEven : classes.tableRowOdd,
|
||||
classes.flexContainer,
|
||||
{
|
||||
[classes.tableRowHover]: index !== -1,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const cellRenderer = ({ cellData, columnIndex, rowIndex }) => {
|
||||
var finalCellData;
|
||||
var style = { height: rowHeight, padding: "0px" };
|
||||
if (cellData == "checkbox") {
|
||||
style["justifyContent"] = "space-evenly";
|
||||
finalCellData = (
|
||||
<Checkbox
|
||||
checked={checkBoxes[rowIndex].selected}
|
||||
onChange={(event) => {
|
||||
setCheckBoxes(
|
||||
checkBoxes.map((checkbox, index) => {
|
||||
if (index == rowIndex) {
|
||||
checkbox.selected = event.target.checked;
|
||||
}
|
||||
return checkbox;
|
||||
})
|
||||
);
|
||||
if (checkBoxes[rowIndex].selected != event.target.checked) {
|
||||
updateSelected({ index: rowIndex, value: event.target.checked });
|
||||
}
|
||||
socket.emit("row_selected", {
|
||||
data: { node: nodes[rowIndex].node, name: nodes[rowIndex].name },
|
||||
isSelected: event.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
finalCellData = cellData;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
component="div"
|
||||
className={clsx(
|
||||
classes.tableCell,
|
||||
classes.flexContainer,
|
||||
classes.noClick
|
||||
)}
|
||||
variant="body"
|
||||
onClick={onNodeClicked}
|
||||
style={style}
|
||||
>
|
||||
{finalCellData}
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
|
||||
const headerRenderer = ({ label, columnIndex }) => {
|
||||
return (
|
||||
<TableCell
|
||||
component="div"
|
||||
className={clsx(
|
||||
classes.tableCell,
|
||||
classes.flexContainer,
|
||||
classes.noClick
|
||||
)}
|
||||
variant="head"
|
||||
style={{ height: headerHeight, padding: "0px" }}
|
||||
>
|
||||
<Typography
|
||||
style={{ width: "100%" }}
|
||||
align="left"
|
||||
variant="caption"
|
||||
component="h2"
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<Table
|
||||
height={height}
|
||||
width={width}
|
||||
rowCount={rowCount}
|
||||
rowHeight={rowHeight}
|
||||
gridStyle={{
|
||||
direction: "inherit",
|
||||
}}
|
||||
size={"small"}
|
||||
rowGetter={rowGetter}
|
||||
className={clsx(classes.table, classes.noClick)}
|
||||
rowClassName={getRowClassName}
|
||||
headerHeight={headerHeight}
|
||||
>
|
||||
{columns.map(({ dataKey, ...other }, index) => {
|
||||
return (
|
||||
<Column
|
||||
key={dataKey}
|
||||
headerRenderer={(headerProps) =>
|
||||
headerRenderer({
|
||||
...headerProps,
|
||||
columnIndex: index,
|
||||
})
|
||||
}
|
||||
className={classes.flexContainer}
|
||||
cellRenderer={cellRenderer}
|
||||
dataKey={dataKey}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
)}
|
||||
</AutoSizer>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(getRows, { updateSelected })(
|
||||
withStyles(styles)(DataGrid)
|
||||
);
|
||||
194
buildscripts/libdeps/graph_visualizer_web_stack/src/DrawGraph.js
Normal file
194
buildscripts/libdeps/graph_visualizer_web_stack/src/DrawGraph.js
Normal file
@@ -0,0 +1,194 @@
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import * as THREE from "three";
|
||||
import { connect } from "react-redux";
|
||||
import ForceGraph2D from "react-force-graph-2d";
|
||||
import ForceGraph3D from "react-force-graph-3d";
|
||||
import SwitchComponents from "./SwitchComponent";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
|
||||
import theme from "./theme";
|
||||
import { socket } from "./connect";
|
||||
import { getGraphData } from "./redux/store";
|
||||
import { updateCheckbox } from "./redux/nodes";
|
||||
import { setFindNode } from "./redux/findNode";
|
||||
import LoadingBar from "./LoadingBar";
|
||||
|
||||
const handleFindNode = (node_value, graphData, activeComponent, forceRef) => {
|
||||
var targetNode = null;
|
||||
if (graphData) {
|
||||
for (var i = 0; i < graphData.nodes.length; i++) {
|
||||
var node = graphData.nodes[i];
|
||||
if (node.name == node_value || node.id == node_value) {
|
||||
targetNode = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetNode != null) {
|
||||
if (activeComponent == "3D") {
|
||||
if (forceRef.current != null) {
|
||||
forceRef.current.centerAt(targetNode.x, targetNode.y, 2000);
|
||||
forceRef.current.zoom(6, 1000);
|
||||
}
|
||||
} else {
|
||||
const distance = 100;
|
||||
const distRatio =
|
||||
1 + distance / Math.hypot(targetNode.x, targetNode.y, targetNode.z);
|
||||
if (forceRef.current != null) {
|
||||
forceRef.current.cameraPosition(
|
||||
{
|
||||
x: targetNode.x * distRatio,
|
||||
y: targetNode.y * distRatio,
|
||||
z: targetNode.z * distRatio,
|
||||
}, // new position
|
||||
targetNode, // lookAt ({ x, y, z })
|
||||
3000 // ms transition duration
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const DrawGraph = ({
|
||||
size,
|
||||
graphData,
|
||||
nodes,
|
||||
loading,
|
||||
findNode,
|
||||
setFindNode,
|
||||
}) => {
|
||||
const [activeComponent, setActiveComponent] = React.useState("2D");
|
||||
const [selectedNodes, setSelectedNodes] = React.useState([]);
|
||||
const forceRef = useRef(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
handleFindNode(findNode, graphData, activeComponent, forceRef);
|
||||
setFindNode("");
|
||||
}, [findNode, graphData, activeComponent, forceRef]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setSelectedNodes(
|
||||
nodes.map((node) => {
|
||||
if (node.selected) {
|
||||
return node.node;
|
||||
}
|
||||
})
|
||||
);
|
||||
}, [nodes]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (forceRef.current != null) {
|
||||
forceRef.current.d3Force("charge").strength(-1400);
|
||||
}
|
||||
}, [forceRef.current]);
|
||||
|
||||
const paintRing = React.useCallback((node, ctx) => {
|
||||
// add ring just for highlighted nodes
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, 4 * 1.4, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = "green";
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
function colorNodes(node) {
|
||||
switch (node.type) {
|
||||
case "SharedLibrary":
|
||||
return "#e6ed11"; // yellow
|
||||
case "Program":
|
||||
return "#1120ed"; // blue
|
||||
case "shim":
|
||||
return "#800303"; // dark red
|
||||
default:
|
||||
return "#5a706f"; // grey
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<LoadingBar loading={loading} height={"100%"}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (activeComponent == "2D") {
|
||||
setActiveComponent("3D");
|
||||
} else {
|
||||
setActiveComponent("2D");
|
||||
}
|
||||
}}
|
||||
>
|
||||
{activeComponent}
|
||||
</Button>
|
||||
<TextField
|
||||
size="small"
|
||||
label="Find Node"
|
||||
onChange={(event) => {
|
||||
handleFindNode(
|
||||
event.target.value,
|
||||
graphData,
|
||||
activeComponent,
|
||||
forceRef
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<SwitchComponents active={activeComponent}>
|
||||
<ForceGraph2D
|
||||
name="3D"
|
||||
width={size}
|
||||
dagMode="radialout"
|
||||
graphData={graphData}
|
||||
ref={forceRef}
|
||||
nodeColor={colorNodes}
|
||||
nodeOpacity={1}
|
||||
backgroundColor={theme.palette.secondary.dark}
|
||||
linkDirectionalArrowLength={6}
|
||||
linkDirectionalArrowRelPos={1}
|
||||
nodeCanvasObjectMode={(node) => {
|
||||
if (selectedNodes.includes(node.id)) {
|
||||
return "before";
|
||||
}
|
||||
}}
|
||||
nodeCanvasObject={paintRing}
|
||||
onNodeClick={(node, event) => {
|
||||
updateCheckbox(node.id);
|
||||
socket.emit("row_selected", {
|
||||
data: { node: node.id, name: node.name },
|
||||
isSelected: !selectedNodes.includes(node.id),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<ForceGraph3D
|
||||
name="2D"
|
||||
width={size}
|
||||
dagMode="radialout"
|
||||
graphData={graphData}
|
||||
nodeColor={colorNodes}
|
||||
nodeOpacity={1}
|
||||
nodeThreeObject={(node) => {
|
||||
if (!selectedNodes.includes(node.id)) {
|
||||
return new THREE.Mesh(
|
||||
new THREE.SphereGeometry(5, 5, 5),
|
||||
new THREE.MeshLambertMaterial({
|
||||
color: colorNodes(node),
|
||||
transparent: true,
|
||||
opacity: 0.2,
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
onNodeClick={(node, event) => {
|
||||
updateCheckbox(node.id);
|
||||
socket.emit("row_selected", {
|
||||
data: { node: node.id, name: node.name },
|
||||
isSelected: !selectedNodes.includes(node.id),
|
||||
});
|
||||
}}
|
||||
backgroundColor={theme.palette.secondary.dark}
|
||||
linkDirectionalArrowLength={3.5}
|
||||
linkDirectionalArrowRelPos={1}
|
||||
ref={forceRef}
|
||||
/>
|
||||
</SwitchComponents>
|
||||
</LoadingBar>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(getGraphData, { setFindNode })(DrawGraph);
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import LoadingButton from "@material-ui/lab/LoadingButton";
|
||||
import GitIcon from "@material-ui/icons/GitHub";
|
||||
import { green, grey } from "@material-ui/core/colors";
|
||||
|
||||
import { socket } from "./connect";
|
||||
import { getGraphFiles } from "./redux/store";
|
||||
import { setLoading } from "./redux/loading";
|
||||
import theme from "./theme";
|
||||
|
||||
const selectedStyle = {
|
||||
color: theme.palette.getContrastText(green[500]),
|
||||
backgroundColor: green[500],
|
||||
"&:hover": {
|
||||
backgroundColor: green[400],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: green[700],
|
||||
},
|
||||
};
|
||||
|
||||
const unselectedStyle = {
|
||||
color: theme.palette.getContrastText(grey[100]),
|
||||
backgroundColor: grey[100],
|
||||
"&:hover": {
|
||||
backgroundColor: grey[200],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: grey[400],
|
||||
},
|
||||
};
|
||||
|
||||
const GitHashButton = ({ loading, graphFiles, setLoading, text }) => {
|
||||
const [selected, setSelected] = React.useState(false);
|
||||
const [selfLoading, setSelfLoading] = React.useState(false);
|
||||
const [firstLoad, setFirstLoad] = React.useState(true);
|
||||
|
||||
function handleClick() {
|
||||
setSelfLoading(true);
|
||||
setLoading(true);
|
||||
socket.emit("git_hash_selected", { hash: text, selected: true });
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const selectedGraphFile = graphFiles.filter(
|
||||
(graphFile) => graphFile.git == text
|
||||
);
|
||||
setSelected(selectedGraphFile[0].selected);
|
||||
|
||||
if (firstLoad && graphFiles.length > 0) {
|
||||
if (graphFiles[0]["git"] == text) {
|
||||
handleClick();
|
||||
}
|
||||
setFirstLoad(false);
|
||||
}
|
||||
}, [graphFiles]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!loading) {
|
||||
setSelfLoading(false);
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
return (
|
||||
<LoadingButton
|
||||
pending={selfLoading}
|
||||
pendingPosition="start"
|
||||
startIcon={<GitIcon />}
|
||||
variant="contained"
|
||||
style={selected ? selectedStyle : unselectedStyle}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{text}
|
||||
</LoadingButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(getGraphFiles, { setLoading })(GitHashButton);
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from "react";
|
||||
import ScrollContainer from "react-indiana-drag-scroll";
|
||||
import { connect } from "react-redux";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import List from "@material-ui/core/List";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
|
||||
import SocketConnection from "./connect";
|
||||
import { getGraphFiles } from "./redux/store";
|
||||
|
||||
import GitHashButton from "./GitHashButton";
|
||||
|
||||
const flexContainer = {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: 0,
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
};
|
||||
|
||||
const textFields = [
|
||||
"Scroll to commit",
|
||||
"Commit Range Begin",
|
||||
"Commit Range End",
|
||||
];
|
||||
|
||||
const GraphCommitDisplay = ({ graphFiles }) => {
|
||||
return (
|
||||
<Paper style={{ height: "100%", width: "100%" }}>
|
||||
<List style={flexContainer}>
|
||||
{textFields.map((text) => (
|
||||
<ListItem key={text}>
|
||||
<TextField size="small" label={text} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
<ScrollContainer
|
||||
vertical={false}
|
||||
style={{ height: "50%" }}
|
||||
className="scroll-container"
|
||||
hideScrollbars={true}
|
||||
>
|
||||
<Table style={{ height: "100%" }}>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
{graphFiles.map((file) => (
|
||||
<TableCell key={file.id}>
|
||||
<GitHashButton text={file.git} />
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<SocketConnection />
|
||||
</ScrollContainer>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(getGraphFiles)(GraphCommitDisplay);
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import { connect } from "react-redux";
|
||||
import { getCounts } from "./redux/store";
|
||||
|
||||
const columns = [
|
||||
{ id: "ID", field: "type", headerName: "Count Type", width: 50 },
|
||||
{ field: "value", headerName: "Value", width: 50 },
|
||||
];
|
||||
|
||||
const useStyles = makeStyles({
|
||||
table: {
|
||||
minWidth: 50,
|
||||
},
|
||||
});
|
||||
|
||||
const GraphInfo = ({ counts, datawidth }) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table className={classes.table} size="small" aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columns.map((column, index) => {
|
||||
return <TableCell key={index}>{column.headerName}</TableCell>;
|
||||
})}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{counts.map((row) => (
|
||||
<TableRow key={row.id}>
|
||||
<TableCell component="th" scope="row">
|
||||
{row.type}
|
||||
</TableCell>
|
||||
<TableCell>{row.value}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(getCounts)(GraphInfo);
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import AppBar from "@material-ui/core/AppBar";
|
||||
import Tabs from "@material-ui/core/Tabs";
|
||||
import Tab from "@material-ui/core/Tab";
|
||||
|
||||
import NodeList from "./NodeList";
|
||||
import InfoExpander from "./InfoExpander";
|
||||
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
id: `scrollable-auto-tab-${index}`,
|
||||
"aria-controls": `scrollable-auto-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function GraphInfoTabs({ nodes, width }) {
|
||||
const classes = useStyles();
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AppBar position="static" color="default">
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
aria-label="scrollable auto tabs example"
|
||||
>
|
||||
<Tab label="Selected Info" {...a11yProps(0)} />
|
||||
<Tab label="Node List" {...a11yProps(1)} />
|
||||
<Tab label="Edge List" {...a11yProps(2)} />
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
<div style={{ height: "100%" }} hidden={value != 0}>
|
||||
<InfoExpander width={width}></InfoExpander>
|
||||
</div>
|
||||
<div style={{ height: "100%" }} hidden={value != 1}>
|
||||
<NodeList nodes={nodes}></NodeList>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { makeStyles, withStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import MuiAccordion from "@material-ui/core/Accordion";
|
||||
import MuiAccordionSummary from "@material-ui/core/AccordionSummary";
|
||||
import MuiAccordionDetails from "@material-ui/core/AccordionDetails";
|
||||
|
||||
import { getSelected } from "./redux/store";
|
||||
|
||||
import GraphInfo from "./GraphInfo";
|
||||
import NodeInfo from "./NodeInfo";
|
||||
import LoadingBar from "./LoadingBar";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
width: "100%",
|
||||
},
|
||||
heading: {
|
||||
fontSize: theme.typography.pxToRem(15),
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
},
|
||||
}));
|
||||
|
||||
const Accordion = withStyles({
|
||||
root: {
|
||||
border: "1px solid rgba(0, 0, 0, .125)",
|
||||
boxShadow: "none",
|
||||
"&:not(:last-child)": {
|
||||
borderBottom: 0,
|
||||
},
|
||||
"&:before": {
|
||||
display: "none",
|
||||
},
|
||||
"&$expanded": {
|
||||
margin: "auto",
|
||||
},
|
||||
},
|
||||
expanded: {},
|
||||
})(MuiAccordion);
|
||||
|
||||
const AccordionSummary = withStyles({
|
||||
root: {
|
||||
backgroundColor: "rgba(0, 0, 0, .03)",
|
||||
borderBottom: "1px solid rgba(0, 0, 0, .125)",
|
||||
marginBottom: -1,
|
||||
minHeight: 56,
|
||||
"&$expanded": {
|
||||
minHeight: 56,
|
||||
},
|
||||
},
|
||||
content: {
|
||||
"&$expanded": {
|
||||
margin: "12px 0",
|
||||
},
|
||||
},
|
||||
expanded: {},
|
||||
})(MuiAccordionSummary);
|
||||
|
||||
const AccordionDetails = withStyles((theme) => ({
|
||||
root: {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
}))(MuiAccordionDetails);
|
||||
|
||||
const InfoExpander = ({ selectedNodes, selectedEdges, loading, width }) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<LoadingBar loading={loading} height={"100%"}>
|
||||
<Paper style={{ maxHeight: "82vh", overflow: "auto" }}>
|
||||
<Accordion>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<Typography className={classes.heading}>Counts</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<GraphInfo datawidth={width} />
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
{selectedNodes.map((node) => (
|
||||
<Accordion key={node.node}>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<Typography className={classes.heading}>{node.name}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<NodeInfo node={node} width={width} />
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
</Paper>
|
||||
</LoadingBar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(getSelected)(InfoExpander);
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import LinearProgress from "@material-ui/core/LinearProgress";
|
||||
import Fade from "@material-ui/core/Fade";
|
||||
|
||||
export default function LoadingBar({ loading, height, children }) {
|
||||
const dimOnTrue = (flag) => {
|
||||
return {
|
||||
opacity: flag ? 0.15 : 1,
|
||||
height: "100%",
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ height: height }}>
|
||||
<Fade
|
||||
in={loading}
|
||||
style={{ transitionDelay: loading ? "300ms" : "0ms" }}
|
||||
unmountOnExit
|
||||
>
|
||||
<LinearProgress />
|
||||
</Fade>
|
||||
<div style={dimOnTrue(loading)}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
187
buildscripts/libdeps/graph_visualizer_web_stack/src/NodeInfo.js
Normal file
187
buildscripts/libdeps/graph_visualizer_web_stack/src/NodeInfo.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { FixedSizeList } from "react-window";
|
||||
import { AutoSizer } from "react-virtualized";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import List from "@material-ui/core/List";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import Collapse from "@material-ui/core/Collapse";
|
||||
import ExpandLess from "@material-ui/icons/ExpandLess";
|
||||
import ExpandMore from "@material-ui/icons/ExpandMore";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Box from "@material-ui/core/Box";
|
||||
|
||||
import { getNodeInfos } from "./redux/store";
|
||||
|
||||
import theme from "./theme";
|
||||
|
||||
import OverflowTooltip from "./OverflowTooltip";
|
||||
|
||||
const NodeInfo = ({ nodeInfos, node, width }) => {
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
width: "100%",
|
||||
maxWidth: width,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
nested: {
|
||||
paddingLeft: theme.spacing(4),
|
||||
},
|
||||
listItem: {
|
||||
width: width,
|
||||
},
|
||||
}));
|
||||
|
||||
const rowHeight = 25;
|
||||
const classes = useStyles();
|
||||
const [openDependers, setOpenDependers] = React.useState(false);
|
||||
const [openDependencies, setOpenDependencies] = React.useState(false);
|
||||
const [openNodeAttribs, setOpenNodeAttribs] = React.useState(false);
|
||||
|
||||
const [nodeInfo, setNodeInfo] = React.useState({
|
||||
id: 0,
|
||||
node: "test/test.so",
|
||||
name: "test",
|
||||
attribs: [{ name: "test", value: "test" }],
|
||||
dependers: [{ node: "test/test3.so", symbols: [] }],
|
||||
dependencies: [{ node: "test/test2.so", symbols: [] }],
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
setNodeInfo(nodeInfos.filter((nodeInfo) => nodeInfo.node == node.node)[0]);
|
||||
}, [nodeInfos]);
|
||||
|
||||
function renderAttribRow({ index, style, data }) {
|
||||
return (
|
||||
<ListItem style={style} key={index}>
|
||||
<Box style={{ margin: "5px" }}>
|
||||
<OverflowTooltip
|
||||
value={data[index].name}
|
||||
text={String(data[index].name) + ":"}
|
||||
/>
|
||||
</Box>
|
||||
<OverflowTooltip
|
||||
value={String(data[index].value)}
|
||||
text={String(data[index].value)}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function renderNodeRow({ index, style, data }) {
|
||||
return (
|
||||
<ListItem style={style} key={index}>
|
||||
<OverflowTooltip
|
||||
button
|
||||
name={data[index].name}
|
||||
value={data[index].node}
|
||||
text={data[index].node}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function listHeight(numItems) {
|
||||
const size = numItems * rowHeight;
|
||||
if (size > 350) {
|
||||
return 350;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
if (nodeInfo == undefined) {
|
||||
return "";
|
||||
}
|
||||
return (
|
||||
<List
|
||||
component="nav"
|
||||
aria-labelledby="nested-list-subheader"
|
||||
className={classes.root}
|
||||
dense={true}
|
||||
>
|
||||
<Paper elevation={3} style={{ backgroundColor: "rgba(0, 0, 0, .03)" }}>
|
||||
<ListItem button>
|
||||
<ListItemText primary={nodeInfo.node} />
|
||||
</ListItem>
|
||||
<ListItem button>
|
||||
<ListItemText primary={nodeInfo.name} />
|
||||
</ListItem>
|
||||
|
||||
<ListItem button onClick={() => setOpenNodeAttribs(!openNodeAttribs)}>
|
||||
<ListItemText primary="Attributes" />
|
||||
{openNodeAttribs ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItem>
|
||||
<Collapse in={openNodeAttribs} timeout="auto" unmountOnExit>
|
||||
<Paper
|
||||
elevation={2}
|
||||
style={{
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
}}
|
||||
>
|
||||
<AutoSizer disableHeight={true}>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
height={listHeight(nodeInfo.attribs.length)}
|
||||
width={width}
|
||||
itemSize={rowHeight}
|
||||
itemCount={nodeInfo.attribs.length}
|
||||
itemData={nodeInfo.attribs}
|
||||
>
|
||||
{renderAttribRow}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</Paper>
|
||||
</Collapse>
|
||||
|
||||
<ListItem button onClick={() => setOpenDependers(!openDependers)}>
|
||||
<ListItemText primary="Dependers" />
|
||||
{openDependers ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItem>
|
||||
<Collapse in={openDependers} timeout="auto" unmountOnExit>
|
||||
<Paper elevation={4}>
|
||||
<AutoSizer disableHeight={true}>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
height={listHeight(nodeInfo.dependers.length)}
|
||||
width={width}
|
||||
itemSize={rowHeight}
|
||||
itemCount={nodeInfo.dependers.length}
|
||||
itemData={nodeInfo.dependers}
|
||||
>
|
||||
{renderNodeRow}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</Paper>
|
||||
</Collapse>
|
||||
|
||||
<ListItem button onClick={() => setOpenDependencies(!openDependencies)}>
|
||||
<ListItemText primary="Dependencies" />
|
||||
{openDependencies ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItem>
|
||||
<Collapse in={openDependencies} timeout="auto" unmountOnExit>
|
||||
<Paper elevation={4}>
|
||||
<AutoSizer disableHeight={true}>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
height={listHeight(nodeInfo.dependencies.length)}
|
||||
width={width}
|
||||
itemSize={rowHeight}
|
||||
itemCount={nodeInfo.dependencies.length}
|
||||
itemData={nodeInfo.dependencies}
|
||||
>
|
||||
{renderNodeRow}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</Paper>
|
||||
</Collapse>
|
||||
</Paper>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(getNodeInfos)(NodeInfo);
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { getNodes } from "./redux/store";
|
||||
import { setFindNode } from "./redux/findNode";
|
||||
import { socket } from "./connect";
|
||||
|
||||
import DataGrid from "./DataGrid";
|
||||
import LoadingBar from "./LoadingBar";
|
||||
|
||||
const columns = [
|
||||
{ dataKey: "check", label: "Selected", width: 70 },
|
||||
{ dataKey: "name", label: "Name", width: 200 },
|
||||
{ id: "ID", dataKey: "node", label: "Node", width: 200 },
|
||||
];
|
||||
|
||||
const NodeList = ({ nodes, loading, setFindNode }) => {
|
||||
function handleCheckBoxes(rowIndex, event) {
|
||||
socket.emit("row_selected", {
|
||||
data: { node: nodes[rowIndex].node, name: nodes[rowIndex].name },
|
||||
isSelected: event.target.checked,
|
||||
});
|
||||
}
|
||||
|
||||
function handleRowClick(event) {
|
||||
setFindNode(event.target.textContent);
|
||||
}
|
||||
|
||||
return (
|
||||
<LoadingBar loading={loading} height={"95%"}>
|
||||
<DataGrid
|
||||
rows={nodes}
|
||||
columns={columns}
|
||||
rowHeight={30}
|
||||
headerHeight={35}
|
||||
onNodeClicked={handleRowClick}
|
||||
onRowSelect={handleCheckBoxes}
|
||||
/>
|
||||
</LoadingBar>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(getNodes, { setFindNode })(NodeList);
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import Fade from "@material-ui/core/Fade";
|
||||
import Box from "@material-ui/core/Box";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import AddCircleOutline from "@material-ui/icons/AddCircleOutline";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
import { socket } from "./connect";
|
||||
import { updateCheckbox } from "./redux/nodes";
|
||||
|
||||
const OverflowTip = (props) => {
|
||||
const textElementRef = useRef(null);
|
||||
const [hoverStatus, setHover] = useState(false);
|
||||
|
||||
const compareSize = (textElementRef) => {
|
||||
if (textElementRef.current != null) {
|
||||
const compare =
|
||||
textElementRef.current.scrollWidth > textElementRef.current.offsetWidth;
|
||||
setHover(compare);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
compareSize(textElementRef);
|
||||
window.addEventListener("resize", compareSize);
|
||||
return function () {
|
||||
window.removeEventListener("resize", compareSize);
|
||||
};
|
||||
}, [props, textElementRef.current]);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={props.value}
|
||||
interactive
|
||||
disableHoverListener={!hoverStatus}
|
||||
style={{ fontSize: "1em" }}
|
||||
enterDelay={500}
|
||||
TransitionComponent={Fade}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
fontSize: "1em",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
<Typography noWrap variant={"body2"} gutterBottom>
|
||||
{props.button && (
|
||||
<IconButton
|
||||
size="small"
|
||||
color="secondary"
|
||||
onClick={(event) => {
|
||||
props.updateCheckbox({ node: props.text, value: "flip" });
|
||||
socket.emit("row_selected", {
|
||||
data: { node: props.text, name: props.name },
|
||||
isSelected: "flip",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<AddCircleOutline style={{ height: "15px", width: "15px" }} />
|
||||
</IconButton>
|
||||
)}
|
||||
<span ref={textElementRef}>{props.text}</span>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(null, { updateCheckbox })(OverflowTip);
|
||||
@@ -0,0 +1,4 @@
|
||||
export default function SwitchComponents({ active, children }) {
|
||||
// Switch all children and return the "active" one
|
||||
return children.filter((child) => child.props.name == active);
|
||||
}
|
||||
100
buildscripts/libdeps/graph_visualizer_web_stack/src/connect.js
Normal file
100
buildscripts/libdeps/graph_visualizer_web_stack/src/connect.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import React from "react";
|
||||
import io from "socket.io-client";
|
||||
import { connect as reduxConnect } from "react-redux";
|
||||
|
||||
import { setNodes, updateCheckboxes } from "./redux/nodes";
|
||||
import { setCounts } from "./redux/counts";
|
||||
import { setNodeInfos } from "./redux/nodeInfo";
|
||||
import { setGraphFiles, selectGraphFile } from "./redux/graphFiles";
|
||||
import { setLoading } from "./redux/loading";
|
||||
import { addLinks } from "./redux/links";
|
||||
import { setGraphData } from "./redux/graphData";
|
||||
|
||||
const { REACT_APP_API_URL } = process.env;
|
||||
|
||||
export const socket = io.connect(REACT_APP_API_URL, {
|
||||
reconnection: true,
|
||||
transports: ["websocket"],
|
||||
});
|
||||
|
||||
const SocketConnection = ({
|
||||
setNodes,
|
||||
updateCheckboxes,
|
||||
setCounts,
|
||||
setGraphFiles,
|
||||
setLoading,
|
||||
selectGraphFile,
|
||||
addLinks,
|
||||
setGraphData,
|
||||
setNodeInfos,
|
||||
}) => {
|
||||
React.useEffect(() => {
|
||||
fetch(REACT_APP_API_URL + "/graph_files")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setGraphFiles(data.graph_files);
|
||||
})
|
||||
.catch((err) => {
|
||||
/* eslint-disable no-console */
|
||||
console.log("Error Reading data " + err);
|
||||
});
|
||||
|
||||
socket.on("other_hash_selected", (incomingData) => {
|
||||
selectGraphFile({
|
||||
hash: incomingData.hash,
|
||||
selected: incomingData.selected,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("graph_nodes", (incomingData) => {
|
||||
setLoading(false);
|
||||
setNodes(
|
||||
incomingData.graphData.nodes.map((node, index) => {
|
||||
return {
|
||||
id: index,
|
||||
node: node.id,
|
||||
name: node.name,
|
||||
check: "checkbox",
|
||||
selected: false,
|
||||
};
|
||||
})
|
||||
);
|
||||
addLinks(incomingData.graphData.links);
|
||||
});
|
||||
|
||||
socket.on("graph_data", (incomingData) => {
|
||||
if (incomingData.graphData) {
|
||||
setGraphData(incomingData.graphData);
|
||||
}
|
||||
if (incomingData.selectedNodes) {
|
||||
updateCheckboxes(
|
||||
incomingData.selectedNodes.map((node, index) => {
|
||||
return { node: node, value: true };
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("graph_results", (incomingData) => {
|
||||
setCounts(incomingData);
|
||||
});
|
||||
|
||||
socket.on("node_infos", (incomingData) => {
|
||||
setNodeInfos(incomingData.nodeInfos);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default reduxConnect(null, {
|
||||
setNodes,
|
||||
updateCheckboxes,
|
||||
setCounts,
|
||||
setNodeInfos,
|
||||
setGraphFiles,
|
||||
setLoading,
|
||||
selectGraphFile,
|
||||
addLinks,
|
||||
setGraphData,
|
||||
})(SocketConnection);
|
||||
21
buildscripts/libdeps/graph_visualizer_web_stack/src/index.js
Normal file
21
buildscripts/libdeps/graph_visualizer_web_stack/src/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||
import { ThemeProvider } from "@material-ui/core/styles";
|
||||
|
||||
import theme from "./theme";
|
||||
import store from "./redux/store";
|
||||
|
||||
import App from "./App";
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
|
||||
document.querySelector("#root")
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
import { initialState } from "./store";
|
||||
|
||||
export const counts = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case "setCounts":
|
||||
return action.payload;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const setCounts = (counts) => ({
|
||||
type: "setCounts",
|
||||
payload: counts,
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { initialState } from "./store";
|
||||
|
||||
export const findNode = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case "setFindNode":
|
||||
return action.payload;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const setFindNode = (node) => ({
|
||||
type: "setFindNode",
|
||||
payload: node,
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { initialState } from "./store";
|
||||
|
||||
export const graphData = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case "setGraphData":
|
||||
return action.payload;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const setGraphData = (graphData) => ({
|
||||
type: "setGraphData",
|
||||
payload: graphData,
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { initialState } from "./store";
|
||||
|
||||
export const graphFiles = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case "setGraphFiles":
|
||||
return action.payload;
|
||||
case "selectGraphFile":
|
||||
const newState = state.map((graphFile, index) => {
|
||||
if (action.payload.hash == graphFile.git) {
|
||||
graphFile.selected = action.payload.selected;
|
||||
} else {
|
||||
graphFile.selected = false;
|
||||
}
|
||||
return graphFile;
|
||||
});
|
||||
return newState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const setGraphFiles = (graphFiles) => ({
|
||||
type: "setGraphFiles",
|
||||
payload: graphFiles,
|
||||
});
|
||||
|
||||
export const selectGraphFile = (graphFiles) => ({
|
||||
type: "selectGraphFile",
|
||||
payload: graphFiles,
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { initialState } from "./store";
|
||||
|
||||
export const links = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case "addLinks":
|
||||
return action.payload;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const addLinks = (links) => ({
|
||||
type: "addLinks",
|
||||
payload: links,
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { initialState } from "./store";
|
||||
|
||||
export const loading = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case "setLoading":
|
||||
return action.payload;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const setLoading = (loading) => ({
|
||||
type: "setLoading",
|
||||
payload: loading,
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { initialState } from "./store";
|
||||
|
||||
export const nodeInfo = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case "setNodeInfos":
|
||||
return action.payload;
|
||||
case "addNodeInfo":
|
||||
return [...state, action.payload];
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const setNodeInfos = (nodeInfos) => ({
|
||||
type: "setNodeInfos",
|
||||
payload: nodeInfos,
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import { initialState } from "./store";
|
||||
|
||||
export const nodes = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case "addNode":
|
||||
var arr = Object.assign(state);
|
||||
return [...arr, action.payload];
|
||||
case "setNodes":
|
||||
return action.payload;
|
||||
case "updateSelected":
|
||||
var newState = Object.assign(state);
|
||||
newState[action.payload.index].selected = action.payload.value;
|
||||
return newState;
|
||||
case "updateCheckbox":
|
||||
var newState = Object.assign(state);
|
||||
newState = state.map((stateNode) => {
|
||||
if (stateNode.node == action.payload.node) {
|
||||
if (action.payload.value == "flip") {
|
||||
stateNode.selected = !stateNode.selected;
|
||||
} else {
|
||||
stateNode.selected = action.payload.value;
|
||||
}
|
||||
}
|
||||
return stateNode;
|
||||
});
|
||||
return newState;
|
||||
case "updateCheckboxes":
|
||||
var newState = state.map((stateNode, index) => {
|
||||
const nodeToUpdate = action.payload.filter(
|
||||
(node) => stateNode.node == node.node
|
||||
);
|
||||
if (nodeToUpdate.length > 0) {
|
||||
stateNode.selected = nodeToUpdate[0].value;
|
||||
}
|
||||
return stateNode;
|
||||
});
|
||||
return newState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const addNode = (node) => ({
|
||||
type: "addNode",
|
||||
payload: node,
|
||||
});
|
||||
|
||||
export const setNodes = (nodes) => ({
|
||||
type: "setNodes",
|
||||
payload: nodes,
|
||||
});
|
||||
|
||||
export const updateSelected = (newValue) => ({
|
||||
type: "updateSelected",
|
||||
payload: newValue,
|
||||
});
|
||||
|
||||
export const updateCheckbox = (newValue) => ({
|
||||
type: "updateCheckbox",
|
||||
payload: newValue,
|
||||
});
|
||||
|
||||
export const updateCheckboxes = (newValue) => ({
|
||||
type: "updateCheckboxes",
|
||||
payload: newValue,
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
import { createStore, combineReducers } from "redux";
|
||||
import { nodes } from "./nodes";
|
||||
import { graphFiles } from "./graphFiles";
|
||||
import { counts } from "./counts";
|
||||
import { nodeInfo } from "./nodeInfo";
|
||||
import { loading } from "./loading";
|
||||
import { links } from "./links";
|
||||
import { graphData } from "./graphData";
|
||||
import { findNode } from "./findNode";
|
||||
|
||||
export const initialState = {
|
||||
loading: false,
|
||||
graphFiles: [
|
||||
// {id: 0, value: 'graphfile.graphml', version: 1, git: '1234567', selected: false}
|
||||
],
|
||||
nodes: [
|
||||
{
|
||||
id: 0,
|
||||
node: "test/test1.so",
|
||||
name: "test1",
|
||||
check: "checkbox",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
node: "test/test2.so",
|
||||
name: "test2",
|
||||
check: "checkbox",
|
||||
selected: false,
|
||||
},
|
||||
],
|
||||
links: [{ source: "test/test1.so", target: "test/test2.so" }],
|
||||
graphData: {
|
||||
nodes: [
|
||||
// {id: 'test/test1.so', name: 'test1.so'},
|
||||
// {id: 'test/test2.so', name: 'test2.so'}
|
||||
],
|
||||
links: [
|
||||
// {source: 'test/test1.so', target: 'test/test2.so'}
|
||||
],
|
||||
},
|
||||
counts: [{ id: 0, type: "node2", value: 0 }],
|
||||
findNode: "",
|
||||
nodeInfo: [
|
||||
{
|
||||
id: 0,
|
||||
node: "test/test.so",
|
||||
name: "test",
|
||||
attribs: [{ name: "test", value: "test" }],
|
||||
dependers: [{ node: "test/test3.so", symbols: [] }],
|
||||
dependencies: [{ node: "test/test2.so", symbols: [] }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const getLoading = (state) => {
|
||||
return { loading: state };
|
||||
};
|
||||
|
||||
export const getGraphFiles = (state) => {
|
||||
return {
|
||||
loading: state.loading,
|
||||
graphFiles: state.graphFiles,
|
||||
};
|
||||
};
|
||||
|
||||
export const getNodeInfos = (state) => {
|
||||
return {
|
||||
nodeInfos: state.nodeInfo,
|
||||
};
|
||||
};
|
||||
|
||||
export const getCounts = (state) => {
|
||||
const counts = state.counts;
|
||||
return {
|
||||
counts: state.counts,
|
||||
};
|
||||
};
|
||||
|
||||
export const getRows = (state) => {
|
||||
return {
|
||||
rowCount: state.nodes.length,
|
||||
rowGetter: ({ index }) => state.nodes[index],
|
||||
checkBox: ({ index }) => state.nodes[index].selected,
|
||||
nodes: state.nodes,
|
||||
};
|
||||
};
|
||||
|
||||
export const getSelected = (state) => {
|
||||
return {
|
||||
selectedNodes: state.nodes.filter((node) => node.selected),
|
||||
selectedEdges: [],
|
||||
loading: state.loading,
|
||||
};
|
||||
};
|
||||
|
||||
export const getNodes = (state) => {
|
||||
return {
|
||||
nodes: state.nodes,
|
||||
loading: state.loading,
|
||||
};
|
||||
};
|
||||
|
||||
export const getGraphData = (state) => {
|
||||
return {
|
||||
nodes: state.nodes,
|
||||
graphData: state.graphData,
|
||||
loading: state.loading,
|
||||
findNode: state.findNode,
|
||||
};
|
||||
};
|
||||
|
||||
export const getFullState = (state) => {
|
||||
return { state };
|
||||
};
|
||||
|
||||
const store = createStore(
|
||||
combineReducers({
|
||||
nodes,
|
||||
counts,
|
||||
nodeInfo,
|
||||
graphFiles,
|
||||
loading,
|
||||
links,
|
||||
graphData,
|
||||
findNode,
|
||||
}),
|
||||
initialState
|
||||
);
|
||||
export default store;
|
||||
22
buildscripts/libdeps/graph_visualizer_web_stack/src/theme.js
Normal file
22
buildscripts/libdeps/graph_visualizer_web_stack/src/theme.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { lightGreen, blueGrey, grey } from "@material-ui/core/colors";
|
||||
import { createMuiTheme } from "@material-ui/core/styles";
|
||||
|
||||
// A custom theme for this app
|
||||
const theme = createMuiTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
light: lightGreen[300],
|
||||
main: lightGreen[500],
|
||||
dark: lightGreen[700],
|
||||
},
|
||||
secondary: {
|
||||
light: grey[300],
|
||||
main: grey[500],
|
||||
dark: grey[800],
|
||||
darkAccent: "#4d4d4d",
|
||||
},
|
||||
mode: "dark",
|
||||
},
|
||||
});
|
||||
|
||||
export default theme;
|
||||
Reference in New Issue
Block a user