summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json2
-rw-r--r--src/actions/simulations.js6
-rw-r--r--src/colors/index.js30
-rw-r--r--src/components/map/MapConstants.js6
-rw-r--r--src/components/map/MapStage.js55
-rw-r--r--src/components/map/elements/Backdrop.js16
-rw-r--r--src/components/map/elements/MapTile.js24
-rw-r--r--src/components/map/groups/GridGroup.js28
-rw-r--r--src/components/map/groups/RoomGroup.js16
-rw-r--r--src/components/modals/Modal.js23
-rw-r--r--src/components/simulations/SimulationActionButtons.js8
-rw-r--r--src/components/simulations/SimulationAuthList.sass7
-rw-r--r--src/containers/simulations/SimulationActions.js3
-rw-r--r--src/pages/App.js45
-rw-r--r--src/reducers/simulations.js10
-rw-r--r--src/routes/index.js4
-rw-r--r--src/util/jquery.js2
17 files changed, 263 insertions, 22 deletions
diff --git a/package.json b/package.json
index 31af798c..7f0ecfd8 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"classnames": "^2.2.5",
"history": "^4.6.3",
"isomorphic-fetch": "^2.2.1",
+ "konva": "^1.6.7",
"node-sass-chokidar": "^0.0.3",
"normalizr": "^3.2.3",
"npm-run-all": "^4.0.2",
@@ -16,6 +17,7 @@
"react-dom": "^15.6.1",
"react-fontawesome": "^1.6.1",
"react-google-login": "^2.9.3",
+ "react-konva": "^1.1.4",
"react-mailto": "^0.4.0",
"react-redux": "^5.0.5",
"react-router-dom": "^4.1.1",
diff --git a/src/actions/simulations.js b/src/actions/simulations.js
index ddfacaf0..926c67bc 100644
--- a/src/actions/simulations.js
+++ b/src/actions/simulations.js
@@ -5,7 +5,7 @@ export const ADD_SIMULATION = "ADD_SIMULATION";
export const ADD_SIMULATION_SUCCEEDED = "ADD_SIMULATION_SUCCEEDED";
export const DELETE_SIMULATION = "DELETE_SIMULATION";
export const DELETE_SIMULATION_SUCCEEDED = "DELETE_SIMULATION_SUCCEEDED";
-export const OPEN_SIMULATION = "OPEN_SIMULATION";
+export const OPEN_SIMULATION_SUCCEEDED = "OPEN_SIMULATION_SUCCEEDED";
export function setAuthVisibilityFilter(filter) {
return {
@@ -58,9 +58,9 @@ export function deleteSimulationSucceeded(id) {
};
}
-export function openSimulation(id) {
+export function openSimulationSucceeded(id) {
return {
- type: OPEN_SIMULATION,
+ type: OPEN_SIMULATION_SUCCEEDED,
id
};
}
diff --git a/src/colors/index.js b/src/colors/index.js
new file mode 100644
index 00000000..aa7c0a5d
--- /dev/null
+++ b/src/colors/index.js
@@ -0,0 +1,30 @@
+export const GRID_COLOR = "rgba(0, 0, 0, 0.5)";
+export const BACKDROP_COLOR = "rgba(255, 255, 255, 1)";
+export const WALL_COLOR = "rgba(0, 0, 0, 1)";
+
+export const ROOM_DEFAULT_COLOR = "rgba(150, 150, 150, 1)";
+export const ROOM_SELECTED_COLOR = "rgba(51, 153, 255, 1)";
+export const ROOM_HOVER_VALID_COLOR = "rgba(51, 153, 255, 0.5)";
+export const ROOM_HOVER_INVALID_COLOR = "rgba(255, 102, 0, 0.5)";
+export const ROOM_NAME_COLOR = "rgba(245, 245, 245, 1)";
+export const ROOM_TYPE_COLOR = "rgba(245, 245, 245, 1)";
+
+export const RACK_BACKGROUND_COLOR = "rgba(170, 170, 170, 1)";
+export const RACK_BORDER_COLOR = "rgba(0, 0, 0, 1)";
+export const RACK_SPACE_BAR_BACKGROUND_COLOR = "rgba(222, 235, 247, 1)";
+export const RACK_SPACE_BAR_FILL_COLOR = "rgba(91, 155, 213, 1)";
+export const RACK_ENERGY_BAR_BACKGROUND_COLOR = "rgba(255, 242, 204, 1)";
+export const RACK_ENERGY_BAR_FILL_COLOR = "rgba(255, 192, 0, 1)";
+
+export const COOLING_ITEM_BACKGROUND_COLOR = "rgba(40, 50, 230, 1)";
+export const COOLING_ITEM_BORDER_COLOR = "rgba(0, 0, 0, 1)";
+
+export const PSU_BACKGROUND_COLOR = "rgba(230, 50, 60, 1)";
+export const PSU_BORDER_COLOR = "rgba(0, 0, 0, 1)";
+
+export const GRAYED_OUT_AREA_COLOR = "rgba(0, 0, 0, 0.6)";
+
+export const SIM_LOW_COLOR = "rgba(197, 224, 180, 1)";
+export const SIM_MID_LOW_COLOR = "rgba(255, 230, 153, 1)";
+export const SIM_MID_HIGH_COLOR = "rgba(248, 203, 173, 1)";
+export const SIM_HIGH_COLOR = "rgba(249, 165, 165, 1)";
diff --git a/src/components/map/MapConstants.js b/src/components/map/MapConstants.js
new file mode 100644
index 00000000..74779c94
--- /dev/null
+++ b/src/components/map/MapConstants.js
@@ -0,0 +1,6 @@
+export const MAP_SIZE = 50;
+export const TILE_SIZE_IN_PIXELS = 50;
+export const MAP_SIZE_IN_PIXELS = MAP_SIZE * TILE_SIZE_IN_PIXELS;
+
+export const GRID_LINE_WIDTH_IN_PIXELS = 2;
+export const ROOM_BORDER_WIDTH_IN_PIXELS = 5;
diff --git a/src/components/map/MapStage.js b/src/components/map/MapStage.js
new file mode 100644
index 00000000..38047064
--- /dev/null
+++ b/src/components/map/MapStage.js
@@ -0,0 +1,55 @@
+import React from "react";
+import {Group, Layer, Stage} from "react-konva";
+import jQuery from "../../util/jquery";
+import Backdrop from "./elements/Backdrop";
+import GridGroup from "./groups/GridGroup";
+import RoomGroup from "./groups/RoomGroup";
+import {MAP_SIZE_IN_PIXELS} from "./MapConstants";
+
+class MapStage extends React.Component {
+ state = {
+ width: 600,
+ height: 400
+ };
+
+ componentWillMount() {
+ this.updateDimensions();
+ }
+
+ componentDidMount() {
+ window.addEventListener("resize", this.updateDimensions.bind(this));
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener("resize", this.updateDimensions.bind(this));
+ }
+
+ updateDimensions() {
+ this.setState({width: jQuery(window).width(), height: jQuery(window).height()});
+ }
+
+ dragBoundHandler(pos) {
+ return {
+ x: pos.x > 0 ? 0 :
+ (pos.x < -MAP_SIZE_IN_PIXELS + this.state.width ? -MAP_SIZE_IN_PIXELS + this.state.width : pos.x),
+ y: pos.y > 0 ? 0 :
+ (pos.y < -MAP_SIZE_IN_PIXELS + this.state.height ? -MAP_SIZE_IN_PIXELS + this.state.height : pos.y)
+ }
+ }
+
+ render() {
+ return (
+ <Stage width={this.state.width} height={this.state.height}>
+ <Layer>
+ <Group draggable={true} dragBoundFunc={this.dragBoundHandler.bind(this)}>
+ <Backdrop/>
+ <RoomGroup/>
+ <GridGroup/>
+ </Group>
+ </Layer>
+ </Stage>
+ )
+ }
+}
+
+export default MapStage;
diff --git a/src/components/map/elements/Backdrop.js b/src/components/map/elements/Backdrop.js
new file mode 100644
index 00000000..32e95989
--- /dev/null
+++ b/src/components/map/elements/Backdrop.js
@@ -0,0 +1,16 @@
+import React from "react";
+import {Rect} from "react-konva";
+import {BACKDROP_COLOR} from "../../../colors/index";
+import {MAP_SIZE_IN_PIXELS} from "../MapConstants";
+
+const Backdrop = () => (
+ <Rect
+ x={0}
+ y={0}
+ width={MAP_SIZE_IN_PIXELS}
+ height={MAP_SIZE_IN_PIXELS}
+ fill={BACKDROP_COLOR}
+ />
+);
+
+export default Backdrop;
diff --git a/src/components/map/elements/MapTile.js b/src/components/map/elements/MapTile.js
new file mode 100644
index 00000000..b0f4959d
--- /dev/null
+++ b/src/components/map/elements/MapTile.js
@@ -0,0 +1,24 @@
+import PropTypes from "prop-types";
+import React from "react";
+import {Group, Rect} from "react-konva";
+import {TILE_SIZE_IN_PIXELS} from "../MapConstants";
+
+const MapTile = () => (
+ <Group>
+ <Rect
+ x={this.props.tileX * TILE_SIZE_IN_PIXELS}
+ y={this.props.tileY * TILE_SIZE_IN_PIXELS}
+ width={TILE_SIZE_IN_PIXELS}
+ height={TILE_SIZE_IN_PIXELS}
+ fill={this.props.fillColor}
+ />
+ </Group>
+);
+
+MapTile.propTypes = {
+ tileX: PropTypes.number.isRequired,
+ tileY: PropTypes.number.isRequired,
+ fillColor: PropTypes.string.isRequired,
+};
+
+export default MapTile;
diff --git a/src/components/map/groups/GridGroup.js b/src/components/map/groups/GridGroup.js
new file mode 100644
index 00000000..2651bf19
--- /dev/null
+++ b/src/components/map/groups/GridGroup.js
@@ -0,0 +1,28 @@
+import React from "react";
+import {Group, Line} from "react-konva";
+import {GRID_COLOR} from "../../../colors/index";
+import {GRID_LINE_WIDTH_IN_PIXELS, MAP_SIZE, MAP_SIZE_IN_PIXELS, TILE_SIZE_IN_PIXELS} from "../MapConstants";
+
+const MAP_COORDINATE_ENTRIES = Array.from(new Array(MAP_SIZE), (x, i) => i);
+const HORIZONTAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map(index => [
+ 0, index * TILE_SIZE_IN_PIXELS,
+ MAP_SIZE_IN_PIXELS, index * TILE_SIZE_IN_PIXELS
+]);
+const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map(index => [
+ index * TILE_SIZE_IN_PIXELS, 0,
+ index * TILE_SIZE_IN_PIXELS, MAP_SIZE_IN_PIXELS
+]);
+
+const GridGroup = () => (
+ <Group>
+ {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map(points => (
+ <Line
+ points={points}
+ stroke={GRID_COLOR}
+ strokeWidth={GRID_LINE_WIDTH_IN_PIXELS}
+ />
+ ))}
+ </Group>
+);
+
+export default GridGroup;
diff --git a/src/components/map/groups/RoomGroup.js b/src/components/map/groups/RoomGroup.js
new file mode 100644
index 00000000..1a8b18d5
--- /dev/null
+++ b/src/components/map/groups/RoomGroup.js
@@ -0,0 +1,16 @@
+import React from "react";
+import {Group, Rect} from "react-konva";
+
+const RoomGroup = () => (
+ <Group>
+ <Rect
+ x={10}
+ y={10}
+ width={50}
+ height={50}
+ fill="green"
+ />
+ </Group>
+);
+
+export default RoomGroup;
diff --git a/src/components/modals/Modal.js b/src/components/modals/Modal.js
index 06273a46..193746b3 100644
--- a/src/components/modals/Modal.js
+++ b/src/components/modals/Modal.js
@@ -1,6 +1,7 @@
import classNames from "classnames";
import PropTypes from "prop-types";
import React from "react";
+import jQuery from "../../util/jquery";
class Modal extends React.Component {
static propTypes = {
@@ -33,15 +34,15 @@ class Modal extends React.Component {
this.openOrCloseModal();
// Trigger auto-focus
- window["$"]("#" + this.id).on("shown.bs.modal", function () {
- window["$"](this).find("input").first().focus();
- });
-
- window["$"]("#" + this.id).on("hide.bs.modal", () => {
- if (this.visible) {
- this.props.onCancel();
- }
- });
+ jQuery("#" + this.id)
+ .on("shown.bs.modal", function () {
+ jQuery(this).find("input").first().focus();
+ })
+ .on("hide.bs.modal", () => {
+ if (this.visible) {
+ this.props.onCancel();
+ }
+ });
}
componentDidUpdate() {
@@ -66,11 +67,11 @@ class Modal extends React.Component {
}
openModal() {
- window["$"]("#" + this.id).modal("show");
+ jQuery("#" + this.id).modal("show");
}
closeModal() {
- window["$"]("#" + this.id).modal("hide");
+ jQuery("#" + this.id).modal("hide");
}
openOrCloseModal() {
diff --git a/src/components/simulations/SimulationActionButtons.js b/src/components/simulations/SimulationActionButtons.js
index d48b4bcf..1731b9be 100644
--- a/src/components/simulations/SimulationActionButtons.js
+++ b/src/components/simulations/SimulationActionButtons.js
@@ -1,11 +1,12 @@
import PropTypes from "prop-types";
import React from 'react';
+import {Link} from "react-router-dom";
-const SimulationActionButtons = ({simulationId, onOpen, onViewUsers, onDelete}) => (
+const SimulationActionButtons = ({simulationId, onViewUsers, onDelete}) => (
<div className="simulation-icons">
- <div className="open" title="Open this simulation" onClick={() => onOpen(simulationId)}>
+ <Link to={"/simulations/" + simulationId} className="open" title="Open this simulation">
<span className="fa fa-play"/>
- </div>
+ </Link>
<div className="users" title="View and edit collaborators on this simulation"
onClick={() => onViewUsers(simulationId)}>
<span className="fa fa-users"/>
@@ -18,7 +19,6 @@ const SimulationActionButtons = ({simulationId, onOpen, onViewUsers, onDelete})
SimulationActionButtons.propTypes = {
simulationId: PropTypes.number.isRequired,
- onOpen: PropTypes.func,
onViewUsers: PropTypes.func,
onDelete: PropTypes.func,
};
diff --git a/src/components/simulations/SimulationAuthList.sass b/src/components/simulations/SimulationAuthList.sass
index 10334c9f..58683446 100644
--- a/src/components/simulations/SimulationAuthList.sass
+++ b/src/components/simulations/SimulationAuthList.sass
@@ -57,8 +57,8 @@
.simulation-row .simulation-icons
text-align: right
-.simulation-row .simulation-icons div
- display: inline
+.simulation-row .simulation-icons div, .simulation-row .simulation-icons a
+ display: inline-block
position: relative
top: 4px
width: 30px
@@ -79,6 +79,9 @@
$icon-color: #0c60bf
background: $icon-color
+ span
+ left: 1px
+
&:hover
background: lighten($icon-color, 10%)
diff --git a/src/containers/simulations/SimulationActions.js b/src/containers/simulations/SimulationActions.js
index e2ca2795..01ccaa91 100644
--- a/src/containers/simulations/SimulationActions.js
+++ b/src/containers/simulations/SimulationActions.js
@@ -1,5 +1,5 @@
import {connect} from "react-redux";
-import {deleteSimulation, openSimulation} from "../../actions/simulations";
+import {deleteSimulation} from "../../actions/simulations";
import SimulationActionButtons from "../../components/simulations/SimulationActionButtons";
const mapStateToProps = (state, ownProps) => {
@@ -10,7 +10,6 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = dispatch => {
return {
- onOpen: (id) => dispatch(openSimulation(id)),
onViewUsers: (id) => {},
onDelete: (id) => dispatch(deleteSimulation(id)),
};
diff --git a/src/pages/App.js b/src/pages/App.js
new file mode 100644
index 00000000..a2a1050b
--- /dev/null
+++ b/src/pages/App.js
@@ -0,0 +1,45 @@
+import PropTypes from "prop-types";
+import React from 'react';
+import {connect} from "react-redux";
+import {openSimulationSucceeded} from "../actions/simulations";
+import {fetchAuthorizationsOfCurrentUser} from "../actions/users";
+import MapStage from "../components/map/MapStage";
+import Navbar from "../components/navigation/Navbar";
+import Login from "../containers/auth/Login";
+
+class AppContainer extends React.Component {
+ static propTypes = {
+ simulationId: PropTypes.number.isRequired,
+ };
+
+ componentDidMount() {
+ this.props.storeSimulationId(this.props.simulationId);
+ this.props.fetchAuthorizationsOfCurrentUser();
+ }
+
+ render() {
+ return (
+ <div className="full-height">
+ <Navbar/>
+ <div className="full-height">
+ <MapStage/>
+ </div>
+ <Login visible={false}/>
+ </div>
+ );
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ storeSimulationId: id => dispatch(openSimulationSucceeded(id)),
+ fetchAuthorizationsOfCurrentUser: () => dispatch(fetchAuthorizationsOfCurrentUser()),
+ };
+};
+
+const App = connect(
+ undefined,
+ mapDispatchToProps
+)(AppContainer);
+
+export default App;
diff --git a/src/reducers/simulations.js b/src/reducers/simulations.js
index 7d0b9d66..9bca7740 100644
--- a/src/reducers/simulations.js
+++ b/src/reducers/simulations.js
@@ -1,6 +1,7 @@
import {
ADD_SIMULATION_SUCCEEDED,
DELETE_SIMULATION_SUCCEEDED,
+ OPEN_SIMULATION_SUCCEEDED,
SET_AUTH_VISIBILITY_FILTER
} from "../actions/simulations";
import {FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED} from "../actions/users";
@@ -29,3 +30,12 @@ export function authVisibilityFilter(state = "SHOW_ALL", action) {
return state;
}
}
+
+export function currentSimulationId(state = -1, action) {
+ switch (action.type) {
+ case OPEN_SIMULATION_SUCCEEDED:
+ return action.id;
+ default:
+ return state;
+ }
+}
diff --git a/src/routes/index.js b/src/routes/index.js
index af1b70b5..deaf8ded 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -1,18 +1,22 @@
import React from 'react';
import {BrowserRouter, Redirect, Route, Switch} from "react-router-dom";
import {userIsLoggedIn} from "../auth/index";
+import App from "../pages/App";
import Home from "../pages/Home";
import NotFound from "../pages/NotFound";
import Profile from "../pages/Profile";
import Simulations from "../pages/Simulations";
const ProtectedComponent = (component) => () => userIsLoggedIn() ? component : <Redirect to="/"/>;
+const AppComponent = ({match}) => userIsLoggedIn() ?
+ <App simulationId={parseInt(match.params.id, 10)}/> : <Redirect to="/"/>;
const Routes = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/simulations" render={ProtectedComponent(<Simulations/>)}/>
+ <Route exact path="/simulations/:id" component={AppComponent}/>
<Route exact path="/profile" render={ProtectedComponent(<Profile/>)}/>
<Route path="/*" component={NotFound}/>
</Switch>
diff --git a/src/util/jquery.js b/src/util/jquery.js
new file mode 100644
index 00000000..dd0a7222
--- /dev/null
+++ b/src/util/jquery.js
@@ -0,0 +1,2 @@
+const jQuery = window["$"];
+export default jQuery;