react-planner-electron
Version:
react-planner-electron is a React Component for plans design. Draw a 2D floorplan and navigate it in 3D mode.
245 lines (213 loc) • 7.52 kB
JSX
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import {Map} from 'immutable';
const grabCircleRadius = 10;
const hoverCircleRadius = 14;
const rulerColor = '#f45c42';
const hoverColor = '#ff9900';
const grabCircleStyle = {
cursor: 'grab',
fill: rulerColor,
transition: 'r 150ms ease-in'
};
const hoverCircleStyle = {
cursor: 'grab',
fill: hoverColor,
transition: 'r 150ms ease-in'
};
const pointsDistance = (x1, y1, x2, y2) => {
if (
!isNaN(x1) &&
!isNaN(y1) &&
!isNaN(x2) &&
!isNaN(y2)
) {
if (!(x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0)) {
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
}
return 0;
};
export default class ImageFul extends Component {
constructor(props) {
super(props);
this.state = {
handleMouseMove1: false,
handleMouseMove2: false,
hover1: false,
hover2: false,
imageLoadError: false
};
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseMove = this.onMouseMove.bind(this);
this.toggleHover1 = this.toggleHover1.bind(this);
this.toggleHover2 = this.toggleHover2.bind(this);
}
onMouseDown(event) {
let target = event.viewerEvent.originalEvent.target;
if (target.nodeName === 'circle') {
if (target.attributes.name) {
if (target.attributes.name.nodeValue === 'fst-anchor') {
this.setState({handleMouseMove1: !this.state.handleMouseMove1});
}
else if (target.attributes.name.nodeValue === 'snd-anchor') {
this.setState({handleMouseMove2: !this.state.handleMouseMove2});
}
}
}
}
onMouseMove(event) {
let {x, y} = event.viewerEvent;
y = this.props.scene.height - y;
let dist = pointsDistance(this.props.x1, this.props.y1, this.props.x2, this.props.y2);
let scale = !isNaN(dist) && dist ? (this.props.distance.length / dist) : 0;
let origin = {
x: this.props.element.x - (this.props.width * scale / 2),
y: this.props.element.y + (this.props.height * scale / 2)
};
let minX = origin.x + (this.props.width * scale);
let minY = origin.y - (this.props.height * scale);
if (x < origin.x) {
x = origin.x;
}
else if (x > minX) {
x = minX;
}
if (y > origin.y) {
y = origin.y;
}
else if (y < minY) {
y = minY;
}
let newX = (x - origin.x);
let newY = (origin.y - y);
if (this.state.handleMouseMove1) {
let dist = pointsDistance(newX, newY, this.props.x2, this.props.y2);
this.context.projectActions.setProperties(new Map({x1: newX, y1: newY, distance: new Map({length: dist})}));
}
else if (this.state.handleMouseMove2) {
let dist = pointsDistance(this.props.x1, this.props.y1, newX, newY);
this.context.projectActions.setProperties(new Map({x2: newX, y2: newY, distance: new Map({length: dist})}));
}
}
componentDidMount() {
document.addEventListener('mousedown-planner-event', this.onMouseDown);
document.addEventListener('mousemove-planner-event', this.onMouseMove);
if (this.props.imageUri) {
let img = new Image;
img.src = this.props.imageUri;
img.onload = () => {
this.setState({imageLoadError: false});
this.context.projectActions.setProperties(new Map({width: img.naturalWidth, height: img.naturalHeight}))
};
img.onerror = () => {
this.setState({imageLoadError: true})
};
}
}
componentWillUnmount() {
document.removeEventListener('mousedown-planner-event', this.onMouseDown);
document.removeEventListener('mousemove-planner-event', this.onMouseMove);
}
componentWillReceiveProps(nextProps) {
if (this.props.imageUri !== nextProps.imageUri) {
let img = new Image;
img.src = nextProps.imageUri;
img.onload = () => {
this.setState({imageLoadError: false});
this.context.projectActions.setProperties(new Map({width: img.naturalWidth, height: img.naturalHeight}))
};
img.onerror = () => {
this.setState({imageLoadError: true})
};
}
}
toggleHover1(e) {
this.setState({hover1: !this.state.hover1})
}
toggleHover2(e) {
this.setState({hover2: !this.state.hover2})
}
render() {
let dist = pointsDistance(this.props.x1, this.props.y1, this.props.x2, this.props.y2);
let scale = !isNaN(dist) && dist ? (this.props.distance.length / dist) : 0;
let half_w = this.props.width / 2;
let ruler = !this.props.element.selected ? null : (
<g>
<line key="1" x1={this.props.x1} y1={this.props.y1} x2={this.props.x2} y2={this.props.y2} stroke={rulerColor}
strokeWidth="3px"/>
<circle
onMouseEnter={this.toggleHover1}
onMouseLeave={this.toggleHover1}
key="2"
name="fst-anchor"
cx={this.props.x1}
cy={this.props.y1}
r={this.state.hover1 || this.state.handleMouseMove1 ? hoverCircleRadius : grabCircleRadius}
style={this.state.hover1 || this.state.handleMouseMove1 ? hoverCircleStyle : grabCircleStyle}/>
<circle
onMouseEnter={this.toggleHover2}
onMouseLeave={this.toggleHover2}
key="3"
name="snd-anchor"
cx={this.props.x2}
cy={this.props.y2}
r={this.state.hover2 || this.state.handleMouseMove2 ? hoverCircleRadius : grabCircleRadius}
style={this.state.hover2 || this.state.handleMouseMove2 ? hoverCircleStyle : grabCircleStyle}/>
</g>
);
return (
<g
transform={`scale(${scale}, ${scale}), scale(1,-1) translate(${-this.props.width / 2}, ${-this.props.height / 2})`}>
{
this.props.imageUri && !this.state.imageLoadError ?
<image
xlinkHref={this.props.imageUri}
x="0"
y="0"
width={this.props.width}
height={this.props.height}
/> :
<g>
<rect x="0" y="0" width={this.props.width} height={this.props.height} fill="#CCC"></rect>
<text
x={half_w}
y={this.props.height / 2}
textAnchor="middle"
alignmentBaseline="central"
fontFamily="Arial"
fontSize="35"
fill="#666"
>
<tspan x={half_w} dy="-2em">Set the image url on the component</tspan>
<tspan x={half_w} dy="1em">property inside the sidebar,</tspan>
<tspan x={half_w} dy="1em">click and move each vertex</tspan>
<tspan x={half_w} dy="1em">of the ruler then set the real distance</tspan>
<tspan x={half_w} dy="1em">in the component property</tspan>
</text>
</g>
}
{ruler}
</g>
)
}
}
ImageFul.propTypes = {
element: PropTypes.object.isRequired,
x1: PropTypes.number.isRequired,
y1: PropTypes.number.isRequired,
x2: PropTypes.number.isRequired,
y2: PropTypes.number.isRequired,
distance: PropTypes.object.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
imageUri: PropTypes.string.isRequired,
layer: PropTypes.object.isRequired,
scene: PropTypes.object.isRequired
};
ImageFul.contextTypes = {
projectActions: PropTypes.object.isRequired,
catalog: PropTypes.object.isRequired,
translator: PropTypes.object.isRequired,
};