diff options
| author | Georgios Andreadis <g.andreadis@student.tudelft.nl> | 2017-09-18 16:52:11 +0200 |
|---|---|---|
| committer | Georgios Andreadis <g.andreadis@student.tudelft.nl> | 2017-09-23 10:06:04 +0200 |
| commit | f8f617c97fcb2df3dbefc9527d974151e367cb60 (patch) | |
| tree | f6405aa54f73b66220f36e3a388725f71d023cfb /src | |
| parent | 9f86ae6de969baa625e3341c796c64f63b5153ce (diff) | |
Implement basic experiment mode with timeline
The timeline doesn't trigger anything yet, but the visual element is in place and connected.
Diffstat (limited to 'src')
24 files changed, 368 insertions, 46 deletions
diff --git a/src/actions/experiments.js b/src/actions/experiments.js index c7cc7e24..c64df019 100644 --- a/src/actions/experiments.js +++ b/src/actions/experiments.js @@ -24,9 +24,10 @@ export function deleteExperiment(id) { }; } -export function openExperimentSucceeded(id) { +export function openExperimentSucceeded(simulationId, experimentId) { return { type: OPEN_EXPERIMENT_SUCCEEDED, - id + simulationId, + experimentId } } diff --git a/src/actions/topology/building.js b/src/actions/topology/building.js index 4138d2b5..019a473c 100644 --- a/src/actions/topology/building.js +++ b/src/actions/topology/building.js @@ -1,5 +1,4 @@ -export const FETCH_LATEST_DATACENTER = "FETCH_LATEST_DATACENTER"; -export const FETCH_LATEST_DATACENTER_SUCCEEDED = "FETCH_LATEST_DATACENTER_SUCCEEDED"; +export const SET_CURRENT_DATACENTER = "SET_CURRENT_DATACENTER"; export const RESET_CURRENT_DATACENTER = "RESET_CURRENT_DATACENTER"; export const START_NEW_ROOM_CONSTRUCTION = "START_NEW_ROOM_CONSTRUCTION"; export const START_NEW_ROOM_CONSTRUCTION_SUCCEEDED = "START_NEW_ROOM_CONSTRUCTION_SUCCEEDED"; @@ -9,19 +8,9 @@ export const CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED = "CANCEL_NEW_ROOM_CONSTRUCT export const ADD_TILE = "ADD_TILE"; export const DELETE_TILE = "DELETE_TILE"; -export function fetchLatestDatacenter() { - return (dispatch, getState) => { - const {currentSimulationId} = getState(); - dispatch({ - type: FETCH_LATEST_DATACENTER, - currentSimulationId - }); - }; -} - -export function fetchLatestDatacenterSucceeded(datacenterId) { +export function setCurrentDatacenter(datacenterId) { return { - type: FETCH_LATEST_DATACENTER_SUCCEEDED, + type: SET_CURRENT_DATACENTER, datacenterId }; } diff --git a/src/api/routes/experiments.js b/src/api/routes/experiments.js index e2306059..84edae2f 100644 --- a/src/api/routes/experiments.js +++ b/src/api/routes/experiments.js @@ -1,5 +1,9 @@ import {deleteById, getById} from "./util"; +export function getExperiment(experimentId) { + return getById("/experiments/{experimentId}", {experimentId}); +} + export function deleteExperiment(experimentId) { return deleteById("/experiments/{experimentId}", {experimentId}); } diff --git a/src/components/map/groups/RackGroup.js b/src/components/map/groups/RackGroup.js index 648c74d7..a34a5543 100644 --- a/src/components/map/groups/RackGroup.js +++ b/src/components/map/groups/RackGroup.js @@ -9,7 +9,7 @@ import TileObject from "../elements/TileObject"; const RackGroup = ({tile, inSimulation, rackLoad}) => { let color = RACK_BACKGROUND_COLOR; - if (inSimulation) { + if (inSimulation && rackLoad) { color = convertLoadToSimulationColor(rackLoad); } diff --git a/src/components/map/groups/TileGroup.js b/src/components/map/groups/TileGroup.js index 3de712d1..c7991507 100644 --- a/src/components/map/groups/TileGroup.js +++ b/src/components/map/groups/TileGroup.js @@ -20,7 +20,7 @@ const TileGroup = ({tile, newTile, inSimulation, roomLoad, onClick}) => { let color = ROOM_DEFAULT_COLOR; if (newTile) { color = ROOM_IN_CONSTRUCTION_COLOR; - } else if (inSimulation) { + } else if (inSimulation && roomLoad) { color = convertLoadToSimulationColor(roomLoad); } diff --git a/src/components/navigation/AppNavbar.js b/src/components/navigation/AppNavbar.js index 94eaa7fa..4f669723 100644 --- a/src/components/navigation/AppNavbar.js +++ b/src/components/navigation/AppNavbar.js @@ -11,10 +11,10 @@ const AppNavbar = ({simulationId, inSimulation}) => ( <NavItem route={"/simulations/" + simulationId}> <Link className="nav-link" - title="Build" + title="Construction" to={"/simulations/" + simulationId}> <FontAwesome name="industry" className="mr-1"/> - Build + Construction </Link> </NavItem> : undefined @@ -23,10 +23,10 @@ const AppNavbar = ({simulationId, inSimulation}) => ( <NavItem route={"/simulations/" + simulationId + "/experiments"}> <Link className="nav-link" - title="Simulate" + title="Experiments" to={"/simulations/" + simulationId + "/experiments"}> <FontAwesome name="play" className="mr-1"/> - Simulate + Experiments </Link> </NavItem> : undefined diff --git a/src/components/sidebars/topology/rack/MachineComponent.js b/src/components/sidebars/topology/rack/MachineComponent.js index b6e9791d..c9211115 100644 --- a/src/components/sidebars/topology/rack/MachineComponent.js +++ b/src/components/sidebars/topology/rack/MachineComponent.js @@ -15,7 +15,7 @@ const UnitIcon = ({id, type}) => ( const MachineComponent = ({position, machine, inSimulation, machineLoad, onClick}) => { let color = "white"; - if (inSimulation) { + if (inSimulation && machineLoad) { color = convertLoadToSimulationColor(machineLoad); } const hasNoUnits = machine.cpuIds.length + machine.gpuIds.length + machine.memoryIds.length diff --git a/src/components/timeline/PlayButtonComponent.js b/src/components/timeline/PlayButtonComponent.js new file mode 100644 index 00000000..6ec70cc3 --- /dev/null +++ b/src/components/timeline/PlayButtonComponent.js @@ -0,0 +1,12 @@ +import React from "react"; + +const PlayButtonComponent = ({isPlaying, onPlay, onPause}) => ( + <div className="play-btn" onClick={() => isPlaying ? onPause() : onPlay()}> + {isPlaying ? + <span className="fa fa-pause"/> : + <span className="fa fa-play"/> + } + </div> +); + +export default PlayButtonComponent; diff --git a/src/components/timeline/Timeline.js b/src/components/timeline/Timeline.js new file mode 100644 index 00000000..a2a858eb --- /dev/null +++ b/src/components/timeline/Timeline.js @@ -0,0 +1,15 @@ +import React from "react"; +import TimelineControlsContainer from "../../containers/timeline/TimelineControlsContainer"; +import TimelineLabelsContainer from "../../containers/timeline/TimelineLabelsContainer"; +import "./Timeline.css"; + +const Timeline = ({currentTick, lastSimulatedTick}) => ( + <div className="timeline-bar"> + <div className="timeline-container"> + <TimelineLabelsContainer/> + <TimelineControlsContainer/> + </div> + </div> +); + +export default Timeline; diff --git a/src/components/timeline/Timeline.sass b/src/components/timeline/Timeline.sass new file mode 100644 index 00000000..eed3174e --- /dev/null +++ b/src/components/timeline/Timeline.sass @@ -0,0 +1,108 @@ +@import ../../style-globals/_variables.sass +@import ../../style-globals/_mixins.sass + +$container-size: 500px +$play-btn-size: 40px +$border-width: 1px +$timeline-border: $border-width solid $gray-semi-dark + +.timeline-bar + display: block + position: absolute + left: 0 + bottom: 20px + width: 100% + text-align: center + z-index: 2000 + +.timeline-container + display: inline-block + margin: 0 auto + text-align: left + + width: $container-size + +.timeline-labels + display: block + height: 25px + line-height: 25px + + div + display: inline-block + + .start-time-label + margin-left: $play-btn-size - $border-width + padding-left: 4px + + .end-time-label + padding-right: 4px + float: right + +.timeline-controls + display: flex + border: $timeline-border + overflow: hidden + + +border-radius($standard-border-radius) + + .play-btn + width: $play-btn-size + height: $play-btn-size + $border-width + line-height: $play-btn-size + $border-width + text-align: center + float: left + margin-top: -$border-width + + font-size: 16pt + background: #333 + color: #eee + + +transition(background, $transition-length) + +user-select + +clickable + + .play-btn:hover + background: #656565 + + .play-btn:active + background: #000 + + .timeline + position: relative + flex: 1 + height: $play-btn-size + line-height: $play-btn-size + float: right + + background: $blue-light + + z-index: 500 + + div + +transition(all, $transition-length) + + .time-marker + position: absolute + top: 0 + left: 0 + + width: 6px + height: 100% + + background: $blue-very-dark + + +border-radius(2px) + + z-index: 503 + + .section-marker + position: absolute + top: 0 + left: 0 + + width: 3px + height: 100% + + background: #222222 + + z-index: 504 diff --git a/src/components/timeline/TimelineControlsComponent.js b/src/components/timeline/TimelineControlsComponent.js new file mode 100644 index 00000000..3f37c3bc --- /dev/null +++ b/src/components/timeline/TimelineControlsComponent.js @@ -0,0 +1,33 @@ +import React from "react"; +import PlayButtonContainer from "../../containers/timeline/PlayButtonContainer"; + +function getXPercentage(tick, maxTick) { + if (maxTick === 0) { + return "0%"; + } else if (tick > maxTick) { + return "100%"; + } + + return (tick / maxTick) + "%"; +} + +const TimelineControlsComponent = ({currentTick, lastSimulatedTick, sectionTicks}) => ( + <div className="timeline-controls"> + <PlayButtonContainer/> + <div className="timeline"> + <div + className="time-marker" + style={{left: getXPercentage(currentTick, lastSimulatedTick)}} + /> + {sectionTicks.map(sectionTick => ( + <div + key={sectionTick} + className="section-marker" + style={{left: getXPercentage(sectionTick, lastSimulatedTick)}} + /> + ))} + </div> + </div> +); + +export default TimelineControlsComponent; diff --git a/src/components/timeline/TimelineLabelsComponent.js b/src/components/timeline/TimelineLabelsComponent.js new file mode 100644 index 00000000..1f6053a1 --- /dev/null +++ b/src/components/timeline/TimelineLabelsComponent.js @@ -0,0 +1,11 @@ +import React from "react"; +import {convertSecondsToFormattedTime} from "../../util/date-time"; + +const TimelineLabelsComponent = ({currentTick, lastSimulatedTick}) => ( + <div className="timeline-labels"> + <div className="start-time-label">{convertSecondsToFormattedTime(currentTick)}</div> + <div className="end-time-label">{convertSecondsToFormattedTime(lastSimulatedTick)}</div> + </div> +); + +export default TimelineLabelsComponent; diff --git a/src/containers/timeline/PlayButtonContainer.js b/src/containers/timeline/PlayButtonContainer.js new file mode 100644 index 00000000..6d4a9f25 --- /dev/null +++ b/src/containers/timeline/PlayButtonContainer.js @@ -0,0 +1,23 @@ +import {connect} from "react-redux"; +import {pauseSimulation, playSimulation} from "../../actions/simulation/playback"; +import PlayButtonComponent from "../../components/timeline/PlayButtonComponent"; + +const mapStateToProps = state => { + return { + isPlaying: state.isPlaying, + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onPlay: () => dispatch(playSimulation()), + onPause: () => dispatch(pauseSimulation()), + }; +}; + +const PlayButtonContainer = connect( + mapStateToProps, + mapDispatchToProps +)(PlayButtonComponent); + +export default PlayButtonContainer; diff --git a/src/containers/timeline/TimelineControlsContainer.js b/src/containers/timeline/TimelineControlsContainer.js new file mode 100644 index 00000000..e5c89060 --- /dev/null +++ b/src/containers/timeline/TimelineControlsContainer.js @@ -0,0 +1,22 @@ +import {connect} from "react-redux"; +import TimelineControlsComponent from "../../components/timeline/TimelineControlsComponent"; + +const mapStateToProps = state => { + let sectionTicks = []; + if (state.currentExperimentId !== -1) { + const sectionIds = state.objects.path[state.objects.experiment[state.currentExperimentId].pathId].sectionIds; + sectionTicks = sectionIds.map(sectionId => state.objects.section[sectionId].startTick); + } + + return { + currentTick: state.currentTick, + lastSimulatedTick: state.lastSimulatedTick, + sectionTicks, + }; +}; + +const TimelineControlsContainer = connect( + mapStateToProps +)(TimelineControlsComponent); + +export default TimelineControlsContainer; diff --git a/src/containers/timeline/TimelineLabelsContainer.js b/src/containers/timeline/TimelineLabelsContainer.js new file mode 100644 index 00000000..b6ff0774 --- /dev/null +++ b/src/containers/timeline/TimelineLabelsContainer.js @@ -0,0 +1,15 @@ +import {connect} from "react-redux"; +import TimelineLabelsComponent from "../../components/timeline/TimelineLabelsComponent"; + +const mapStateToProps = state => { + return { + currentTick: state.currentTick, + lastSimulatedTick: state.lastSimulatedTick, + }; +}; + +const TimelineLabelsContainer = connect( + mapStateToProps +)(TimelineLabelsComponent); + +export default TimelineLabelsContainer; diff --git a/src/pages/App.js b/src/pages/App.js index e70c7e48..87c139ec 100644 --- a/src/pages/App.js +++ b/src/pages/App.js @@ -2,10 +2,12 @@ import PropTypes from "prop-types"; import React from "react"; import {connect} from "react-redux"; import {ShortcutManager} from "react-shortcuts"; +import {openExperimentSucceeded} from "../actions/experiments"; import {openSimulationSucceeded} from "../actions/simulations"; -import {fetchLatestDatacenter, resetCurrentDatacenter} from "../actions/topology/building"; +import {resetCurrentDatacenter} from "../actions/topology/building"; import LoadingScreen from "../components/map/LoadingScreen"; import AppNavbar from "../components/navigation/AppNavbar"; +import Timeline from "../components/timeline/Timeline"; import MapStage from "../containers/map/MapStage"; import DeleteMachineModal from "../containers/modals/DeleteMachineModal"; import DeleteRackModal from "../containers/modals/DeleteRackModal"; @@ -20,15 +22,20 @@ const shortcutManager = new ShortcutManager(KeymapConfiguration); class AppComponent extends React.Component { static propTypes = { simulationId: PropTypes.number.isRequired, + inSimulation: PropTypes.bool, + experimentId: PropTypes.number, }; static childContextTypes = { shortcuts: PropTypes.object.isRequired }; componentDidMount() { - this.props.storeSimulationId(this.props.simulationId); this.props.resetCurrentDatacenter(); - this.props.fetchLatestDatacenter(); + if (this.props.inSimulation) { + this.props.openExperimentSucceeded(this.props.simulationId, this.props.experimentId); + return; + } + this.props.openSimulationSucceeded(this.props.simulationId); } getChildContext() { @@ -48,6 +55,10 @@ class AppComponent extends React.Component { <div className="full-height"> <MapStage/> <TopologySidebar/> + {this.props.inSimulation ? + <Timeline/> : + undefined + } </div> } <EditRoomNameModal/> @@ -68,9 +79,10 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { - storeSimulationId: id => dispatch(openSimulationSucceeded(id)), resetCurrentDatacenter: () => dispatch(resetCurrentDatacenter()), - fetchLatestDatacenter: () => dispatch(fetchLatestDatacenter()), + openSimulationSucceeded: id => dispatch(openSimulationSucceeded(id)), + openExperimentSucceeded: (simulationId, experimentId) => + dispatch(openExperimentSucceeded(simulationId, experimentId)), }; }; diff --git a/src/reducers/current-ids.js b/src/reducers/current-ids.js index c94d7861..c92ff93b 100644 --- a/src/reducers/current-ids.js +++ b/src/reducers/current-ids.js @@ -1,9 +1,10 @@ +import {OPEN_EXPERIMENT_SUCCEEDED} from "../actions/experiments"; import {OPEN_SIMULATION_SUCCEEDED} from "../actions/simulations"; -import {FETCH_LATEST_DATACENTER_SUCCEEDED, RESET_CURRENT_DATACENTER} from "../actions/topology/building"; +import {RESET_CURRENT_DATACENTER, SET_CURRENT_DATACENTER} from "../actions/topology/building"; export function currentDatacenterId(state = -1, action) { switch (action.type) { - case FETCH_LATEST_DATACENTER_SUCCEEDED: + case SET_CURRENT_DATACENTER: return action.datacenterId; case RESET_CURRENT_DATACENTER: return -1; @@ -16,6 +17,8 @@ export function currentSimulationId(state = -1, action) { switch (action.type) { case OPEN_SIMULATION_SUCCEEDED: return action.id; + case OPEN_EXPERIMENT_SUCCEEDED: + return action.simulationId; default: return state; } diff --git a/src/reducers/simulation-mode.js b/src/reducers/simulation-mode.js index da6aa94a..60084824 100644 --- a/src/reducers/simulation-mode.js +++ b/src/reducers/simulation-mode.js @@ -6,7 +6,7 @@ import {GO_TO_TICK, SET_LAST_SIMULATED_TICK} from "../actions/simulation/tick"; export function currentExperimentId(state = -1, action) { switch (action.type) { case OPEN_EXPERIMENT_SUCCEEDED: - return action.id; + return action.experimentId; default: return state; } diff --git a/src/routes/index.js b/src/routes/index.js index 8a155cb0..96ac885c 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -10,17 +10,29 @@ 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="/"/>; + <App simulationId={parseInt(match.params.simulationId, 10)}/> : + <Redirect to="/"/>; + const ExperimentsComponent = ({match}) => userIsLoggedIn() ? - <Experiments simulationId={parseInt(match.params.id, 10)}/> : <Redirect to="/"/>; + <Experiments simulationId={parseInt(match.params.simulationId, 10)}/> : + <Redirect to="/"/>; + +const SimulationComponent = ({match}) => userIsLoggedIn() ? + <App + simulationId={parseInt(match.params.simulationId, 10)} + inSimulation={true} + experimentId={parseInt(match.params.experimentId, 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="/simulations/:id/experiments" component={ExperimentsComponent}/> + <Route exact path="/simulations/:simulationId" component={AppComponent}/> + <Route exact path="/simulations/:simulationId/experiments" component={ExperimentsComponent}/> + <Route exact path="/simulations/:simulationId/experiments/:experimentId" component={SimulationComponent}/> <Route exact path="/profile" render={ProtectedComponent(<Profile/>)}/> <Route path="/*" component={NotFound}/> </Switch> diff --git a/src/sagas/experiments.js b/src/sagas/experiments.js index d1334759..cf92e97f 100644 --- a/src/sagas/experiments.js +++ b/src/sagas/experiments.js @@ -1,8 +1,20 @@ import {call, put, select} from "redux-saga/effects"; import {addPropToStoreObject, addToStore} from "../actions/objects"; -import {deleteExperiment} from "../api/routes/experiments"; +import {deleteExperiment, getExperiment} from "../api/routes/experiments"; import {addExperiment, getExperimentsOfSimulation} from "../api/routes/simulations"; import {fetchAndStoreAllSchedulers, fetchAndStoreAllTraces, fetchAndStorePathsOfSimulation} from "./objects"; +import {fetchAllDatacentersOfExperiment} from "./topology"; + +export function* onOpenExperimentSucceeded(action) { + try { + const experiment = yield call(getExperiment, action.experimentId); + yield put(addToStore("experiment", experiment)); + + yield fetchAllDatacentersOfExperiment(experiment); + } catch (error) { + console.error(error); + } +} export function* onFetchExperimentsOfSimulation() { try { diff --git a/src/sagas/index.js b/src/sagas/index.js index 30ca2f89..b10d6a1c 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -1,19 +1,28 @@ import {takeEvery} from "redux-saga/effects"; import {LOG_IN} from "../actions/auth"; -import {ADD_EXPERIMENT, DELETE_EXPERIMENT, FETCH_EXPERIMENTS_OF_SIMULATION} from "../actions/experiments"; +import { + ADD_EXPERIMENT, + DELETE_EXPERIMENT, + FETCH_EXPERIMENTS_OF_SIMULATION, + OPEN_EXPERIMENT_SUCCEEDED +} from "../actions/experiments"; import {ADD_SIMULATION, DELETE_SIMULATION, OPEN_SIMULATION_SUCCEEDED} from "../actions/simulations"; import { ADD_TILE, CANCEL_NEW_ROOM_CONSTRUCTION, DELETE_TILE, - FETCH_LATEST_DATACENTER, START_NEW_ROOM_CONSTRUCTION } from "../actions/topology/building"; import {ADD_UNIT, DELETE_MACHINE, DELETE_UNIT} from "../actions/topology/machine"; import {ADD_MACHINE, DELETE_RACK, EDIT_RACK_NAME} from "../actions/topology/rack"; import {ADD_RACK_TO_TILE, DELETE_ROOM, EDIT_ROOM_NAME} from "../actions/topology/room"; import {DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER} from "../actions/users"; -import {onAddExperiment, onDeleteExperiment, onFetchExperimentsOfSimulation} from "./experiments"; +import { + onAddExperiment, + onDeleteExperiment, + onFetchExperimentsOfSimulation, + onOpenExperimentSucceeded +} from "./experiments"; import {onDeleteCurrentUser} from "./profile"; import {onOpenSimulationSucceeded, onSimulationAdd, onSimulationDelete} from "./simulations"; import { @@ -29,7 +38,6 @@ import { onDeleteUnit, onEditRackName, onEditRoomName, - onFetchLatestDatacenter, onStartNewRoomConstruction } from "./topology"; import {onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser} from "./users"; @@ -44,8 +52,8 @@ export default function* rootSaga() { yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser); yield takeEvery(OPEN_SIMULATION_SUCCEEDED, onOpenSimulationSucceeded); + yield takeEvery(OPEN_EXPERIMENT_SUCCEEDED, onOpenExperimentSucceeded); - 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); diff --git a/src/sagas/simulations.js b/src/sagas/simulations.js index 57eb6bad..cf0358f9 100644 --- a/src/sagas/simulations.js +++ b/src/sagas/simulations.js @@ -2,11 +2,14 @@ import {call, put} from "redux-saga/effects"; import {addToStore} from "../actions/objects"; import {addSimulationSucceeded, deleteSimulationSucceeded} from "../actions/simulations"; import {addSimulation, deleteSimulation, getSimulation} from "../api/routes/simulations"; +import {fetchLatestDatacenter} from "./topology"; export function* onOpenSimulationSucceeded(action) { try { const simulation = yield call(getSimulation, action.id); yield put(addToStore("simulation", simulation)); + + yield fetchLatestDatacenter(action.id); } catch (error) { console.error(error); } diff --git a/src/sagas/topology.js b/src/sagas/topology.js index 763d4569..6a3e03af 100644 --- a/src/sagas/topology.js +++ b/src/sagas/topology.js @@ -8,7 +8,7 @@ import { } from "../actions/objects"; import { cancelNewRoomConstructionSucceeded, - fetchLatestDatacenterSucceeded, + setCurrentDatacenter, startNewRoomConstructionSucceeded } from "../actions/topology/building"; import {addRoomToDatacenter} from "../api/routes/datacenters"; @@ -38,6 +38,7 @@ import { fetchAndStoreGPU, fetchAndStoreMachinesOfTile, fetchAndStoreMemory, + fetchAndStorePath, fetchAndStorePathsOfSimulation, fetchAndStorePSU, fetchAndStoreRackOnTile, @@ -47,15 +48,31 @@ import { fetchAndStoreTilesOfRoom } from "./objects"; -export function* onFetchLatestDatacenter(action) { +export function* fetchLatestDatacenter(simulationId) { try { - const paths = yield fetchAndStorePathsOfSimulation(action.currentSimulationId); + const paths = yield fetchAndStorePathsOfSimulation(simulationId); const latestPath = paths[paths.length - 1]; const sections = yield fetchAndStoreSectionsOfPath(latestPath.id); const latestSection = sections[sections.length - 1]; yield fetchAllUnitSpecifications(); yield fetchDatacenter(latestSection.datacenterId); - yield put(fetchLatestDatacenterSucceeded(latestSection.datacenterId)); + yield put(setCurrentDatacenter(latestSection.datacenterId)); + } catch (error) { + console.error(error); + } +} + +export function* fetchAllDatacentersOfExperiment(experiment) { + try { + const path = yield fetchAndStorePath(experiment.pathId); + const sections = yield fetchAndStoreSectionsOfPath(path.id); + path.sectionIds = sections.map(section => section.id); + yield fetchAllUnitSpecifications(); + + for (let i in sections) { + yield fetchDatacenter(sections[i].datacenterId); + } + yield put(setCurrentDatacenter(sections[0].datacenterId)); } catch (error) { console.error(error); } diff --git a/src/util/date-time.js b/src/util/date-time.js index ef3524db..3ec6b671 100644 --- a/src/util/date-time.js +++ b/src/util/date-time.js @@ -88,7 +88,7 @@ export function getCurrentDateTime() { * Pads the given integer to have at least two digits. * * @param integer An integer to be padded. - * @returns {string} A string containing the padded integer + * @returns {string} A string containing the padded integer. */ export function addPaddingToTwo(integer) { if (integer < 10) { @@ -97,3 +97,25 @@ export function addPaddingToTwo(integer) { return integer.toString(); } } + +/** + * Formats the given number of seconds/ticks to a formatted time representation. + * + * @param seconds The number of seconds. + * @returns {string} A string representation of that amount of second, in the from of HH:MM:SS. + */ +export function convertSecondsToFormattedTime(seconds) { + if (seconds <= 0) { + return "00:00:00"; + } + + let hour = Math.floor(seconds / 3600); + let minute = Math.floor(seconds / 60) % 60; + let second = seconds % 60; + + hour = isNaN(hour) ? 0 : hour; + minute = isNaN(minute) ? 0 : minute; + second = isNaN(second) ? 0 : second; + + return addPaddingToTwo(hour) + ":" + addPaddingToTwo(minute) + ":" + addPaddingToTwo(second); +} |
