Skip to content

Commit

Permalink
WIP for connector
Browse files Browse the repository at this point in the history
  • Loading branch information
Christopher Plantijn committed Feb 14, 2017
1 parent 6fe9237 commit 94cd55e
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 39 deletions.
2 changes: 2 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"no-underscore-dangle": 0,
"global-require": 0,
"comma-dangle": 0,
"consistent-return": 0,
"react/jsx-closing-bracket-location": 0,
"react/react-in-jsx-scope": 0,
"react/require-default-props": 0
}
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@
},
"dependencies": {
"react": "^15.4.2",
"react-component-resizable": "^1.1.0-rc1",
"react-dom": "^15.4.2",
"react-hot-loader": "^3.0.0-beta.6",
"react-resizable": "^1.6.0",
"short-id": "0.1.0-1"
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Component, PropTypes } from 'react';

export default class Node extends Component {
export default class ConnectNode extends Component {
render() {
const { children = null } = this.props;
return (
<div className="rec-node">
<div {...this.props}>
{ children }
</div>
);
Expand Down
95 changes: 81 additions & 14 deletions src/js/components/ElementConnector/Connectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,91 @@ export default class Connectors extends Component {
constructor(props) {
super(props);
this._drawLines = this._drawLines.bind(this);
this._animateLines = this._animateLines.bind(this);
this._svgContainer = null;
this._paths = [];
}

componentDidMount() {
const { nodeMap = null } = this.props;
if (nodeMap) {
this._animateLines();
}
}

componentDidUpdate(oldProps) {
const { nodeMap: oldNodeMap = null } = oldProps;
const { nodeMap = null } = this.props;

if (oldNodeMap && nodeMap) {
const oldNodeLen = Object.keys(oldNodeMap).length;
const currentNodeLen = Object.keys(nodeMap).length;
if (currentNodeLen !== oldNodeLen) {
this._animateLines(oldNodeLen - 1, currentNodeLen > oldNodeLen);
}
} else if (!oldNodeMap && nodeMap) {
this._animateLines();
}
}

_animateLines(startIndex = 0, adding = true) {
const {
animate = true,
animationDuration = 400,
animationDelay = 0,
animationEasing = 'ease-in-out'
} = this.props;

if (!this._svgContainer || !animate || !this._paths.length) {
return false;
}

let delayQueue = animationDelay;
const validPaths = this._paths.filter(Boolean);

validPaths.forEach((path, index) => {
if (index >= startIndex) {
const length = path.getTotalLength();
path.style.transition = path.style.transition = 'none';
path.style.strokeDasharray = `${length} ${length}`;
path.style.strokeDashoffset = length;
path.getBoundingClientRect();
path.style.transition = path.style.transition = `stroke-dashoffset ${animationDuration / 1000}s ${animationEasing}`;

setTimeout(() => {
path.style.strokeDashoffset = 0;
}, delayQueue);
delayQueue += animationDuration;
}
});
}

_drawLines() {
const {
nodeMap = null,
strokeWidth = 5,
strokeColor = 'red',
strokeOpacity,
lineFill = 'transparent'
strokeWidth = 1,
strokeColor = 'black',
strokeOpacity = 1,
lineFill = 'transparent',
} = this.props;

if (!nodeMap) {
if (!nodeMap || !this._svgContainer) {
return null;
}

console.log('map', nodeMap);
this._paths = [];
const containerTop = this._svgContainer.getBoundingClientRect().top;

return nodeMap.map((node, index) => {
if (index < nodeMap.length - 1) {
const nextNode = nodeMap[index + 1];
const lineHeight = Math.round((nextNode.top + (nextNode.height / 2)) - (node.top + (node.height / 2)));
const lineWidth = Math.round(nextNode.left - node.left);
const lineTop = Math.round(node.top + (node.height / 2)) - strokeWidth;
const cpt = Math.round(lineWidth * Math.min(lineHeight / 300, 1));
let d = `M0,${strokeWidth} C${cpt},0 ${lineWidth - cpt},${lineHeight + strokeWidth} ${lineWidth},${lineHeight + strokeWidth}`;
const startPosX = node.width / 2;
const startPosY = (node.top - containerTop) + node.height;
const endPosX = nextNode.width / 2;
const endPosY = Math.round(startPosY + (nextNode.top - node.top - nextNode.height));
console.log('nextNode', nextNode.top, nextNode.height);
const d = `M${startPosX},${startPosY} ${endPosX},${endPosY}`;

d = `M${(node.width / 2) + node.left},${node.top} C${cpt},0 ${lineWidth - cpt},${lineHeight + strokeWidth} ${(nextNode.width / 2) + nextNode.left},${nextNode.top - nextNode.height}`;
return (
<path
key={shortId.generate()}
Expand All @@ -39,16 +98,24 @@ export default class Connectors extends Component {
strokeWidth={strokeWidth}
opacity={strokeOpacity}
fill={lineFill}
ref={(path) => {
this._paths.push(path);
}}
/>
);
}
})
});
}

render() {
const connectors = this._drawLines();
return (
<svg className="rec-svg-overlay">
<svg
ref={(container) => {
this._svgContainer = container;
}}
className="rec-svg-overlay"
>
{ connectors }
</svg>
);
Expand Down
52 changes: 39 additions & 13 deletions src/js/components/ElementConnector/Workspace.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Component, PropTypes, Children, cloneElement } from 'react';
import { Component, PropTypes, Children, cloneElement, isValidElement } from 'react';
import { findDOMNode } from 'react-dom';
import Connectors from './Connectors';
import shortId from 'short-id';
import Resizable from 'react-component-resizable';

export default class Workspace extends Component {
constructor(props) {
Expand All @@ -11,17 +13,19 @@ export default class Workspace extends Component {
};
}

componentDidMount() {
this._calculatePositions();
}

_calculatePositions() {
const { children } = this.props;
const nodeMap = [];

Object.keys(this.refs)
.forEach((key) => {
const boundingBox = findDOMNode(this.refs[key]).getBoundingClientRect();
let id = 'connect';

if (this.refs[key].props.id) {
id = this.refs[key].props.id;
}
console.log(id, boundingBox)
nodeMap.push({
key,
top: boundingBox.top,
Expand All @@ -36,17 +40,39 @@ export default class Workspace extends Component {
});
}

_recursiveCloneChildren(children) {
return Children.map(children, (child, key) => {
if (isValidElement(child)) {
let newProps = {};

if (child.type.displayName === 'ConnectNode') {
newProps = { ref: shortId.generate() };
} else if (child.props.children) {
newProps.children = this._recursiveCloneChildren(child.props.children);
}
return cloneElement(child, newProps);
}
return child;
});
}

render() {
const { children } = this.props;
const renderedChildren = this._recursiveCloneChildren(children);
return (
<div className="rec-workspace">
<Connectors nodeMap={this.state.nodeMap} />
{Children.map(children, (child, key) => {
return cloneElement(child, {
ref: `node-${key}`
});
})}
</div>
<Resizable
className="rec-workspace"
onResize={this._calculatePositions}
>
<Connectors
animate
animationDuration={360}
animationEasing="linear"
strokeWidth={1}
nodeMap={this.state.nodeMap}
/>
{ renderedChildren }
</Resizable>
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/js/components/ElementConnector/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export Node from './Node';
export ConnectNode from './ConnectNode';
export Workspace from './Workspace';
55 changes: 48 additions & 7 deletions src/js/container/App.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,57 @@
import { Component } from 'react';
import { Workspace, Node } from '../components/ElementConnector';
import { Workspace, ConnectNode } from '../components/ElementConnector';

export default class App extends Component {
constructor() {
super();
this._generateNodes = this._generateNodes.bind(this);
this.state = {
nodes: [1, 2, 3, 4]
};
}

componentDidMount() {
// setTimeout(() => {
// this.setState({
// nodes: [1, 2, 3, 4, 5, 6]
// })
// }, 3000)
}

_generateNodes() {
return this.state.nodes.map((node) => {
return (
<ConnectNode
key={shortId.generate()}
>
Hey
</ConnectNode>
);
})
}
render() {
// const nodes = this._generateNodes();
return (
<Workspace>
<Node>
<h1>Test 1</h1>
</Node>
<Node>
<h1>Test 2</h1>
</Node>
<div className="rec-node"><span>Hey</span></div>
<ConnectNode style={{background:'red'}}>
<h1>Test</h1>
</ConnectNode>
<ConnectNode style={{background:'red'}}>
<span style={{display: 'block', width: '100%'}}>Hey</span>
</ConnectNode>
<ConnectNode style={{background:'red'}}>
<h1>Test</h1>
</ConnectNode>
<div id="outsider" style={{background:'red'}}>
<ConnectNode id="out-connect">
<div style={{display:'block'}}>
<div>
<h1>Test Out</h1>
</div>
</div>
</ConnectNode>
</div>
</Workspace>
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/styles/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
width: 100%;
}
.rec-node {
margin-top: 6rem;
text-align: center;
border: 1px solid black;
margin-top: 6rem;
display: block;
}

0 comments on commit 94cd55e

Please sign in to comment.