|  |  | 
 |  |  | import React, { useState, useRef, useEffect, useMemo } from "react"; | 
 |  |  | import { useTranslate } from "react-admin"; | 
 |  |  | import { useTranslate, useRefresh, useSidebarState } from "react-admin"; | 
 |  |  | import { | 
 |  |  |     TextField, | 
 |  |  |     Select, | 
 |  |  |     MenuItem, | 
 |  |  |     Button, | 
 |  |  |     Box, | 
 |  |  |     SpeedDial, | 
 |  |  |     SpeedDialAction, | 
 |  |  |     useTheme, | 
 |  |  |     Snackbar, | 
 |  |  |     Fab, | 
 |  |  | } 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 { MAP_MODE } from "./constants"; | 
 |  |  | 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 Batch from "./batch"; | 
 |  |  | import * as Http from './http'; | 
 |  |  | import WebSocketClient from './websocket' | 
 |  |  | import ConfirmButton from "../page/components/ConfirmButton"; | 
 |  |  | import { FitScreen, AltRoute, RotateRight } from '@mui/icons-material'; | 
 |  |  | import MapSearch from "./header/MapSearch"; | 
 |  |  | import { startupOrShutdown } from "./http"; | 
 |  |  | import PulseSignal from "../page/components/PulseSignal"; | 
 |  |  | import FakeFab from "./header/FakeFab"; | 
 |  |  |  | 
 |  |  | let player; | 
 |  |  | let websocket; | 
 |  |  |  | 
 |  |  | const Map = () => { | 
 |  |  |     const notify = useNotification(); | 
 |  |  |     const [sidebarOpen] = useSidebarState(); | 
 |  |  |     const translate = useTranslate(); | 
 |  |  |     const theme = useTheme(); | 
 |  |  |     const themeMode = theme.palette.mode; | 
 |  |  | 
 |  |  |     const [app, setApp] = useState(null); | 
 |  |  |     const [mapContainer, setMapContainer] = useState(null); | 
 |  |  |  | 
 |  |  |     const [mode, setMode] = useState(MapMode.OBSERVER_MODE); | 
 |  |  |     const [mode, setMode] = useState(MAP_MODE.OBSERVER_MODE); | 
 |  |  |     const modeRef = useRef(mode); | 
 |  |  |     const [dataFetched, setDataFetched] = useState(false); | 
 |  |  |     const [insightVisible, setInsightVisible] = useState(false); | 
 |  |  |     const [deviceVisible, setDeviceVisible] = useState(false); | 
 |  |  |     const [settingsVisible, setSettingsVisible] = useState(false); | 
 |  |  |     const [batchSelectionVisible, setBatchSelectionVisible] = useState(false); | 
 |  |  |  | 
 |  |  |     const [spriteSettings, setSpriteSettings] = useState(null); | 
 |  |  |     const prevSpriteSettingsRef = React.useRef(); | 
 |  |  |     const [curSprite, setCurSprite] = useState(null); | 
 |  |  |     const [batchSprites, setBatchSprites] = useState([]); | 
 |  |  |  | 
 |  |  |     const [rcsStatus, setRcsStatus] = useState(null); | 
 |  |  |     const [showRoutes, setShowRoutes] = useState(false); | 
 |  |  |     const [curZone, setCurZone] = useState(() => { | 
 |  |  |         const storedValue = localStorage.getItem('curZone'); | 
 |  |  |         return storedValue !== null ? JSON.parse(storedValue) : null; | 
 |  |  |     }); | 
 |  |  |  | 
 |  |  |     const handleResize = () => { | 
 |  |  |         if (!contentRef) { return; } | 
 |  |  |         const width = contentRef.current.offsetWidth; | 
 |  |  |         const height = contentRef.current.offsetHeight; | 
 |  |  |         player.resize(width, height); | 
 |  |  |     }; | 
 |  |  |  | 
 |  |  |     useEffect(() => { | 
 |  |  |         Tool.patchRaLayout('0px'); | 
 |  |  | 
 |  |  |             Tool.setMapContainer(player.mapContainer); | 
 |  |  |             Tool.setThemeMode(themeMode); | 
 |  |  |             Http.setNotify(notify); | 
 |  |  |             Http.setMapContainer(player.mapContainer); | 
 |  |  |             websocket = new WebSocketClient('/ws/map/websocket'); | 
 |  |  |  | 
 |  |  |             await Http.fetchMapData(0); | 
 |  |  |             // websocket.connect(); | 
 |  |  |             // websocket.onMessage = (data) => { | 
 |  |  |             //     Tool.updateMapStatusInRealTime(data, () => curFloorRef.current, setCurSPrite); | 
 |  |  |             // } | 
 |  |  |             await Http.fetchMapData(curZone, setRcsStatus, setCurSprite); | 
 |  |  |             websocket.connect(); | 
 |  |  |             websocket.onMessage = (wsMsg) => { | 
 |  |  |                 if (modeRef.current === MAP_MODE.OBSERVER_MODE) { | 
 |  |  |                     Tool.generateDynamicGraphic(curZone, JSON.parse(wsMsg), setCurSprite); | 
 |  |  |                 } | 
 |  |  |             } | 
 |  |  |  | 
 |  |  |             player.rotateMap(localStorage.getItem('mapRotation')); | 
 |  |  |             setTimeout(() => { | 
 |  |  |                 notify.info(translate('page.map.welcome')); | 
 |  |  |                 player.adaptScreen(); | 
 |  |  |                 setDataFetched(true); | 
 |  |  |             }, 200) | 
 |  |  |         } | 
 |  |  |         initialize(); | 
 |  |  |  | 
 |  |  |         // resize | 
 |  |  |         const handleResize = () => { | 
 |  |  |             const width = contentRef.current.offsetWidth; | 
 |  |  |             const height = contentRef.current.offsetHeight; | 
 |  |  |  | 
 |  |  |             player.resize(width, height); | 
 |  |  |         }; | 
 |  |  |         handleResize(); | 
 |  |  |         window.addEventListener('resize', handleResize); | 
 |  |  |  | 
 |  |  |         notify('Welcome to Rcs', 'info'); | 
 |  |  |  | 
 |  |  |         return () => { | 
 |  |  |             if (websocket) { | 
 |  |  | 
 |  |  |             window.removeEventListener('resize', handleResize); | 
 |  |  |             Tool.patchRaLayout(''); | 
 |  |  |         }; | 
 |  |  |     }, []) | 
 |  |  |  | 
 |  |  |     useEffect(() => { | 
 |  |  |         setTimeout(handleResize, 300) | 
 |  |  |     }, [sidebarOpen]); | 
 |  |  |  | 
 |  |  |     useEffect(() => { | 
 |  |  |         player.setTheme(themeMode); | 
 |  |  |         Tool.setThemeMode(themeMode); | 
 |  |  |     }, [themeMode]) | 
 |  |  |  | 
 |  |  |     const switchMode = (mode) => { | 
 |  |  |         Tool.removeSelectedEffect(); | 
 |  |  |         modeRef.current = mode; | 
 |  |  |  | 
 |  |  |         Tool.removeSelectedEffect(); | 
 |  |  |         player.hideGridLines(); | 
 |  |  |  | 
 |  |  |         setInsightVisible(false); | 
 |  |  |         setDeviceVisible(false); | 
 |  |  |         setSettingsVisible(false); | 
 |  |  |         setBatchSelectionVisible(false); | 
 |  |  |  | 
 |  |  |         setSpriteSettings(null); | 
 |  |  |         setCurSprite(null); | 
 |  |  |         setBatchSprites([]); | 
 |  |  |  | 
 |  |  |         switch (mode) { | 
 |  |  |             case MapMode.OBSERVER_MODE: | 
 |  |  |                 player.hideGridLines(); | 
 |  |  |  | 
 |  |  |             case MAP_MODE.OBSERVER_MODE: | 
 |  |  |                 player.activateMapMultiSelect((selectedSprites, restartFn) => { | 
 |  |  |                     console.log(selectedSprites); | 
 |  |  |                     Tool.multipleSelectEnhancer(selectedSprites, setCurSprite, setBatchSprites); | 
 |  |  |                 }); | 
 |  |  |  | 
 |  |  |                 mapContainer.children.forEach(child => { | 
 |  |  |                     Tool.beInsight(child, setCurSprite); | 
 |  |  |                 }) | 
 |  |  |                 break | 
 |  |  |             case MapMode.MOVABLE_MODE: | 
 |  |  |             case MAP_MODE.MOVABLE_MODE: | 
 |  |  |                 player.showGridLines(); | 
 |  |  |                 Tool.hideRoutes(curZone, setShowRoutes); | 
 |  |  |  | 
 |  |  |                 player.activateMapMultiSelect((selectedSprites, restartFn) => { | 
 |  |  |                     Tool.spriteListBeMovable(selectedSprites, () => { | 
 |  |  |                         restartFn(); | 
 |  |  |                     }); | 
 |  |  |                 }); | 
 |  |  |                 break | 
 |  |  |             case MapMode.SETTINGS_MODE: | 
 |  |  |                 player.hideGridLines(); | 
 |  |  |  | 
 |  |  |                 mapContainer.children.forEach(child => { | 
 |  |  |                     Tool.beMovable(child); | 
 |  |  |                 }) | 
 |  |  |                 break | 
 |  |  |             case MAP_MODE.SETTINGS_MODE: | 
 |  |  |                 player.activateMapMultiSelect((selectedSprites, restartFn) => { | 
 |  |  |                     console.log(selectedSprites); | 
 |  |  |                     Tool.multipleSelectEnhancer(selectedSprites, setCurSprite, setBatchSprites); | 
 |  |  |                 }); | 
 |  |  |  | 
 |  |  |                 mapContainer.children.forEach(child => { | 
 |  |  |                     Tool.beSettings(child, setSpriteSettings); | 
 |  |  |                     Tool.beSettings(child, setCurSprite); | 
 |  |  |                 }) | 
 |  |  |                 break | 
 |  |  |             default: | 
 |  |  | 
 |  |  |         sprite.x = mapX; | 
 |  |  |         sprite.y = mapY; | 
 |  |  |  | 
 |  |  |         // sprite.scale.set(mapContainer.scale.x); | 
 |  |  |         sprite.rotation = -mapContainer.rotation; | 
 |  |  |  | 
 |  |  |         Tool.initSprite(sprite, type); | 
 |  |  |         mapContainer.addChild(sprite); | 
 |  |  |         Tool.beMovable(sprite); | 
 |  |  |     }; | 
 |  |  |  | 
 |  |  |     // watch spriteSettings | 
 |  |  |     useEffect(() => { | 
 |  |  |     // watch curSprite | 
 |  |  |     React.useEffect(() => { | 
 |  |  |         if (!mapContainer) { | 
 |  |  |             return; | 
 |  |  |         } | 
 |  |  |         prevSpriteSettingsRef.current = spriteSettings; | 
 |  |  |         if (spriteSettings && prevSpriteSettings !== spriteSettings) { | 
 |  |  |             Tool.removeSelectedEffect(); | 
 |  |  |         } | 
 |  |  |         if (spriteSettings) { | 
 |  |  |             Tool.showSelectedEffect(spriteSettings) | 
 |  |  |             setSettingsVisible(true); | 
 |  |  |         Tool.removeSelectedEffect(); | 
 |  |  |         if (curSprite) { | 
 |  |  |             if (mode === MAP_MODE.OBSERVER_MODE) { | 
 |  |  |                 Tool.showSelectedEffect(curSprite); | 
 |  |  |                 setInsightVisible(true); | 
 |  |  |             } | 
 |  |  |             if (mode === MAP_MODE.SETTINGS_MODE) { | 
 |  |  |                 Tool.showSelectedEffect(curSprite); | 
 |  |  |                 setSettingsVisible(true); | 
 |  |  |             } | 
 |  |  |         } else { | 
 |  |  |             Tool.removeSelectedEffect(); | 
 |  |  |             setInsightVisible(false); | 
 |  |  |             setSettingsVisible(false); | 
 |  |  |         } | 
 |  |  |     }, [spriteSettings, mapContainer]) | 
 |  |  |     const prevSpriteSettings = prevSpriteSettingsRef.current; | 
 |  |  |     }, [curSprite]); | 
 |  |  |  | 
 |  |  |     const actions = [ | 
 |  |  |         { icon: <FileCopyIcon />, name: '复制' }, | 
 |  |  |         { icon: <SaveIcon />, name: '保存' }, | 
 |  |  |         { icon: <PrintIcon />, name: '打印' }, | 
 |  |  |         { icon: <ShareIcon />, name: '分享' }, | 
 |  |  |         { icon: <EditIcon />, name: '编辑' }, | 
 |  |  |     ]; | 
 |  |  |     // watch batchSprites | 
 |  |  |     React.useEffect(() => { | 
 |  |  |         if (!mapContainer) { | 
 |  |  |             return; | 
 |  |  |         } | 
 |  |  |         if (batchSprites?.length > 0) { | 
 |  |  |             setBatchSelectionVisible(true) | 
 |  |  |         } else { | 
 |  |  |             player.clearSelectedSprites(); | 
 |  |  |             setBatchSelectionVisible(false) | 
 |  |  |         } | 
 |  |  |     }, [batchSprites]) | 
 |  |  |  | 
 |  |  |     return ( | 
 |  |  |         <Box | 
 |  |  |             sx={{ | 
 |  |  |                 width: '100%', | 
 |  |  |                 height: '100%', | 
 |  |  |                 display: 'flex', | 
 |  |  |                 flexDirection: 'column', | 
 |  |  | 
 |  |  |                 sx={{ | 
 |  |  |                     display: 'flex', | 
 |  |  |                     alignItems: 'center', | 
 |  |  |                     backgroundColor: '#f5f5f5', | 
 |  |  |                     color: '#000', | 
 |  |  |                     backgroundColor: theme.palette.background.default, | 
 |  |  |                     color: theme.palette.text.primary, | 
 |  |  |                     padding: '0 16px', | 
 |  |  |                     height: '64px', | 
 |  |  |                     flexShrink: 0, // keep height | 
 |  |  |                     zIndex: 200 | 
 |  |  |                     zIndex: 200, | 
 |  |  |                     boxShadow: theme.shadows[1], | 
 |  |  |                 }} | 
 |  |  |             > | 
 |  |  |                 <TextField | 
 |  |  |                     variant="outlined" | 
 |  |  |                     size="small" | 
 |  |  |                     placeholder="搜索..." | 
 |  |  |                     sx={{ | 
 |  |  |                         width: '200px', | 
 |  |  |                         backgroundColor: '#fff', | 
 |  |  |                         borderRadius: 1, | 
 |  |  |                     }} | 
 |  |  |                 <MapSearch | 
 |  |  |                     mode={mode} | 
 |  |  |                     setMode={setMode} | 
 |  |  |                     dataFetched={dataFetched} | 
 |  |  |                     curZone={curZone} | 
 |  |  |                     curSprite={curSprite} | 
 |  |  |                     setCurSprite={setCurSprite} | 
 |  |  |                 /> | 
 |  |  |                 <Box sx={{ flexGrow: 1 }} /> | 
 |  |  |  | 
 |  |  |                 {mode === MapMode.OBSERVER_MODE && ( | 
 |  |  |                 {mode === MAP_MODE.OBSERVER_MODE && ( | 
 |  |  |                     <> | 
 |  |  |                         <Box sx={{ mr: 2, display: 'flex', alignItems: 'center' }}> | 
 |  |  |                             <PulseSignal | 
 |  |  |                                 negative | 
 |  |  |                                 negativeColor='#a4b0be' | 
 |  |  |                                 flag={rcsStatus} | 
 |  |  |                                 width={12} | 
 |  |  |                             /> | 
 |  |  |                         </Box> | 
 |  |  |                         <Button | 
 |  |  |                             variant="contained" | 
 |  |  |                             color="primary" | 
 |  |  |                             sx={{ mr: 1 }} | 
 |  |  |                             color={rcsStatus ? 'inherit' : 'primary'} | 
 |  |  |                             sx={{ mr: 2 }} | 
 |  |  |                             onClick={() => { | 
 |  |  |                                 startupOrShutdown(() => { | 
 |  |  |                                     setRcsStatus(!rcsStatus); | 
 |  |  |                                 }); | 
 |  |  |                             }} | 
 |  |  |                         > | 
 |  |  |                             停止RCS运转 | 
 |  |  |                             {rcsStatus ? translate('page.map.action.shutdown') : translate('page.map.action.startup')} | 
 |  |  |                         </Button> | 
 |  |  |                         <Button variant="contained" color="secondary"> | 
 |  |  |                             模拟AGV运行 | 
 |  |  |                         <Button variant="contained" color="primary"> | 
 |  |  |                             {translate('page.map.action.monitor')} | 
 |  |  |                         </Button> | 
 |  |  |                     </> | 
 |  |  |                 )} | 
 |  |  |  | 
 |  |  |                 {mode === MapMode.MOVABLE_MODE && ( | 
 |  |  |                 {mode === MAP_MODE.MOVABLE_MODE && ( | 
 |  |  |                     <> | 
 |  |  |                         <Button | 
 |  |  |                             variant="outlined" | 
 |  |  |                             sx={{ mr: 2 }} | 
 |  |  |                             onClick={() => { | 
 |  |  |                                 Tool.clearMapData(); | 
 |  |  |                             }} | 
 |  |  |                         > | 
 |  |  |                             {translate('page.map.action.clear')} | 
 |  |  |                         </Button> | 
 |  |  |                         <ConfirmButton | 
 |  |  |                             label="page.map.action.save" | 
 |  |  |                             variant="contained" | 
 |  |  |                             sx={{ mr: 2 }} | 
 |  |  |                             onConfirm={() => { | 
 |  |  |                                 Http.saveMapData(curZone); | 
 |  |  |                             }} | 
 |  |  |                         /> | 
 |  |  |                         <Button | 
 |  |  |                             variant="contained" | 
 |  |  |                             color="primary" | 
 |  |  |                             sx={{ mr: 1 }} | 
 |  |  |                             onClick={() => setDeviceVisible(!deviceVisible)} | 
 |  |  |                         > | 
 |  |  |                             {translate('page.map.devices.title')} | 
 |  |  | 
 |  |  |                     </> | 
 |  |  |                 )} | 
 |  |  |  | 
 |  |  |                 {mode === MapMode.SETTINGS_MODE && ( | 
 |  |  |                 {mode === MAP_MODE.SETTINGS_MODE && ( | 
 |  |  |                     <> | 
 |  |  |                         <Button | 
 |  |  |                             variant="outlined" | 
 |  |  |                             sx={{ mr: 2 }} | 
 |  |  |                             onClick={() => { | 
 |  |  |                                 Tool.clearMapData(); | 
 |  |  |                             }} | 
 |  |  |                         > | 
 |  |  |                             {translate('page.map.action.clear')} | 
 |  |  |                         </Button> | 
 |  |  |                         <ConfirmButton | 
 |  |  |                             label="page.map.action.save" | 
 |  |  |                             variant="contained" | 
 |  |  |                             onConfirm={() => { | 
 |  |  |                                 Http.saveMapData(curZone); | 
 |  |  |                             }} | 
 |  |  |                         /> | 
 |  |  |                     </> | 
 |  |  |                 )} | 
 |  |  |  | 
 |  |  |                 <Select | 
 |  |  |                     value={mode} | 
 |  |  |                     value={mode ?? ''} | 
 |  |  |                     onChange={(event) => { | 
 |  |  |                         setMode(event.target.value); | 
 |  |  |                     }} | 
 |  |  | 
 |  |  |                     size="small" | 
 |  |  |                     sx={{ | 
 |  |  |                         ml: 2, | 
 |  |  |                         backgroundColor: '#fff', | 
 |  |  |                         backgroundColor: theme.palette.background.paper, | 
 |  |  |                         color: theme.palette.text.primary, | 
 |  |  |                         borderRadius: 1, | 
 |  |  |                     }} | 
 |  |  |                 > | 
 |  |  |                     <MenuItem value={MapMode.OBSERVER_MODE}>监控模式</MenuItem> | 
 |  |  |                     <MenuItem value={MapMode.MOVABLE_MODE}>编辑模式</MenuItem> | 
 |  |  |                     <MenuItem value={MapMode.SETTINGS_MODE}>配置模式</MenuItem> | 
 |  |  |                     <MenuItem value={MAP_MODE.OBSERVER_MODE}>{translate('page.map.mode.observer')}</MenuItem> | 
 |  |  |                     <MenuItem value={MAP_MODE.MOVABLE_MODE}>{translate('page.map.mode.movable')}</MenuItem> | 
 |  |  |                     <MenuItem value={MAP_MODE.SETTINGS_MODE}>{translate('page.map.mode.settings')}</MenuItem> | 
 |  |  |                 </Select> | 
 |  |  |             </Box> | 
 |  |  |             {/* content */} | 
 |  |  | 
 |  |  |                     flexGrow: 1,    // fill remaining of map space  | 
 |  |  |                     position: 'relative', | 
 |  |  |                     backgroundColor: '#fff', | 
 |  |  |                     ...(mode === MAP_MODE.SETTINGS_MODE && { | 
 |  |  |                         animation: 'settingsPulse 1.5s infinite', | 
 |  |  |                         '@keyframes settingsPulse': { | 
 |  |  |                             '0%': { | 
 |  |  |                                 boxShadow: `0 0 3px 1px ${theme.palette.primary.main.replace('rgb', 'rgba').replace(')', `, 0.1)`)}`, | 
 |  |  |                             }, | 
 |  |  |                             '50%': { | 
 |  |  |                                 boxShadow: `0 0 8px 3px ${theme.palette.primary.main.replace('rgb', 'rgba').replace(')', `, 0.5)`)}`, | 
 |  |  |                             }, | 
 |  |  |                             '100%': { | 
 |  |  |                                 boxShadow: `0 0 3px 1px ${theme.palette.primary.main.replace('rgb', 'rgba').replace(')', `, 0.1)`)}`, | 
 |  |  |                             }, | 
 |  |  |                         }, | 
 |  |  |                     }) | 
 |  |  |                 }} | 
 |  |  |             > | 
 |  |  |                 <Box | 
 |  |  | 
 |  |  |                     }} /> | 
 |  |  |                 </Box> | 
 |  |  |  | 
 |  |  |                 <SpeedDial | 
 |  |  |                     ariaLabel="SpeedDial 示例" | 
 |  |  |                     sx={{ position: 'absolute', bottom: 16, right: 16 }} | 
 |  |  |                     icon={<MoreVertIcon />} | 
 |  |  |                 <Box | 
 |  |  |                     sx={{ | 
 |  |  |                         position: 'absolute', | 
 |  |  |                         left: 35, | 
 |  |  |                         bottom: 30, | 
 |  |  |                         display: 'flex', | 
 |  |  |                         flexDirection: 'column', | 
 |  |  |                         gap: 2 | 
 |  |  |                     }} | 
 |  |  |                 > | 
 |  |  |                     {actions.map((action) => ( | 
 |  |  |                         <SpeedDialAction | 
 |  |  |                             key={action.name} | 
 |  |  |                             icon={action.icon} | 
 |  |  |                             tooltipTitle={action.name} | 
 |  |  |                         /> | 
 |  |  |                     ))} | 
 |  |  |                 </SpeedDial> | 
 |  |  |                     {mode !== MAP_MODE.MOVABLE_MODE && ( | 
 |  |  |                         <> | 
 |  |  |                             <Fab | 
 |  |  |                                 variant="extended" | 
 |  |  |                                 color={showRoutes ? 'primary' : 'default'} | 
 |  |  |                                 size="small" | 
 |  |  |                                 onClick={() => { | 
 |  |  |                                     showRoutes ? Tool.hideRoutes(curZone, setShowRoutes) : Tool.showRoutes(curZone, setShowRoutes) | 
 |  |  |                                 }} | 
 |  |  |                             > | 
 |  |  |                                 <AltRoute /> | 
 |  |  |                             </Fab> | 
 |  |  |                             <FakeFab | 
 |  |  |                             /> | 
 |  |  |                         </> | 
 |  |  |                     )} | 
 |  |  |                     <Fab | 
 |  |  |                         variant="extended" | 
 |  |  |                         color="primary" | 
 |  |  |                         size="small" | 
 |  |  |                         onClick={() => { | 
 |  |  |                             player.rotateMap(Math.PI / 2); | 
 |  |  |                         }} | 
 |  |  |                     > | 
 |  |  |                         <RotateRight /> | 
 |  |  |                         {/*  {translate('page.map.action.adapt')} */} | 
 |  |  |                     </Fab> | 
 |  |  |                     <Fab | 
 |  |  |                         variant="extended" | 
 |  |  |                         color="primary" | 
 |  |  |                         size="small" | 
 |  |  |                         onClick={() => { | 
 |  |  |                             player.adaptScreen(); | 
 |  |  |                         }} | 
 |  |  |                     > | 
 |  |  |                         <FitScreen /> | 
 |  |  |                         {/*  {translate('page.map.action.adapt')} */} | 
 |  |  |                     </Fab> | 
 |  |  |                 </Box> | 
 |  |  |             </Box> | 
 |  |  |  | 
 |  |  |             <Insight | 
 |  |  |                 open={insightVisible} | 
 |  |  |                 onCancel={() => { | 
 |  |  |                     setInsightVisible(false); | 
 |  |  |                     setCurSprite(null); | 
 |  |  |                 }} | 
 |  |  |                 sprite={curSprite} | 
 |  |  |                 width={570} | 
 |  |  |             /> | 
 |  |  |  | 
 |  |  |             <Device | 
 |  |  | 
 |  |  |             <Settings | 
 |  |  |                 open={settingsVisible} | 
 |  |  |                 onCancel={() => { | 
 |  |  |                     setSettingsVisible(false); | 
 |  |  |                     setCurSprite(null); | 
 |  |  |                 }} | 
 |  |  |                 sprite={curSprite} | 
 |  |  |                 setSpriteSettings={setCurSprite} | 
 |  |  |                 width={570} | 
 |  |  |             /> | 
 |  |  |  | 
 |  |  |             <Batch | 
 |  |  |                 open={batchSelectionVisible} | 
 |  |  |                 onCancel={() => { | 
 |  |  |                     setBatchSprites([]); | 
 |  |  |                 }} | 
 |  |  |                 batchSprites={batchSprites} | 
 |  |  |                 mode={mode} | 
 |  |  |                 width={570} | 
 |  |  |             /> | 
 |  |  |  | 
 |  |  |         </Box> | 
 |  |  | 
 |  |  |         </NotificationProvider> | 
 |  |  |     ) | 
 |  |  | } | 
 |  |  |  | 
 |  |  | export const MapMode = Object.freeze({ | 
 |  |  |     OBSERVER_MODE: "1", | 
 |  |  |     MOVABLE_MODE: "2", | 
 |  |  |     SETTINGS_MODE: "3", | 
 |  |  | }) | 
 |  |  |  | 
 |  |  | export default MapPage; |