import React, { useState, useRef, useEffect, useMemo } from "react";
|
import { useTranslate } from "react-admin";
|
import {
|
TextField,
|
Select,
|
MenuItem,
|
Button,
|
Box,
|
SpeedDial,
|
SpeedDialAction,
|
useTheme,
|
Snackbar,
|
} from '@mui/material';
|
import {
|
MoreVert as MoreVertIcon,
|
Edit as EditIcon,
|
FileCopy as FileCopyIcon,
|
Save as SaveIcon,
|
Print as PrintIcon,
|
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'
|
import ConfirmButton from "../page/components/ConfirmButton";
|
|
let player;
|
let websocket;
|
|
const Map = () => {
|
const notify = useNotification();
|
const translate = useTranslate();
|
const theme = useTheme();
|
const themeMode = theme.palette.mode;
|
|
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 = useRef();
|
|
const [curZone, setCurZone] = useState(() => {
|
const storedValue = localStorage.getItem('curZone');
|
return storedValue !== null ? JSON.parse(storedValue) : null;
|
});
|
|
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();
|
|
// resize
|
const handleResize = () => {
|
const width = contentRef.current.offsetWidth;
|
const height = contentRef.current.offsetHeight;
|
|
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);
|
Tool.patchRaLayout('');
|
};
|
}, [])
|
|
useEffect(() => {
|
player.setTheme(themeMode);
|
Tool.setThemeMode(themeMode);
|
}, [themeMode])
|
|
const switchMode = (mode) => {
|
Tool.removeSelectedEffect();
|
|
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: '复制' },
|
{ icon: <SaveIcon />, name: '保存' },
|
{ icon: <PrintIcon />, name: '打印' },
|
{ icon: <ShareIcon />, name: '分享' },
|
{ icon: <EditIcon />, name: '编辑' },
|
];
|
|
return (
|
<Box
|
sx={{
|
height: '100%',
|
display: 'flex',
|
flexDirection: 'column',
|
}}
|
>
|
{/* header */}
|
<Box
|
sx={{
|
display: 'flex',
|
alignItems: 'center',
|
backgroundColor: '#f5f5f5',
|
color: '#000',
|
padding: '0 16px',
|
height: '64px',
|
flexShrink: 0, // keep height
|
zIndex: 200,
|
}}
|
>
|
<TextField
|
variant="outlined"
|
size="small"
|
placeholder="搜索..."
|
sx={{
|
width: '200px',
|
backgroundColor: '#fff',
|
borderRadius: 1,
|
}}
|
/>
|
<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 && (
|
<>
|
<ConfirmButton
|
label="page.map.action.save"
|
variant="contained"
|
onConfirm={() => {
|
Http.saveMapData(curZone, mapContainer);
|
}}
|
/>
|
</>
|
)}
|
|
<Select
|
value={mode}
|
onChange={(event) => {
|
setMode(event.target.value);
|
}}
|
variant="outlined"
|
size="small"
|
sx={{
|
ml: 2,
|
backgroundColor: '#fff',
|
borderRadius: 1,
|
}}
|
>
|
<MenuItem value={MapMode.OBSERVER_MODE}>监控模式</MenuItem>
|
<MenuItem value={MapMode.MOVABLE_MODE}>编辑模式</MenuItem>
|
<MenuItem value={MapMode.SETTINGS_MODE}>配置模式</MenuItem>
|
</Select>
|
</Box>
|
{/* content */}
|
<Box
|
sx={{
|
flexGrow: 1, // fill remaining of map space
|
position: 'relative',
|
backgroundColor: '#fff',
|
}}
|
>
|
<Box
|
ref={contentRef}
|
sx={{
|
position: 'relative',
|
width: '100%',
|
height: '100%',
|
backgroundColor: '#e0e0e0',
|
overflowY: 'hidden',
|
}}
|
>
|
<div ref={mapRef} style={{
|
position: 'absolute',
|
top: 0,
|
left: 0,
|
width: '100%',
|
height: '100%',
|
}} />
|
</Box>
|
|
<SpeedDial
|
ariaLabel="SpeedDial 示例"
|
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
icon={<MoreVertIcon />}
|
>
|
{actions.map((action) => (
|
<SpeedDialAction
|
key={action.name}
|
icon={action.icon}
|
tooltipTitle={action.name}
|
/>
|
))}
|
</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;
|