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

Added ModalSpinner with demo

Moving ContainmentCanvas (issues with redux and global state...)
parent efc00ae4
......@@ -8,7 +8,8 @@ import DemoProjectSeedCards from '../src/components/ProjectSeedCards/demo';
import DemoAttributeEditor from '../src/components/AttributeEditor/demo';
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';
export default class demoApp extends Component {
componentDidMount() {
......@@ -35,6 +36,14 @@ export default class demoApp extends Component {
component: <DemoUserProfileNavigator/>,
title: 'UserProfileNavigator',
},
{
component: <DemoModalSpinner/>,
title: 'ModalSpinner',
},
// {
// component: <DemoContainmentCanvas/>,
// title: 'ContainmentCanvas',
// },
];
return (
......
This diff is collapsed.
/**
* @author kecso / https://github.com/kecso
*/
export default class BasicEventManager {
constructor() {
this.subscribers = {};
this.lastEvents = {};
this.subscribe = this.subscribe.bind(this);
this.unsubscribe = this.unsubscribe.bind(this);
this.fire = this.fire.bind(this);
}
fire(id, event) {
const oldEvent = this.lastEvents[id];
this.lastEvents[id] = event;
if (JSON.stringify(oldEvent) !== JSON.stringify(event)) {
(this.subscribers[id] || []).forEach((eventFn) => {
eventFn(id, event);
});
}
}
subscribe(id, eventFn) {
this.subscribers[id] = this.subscribers[id] || [];
this.subscribers[id].push(eventFn);
return this.lastEvents[id];
}
unsubscribe(id, eventFn) {
this.subscribers[id] = this.subscribers[id] || [];
this.subscribers[id].splice(this.subscribers[id].indexOf(eventFn), 1);
}
}
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Z_LEVELS from '../../utils/zLevels';
export default class BasicConnectingComponent extends Component {
static propTypes = {
connectionManager: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
const {connectionManager} = this.props;
connectionManager.setListener(this.onChange);
}
state = {
startPos: null,
currentPos: null,
isConnecting: false,
};
onChange = (event) => {
this.setState(event);
};
render() {
const {isConnecting, startPos, currentPos} = this.state;
let top;
let left;
let width;
let height;
if (isConnecting) {
top = Math.min(startPos.y, currentPos.y);
left = Math.min(startPos.x, currentPos.x);
height = Math.abs(currentPos.y - startPos.y) + 5;
width = Math.abs(currentPos.x - startPos.x) + 5;
if (height > 0 && width > 0) {
return (
<svg
width={width}
height={height}
viewBox={`${0} ${0} ${width} ${height}`}
style={{
position: 'absolute',
top: `${top - 5}px`,
left: `${left - 5}px`,
zIndex: Z_LEVELS.connection,
}}
>
<line
strokeWidth="3"
stroke="orange"
strokeDasharray="5 5"
x1={startPos.x - (left - 5)}
y1={startPos.y - (top - 5)}
x2={currentPos.x - (left - 5)}
y2={currentPos.y - (top - 5)}
/>
</svg>
);
}
}
return null;
}
}
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import ZLEVELS from '../../utils/zLevels';
export default class BasicConnection extends Component {
static propTypes = {
path: PropTypes.arrayOf(PropTypes.object).isRequired,
onClick: PropTypes.func,
hasWrapper: PropTypes.bool.isRequired,
dashed: PropTypes.bool.isRequired,
color: PropTypes.string,
};
static defaultProps = {
onClick: null,
color: 'black',
};
onClick = (event) => {
const {onClick} = this.props;
if (onClick) {
onClick(event);
}
};
getBoundingBox = () => {
const {path} = this.props;
let minX;
let maxX;
let minY;
let maxY;
if (path.length === 0) {
return null;
}
minX = path[0].x;
maxX = path[0].x;
minY = path[0].y;
maxY = path[0].y;
path.forEach((point) => {
if (point.x < minX) {
minX = point.x;
}
if (point.x > maxX) {
maxX = point.x;
}
if (point.y < minY) {
minY = point.y;
}
if (point.y > maxY) {
maxY = point.y;
}
});
return {
x: minX,
y: minY,
width: Math.max(maxX - minX, 2),
height: Math.max(maxY - minY, 2),
};
};
render() {
const {path, hasWrapper, dashed, color} = this.props;
const box = this.getBoundingBox();
const sections = [];
let i;
const style = hasWrapper ? {} : {
position: 'absolute',
top: box.y,
left: box.x,
zIndex: ZLEVELS.connection,
};
if (box === null) {
return null;
}
for (i = 0; i < path.length - 1; i += 1) {
sections.push(<path
key={i}
d={`M${
path[i].x} ${
path[i].y} L${
path[i + 1].x} ${
path[i + 1].y}`}
strokeWidth={1.6}
strokeDasharray={dashed ? 5 : 0}
stroke={color}
/>);
}
return (
<svg
width={box.width}
height={box.height}
style={style}
viewBox={`${box.x} ${box.y} ${box.width} ${box.height}`}
>
{sections}
</svg>);
}
}
/**
* @author kecso / https://github.com/kecso
*/
export default class ConnectionManager {
constructor() {
this.isConnecting = false;
this.type = null;
this.source = null;
this.notifyMe = null;
this.changeFn = null;
this.startPos = null;
this.currentPos = null;
this.startConnection = this.startConnection.bind(this);
this.endConnection = this.endConnection.bind(this);
this.setListener = this.setListener.bind(this);
this.clearListener = this.clearListener.bind(this);
}
startConnection(source, type, position, notifyMe) {
this.isConnecting = true;
this.type = type;
this.source = source;
this.startPos = position;
this.notifyMe = typeof notifyMe === 'function' ? notifyMe : () => {
console.log('no notification is sent');
};
this.fireChange();
}
endConnection() {
this.isConnecting = false;
const connection = {source: this.source, type: this.type};
this.source = null;
this.type = null;
if (typeof this.notifyMe === 'function') { this.notifyMe(); }
this.notifyMe = null;
this.fireChange();
return connection;
}
setListener(changeFn) {
this.changeFn = changeFn;
}
clearListener() {
this.changeFn = null;
}
onMouseMove(newPos) {
this.currentPos = newPos;
this.fireChange();
}
fireChange() {
if (this.changeFn) {
this.changeFn({
isConnecting: this.isConnecting,
startPos: this.startPos,
currentPos: this.currentPos,
});
}
}
}
import React from 'react';
import PropTypes from 'prop-types';
import {DropTarget} from 'react-dnd';
import SingleConnectedNode from './SingleConnectedNode';
import DRAG_TYPES from '../../utils/dragTypes';
import ContainmentCanvasItem from './ContainmentCanvasItem';
import ConnectionManager from './ConnectionManager';
import BasicConnectingComponent from './BasicConnectingComponent';
import BasicEventManager from '../BasicEventManager/BasicEventManager';
import getIndexedName from '../../utils/getIndexedName';
import Z_LEVELS from '../../utils/zLevels';
import ContainmentCanvasInfoCard from './ContainmentCanvasInfoCard';
// TODO we only take loaded children into account
function getChildrenNames(gmeClient, nodeId) {
const container = gmeClient.getNode(nodeId);
const childrenIds = container.getChildrenIds();
const names = [];
childrenIds.forEach((childId) => {
const node = gmeClient.getNode(childId);
if (node) {
names.push(node.getAttribute('name'));
}
});
return names;
}
const canvasTarget = {
drop(props, monitor, canvas) {
const dragItem = monitor.getItem();
if (dragItem.move) {
const offset = monitor.getDifferenceFromInitialOffset();
const node = props.gmeClient.getNode(dragItem.gmeId);
const position = node.getRegistry('position');
position.x += offset.x / props.scale;
position.y += offset.y / props.scale;
position.x = Math.trunc(position.x);
position.y = Math.trunc(position.y);
props.gmeClient.setRegistry(dragItem.gmeId, 'position', position);
} else if (dragItem.create) {
const dragOffset = monitor.getClientOffset();
const metaNode = props.gmeClient.getNode(dragItem.gmeId);
const position = {
x: ((dragOffset.x - canvas.offset.x) + canvas.props.scrollPos.x) / props.scale,
y: ((dragOffset.y - canvas.offset.y) + canvas.props.scrollPos.y) / props.scale,
};
let name = metaNode.getAttribute('ShortName') || metaNode.getAttribute('name');
name = getIndexedName(name, getChildrenNames(props.gmeClient, props.activeNode));
position.x -= dragItem.nodeData.bbox.width / 2;
position.y -= dragItem.nodeData.bbox.height / 2;
position.x = Math.trunc(position.x);
position.y = Math.trunc(position.y);
// TODO: Fix when client accepts 0
position.x = position.x > 0 ? position.x : 1;
position.y = position.y > 0 ? position.y : 1;
props.gmeClient.createNode({
parentId: props.activeNode,
baseId: dragItem.gmeId,
}, {
attributes: {
name,
},
registry: {
position,
},
});
}
canvas.setState({dragMode: 'none'});
},
hover(props, monitor, component) {
const item = monitor.getItem();
let dragState;
if (item.create) {
dragState = 'create';
}
if (item.move) {
dragState = 'move';
}
component.setState({dragMode: dragState});
},
};
function collect(connector, monitor) {
return {
connectDropTarget: connector.dropTarget(),
isOver: monitor.isOver(),
};
}
class ContainmentCanvas extends SingleConnectedNode {
static propTypes = {
gmeClient: PropTypes.object.isRequired,
scrollPos: PropTypes.object.isRequired,
activeNode: PropTypes.string.isRequired,
scale: PropTypes.number.isRequired,
connectDropTarget: PropTypes.func.isRequired,
isOver: PropTypes.bool.isRequired,
};
state = {
children: [],
nodeInfo: {},
dragMode: 'none',
};
cm = null;
em = null;
offset = {
x: 0,
y: 0,
};
constructor(props) {
super(props);
this.cm = new ConnectionManager();
this.em = new BasicEventManager();
}
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) => {
event.stopPropagation();
event.preventDefault();
if (this.cm.isConnecting) {
this.cm.endConnection();
}
this.props.clearSelection();
};
onMouseLeave = (event) => {
event.stopPropagation();
if (this.cm.isConnecting) {
this.cm.endConnection();
}
};
onMouseMove = (event) => {
const {scrollPos} = this.props;
this.cm.onMouseMove({
x: event.clientX + (scrollPos.x - this.offset.x),
y: event.clientY + (scrollPos.y - this.offset.y),
});
};
render() {
const {connectDropTarget, activeNode, gmeClient} = this.props;
const {children, dragMode} = this.state;
const childrenItems = children.map(child => (<ContainmentCanvasItem
key={child.id}
gmeClient={gmeClient}
activeNode={child.id}
contextNode={activeNode}
connectionManager={this.cm}
eventManager={this.em}
/>));
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}
>
<BasicConnectingComponent connectionManager={this.cm}/>
{childrenItems.length > 0 ? childrenItems : ContainmentCanvasInfoCard()}
</div>);
return connectDropTarget(content);
}
}
export default (DropTarget(DRAG_TYPES.GME_NODE, canvasTarget, collect)(ContainmentCanvas));
import React from 'react';
import {Link} from 'react-router-dom';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
const ContainmentCanvasInfoCard = () => (
<Card style={{
position: 'absolute',
maxWidth: 600,
left: 'calc(50% - 300px)',
top: '20%',
}}
>
<CardContent>
<Typography style={{marginBottom: 20}} variant="headline" component="h2">
This is your canvas
</Typography>
<Typography component="p">
Use the left menu to add components to your system. Locate which components you
need and drag and drop them onto this Canvas. Based on their interfaces you can wire
components together by clicking the port icons. <br/><br/>
To set the parameter simply double-click it and the parameter editor will show up.
From there you can click the inlined icon and it will take you to the official
Modelica® Standard Library documentation.
</Typography>
</CardContent>
<CardActions>