| | |
| | | import React, { useState, useRef, useEffect, useMemo } from "react"; |
| | | import { useTranslate } from "react-admin"; |
| | | import { |
| | | TextField, |
| | | Select, |
| | |
| | | SpeedDial, |
| | | SpeedDialAction, |
| | | useTheme, |
| | | Snackbar, |
| | | } from '@mui/material'; |
| | | import { |
| | | MoreVert as MoreVertIcon, |
| | |
| | | Share as ShareIcon, |
| | | } from '@mui/icons-material'; |
| | | import Player from './player'; |
| | | import * as Tool from './tool'; |
| | | import { NotificationProvider, useNotification } from './Notification'; |
| | | import Insight from "./insight"; |
| | | import Device from "./Device"; |
| | | import Settings from "./settings"; |
| | | import * as Http from './http'; |
| | | import WebSocketClient from './websocket' |
| | | |
| | | let player; |
| | | let websocket; |
| | | |
| | | const MapPage = () => { |
| | | const mapRef = useRef(); |
| | | const contentRef = React.useRef(); |
| | | const [app, setApp] = useState(null); |
| | | const [mapContainer, setMapContainer] = React.useState(null); |
| | | |
| | | const [mode, setMode] = useState('monitoring'); |
| | | |
| | | const Map = () => { |
| | | const notify = useNotification(); |
| | | const translate = useTranslate(); |
| | | const theme = useTheme(); |
| | | const themeMode = theme.palette.mode; |
| | | |
| | | useEffect(() => { |
| | | const parentElement = document.getElementById('main-content'); |
| | | if (parentElement && parentElement.classList.contains('RaLayout-content')) { |
| | | parentElement.style.paddingLeft = '0px'; |
| | | parentElement.style.paddingRight = '0px'; |
| | | } |
| | | const mapRef = useRef(); |
| | | const contentRef = useRef(); |
| | | const [app, setApp] = useState(null); |
| | | const [mapContainer, setMapContainer] = useState(null); |
| | | |
| | | const [mode, setMode] = useState(MapMode.OBSERVER_MODE); |
| | | const [insightVisible, setInsightVisible] = useState(false); |
| | | const [deviceVisible, setDeviceVisible] = useState(false); |
| | | const [settingsVisible, setSettingsVisible] = useState(false); |
| | | |
| | | const [spriteSettings, setSpriteSettings] = useState(null); |
| | | const prevSpriteSettingsRef = React.useRef(); |
| | | |
| | | useEffect(() => { |
| | | Tool.patchRaLayout('0px'); |
| | | const initialize = async () => { |
| | | player = new Player(mapRef.current, themeMode); |
| | | setApp(player.app); |
| | | setMapContainer(player.mapContainer); |
| | | Tool.setApp(player.app); |
| | | Tool.setMapContainer(player.mapContainer); |
| | | Tool.setThemeMode(themeMode); |
| | | Http.setNotify(notify); |
| | | // websocket = new WebSocketClient('/ws/map/websocket'); |
| | | |
| | | await Http.fetchMapData(0); |
| | | // websocket.connect(); |
| | | // websocket.onMessage = (data) => { |
| | | // Tool.updateMapStatusInRealTime(data, () => curFloorRef.current, setCurSPrite); |
| | | // } |
| | | |
| | | } |
| | | initialize(); |
| | |
| | | |
| | | player.resize(width, height); |
| | | }; |
| | | handleResize(); |
| | | window.addEventListener('resize', handleResize); |
| | | |
| | | notify.info('Welcome to Rcs'); |
| | | |
| | | return () => { |
| | | if (websocket) { |
| | | websocket.onMessage = () => { } |
| | | websocket.close(); |
| | | } |
| | | player.destroy(); |
| | | window.removeEventListener('resize', handleResize); |
| | | |
| | | if (parentElement && parentElement.classList.contains('RaLayout-content')) { |
| | | parentElement.style.paddingLeft = ''; |
| | | parentElement.style.paddingRight = ''; |
| | | } |
| | | Tool.patchRaLayout(''); |
| | | }; |
| | | }, [themeMode]) |
| | | |
| | | const switchMode = (mode) => { |
| | | Tool.removeSelectedEffect(); |
| | | |
| | | const handleModeChange = (event) => { |
| | | setMode(event.target.value); |
| | | setDeviceVisible(false); |
| | | setSettingsVisible(false); |
| | | |
| | | setSpriteSettings(null); |
| | | |
| | | switch (mode) { |
| | | case MapMode.OBSERVER_MODE: |
| | | player.hideGridLines(); |
| | | player.hideStarryBackground(); |
| | | |
| | | player.activateMapMultiSelect((selectedSprites, restartFn) => { |
| | | console.log(selectedSprites); |
| | | }); |
| | | |
| | | break |
| | | case MapMode.MOVABLE_MODE: |
| | | player.showGridLines(); |
| | | player.hideStarryBackground(); |
| | | |
| | | player.activateMapMultiSelect((selectedSprites, restartFn) => { |
| | | Tool.spriteListBeMovable(selectedSprites, () => { |
| | | restartFn(); |
| | | }); |
| | | }); |
| | | break |
| | | case MapMode.SETTINGS_MODE: |
| | | player.hideGridLines(); |
| | | player.showStarryBackground(); // 0x2f68ac |
| | | |
| | | player.activateMapMultiSelect((selectedSprites, restartFn) => { |
| | | console.log(selectedSprites); |
| | | }); |
| | | |
| | | mapContainer.children.forEach(child => { |
| | | Tool.beSettings(child, setSpriteSettings); |
| | | }) |
| | | break |
| | | default: |
| | | break |
| | | } |
| | | } |
| | | |
| | | useEffect(() => { |
| | | if (!mapContainer) { |
| | | return |
| | | } |
| | | switchMode(mode); |
| | | }, [mode, mapContainer]); |
| | | |
| | | const onDrop = (sprite, type, x, y) => { |
| | | const { mapX, mapY } = Tool.getRealPosition(x, y); |
| | | sprite.x = mapX; |
| | | sprite.y = mapY; |
| | | |
| | | Tool.initSprite(sprite, type); |
| | | mapContainer.addChild(sprite); |
| | | Tool.beMovable(sprite); |
| | | }; |
| | | |
| | | // watch spriteSettings |
| | | useEffect(() => { |
| | | if (!mapContainer) { |
| | | return; |
| | | } |
| | | prevSpriteSettingsRef.current = spriteSettings; |
| | | if (spriteSettings && prevSpriteSettings !== spriteSettings) { |
| | | Tool.removeSelectedEffect(); |
| | | } |
| | | if (spriteSettings) { |
| | | Tool.showSelectedEffect(spriteSettings) |
| | | setSettingsVisible(true); |
| | | } else { |
| | | Tool.removeSelectedEffect(); |
| | | } |
| | | }, [spriteSettings, mapContainer]) |
| | | const prevSpriteSettings = prevSpriteSettingsRef.current; |
| | | |
| | | const actions = [ |
| | | { icon: <FileCopyIcon />, name: '复制' }, |
| | |
| | | flexDirection: 'column', |
| | | }} |
| | | > |
| | | {/* header */} |
| | | <Box |
| | | sx={{ |
| | | display: 'flex', |
| | |
| | | padding: '0 16px', |
| | | height: '64px', |
| | | flexShrink: 0, // keep height |
| | | zIndex: 200 |
| | | }} |
| | | > |
| | | <TextField |
| | |
| | | }} |
| | | /> |
| | | <Box sx={{ flexGrow: 1 }} /> |
| | | |
| | | {mode === MapMode.OBSERVER_MODE && ( |
| | | <> |
| | | <Button |
| | | variant="contained" |
| | | color="primary" |
| | | sx={{ mr: 1 }} |
| | | > |
| | | 停止RCS运转 |
| | | </Button> |
| | | <Button variant="contained" color="secondary"> |
| | | 模拟AGV运行 |
| | | </Button> |
| | | </> |
| | | )} |
| | | |
| | | {mode === MapMode.MOVABLE_MODE && ( |
| | | <> |
| | | <Button |
| | | variant="contained" |
| | | color="primary" |
| | | sx={{ mr: 1 }} |
| | | onClick={() => setDeviceVisible(!deviceVisible)} |
| | | > |
| | | {translate('page.map.devices.title')} |
| | | </Button> |
| | | </> |
| | | )} |
| | | |
| | | {mode === MapMode.SETTINGS_MODE && ( |
| | | <> |
| | | </> |
| | | )} |
| | | |
| | | <Select |
| | | value={mode} |
| | | onChange={handleModeChange} |
| | | onChange={(event) => { |
| | | setMode(event.target.value); |
| | | }} |
| | | variant="outlined" |
| | | size="small" |
| | | sx={{ |
| | | mr: 2, |
| | | ml: 2, |
| | | backgroundColor: '#fff', |
| | | borderRadius: 1, |
| | | }} |
| | | > |
| | | <MenuItem value="monitoring">监控模式</MenuItem> |
| | | <MenuItem value="edit">编辑模式</MenuItem> |
| | | <MenuItem value="configuration">配置模式</MenuItem> |
| | | <MenuItem value={MapMode.OBSERVER_MODE}>监控模式</MenuItem> |
| | | <MenuItem value={MapMode.MOVABLE_MODE}>编辑模式</MenuItem> |
| | | <MenuItem value={MapMode.SETTINGS_MODE}>配置模式</MenuItem> |
| | | </Select> |
| | | <Button |
| | | variant="contained" |
| | | color="primary" |
| | | sx={{ mr: 1 }} |
| | | > |
| | | 停止RCS运转 |
| | | </Button> |
| | | <Button variant="contained" color="secondary"> |
| | | 模拟AGV运行 |
| | | </Button> |
| | | </Box> |
| | | {/* content */} |
| | | <Box |
| | | sx={{ |
| | | flexGrow: 1, // fill remaining of map space |
| | |
| | | width: '100%', |
| | | height: '100%', |
| | | backgroundColor: '#e0e0e0', |
| | | overflowY: 'hidden', |
| | | }} |
| | | > |
| | | <div ref={mapRef} style={{ |
| | |
| | | ))} |
| | | </SpeedDial> |
| | | </Box> |
| | | |
| | | <Insight |
| | | open={insightVisible} |
| | | onCancel={() => { |
| | | setInsightVisible(false); |
| | | }} |
| | | width={378} |
| | | /> |
| | | |
| | | <Device |
| | | open={deviceVisible} |
| | | onCancel={() => { |
| | | setDeviceVisible(false); |
| | | }} |
| | | onDrop={onDrop} |
| | | width={378} |
| | | /> |
| | | |
| | | <Settings |
| | | open={settingsVisible} |
| | | onCancel={() => { |
| | | setSpriteSettings(null); |
| | | setSettingsVisible(false); |
| | | }} |
| | | sprite={spriteSettings} |
| | | setSpriteSettings={setSpriteSettings} |
| | | width={570} |
| | | /> |
| | | |
| | | </Box> |
| | | ); |
| | | } |
| | | |
| | | const MapPage = () => { |
| | | return ( |
| | | <NotificationProvider> |
| | | <Map /> |
| | | </NotificationProvider> |
| | | ) |
| | | } |
| | | |
| | | export const MapMode = Object.freeze({ |
| | | OBSERVER_MODE: "1", |
| | | MOVABLE_MODE: "2", |
| | | SETTINGS_MODE: "3", |
| | | }) |
| | | |
| | | export default MapPage; |