summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGeorgios Andreadis <g.andreadis@student.tudelft.nl>2017-08-31 17:59:51 +0200
committerGeorgios Andreadis <g.andreadis@student.tudelft.nl>2017-09-23 10:05:50 +0200
commit3f736cd3db63f106eac02f220477b4a0f3b0eceb (patch)
tree80afa73f8c4d281b2fccba8ad2baa7c10f7e7e84 /src
parentb17f1d8cb4815f57a4b7043cc91b867ec3cbc867 (diff)
Implement room creation
Diffstat (limited to 'src')
-rw-r--r--src/actions/objects.js22
-rw-r--r--src/actions/topology.js112
-rw-r--r--src/api/routes/token-signin.js2
-rw-r--r--src/colors/index.js2
-rw-r--r--src/components/map/MapStage.js31
-rw-r--r--src/components/map/elements/HoverTile.js25
-rw-r--r--src/components/map/elements/RoomTile.js6
-rw-r--r--src/components/map/groups/DatacenterGroup.js16
-rw-r--r--src/components/map/groups/RoomGroup.js39
-rw-r--r--src/components/map/groups/TileGroup.js8
-rw-r--r--src/components/map/groups/WallGroup.js22
-rw-r--r--src/components/map/layers/HoverTileLayerComponent.js27
-rw-r--r--src/components/sidebars/BuildingSidebarContent.js9
-rw-r--r--src/components/sidebars/topology/TopologySidebarComponent.js (renamed from src/components/sidebars/TopologySidebarComponent.js)4
-rw-r--r--src/components/sidebars/topology/building/BuildingSidebarContentComponent.js20
-rw-r--r--src/components/sidebars/topology/building/CancelNewRoomConstructionComponent.js9
-rw-r--r--src/components/sidebars/topology/building/FinishNewRoomConstructionComponent.js9
-rw-r--r--src/components/sidebars/topology/building/StartNewRoomConstructionComponent.js9
-rw-r--r--src/containers/map/DatacenterContainer.js5
-rw-r--r--src/containers/map/RoomContainer.js8
-rw-r--r--src/containers/map/TileContainer.js13
-rw-r--r--src/containers/map/WallContainer.js14
-rw-r--r--src/containers/map/layers/HoverTileLayer.js23
-rw-r--r--src/containers/sidebars/topology/TopologySidebar.js (renamed from src/containers/sidebars/TopologySidebar.js)2
-rw-r--r--src/containers/sidebars/topology/building/BuildingSidebarContent.js14
-rw-r--r--src/containers/sidebars/topology/building/CancelNewRoomConstructionButton.js16
-rw-r--r--src/containers/sidebars/topology/building/FinishNewRoomConstructionButton.js16
-rw-r--r--src/containers/sidebars/topology/building/StartNewRoomConstructionButton.js16
-rw-r--r--src/containers/simulations/SimulationActions.js2
-rw-r--r--src/index.sass5
-rw-r--r--src/pages/App.js2
-rw-r--r--src/reducers/index.js3
-rw-r--r--src/reducers/objects.js33
-rw-r--r--src/reducers/topology.js19
-rw-r--r--src/sagas/index.js20
-rw-r--r--src/sagas/objects.js7
-rw-r--r--src/sagas/topology.js74
-rw-r--r--src/util/tile-calculations.js6
38 files changed, 588 insertions, 82 deletions
diff --git a/src/actions/objects.js b/src/actions/objects.js
index 6fdb83cd..60885471 100644
--- a/src/actions/objects.js
+++ b/src/actions/objects.js
@@ -1,5 +1,7 @@
export const ADD_TO_STORE = "ADD_TO_STORE";
export const ADD_PROP_TO_STORE_OBJECT = "ADD_PROP_TO_STORE_OBJECT";
+export const ADD_ID_TO_STORE_OBJECT_LIST_PROP = "ADD_ID_TO_STORE_OBJECT_LIST_PROP";
+export const REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP = "REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP";
export function addToStore(objectType, object) {
return {
@@ -17,3 +19,23 @@ export function addPropToStoreObject(objectType, objectId, propObject) {
propObject
};
}
+
+export function addIdToStoreObjectListProp(objectType, objectId, propName, id) {
+ return {
+ type: ADD_ID_TO_STORE_OBJECT_LIST_PROP,
+ objectType,
+ objectId,
+ propName,
+ id
+ };
+}
+
+export function removeIdFromStoreObjectListProp(objectType, objectId, propName, id) {
+ return {
+ type: REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP,
+ objectType,
+ objectId,
+ propName,
+ id
+ };
+}
diff --git a/src/actions/topology.js b/src/actions/topology.js
index de742bb1..76b1ef27 100644
--- a/src/actions/topology.js
+++ b/src/actions/topology.js
@@ -1,7 +1,18 @@
+import {addIdToStoreObjectListProp, removeIdFromStoreObjectListProp} from "./objects";
+
export const FETCH_TOPOLOGY_OF_DATACENTER = "FETCH_TOPOLOGY_OF_DATACENTER";
export const FETCH_TOPOLOGY_OF_DATACENTER_SUCCEEDED = "FETCH_TOPOLOGY_OF_DATACENTER_SUCCEEDED";
export const FETCH_LATEST_DATACENTER = "FETCH_LATEST_DATACENTER";
export const FETCH_LATEST_DATACENTER_SUCCEEDED = "FETCH_LATEST_DATACENTER_SUCCEEDED";
+export const START_NEW_ROOM_CONSTRUCTION = "START_NEW_ROOM_CONSTRUCTION";
+export const START_NEW_ROOM_CONSTRUCTION_SUCCEEDED = "START_NEW_ROOM_CONSTRUCTION_SUCCEEDED";
+export const FINISH_NEW_ROOM_CONSTRUCTION = "FINISH_NEW_ROOM_CONSTRUCTION";
+export const CANCEL_NEW_ROOM_CONSTRUCTION = "CANCEL_NEW_ROOM_CONSTRUCTION";
+export const CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED = "CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED";
+export const ADD_TILE = "ADD_TILE";
+export const ADD_TILE_SUCCEEDED = "ADD_TILE_SUCCEEDED";
+export const DELETE_TILE = "DELETE_TILE";
+export const DELETE_TILE_SUCCEEDED = "DELETE_TILE_SUCCEEDED";
export function fetchLatestDatacenter() {
return (dispatch, getState) => {
@@ -19,3 +30,104 @@ export function fetchLatestDatacenterSucceeded(datacenterId) {
datacenterId
};
}
+
+export function startNewRoomConstruction() {
+ return {
+ type: START_NEW_ROOM_CONSTRUCTION
+ };
+}
+
+export function startNewRoomConstructionSucceeded(roomId) {
+ return (dispatch, getState) => {
+ const {currentDatacenterId} = getState();
+ dispatch(addIdToStoreObjectListProp("datacenter", currentDatacenterId, "roomIds", roomId));
+ dispatch({
+ type: START_NEW_ROOM_CONSTRUCTION_SUCCEEDED,
+ roomId
+ });
+ };
+}
+
+export function finishNewRoomConstruction() {
+ return (dispatch, getState) => {
+ const {objects, currentRoomInConstruction} = getState();
+ if (objects.room[currentRoomInConstruction].tileIds.length === 0) {
+ dispatch(cancelNewRoomConstruction());
+ return;
+ }
+
+ dispatch({
+ type: FINISH_NEW_ROOM_CONSTRUCTION
+ });
+ };
+}
+
+export function cancelNewRoomConstruction() {
+ return {
+ type: CANCEL_NEW_ROOM_CONSTRUCTION
+ };
+}
+
+export function cancelNewRoomConstructionSucceeded() {
+ return (dispatch, getState) => {
+ const {currentDatacenterId, currentRoomInConstruction} = getState();
+ dispatch(removeIdFromStoreObjectListProp("datacenter", currentDatacenterId, "roomIds",
+ currentRoomInConstruction));
+ dispatch({
+ type: CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED
+ });
+ };
+}
+
+export function toggleTileAtLocation(positionX, positionY) {
+ return (dispatch, getState) => {
+ const {objects, currentRoomInConstruction} = getState();
+
+ const tileIds = objects.room[currentRoomInConstruction].tileIds;
+ for (let index in tileIds) {
+ if (objects.tile[tileIds[index]].positionX === positionX
+ && objects.tile[tileIds[index]].positionY === positionY) {
+ dispatch(deleteTile(tileIds[index]));
+ return;
+ }
+ }
+ dispatch(addTile(positionX, positionY));
+ };
+}
+
+export function addTile(positionX, positionY) {
+ return {
+ type: ADD_TILE,
+ positionX,
+ positionY
+ };
+}
+
+export function addTileSucceeded(tileId) {
+ return (dispatch, getState) => {
+ const {currentRoomInConstruction} = getState();
+ dispatch(addIdToStoreObjectListProp("room", currentRoomInConstruction, "tileIds", tileId));
+ dispatch({
+ type: ADD_TILE_SUCCEEDED,
+ tileId
+ });
+ };
+}
+
+export function deleteTile(tileId) {
+ return {
+ type: DELETE_TILE,
+ tileId
+ }
+}
+
+export function deleteTileSucceeded(tileId) {
+ return (dispatch, getState) => {
+ const {currentRoomInConstruction} = getState();
+ dispatch(removeIdFromStoreObjectListProp("room", currentRoomInConstruction, "tileIds", tileId));
+ dispatch({
+ type: DELETE_TILE_SUCCEEDED,
+ tileId
+ });
+ };
+}
diff --git a/src/api/routes/token-signin.js b/src/api/routes/token-signin.js
index 76a39572..dbf70869 100644
--- a/src/api/routes/token-signin.js
+++ b/src/api/routes/token-signin.js
@@ -1,5 +1,5 @@
export function performTokenSignIn(token) {
- return new Promise((resolve, reject) => {
+ return new Promise(resolve => {
window["jQuery"].post(
"/tokensignin",
{idtoken: token},
diff --git a/src/colors/index.js b/src/colors/index.js
index cc5b86fe..22ce9feb 100644
--- a/src/colors/index.js
+++ b/src/colors/index.js
@@ -3,7 +3,7 @@ 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_IN_CONSTRUCTION_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)";
diff --git a/src/components/map/MapStage.js b/src/components/map/MapStage.js
index 644b4c54..541df1d6 100644
--- a/src/components/map/MapStage.js
+++ b/src/components/map/MapStage.js
@@ -1,6 +1,7 @@
import React from "react";
import {Group, Layer, Stage} from "react-konva";
import DatacenterContainer from "../../containers/map/DatacenterContainer";
+import HoverTileLayer from "../../containers/map/layers/HoverTileLayer";
import jQuery from "../../util/jquery";
import {NAVBAR_HEIGHT} from "../navigation/Navbar";
import Backdrop from "./elements/Backdrop";
@@ -10,7 +11,11 @@ import {MAP_SIZE_IN_PIXELS} from "./MapConstants";
class MapStage extends React.Component {
state = {
width: 600,
- height: 400
+ height: 400,
+ x: 0,
+ y: 0,
+ mouseX: 0,
+ mouseY: 0
};
componentWillMount() {
@@ -29,18 +34,30 @@ class MapStage extends React.Component {
this.setState({width: jQuery(window).width(), height: jQuery(window).height() - NAVBAR_HEIGHT});
}
+ updateMousePosition() {
+ const mousePos = this.stage.getStage().getPointerPosition();
+ this.setState({mouseX: mousePos.x, mouseY: mousePos.y});
+ }
+
dragBoundFunc(pos) {
- return {
+ const updatedPosition = {
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)
- }
+ };
+
+ this.setState(updatedPosition);
+
+ return updatedPosition;
}
render() {
return (
- <Stage width={this.state.width} height={this.state.height}>
+ <Stage ref={(stage) => {this.stage = stage;}}
+ width={this.state.width}
+ height={this.state.height}
+ onMouseMove={this.updateMousePosition.bind(this)}>
<Layer>
<Group draggable={true} dragBoundFunc={this.dragBoundFunc.bind(this)}>
<Backdrop/>
@@ -48,6 +65,12 @@ class MapStage extends React.Component {
<GridGroup/>
</Group>
</Layer>
+ <HoverTileLayer
+ mainGroupX={this.state.x}
+ mainGroupY={this.state.y}
+ mouseX={this.state.mouseX}
+ mouseY={this.state.mouseY}
+ />
</Stage>
)
}
diff --git a/src/components/map/elements/HoverTile.js b/src/components/map/elements/HoverTile.js
new file mode 100644
index 00000000..a8bb2085
--- /dev/null
+++ b/src/components/map/elements/HoverTile.js
@@ -0,0 +1,25 @@
+import PropTypes from "prop-types";
+import React from "react";
+import {Rect} from "react-konva";
+import {ROOM_HOVER_INVALID_COLOR, ROOM_HOVER_VALID_COLOR} from "../../../colors/index";
+import {TILE_SIZE_IN_PIXELS} from "../MapConstants";
+
+const HoverTile = ({pixelX, pixelY, isValid, onClick}) => (
+ <Rect
+ x={pixelX}
+ y={pixelY}
+ width={TILE_SIZE_IN_PIXELS}
+ height={TILE_SIZE_IN_PIXELS}
+ fill={isValid ? ROOM_HOVER_VALID_COLOR : ROOM_HOVER_INVALID_COLOR}
+ onClick={onClick}
+ />
+);
+
+HoverTile.propTypes = {
+ pixelX: PropTypes.number.isRequired,
+ pixelY: PropTypes.number.isRequired,
+ isValid: PropTypes.bool.isRequired,
+ onClick: PropTypes.func.isRequired,
+};
+
+export default HoverTile;
diff --git a/src/components/map/elements/RoomTile.js b/src/components/map/elements/RoomTile.js
index 759dcf35..4d5e50fc 100644
--- a/src/components/map/elements/RoomTile.js
+++ b/src/components/map/elements/RoomTile.js
@@ -1,16 +1,16 @@
import React from "react";
import {Rect} from "react-konva";
-import {ROOM_DEFAULT_COLOR} from "../../../colors/index";
+import {ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR} from "../../../colors/index";
import Shapes from "../../../shapes/index";
import {TILE_SIZE_IN_PIXELS} from "../MapConstants";
-const RoomTile = ({tile}) => (
+const RoomTile = ({tile, newTile}) => (
<Rect
x={tile.positionX * TILE_SIZE_IN_PIXELS}
y={tile.positionY * TILE_SIZE_IN_PIXELS}
width={TILE_SIZE_IN_PIXELS}
height={TILE_SIZE_IN_PIXELS}
- fill={ROOM_DEFAULT_COLOR}
+ fill={newTile ? ROOM_IN_CONSTRUCTION_COLOR : ROOM_DEFAULT_COLOR}
/>
);
diff --git a/src/components/map/groups/DatacenterGroup.js b/src/components/map/groups/DatacenterGroup.js
index 2f4dc2eb..0830ac4e 100644
--- a/src/components/map/groups/DatacenterGroup.js
+++ b/src/components/map/groups/DatacenterGroup.js
@@ -12,8 +12,8 @@ const DatacenterGroup = ({datacenter, interactionLevel}) => {
if (interactionLevel.mode === "BUILDING") {
return (
<Group>
- {datacenter.rooms.map(room => (
- <RoomContainer key={room.id} room={room}/>
+ {datacenter.roomIds.map(roomId => (
+ <RoomContainer key={roomId} roomId={roomId}/>
))}
</Group>
);
@@ -21,14 +21,14 @@ const DatacenterGroup = ({datacenter, interactionLevel}) => {
return (
<Group>
- {datacenter.rooms
- .filter(room => room.id !== interactionLevel.roomId)
- .map(room => <RoomContainer key={room.id} room={room}/>)
+ {datacenter.roomIds
+ .filter(roomId => roomId !== interactionLevel.roomId)
+ .map(roomId => <RoomContainer key={roomId} roomId={roomId}/>)
}
{interactionLevel.mode === "ROOM" ? <GrayContainer/> : null}
- {datacenter.rooms
- .filter(room => room.id === interactionLevel.roomId)
- .map(room => <RoomContainer key={room.id} room={room}/>)
+ {datacenter.roomIds
+ .filter(roomId => roomId === interactionLevel.roomId)
+ .map(roomId => <RoomContainer key={roomId} roomId={roomId}/>)
}
</Group>
);
diff --git a/src/components/map/groups/RoomGroup.js b/src/components/map/groups/RoomGroup.js
index b6c285e9..85d3c7c9 100644
--- a/src/components/map/groups/RoomGroup.js
+++ b/src/components/map/groups/RoomGroup.js
@@ -2,35 +2,40 @@ import React from "react";
import {Group} from "react-konva";
import GrayContainer from "../../../containers/map/GrayContainer";
import TileContainer from "../../../containers/map/TileContainer";
+import WallContainer from "../../../containers/map/WallContainer";
import Shapes from "../../../shapes/index";
-import {deriveWallLocations} from "../../../util/tile-calculations";
-import WallSegment from "../elements/WallSegment";
-const RoomGroup = ({room, interactionLevel, onClick}) => {
+const RoomGroup = ({room, interactionLevel, currentRoomInConstruction, onClick}) => {
+ if (currentRoomInConstruction === room.id) {
+ return (
+ <Group onClick={onClick}>
+ {room.tileIds.map(tileId => (
+ <TileContainer key={tileId} tileId={tileId} newTile={true}/>
+ ))}
+ </Group>
+ );
+ }
+
return (
- <Group
- onClick={onClick}
- >
+ <Group onClick={onClick}>
{(() => {
if (interactionLevel.mode === "OBJECT" && interactionLevel.roomId === room.id) {
return [
- room.tiles
- .filter(tile => tile.id !== interactionLevel.tileId)
- .map(tile => <TileContainer key={tile.id} tile={tile}/>),
+ room.tileIds
+ .filter(tileId => tileId !== interactionLevel.tileId)
+ .map(tileId => <TileContainer key={tileId} tileId={tileId}/>),
<GrayContainer key={-1}/>,
- room.tiles
- .filter(tile => tile.id === interactionLevel.tileId)
- .map(tile => <TileContainer key={tile.id} tile={tile}/>)
+ room.tileIds
+ .filter(tileId => tileId === interactionLevel.tileId)
+ .map(tileId => <TileContainer key={tileId} tileId={tileId}/>)
];
} else {
- return room.tiles.map(tile => (
- <TileContainer key={tile.id} tile={tile}/>
+ return room.tileIds.map(tileId => (
+ <TileContainer key={tileId} tileId={tileId}/>
));
}
})()}
- {deriveWallLocations(room).map((wallSegment, index) => (
- <WallSegment key={index} wallSegment={wallSegment}/>
- ))}
+ <WallContainer roomId={room.id}/>
</Group>
);
};
diff --git a/src/components/map/groups/TileGroup.js b/src/components/map/groups/TileGroup.js
index 5920a2b6..8fbdeb31 100644
--- a/src/components/map/groups/TileGroup.js
+++ b/src/components/map/groups/TileGroup.js
@@ -1,10 +1,11 @@
+import PropTypes from "prop-types";
import React from "react";
import {Group} from "react-konva";
import Shapes from "../../../shapes/index";
import RoomTile from "../elements/RoomTile";
import RackGroup from "./RackGroup";
-const TileGroup = ({tile, onClick}) => {
+const TileGroup = ({tile, newTile, onClick}) => {
let tileObject;
switch (tile.objectType) {
case "RACK":
@@ -16,9 +17,9 @@ const TileGroup = ({tile, onClick}) => {
return (
<Group
- onClick={onClick}
+ onClick={() => onClick(tile)}
>
- <RoomTile tile={tile}/>
+ <RoomTile tile={tile} newTile={newTile}/>
{tileObject}
</Group>
);
@@ -26,6 +27,7 @@ const TileGroup = ({tile, onClick}) => {
TileGroup.propTypes = {
tile: Shapes.Tile,
+ newTile: PropTypes.bool,
};
export default TileGroup;
diff --git a/src/components/map/groups/WallGroup.js b/src/components/map/groups/WallGroup.js
new file mode 100644
index 00000000..f21d91a5
--- /dev/null
+++ b/src/components/map/groups/WallGroup.js
@@ -0,0 +1,22 @@
+import PropTypes from "prop-types";
+import React from "react";
+import {Group} from "react-konva";
+import Shapes from "../../../shapes/index";
+import {deriveWallLocations} from "../../../util/tile-calculations";
+import WallSegment from "../elements/WallSegment";
+
+const WallGroup = ({tiles}) => {
+ return (
+ <Group>
+ {deriveWallLocations(tiles).map((wallSegment, index) => (
+ <WallSegment key={index} wallSegment={wallSegment}/>
+ ))}
+ </Group>
+ );
+};
+
+WallGroup.propTypes = {
+ tiles: PropTypes.arrayOf(Shapes.Tile).isRequired,
+};
+
+export default WallGroup;
diff --git a/src/components/map/layers/HoverTileLayerComponent.js b/src/components/map/layers/HoverTileLayerComponent.js
new file mode 100644
index 00000000..691b9733
--- /dev/null
+++ b/src/components/map/layers/HoverTileLayerComponent.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import {Layer} from "react-konva";
+import HoverTile from "../elements/HoverTile";
+import {TILE_SIZE_IN_PIXELS} from "../MapConstants";
+
+const HoverTileLayerComponent = ({mainGroupX, mainGroupY, mouseX, mouseY, currentRoomInConstruction, isValid, onClick}) => {
+ if (currentRoomInConstruction === -1) {
+ return <Layer/>
+ }
+
+ const positionX = Math.floor((mouseX - mainGroupX) / TILE_SIZE_IN_PIXELS);
+ const positionY = Math.floor((mouseY - mainGroupY) / TILE_SIZE_IN_PIXELS);
+ const pixelX = positionX * TILE_SIZE_IN_PIXELS + mainGroupX;
+ const pixelY = positionY * TILE_SIZE_IN_PIXELS + mainGroupY;
+ const validity = isValid(positionX, positionY);
+
+ return (
+ <Layer>
+ <HoverTile
+ pixelX={pixelX} pixelY={pixelY}
+ isValid={validity} onClick={() => onClick(positionX, positionY)}
+ />
+ </Layer>
+ );
+};
+
+export default HoverTileLayerComponent;
diff --git a/src/components/sidebars/BuildingSidebarContent.js b/src/components/sidebars/BuildingSidebarContent.js
deleted file mode 100644
index 44688bb8..00000000
--- a/src/components/sidebars/BuildingSidebarContent.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from "react";
-
-const BuildingSidebarContent = () => {
- return (
- <p>Test</p>
- );
-};
-
-export default BuildingSidebarContent;
diff --git a/src/components/sidebars/TopologySidebarComponent.js b/src/components/sidebars/topology/TopologySidebarComponent.js
index 371463d1..932d2ecf 100644
--- a/src/components/sidebars/TopologySidebarComponent.js
+++ b/src/components/sidebars/topology/TopologySidebarComponent.js
@@ -1,6 +1,6 @@
import React from "react";
-import BuildingSidebarContent from "./BuildingSidebarContent";
-import Sidebar from "./Sidebar";
+import BuildingSidebarContent from "../../../containers/sidebars/topology/building/BuildingSidebarContent";
+import Sidebar from "../Sidebar";
const TopologySidebarComponent = ({interactionLevel}) => {
let sidebarHeading;
diff --git a/src/components/sidebars/topology/building/BuildingSidebarContentComponent.js b/src/components/sidebars/topology/building/BuildingSidebarContentComponent.js
new file mode 100644
index 00000000..b88b23b7
--- /dev/null
+++ b/src/components/sidebars/topology/building/BuildingSidebarContentComponent.js
@@ -0,0 +1,20 @@
+import React from "react";
+import CancelNewRoomConstructionButton from "../../../../containers/sidebars/topology/building/CancelNewRoomConstructionButton";
+import FinishNewRoomConstructionButton from "../../../../containers/sidebars/topology/building/FinishNewRoomConstructionButton";
+import StartNewRoomConstructionButton from "../../../../containers/sidebars/topology/building/StartNewRoomConstructionButton";
+
+const BuildingSidebarContentComponent = ({currentRoomInConstruction}) => {
+ if (currentRoomInConstruction !== -1) {
+ return (
+ <div>
+ <FinishNewRoomConstructionButton/>
+ <CancelNewRoomConstructionButton/>
+ </div>
+ );
+ }
+ return (
+ <StartNewRoomConstructionButton/>
+ );
+};
+
+export default BuildingSidebarContentComponent;
diff --git a/src/components/sidebars/topology/building/CancelNewRoomConstructionComponent.js b/src/components/sidebars/topology/building/CancelNewRoomConstructionComponent.js
new file mode 100644
index 00000000..15f199a6
--- /dev/null
+++ b/src/components/sidebars/topology/building/CancelNewRoomConstructionComponent.js
@@ -0,0 +1,9 @@
+import React from "react";
+
+const CancelNewRoomConstructionComponent = ({onClick}) => (
+ <div className="btn btn-default btn-block" onClick={onClick}>
+ Cancel construction
+ </div>
+);
+
+export default CancelNewRoomConstructionComponent;
diff --git a/src/components/sidebars/topology/building/FinishNewRoomConstructionComponent.js b/src/components/sidebars/topology/building/FinishNewRoomConstructionComponent.js
new file mode 100644
index 00000000..d9edbb61
--- /dev/null
+++ b/src/components/sidebars/topology/building/FinishNewRoomConstructionComponent.js
@@ -0,0 +1,9 @@
+import React from "react";
+
+const FinishNewRoomConstructionComponent = ({onClick}) => (
+ <div className="btn btn-primary btn-block" onClick={onClick}>
+ Finalize new room
+ </div>
+);
+
+export default FinishNewRoomConstructionComponent;
diff --git a/src/components/sidebars/topology/building/StartNewRoomConstructionComponent.js b/src/components/sidebars/topology/building/StartNewRoomConstructionComponent.js
new file mode 100644
index 00000000..60573532
--- /dev/null
+++ b/src/components/sidebars/topology/building/StartNewRoomConstructionComponent.js
@@ -0,0 +1,9 @@
+import React from "react";
+
+const StartNewRoomConstructionComponent = ({onClick}) => (
+ <div className="btn btn-primary btn-block" onClick={onClick}>
+ Construct a new room
+ </div>
+);
+
+export default StartNewRoomConstructionComponent;
diff --git a/src/containers/map/DatacenterContainer.js b/src/containers/map/DatacenterContainer.js
index 00b6e79f..8c80146d 100644
--- a/src/containers/map/DatacenterContainer.js
+++ b/src/containers/map/DatacenterContainer.js
@@ -1,16 +1,13 @@
import {connect} from "react-redux";
import DatacenterGroup from "../../components/map/groups/DatacenterGroup";
-import {denormalize} from "../../store/denormalizer";
const mapStateToProps = state => {
if (state.currentDatacenterId === -1) {
return {};
}
- const datacenter = denormalize(state, "datacenter", state.currentDatacenterId);
-
return {
- datacenter,
+ datacenter: state.objects.datacenter[state.currentDatacenterId],
interactionLevel: state.interactionLevel
};
};
diff --git a/src/containers/map/RoomContainer.js b/src/containers/map/RoomContainer.js
index d46324b2..2d078e09 100644
--- a/src/containers/map/RoomContainer.js
+++ b/src/containers/map/RoomContainer.js
@@ -2,15 +2,17 @@ import {connect} from "react-redux";
import {goFromBuildingToRoom} from "../../actions/interaction-level";
import RoomGroup from "../../components/map/groups/RoomGroup";
-const mapStateToProps = state => {
+const mapStateToProps = (state, ownProps) => {
return {
- interactionLevel: state.interactionLevel
+ interactionLevel: state.interactionLevel,
+ currentRoomInConstruction: state.currentRoomInConstruction,
+ room: state.objects.room[ownProps.roomId],
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
- onClick: () => dispatch(goFromBuildingToRoom(ownProps.room.id))
+ onClick: () => dispatch(goFromBuildingToRoom(ownProps.roomId)),
};
};
diff --git a/src/containers/map/TileContainer.js b/src/containers/map/TileContainer.js
index 0456c423..d00c3611 100644
--- a/src/containers/map/TileContainer.js
+++ b/src/containers/map/TileContainer.js
@@ -2,17 +2,18 @@ import {connect} from "react-redux";
import {goFromRoomToObject} from "../../actions/interaction-level";
import TileGroup from "../../components/map/groups/TileGroup";
-const mapStateToProps = state => {
+const mapStateToProps = (state, ownProps) => {
return {
- interactionLevel: state.interactionLevel
+ interactionLevel: state.interactionLevel,
+ tile: state.objects.tile[ownProps.tileId],
};
};
-const mapDispatchToProps = (dispatch, ownProps) => {
+const mapDispatchToProps = dispatch => {
return {
- onClick: () => {
- if (ownProps.tile.objectType) {
- dispatch(goFromRoomToObject(ownProps.tile.id))
+ onClick: tile => {
+ if (tile.objectType) {
+ dispatch(goFromRoomToObject(tile.id))
}
}
};
diff --git a/src/containers/map/WallContainer.js b/src/containers/map/WallContainer.js
new file mode 100644
index 00000000..f8ccb2e9
--- /dev/null
+++ b/src/containers/map/WallContainer.js
@@ -0,0 +1,14 @@
+import {connect} from "react-redux";
+import WallGroup from "../../components/map/groups/WallGroup";
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ tiles: state.objects.room[ownProps.roomId].tileIds.map(tileId => state.objects.tile[tileId]),
+ };
+};
+
+const WallContainer = connect(
+ mapStateToProps
+)(WallGroup);
+
+export default WallContainer;
diff --git a/src/containers/map/layers/HoverTileLayer.js b/src/containers/map/layers/HoverTileLayer.js
new file mode 100644
index 00000000..b8868233
--- /dev/null
+++ b/src/containers/map/layers/HoverTileLayer.js
@@ -0,0 +1,23 @@
+import {connect} from "react-redux";
+import {toggleTileAtLocation} from "../../../actions/topology";
+import HoverTileLayerComponent from "../../../components/map/layers/HoverTileLayerComponent";
+
+const mapStateToProps = state => {
+ return {
+ currentRoomInConstruction: state.currentRoomInConstruction,
+ isValid: (x, y) => true, // TODO implement proper validation
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onClick: (x, y) => dispatch(toggleTileAtLocation(x, y)),
+ };
+};
+
+const HoverTileLayer = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(HoverTileLayerComponent);
+
+export default HoverTileLayer;
diff --git a/src/containers/sidebars/TopologySidebar.js b/src/containers/sidebars/topology/TopologySidebar.js
index 1909443a..6ed836da 100644
--- a/src/containers/sidebars/TopologySidebar.js
+++ b/src/containers/sidebars/topology/TopologySidebar.js
@@ -1,5 +1,5 @@
import {connect} from "react-redux";
-import TopologySidebarComponent from "../../components/sidebars/TopologySidebarComponent";
+import TopologySidebarComponent from "../../../components/sidebars/topology/TopologySidebarComponent";
const mapStateToProps = state => {
return {
diff --git a/src/containers/sidebars/topology/building/BuildingSidebarContent.js b/src/containers/sidebars/topology/building/BuildingSidebarContent.js
new file mode 100644
index 00000000..c70c6982
--- /dev/null
+++ b/src/containers/sidebars/topology/building/BuildingSidebarContent.js
@@ -0,0 +1,14 @@
+import {connect} from "react-redux";
+import BuildingSidebarContentComponent from "../../../../components/sidebars/topology/building/BuildingSidebarContentComponent";
+
+const mapStateToProps = state => {
+ return {
+ currentRoomInConstruction: state.currentRoomInConstruction
+ };
+};
+
+const BuildingSidebarContent = connect(
+ mapStateToProps
+)(BuildingSidebarContentComponent);
+
+export default BuildingSidebarContent;
diff --git a/src/containers/sidebars/topology/building/CancelNewRoomConstructionButton.js b/src/containers/sidebars/topology/building/CancelNewRoomConstructionButton.js
new file mode 100644
index 00000000..6061da96
--- /dev/null
+++ b/src/containers/sidebars/topology/building/CancelNewRoomConstructionButton.js
@@ -0,0 +1,16 @@
+import {connect} from "react-redux";
+import {cancelNewRoomConstruction} from "../../../../actions/topology";
+import CancelNewRoomConstructionComponent from "../../../../components/sidebars/topology/building/CancelNewRoomConstructionComponent";
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onClick: () => dispatch(cancelNewRoomConstruction()),
+ };
+};
+
+const CancelNewRoomConstructionButton = connect(
+ null,
+ mapDispatchToProps
+)(CancelNewRoomConstructionComponent);
+
+export default CancelNewRoomConstructionButton;
diff --git a/src/containers/sidebars/topology/building/FinishNewRoomConstructionButton.js b/src/containers/sidebars/topology/building/FinishNewRoomConstructionButton.js
new file mode 100644
index 00000000..ca34dcc3
--- /dev/null
+++ b/src/containers/sidebars/topology/building/FinishNewRoomConstructionButton.js
@@ -0,0 +1,16 @@
+import {connect} from "react-redux";
+import {finishNewRoomConstruction} from "../../../../actions/topology";
+import FinishNewRoomConstructionComponent from "../../../../components/sidebars/topology/building/FinishNewRoomConstructionComponent";
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onClick: () => dispatch(finishNewRoomConstruction()),
+ };
+};
+
+const FinishNewRoomConstructionButton = connect(
+ null,
+ mapDispatchToProps
+)(FinishNewRoomConstructionComponent);
+
+export default FinishNewRoomConstructionButton;
diff --git a/src/containers/sidebars/topology/building/StartNewRoomConstructionButton.js b/src/containers/sidebars/topology/building/StartNewRoomConstructionButton.js
new file mode 100644
index 00000000..f26eb5d4
--- /dev/null
+++ b/src/containers/sidebars/topology/building/StartNewRoomConstructionButton.js
@@ -0,0 +1,16 @@
+import {connect} from "react-redux";
+import {startNewRoomConstruction} from "../../../../actions/topology";
+import StartNewRoomConstructionComponent from "../../../../components/sidebars/topology/building/StartNewRoomConstructionComponent";
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onClick: () => dispatch(startNewRoomConstruction()),
+ };
+};
+
+const StartNewRoomConstructionButton = connect(
+ null,
+ mapDispatchToProps
+)(StartNewRoomConstructionComponent);
+
+export default StartNewRoomConstructionButton;
diff --git a/src/containers/simulations/SimulationActions.js b/src/containers/simulations/SimulationActions.js
index 01ccaa91..4dc93c77 100644
--- a/src/containers/simulations/SimulationActions.js
+++ b/src/containers/simulations/SimulationActions.js
@@ -10,7 +10,7 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = dispatch => {
return {
- onViewUsers: (id) => {},
+ onViewUsers: (id) => {}, // TODO implement user viewing
onDelete: (id) => dispatch(deleteSimulation(id)),
};
};
diff --git a/src/index.sass b/src/index.sass
index 18d14843..4b36d4e9 100644
--- a/src/index.sass
+++ b/src/index.sass
@@ -1,3 +1,5 @@
+@import ./style-globals/_mixins.sass
+
html, body, #root
margin: 0
padding: 0
@@ -17,5 +19,8 @@ html, body, #root
.text-page-container
padding-top: 80px
+.btn
+ +clickable
+
a, a:hover
text-decoration: none
diff --git a/src/pages/App.js b/src/pages/App.js
index 4f973ab7..a6f4dad4 100644
--- a/src/pages/App.js
+++ b/src/pages/App.js
@@ -5,7 +5,7 @@ import {openSimulationSucceeded} from "../actions/simulations";
import {fetchLatestDatacenter} from "../actions/topology";
import MapStage from "../components/map/MapStage";
import AppNavbar from "../components/navigation/AppNavbar";
-import TopologySidebar from "../containers/sidebars/TopologySidebar";
+import TopologySidebar from "../containers/sidebars/topology/TopologySidebar";
class AppContainer extends React.Component {
static propTypes = {
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 4ddaaec9..d3ace393 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -4,7 +4,7 @@ import {interactionLevel} from "./interaction-level";
import {modals} from "./modals";
import {objects} from "./objects";
import {authorizationsOfCurrentUser, authVisibilityFilter, currentSimulationId} from "./simulations";
-import {currentDatacenterId} from "./topology";
+import {currentDatacenterId, currentRoomInConstruction} from "./topology";
const rootReducer = combineReducers({
auth,
@@ -15,6 +15,7 @@ const rootReducer = combineReducers({
currentSimulationId,
currentDatacenterId,
interactionLevel,
+ currentRoomInConstruction,
});
export default rootReducer;
diff --git a/src/reducers/objects.js b/src/reducers/objects.js
index 4fbffea6..801a5456 100644
--- a/src/reducers/objects.js
+++ b/src/reducers/objects.js
@@ -1,5 +1,10 @@
import {combineReducers} from "redux";
-import {ADD_PROP_TO_STORE_OBJECT, ADD_TO_STORE} from "../actions/objects";
+import {
+ ADD_ID_TO_STORE_OBJECT_LIST_PROP,
+ ADD_PROP_TO_STORE_OBJECT,
+ ADD_TO_STORE,
+ REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP
+} from "../actions/objects";
export const objects = combineReducers({
simulation: object("simulation"),
@@ -33,14 +38,40 @@ function objectWithId(type, getId) {
if (action.type === ADD_TO_STORE) {
return Object.assign(
+ {},
state,
{[getId(action.object)]: action.object}
);
} else if (action.type === ADD_PROP_TO_STORE_OBJECT) {
return Object.assign(
+ {},
state,
{[action.objectId]: Object.assign(state[action.objectId], action.propObject)}
);
+ } else if (action.type === ADD_ID_TO_STORE_OBJECT_LIST_PROP) {
+ return Object.assign(
+ {},
+ state,
+ {
+ [action.objectId]: Object.assign(
+ {},
+ state[action.objectId],
+ {[action.propName]: [...state[action.objectId][action.propName], action.id]}
+ )
+ }
+ );
+ } else if (action.type === REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP) {
+ return Object.assign(
+ {},
+ state,
+ {
+ [action.objectId]: Object.assign(
+ {},
+ state[action.objectId],
+ {[action.propName]: state[action.objectId][action.propName].filter(id => id !== action.id)}
+ )
+ }
+ );
}
return state;
diff --git a/src/reducers/topology.js b/src/reducers/topology.js
index caafb7c1..c8690816 100644
--- a/src/reducers/topology.js
+++ b/src/reducers/topology.js
@@ -1,4 +1,9 @@
-import {FETCH_LATEST_DATACENTER_SUCCEEDED} from "../actions/topology";
+import {
+ CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED,
+ FETCH_LATEST_DATACENTER_SUCCEEDED,
+ FINISH_NEW_ROOM_CONSTRUCTION,
+ START_NEW_ROOM_CONSTRUCTION_SUCCEEDED
+} from "../actions/topology";
export function currentDatacenterId(state = -1, action) {
switch (action.type) {
@@ -8,3 +13,15 @@ export function currentDatacenterId(state = -1, action) {
return state;
}
}
+
+export function currentRoomInConstruction(state = -1, action) {
+ switch (action.type) {
+ case START_NEW_ROOM_CONSTRUCTION_SUCCEEDED:
+ return action.roomId;
+ case CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED:
+ case FINISH_NEW_ROOM_CONSTRUCTION:
+ return -1;
+ default:
+ return state;
+ }
+}
diff --git a/src/sagas/index.js b/src/sagas/index.js
index c6177cd4..7c1d921f 100644
--- a/src/sagas/index.js
+++ b/src/sagas/index.js
@@ -1,11 +1,23 @@
import {takeEvery} from "redux-saga/effects";
import {LOG_IN} from "../actions/auth";
import {ADD_SIMULATION, DELETE_SIMULATION} from "../actions/simulations";
-import {FETCH_LATEST_DATACENTER} from "../actions/topology";
+import {
+ ADD_TILE,
+ CANCEL_NEW_ROOM_CONSTRUCTION,
+ DELETE_TILE,
+ FETCH_LATEST_DATACENTER,
+ START_NEW_ROOM_CONSTRUCTION
+} from "../actions/topology";
import {DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER} from "../actions/users";
import {onDeleteCurrentUser} from "./profile";
import {onSimulationAdd, onSimulationDelete} from "./simulations";
-import {onFetchLatestDatacenter} from "./topology";
+import {
+ onAddTile,
+ onCancelNewRoomConstruction,
+ onDeleteTile,
+ onFetchLatestDatacenter,
+ onStartNewRoomConstruction
+} from "./topology";
import {onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser} from "./users";
export default function* rootSaga() {
@@ -15,4 +27,8 @@ export default function* rootSaga() {
yield takeEvery(DELETE_SIMULATION, onSimulationDelete);
yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser);
yield takeEvery(FETCH_LATEST_DATACENTER, onFetchLatestDatacenter);
+ yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction);
+ yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction);
+ yield takeEvery(ADD_TILE, onAddTile);
+ yield takeEvery(DELETE_TILE, onDeleteTile);
}
diff --git a/src/sagas/objects.js b/src/sagas/objects.js
index 5fac6c3e..5c9f00c2 100644
--- a/src/sagas/objects.js
+++ b/src/sagas/objects.js
@@ -39,11 +39,12 @@ export const OBJECT_SELECTORS = {
function* fetchAndStoreObject(objectType, id, apiCall) {
const objectStore = yield select(OBJECT_SELECTORS[objectType]);
- if (!objectStore[id]) {
- const object = yield apiCall;
+ let object = objectStore[id];
+ if (!object) {
+ object = yield apiCall;
yield put(addToStore(objectType, object));
}
- return objectStore[id];
+ return object;
}
function* fetchAndStoreObjects(objectType, apiCall) {
diff --git a/src/sagas/topology.js b/src/sagas/topology.js
index a15e6a30..815cd842 100644
--- a/src/sagas/topology.js
+++ b/src/sagas/topology.js
@@ -1,6 +1,15 @@
-import {put} from "redux-saga/effects";
-import {addPropToStoreObject} from "../actions/objects";
-import {fetchLatestDatacenterSucceeded} from "../actions/topology";
+import {call, put, select} from "redux-saga/effects";
+import {addPropToStoreObject, addToStore} from "../actions/objects";
+import {
+ addTileSucceeded,
+ cancelNewRoomConstructionSucceeded,
+ deleteTileSucceeded,
+ fetchLatestDatacenterSucceeded,
+ startNewRoomConstructionSucceeded
+} from "../actions/topology";
+import {addRoomToDatacenter} from "../api/routes/datacenters";
+import {addTileToRoom, deleteRoom} from "../api/routes/rooms";
+import {deleteTile} from "../api/routes/tiles";
import {
fetchAndStoreCoolingItem,
fetchAndStoreDatacenter,
@@ -27,11 +36,12 @@ export function* onFetchLatestDatacenter(action) {
export function* fetchDatacenter(datacenterId) {
try {
- const datacenter = yield fetchAndStoreDatacenter(datacenterId);
- datacenter.roomIds = (yield fetchAndStoreRoomsOfDatacenter(datacenterId)).map(room => room.id);
+ yield fetchAndStoreDatacenter(datacenterId);
+ const rooms = yield fetchAndStoreRoomsOfDatacenter(datacenterId);
+ yield put(addPropToStoreObject("datacenter", datacenterId, {roomIds: rooms.map(room => room.id)}));
- for (let index in datacenter.roomIds) {
- yield fetchRoom(datacenter.roomIds[index]);
+ for (let index in rooms) {
+ yield fetchRoom(rooms[index].id);
}
} catch (error) {
console.log(error);
@@ -69,3 +79,53 @@ function* fetchTile(tile) {
console.warn("Unknown object type encountered while fetching tile objects");
}
}
+
+export function* onStartNewRoomConstruction() {
+ try {
+ const datacenterId = yield select(state => state.currentDatacenterId);
+ const room = yield call(addRoomToDatacenter, {
+ id: -1,
+ datacenterId,
+ roomType: "SERVER"
+ });
+ const roomWithEmptyTileList = Object.assign(room, {tileIds: []});
+ yield put(addToStore("room", roomWithEmptyTileList));
+ yield put(startNewRoomConstructionSucceeded(room.id));
+ } catch (error) {
+ console.log(error);
+ }
+}
+
+export function* onCancelNewRoomConstruction() {
+ try {
+ const roomId = yield select(state => state.currentRoomInConstruction);
+ yield call(deleteRoom, roomId);
+ yield put(cancelNewRoomConstructionSucceeded());
+ } catch (error) {
+ console.log(error);
+ }
+}
+
+export function* onAddTile(action) {
+ try {
+ const roomId = yield select(state => state.currentRoomInConstruction);
+ const tile = yield call(addTileToRoom, {
+ roomId,
+ positionX: action.positionX,
+ positionY: action.positionY
+ });
+ yield put(addToStore("tile", tile));
+ yield put(addTileSucceeded(tile.id));
+ } catch (error) {
+ console.log(error);
+ }
+}
+
+export function* onDeleteTile(action) {
+ try {
+ yield call(deleteTile, action.tileId);
+ yield put(deleteTileSucceeded(action.tileId));
+ } catch (error) {
+ console.log(error);
+ }
+}
diff --git a/src/util/tile-calculations.js b/src/util/tile-calculations.js
index 478c09f9..3050a404 100644
--- a/src/util/tile-calculations.js
+++ b/src/util/tile-calculations.js
@@ -1,10 +1,10 @@
-export function deriveWallLocations(room) {
+export function deriveWallLocations(tiles) {
const verticalWalls = {};
const horizontalWalls = {};
// Determine wall segments
- room.tiles.forEach(tile => {
+ tiles.forEach(tile => {
const x = tile.positionX, y = tile.positionY;
for (let dX = -1; dX <= 1; dX++) {
for (let dY = -1; dY <= 1; dY++) {
@@ -13,7 +13,7 @@ export function deriveWallLocations(room) {
}
let doInsert = true;
- room.tiles.forEach(otherTile => {
+ tiles.forEach(otherTile => {
if (otherTile.positionX === x + dX && otherTile.positionY === y + dY) {
doInsert = false;
}