Skip to content

Commit

Permalink
UI for controls.
Browse files Browse the repository at this point in the history
- Make backend address configurable via env variable
- `BACKEND_HOST=1.2.3.4:4040 npm start` points the frontend to the app on that server. Just for development
- Render control icons
  - removed close x for details panel
  - added control icons in its space
  - closing of panel still works by clicking on same node, or background
- Dont allow control while pending
- Render and auto-dismiss control error
- Make tests pass
  • Loading branch information
davkal authored and tomwilkie committed Nov 6, 2015
1 parent 8f957c4 commit abcb94b
Show file tree
Hide file tree
Showing 20 changed files with 267 additions and 69 deletions.
3 changes: 3 additions & 0 deletions client/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
"templateStrings": true,
"jsx": true
},
"globals": {
__WS_URL__: false
},
"rules": {
/**
* Strict mode
Expand Down
38 changes: 34 additions & 4 deletions client/app/scripts/actions/app-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,29 @@ module.exports = {
});
},

clearControlError: function() {
AppDispatcher.dispatch({
type: ActionTypes.CLEAR_CONTROL_ERROR
});
},

closeWebsocket: function() {
AppDispatcher.dispatch({
type: ActionTypes.CLOSE_WEBSOCKET
});
},

doControl: function(probeId, nodeId, control) {
AppDispatcher.dispatch({
type: ActionTypes.DO_CONTROL
});
WebapiUtils.doControl(
probeId,
nodeId,
control,
);
},

enterEdge: function(edgeId) {
AppDispatcher.dispatch({
type: ActionTypes.ENTER_EDGE,
Expand Down Expand Up @@ -107,6 +124,19 @@ module.exports = {
});
},

receiveControlError: function(err) {
AppDispatcher.dispatch({
type: ActionTypes.DO_CONTROL_ERROR,
error: err
});
},

receiveControlSuccess: function() {
AppDispatcher.dispatch({
type: ActionTypes.DO_CONTROL_SUCCESS
});
},

receiveNodeDetails: function(details) {
AppDispatcher.dispatch({
type: ActionTypes.RECEIVE_NODE_DETAILS,
Expand Down Expand Up @@ -139,15 +169,15 @@ module.exports = {

receiveApiDetails: function(apiDetails) {
AppDispatcher.dispatch({
type: ActionTypes.RECEIVE_API_DETAILS,
version: apiDetails.version
type: ActionTypes.RECEIVE_API_DETAILS,
version: apiDetails.version
});
},

receiveError: function(errorUrl) {
AppDispatcher.dispatch({
errorUrl: errorUrl,
type: ActionTypes.RECEIVE_ERROR
errorUrl: errorUrl,
type: ActionTypes.RECEIVE_ERROR
});
},

Expand Down
9 changes: 7 additions & 2 deletions client/app/scripts/charts/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ const Node = React.createClass({
const subLabelOffsetY = labelOffsetY + 17;
const isPseudo = !!this.props.pseudo;
const color = isPseudo ? '' : this.getNodeColor(this.props.rank);
const onClick = this.props.onClick;
const onMouseEnter = this.handleMouseEnter;
const onMouseLeave = this.handleMouseLeave;
const onMouseClick = this.handleMouseClick;
const classNames = ['node'];
const animConfig = [80, 20]; // stiffness, bounce
const label = this.ellipsis(props.label, 14, scale(4 * scaleFactor));
Expand All @@ -51,7 +51,7 @@ const Node = React.createClass({
const transform = `translate(${interpolated.x.val},${interpolated.y.val})`;
return (
<g className={classes} transform={transform} id={props.id}
onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
onClick={onMouseClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
{props.highlighted && <circle r={scale(0.7 * interpolated.f.val)} className="highlighted"></circle>}
<circle r={scale(0.5 * interpolated.f.val)} className="border" stroke={color}></circle>
<circle r={scale(0.45 * interpolated.f.val)} className="shadow"></circle>
Expand Down Expand Up @@ -79,6 +79,11 @@ const Node = React.createClass({
return truncatedText;
},

handleMouseClick: function(ev) {
ev.stopPropagation();
AppActions.clickNode(ev.currentTarget.id);
},

handleMouseEnter: function(ev) {
AppActions.enterNode(ev.currentTarget.id);
},
Expand Down
4 changes: 2 additions & 2 deletions client/app/scripts/charts/nodes-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ const NodesChart = React.createClass({
<div className="nodes-chart">
{errorEmpty}
{errorMaxNodesExceeded}
<svg width="100%" height="100%" className={svgClassNames} onMouseUp={this.handleMouseUp}>
<svg width="100%" height="100%" className={svgClassNames} onClick={this.handleMouseClick}>
<Spring endValue={{val: translate, config: [80, 20]}}>
{function(interpolated) {
let interpolatedTranslate = wasShifted ? interpolated.val : panTranslate;
Expand Down Expand Up @@ -402,7 +402,7 @@ const NodesChart = React.createClass({

isZooming: false, // distinguish pan/zoom from click

handleMouseUp: function() {
handleMouseClick: function() {
if (!this.isZooming) {
AppActions.clickCloseDetails();
// allow shifts again
Expand Down
2 changes: 2 additions & 0 deletions client/app/scripts/components/__tests__/node-details-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ jest.dontMock('../node-details.js');
jest.dontMock('../../mixins/node-color-mixin');
jest.dontMock('../../utils/title-utils');

__WS_URL__ = false

describe('NodeDetails', () => {
let NodeDetails;
let nodes;
Expand Down
4 changes: 4 additions & 0 deletions client/app/scripts/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const ESC_KEY_CODE = 27;
function getStateFromStores() {
return {
activeTopologyOptions: AppStore.getActiveTopologyOptions(),
controlError: AppStore.getControlError(),
controlPending: AppStore.isControlPending(),
currentTopology: AppStore.getCurrentTopology(),
currentTopologyId: AppStore.getCurrentTopologyId(),
currentTopologyOptions: AppStore.getCurrentTopologyOptions(),
Expand Down Expand Up @@ -81,6 +83,8 @@ const App = React.createClass({
return (
<div>
{showingDetails && <Details nodes={this.state.nodes}
controlError={this.state.controlError}
controlPending={this.state.controlPending}
nodeId={this.state.selectedNodeId}
details={this.state.nodeDetails} /> }

Expand Down
14 changes: 1 addition & 13 deletions client/app/scripts/components/details.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const React = require('react');
const mui = require('material-ui');
const Paper = mui.Paper;

const AppActions = require('../actions/app-actions');
const NodeDetails = require('./node-details');

const Details = React.createClass({
Expand All @@ -11,21 +10,10 @@ const Details = React.createClass({
return (
<div id="details">
<Paper zDepth={3} style={{height: '100%', paddingBottom: 8}}>
<div className="details-tools-wrapper">
<div className="details-tools">
<span className="fa fa-close" onClick={this.handleClickClose} />
</div>
</div>
<NodeDetails nodeId={this.props.nodeId} details={this.props.details}
nodes={this.props.nodes} />
<NodeDetails {...this.props} />
</Paper>
</div>
);
},

handleClickClose: function(ev) {
ev.preventDefault();
AppActions.clickCloseDetails();
}

});
Expand Down
23 changes: 23 additions & 0 deletions client/app/scripts/components/node-control-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const React = require('react');
const AppActions = require('../actions/app-actions');

const NodeControlButton = React.createClass({

render: function() {
let className = `node-control-button fa ${this.props.control.icon}`;
if (this.props.pending) {
className += ' node-control-button-pending';
}
return (
<span className={className} title={this.props.control.human} onClick={this.handleClick} />
);
},

handleClick: function(ev) {
ev.preventDefault();
AppActions.doControl(this.props.control.probeId, this.props.control.nodeId, this.props.control.id);
}

});

module.exports = NodeControlButton;
25 changes: 25 additions & 0 deletions client/app/scripts/components/node-details-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const React = require('react');

const NodeControlButton = require('./node-control-button');

const NodeDetailsControls = React.createClass({

render: function() {
return (
<div className="node-details-controls">
{this.props.error && <div className="node-details-controls-error" title={this.props.error}>
<span className="node-details-controls-error-icon fa fa-warning" />
<span className="node-details-controls-error-messages">{this.props.error}</span>
</div>}
{this.props.controls && this.props.controls.map(control => {
return (
<NodeControlButton control={control} pending={this.props.pending} />
);
})}
</div>
);
}

});

module.exports = NodeDetailsControls;
3 changes: 3 additions & 0 deletions client/app/scripts/components/node-details.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const _ = require('lodash');
const React = require('react');

const NodeDetailsControls = require('./node-details-controls');
const NodeDetailsTable = require('./node-details-table');
const NodeColorMixin = require('../mixins/node-color-mixin');
const TitleUtils = require('../utils/title-utils');
Expand Down Expand Up @@ -65,6 +66,8 @@ const NodeDetails = React.createClass({
return (
<div className="node-details">
<div className="node-details-header" style={style}>
<NodeDetailsControls controls={details.controls}
pending={this.props.controlPending} error={this.props.controlError} />
<h2 className="node-details-header-label truncate" title={details.label_major}>
{details.label_major}
</h2>
Expand Down
6 changes: 0 additions & 6 deletions client/app/scripts/components/nodes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const React = require('react');

const NodesChart = require('../charts/nodes-chart');
const AppActions = require('../actions/app-actions');

const navbarHeight = 160;
const marginTop = 0;
Expand All @@ -23,18 +22,13 @@ const Nodes = React.createClass({
window.removeEventListener('resize', this.handleResize);
},

onNodeClick: function(ev) {
AppActions.clickNode(ev.currentTarget.id);
},

render: function() {
return (
<NodesChart
highlightedEdgeIds={this.props.highlightedEdgeIds}
highlightedNodeIds={this.props.highlightedNodeIds}
selectedNodeId={this.props.selectedNodeId}
nodes={this.props.nodes}
onNodeClick={this.onNodeClick}
width={this.state.width}
height={this.state.height}
topologyId={this.props.topologyId}
Expand Down
4 changes: 4 additions & 0 deletions client/app/scripts/constants/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ const keymirror = require('keymirror');

module.exports = keymirror({
CHANGE_TOPOLOGY_OPTION: null,
CLEAR_CONTROL_ERROR: null,
CLICK_CLOSE_DETAILS: null,
CLICK_NODE: null,
CLICK_TOPOLOGY: null,
CLOSE_WEBSOCKET: null,
DO_CONTROL: null,
DO_CONTROL_ERROR: null,
DO_CONTROL_SUCCESS: null,
ENTER_EDGE: null,
ENTER_NODE: null,
HIT_ESC_KEY: null,
Expand Down
2 changes: 1 addition & 1 deletion client/app/scripts/dispatcher/app-dispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const AppDispatcher = new flux.Dispatcher();

AppDispatcher.dispatch = _.wrap(flux.Dispatcher.prototype.dispatch, function(func) {
const args = Array.prototype.slice.call(arguments, 1);
// console.log(args[0]);
console.log(args[0]);
func.apply(this, args);
});

Expand Down
8 changes: 0 additions & 8 deletions client/app/scripts/local.js

This file was deleted.

Loading

0 comments on commit abcb94b

Please sign in to comment.