-
Jaden DIEFENBAUGH authoredJaden DIEFENBAUGH authored
ToolchainConnection.jsx 4.39 KiB
// @flow
import * as React from 'react';
import { ContextMenu, MenuItem, ContextMenuTrigger } from 'react-contextmenu';
import cn from 'classnames';
import type { BlockCoords, ConnectionType } from './types.js';
type Props = {
fromLocMap: BlockCoords,
toLocMap: BlockCoords,
// provides the location of the ends of the connection
connection: ConnectionType,
// the color of the channel the connection is sync'd to
channelColor: string,
errors: string[],
handleContextMenu?: (e: any, ConnectionType) => any,
};
// gets a unique id for the connection
export const connectionToId = ({ from, to, channel }: {from: string, to: string, channel: string}) =>
`${ from.replace('.', '-') }-${ to.replace('.', '-') }`;
class ToolchainConnection extends React.Component<Props> {
constructor(props: Props){
super(props);
}
/*
* Custom implementation of shouldComponentUpdate for performance reasons!
* "Why not just use PureComponent??"
* PureComponent implements a default shouldComponentUpdate that shallowly compares
* all current & next props/state values.
* Because the click handlers are always not equal (since they're dynamically generated in the loop in
* GraphicalEditor.jsx), blocks are *always* rerendered.
* Also, the locMap objects are technically somehow a different object whenever
* a subset of the blocks are moved.
* This means that every block change means rerendering all the blocks.
* By writing a custom shouldComponentUpdate, we can use more detailed logic that prevents
* the unnecessary rerenders.
* A couple things to keep in mind:
* - the click handlers dont functionally change if the block data doesnt change.
* - the errors array is a different array every time technically, so check the values
* - only the positions (x & y) of the locmap data matters
* - the channel in the connection data can change, but not the to/from
*/
shouldComponentUpdate(nextProps: Props){
for(const key in nextProps){
if(key === 'fromLocMap' || key === 'toLocMap' || key === 'handleContextMenu'){
continue;
} else if(key === 'errors'){
if(nextProps[key].length !== this.props.errors.length)
return true;
for(const i in nextProps[key]){
if(nextProps[key][i] !== this.props[key][i])
return true;
}
} else if(key === 'connection'){
if(nextProps[key].channel !== this.props[key].channel
|| nextProps[key].from !== this.props[key].from
|| nextProps[key].to !== this.props[key].to
)
return true;
} else if(nextProps[key] !== this.props[key]){
return true;
}
}
const fromLoc = this.props.fromLocMap;
const toLoc = this.props.toLocMap;
const fromLocNext = nextProps.fromLocMap;
const toLocNext = nextProps.toLocMap;
if(fromLoc.x !== fromLocNext.x)
return true;
if(fromLoc.y !== fromLocNext.y)
return true;
if(toLoc.x !== toLocNext.x)
return true;
if(toLoc.y !== toLocNext.y)
return true;
return false;
}
render = () => {
const conn = this.props.connection;
const connectionId = `#${ connectionToId(conn) }`;
const fromLoc = this.props.fromLocMap;
const toLoc = this.props.toLocMap;
//console.log(`connecting "${ connId }" from ${ fromInfo } to ${ toInfo }`);
//x & y coords for the beginning and end of the connection
/* the right-click menu wraps the actual lines */
return (
<g className='tcConnection'
onContextMenu={e => this.props.handleContextMenu ? this.props.handleContextMenu(e, conn) : {}}
>
{/* the first line is almost invisible to widen the space for the mouse events (so its easier for users to do stuff */}
<line
id={connectionId}
stroke={this.props.channelColor}
strokeOpacity={0.005}
strokeWidth={10}
x1={fromLoc.x + 2}
y1={fromLoc.y + 9}
x2={toLoc.x + 2}
y2={toLoc.y + 9}
>
<title>
Connection from {conn.from} to {conn.to} synchronized to the {conn.channel} channel{' '}
{ this.props.errors[0] }
</title>
</line>
{/* the second line is just for show */}
<line
className={cn({ error: this.props.errors.length > 0 })}
stroke={this.props.channelColor}
strokeWidth={2}
x1={fromLoc.x + 2}
y1={fromLoc.y + 6}
x2={toLoc.x + 2}
y2={toLoc.y + 6}
>
<title>
Connection from {conn.from} to {conn.to} synchronized to the {conn.channel} channel{' '}
{ (this.props.errors || [])[0] }
</title>
</line>
</g>
);
}
}
export default ToolchainConnection;