Commit dda1775c authored by Tamas Kecskes's avatar Tamas Kecskes
Browse files

Extracting a common Canvas that can serve as both ContainmentCanvas and as SelectorCanvas

parent 383606f2
......@@ -14,6 +14,7 @@ import DemoConfirmDialog from '../src/components/ConfirmDialog/demo';
import DemoUserProfileNavigator from '../src/components/UserProfileNavigator/demo';
import DemoModalSpinner from '../src/components/ModalSpinner/demo';
import DemoContainmentCanvas from '../src/components/ContainmentCanvas/demo';
import DemoBasicContainmentCanvas from '../src/components/BasicContainmentCanvas/demo';
import DemoSingleConnectedNode from '../src/components/SingleConnectedNode/demo';
import DemoInfoCard from '../src/components/InfoCard/demo';
import DemoPartBrowser from '../src/components/PartBrowser/demo';
......@@ -48,6 +49,11 @@ class demoApp extends Component {
component: <DemoModalSpinner/>,
title: 'ModalSpinner',
},
{
component: <DemoBasicContainmentCanvas/>,
title: 'BasicContainmentCanvas',
height: 400,
},
{
component: <DemoContainmentCanvas/>,
title: 'ContainmentCanvas',
......
import React from 'react';
import PropTypes from 'prop-types';
import SingleConnectedNode from '../SingleConnectedNode/SingleConnectedNode';
import {SVGRegistryBasedCanvasItem} from '../SVGRegistryBasedCanvasItem';
import ConnectionManager from '../ConnectionManager/ConnectionManager';
import BasicConnectingComponent from '../ConnectionManager/BasicConnectingComponent';
import BasicEventManager from '../BasicEventManager/BasicEventManager';
import Z_LEVELS from '../../utils/zLevels';
class BasicContainmentCanvas extends SingleConnectedNode {
static propTypes = {
gmeClient: PropTypes.object.isRequired,
scrollPos: PropTypes.object.isRequired,
activeNode: PropTypes.string.isRequired,
scale: PropTypes.number.isRequired,
connectDropTarget: PropTypes.func,
isOver: PropTypes.bool.isRequired,
};
state = {
children: [],
nodeInfo: {},
dragMode: 'none',
};
cm = null;
em = null;
item = null;
infoCard = null;
offset = {
x: 0,
y: 0,
};
connectDropTarget = function (content) {
return content;
};
constructor(props) {
super(props);
this.em = new BasicEventManager();
this.item = React.createElement(SVGRegistryBasedCanvasItem);
if (this.props.children !== undefined) {
if (this.props.children[0] !== undefined) {
this.item = this.props.children[0];
if (this.props.children[1] !== undefined) {
this.infoCard = this.props.children[1];
}
} else {
this.item = this.props.children;
}
}
if (this.props.connectDropTarget !== undefined) {
this.connectDropTarget = this.props.connectDropTarget;
this.cm = new ConnectionManager();
}
}
populateChildren(nodeObj) {
const childrenIds = nodeObj.getChildrenIds();
const newChildren = childrenIds.map(id => ({id}));
this.setState({
children: newChildren,
nodeInfo: {
name: nodeObj.getAttribute('name'),
},
});
}
onNodeLoad(nodeObj) {
this.populateChildren(nodeObj, true);
}
onNodeUpdate(nodeObj) {
this.populateChildren(nodeObj);
}
onMouseClick = (event) => {
};
onMouseLeave = (event) => {
};
onMouseMove = (event) => {
};
render() {
const {activeNode, gmeClient} = this.props;
const {children, dragMode} = this.state;
const childrenItems = children.map(child => React.cloneElement(this.item, {
key: child.id,
gmeClient,
activeNode: child.id,
contextNode: activeNode,
connectionManager: this.cm,
eventManager: this.em,
scale: 1,
}));
const connectingComponent = this.cm ? <BasicConnectingComponent connectionManager={this.cm}/> : null;
const content = (
<div
ref={(canvas) => {
if (canvas) {
const {offsetLeft, offsetTop} = canvas.offsetParent;
this.offset = {
x: offsetLeft,
y: offsetTop,
};
}
}}
style={{
backgroundColor: dragMode === 'create' ? 'lightgreen' : undefined,
width: '100%',
height: '100%',
overflow: 'auto',
zIndex: Z_LEVELS.canvas,
position: 'absolute',
}}
role="presentation"
onClick={this.onMouseClick}
onKeyDown={() => {
}}
onContextMenu={this.onMouseClick}
onMouseLeave={this.onMouseLeave}
onMouseMove={this.onMouseMove}
>
{connectingComponent}
{childrenItems.length > 0 ? childrenItems : this.infoCard}
</div>);
return this.connectDropTarget(content);
}
}
export default BasicContainmentCanvas;
/* globals window */
import React, {Component} from 'react';
import BasicContainmentCanvas from './index';
export default class DemoBasicContainmentCanvas extends Component {
constructor(props) {
super(props);
this.gmeClient = new window.GME.classes.Client(window.GME.gmeConfig);
}
render() {
return (
<div style={{width: '90%'}}>
<BasicContainmentCanvas
gmeClient={this.gmeClient}
scrollPos={{
x: 0,
y: 0,
}}
activeNode=""
isOver={false}
scale={1}
/>
</div>
);
}
}
export {default} from './BasicContainmentCanvas';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {Samy} from 'react-samy-svg';
import BasicConnection from 'webgme-react-components/src/components/BasicConnection';
import getSVGData from 'webgme-react-components/src/utils/getSVGData';
import Territory from 'webgme-react-components/src/components/Territory';
import ZLEVELS from '../gme/utils/zLevels';
import colorHash from '../gme/utils/colorHash';
const mapStateToProps = state => ({
scale: state.scale,
variables: state.plotData.variables,
});
const mapDispatchToProps = (/* dispatch */) => ({});
class SelectorCanvasItem extends Component {
static propTypes = {
gmeClient: PropTypes.object.isRequired,
activeNode: PropTypes.string.isRequired, // This is not the same as the state.activeNode..
scale: PropTypes.number.isRequired,
eventManager: PropTypes.object.isRequired,
variables: PropTypes.arrayOf(PropTypes.string).isRequired,
};
// TODO we need to gather the children info (new base class maybe)
state = {
position: null,
modelicaUri: 'Default',
childrenName2Id: {},
childInfo: {},
isConnection: null,
color: 'black',
endPoints: {
src: {id: null},
dst: {id: null},
},
territory: (() => {
const {activeNode} = this.props;
return {[activeNode]: {children: 0}};
})(),
justRemovedIds: [],
};
getChildInfo = (childNode) => {
const {gmeClient} = this.props;
const metaNodes = gmeClient.getAllMetaNodes(true);
const info = {
name: childNode.getAttribute('name'),
validConnection: {},
};
const paths = Object.keys(metaNodes);
paths.forEach((path) => {
if (childNode.isValidTargetOf(path, 'src')) {
info.validConnection.src = path;
}
if (childNode.isValidTargetOf(path, 'dst')) {
info.validConnection.dst = path;
}
});
return info;
};
getAttributeItems = () => {
const {gmeClient, activeNode, scale} = this.props;
const node = gmeClient.getNode(activeNode);
const {attributes} = getSVGData(node);
const attributeItems = [];
const names = Object.keys(attributes);
if (node === null) {
return null;
}
names.forEach((key) => {
attributeItems.push((
<svg
key={key}
style={{
position: 'absolute',
top: /* position.y + */attributes[key].bbox.y * scale,
left: /* position.x + */attributes[key].bbox.x * scale,
}}
viewBox={`${attributes[key].bbox.x * scale} ${attributes[key].bbox.y * scale}
${attributes[key].bbox.width * scale}
${attributes[key].bbox.height * scale}`}
>
<text
x={(attributes[key].parameters.x || 0) * scale}
y={(attributes[key].parameters.y || 0) * scale}
alignmentBaseline={attributes[key].parameters['alignment-baseline'] || 'middle'}
fill={attributes[key].parameters.fill || 'rgb(0,0,255)'}
fontFamily={attributes[key].parameters['font-family'] || 'Veranda'}
fontSize={Number(attributes[key].parameters['font-size'] || '18') * scale}
textAnchor={attributes[key].parameters['text-anchor'] || 'middle'}
>
{attributes[key].text.substring(0, attributes[key].position) +
node.getAttribute(key) +
attributes[key].text.substring(attributes[key].position)}
</text>
</svg>));
});
return attributeItems;
};
getSelectionItems = (nodeId, opacity) => {
const {gmeClient, variables, activeNode} = this.props;
const node = gmeClient.getNode(nodeId);
const hostNode = gmeClient.getNode(activeNode);
if (!(node && hostNode)) {
return [];
}
let variablePrefix;
if (nodeId === activeNode) {
variablePrefix = hostNode.getAttribute('name');
} else {
variablePrefix = `${hostNode.getAttribute('name')}.${node.getAttribute('name')}`;
}
const matches = variables.filter(variable =>
variable.substring(Math.max(0, variable.indexOf('(') + 1), variable.lastIndexOf('.')) === variablePrefix);
return matches.map((variable, index) => {
const step = 100 / matches.length;
return (
<div style={{
top: `${index * step}%`,
width: '100%',
height: `${step}%`,
opacity: opacity || 0.5,
position: 'absolute',
backgroundColor: colorHash(variable).rgb,
}}
/>
);
});
};
territoryUpdates = (hash, loads, updates, unloads) => {
const {activeNode, gmeClient, eventManager} = this.props;
const {endPoints} = this.state;
// console.log('event-', hash, loads, updates, unloads);
if (unloads.indexOf(activeNode) !== -1) {
// main object have been unloaded so remove everything...
if (endPoints.src.event) {
eventManager.unsubscribe(endPoints.src.id, endPoints.src.event);
}
if (endPoints.dst.event) {
eventManager.unsubscribe(endPoints.dst.id, endPoints.dst.event);
}
this.setState({
position: null,
modelicaUri: 'Default',
childrenName2Id: {},
childInfo: {},
isConnection: null,
endPoints: {
src: {id: null},
dst: {id: null},
},
territory: null,
justRemovedIds: [],
});
return;
}
const nodeObj = gmeClient.getNode(activeNode);
const metaNode = gmeClient.getNode(nodeObj.getMetaTypeId());
const validPointers = nodeObj.getValidPointerNames();
const isConnection = validPointers.indexOf('src') !== -1 && validPointers.indexOf('dst') !== -1;
let newEndpoints = null;
let modelicaUri = 'Default';
let color = 'black';
const territory = {};
let newChildrenName2Id = {};
const childrenPaths = nodeObj.getChildrenIds();
let newChildInfo = {};
if (isConnection) {
newEndpoints = {
src: {
id: nodeObj.getPointerId('src'),
position: null,
event: this.srcEvent,
},
dst: {
id: nodeObj.getPointerId('dst'),
position: null,
event: this.dstEvent,
},
};
color = nodeObj.getRegistry('color');
if (endPoints.src.id !== newEndpoints.src.id || endPoints.dst.id !== newEndpoints.dst.id) {
// subscription to events
let event;
event = eventManager.subscribe(newEndpoints.src.id, newEndpoints.src.event);
if (event) {
newEndpoints.src.position = event.position;
}
event = eventManager.subscribe(newEndpoints.dst.id, newEndpoints.dst.event);
if (event) {
newEndpoints.dst.position = event.position;
}
} else {
newEndpoints = endPoints;
}
territory[activeNode] = {children: 0};
} else {
newEndpoints = endPoints;
newChildrenName2Id = this.state.childrenName2Id;
newChildInfo = this.state.childInfo;
modelicaUri = metaNode.getAttribute('ModelicaURI') || 'Default';
childrenPaths.forEach((childPath) => {
if (loads.indexOf(childPath) !== -1 || updates.indexOf(childPath) !== -1) {
const childNode = gmeClient.getNode(childPath);
newChildrenName2Id[childNode.getAttribute('name')] = childPath;
newChildInfo[childPath] = this.getChildInfo(childNode);
} else if (unloads.indexOf(childPath) !== -1) {
const names = Object.keys(newChildrenName2Id);
names.forEach((name) => {
if (newChildrenName2Id[name] === childPath) {
delete newChildrenName2Id[name];
delete newChildInfo[childPath];
}
});
}
});
territory[activeNode] = {children: 1};
}
this.setState({
position: nodeObj.getRegistry('position'),
modelicaUri,
isConnection,
color,
endPoints: newEndpoints,
childrenName2Id: newChildrenName2Id,
childInfo: newChildInfo,
territory,
justRemovedIds: unloads,
});
};
srcEvent = (id, event) => {
const {position} = this.state.endPoints.src;
if (id !== this.state.endPoints.src.id) {
return;
}
if (event.position === null || position === null ||
event.position.x !== position.x || event.position.y !== position.y) {
const {endPoints} = this.state;
endPoints.src.position = event.position;
this.setState({endPoints});
}
};
dstEvent = (id, event) => {
const {position} = this.state.endPoints.dst;
if (id !== this.state.endPoints.dst.id) {
return;
}
if (event.position === null || position === null ||
event.position.x !== position.x || event.position.y !== position.y) {
const {endPoints} = this.state;
endPoints.dst.position = event.position;
this.setState({endPoints});
}
};
boxRender = () => {
const {
scale,
eventManager,
activeNode,
gmeClient,
} = this.props;
const {
position,
childrenName2Id,
justRemovedIds,
} = this.state;
const {ports, bbox, base} = getSVGData(gmeClient.getNode(activeNode));
const events = [];
const portComponents = [];
justRemovedIds.forEach((removedId) => {
events.push({
id: removedId,
position: null,
});
});
Object.keys(ports).forEach((name) => {
const id = childrenName2Id[name];
const port = ports[name];
if (id) {
portComponents.push((
<div
key={id}
role="presentation"
style={{
position: 'absolute',
left: (scale * port.x) - 2,
top: (scale * port.y) - 2,
width: (scale * port.width) + 4,
height: (scale * port.height) + 4,
}}
>
{this.getSelectionItems(id, 1)}
</div>
));
events.push({
id: childrenName2Id[name],
position: {
x: (position.x * scale) + (scale * (ports[name].x + (ports[name].width / 2))),
y: (position.y * scale) + (scale * (ports[name].y + (ports[name].height / 2))),
},
});
}
});
events.forEach((event) => {
eventManager.fire(event.id, {position: event.position});
});
const content = (
<div
style={{
opacity: 0.99,
position: 'absolute',
top: position.y * scale,
left: position.x * scale,
height: bbox.height * scale,
width: bbox.width * scale,
zIndex: ZLEVELS.item,
}}
role="presentation"
>
{this.getSelectionItems(activeNode)}
{portComponents}
<Samy
svgXML={base}
style={{
height: bbox.height * scale,
width: bbox.width * scale,
}}
/>
{this.getAttributeItems()}
</div>);
return content;
};
connectionRender = () => {
const {endPoints, color} = this.state;
const {activeNode} = this.props;
let points;
let midpoint;
if (endPoints.src.position && endPoints.dst.position) {
midpoint = {
x: endPoints.src.position.x,
y: endPoints.dst.position.y,
};
points = [endPoints.src.position, JSON.parse(JSON.stringify(midpoint)), endPoints.dst.position];