diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-07-20 10:51:39 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-07-20 10:51:39 +0200 |
| commit | 51c759e74b088d405b63fdb3e374822308d21366 (patch) | |
| tree | 3094cb874872d932d278d98d60f79902bf08b1a0 /opendc-web/opendc-web-ui/src/components | |
| parent | db1d2c2f8c18850dedf34b5d690b6cd6a1d1f6b5 (diff) | |
| parent | 28d6d13844db28745bc2813e87a367131f862070 (diff) | |
merge: Address technical dept in topology view (#162)
This pull request aims to address some of the technical debt in the topology
view of the OpenDC frontend.
* Add support for panning of the datacenter topology
* Isolate world coordinate space (world objects do not depend on camera scale or position)
* Split transpiled modules into a separate chunk to reduce deduplication
* Encode state in topology actions to reduce global state
* Restructure components per page
* Enable more ESLint rules through `eslint:recommended` ruleset
* Move page components in separate files.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/components')
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/AppNavigation.js | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/AppPage.js | 3 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/map/MapStage.js | 66 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.js | 28 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js | 28 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js | 22 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js | 25 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js | 44 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js | 64 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js | 26 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js | 11 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js | 6 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarContainer.js | 37 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js | 16 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarContainer.js | 32 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarContainer.js | 32 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js (renamed from opendc-web/opendc-web-ui/src/components/projects/NewScenario.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js) | 10 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js | 121 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js (renamed from opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultInfo.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js (renamed from opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js) | 28 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js (renamed from opendc-web/opendc-web-ui/src/components/projects/ScenarioState.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js (renamed from opendc-web/opendc-web-ui/src/components/projects/ScenarioTable.js) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js) | 26 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/projects/NewProject.js | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/projects/NewTopology.js | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js) | 26 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js | 98 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js | 76 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js | 77 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/GrayContainer.js) | 4 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js) | 3 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js | 83 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/map/MapStage.module.scss) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/RackContainer.js) | 4 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js) | 15 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js) | 14 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/RoomContainer.js) | 8 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/TileContainer.js) | 6 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/TopologyContainer.js) | 4 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/WallContainer.js) | 8 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.js) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.module.scss) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.js) | 12 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.module.scss) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js | 35 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.module.scss) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js) | 4 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js) | 22 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js | 30 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js | 24 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js | 27 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js | 44 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js) | 28 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js | 55 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js) | 20 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js) | 35 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js) | 50 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js) | 22 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.module.scss) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js) | 4 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionContainer.js) | 18 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachine.js) | 14 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js) | 17 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddContainer.js) | 7 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js) | 6 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListContainer.js) | 17 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js) | 23 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabContainer.js) | 21 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js) | 13 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js) | 4 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js) | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js) | 6 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js) | 6 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js) | 14 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js) | 13 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js) | 11 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js) | 5 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js) | 4 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js) | 8 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js) | 10 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/util/modals/Modal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/Modal.js) | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js) | 0 |
97 files changed, 1020 insertions, 692 deletions
diff --git a/opendc-web/opendc-web-ui/src/components/AppNavigation.js b/opendc-web/opendc-web-ui/src/components/AppNavigation.js index b3f11f34..178c3ec0 100644 --- a/opendc-web/opendc-web-ui/src/components/AppNavigation.js +++ b/opendc-web/opendc-web-ui/src/components/AppNavigation.js @@ -20,7 +20,7 @@ * SOFTWARE. */ -import { Dropdown, DropdownItem, DropdownToggle, Nav, NavItem, NavList } from '@patternfly/react-core' +import { Nav, NavItem, NavList } from '@patternfly/react-core' import { useRouter } from 'next/router' import NavItemLink from './util/NavItemLink' import { useProject } from '../data/project' diff --git a/opendc-web/opendc-web-ui/src/components/AppPage.js b/opendc-web/opendc-web-ui/src/components/AppPage.js index 7cf9cc15..2e0ea4cc 100644 --- a/opendc-web/opendc-web-ui/src/components/AppPage.js +++ b/opendc-web/opendc-web-ui/src/components/AppPage.js @@ -22,8 +22,7 @@ import PropTypes from 'prop-types' import { AppHeader } from './AppHeader' -import { AppNavigation } from './AppNavigation' -import React, { useState } from 'react' +import React from 'react' import { Page } from '@patternfly/react-core' export function AppPage({ children, breadcrumb, tertiaryNav }) { diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js b/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js deleted file mode 100644 index 73accf3f..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js +++ /dev/null @@ -1,66 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react' -import { HotKeys } from 'react-hotkeys' -import { Stage } from 'react-konva' -import { MAP_MOVE_PIXELS_PER_EVENT } from './MapConstants' -import { Provider, useDispatch, useStore } from 'react-redux' -import useResizeObserver from 'use-resize-observer' -import { mapContainer } from './MapStage.module.scss' -import { useMapPosition } from '../../../data/map' -import { setMapDimensions, setMapPositionWithBoundsCheck, zoomInOnPosition } from '../../../redux/actions/map' -import MapLayer from './layers/MapLayer' -import RoomHoverLayer from './layers/RoomHoverLayer' -import ObjectHoverLayer from './layers/ObjectHoverLayer' - -function MapStage() { - const store = useStore() - const dispatch = useDispatch() - - const stage = useRef(null) - const [pos, setPos] = useState([0, 0]) - const [x, y] = pos - const handlers = { - MOVE_LEFT: () => moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0), - MOVE_RIGHT: () => moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0), - MOVE_UP: () => moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT), - MOVE_DOWN: () => moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT), - } - const mapPosition = useMapPosition() - const { ref, width = 100, height = 100 } = useResizeObserver() - - const moveWithDelta = (deltaX, deltaY) => - dispatch(setMapPositionWithBoundsCheck(mapPosition.x + deltaX, mapPosition.y + deltaY)) - const updateMousePosition = () => { - if (!stage.current) { - return - } - - const mousePos = stage.current.getStage().getPointerPosition() - setPos([mousePos.x, mousePos.y]) - } - const updateScale = ({ evt }) => dispatch(zoomInOnPosition(evt.deltaY < 0, x, y)) - - useEffect(() => { - window['exportCanvasToImage'] = () => { - const download = document.createElement('a') - download.href = stage.current.getStage().toDataURL() - download.download = 'opendc-canvas-export-' + Date.now() + '.png' - download.click() - } - }, [stage]) - - useEffect(() => dispatch(setMapDimensions(width, height)), [width, height]) // eslint-disable-line react-hooks/exhaustive-deps - - return ( - <HotKeys handlers={handlers} allowChanges={true} innerRef={ref} className={mapContainer}> - <Stage ref={stage} width={width} height={height} onMouseMove={updateMousePosition} onWheel={updateScale}> - <Provider store={store}> - <MapLayer /> - <RoomHoverLayer mouseX={x} mouseY={y} /> - <ObjectHoverLayer mouseX={x} mouseY={y} /> - </Provider> - </Stage> - </HotKeys> - ) -} - -export default MapStage diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.js deleted file mode 100644 index 4c60bfb2..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.js +++ /dev/null @@ -1,28 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { control, toolBar } from './Toolbar.module.scss' -import { Button } from '@patternfly/react-core' -import { SearchPlusIcon, SearchMinusIcon } from '@patternfly/react-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faCamera } from '@fortawesome/free-solid-svg-icons' - -const Toolbar = ({ onZoom, onExport }) => ( - <div className={toolBar}> - <Button variant="tertiary" title="Zoom in" onClick={() => onZoom(true)} className={control}> - <SearchPlusIcon /> - </Button> - <Button variant="tertiary" title="Zoom out" onClick={() => onZoom(false)} className={control}> - <SearchMinusIcon /> - </Button> - <Button variant="tertiary" title="Export Canvas to PNG Image" onClick={() => onExport()} className={control}> - <FontAwesomeIcon icon={faCamera} /> - </Button> - </div> -) - -Toolbar.propTypes = { - onZoom: PropTypes.func, - onExport: PropTypes.func, -} - -export default Toolbar diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js deleted file mode 100644 index 11bba0e1..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js +++ /dev/null @@ -1,28 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { ROOM_HOVER_INVALID_COLOR, ROOM_HOVER_VALID_COLOR } from '../../../../util/colors' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const HoverTile = ({ pixelX, pixelY, isValid, scale, onClick }) => ( - <Rect - x={pixelX} - y={pixelY} - scaleX={scale} - scaleY={scale} - width={TILE_SIZE_IN_PIXELS} - height={TILE_SIZE_IN_PIXELS} - fill={isValid ? ROOM_HOVER_VALID_COLOR : ROOM_HOVER_INVALID_COLOR} - onClick={onClick} - /> -) - -HoverTile.propTypes = { - pixelX: PropTypes.number.isRequired, - pixelY: PropTypes.number.isRequired, - isValid: PropTypes.bool.isRequired, - scale: PropTypes.number.isRequired, - onClick: PropTypes.func.isRequired, -} - -export default HoverTile 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 deleted file mode 100644 index ed718601..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js +++ /dev/null @@ -1,22 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { Tile } from '../../../../shapes' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const RoomTile = ({ tile, color }) => ( - <Rect - x={tile.positionX * TILE_SIZE_IN_PIXELS} - y={tile.positionY * TILE_SIZE_IN_PIXELS} - width={TILE_SIZE_IN_PIXELS} - height={TILE_SIZE_IN_PIXELS} - fill={color} - /> -) - -RoomTile.propTypes = { - tile: Tile, - color: PropTypes.string, -} - -export default RoomTile diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js deleted file mode 100644 index 9e87cc82..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { OBJECT_BORDER_COLOR } from '../../../../util/colors' -import { OBJECT_BORDER_WIDTH_IN_PIXELS, OBJECT_MARGIN_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const TileObject = ({ positionX, positionY, color }) => ( - <Rect - x={positionX * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} - y={positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} - width={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} - height={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} - fill={color} - stroke={OBJECT_BORDER_COLOR} - strokeWidth={OBJECT_BORDER_WIDTH_IN_PIXELS} - /> -) - -TileObject.propTypes = { - positionX: PropTypes.number.isRequired, - positionY: PropTypes.number.isRequired, - color: PropTypes.string.isRequired, -} - -export default TileObject diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js deleted file mode 100644 index be3a00a8..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js +++ /dev/null @@ -1,44 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group, Line } from 'react-konva' -import { TILE_PLUS_COLOR } from '../../../../util/colors' -import { TILE_PLUS_MARGIN_IN_PIXELS, TILE_PLUS_WIDTH_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const TilePlusIcon = ({ pixelX, pixelY, mapScale }) => { - const linePoints = [ - [ - pixelX + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - pixelY + TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - pixelX + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - pixelY + TILE_SIZE_IN_PIXELS * mapScale - TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - ], - [ - pixelX + TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - pixelY + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - pixelX + TILE_SIZE_IN_PIXELS * mapScale - TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - pixelY + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - ], - ] - return ( - <Group> - {linePoints.map((points, index) => ( - <Line - key={index} - points={points} - lineCap="round" - stroke={TILE_PLUS_COLOR} - strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * mapScale} - listening={false} - /> - ))} - </Group> - ) -} - -TilePlusIcon.propTypes = { - pixelX: PropTypes.number, - pixelY: PropTypes.number, - mapScale: PropTypes.number, -} - -export default TilePlusIcon 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 deleted file mode 100644 index a88a8b34..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js +++ /dev/null @@ -1,64 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useEffect, useState } from 'react' -import { Layer } from 'react-konva' -import HoverTile from '../elements/HoverTile' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' - -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) - - useEffect(() => { - if (!isEnabled()) { - return - } - - 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 !== x || positionY !== y) { - setPos([positionX, positionY]) - setValid(isValid(positionX, positionY)) - } - }, [isEnabled, isValid, x, y, mouseX, mouseY, mapPosition, mapScale]) - - if (!isEnabled()) { - return <Layer /> - } - - 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> - ) -} - -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, - children: PropTypes.node, -} - -export default HoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js deleted file mode 100644 index efe5b4e5..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js +++ /dev/null @@ -1,26 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group, Layer } from 'react-konva' -import Backdrop from '../elements/Backdrop' -import GridGroup from '../groups/GridGroup' -import TopologyContainer from '../TopologyContainer' - -const MapLayerComponent = ({ mapPosition, mapScale }) => ( - <Layer> - <Group x={mapPosition.x} y={mapPosition.y} scaleX={mapScale} scaleY={mapScale}> - <Backdrop /> - <TopologyContainer /> - <GridGroup /> - </Group> - </Layer> -) - -MapLayerComponent.propTypes = { - mapPosition: PropTypes.shape({ - x: PropTypes.number, - y: PropTypes.number, - }), - mapScale: PropTypes.number, -} - -export default MapLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js deleted file mode 100644 index 661fc255..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import TilePlusIcon from '../elements/TilePlusIcon' -import HoverLayerComponent from './HoverLayerComponent' - -const ObjectHoverLayerComponent = (props) => ( - <HoverLayerComponent {...props}> - <TilePlusIcon {...props} /> - </HoverLayerComponent> -) - -export default ObjectHoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js deleted file mode 100644 index 887e2891..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import HoverLayerComponent from './HoverLayerComponent' - -const RoomHoverLayerComponent = (props) => <HoverLayerComponent {...props} /> - -export default RoomHoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarContainer.js deleted file mode 100644 index 94d9f2c3..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarContainer.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 React from 'react' -import { useSelector } from 'react-redux' -import MachineSidebarComponent from './MachineSidebarComponent' - -const MachineSidebarContainer = (props) => { - const machineId = useSelector( - (state) => - state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rack].machines[ - state.interactionLevel.position - 1 - ] - ) - return <MachineSidebarComponent {...props} machineId={machineId} /> -} - -export default MachineSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js deleted file mode 100644 index c8543134..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js +++ /dev/null @@ -1,16 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { SaveIcon } from '@patternfly/react-icons' -import { Button } from '@patternfly/react-core' - -const AddPrefabComponent = ({ onClick }) => ( - <Button variant="primary" icon={<SaveIcon />} isBlock onClick={onClick} className="pf-u-mb-sm"> - Save this rack to a prefab - </Button> -) - -AddPrefabComponent.propTypes = { - onClick: PropTypes.func, -} - -export default AddPrefabComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarContainer.js deleted file mode 100644 index 2b31413d..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarContainer.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 React from 'react' -import { useSelector } from 'react-redux' -import RackSidebarComponent from './RackSidebarComponent' - -const RackSidebarContainer = (props) => { - const tileId = useSelector((state) => state.interactionLevel.tileId) - return <RackSidebarComponent {...props} tileId={tileId} /> -} - -export default RackSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarContainer.js deleted file mode 100644 index 2076b00e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarContainer.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 React from 'react' -import { useSelector } from 'react-redux' -import RoomSidebarComponent from './RoomSidebarComponent' - -const RoomSidebarContainer = (props) => { - const roomId = useSelector((state) => state.interactionLevel.roomId) - return <RoomSidebarComponent {...props} roomId={roomId} /> -} - -export default RoomSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewScenario.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js index 6d4f48c1..856282a7 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewScenario.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js @@ -24,8 +24,8 @@ import PropTypes from 'prop-types' import { PlusIcon } from '@patternfly/react-icons' import { Button } from '@patternfly/react-core' import { useState } from 'react' -import NewScenarioModal from '../modals/custom-components/NewScenarioModal' import { useMutation } from 'react-query' +import NewScenarioModal from './NewScenarioModal' function NewScenario({ portfolioId }) { const [isVisible, setVisible] = useState(false) diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js index 94d0d424..7f620c8c 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js @@ -1,7 +1,6 @@ import PropTypes from 'prop-types' import React, { useRef, useState } from 'react' -import { Portfolio } from '../../../shapes' -import Modal from '../Modal' +import Modal from '../util/modals/Modal' import { Checkbox, Form, @@ -12,9 +11,9 @@ import { NumberInput, TextInput, } from '@patternfly/react-core' -import { useSchedulers, useTraces } from '../../../data/experiments' -import { useProjectTopologies } from '../../../data/topology' -import { usePortfolio } from '../../../data/project' +import { useSchedulers, useTraces } from '../../data/experiments' +import { useProjectTopologies } from '../../data/topology' +import { usePortfolio } from '../../data/project' const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { const { data: portfolio } = usePortfolio(portfolioId) @@ -22,6 +21,7 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC const { data: traces = [] } = useTraces() const { data: schedulers = [] } = useSchedulers() + // eslint-disable-next-line no-unused-vars const [isSubmitted, setSubmitted] = useState(false) const [traceLoad, setTraceLoad] = useState(100) const [trace, setTrace] = useState(undefined) diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js new file mode 100644 index 00000000..580b0a29 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js @@ -0,0 +1,121 @@ +/* + * 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 PropTypes from 'prop-types' +import { + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + Chip, + ChipGroup, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import React from 'react' +import { usePortfolio } from '../../data/project' +import { METRIC_NAMES } from '../../util/available-metrics' +import NewScenario from './NewScenario' +import ScenarioTable from './ScenarioTable' + +function PortfolioOverview({ portfolioId }) { + const { data: portfolio } = usePortfolio(portfolioId) + + return ( + <Grid hasGutter> + <GridItem md={2}> + <Card> + <CardTitle>Details</CardTitle> + <CardBody> + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Name</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.name ?? <Skeleton screenreaderText="Loading portfolio" />} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Scenarios</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.scenarioIds.length ?? <Skeleton screenreaderText="Loading portfolio" />} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Metrics</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.targets?.enabledMetrics ? ( + portfolio.targets.enabledMetrics.length > 0 ? ( + <ChipGroup> + {portfolio.targets.enabledMetrics.map((metric) => ( + <Chip isReadOnly key={metric}> + {METRIC_NAMES[metric]} + </Chip> + ))} + </ChipGroup> + ) : ( + 'No metrics enabled' + ) + ) : ( + <Skeleton screenreaderText="Loading portfolio" /> + )} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Repeats per Scenario</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.targets?.repeatsPerScenario ?? ( + <Skeleton screenreaderText="Loading portfolio" /> + )} + </DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + </CardBody> + </Card> + </GridItem> + <GridItem md={6}> + <Card> + <CardHeader> + <CardActions> + <NewScenario portfolioId={portfolioId} /> + </CardActions> + <CardTitle>Scenarios</CardTitle> + </CardHeader> + <CardBody> + <ScenarioTable portfolioId={portfolioId} /> + </CardBody> + </Card> + </GridItem> + </Grid> + ) +} + +PortfolioOverview.propTypes = { + portfolioId: PropTypes.string, +} + +export default PortfolioOverview diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultInfo.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js index 09348e60..dbfa928f 100644 --- a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultInfo.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js @@ -23,7 +23,7 @@ import PropTypes from 'prop-types' import { Tooltip } from '@patternfly/react-core' import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons' -import { METRIC_DESCRIPTIONS } from '../../../util/available-metrics' +import { METRIC_DESCRIPTIONS } from '../../util/available-metrics' function PortfolioResultInfo({ metric }) { return ( diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js index 6a96c70c..00023d9e 100644 --- a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js @@ -1,7 +1,29 @@ +/* + * 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 React from 'react' import PropTypes from 'prop-types' import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts' -import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_NAMES_SHORT, METRIC_UNITS } from '../../../util/available-metrics' +import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_UNITS } from '../../util/available-metrics' import { mean, std } from 'mathjs' import approx from 'approximate-number' import { @@ -20,9 +42,9 @@ import { Title, } from '@patternfly/react-core' import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons' -import { usePortfolioScenarios } from '../../../data/project' -import NewScenario from '../../projects/NewScenario' +import { usePortfolioScenarios } from '../../data/project' import PortfolioResultInfo from './PortfolioResultInfo' +import NewScenario from './NewScenario' const PortfolioResults = ({ portfolioId }) => { const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) diff --git a/opendc-web/opendc-web-ui/src/components/projects/ScenarioState.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js index 285345e7..66691580 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ScenarioState.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js @@ -21,7 +21,7 @@ */ import PropTypes from 'prop-types' -import { ClockIcon, InfoIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons' +import { ClockIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons' function ScenarioState({ state }) { switch (state) { diff --git a/opendc-web/opendc-web-ui/src/components/projects/ScenarioTable.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js index 9966e3ba..9966e3ba 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ScenarioTable.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js index ae4cb9cd..87ea059d 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js @@ -24,8 +24,8 @@ import PropTypes from 'prop-types' import { PlusIcon } from '@patternfly/react-icons' import { Button } from '@patternfly/react-core' import { useState } from 'react' -import NewPortfolioModal from '../modals/custom-components/NewPortfolioModal' import { useMutation } from 'react-query' +import NewPortfolioModal from './NewPortfolioModal' function NewPortfolio({ projectId }) { const [isVisible, setVisible] = useState(false) diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js index afe07597..4276d7d4 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js @@ -1,6 +1,27 @@ +/* + * 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 PropTypes from 'prop-types' import React, { useRef, useState } from 'react' -import Modal from '../Modal' import { Form, FormGroup, @@ -12,7 +33,8 @@ import { SelectVariant, TextInput, } from '@patternfly/react-core' -import { METRIC_GROUPS, METRIC_NAMES } from '../../../util/available-metrics' +import Modal from '../util/modals/Modal' +import { METRIC_GROUPS, METRIC_NAMES } from '../../util/available-metrics' const NewPortfolioModal = ({ isOpen, onSubmit: onSubmitUpstream, onCancel: onUpstreamCancel }) => { const nameInput = useRef(null) diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js index 4f5d79cf..984264dc 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js @@ -1,9 +1,9 @@ import React, { useState } from 'react' -import TextInputModal from '../../components/modals/TextInputModal' import { Button } from '@patternfly/react-core' import { useMutation } from 'react-query' import { PlusIcon } from '@patternfly/react-icons' import { buttonContainer } from './NewProject.module.scss' +import TextInputModal from '../util/modals/TextInputModal' /** * A container for creating a new project. diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js index c6c4171b..bf59e020 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js @@ -24,9 +24,9 @@ import PropTypes from 'prop-types' import { PlusIcon } from '@patternfly/react-icons' import { Button } from '@patternfly/react-core' import { useState } from 'react' -import NewTopologyModal from '../modals/custom-components/NewTopologyModal' import { useDispatch } from 'react-redux' import { addTopology } from '../../redux/actions/topologies' +import NewTopologyModal from './NewTopologyModal' function NewTopology({ projectId }) { const [isVisible, setVisible] = useState(false) diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js index 49952aec..a495f73e 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js @@ -1,8 +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 PropTypes from 'prop-types' import React, { useRef, useState } from 'react' -import Modal from '../Modal' import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core' -import { useProjectTopologies } from '../../../data/topology' +import { useProjectTopologies } from '../../data/topology' +import Modal from '../util/modals/Modal' const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { const nameInput = useRef(null) diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js new file mode 100644 index 00000000..65b8f5a0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js @@ -0,0 +1,98 @@ +/* + * 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 PropTypes from 'prop-types' +import { + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import NewTopology from './NewTopology' +import TopologyTable from './TopologyTable' +import NewPortfolio from './NewPortfolio' +import PortfolioTable from './PortfolioTable' +import { useProject } from '../../data/project' + +function ProjectOverview({ projectId }) { + const { data: project } = useProject(projectId) + + return ( + <Grid hasGutter> + <GridItem md={2}> + <Card> + <CardTitle>Details</CardTitle> + <CardBody> + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Name</DescriptionListTerm> + <DescriptionListDescription> + {project?.name ?? <Skeleton screenreaderText="Loading project" />} + </DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + </CardBody> + </Card> + </GridItem> + <GridItem md={5}> + <Card> + <CardHeader> + <CardActions> + <NewTopology projectId={projectId} /> + </CardActions> + <CardTitle>Topologies</CardTitle> + </CardHeader> + <CardBody> + <TopologyTable projectId={projectId} /> + </CardBody> + </Card> + </GridItem> + <GridItem md={5}> + <Card> + <CardHeader> + <CardActions> + <NewPortfolio projectId={projectId} /> + </CardActions> + <CardTitle>Portfolios</CardTitle> + </CardHeader> + <CardBody> + <PortfolioTable projectId={projectId} /> + </CardBody> + </Card> + </GridItem> + </Grid> + ) +} + +ProjectOverview.propTypes = { + projectId: PropTypes.string, +} + +export default ProjectOverview diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js new file mode 100644 index 00000000..c16f554c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js @@ -0,0 +1,76 @@ +/* + * 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 React, { useState } from 'react' +import { + Bullseye, + Drawer, + DrawerContent, + DrawerContentBody, + EmptyState, + EmptyStateIcon, + Spinner, + Title, +} from '@patternfly/react-core' +import { configure, HotKeys } from 'react-hotkeys' +import { KeymapConfiguration } from '../../hotkeys' +import MapStage from './map/MapStage' +import Collapse from './map/controls/Collapse' +import { useSelector } from 'react-redux' +import TopologySidebar from './sidebar/TopologySidebar' + +function TopologyMap() { + const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1') + const interactionLevel = useSelector((state) => state.interactionLevel) + + const [isExpanded, setExpanded] = useState(true) + const panelContent = <TopologySidebar interactionLevel={interactionLevel} onClose={() => setExpanded(false)} /> + + // Make sure that holding down a key will generate repeated events + configure({ + ignoreRepeatedEventsWhenKeyHeldDown: false, + }) + + return topologyIsLoading ? ( + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={Spinner} /> + <Title size="lg" headingLevel="h4"> + Loading Topology + </Title> + </EmptyState> + </Bullseye> + ) : ( + <HotKeys keyMap={KeymapConfiguration} allowChanges={true} className="full-height"> + <Drawer isExpanded={isExpanded}> + <DrawerContent panelContent={panelContent}> + <DrawerContentBody> + <MapStage /> + <Collapse onClick={() => setExpanded(true)} /> + </DrawerContentBody> + </DrawerContent> + </Drawer> + </HotKeys> + ) +} + +export default TopologyMap diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js new file mode 100644 index 00000000..f773dcd1 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js @@ -0,0 +1,77 @@ +/* + * 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 PropTypes from 'prop-types' +import { + Card, + CardBody, + CardTitle, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import { useTopology } from '../../data/topology' +import { parseAndFormatDateTime } from '../../util/date-time' + +function TopologyOverview({ topologyId }) { + const { data: topology } = useTopology(topologyId) + + return ( + <Grid hasGutter> + <GridItem md={2}> + <Card> + <CardTitle>Details</CardTitle> + <CardBody> + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Name</DescriptionListTerm> + <DescriptionListDescription> + {topology?.name ?? <Skeleton screenreaderText="Loading topology" />} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Last edited</DescriptionListTerm> + <DescriptionListDescription> + {topology ? ( + parseAndFormatDateTime(topology.datetimeLastEdited) + ) : ( + <Skeleton screenreaderText="Loading topology" /> + )} + </DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + </CardBody> + </Card> + </GridItem> + </Grid> + ) +} + +TopologyOverview.propTypes = { + topologyId: PropTypes.string, +} + +export default TopologyOverview diff --git a/opendc-web/opendc-web-ui/src/components/app/map/GrayContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js index 4791940f..ccf637e5 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/GrayContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js @@ -23,9 +23,9 @@ import React from 'react' import { useDispatch } from 'react-redux' import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' -import GrayLayer from '../../../components/app/map/elements/GrayLayer' +import GrayLayer from './elements/GrayLayer' -const GrayContainer = () => { +function GrayContainer() { const dispatch = useDispatch() const onClick = () => dispatch(goDownOneInteractionLevel()) return <GrayLayer onClick={onClick} /> diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js index 45799f70..4c3b2757 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js @@ -12,9 +12,6 @@ export const WALL_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 export const OBJECT_BORDER_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 export const TILE_PLUS_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 10 -export const SIDEBAR_WIDTH = 350 -export const VIEWPORT_PADDING = 50 - export const RACK_FILL_ICON_WIDTH = OBJECT_SIZE_IN_PIXELS / 3 export const RACK_FILL_ICON_OPACITY = 0.8 diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js new file mode 100644 index 00000000..5d19b3ad --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js @@ -0,0 +1,83 @@ +import React, { useRef, useState } from 'react' +import { HotKeys } from 'react-hotkeys' +import { Stage } from 'react-konva' +import { MAP_MAX_SCALE, MAP_MIN_SCALE, MAP_MOVE_PIXELS_PER_EVENT, MAP_SCALE_PER_EVENT } from './MapConstants' +import { Provider, useStore } from 'react-redux' +import useResizeObserver from 'use-resize-observer' +import { mapContainer } from './MapStage.module.scss' +import MapLayer from './layers/MapLayer' +import RoomHoverLayer from './layers/RoomHoverLayer' +import ObjectHoverLayer from './layers/ObjectHoverLayer' +import ScaleIndicator from './controls/ScaleIndicator' +import Toolbar from './controls/Toolbar' + +function MapStage() { + const store = useStore() + const { ref, width = 100, height = 100 } = useResizeObserver() + const stageRef = useRef(null) + const [[x, y], setPos] = useState([0, 0]) + const [scale, setScale] = useState(1) + + const clampScale = (target) => Math.min(Math.max(target, MAP_MIN_SCALE), MAP_MAX_SCALE) + const moveWithDelta = (deltaX, deltaY) => setPos(([x, y]) => [x + deltaX, y + deltaY]) + + const onZoom = (e) => { + e.evt.preventDefault() + + const stage = stageRef.current.getStage() + const oldScale = scale + + const pointer = stage.getPointerPosition() + const mousePointTo = { + x: (pointer.x - x) / oldScale, + y: (pointer.y - y) / oldScale, + } + + const newScale = clampScale(e.evt.deltaY > 0 ? oldScale * MAP_SCALE_PER_EVENT : oldScale / MAP_SCALE_PER_EVENT) + + setScale(newScale) + setPos([pointer.x - mousePointTo.x * newScale, pointer.y - mousePointTo.y * newScale]) + } + const onZoomButton = (zoomIn) => + setScale((scale) => clampScale(zoomIn ? scale * MAP_SCALE_PER_EVENT : scale / MAP_SCALE_PER_EVENT)) + const onDragEnd = (e) => setPos([e.target.x(), e.target.y()]) + const onExport = () => { + const download = document.createElement('a') + download.href = stageRef.current.getStage().toDataURL() + download.download = 'opendc-canvas-export-' + Date.now() + '.png' + download.click() + } + + const handlers = { + MOVE_LEFT: () => moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0), + MOVE_RIGHT: () => moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0), + MOVE_UP: () => moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT), + MOVE_DOWN: () => moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT), + } + + return ( + <HotKeys handlers={handlers} allowChanges={true} innerRef={ref} className={mapContainer}> + <Stage + ref={stageRef} + onWheel={onZoom} + onDragEnd={onDragEnd} + draggable + width={width} + height={height} + scale={{ x: scale, y: scale }} + x={x} + y={y} + > + <Provider store={store}> + <MapLayer /> + <RoomHoverLayer /> + <ObjectHoverLayer /> + </Provider> + </Stage> + <ScaleIndicator scale={scale} /> + <Toolbar onZoom={onZoomButton} onExport={onExport} /> + </HotKeys> + ) +} + +export default MapStage diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss index d879b4c8..d879b4c8 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.module.scss +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js index 3c75d3a7..14449a91 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/RackContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js @@ -22,10 +22,10 @@ import React from 'react' import { useSelector } from 'react-redux' -import RackGroup from '../../../components/app/map/groups/RackGroup' import { Tile } from '../../../shapes' +import RackGroup from './groups/RackGroup' -const RackContainer = ({ tile }) => { +function RackContainer({ tile }) { const interactionLevel = useSelector((state) => state.interactionLevel) return <RackGroup interactionLevel={interactionLevel} tile={tile} /> } diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js index 838aea5a..c35cbde7 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js @@ -1,12 +1,12 @@ import React from 'react' import PropTypes from 'prop-types' import { useSelector } from 'react-redux' -import RackFillBar from '../../../components/app/map/elements/RackFillBar' +import RackFillBar from './elements/RackFillBar' -const RackSpaceFillContainer = (props) => { - const state = useSelector((state) => { +function RackSpaceFillContainer({ tileId, ...props }) { + const fillFraction = useSelector((state) => { let energyConsumptionTotal = 0 - const rack = state.objects.rack[state.objects.tile[props.tileId].rack] + const rack = state.objects.rack[state.objects.tile[tileId].rack] const machineIds = rack.machines machineIds.forEach((machineId) => { if (machineId !== null) { @@ -22,12 +22,9 @@ const RackSpaceFillContainer = (props) => { } }) - return { - type: 'energy', - fillFraction: Math.min(1, energyConsumptionTotal / rack.powerCapacityW), - } + return Math.min(1, energyConsumptionTotal / rack.powerCapacityW) }) - return <RackFillBar {...props} {...state} /> + return <RackFillBar {...props} type="energy" fillFraction={fillFraction} /> } RackSpaceFillContainer.propTypes = { diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js index 6791120e..a6766f33 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js @@ -23,17 +23,11 @@ import React from 'react' import PropTypes from 'prop-types' import { useSelector } from 'react-redux' -import RackFillBar from '../../../components/app/map/elements/RackFillBar' +import RackFillBar from './elements/RackFillBar' -const RackSpaceFillContainer = (props) => { - const state = useSelector((state) => { - const machineIds = state.objects.rack[state.objects.tile[props.tileId].rack].machines - return { - type: 'space', - fillFraction: machineIds.filter((id) => id !== null).length / machineIds.length, - } - }) - return <RackFillBar {...props} {...state} /> +function RackSpaceFillContainer({ tileId, ...props }) { + const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) + return <RackFillBar {...props} type="space" fillFraction={rack.machines.length / rack.capacity} /> } RackSpaceFillContainer.propTypes = { diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js index 26fbcd7a..93ba9c93 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/RoomContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js @@ -24,18 +24,18 @@ import PropTypes from 'prop-types' import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { goFromBuildingToRoom } from '../../../redux/actions/interaction-level' -import RoomGroup from '../../../components/app/map/groups/RoomGroup' +import RoomGroup from './groups/RoomGroup' -const RoomContainer = (props) => { +function RoomContainer({ roomId, ...props }) { const state = useSelector((state) => { return { interactionLevel: state.interactionLevel, currentRoomInConstruction: state.construction.currentRoomInConstruction, - room: state.objects.room[props.roomId], + room: state.objects.room[roomId], } }) const dispatch = useDispatch() - return <RoomGroup {...props} {...state} onClick={() => dispatch(goFromBuildingToRoom(props.roomId))} /> + return <RoomGroup {...props} {...state} onClick={() => dispatch(goFromBuildingToRoom(roomId))} /> } RoomContainer.propTypes = { diff --git a/opendc-web/opendc-web-ui/src/components/app/map/TileContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js index bfcbf735..149e26a1 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/TileContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js @@ -24,11 +24,11 @@ import React from 'react' import PropTypes from 'prop-types' import { useDispatch, useSelector } from 'react-redux' import { goFromRoomToRack } from '../../../redux/actions/interaction-level' -import TileGroup from '../../../components/app/map/groups/TileGroup' +import TileGroup from './groups/TileGroup' -const TileContainer = (props) => { +function TileContainer({ tileId, ...props }) { const interactionLevel = useSelector((state) => state.interactionLevel) - const tile = useSelector((state) => state.objects.tile[props.tileId]) + const tile = useSelector((state) => state.objects.tile[tileId]) const dispatch = useDispatch() const onClick = (tile) => { diff --git a/opendc-web/opendc-web-ui/src/components/app/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js index 78e75d0f..eaebabd5 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/TopologyContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js @@ -22,10 +22,10 @@ import React from 'react' import { useSelector } from 'react-redux' -import TopologyGroup from '../../../components/app/map/groups/TopologyGroup' import { useActiveTopology } from '../../../data/topology' +import TopologyGroup from './groups/TopologyGroup' -const TopologyContainer = () => { +function TopologyContainer() { const topology = useActiveTopology() const interactionLevel = useSelector((state) => state.interactionLevel) diff --git a/opendc-web/opendc-web-ui/src/components/app/map/WallContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js index 51dffe4b..77f553dd 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/WallContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js @@ -23,12 +23,10 @@ import React from 'react' import PropTypes from 'prop-types' import { useSelector } from 'react-redux' -import WallGroup from '../../../components/app/map/groups/WallGroup' +import WallGroup from './groups/WallGroup' -const WallContainer = (props) => { - const tiles = useSelector((state) => - state.objects.room[props.roomId].tiles.map((tileId) => state.objects.tile[tileId]) - ) +function WallContainer({ roomId, ...props }) { + const tiles = useSelector((state) => state.objects.room[roomId].tiles.map((tileId) => state.objects.tile[tileId])) return <WallGroup {...props} tiles={tiles} /> } diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js index f54b7c84..f54b7c84 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss index 0c1fac94..0c1fac94 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.module.scss +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js index 11c2f2d3..58d2ccc9 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js @@ -3,11 +3,13 @@ import React from 'react' import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants' import { scaleIndicator } from './ScaleIndicator.module.scss' -const ScaleIndicator = ({ scale }) => ( - <div className={scaleIndicator} style={{ width: TILE_SIZE_IN_PIXELS * scale }}> - {TILE_SIZE_IN_METERS}m - </div> -) +function ScaleIndicator({ scale }) { + return ( + <div className={scaleIndicator} style={{ width: TILE_SIZE_IN_PIXELS * scale }}> + {TILE_SIZE_IN_METERS}m + </div> + ) +} ScaleIndicator.propTypes = { scale: PropTypes.number.isRequired, diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss index f19e0ff2..f19e0ff2 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.module.scss +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js new file mode 100644 index 00000000..469fd515 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { control, toolBar } from './Toolbar.module.scss' +import { Button } from '@patternfly/react-core' +import { SearchPlusIcon, SearchMinusIcon } from '@patternfly/react-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCamera } from '@fortawesome/free-solid-svg-icons' + +function Toolbar({ onZoom, onExport }) { + return ( + <div className={toolBar}> + <Button variant="tertiary" title="Zoom in" onClick={() => onZoom(true)} className={control}> + <SearchPlusIcon /> + </Button> + <Button variant="tertiary" title="Zoom out" onClick={() => onZoom(false)} className={control}> + <SearchMinusIcon /> + </Button> + <Button + variant="tertiary" + title="Export Canvas to PNG Image" + onClick={() => onExport()} + className={control} + > + <FontAwesomeIcon icon={faCamera} /> + </Button> + </div> + ) +} + +Toolbar.propTypes = { + onZoom: PropTypes.func, + onExport: PropTypes.func, +} + +export default Toolbar diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss index 0d505acc..0d505acc 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.module.scss +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js index 8ccfe584..93037b51 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js @@ -3,6 +3,8 @@ import { Rect } from 'react-konva' import { BACKDROP_COLOR } from '../../../../util/colors' import { MAP_SIZE_IN_PIXELS } from '../MapConstants' -const Backdrop = () => <Rect x={0} y={0} width={MAP_SIZE_IN_PIXELS} height={MAP_SIZE_IN_PIXELS} fill={BACKDROP_COLOR} /> +function Backdrop() { + return <Rect x={0} y={0} width={MAP_SIZE_IN_PIXELS} height={MAP_SIZE_IN_PIXELS} fill={BACKDROP_COLOR} /> +} export default Backdrop diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js index 35af4d96..08c687f6 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js @@ -4,16 +4,18 @@ import { Rect } from 'react-konva' import { GRAYED_OUT_AREA_COLOR } from '../../../../util/colors' import { MAP_SIZE_IN_PIXELS } from '../MapConstants' -const GrayLayer = ({ onClick }) => ( - <Rect - x={0} - y={0} - width={MAP_SIZE_IN_PIXELS} - height={MAP_SIZE_IN_PIXELS} - fill={GRAYED_OUT_AREA_COLOR} - onClick={onClick} - /> -) +function GrayLayer({ onClick }) { + return ( + <Rect + x={0} + y={0} + width={MAP_SIZE_IN_PIXELS} + height={MAP_SIZE_IN_PIXELS} + fill={GRAYED_OUT_AREA_COLOR} + onClick={onClick} + /> + ) +} GrayLayer.propTypes = { onClick: PropTypes.func, diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js new file mode 100644 index 00000000..20c2c6d1 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js @@ -0,0 +1,30 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { ROOM_HOVER_INVALID_COLOR, ROOM_HOVER_VALID_COLOR } from '../../../../util/colors' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function HoverTile({ x, y, isValid, scale = 1, onClick }) { + return ( + <Rect + x={x} + y={y} + scaleX={scale} + scaleY={scale} + width={TILE_SIZE_IN_PIXELS} + height={TILE_SIZE_IN_PIXELS} + fill={isValid ? ROOM_HOVER_VALID_COLOR : ROOM_HOVER_INVALID_COLOR} + onClick={onClick} + /> + ) +} + +HoverTile.propTypes = { + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + isValid: PropTypes.bool.isRequired, + scale: PropTypes.number, + onClick: PropTypes.func.isRequired, +} + +export default HoverTile diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js index 7d304b6b..7d304b6b 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js index 8c573a6f..aa284944 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js @@ -16,7 +16,7 @@ import { } from '../MapConstants' import ImageComponent from './ImageComponent' -const RackFillBar = ({ positionX, positionY, type, fillFraction }) => { +function RackFillBar({ positionX, positionY, type, fillFraction }) { const halfOfObjectBorderWidth = OBJECT_BORDER_WIDTH_IN_PIXELS / 2 const x = positionX * TILE_SIZE_IN_PIXELS + diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js new file mode 100644 index 00000000..e7329dc0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { Tile } from '../../../../shapes' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function RoomTile({ tile, color }) { + return ( + <Rect + x={tile.positionX * TILE_SIZE_IN_PIXELS} + y={tile.positionY * TILE_SIZE_IN_PIXELS} + width={TILE_SIZE_IN_PIXELS} + height={TILE_SIZE_IN_PIXELS} + fill={color} + /> + ) +} + +RoomTile.propTypes = { + tile: Tile, + color: PropTypes.string, +} + +export default RoomTile diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js new file mode 100644 index 00000000..3211f187 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { OBJECT_BORDER_COLOR } from '../../../../util/colors' +import { OBJECT_BORDER_WIDTH_IN_PIXELS, OBJECT_MARGIN_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function TileObject({ positionX, positionY, color }) { + return ( + <Rect + x={positionX * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} + y={positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} + width={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} + height={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} + fill={color} + stroke={OBJECT_BORDER_COLOR} + strokeWidth={OBJECT_BORDER_WIDTH_IN_PIXELS} + /> + ) +} + +TileObject.propTypes = { + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + color: PropTypes.string.isRequired, +} + +export default TileObject diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js new file mode 100644 index 00000000..186c2b3a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js @@ -0,0 +1,44 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group, Line } from 'react-konva' +import { TILE_PLUS_COLOR } from '../../../../util/colors' +import { TILE_PLUS_MARGIN_IN_PIXELS, TILE_PLUS_WIDTH_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function TilePlusIcon({ x, y, scale = 1 }) { + const linePoints = [ + [ + x + 0.5 * TILE_SIZE_IN_PIXELS * scale, + y + TILE_PLUS_MARGIN_IN_PIXELS * scale, + x + 0.5 * TILE_SIZE_IN_PIXELS * scale, + y + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, + ], + [ + x + TILE_PLUS_MARGIN_IN_PIXELS * scale, + y + 0.5 * TILE_SIZE_IN_PIXELS * scale, + x + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, + y + 0.5 * TILE_SIZE_IN_PIXELS * scale, + ], + ] + return ( + <Group> + {linePoints.map((points, index) => ( + <Line + key={index} + points={points} + lineCap="round" + stroke={TILE_PLUS_COLOR} + strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * scale} + listening={false} + /> + ))} + </Group> + ) +} + +TilePlusIcon.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + scale: PropTypes.number, +} + +export default TilePlusIcon diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js index ad6412c3..4f18813e 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js @@ -4,7 +4,7 @@ import { WallSegment as WallSegmentShape } from '../../../../shapes' import { WALL_COLOR } from '../../../../util/colors' import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from '../MapConstants' -const WallSegment = ({ wallSegment }) => { +function WallSegment({ wallSegment }) { let points if (wallSegment.isHorizontal) { points = [ diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js index ebc00244..d66a18de 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js @@ -17,18 +17,20 @@ const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ MAP_SIZE_IN_PIXELS, ]) -const GridGroup = () => ( - <Group> - {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => ( - <Line - key={index} - points={points} - stroke={GRID_COLOR} - strokeWidth={GRID_LINE_WIDTH_IN_PIXELS} - listening={false} - /> - ))} - </Group> -) +function GridGroup() { + return ( + <Group> + {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => ( + <Line + key={index} + points={points} + stroke={GRID_COLOR} + strokeWidth={GRID_LINE_WIDTH_IN_PIXELS} + listening={false} + /> + ))} + </Group> + ) +} export default GridGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js index 9c4abc4a..46030135 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js @@ -6,7 +6,7 @@ import TileObject from '../elements/TileObject' import RackSpaceFillContainer from '../RackSpaceFillContainer' import RackEnergyFillContainer from '../RackEnergyFillContainer' -const RackGroup = ({ tile }) => { +function RackGroup({ tile }) { return ( <Group> <TileObject positionX={tile.positionX} positionY={tile.positionY} color={RACK_BACKGROUND_COLOR} /> diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js index a14f3676..a42e7bb7 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js @@ -6,7 +6,7 @@ import GrayContainer from '../GrayContainer' import TileContainer from '../TileContainer' import WallContainer from '../WallContainer' -const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick }) => { +function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick }) { if (currentRoomInConstruction === room._id) { return ( <Group onClick={onClick}> diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js index cd36c7e5..f2084017 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js @@ -6,7 +6,7 @@ import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util import RoomTile from '../elements/RoomTile' import RackContainer from '../RackContainer' -const TileGroup = ({ tile, newTile, onClick }) => { +function TileGroup({ tile, newTile, onClick }) { let tileObject if (tile.rack) { tileObject = <RackContainer tile={tile} /> diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js index d3bcb279..011dcf34 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js @@ -4,7 +4,7 @@ import { InteractionLevel, Topology } from '../../../../shapes' import RoomContainer from '../RoomContainer' import GrayContainer from '../GrayContainer' -const TopologyGroup = ({ topology, interactionLevel }) => { +function TopologyGroup({ topology, interactionLevel }) { if (!topology) { return <Group /> } diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js index c73a95a7..6cbd1cd0 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js @@ -5,7 +5,7 @@ import { Tile } from '../../../../shapes' import { deriveWallLocations } from '../../../../util/tile-calculations' import WallSegment from '../elements/WallSegment' -const WallGroup = ({ tiles }) => { +function WallGroup({ tiles }) { return ( <Group> {deriveWallLocations(tiles).map((wallSegment, index) => ( diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js new file mode 100644 index 00000000..2b1060c0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js @@ -0,0 +1,55 @@ +import PropTypes from 'prop-types' +import React, { useMemo, useState } from 'react' +import { Layer } from 'react-konva/lib/ReactKonva' +import HoverTile from '../elements/HoverTile' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' +import { useEffectRef } from '../../../../util/effect-ref' + +function HoverLayerComponent({ isEnabled, isValid, onClick, children }) { + const [[mouseWorldX, mouseWorldY], setPos] = useState([0, 0]) + + const layerRef = useEffectRef((layer) => { + if (!layer) { + return + } + + const stage = layer.getStage() + + // Transform used to convert mouse coordinates to world coordinates + const transform = stage.getAbsoluteTransform().copy() + transform.invert() + + stage.on('mousemove.hover', () => { + const { x, y } = transform.point(stage.getPointerPosition()) + setPos([x, y]) + }) + return () => stage.off('mousemove.hover') + }) + + const gridX = Math.floor(mouseWorldX / TILE_SIZE_IN_PIXELS) + const gridY = Math.floor(mouseWorldY / TILE_SIZE_IN_PIXELS) + const valid = useMemo(() => isEnabled && isValid(gridX, gridY), [isEnabled, isValid, gridX, gridY]) + + if (!isEnabled) { + return <Layer /> + } + + const x = gridX * TILE_SIZE_IN_PIXELS + const y = gridY * TILE_SIZE_IN_PIXELS + + return ( + <Layer opacity={0.6} ref={layerRef}> + <HoverTile x={x} y={y} isValid={valid} onClick={() => (valid ? onClick(gridX, gridY) : undefined)} /> + {children ? React.cloneElement(children, { x, y, scale: 1 }) : undefined} + </Layer> + ) +} + +HoverLayerComponent.propTypes = { + isEnabled: PropTypes.bool.isRequired, + isValid: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + children: PropTypes.node, +} + +export default HoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js index badb9f68..c902532b 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js @@ -21,13 +21,21 @@ */ import React from 'react' -import MapLayerComponent from '../../../../components/app/map/layers/MapLayerComponent' -import { useMapPosition, useMapScale } from '../../../../data/map' +import { Group, Layer } from 'react-konva' +import Backdrop from '../elements/Backdrop' +import TopologyContainer from '../TopologyContainer' +import GridGroup from '../groups/GridGroup' -const MapLayer = (props) => { - const position = useMapPosition() - const scale = useMapScale() - return <MapLayerComponent {...props} mapPosition={position} mapScale={scale} /> +function MapLayer() { + return ( + <Layer> + <Group> + <Backdrop /> + <TopologyContainer /> + <GridGroup /> + </Group> + </Layer> + ) } export default MapLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js index 9a087bd5..47d9c992 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js @@ -23,32 +23,31 @@ import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { addRackToTile } from '../../../../redux/actions/topology/room' -import ObjectHoverLayerComponent from '../../../../components/app/map/layers/ObjectHoverLayerComponent' import { findTileWithPosition } from '../../../../util/tile-calculations' +import HoverLayerComponent from './HoverLayerComponent' +import TilePlusIcon from '../elements/TilePlusIcon' -const ObjectHoverLayer = (props) => { - const state = useSelector((state) => { - return { - mapPosition: state.map.position, - mapScale: state.map.scale, - isEnabled: () => state.construction.inRackConstructionMode, - isValid: (x, y) => { - if (state.interactionLevel.mode !== 'ROOM') { - return false - } +function ObjectHoverLayer() { + const isEnabled = useSelector((state) => state.construction.inRackConstructionMode) + const isValid = useSelector((state) => (x, y) => { + if (state.interactionLevel.mode !== 'ROOM') { + return false + } - const currentRoom = state.objects.room[state.interactionLevel.roomId] - const tiles = currentRoom.tiles.map((tileId) => state.objects.tile[tileId]) - const tile = findTileWithPosition(tiles, x, y) + const currentRoom = state.objects.room[state.interactionLevel.roomId] + const tiles = currentRoom.tiles.map((tileId) => state.objects.tile[tileId]) + const tile = findTileWithPosition(tiles, x, y) - return !(tile === null || tile.rack) - }, - } + return !(tile === null || tile.rack) }) const dispatch = useDispatch() const onClick = (x, y) => dispatch(addRackToTile(x, y)) - return <ObjectHoverLayerComponent {...props} {...state} onClick={onClick} /> + return ( + <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid}> + <TilePlusIcon /> + </HoverLayerComponent> + ) } export default ObjectHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js index 87240813..59f83b2b 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js @@ -23,45 +23,39 @@ import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { toggleTileAtLocation } from '../../../../redux/actions/topology/building' -import RoomHoverLayerComponent from '../../../../components/app/map/layers/RoomHoverLayerComponent' import { deriveValidNextTilePositions, findPositionInPositions, findPositionInRooms, } from '../../../../util/tile-calculations' +import HoverLayerComponent from './HoverLayerComponent' -const RoomHoverLayer = (props) => { +function RoomHoverLayer() { const dispatch = useDispatch() const onClick = (x, y) => dispatch(toggleTileAtLocation(x, y)) + const isEnabled = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + const isValid = useSelector((state) => (x, y) => { + const newRoom = { ...state.objects.room[state.construction.currentRoomInConstruction] } + const oldRooms = Object.keys(state.objects.room) + .map((id) => ({ ...state.objects.room[id] })) + .filter( + (room) => + state.objects.topology[state.currentTopologyId].rooms.indexOf(room._id) !== -1 && + room._id !== state.construction.currentRoomInConstruction + ) - const state = useSelector((state) => { - return { - mapPosition: state.map.position, - mapScale: state.map.scale, - isEnabled: () => state.construction.currentRoomInConstruction !== '-1', - isValid: (x, y) => { - const newRoom = Object.assign({}, state.objects.room[state.construction.currentRoomInConstruction]) - const oldRooms = Object.keys(state.objects.room) - .map((id) => Object.assign({}, state.objects.room[id])) - .filter( - (room) => - state.objects.topology[state.currentTopologyId].rooms.indexOf(room._id) !== -1 && - room._id !== state.construction.currentRoomInConstruction - ) - - ;[...oldRooms, newRoom].forEach((room) => { - room.tiles = room.tiles.map((tileId) => state.objects.tile[tileId]) - }) - if (newRoom.tiles.length === 0) { - return findPositionInRooms(oldRooms, x, y) === -1 - } - - const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles) - return findPositionInPositions(validNextPositions, x, y) !== -1 - }, + ;[...oldRooms, newRoom].forEach((room) => { + room.tiles = room.tiles.map((tileId) => state.objects.tile[tileId]) + }) + if (newRoom.tiles.length === 0) { + return findPositionInRooms(oldRooms, x, y) === -1 } + + const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles) + return findPositionInPositions(validNextPositions, x, y) !== -1 }) - return <RoomHoverLayerComponent onClick={onClick} {...props} {...state} /> + + return <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid} /> } export default RoomHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js index ececd07b..ececd07b 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js index c4a880b1..5d9340b2 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import React from 'react' -import { InteractionLevel } from '../../../../shapes' -import BuildingSidebarComponent from './building/BuildingSidebarComponent' +import { InteractionLevel } from '../../../shapes' +import BuildingSidebar from './building/BuildingSidebar' import { Button, DrawerActions, @@ -14,11 +14,11 @@ import { } from '@patternfly/react-core' import { AngleLeftIcon } from '@patternfly/react-icons' import { useDispatch } from 'react-redux' -import { goDownOneInteractionLevel } from '../../../../redux/actions/interaction-level' import { backButton } from './TopologySidebar.module.scss' -import RoomSidebarContainer from './room/RoomSidebarContainer' -import RackSidebarContainer from './rack/RackSidebarContainer' -import MachineSidebarContainer from './machine/MachineSidebarContainer' +import RoomSidebar from './room/RoomSidebar' +import RackSidebar from './rack/RackSidebar' +import MachineSidebar from './machine/MachineSidebar' +import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' const name = { BUILDING: 'Building', @@ -27,21 +27,21 @@ const name = { MACHINE: 'Machine', } -const TopologySidebar = ({ interactionLevel, onClose }) => { +function TopologySidebar({ interactionLevel, onClose }) { let sidebarContent switch (interactionLevel.mode) { case 'BUILDING': - sidebarContent = <BuildingSidebarComponent /> + sidebarContent = <BuildingSidebar /> break case 'ROOM': - sidebarContent = <RoomSidebarContainer /> + sidebarContent = <RoomSidebar roomId={interactionLevel.roomId} /> break case 'RACK': - sidebarContent = <RackSidebarContainer /> + sidebarContent = <RackSidebar tileId={interactionLevel.tileId} /> break case 'MACHINE': - sidebarContent = <MachineSidebarContainer /> + sidebarContent = <MachineSidebar tileId={interactionLevel.tileId} position={interactionLevel.position} /> break default: sidebarContent = 'Missing Content' diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss index 45dc98da..45dc98da 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.module.scss +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js index 6c2556d3..5fcd46be 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js @@ -1,8 +1,8 @@ import React from 'react' import NewRoomConstructionContainer from './NewRoomConstructionContainer' -const BuildingSidebarComponent = () => { +function BuildingSidebar() { return <NewRoomConstructionContainer /> } -export default BuildingSidebarComponent +export default BuildingSidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js index 656b2515..9fc85d0c 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js @@ -4,7 +4,7 @@ import { Button, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from '@pat import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' -const NewRoomConstructionComponent = ({ onStart, onFinish, onCancel, currentRoomInConstruction }) => { +function NewRoomConstructionComponent({ onStart, onFinish, onCancel, currentRoomInConstruction }) { if (currentRoomInConstruction === '-1') { return ( <Button isBlock icon={<PlusIcon />} onClick={onStart}> diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js index 0836263c..c149b224 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js @@ -26,20 +26,20 @@ import { cancelNewRoomConstruction, finishNewRoomConstruction, startNewRoomConstruction, -} from '../../../../../redux/actions/topology/building' +} from '../../../../redux/actions/topology/building' import NewRoomConstructionComponent from './NewRoomConstructionComponent' -const NewRoomConstructionButton = (props) => { +function NewRoomConstructionButton() { const currentRoomInConstruction = useSelector((state) => state.construction.currentRoomInConstruction) - const dispatch = useDispatch() - const actions = { - onStart: () => dispatch(startNewRoomConstruction()), - onFinish: () => dispatch(finishNewRoomConstruction()), - onCancel: () => dispatch(cancelNewRoomConstruction()), - } + return ( - <NewRoomConstructionComponent {...props} {...actions} currentRoomInConstruction={currentRoomInConstruction} /> + <NewRoomConstructionComponent + onStart={() => dispatch(startNewRoomConstruction())} + onFinish={() => dispatch(finishNewRoomConstruction())} + onCancel={() => dispatch(cancelNewRoomConstruction())} + currentRoomInConstruction={currentRoomInConstruction} + /> ) } diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachine.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js index a7bf3719..00ce4603 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachine.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js @@ -21,18 +21,20 @@ */ import React, { useState } from 'react' -import { useDispatch } from 'react-redux' -import { deleteMachine } from '../../../../../redux/actions/topology/machine' +import { useDispatch, useSelector } from 'react-redux' import { Button } from '@patternfly/react-core' -import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' -import ConfirmationModal from '../../../../modals/ConfirmationModal' +import { TrashIcon } from '@patternfly/react-icons' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteMachine } from '../../../../redux/actions/topology/machine' -const DeleteMachine = () => { +function DeleteMachine() { const dispatch = useDispatch() const [isVisible, setVisible] = useState(false) + const rackId = useSelector((state) => state.objects.tile[state.interactionLevel.tileId].rack) + const position = useSelector((state) => state.interactionLevel.position) const callback = (isConfirmed) => { if (isConfirmed) { - dispatch(deleteMachine()) + dispatch(deleteMachine(rackId, position)) } setVisible(false) } diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js index d3d4a8cf..0c3dea98 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js @@ -12,8 +12,12 @@ import { } from '@patternfly/react-core' import { useSelector } from 'react-redux' -const MachineSidebarComponent = ({ machineId }) => { - const machine = useSelector((state) => state.objects.machine[machineId]) +function MachineSidebar({ tileId, position }) { + const machine = useSelector(({ objects }) => { + const rack = objects.rack[objects.tile[tileId].rack] + return objects.machine[rack.machines[position - 1]] + }) + const machineId = machine._id return ( <div> <TextContent> @@ -31,14 +35,15 @@ const MachineSidebarComponent = ({ machineId }) => { <Title headingLevel="h2">Units</Title> </TextContent> <div className="pf-u-h-100"> - <UnitTabsComponent /> + <UnitTabsComponent machineId={machineId} /> </div> </div> ) } -MachineSidebarComponent.propTypes = { - machineId: PropTypes.string, +MachineSidebar.propTypes = { + tileId: PropTypes.string.isRequired, + position: PropTypes.number.isRequired, } -export default MachineSidebarComponent +export default MachineSidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js index 88591208..88591208 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js index 8a6680e6..fc805b95 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js @@ -23,19 +23,20 @@ import PropTypes from 'prop-types' import React from 'react' import { useDispatch, useSelector } from 'react-redux' -import { addUnit } from '../../../../../redux/actions/topology/machine' import UnitAddComponent from './UnitAddComponent' +import { addUnit } from '../../../../redux/actions/topology/machine' -const UnitAddContainer = ({ unitType }) => { +function UnitAddContainer({ machineId, unitType }) { const units = useSelector((state) => Object.values(state.objects[unitType])) const dispatch = useDispatch() - const onAdd = (id) => dispatch(addUnit(unitType, id)) + const onAdd = (id) => dispatch(addUnit(machineId, unitType, id)) return <UnitAddComponent onAdd={onAdd} units={units} /> } UnitAddContainer.propTypes = { + machineId: PropTypes.string.isRequired, unitType: PropTypes.string.isRequired, } diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js index 9c3c08fd..daa3e7a7 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js @@ -1,6 +1,5 @@ import PropTypes from 'prop-types' import React from 'react' -import { ProcessingUnit, StorageUnit } from '../../../../../shapes' import { Button, DataList, @@ -20,8 +19,9 @@ import { Title, } from '@patternfly/react-core' import { CubesIcon, InfoIcon, TrashIcon } from '@patternfly/react-icons' +import { ProcessingUnit, StorageUnit } from '../../../../shapes' -const UnitInfo = ({ unit, unitType }) => { +function UnitInfo({ unit, unitType }) { if (unitType === 'cpu' || unitType === 'gpu') { return ( <DescriptionList> @@ -64,7 +64,7 @@ UnitInfo.propTypes = { unit: PropTypes.oneOfType([ProcessingUnit, StorageUnit]).isRequired, } -const UnitListComponent = ({ unitType, units, onDelete }) => { +function UnitListComponent({ unitType, units, onDelete }) { if (units.length === 0) { return ( <EmptyState> diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js index 2d994f97..901fa45b 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js @@ -24,7 +24,7 @@ import PropTypes from 'prop-types' import React from 'react' import { useDispatch, useSelector } from 'react-redux' import UnitListComponent from './UnitListComponent' -import { deleteUnit } from '../../../../../redux/actions/topology/machine' +import { deleteUnit } from '../../../../redux/actions/topology/machine' const unitMapping = { cpu: 'cpus', @@ -33,23 +33,20 @@ const unitMapping = { storage: 'storages', } -const UnitListContainer = ({ unitType, ...props }) => { +function UnitListContainer({ machineId, unitType }) { const dispatch = useDispatch() const units = useSelector((state) => { - const machine = - state.objects.machine[ - state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rack].machines[ - state.interactionLevel.position - 1 - ] - ] + const machine = state.objects.machine[machineId] return machine[unitMapping[unitType]].map((id) => state.objects[unitType][id]) }) - const onDelete = (unit, unitType) => dispatch(deleteUnit(unitType, unit._id)) - return <UnitListComponent {...props} units={units} unitType={unitType} onDelete={onDelete} /> + const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit._id)) + + return <UnitListComponent units={units} unitType={unitType} onDelete={onDelete} /> } UnitListContainer.propTypes = { + machineId: PropTypes.string.isRequired, unitType: PropTypes.string.isRequired, } diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js index 723ed2e2..6d10d2df 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js @@ -1,31 +1,36 @@ +import PropTypes from 'prop-types' import React, { useState } from 'react' import { Tab, Tabs, TabTitleText } from '@patternfly/react-core' import UnitAddContainer from './UnitAddContainer' import UnitListContainer from './UnitListContainer' -const UnitTabsComponent = () => { +function UnitTabsComponent({ machineId }) { const [activeTab, setActiveTab] = useState('cpu-units') return ( <Tabs activeKey={activeTab} onSelect={(_, tab) => setActiveTab(tab)}> <Tab eventKey="cpu-units" title={<TabTitleText>CPU</TabTitleText>}> - <UnitAddContainer unitType="cpu" /> - <UnitListContainer unitType="cpu" /> + <UnitAddContainer machineId={machineId} unitType="cpu" /> + <UnitListContainer machineId={machineId} unitType="cpu" /> </Tab> <Tab eventKey="gpu-units" title={<TabTitleText>GPU</TabTitleText>}> - <UnitAddContainer unitType="gpu" /> - <UnitListContainer unitType="gpu" /> + <UnitAddContainer machineId={machineId} unitType="gpu" /> + <UnitListContainer machineId={machineId} unitType="gpu" /> </Tab> <Tab eventKey="memory-units" title={<TabTitleText>Memory</TabTitleText>}> - <UnitAddContainer unitType="memory" /> - <UnitListContainer unitType="memory" /> + <UnitAddContainer machineId={machineId} unitType="memory" /> + <UnitListContainer machineId={machineId} unitType="memory" /> </Tab> <Tab eventKey="storage-units" title={<TabTitleText>Storage</TabTitleText>}> - <UnitAddContainer unitType="storage" /> - <UnitListContainer unitType="storage" /> + <UnitAddContainer machineId={machineId} unitType="storage" /> + <UnitListContainer machineId={machineId} unitType="storage" /> </Tab> </Tabs> ) } +UnitTabsComponent.propTypes = { + machineId: PropTypes.string.isRequired, +} + export default UnitTabsComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js index d3d9aaf5..e944c2e8 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js @@ -20,14 +20,25 @@ * SOFTWARE. */ +import PropTypes from 'prop-types' import React from 'react' import { useDispatch } from 'react-redux' -import { addPrefab } from '../../../../../redux/actions/prefabs' -import AddPrefabComponent from './AddPrefabComponent' +import { Button } from '@patternfly/react-core' +import { SaveIcon } from '@patternfly/react-icons' +import { addPrefab } from '../../../../api/prefabs' -const AddPrefabContainer = (props) => { +function AddPrefab({ tileId }) { const dispatch = useDispatch() - return <AddPrefabComponent {...props} onClick={() => dispatch(addPrefab('name'))} /> + const onClick = () => dispatch(addPrefab('name', tileId)) + return ( + <Button variant="primary" icon={<SaveIcon />} isBlock onClick={onClick} className="pf-u-mb-sm"> + Save this rack to a prefab + </Button> + ) } -export default AddPrefabContainer +AddPrefab.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default AddPrefab diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js index 47959f03..80c6349a 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js @@ -20,19 +20,20 @@ * SOFTWARE. */ +import PropTypes from 'prop-types' import React, { useState } from 'react' import { useDispatch } from 'react-redux' -import ConfirmationModal from '../../../../../components/modals/ConfirmationModal' -import { deleteRack } from '../../../../../redux/actions/topology/rack' import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' import { Button } from '@patternfly/react-core' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteRack } from '../../../../redux/actions/topology/rack' -const DeleteRackContainer = () => { +function DeleteRackContainer({ tileId }) { const dispatch = useDispatch() const [isVisible, setVisible] = useState(false) const callback = (isConfirmed) => { if (isConfirmed) { - dispatch(deleteRack()) + dispatch(deleteRack(tileId)) } setVisible(false) } @@ -51,4 +52,8 @@ const DeleteRackContainer = () => { ) } +DeleteRackContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + export default DeleteRackContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js index 1617b3bf..921622d6 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types' import React from 'react' import Image from 'next/image' -import { Machine } from '../../../../../shapes' import { Flex, Label } from '@patternfly/react-core' +import { Machine } from '../../../../shapes' const UnitIcon = ({ id, type }) => ( <Image @@ -19,7 +19,7 @@ UnitIcon.propTypes = { type: PropTypes.string, } -const MachineComponent = ({ machine, onClick }) => { +function MachineComponent({ machine, onClick }) { const hasNoUnits = machine.cpus.length + machine.gpus.length + machine.memories.length + machine.storages.length === 0 diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js index 27834cf4..de7a2140 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js @@ -1,7 +1,6 @@ import PropTypes from 'prop-types' import React from 'react' import MachineComponent from './MachineComponent' -import { Machine } from '../../../../../shapes' import { Badge, Button, @@ -13,6 +12,7 @@ import { DataListItemRow, } from '@patternfly/react-core' import { AngleRightIcon, PlusIcon } from '@patternfly/react-icons' +import { Machine } from '../../../../shapes' function MachineListComponent({ machines = [], onSelect, onAdd }) { return ( diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js index 54e6db0a..6fbff949 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js @@ -24,8 +24,8 @@ import PropTypes from 'prop-types' import React, { useMemo } from 'react' import { useDispatch, useSelector } from 'react-redux' import MachineListComponent from './MachineListComponent' -import { goFromRackToMachine } from '../../../../../redux/actions/interaction-level' -import { addMachine } from '../../../../../redux/actions/topology/rack' +import { goFromRackToMachine } from '../../../../redux/actions/interaction-level' +import { addMachine } from '../../../../redux/actions/topology/rack' function MachineListContainer({ tileId, ...props }) { const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) @@ -43,7 +43,7 @@ function MachineListContainer({ tileId, ...props }) { <MachineListComponent {...props} machines={machinesNull} - onAdd={(index) => dispatch(addMachine(index))} + onAdd={(index) => dispatch(addMachine(rack._id, index))} onSelect={(index) => dispatch(goFromRackToMachine(index))} /> ) diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js index 11529b55..09d73af7 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js @@ -2,14 +2,14 @@ import PropTypes from 'prop-types' import React from 'react' import { useDispatch, useSelector } from 'react-redux' import NameComponent from '../NameComponent' -import { editRackName } from '../../../../../redux/actions/topology/rack' +import { editRackName } from '../../../../redux/actions/topology/rack' const RackNameContainer = ({ tileId }) => { - const rackName = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack].name) + const { name: rackName, _id } = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) const dispatch = useDispatch() const callback = (name) => { if (name) { - dispatch(editRackName(name)) + dispatch(editRackName(_id, name)) } } return <NameComponent name={rackName} onEdit={callback} /> diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js index dd5117f7..3c9f152a 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types' import React from 'react' -import { machineListContainer, sidebarContainer } from './RackSidebarComponent.module.scss' +import { machineListContainer, sidebarContainer } from './RackSidebar.module.scss' import RackNameContainer from './RackNameContainer' -import AddPrefabContainer from './AddPrefabContainer' +import AddPrefab from './AddPrefab' import DeleteRackContainer from './DeleteRackContainer' import MachineListContainer from './MachineListContainer' import { @@ -16,7 +16,7 @@ import { } from '@patternfly/react-core' import { useSelector } from 'react-redux' -function RackSidebarComponent({ tileId }) { +function RackSidebar({ tileId }) { const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) return ( @@ -39,8 +39,8 @@ function RackSidebarComponent({ tileId }) { </TextListItem> </TextList> <Title headingLevel="h2">Actions</Title> - <AddPrefabContainer /> - <DeleteRackContainer /> + <AddPrefab tileId={tileId} /> + <DeleteRackContainer tileId={tileId} /> <Title headingLevel="h2">Slots</Title> </TextContent> @@ -51,8 +51,8 @@ function RackSidebarComponent({ tileId }) { ) } -RackSidebarComponent.propTypes = { +RackSidebar.propTypes = { tileId: PropTypes.string.isRequired, } -export default RackSidebarComponent +export default RackSidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss index 6f258aec..6f258aec 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js index 284c4d53..29b8f78a 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js @@ -20,19 +20,20 @@ * SOFTWARE. */ +import PropTypes from 'prop-types' import React, { useState } from 'react' import { useDispatch } from 'react-redux' -import ConfirmationModal from '../../../../../components/modals/ConfirmationModal' -import { deleteRoom } from '../../../../../redux/actions/topology/room' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteRoom } from '../../../../redux/actions/topology/room' import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' import { Button } from '@patternfly/react-core' -const DeleteRoomContainer = () => { +function DeleteRoomContainer({ roomId }) { const dispatch = useDispatch() const [isVisible, setVisible] = useState(false) const callback = (isConfirmed) => { if (isConfirmed) { - dispatch(deleteRoom()) + dispatch(deleteRoom(roomId)) } setVisible(false) } @@ -51,4 +52,8 @@ const DeleteRoomContainer = () => { ) } +DeleteRoomContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + export default DeleteRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js index 6db2bfb6..7a278cd6 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js @@ -20,19 +20,20 @@ * SOFTWARE. */ +import PropTypes from 'prop-types' import React from 'react' import { useDispatch, useSelector } from 'react-redux' -import { finishRoomEdit, startRoomEdit } from '../../../../../redux/actions/topology/building' +import { finishRoomEdit, startRoomEdit } from '../../../../redux/actions/topology/building' import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' import PencilAltIcon from '@patternfly/react-icons/dist/js/icons/pencil-alt-icon' import { Button } from '@patternfly/react-core' -const EditRoomContainer = () => { +function EditRoomContainer({ roomId }) { const isEditing = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') const isInRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) const dispatch = useDispatch() - const onEdit = () => dispatch(startRoomEdit()) + const onEdit = () => dispatch(startRoomEdit(roomId)) const onFinish = () => dispatch(finishRoomEdit()) return isEditing ? ( @@ -53,4 +54,8 @@ const EditRoomContainer = () => { ) } +EditRoomContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + export default EditRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js index 8aebe969..a384d5d5 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js @@ -1,13 +1,12 @@ import PropTypes from 'prop-types' import React from 'react' import { Button } from '@patternfly/react-core' -import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' -import TimesIcon from '@patternfly/react-icons/dist/js/icons/times-icon' +import { PlusIcon, TimesIcon } from '@patternfly/react-icons' const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => { if (inRackConstructionMode) { return ( - <Button isBlock={true} icon={<TimesIcon />} onClick={onStop}> + <Button isBlock={true} icon={<TimesIcon />} onClick={onStop} className="pf-u-mb-sm"> Stop rack construction </Button> ) diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js index 38af447a..e04287a5 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js @@ -22,10 +22,10 @@ import React from 'react' import { useDispatch, useSelector } from 'react-redux' -import { startRackConstruction, stopRackConstruction } from '../../../../../redux/actions/topology/room' +import { startRackConstruction, stopRackConstruction } from '../../../../redux/actions/topology/room' import RackConstructionComponent from './RackConstructionComponent' -const RackConstructionContainer = (props) => { +function RackConstructionContainer(props) { const isRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) const isEditingRoom = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js index d7b006a6..e8d8b33c 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js @@ -23,15 +23,15 @@ import PropTypes from 'prop-types' import React from 'react' import { useDispatch, useSelector } from 'react-redux' -import NameComponent from '../../../../../components/app/sidebars/topology/NameComponent' -import { editRoomName } from '../../../../../redux/actions/topology/room' +import NameComponent from '../NameComponent' +import { editRoomName } from '../../../../redux/actions/topology/room' function RoomName({ roomId }) { - const roomName = useSelector((state) => state.objects.room[roomId].name) + const { name: roomName, _id } = useSelector((state) => state.objects.room[roomId]) const dispatch = useDispatch() const callback = (name) => { if (name) { - dispatch(editRoomName(name)) + dispatch(editRoomName(_id, name)) } } return <NameComponent name={roomName} onEdit={callback} /> diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js index fac58c51..6ad489e0 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js @@ -13,7 +13,7 @@ import { Title, } from '@patternfly/react-core' -const RoomSidebarComponent = ({ roomId }) => { +const RoomSidebar = ({ roomId }) => { return ( <TextContent> <Title headingLevel="h2">Details</Title> @@ -30,14 +30,14 @@ const RoomSidebarComponent = ({ roomId }) => { </TextList> <Title headingLevel="h2">Construction</Title> <RackConstructionContainer /> - <EditRoomContainer /> - <DeleteRoomContainer /> + <EditRoomContainer roomId={roomId} /> + <DeleteRoomContainer roomId={roomId} /> </TextContent> ) } -RoomSidebarComponent.propTypes = { +RoomSidebar.propTypes = { roomId: PropTypes.string.isRequired, } -export default RoomSidebarComponent +export default RoomSidebar diff --git a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js index f6e1c98b..f6e1c98b 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js +++ b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js diff --git a/opendc-web/opendc-web-ui/src/components/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js index d4577062..d4577062 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/Modal.js +++ b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js diff --git a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js index 392a729e..392a729e 100644 --- a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js +++ b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js |
