diff options
Diffstat (limited to 'opendc-web/opendc-web-ui/src')
35 files changed, 467 insertions, 416 deletions
diff --git a/opendc-web/opendc-web-ui/src/auth/hook.js b/opendc-web/opendc-web-ui/src/auth/hook.js index ddaf53ed..8dd85fdd 100644 --- a/opendc-web/opendc-web-ui/src/auth/hook.js +++ b/opendc-web/opendc-web-ui/src/auth/hook.js @@ -23,8 +23,9 @@ import { useEffect, useState } from 'react' import { userIsLoggedIn } from './index' import { useRouter } from 'next/router' +import { useSelector } from 'react-redux' -export function useAuth() { +export function useIsLoggedIn() { const [isLoggedIn, setLoggedIn] = useState(false) useEffect(() => { @@ -42,3 +43,7 @@ export function useRequireAuth() { } }) } + +export function useUser() { + return useSelector((state) => state.auth) +} diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js index dd32927f..7c97f3e4 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { HotKeys } from 'react-hotkeys' import { Stage } from 'react-konva' import MapLayer from '../../../containers/app/map/layers/MapLayer' diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js index 2b5c569f..7d304b6b 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js @@ -1,48 +1,36 @@ import PropTypes from 'prop-types' -import React from 'react' +import React, { useEffect, useState } from 'react' import { Image } from 'react-konva' -class ImageComponent extends React.Component { - static imageCaches = {} - static propTypes = { - src: PropTypes.string.isRequired, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, - } +const imageCaches = {} - state = { - image: null, - } +function ImageComponent({ src, x, y, width, height, opacity }) { + const [image, setImage] = useState(null) - componentDidMount() { - if (ImageComponent.imageCaches[this.props.src]) { - this.setState({ image: ImageComponent.imageCaches[this.props.src] }) + useEffect(() => { + if (imageCaches[src]) { + setImage(imageCaches[src]) return } const image = new window.Image() - image.src = this.props.src + image.src = src image.onload = () => { - this.setState({ image }) - ImageComponent.imageCaches[this.props.src] = image + setImage(image) + imageCaches[src] = image } - } + }, [src]) - render() { - return ( - <Image - image={this.state.image} - x={this.props.x} - y={this.props.y} - width={this.props.width} - height={this.props.height} - opacity={this.props.opacity} - /> - ) - } + return <Image image={image} x={x} y={y} width={width} height={height} opacity={opacity} /> +} + +ImageComponent.propTypes = { + src: PropTypes.string.isRequired, + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + opacity: PropTypes.number.isRequired, } export default ImageComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js index 43bf918e..b2cc1273 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js @@ -1,6 +1,6 @@ import React from 'react' import { Rect } from 'react-konva' -import Shapes from '../../../../shapes/index' +import { Tile } from '../../../../shapes' import { TILE_SIZE_IN_PIXELS } from '../MapConstants' const RoomTile = ({ tile, color }) => ( @@ -14,7 +14,7 @@ const RoomTile = ({ tile, color }) => ( ) RoomTile.propTypes = { - tile: Shapes.Tile, + tile: Tile, } export default RoomTile diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js index 8aa2aebf..ad6412c3 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js @@ -1,6 +1,6 @@ import React from 'react' import { Line } from 'react-konva' -import Shapes from '../../../../shapes/index' +import { WallSegment as WallSegmentShape } from '../../../../shapes' import { WALL_COLOR } from '../../../../util/colors' import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from '../MapConstants' @@ -26,7 +26,7 @@ const WallSegment = ({ wallSegment }) => { } WallSegment.propTypes = { - wallSegment: Shapes.WallSegment, + wallSegment: WallSegmentShape, } export default WallSegment diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js index eb6dc24a..40e28f01 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js @@ -2,7 +2,7 @@ import React from 'react' import { Group } from 'react-konva' import RackEnergyFillContainer from '../../../../containers/app/map/RackEnergyFillContainer' import RackSpaceFillContainer from '../../../../containers/app/map/RackSpaceFillContainer' -import Shapes from '../../../../shapes/index' +import { Tile } from '../../../../shapes' import { RACK_BACKGROUND_COLOR } from '../../../../util/colors' import TileObject from '../elements/TileObject' @@ -19,7 +19,7 @@ const RackGroup = ({ tile }) => { } RackGroup.propTypes = { - tile: Shapes.Tile, + tile: Tile, } export default RackGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js index 1fd54687..d7c207ca 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js @@ -3,7 +3,7 @@ import { Group } from 'react-konva' import GrayContainer from '../../../../containers/app/map/GrayContainer' import TileContainer from '../../../../containers/app/map/TileContainer' import WallContainer from '../../../../containers/app/map/WallContainer' -import Shapes from '../../../../shapes/index' +import { Room } from '../../../../shapes' const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick }) => { if (currentRoomInConstruction === room._id) { @@ -42,7 +42,7 @@ const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick } RoomGroup.propTypes = { - room: Shapes.Room, + room: Room, } export default RoomGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js index 1e106823..ff6ec7ec 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types' import React from 'react' import { Group } from 'react-konva' import RackContainer from '../../../../containers/app/map/RackContainer' -import Shapes from '../../../../shapes/index' +import { Tile } from '../../../../shapes' import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util/colors' import RoomTile from '../elements/RoomTile' @@ -28,7 +28,7 @@ const TileGroup = ({ tile, newTile, roomLoad, onClick }) => { } TileGroup.propTypes = { - tile: Shapes.Tile, + tile: Tile, newTile: PropTypes.bool, } diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js index 6096fc8b..57107768 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js @@ -2,7 +2,7 @@ import React from 'react' import { Group } from 'react-konva' import GrayContainer from '../../../../containers/app/map/GrayContainer' import RoomContainer from '../../../../containers/app/map/RoomContainer' -import Shapes from '../../../../shapes/index' +import { InteractionLevel, Topology } from '../../../../shapes' const TopologyGroup = ({ topology, interactionLevel }) => { if (!topology) { @@ -37,8 +37,8 @@ const TopologyGroup = ({ topology, interactionLevel }) => { } TopologyGroup.propTypes = { - topology: Shapes.Topology, - interactionLevel: Shapes.InteractionLevel, + topology: Topology, + interactionLevel: InteractionLevel, } export default TopologyGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js index 7b0f5ca0..855d444f 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import React from 'react' import { Group } from 'react-konva' -import Shapes from '../../../../shapes/index' +import { Tile } from '../../../../shapes/index' import { deriveWallLocations } from '../../../../util/tile-calculations' import WallSegment from '../elements/WallSegment' @@ -16,7 +16,7 @@ const WallGroup = ({ tiles }) => { } WallGroup.propTypes = { - tiles: PropTypes.arrayOf(Shapes.Tile).isRequired, + tiles: PropTypes.arrayOf(Tile).isRequired, } export default WallGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js index bead87de..08d31dac 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js @@ -1,75 +1,63 @@ import PropTypes from 'prop-types' -import React from 'react' +import React, { useEffect, useState } from 'react' import { Layer } from 'react-konva' import HoverTile from '../elements/HoverTile' import { TILE_SIZE_IN_PIXELS } from '../MapConstants' -class HoverLayerComponent extends React.Component { - static propTypes = { - mouseX: PropTypes.number.isRequired, - mouseY: PropTypes.number.isRequired, - mapPosition: PropTypes.object.isRequired, - mapScale: PropTypes.number.isRequired, - isEnabled: PropTypes.func.isRequired, - onClick: PropTypes.func.isRequired, - } - - state = { - positionX: -1, - positionY: -1, - validity: false, - } +function HoverLayerComponent({ mouseX, mouseY, mapPosition, mapScale, isEnabled, isValid, onClick, children }) { + const [pos, setPos] = useState([-1, -1]) + const [x, y] = pos + const [valid, setValid] = useState(false) - componentDidUpdate() { - if (!this.props.isEnabled()) { + useEffect(() => { + if (!isEnabled()) { return } - const positionX = Math.floor( - (this.props.mouseX - this.props.mapPosition.x) / (this.props.mapScale * TILE_SIZE_IN_PIXELS) - ) - const positionY = Math.floor( - (this.props.mouseY - this.props.mapPosition.y) / (this.props.mapScale * TILE_SIZE_IN_PIXELS) - ) + const positionX = Math.floor((mouseX - mapPosition.x) / (mapScale * TILE_SIZE_IN_PIXELS)) + const positionY = Math.floor((mouseY - mapPosition.y) / (mapScale * TILE_SIZE_IN_PIXELS)) - if (positionX !== this.state.positionX || positionY !== this.state.positionY) { - this.setState({ - positionX, - positionY, - validity: this.props.isValid(positionX, positionY), - }) + if (positionX !== x || positionY !== y) { + setPos([positionX, positionY]) + setValid(isValid(positionX, positionY)) } - } + }, [mouseX, mouseY, mapPosition, mapScale]) - render() { - if (!this.props.isEnabled()) { - return <Layer /> - } + if (!isEnabled()) { + return <Layer /> + } - const pixelX = this.props.mapScale * this.state.positionX * TILE_SIZE_IN_PIXELS + this.props.mapPosition.x - const pixelY = this.props.mapScale * this.state.positionY * TILE_SIZE_IN_PIXELS + this.props.mapPosition.y + const pixelX = mapScale * x * TILE_SIZE_IN_PIXELS + mapPosition.x + const pixelY = mapScale * y * TILE_SIZE_IN_PIXELS + mapPosition.y + + return ( + <Layer opacity={0.6}> + <HoverTile + pixelX={pixelX} + pixelY={pixelY} + scale={mapScale} + isValid={valid} + onClick={() => (valid ? onClick(x, y) : undefined)} + /> + {children + ? React.cloneElement(children, { + pixelX, + pixelY, + scale: mapScale, + }) + : undefined} + </Layer> + ) +} - return ( - <Layer opacity={0.6}> - <HoverTile - pixelX={pixelX} - pixelY={pixelY} - scale={this.props.mapScale} - isValid={this.state.validity} - onClick={() => - this.state.validity ? this.props.onClick(this.state.positionX, this.state.positionY) : undefined - } - /> - {this.props.children - ? React.cloneElement(this.props.children, { - pixelX, - pixelY, - scale: this.props.mapScale, - }) - : undefined} - </Layer> - ) - } +HoverLayerComponent.propTypes = { + mouseX: PropTypes.number.isRequired, + mouseY: PropTypes.number.isRequired, + mapPosition: PropTypes.object.isRequired, + mapScale: PropTypes.number.isRequired, + isEnabled: PropTypes.func.isRequired, + isValid: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, } export default HoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js index 759acd57..983a5c1d 100644 --- a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts' import { AVAILABLE_METRICS, METRIC_NAMES_SHORT, METRIC_UNITS } from '../../../util/available-metrics' import { mean, std } from 'mathjs' -import Shapes from '../../../shapes/index' +import { Portfolio, Scenario } from '../../../shapes' import approx from 'approximate-number' const PortfolioResultsComponent = ({ portfolio, scenarios }) => { @@ -86,8 +86,8 @@ const PortfolioResultsComponent = ({ portfolio, scenarios }) => { } PortfolioResultsComponent.propTypes = { - portfolio: Shapes.Portfolio, - scenarios: PropTypes.arrayOf(Shapes.Scenario), + portfolio: Portfolio, + scenarios: PropTypes.arrayOf(Scenario), } export default PortfolioResultsComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js index b714a7d2..d002b473 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js @@ -1,67 +1,65 @@ import PropTypes from 'prop-types' import React from 'react' -import Shapes from '../../../../shapes' +import { Portfolio } from '../../../../shapes' import Link from 'next/link' import FontAwesome from 'react-fontawesome' import ScenarioListContainer from '../../../../containers/app/sidebars/project/ScenarioListContainer' -class PortfolioListComponent extends React.Component { - static propTypes = { - portfolios: PropTypes.arrayOf(Shapes.Portfolio), - currentProjectId: PropTypes.string.isRequired, - currentPortfolioId: PropTypes.string, - onNewPortfolio: PropTypes.func.isRequired, - onChoosePortfolio: PropTypes.func.isRequired, - onDeletePortfolio: PropTypes.func.isRequired, - } +function PortfolioListComponent({ + portfolios, + currentProjectId, + currentPortfolioId, + onNewPortfolio, + onChoosePortfolio, + onDeletePortfolio, +}) { + return ( + <div className="pb-3"> + <h2> + Portfolios + <button className="btn btn-outline-primary float-right" onClick={(e) => onNewPortfolio(e)}> + <FontAwesome name="plus" /> + </button> + </h2> - onDelete(id) { - this.props.onDeletePortfolio(id) - } - - render() { - return ( - <div className="pb-3"> - <h2> - Portfolios - <button - className="btn btn-outline-primary float-right" - onClick={this.props.onNewPortfolio.bind(this)} - > - <FontAwesome name="plus" /> - </button> - </h2> - - {this.props.portfolios.map((portfolio, idx) => ( - <div key={portfolio._id}> - <div className="row mb-1"> - <div - className={ - 'col-7 align-self-center ' + - (portfolio._id === this.props.currentPortfolioId ? 'font-weight-bold' : '') - } - > - {portfolio.name} - </div> - <div className="col-5 text-right"> - <Link href={`/projects/${this.props.currentProjectId}/portfolios/${portfolio._id}`}> - <a - className="btn btn-outline-primary mr-1 fa fa-play" - onClick={() => this.props.onChoosePortfolio(portfolio._id)} - /> - </Link> - <span - className="btn btn-outline-danger fa fa-trash" - onClick={() => this.onDelete(portfolio._id)} + {portfolios.map((portfolio, idx) => ( + <div key={portfolio._id}> + <div className="row mb-1"> + <div + className={ + 'col-7 align-self-center ' + + (portfolio._id === currentPortfolioId ? 'font-weight-bold' : '') + } + > + {portfolio.name} + </div> + <div className="col-5 text-right"> + <Link href={`/projects/${currentProjectId}/portfolios/${portfolio._id}`}> + <a + className="btn btn-outline-primary mr-1 fa fa-play" + onClick={() => onChoosePortfolio(portfolio._id)} /> - </div> + </Link> + <span + className="btn btn-outline-danger fa fa-trash" + onClick={() => onDeletePortfolio(portfolio._id)} + /> </div> - <ScenarioListContainer portfolioId={portfolio._id} /> </div> - ))} - </div> - ) - } + <ScenarioListContainer portfolioId={portfolio._id} /> + </div> + ))} + </div> + ) +} + +PortfolioListComponent.propTypes = { + portfolios: PropTypes.arrayOf(Portfolio), + currentProjectId: PropTypes.string.isRequired, + currentPortfolioId: PropTypes.string, + onNewPortfolio: PropTypes.func.isRequired, + onChoosePortfolio: PropTypes.func.isRequired, + onDeletePortfolio: PropTypes.func.isRequired, } export default PortfolioListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js index 4efa99ef..26543d12 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js @@ -1,65 +1,64 @@ import PropTypes from 'prop-types' import React from 'react' -import Shapes from '../../../../shapes' +import { Scenario } from '../../../../shapes' import Link from 'next/link' import FontAwesome from 'react-fontawesome' -class ScenarioListComponent extends React.Component { - static propTypes = { - scenarios: PropTypes.arrayOf(Shapes.Scenario), - portfolioId: PropTypes.string, - currentProjectId: PropTypes.string.isRequired, - currentScenarioId: PropTypes.string, - onNewScenario: PropTypes.func.isRequired, - onChooseScenario: PropTypes.func.isRequired, - onDeleteScenario: PropTypes.func.isRequired, - } - - onDelete(id) { - this.props.onDeleteScenario(id) - } - - render() { - return ( - <> - {this.props.scenarios.map((scenario, idx) => ( - <div key={scenario._id} className="row mb-1"> - <div - className={ - 'col-7 pl-5 align-self-center ' + - (scenario._id === this.props.currentScenarioId ? 'font-weight-bold' : '') - } - > - {scenario.name} - </div> - <div className="col-5 text-right"> - <Link - href={`/projects/${this.props.currentProjectId}/portfolios/${scenario.portfolioId}/scenarios/${scenario._id}`} - > - <a - className="btn btn-outline-primary mr-1 fa fa-play disabled" - onClick={() => this.props.onChooseScenario(scenario.portfolioId, scenario._id)} - /> - </Link> - <span - className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')} - onClick={() => (idx !== 0 ? this.onDelete(scenario._id) : undefined)} - /> - </div> - </div> - ))} - <div className="pl-4 mb-2"> +function ScenarioListComponent({ + scenarios, + portfolioId, + currentProjectId, + currentScenarioId, + onNewScenario, + onChooseScenario, + onDeleteScenario, +}) { + return ( + <> + {scenarios.map((scenario, idx) => ( + <div key={scenario._id} className="row mb-1"> <div - className="btn btn-outline-primary" - onClick={() => this.props.onNewScenario(this.props.portfolioId)} + className={ + 'col-7 pl-5 align-self-center ' + + (scenario._id === currentScenarioId ? 'font-weight-bold' : '') + } > - <FontAwesome name="plus" className="mr-1" /> - New scenario + {scenario.name} </div> + <div className="col-5 text-right"> + <Link + href={`/projects/${currentProjectId}/portfolios/${scenario.portfolioId}/scenarios/${scenario._id}`} + > + <a + className="btn btn-outline-primary mr-1 fa fa-play disabled" + onClick={() => onChooseScenario(scenario.portfolioId, scenario._id)} + /> + </Link> + <span + className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')} + onClick={() => (idx !== 0 ? onDeleteScenario(scenario._id) : undefined)} + /> + </div> + </div> + ))} + <div className="pl-4 mb-2"> + <div className="btn btn-outline-primary" onClick={() => onNewScenario(this.props.portfolioId)}> + <FontAwesome name="plus" className="mr-1" /> + New scenario </div> - </> - ) - } + </div> + </> + ) +} + +ScenarioListComponent.propTypes = { + scenarios: PropTypes.arrayOf(Scenario), + portfolioId: PropTypes.string, + currentProjectId: PropTypes.string.isRequired, + currentScenarioId: PropTypes.string, + onNewScenario: PropTypes.func.isRequired, + onChooseScenario: PropTypes.func.isRequired, + onDeleteScenario: PropTypes.func.isRequired, } export default ScenarioListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js index 2f42f7e4..a7d78c4a 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js @@ -1,60 +1,49 @@ import PropTypes from 'prop-types' import React from 'react' -import Shapes from '../../../../shapes' +import { Topology } from '../../../../shapes' import FontAwesome from 'react-fontawesome' -class TopologyListComponent extends React.Component { - static propTypes = { - topologies: PropTypes.arrayOf(Shapes.Topology), - currentTopologyId: PropTypes.string, - onChooseTopology: PropTypes.func.isRequired, - onNewTopology: PropTypes.func.isRequired, - onDeleteTopology: PropTypes.func.isRequired, - } +function TopologyListComponent({ topologies, currentTopologyId, onChooseTopology, onNewTopology, onDeleteTopology }) { + return ( + <div className="pb-3"> + <h2> + Topologies + <button className="btn btn-outline-primary float-right" onClick={onNewTopology}> + <FontAwesome name="plus" /> + </button> + </h2> - onChoose(id) { - this.props.onChooseTopology(id) - } - - onDelete(id) { - this.props.onDeleteTopology(id) - } - - render() { - return ( - <div className="pb-3"> - <h2> - Topologies - <button className="btn btn-outline-primary float-right" onClick={this.props.onNewTopology}> - <FontAwesome name="plus" /> - </button> - </h2> - - {this.props.topologies.map((topology, idx) => ( - <div key={topology._id} className="row mb-1"> - <div - className={ - 'col-7 align-self-center ' + - (topology._id === this.props.currentTopologyId ? 'font-weight-bold' : '') - } - > - {topology.name} - </div> - <div className="col-5 text-right"> - <span - className="btn btn-outline-primary mr-1 fa fa-play" - onClick={() => this.onChoose(topology._id)} - /> - <span - className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')} - onClick={() => (idx !== 0 ? this.onDelete(topology._id) : undefined)} - /> - </div> + {topologies.map((topology, idx) => ( + <div key={topology._id} className="row mb-1"> + <div + className={ + 'col-7 align-self-center ' + (topology._id === currentTopologyId ? 'font-weight-bold' : '') + } + > + {topology.name} </div> - ))} - </div> - ) - } + <div className="col-5 text-right"> + <span + className="btn btn-outline-primary mr-1 fa fa-play" + onClick={() => onChooseTopology(topology._id)} + /> + <span + className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')} + onClick={() => (idx !== 0 ? onDeleteTopology(topology._id) : undefined)} + /> + </div> + </div> + ))} + </div> + ) +} + +TopologyListComponent.propTypes = { + topologies: PropTypes.arrayOf(Topology), + currentTopologyId: PropTypes.string, + onChooseTopology: PropTypes.func.isRequired, + onNewTopology: PropTypes.func.isRequired, + onDeleteTopology: PropTypes.func.isRequired, } export default TopologyListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js index 4e9dbc7e..f80fccc8 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js @@ -1,35 +1,35 @@ import PropTypes from 'prop-types' -import React from 'react' +import React, { useRef } from 'react' -class UnitAddComponent extends React.Component { - static propTypes = { - units: PropTypes.array.isRequired, - onAdd: PropTypes.func.isRequired, - } +function UnitAddComponent({ units, onAdd }) { + const unitSelect = useRef(null) - render() { - return ( - <div className="form-inline"> - <div className="form-group w-100"> - <select className="form-control w-70 mr-1" ref={(unitSelect) => (this.unitSelect = unitSelect)}> - {this.props.units.map((unit) => ( - <option value={unit._id} key={unit._id}> - {unit.name} - </option> - ))} - </select> - <button - type="submit" - className="btn btn-outline-primary" - onClick={() => this.props.onAdd(this.unitSelect.value)} - > - <span className="fa fa-plus mr-2" /> - Add - </button> - </div> + return ( + <div className="form-inline"> + <div className="form-group w-100"> + <select className="form-control w-70 mr-1" ref={unitSelect}> + {units.map((unit) => ( + <option value={unit._id} key={unit._id}> + {unit.name} + </option> + ))} + </select> + <button + type="submit" + className="btn btn-outline-primary" + onClick={() => onAdd(unitSelect.current.value)} + > + <span className="fa fa-plus mr-2" /> + Add + </button> </div> - ) - } + </div> + ) +} + +UnitAddComponent.propTypes = { + units: PropTypes.array.isRequired, + onAdd: PropTypes.func.isRequired, } export default UnitAddComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js index caa3dc04..4db0e7fe 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js @@ -1,5 +1,5 @@ import React from 'react' -import Shapes from '../../../../../shapes' +import { Machine } from '../../../../../shapes' const UnitIcon = ({ id, type }) => ( <div> @@ -37,7 +37,7 @@ const MachineComponent = ({ position, machine, onClick }) => { } MachineComponent.propTypes = { - machine: Shapes.Machine, + machine: Machine, } export default MachineComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js index d0918c7e..6758fdc0 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js +++ b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js @@ -1,54 +1,41 @@ import PropTypes from 'prop-types' -import React from 'react' +import React, { useRef } from 'react' import Modal from './Modal' -class TextInputModal extends React.Component { - static propTypes = { - title: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - show: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, - initialValue: PropTypes.string, +function TextInputModal({ title, label, show, callback, initialValue }) { + const textInput = useRef(null) + const onSubmit = () => { + callback(textInput.current.value) + textInput.current.value = '' } - - componentDidUpdate() { - if (this.props.initialValue && this.textInput) { - this.textInput.value = this.props.initialValue - } - } - - onSubmit() { - this.props.callback(this.textInput.value) - this.textInput.value = '' - } - - onCancel() { - this.props.callback(undefined) - this.textInput.value = '' + const onCancel = () => { + callback(undefined) + textInput.current.value = '' } - render() { - return ( - <Modal - title={this.props.title} - show={this.props.show} - onSubmit={this.onSubmit.bind(this)} - onCancel={this.onCancel.bind(this)} + return ( + <Modal title={title} show={show} onSubmit={onSubmit} onCancel={onCancel}> + <form + onSubmit={(e) => { + e.preventDefault() + onSubmit() + }} > - <form - onSubmit={(e) => { - e.preventDefault() - this.onSubmit() - }} - > - <div className="form-group"> - <label className="form-control-label">{this.props.label}</label> - <input type="text" className="form-control" ref={(textInput) => (this.textInput = textInput)} /> - </div> - </form> - </Modal> - ) - } + <div className="form-group"> + <label className="form-control-label">{label}</label> + <input type="text" className="form-control" ref={textInput} defaultValue={initialValue} /> + </div> + </form> + </Modal> + ) +} + +TextInputModal.propTypes = { + title: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, + initialValue: PropTypes.string, } export default TextInputModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js index 01a5719c..782812ac 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import React, { useRef } from 'react' import { Form, FormGroup, Input, Label } from 'reactstrap' -import Shapes from '../../../shapes' +import { Scheduler, Topology, Trace } from '../../../shapes' import Modal from '../Modal' const NewScenarioModalComponent = ({ @@ -135,9 +135,9 @@ NewScenarioModalComponent.propTypes = { show: PropTypes.bool.isRequired, currentPortfolioId: PropTypes.string.isRequired, currentPortfolioScenarioIds: PropTypes.arrayOf(PropTypes.string), - traces: PropTypes.arrayOf(Shapes.Trace), - topologies: PropTypes.arrayOf(Shapes.Topology), - schedulers: PropTypes.arrayOf(Shapes.Scheduler), + traces: PropTypes.arrayOf(Trace), + topologies: PropTypes.arrayOf(Topology), + schedulers: PropTypes.arrayOf(Scheduler), callback: PropTypes.func.isRequired, } diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js index 9fee8831..f06fe797 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import { Form, FormGroup, Input, Label } from 'reactstrap' import React, { useRef } from 'react' -import Shapes from '../../../shapes' +import { Topology } from '../../../shapes' import Modal from '../Modal' const NewTopologyModalComponent = ({ show, onCreateTopology, onDuplicateTopology, onCancel, topologies }) => { @@ -62,7 +62,7 @@ const NewTopologyModalComponent = ({ show, onCreateTopology, onDuplicateTopology NewTopologyModalComponent.propTypes = { show: PropTypes.bool.isRequired, - topologies: PropTypes.arrayOf(Shapes.Topology), + topologies: PropTypes.arrayOf(Topology), onCreateTopology: PropTypes.func.isRequired, onDuplicateTopology: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js index 90f55665..025d33a1 100644 --- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js +++ b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js @@ -11,12 +11,11 @@ import { Nav, Container, } from 'reactstrap' -import { userIsLoggedIn } from '../../auth/index' import Login from '../../containers/auth/Login' import Logout from '../../containers/auth/Logout' import ProfileName from '../../containers/auth/ProfileName' import { login, navbar, opendcBrand } from './Navbar.module.scss' -import { useAuth } from '../../auth/hook' +import { useIsLoggedIn } from '../../auth/hook' export const NAVBAR_HEIGHT = 60 @@ -45,7 +44,7 @@ export const NavItem = ({ route, children }) => { export const LoggedInSection = () => { const router = useRouter() - const isLoggedIn = useAuth() + const isLoggedIn = useIsLoggedIn() return ( <Nav navbar className="auth-links"> {isLoggedIn ? ( diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js index 8eb4f93b..15147e08 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types' import React from 'react' -import Shapes from '../../shapes/index' +import { Authorization } from '../../shapes' import ProjectAuthRow from './ProjectAuthRow' const ProjectAuthList = ({ authorizations }) => { @@ -33,7 +33,7 @@ const ProjectAuthList = ({ authorizations }) => { } ProjectAuthList.propTypes = { - authorizations: PropTypes.arrayOf(Shapes.Authorization).isRequired, + authorizations: PropTypes.arrayOf(Authorization).isRequired, } export default ProjectAuthList diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js index 3f904061..1c1d5cd8 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js @@ -1,7 +1,7 @@ import classNames from 'classnames' import React from 'react' import ProjectActions from '../../containers/projects/ProjectActions' -import Shapes from '../../shapes/index' +import { Authorization } from '../../shapes/index' import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' import { parseAndFormatDateTime } from '../../util/date-time' @@ -18,7 +18,7 @@ const ProjectAuthRow = ({ projectAuth }) => ( ) ProjectAuthRow.propTypes = { - projectAuth: Shapes.Authorization.isRequired, + projectAuth: Authorization.isRequired, } export default ProjectAuthRow diff --git a/opendc-web/opendc-web-ui/src/containers/app/App.js b/opendc-web/opendc-web-ui/src/containers/app/App.js index df159cc2..bb9c5d56 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/App.js +++ b/opendc-web/opendc-web-ui/src/containers/app/App.js @@ -23,7 +23,6 @@ import PropTypes from 'prop-types' import React, { useEffect } from 'react' import Head from 'next/head' -import { useRouter } from 'next/router' import { HotKeys } from 'react-hotkeys' import { useDispatch, useSelector } from 'react-redux' import { openPortfolioSucceeded } from '../../actions/portfolios' @@ -47,17 +46,13 @@ import NewScenarioModal from '../../containers/modals/NewScenarioModal' import PortfolioResultsContainer from '../../containers/app/results/PortfolioResultsContainer' import KeymapConfiguration from '../../shortcuts/keymap' import { useRequireAuth } from '../../auth/hook' +import { useProject } from '../../store/hooks/project' const App = ({ projectId, portfolioId, scenarioId }) => { useRequireAuth() - const projectName = useSelector( - (state) => - state.currentProjectId !== '-1' && - state.objects.project[state.currentProjectId] && - state.objects.project[state.currentProjectId].name - ) - const topologyIsLoading = useSelector((state) => state.currentTopologyIdd === '-1') + const projectName = useProject()?.name + const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1') const dispatch = useDispatch() useEffect(() => { diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js b/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js index 9394238d..db9a2dd1 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js +++ b/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js @@ -1,11 +1,12 @@ import React from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useDispatch } from 'react-redux' import { setMapDimensions, setMapPositionWithBoundsCheck, zoomInOnPosition } from '../../../actions/map' import MapStageComponent from '../../../components/app/map/MapStageComponent' +import { useMapDimensions, useMapPosition } from '../../../store/hooks/map' const MapStage = () => { - const position = useSelector((state) => state.map.position) - const dimensions = useSelector((state) => state.map.dimensions) + const position = useMapPosition() + const dimensions = useMapDimensions() const dispatch = useDispatch() const zoomInOnPositionA = (zoomIn, x, y) => dispatch(zoomInOnPosition(zoomIn, x, y)) const setMapPositionWithBoundsCheckA = (x, y) => dispatch(setMapPositionWithBoundsCheck(x, y)) diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js index 612ca41c..30379490 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js @@ -1,11 +1,10 @@ import React from 'react' import { useSelector } from 'react-redux' import TopologyGroup from '../../../components/app/map/groups/TopologyGroup' +import { useTopology } from '../../../store/hooks/topology' const TopologyContainer = () => { - const topology = useSelector( - (state) => state.currentTopologyId !== '-1' && state.objects.topology[state.currentTopologyId] - ) + const topology = useTopology() const interactionLevel = useSelector((state) => state.interactionLevel) return <TopologyGroup topology={topology} interactionLevel={interactionLevel} /> diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js index e9d58b9f..03834188 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js @@ -1,9 +1,9 @@ import React from 'react' -import { useSelector } from 'react-redux' import ScaleIndicatorComponent from '../../../../components/app/map/controls/ScaleIndicatorComponent' +import { useMapScale } from '../../../../store/hooks/map' const ScaleIndicatorContainer = (props) => { - const scale = useSelector((state) => state.map.scale) + const scale = useMapScale() return <ScaleIndicatorComponent {...props} scale={scale} /> } diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js index a18dfd5b..797bdf7f 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js @@ -1,11 +1,12 @@ import React from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useDispatch } from 'react-redux' import { zoomInOnCenter } from '../../../../actions/map' import ZoomControlComponent from '../../../../components/app/map/controls/ZoomControlComponent' +import { useMapScale } from '../../../../store/hooks/map' const ZoomControlContainer = () => { const dispatch = useDispatch() - const scale = useSelector((state) => state.map.scale) + const scale = useMapScale() return <ZoomControlComponent mapScale={scale} zoomInOnCenter={(zoomIn) => dispatch(zoomInOnCenter(zoomIn))} /> } diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js index 5f701b4b..ccd0ea8f 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js +++ b/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js @@ -1,9 +1,10 @@ import React from 'react' -import { useSelector } from 'react-redux' import MapLayerComponent from '../../../../components/app/map/layers/MapLayerComponent' +import { useMapPosition, useMapScale } from '../../../../store/hooks/map' const MapLayer = (props) => { - const { position, scale } = useSelector((state) => state.map) + const position = useMapPosition() + const scale = useMapScale() return <MapLayerComponent {...props} mapPosition={position} mapScale={scale} /> } diff --git a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js index 291c0068..cbbe42b4 100644 --- a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js +++ b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js @@ -1,9 +1,13 @@ import React from 'react' -import { useSelector } from 'react-redux' +import { useUser } from '../../auth/hook' function ProfileName() { - const name = useSelector((state) => `${state.auth.givenName} ${state.auth.familyName}`) - return <span>{name}</span> + const user = useUser() + return ( + <span> + {user.givenName} {user.familyName} + </span> + ) } export default ProfileName diff --git a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js b/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js index 42a44345..d0d28ccb 100644 --- a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js @@ -1,11 +1,9 @@ import React from 'react' -import { useSelector } from 'react-redux' import AppNavbarComponent from '../../components/navigation/AppNavbarComponent' +import { useProject } from '../../store/hooks/project' const AppNavbarContainer = (props) => { - const project = useSelector((state) => - state.currentProjectId !== '-1' ? state.objects.project[state.currentProjectId] : undefined - ) + const project = useProject() return <AppNavbarComponent {...props} project={project} /> } diff --git a/opendc-web/opendc-web-ui/src/shapes/index.js b/opendc-web/opendc-web-ui/src/shapes/index.js index 9fab6f5d..621c7d25 100644 --- a/opendc-web/opendc-web-ui/src/shapes/index.js +++ b/opendc-web/opendc-web-ui/src/shapes/index.js @@ -1,8 +1,6 @@ import PropTypes from 'prop-types' -const Shapes = {} - -Shapes.User = PropTypes.shape({ +export const User = PropTypes.shape({ _id: PropTypes.string.isRequired, googleId: PropTypes.string.isRequired, email: PropTypes.string.isRequired, @@ -11,7 +9,7 @@ Shapes.User = PropTypes.shape({ authorizations: PropTypes.array.isRequired, }) -Shapes.Project = PropTypes.shape({ +export const Project = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, datetimeCreated: PropTypes.string.isRequired, @@ -20,15 +18,15 @@ Shapes.Project = PropTypes.shape({ portfolioIds: PropTypes.array.isRequired, }) -Shapes.Authorization = PropTypes.shape({ +export const Authorization = PropTypes.shape({ userId: PropTypes.string.isRequired, - user: Shapes.User, + user: User, projectId: PropTypes.string.isRequired, - project: Shapes.Project, + project: Project, authorizationLevel: PropTypes.string.isRequired, }) -Shapes.ProcessingUnit = PropTypes.shape({ +export const ProcessingUnit = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, clockRateMhz: PropTypes.number.isRequired, @@ -36,7 +34,7 @@ Shapes.ProcessingUnit = PropTypes.shape({ energyConsumptionW: PropTypes.number.isRequired, }) -Shapes.StorageUnit = PropTypes.shape({ +export const StorageUnit = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, speedMbPerS: PropTypes.number.isRequired, @@ -44,59 +42,59 @@ Shapes.StorageUnit = PropTypes.shape({ energyConsumptionW: PropTypes.number.isRequired, }) -Shapes.Machine = PropTypes.shape({ +export const Machine = PropTypes.shape({ _id: PropTypes.string.isRequired, rackId: PropTypes.string.isRequired, position: PropTypes.number.isRequired, cpuIds: PropTypes.arrayOf(PropTypes.string.isRequired), - cpus: PropTypes.arrayOf(Shapes.ProcessingUnit), + cpus: PropTypes.arrayOf(ProcessingUnit), gpuIds: PropTypes.arrayOf(PropTypes.string.isRequired), - gpus: PropTypes.arrayOf(Shapes.ProcessingUnit), + gpus: PropTypes.arrayOf(ProcessingUnit), memoryIds: PropTypes.arrayOf(PropTypes.string.isRequired), - memories: PropTypes.arrayOf(Shapes.StorageUnit), + memories: PropTypes.arrayOf(StorageUnit), storageIds: PropTypes.arrayOf(PropTypes.string.isRequired), - storages: PropTypes.arrayOf(Shapes.StorageUnit), + storages: PropTypes.arrayOf(StorageUnit), }) -Shapes.Rack = PropTypes.shape({ +export const Rack = PropTypes.shape({ _id: PropTypes.string.isRequired, capacity: PropTypes.number.isRequired, powerCapacityW: PropTypes.number.isRequired, - machines: PropTypes.arrayOf(Shapes.Machine), + machines: PropTypes.arrayOf(Machine), }) -Shapes.Tile = PropTypes.shape({ +export const Tile = PropTypes.shape({ _id: PropTypes.string.isRequired, roomId: PropTypes.string.isRequired, positionX: PropTypes.number.isRequired, positionY: PropTypes.number.isRequired, rackId: PropTypes.string, - rack: Shapes.Rack, + rack: Rack, }) -Shapes.Room = PropTypes.shape({ +export const Room = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, - tiles: PropTypes.arrayOf(Shapes.Tile), + tiles: PropTypes.arrayOf(Tile), }) -Shapes.Topology = PropTypes.shape({ +export const Topology = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, - rooms: PropTypes.arrayOf(Shapes.Room), + rooms: PropTypes.arrayOf(Room), }) -Shapes.Scheduler = PropTypes.shape({ +export const Scheduler = PropTypes.shape({ name: PropTypes.string.isRequired, }) -Shapes.Trace = PropTypes.shape({ +export const Trace = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, type: PropTypes.string.isRequired, }) -Shapes.Portfolio = PropTypes.shape({ +export const Portfolio = PropTypes.shape({ _id: PropTypes.string.isRequired, projectId: PropTypes.string.isRequired, name: PropTypes.string.isRequired, @@ -107,7 +105,7 @@ Shapes.Portfolio = PropTypes.shape({ }).isRequired, }) -Shapes.Scenario = PropTypes.shape({ +export const Scenario = PropTypes.shape({ _id: PropTypes.string.isRequired, portfolioId: PropTypes.string.isRequired, name: PropTypes.string.isRequired, @@ -116,33 +114,31 @@ Shapes.Scenario = PropTypes.shape({ }).isRequired, trace: PropTypes.shape({ traceId: PropTypes.string.isRequired, - trace: Shapes.Trace, + trace: Trace, loadSamplingFraction: PropTypes.number.isRequired, }).isRequired, topology: PropTypes.shape({ topologyId: PropTypes.string.isRequired, - topology: Shapes.Topology, + topology: Topology, }).isRequired, operational: PropTypes.shape({ failuresEnabled: PropTypes.bool.isRequired, performanceInterferenceEnabled: PropTypes.bool.isRequired, schedulerName: PropTypes.string.isRequired, - scheduler: Shapes.Scheduler, + scheduler: Scheduler, }).isRequired, results: PropTypes.object, }) -Shapes.WallSegment = PropTypes.shape({ +export const WallSegment = PropTypes.shape({ startPosX: PropTypes.number.isRequired, startPosY: PropTypes.number.isRequired, isHorizontal: PropTypes.bool.isRequired, length: PropTypes.number.isRequired, }) -Shapes.InteractionLevel = PropTypes.shape({ +export const InteractionLevel = PropTypes.shape({ mode: PropTypes.string.isRequired, roomId: PropTypes.string, rackId: PropTypes.string, }) - -export default Shapes diff --git a/opendc-web/opendc-web-ui/src/store/hooks/map.js b/opendc-web/opendc-web-ui/src/store/hooks/map.js new file mode 100644 index 00000000..6aef6ac5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/store/hooks/map.js @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useSelector } from 'react-redux' + +/** + * Return the map scale. + */ +export function useMapScale() { + return useSelector((state) => state.map.scale) +} + +/** + * Return the map position. + */ +export function useMapPosition() { + return useSelector((state) => state.map.position) +} + +export function useMapDimensions() { + return useSelector((state) => state.map.dimensions) +} diff --git a/opendc-web/opendc-web-ui/src/store/hooks/project.js b/opendc-web/opendc-web-ui/src/store/hooks/project.js new file mode 100644 index 00000000..0f2f1b66 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/store/hooks/project.js @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useSelector } from 'react-redux' + +/** + * Return the current active project. + */ +export function useProject() { + return useSelector((state) => + state.currentProjectId !== '-1' ? state.objects.project[state.currentProjectId] : undefined + ) +} diff --git a/opendc-web/opendc-web-ui/src/store/hooks/topology.js b/opendc-web/opendc-web-ui/src/store/hooks/topology.js new file mode 100644 index 00000000..e5e14804 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/store/hooks/topology.js @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useSelector } from 'react-redux' + +/** + * Return the current active topology. + */ +export function useTopology() { + return useSelector((state) => state.currentTopologyId !== '-1' && state.objects.topology[state.currentTopologyId]) +} |
