import React from 'react'; import PropTypes from 'prop-types'; import { List } from 'immutable'; import Base from './Base'; import style from './style'; import Path from './path'; const connectorMargin = 20; /** @extends React.PureComponent */ class VerticalLayout extends Base { static defaultProps = { withConnectors: false, spacing: 10 } constructor(props) { super(props); this.state = { childTransforms: List() }; } updateChildTransforms(childBoxes) { return childBoxes.reduce((transforms, box, i) => ( transforms.set(i, `translate(${ box.offsetX } ${ box.offsetY })`) ), this.state.childTransforms); } makeCurve(box) { const thisBox = this.getBBox(); const distance = Math.abs(box.offsetY + box.axisY - thisBox.axisY); if (distance >= 15) { const curve = (box.axisY + box.offsetY > thisBox.axisY) ? 10 : -10; return new Path() // Left .moveTo({ x: 10, y: box.axisY + box.offsetY - curve }) .quadraticCurveTo({ cx: 0, cy: curve, x: 10, y: curve, relative: true }) .lineTo({ x: box.offsetX + box.axisX1 }) // Right .moveTo({ x: thisBox.width - 10, y: box.axisY + box.offsetY - curve }) .quadraticCurveTo({ cx: 0, cy: curve, x: -10, y: curve, relative: true }) .lineTo({ x: box.offsetX + box.axisX2 }); } else { const anchor = box.offsetY + box.axisY - thisBox.axisY; return new Path() // Left .moveTo({ x: 0, y: thisBox.axisY }) .cubicCurveTo({ cx1: 15, cy1: 0, cx2: 10, cy2: anchor, x: 20, y: anchor, relative: true }) .lineTo({ x: box.offsetX + box.axisX1 }) // Right .moveTo({ x: thisBox.width, y: thisBox.axisY }) .cubicCurveTo({ cx1: -15, cy1: 0, cx2: -10, cy2: anchor, x: -20, y: anchor, relative: true }) .lineTo({ x: box.offsetX + box.axisX2 }); } } makeSide(box) { const thisBox = this.getBBox(); const distance = Math.abs(box.offsetY + box.axisY - thisBox.axisY); if (distance >= 15) { const shift = (box.offsetY + box.axisY > thisBox.axisY) ? 10 : -10; const edge = box.offsetY + box.axisY - shift; return new Path() // Left .moveTo({ x: 0, y: thisBox.axisY }) .quadraticCurveTo({ cx: 10, cy: 0, x: 10, y: shift, relative: true }) .lineTo({ y: edge }) // Right .moveTo({ x: thisBox.width, y: thisBox.axisY }) .quadraticCurveTo({ cx: -10, cy: 0, x: -10, y: shift, relative: true }) .lineTo({ y: edge }); } } preReflow() { return this.children; } reflow() { const { spacing, withConnectors } = this.props; const childBoxes = this.children.map(child => child.getBBox()); const horizontalCenter = childBoxes.reduce((center, box) => Math.max(center, box.width / 2), 0); const margin = withConnectors ? connectorMargin : 0; const width = childBoxes.reduce((width, box) => Math.max(width, box.width), 0) + 2 * margin; const height = childBoxes.reduce((height, box) => height + box.height, 0) + (childBoxes.length - 1) * spacing; this.setBBox({ width, height }, { axisY: true, axisX1: true, axisX2: true }); let offset = 0; childBoxes.forEach(box => { box.offsetX = horizontalCenter - box.width / 2 + margin; box.offsetY = offset; offset += spacing + box.height; }); return this.setStateAsync({ childTransforms: this.updateChildTransforms(childBoxes), connectorPaths: withConnectors ? [ ...childBoxes.map(box => this.makeCurve(box)), this.makeSide(childBoxes[0]), this.makeSide(childBoxes[childBoxes.length - 1]) ].join('') : '' }); } childRef = i => child => this.children[i] = child render() { const { children } = this.props; const { childTransforms, connectorPaths } = this.state; this.children = []; return { React.Children.map(children, (child, i) => ( { React.cloneElement(child, { ref: this.childRef(i) }) } )) } ; } } VerticalLayout.propTypes = { children: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.node), PropTypes.node ]).isRequired, spacing: PropTypes.number, withConnectors: PropTypes.bool }; export default VerticalLayout;