diff options
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | src/actions/simulations.js | 6 | ||||
| -rw-r--r-- | src/colors/index.js | 30 | ||||
| -rw-r--r-- | src/components/map/MapConstants.js | 6 | ||||
| -rw-r--r-- | src/components/map/MapStage.js | 55 | ||||
| -rw-r--r-- | src/components/map/elements/Backdrop.js | 16 | ||||
| -rw-r--r-- | src/components/map/elements/MapTile.js | 24 | ||||
| -rw-r--r-- | src/components/map/groups/GridGroup.js | 28 | ||||
| -rw-r--r-- | src/components/map/groups/RoomGroup.js | 16 | ||||
| -rw-r--r-- | src/components/modals/Modal.js | 23 | ||||
| -rw-r--r-- | src/components/simulations/SimulationActionButtons.js | 8 | ||||
| -rw-r--r-- | src/components/simulations/SimulationAuthList.sass | 7 | ||||
| -rw-r--r-- | src/containers/simulations/SimulationActions.js | 3 | ||||
| -rw-r--r-- | src/pages/App.js | 45 | ||||
| -rw-r--r-- | src/reducers/simulations.js | 10 | ||||
| -rw-r--r-- | src/routes/index.js | 4 | ||||
| -rw-r--r-- | src/util/jquery.js | 2 |
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; |
