|  |  |  | 
|---|
|  |  |  | 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 notify = useNotification(); | 
|---|
|  |  |  | const translate = useTranslate(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const mapRef = useRef(); | 
|---|
|  |  |  | const contentRef = useRef(); | 
|---|
|  |  |  | const [app, setApp] = useState(null); | 
|---|
|  |  |  | const [mapContainer, setMapContainer] = useState(null); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const [mode, setMode] = useState(MapMode.OBSERVER_MODE); | 
|---|
|  |  |  | const [deviceVisible, setDeviceVisible] = React.useState(false); | 
|---|
|  |  |  | const [mode, setMode] = useState(MAP_MODE.OBSERVER_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 [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'); | 
|---|
|  |  |  | 
|---|
|  |  |  | setMapContainer(player.mapContainer); | 
|---|
|  |  |  | Tool.setApp(player.app); | 
|---|
|  |  |  | Tool.setMapContainer(player.mapContainer); | 
|---|
|  |  |  | Tool.setThemeMode(themeMode); | 
|---|
|  |  |  | Http.setNotify(notify); | 
|---|
|  |  |  | Http.setMapContainer(player.mapContainer); | 
|---|
|  |  |  | websocket = new WebSocketClient('/ws/map/websocket'); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | await Http.fetchMapData(0); | 
|---|
|  |  |  | await Http.fetchMapData(curZone, setRcsStatus, setCurSprite); | 
|---|
|  |  |  | websocket.connect(); | 
|---|
|  |  |  | websocket.onMessage = (wsMsg) => { | 
|---|
|  |  |  | 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) { | 
|---|
|  |  |  | websocket.onMessage = () => { } | 
|---|
|  |  |  | websocket.close(); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | player.destroy(); | 
|---|
|  |  |  | window.removeEventListener('resize', handleResize); | 
|---|
|  |  |  | Tool.patchRaLayout(''); | 
|---|
|  |  |  | Tool.isF | 
|---|
|  |  |  | }; | 
|---|
|  |  |  | }, []) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | useEffect(() => { | 
|---|
|  |  |  | setTimeout(handleResize, 300) | 
|---|
|  |  |  | }, [sidebarOpen]); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | useEffect(() => { | 
|---|
|  |  |  | player.setTheme(themeMode); | 
|---|
|  |  |  | Tool.setThemeMode(themeMode); | 
|---|
|  |  |  | }, [themeMode]) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const switchMode = (mode) => { | 
|---|
|  |  |  | switch (mode) { | 
|---|
|  |  |  | case MapMode.OBSERVER_MODE: | 
|---|
|  |  |  | setDeviceVisible(false); | 
|---|
|  |  |  | player.hideGridLines(); | 
|---|
|  |  |  | Tool.removeSelectedEffect(); | 
|---|
|  |  |  | player.hideGridLines(); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | setInsightVisible(false); | 
|---|
|  |  |  | setDeviceVisible(false); | 
|---|
|  |  |  | setSettingsVisible(false); | 
|---|
|  |  |  | setBatchSelectionVisible(false); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | setCurSprite(null); | 
|---|
|  |  |  | setBatchSprites([]); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | switch (mode) { | 
|---|
|  |  |  | 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(); | 
|---|
|  |  |  | setDeviceVisible(false); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 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, setCurSprite); | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | break | 
|---|
|  |  |  | default: | 
|---|
|  |  |  | break | 
|---|
|  |  |  | 
|---|
|  |  |  | return | 
|---|
|  |  |  | } | 
|---|
|  |  |  | switchMode(mode); | 
|---|
|  |  |  | }, [mode]); | 
|---|
|  |  |  | }, [mode, mapContainer]); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const onDrop = (sprite, type, x, y) => { | 
|---|
|  |  |  | console.log(sprite, type, x, y); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const { mapX, mapY } = Tool.getRealPosition(x, y); | 
|---|
|  |  |  | sprite.x = mapX; | 
|---|
|  |  |  | sprite.y = mapY; | 
|---|
|  |  |  | 
|---|
|  |  |  | Tool.beMovable(sprite); | 
|---|
|  |  |  | }; | 
|---|
|  |  |  |  | 
|---|
|  |  |  | const actions = [ | 
|---|
|  |  |  | { icon: <FileCopyIcon />, name: '复制' }, | 
|---|
|  |  |  | { icon: <SaveIcon />, name: '保存' }, | 
|---|
|  |  |  | { icon: <PrintIcon />, name: '打印' }, | 
|---|
|  |  |  | { icon: <ShareIcon />, name: '分享' }, | 
|---|
|  |  |  | { icon: <EditIcon />, name: '编辑' }, | 
|---|
|  |  |  | ]; | 
|---|
|  |  |  | // watch curSprite | 
|---|
|  |  |  | React.useEffect(() => { | 
|---|
|  |  |  | if (!mapContainer) { | 
|---|
|  |  |  | return; | 
|---|
|  |  |  | } | 
|---|
|  |  |  | 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); | 
|---|
|  |  |  | } | 
|---|
|  |  |  | }, [curSprite]); | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 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={() => { | 
|---|
|  |  |  | setCurSprite(null); | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | sprite={curSprite} | 
|---|
|  |  |  | width={570} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Device | 
|---|
|  |  |  | open={deviceVisible} | 
|---|
|  |  |  | 
|---|
|  |  |  | }} | 
|---|
|  |  |  | onDrop={onDrop} | 
|---|
|  |  |  | width={378} | 
|---|
|  |  |  | /> | 
|---|
|  |  |  |  | 
|---|
|  |  |  | <Settings | 
|---|
|  |  |  | open={settingsVisible} | 
|---|
|  |  |  | onCancel={() => { | 
|---|
|  |  |  | 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; | 
|---|