summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/components/app/map/layers
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-16 17:37:01 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-16 17:37:01 +0200
commitf2aeecccc096728d3df955b71e711c8d9c429427 (patch)
tree14494ef902f054a38f93af29976be81f8d5dba75 /opendc-web/opendc-web-ui/src/components/app/map/layers
parente5caf6c6122684e441d1d73e2e0507fdd36c67e0 (diff)
refactor(ui): Isolate world coordinate space
This change updates the topology view in the OpenDC frontend to isolate the world coordinate space. This means that zooming and panning should not affect the coordinates in world space (but only in camera space). In turn, this allows us to remove the dependency on Redux for the camera controls.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/components/app/map/layers')
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js67
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js20
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js26
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js35
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js11
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js50
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js6
7 files changed, 82 insertions, 133 deletions
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js
index a88a8b34..2b1060c0 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js
@@ -1,61 +1,52 @@
import PropTypes from 'prop-types'
-import React, { useEffect, useState } from 'react'
-import { Layer } from 'react-konva'
+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({ mouseX, mouseY, mapPosition, mapScale, isEnabled, isValid, onClick, children }) {
- const [pos, setPos] = useState([-1, -1])
- const [x, y] = pos
- const [valid, setValid] = useState(false)
+function HoverLayerComponent({ isEnabled, isValid, onClick, children }) {
+ const [[mouseWorldX, mouseWorldY], setPos] = useState([0, 0])
- useEffect(() => {
- if (!isEnabled()) {
+ const layerRef = useEffectRef((layer) => {
+ if (!layer) {
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))
+ const stage = layer.getStage()
- if (positionX !== x || positionY !== y) {
- setPos([positionX, positionY])
- setValid(isValid(positionX, positionY))
- }
- }, [isEnabled, isValid, x, y, mouseX, mouseY, mapPosition, mapScale])
+ // 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()) {
+ if (!isEnabled) {
return <Layer />
}
- const pixelX = mapScale * x * TILE_SIZE_IN_PIXELS + mapPosition.x
- const pixelY = mapScale * y * TILE_SIZE_IN_PIXELS + mapPosition.y
+ const x = gridX * TILE_SIZE_IN_PIXELS
+ const y = gridY * TILE_SIZE_IN_PIXELS
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 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 = {
- mouseX: PropTypes.number.isRequired,
- mouseY: PropTypes.number.isRequired,
- mapPosition: PropTypes.object.isRequired,
- mapScale: PropTypes.number.isRequired,
- isEnabled: PropTypes.func.isRequired,
+ isEnabled: PropTypes.bool.isRequired,
isValid: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired,
children: PropTypes.node,
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/components/app/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/app/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/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/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/components/app/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/app/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/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/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/app/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/app/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/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