summaryrefslogtreecommitdiff
path: root/opendc-web
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web')
-rw-r--r--opendc-web/opendc-web-ui/src/auth/hook.js7
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js54
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js104
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js106
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js105
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js91
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js56
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js75
-rw-r--r--opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js8
-rw-r--r--opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/navigation/Navbar.js5
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js4
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/App.js11
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js7
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js5
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js4
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js5
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js5
-rw-r--r--opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js10
-rw-r--r--opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js6
-rw-r--r--opendc-web/opendc-web-ui/src/shapes/index.js62
-rw-r--r--opendc-web/opendc-web-ui/src/store/hooks/map.js41
-rw-r--r--opendc-web/opendc-web-ui/src/store/hooks/project.js32
-rw-r--r--opendc-web/opendc-web-ui/src/store/hooks/topology.js30
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])
+}