@6thquake/react-material
Version:
React components that implement Google's Material Design.
307 lines (267 loc) • 7.97 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import PropTypes from 'prop-types';
import React, { Component, PureComponent } from 'react';
import ReactDOM from 'react-dom';
import PubSub from 'pubsub-js';
import Arrow from '../Arrow.js';
const defaultAnchor = {
x: 0.5,
y: 1
};
const defaultBorderColor = '#000';
const defaultBorderStyle = 'solid';
const defaultBorderWidth = 1;
const optionalStyleProps = process.env.NODE_ENV !== "production" ? {
borderColor: PropTypes.string,
borderStyle: PropTypes.string,
borderWidth: PropTypes.number,
className: PropTypes.string,
zIndex: PropTypes.number
} : {};
export default class LineTo extends Component {
constructor(props) {
super(props);
this.state = {
fromAnchor: 'center middle',
toAnchor: 'center middle'
};
}
componentWillMount() {
// this.fromAnchor = this.parseAnchor(this.props.fromAnchor);
// this.toAnchor = this.parseAnchor(this.props.toAnchor);
this.delay = this.parseDelay(this.props.delay);
}
componentDidMount() {
this.delay = this.parseDelay(this.props.delay);
if (typeof this.delay !== 'undefined') {
this.deferUpdate(this.delay);
}
this.pubsub_token = PubSub.subscribe('boxMove', (msg, data) => {
this.setState({
fromAnchor: data[0],
toAnchor: data[1]
});
});
}
componentWillReceiveProps(nextProps) {
/* if (nextProps.fromAnchor !== this.props.fromAnchor) {
this.fromAnchor = this.parseAnchor(this.props.fromAnchor);
}
if (nextProps.toAnchor !== this.props.toAnchor) {
this.toAnchor = this.parseAnchor(this.props.toAnchor);
} */
this.delay = this.parseDelay(nextProps.delay);
if (typeof this.delay !== 'undefined') {
this.deferUpdate(this.delay);
}
}
componentWillUnmount() {
if (this.t) {
clearTimeout(this.t);
this.t = null;
}
}
shouldComponentUpdate() {
// Always update component if the parent component has been updated.
// The reason for this is that we would not only like to update
// this component when the props have changed, but also when
// the position of our target elements has changed.
// We could return true only if the positions of `from` and `to` have
// changed, but that may be expensive and unnecessary.
return true;
} // Forced update after delay (MS)
deferUpdate(delay) {
if (this.t) {
clearTimeout(this.t);
}
this.t = setTimeout(() => this.forceUpdate(), delay);
}
parseDelay(value) {
if (typeof value === 'undefined') {
return value;
}
if (typeof value === 'boolean' && value) {
return 0;
}
const delay = parseInt(value, 10);
if (isNaN(delay) || !isFinite(delay)) {
throw new Error(`LinkTo could not parse delay attribute "${value}"`);
}
return delay;
}
parseAnchorText(value) {
// Try to infer the relevant axis.
switch (value) {
case 'top':
return {
y: 0
};
case 'left':
return {
x: 0
};
case 'top-middle':
return {
y: 0.25
};
case 'left-center':
return {
x: 0.25
};
case 'middle':
return {
y: 0.5
};
case 'center':
return {
x: 0.5
};
case 'middle-bottom':
return {
y: 0.75
};
case 'center-right':
return {
x: 0.75
};
case 'bottom':
return {
y: 1
};
case 'right':
return {
x: 1
};
}
return null;
}
parseAnchor(value) {
if (!value) {
return defaultAnchor;
}
const parts = value.split(' ');
if (parts.length > 2) {
throw new Error('LinkTo anchor format is "<x> <y>"');
}
const [x, y] = parts;
return _extends({}, defaultAnchor, x ? this.parseAnchorText(x) : {}, y ? this.parseAnchorText(y) : {});
}
detect() {
const {
from,
to
} = this.props;
if (!from || !to) {
return false;
}
const anchor0 = this.parseAnchor(this.state.fromAnchor);
const anchor1 = this.parseAnchor(this.state.toAnchor);
const box0 = from.getBoundingClientRect();
const box1 = to.getBoundingClientRect();
const offsetX = window.pageXOffset;
const offsetY = window.pageYOffset;
const x0 = box0.left + box0.width * anchor0.x + offsetX;
const x1 = box1.left + box1.width * anchor1.x + offsetX;
const y0 = box0.top + box0.height * anchor0.y + offsetY;
const y1 = box1.top + box1.height * anchor1.y + offsetY;
return {
x0,
y0,
x1,
y1
};
}
render() {
const points = this.detect();
const doc = document.documentElement;
const body = document.body;
const offsetX = (doc.scrollLeft || body.scrollLeft || 0) - (doc.clientLeft || body.clientLeft || 0);
const offsetY = (doc.scrollTop || body.scrollTop || 0) - (doc.clientTop || body.clientTop || 0);
const toX = points.x1 - offsetX - 120;
const toY = points.y1 - offsetY - 415;
return points ? React.createElement("div", null, React.createElement(Line, _extends({}, points, this.props, {
toX: toX,
toY: toY
}))) : null;
}
}
process.env.NODE_ENV !== "production" ? LineTo.propTypes = _extends({}, {
// from: PropTypes.string.isRequired,
// to: PropTypes.string.isRequired,
// within: PropTypes.string,
// fromAnchor: PropTypes.string,
// toAnchor: PropTypes.string,
delay: PropTypes.oneOfType([PropTypes.number, PropTypes.bool])
}, optionalStyleProps) : void 0;
export class Line extends PureComponent {
componentDidMount() {
document.body.appendChild(this.el);
document.body.appendChild(this.arrow);
}
componentWillUnmount() {
document.body.removeChild(this.el);
document.body.removeChild(this.arrow);
}
render() {
const {
x0,
y0,
x1,
y1
} = this.props;
const dy = y1 - y0;
const dx = x1 - x0;
const angle = Math.atan2(dy, dx) * 180 / Math.PI;
const length = Math.sqrt(dx * dx + dy * dy);
const linePositionStyle = {
position: 'absolute',
top: `${y0}px`,
left: `${x0}px`,
width: `${length}px`,
zIndex: Number.isFinite(this.props.zIndex) ? String(this.props.zIndex) : '1',
transform: `rotate(${angle}deg)`,
// Rotate around (x0, y0)
transformOrigin: '0 0'
};
const arrowPositionStyle = {
transform: `rotate(${angle}deg )`,
padding: '0',
margin: '0',
position: 'absolute',
top: `${y1 - 10}px`,
left: `${x1 - 5}px`,
zIndex: '2'
};
const defaultStyle = {
borderTopColor: this.props.borderColor || defaultBorderColor,
borderTopStyle: this.props.borderStyle || defaultBorderStyle,
borderTopWidth: this.props.borderWidth || defaultBorderWidth
};
const lineprops = {
// className: this.props.className,
style: _extends({}, defaultStyle, linePositionStyle)
}; // We need a wrapper element to prevent an exception when then
// React component is removed. This is because we manually
// move the rendered DOM element after creation.
return React.createElement("div", null, React.createElement("div", {
className: "react-lineto-placeholder"
}, React.createElement("div", _extends({
ref: el => {
this.el = el;
}
}, lineprops))), React.createElement("div", {
ref: arrow => {
this.arrow = arrow;
},
style: arrowPositionStyle
}, React.createElement(Arrow, {
arrowStyle: this.props.arrowStyle
})));
}
}
process.env.NODE_ENV !== "production" ? Line.propTypes = _extends({}, {
x0: PropTypes.number.isRequired,
y0: PropTypes.number.isRequired,
x1: PropTypes.number.isRequired,
y1: PropTypes.number.isRequired
}, optionalStyleProps) : void 0;