Skip to content
This repository has been archived by the owner on Oct 17, 2018. It is now read-only.

Commit

Permalink
Added drawing of connections between services and resources
Browse files Browse the repository at this point in the history
  • Loading branch information
quintesse committed Jul 25, 2018
1 parent 0a278c5 commit 2f2b903
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 44 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"element-resize-event": "^2.0.9",
"lodash": "^4.17.10",
"prop-types": "latest",
"react": "^16.4.1",
Expand Down
33 changes: 33 additions & 0 deletions src/t2-design/components/DOMRef.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import ReactDOM from "react-dom";
import PropTypes from 'prop-types';

class DOMRef extends React.Component {

constructor(props) {
super(props);
}

componentDidMount() {
let node = ReactDOM.findDOMNode(this);
this.props.domRef(this.props, node);
}

componentWillUnmount() {
this.props.domRef(this.props, null);
}

render() {
return this.props.children;
}
}

DOMRef.propTypes = {
domRef: PropTypes.func,
};

DOMRef.defaultProps = {
domRef: ()=>{},
};

export default DOMRef;
15 changes: 6 additions & 9 deletions src/t2-design/components/IconItem.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
import React from 'react';
import ReactDOM from "react-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames';
import _ from "lodash";

import { Icon } from 'semantic-ui-react'

import Item from './Item'

import './IconItem.css';

const IconItem = (props) => (
<Icon
<Item {...props}><Icon
name={props.icon}
className={classNames('topo-item', 'topo-item-icon', 'selectable', {'selected': props.selected})}
bordered={props.selected}
size="huge"
onClick={() => props.onSelect(props)}
/>
/></Item>
);

IconItem.propTypes = {
icon: PropTypes.string.isRequired,
klass: PropTypes.string.isRequired,
selected: PropTypes.bool,
onSelect: PropTypes.func,
belongsTo: PropTypes.string,
};

IconItem.defaultProps = {
icon: 'question circle outline',
selected: false,
onSelect: ()=>{},
belongsTo: null,
};

export default IconItem;
27 changes: 27 additions & 0 deletions src/t2-design/components/Item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import ReactDOM from "react-dom";
import PropTypes from 'prop-types';

import DOMRef from './DOMRef'

const Item = (props) => (
<DOMRef domRef={(bp, node) => props.domRef(props, node)}>{props.children}</DOMRef>
);

Item.propTypes = {
type: PropTypes.string.isRequired,
klass: PropTypes.string.isRequired,
domRef: PropTypes.func,
selected: PropTypes.bool,
onSelect: PropTypes.func,
belongsTo: PropTypes.string,
};

Item.defaultProps = {
domRef: ()=>{},
selected: false,
onSelect: ()=>{},
belongsTo: null,
};

export default Item;
26 changes: 21 additions & 5 deletions src/t2-design/components/Line.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';

const Line = (props) => (
<svg></svg>
);
const Line = (props) => {
if (props.fromNode && props.toNode) {
return (
<path
d={pathDimensions(...calculateAnchorPoints(props.fromNode, props.toNode))}
style={{stroke: 'black', strokeWidth: '3px', fill: 'none' }} />
)
} else {
return null;
}
}

const pathDimensions = (x1, y1, x2, y2) => `M${x1},${y1} L${x2},${y2}`;

const calculateAnchorPoints = (from, to) => {
let fromRect = from.getBoundingClientRect();
let toRect = to.getBoundingClientRect();
return [fromRect.x + fromRect.width / 2.0, fromRect.y + fromRect.height / 2.0, toRect.x + toRect.width / 2.0, toRect.y + toRect.height / 2.0];
}

Line.propTypes = {
from: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
fromNode: PropTypes.object,
toNode: PropTypes.object,
type: PropTypes.string.isRequired,
selected: PropTypes.bool,
onSelect: PropTypes.func,
Expand Down
19 changes: 8 additions & 11 deletions src/t2-design/components/Service.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,37 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import _ from 'lodash';

import { Card, Icon } from 'semantic-ui-react'

import Item from './Item'

import './Service.css';

const Service = (props) => (
<Card
<Item {...props}><Card
className={classNames('topo-item', 'topo-service', 'selectable', {'selected': props.selected})}
color={props.selected?'red':'grey'}
color={props.selected ? 'red' : 'grey'}
onClick={() => props.onSelect(props)}
>
<Card.Content textAlign="left">
<Icon name={props.icon} className="floatleft" size="big" />
<Icon name={props.icon} className="floatleft" size="big"/>
<Card.Header>{props.name}</Card.Header>
<Card.Meta>Service / Deployment / Build</Card.Meta>
</Card.Content>
</Card>
</Card></Item>
);

Service.propTypes = {
name: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
selected: PropTypes.bool,
onSelect: PropTypes.func,
belongsTo: PropTypes.string,
};

Service.defaultProps = {
icon: 'cogs',
klass: 'service',
selected: false,
onSelect: ()=>{},
belongsTo: null,
};

export default Service;
7 changes: 7 additions & 0 deletions src/t2-design/components/Topology.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@
.topo .topo-service-stack .internals {
height: 120px !important;
}

.topo .topo-svg {
position: absolute;
z-index: -10;
width: 100%;
height: 100%;
}
66 changes: 47 additions & 19 deletions src/t2-design/components/Topology.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import elementResizeEvent from 'element-resize-event';
import _ from "lodash";

import { Grid } from 'semantic-ui-react'

import DOMRef from "./DOMRef";
import ConfigMap from "./ConfigMap";
import Route from "./Route";
import Service from './Service';
Expand All @@ -18,6 +21,7 @@ class Topology extends React.Component {
super(props);
this.state = {
selectedItemId: null,
nodeDOMRefs: {},
};
}

Expand All @@ -34,15 +38,32 @@ class Topology extends React.Component {
case 'storage': Node = Storage; break;
default: return null;
}
return (<Node key={id} id={id} {...node} onSelect={this.onSelectItem} selected={this.state.selectedItemId===id} />);
return (<Node key={id} id={id} {...node} domRef={this.onItemDOMRef} onSelect={this.onSelectItem} selected={this.state.selectedItemId===id} />);
}

createEdgeElements = (edges) => {
return Object.entries(edges).map(([id, edge]) => this.createEdgeElement(id, edge));
}

createEdgeElement = (id, edge) => {
return (<Line key={id} id={id} {...edge} />);
return (<Line key={id} id={id} {...edge} fromNode={this.state.nodeDOMRefs[edge.from]} toNode={this.state.nodeDOMRefs[edge.to]} />);
}

onDOMRef = (item, node) => {
elementResizeEvent(node, this.onResize)
}

onItemDOMRef= (item, node) => {
elementResizeEvent(node, this.onResize)
this.setState((prevState, props) => {
return ({ nodeDOMRefs: { ...prevState.nodeDOMRefs, [item.id]: node } });
});
}

onResize = () => {
if (!_.isEmpty(this.state.nodeDOMRefs)) {
this.forceUpdate();
}
}

onSelectItem = (item) => {
Expand All @@ -65,24 +86,31 @@ class Topology extends React.Component {
}

render() {
let elems = this.createNodeElements(this.props.layout.nodes);
let nodeElems = this.createNodeElements(this.props.layout.nodes);
let serviceElems = nodeElems.filter(e => e.props.klass === 'service');
let edgeElems = this.createEdgeElements(this.props.layout.edges);
return (
<div className={classNames('topo')}>
{elems.filter(e => e.props.klass === 'service').map(se => {
return (<Grid
key={se.props.id}
className={classNames('topo-service-stack')}
textAlign="center"
verticalAlign="middle"
>
{this.rowForService(elems, "external", "externals", se.props.id)}
<Grid.Row columns="1" className="services">
<Grid.Column>{se}</Grid.Column>
</Grid.Row>
{this.rowForService(elems, "internal", "internals", se.props.id)}
</Grid>);
})}
</div>
<DOMRef domRef={this.onDOMRef}>
<div className={classNames('topo')}>
{serviceElems.map(se => {
return (<Grid
key={se.props.id}
className={classNames('topo-service-stack')}
textAlign="center"
verticalAlign="middle"
>
{this.rowForService(nodeElems, "external", "externals", se.props.id)}
<Grid.Row columns="1" className="services">
<Grid.Column>{se}</Grid.Column>
</Grid.Row>
{this.rowForService(nodeElems, "internal", "internals", se.props.id)}
</Grid>);
})}
<svg className={classNames('topo-svg')}>
{edgeElems}
</svg>
</div>
</DOMRef>
);
}
}
Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,10 @@ electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.30:
version "1.3.52"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.52.tgz#d2d9f1270ba4a3b967b831c40ef71fb4d9ab5ce0"

element-resize-event@^2.0.9:
version "2.0.9"
resolved "https://registry.yarnpkg.com/element-resize-event/-/element-resize-event-2.0.9.tgz#2f5e1581a296eb5275210c141bc56342e218f876"

elliptic@^6.0.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
Expand Down

0 comments on commit 2f2b903

Please sign in to comment.