| import * as React from 'react' | 
| import * as PIXI from 'pixi.js'; | 
| import { FormattedMessage, useIntl, useModel } from '@umijs/max'; | 
| import { Layout, Button, Flex, Row, Col, FloatButton, Select, notification, Segmented } from 'antd'; | 
| const { Header, Content } = Layout; | 
| import { | 
|     AppstoreAddOutlined, | 
|     FileAddOutlined, | 
|     CompressOutlined, | 
|     SettingOutlined, | 
| } from '@ant-design/icons'; | 
| import './index.css' | 
| import { createStyles } from 'antd-style'; | 
| import Edit from './components/device'; | 
| import Settings from './components/settings' | 
| import * as Utils from './utils' | 
| import WebSocketClient from './websocket' | 
| import Player from './player'; | 
| import MapSearch from './header/search'; | 
| import MapFloor from './header/floor'; | 
| import MapDrawer from './drawer'; | 
| import BatchDrawer from './batch'; | 
|   | 
| const useStyles = createStyles(({ token }) => { | 
|     let dark = token.colorBgBase === '#000'; | 
|     return { | 
|         dark: dark, | 
|         layout: { | 
|             overflow: 'hidden', | 
|         }, | 
|         header: { | 
|             height: 64, | 
|             paddingInline: 48, | 
|             lineHeight: '64px', | 
|             padding: 0, | 
|         }, | 
|         flex: { | 
|             width: '100%', | 
|             height: '100%', | 
|             padding: '0 30px', | 
|         }, | 
|         content: { | 
|             backgroundColor: '#F8FAFB', | 
|             height: 'calc(100vh - 120px)', | 
|             position: 'relative' | 
|         }, | 
|         select: { | 
|             color: 'red', | 
|             fontWeight: 'bold', | 
|         }, | 
|         headerCol: { | 
|             paddingLeft: '50px' | 
|         } | 
|     }; | 
| }); | 
|   | 
| export const MapModel = Object.freeze({ | 
|     OBSERVER_MODEL: "1", | 
|     MOVABLE_MODEL: "2", | 
|     SETTINGS_MODEL: "3", | 
| }) | 
|   | 
| let player, websocket; | 
|   | 
| const Map = () => { | 
|     const intl = useIntl(); | 
|     const { initialState, setInitialState } = useModel('@@initialState'); | 
|     const [notify, contextHolder] = notification.useNotification(); | 
|     const { styles } = useStyles(); | 
|     const mapRef = React.useRef(); | 
|     const contentRef = React.useRef(); | 
|   | 
|     const [model, setModel] = React.useState(null); | 
|     const [deviceVisible, setDeviceVisible] = React.useState(false); | 
|     const [settingsVisible, setSettingsVisible] = React.useState(false); | 
|     const [windowSize, setWindowSize] = React.useState({ | 
|         width: window.innerWidth, | 
|         height: window.innerHeight, | 
|     }); | 
|     const [app, setApp] = React.useState(null); | 
|     const [mapContainer, setMapContainer] = React.useState(null); | 
|     const [spriteBySettings, setSpriteBySettings] = React.useState(null); | 
|     const prevSpriteBySettingsRef = React.useRef(); | 
|     const [drawerVisible, setDrawerVisible] = React.useState(false); | 
|     const [dataFetched, setDataFetched] = React.useState(false); | 
|     const [curSprite, setCurSPrite] = React.useState(null); | 
|     const prevCurSpriteRef = React.useRef(); | 
|     const [floorList, setFloorList] = React.useState([]); | 
|     const [curFloor, setCurFloor] = React.useState(() => { | 
|         const storedValue = localStorage.getItem('curFloor'); | 
|         return storedValue !== null ? JSON.parse(storedValue) : null; | 
|     }); | 
|     const [batchSprites, setBatchSprites] = React.useState([]); | 
|     const [batchDrawerVisible, setBatchDrawerVisible] = React.useState(false); | 
|   | 
|     // init func | 
|     React.useEffect(() => { | 
|         const initialize = async () => { | 
|             player = new Player(mapRef.current, styles.dark); | 
|             setApp(player.app); | 
|             setMapContainer(player.mapContainer); | 
|             Utils.syncApp(player.app); | 
|             Utils.syncMapContainer(player.mapContainer); | 
|             Utils.syncNotify(notify); | 
|   | 
|             websocket = new WebSocketClient('/ws/map/websocket'); | 
|             websocket.connect(); | 
|   | 
|             const handleResize = () => { | 
|                 setWindowSize({ | 
|                     width: window.innerWidth, | 
|                     height: window.innerHeight, | 
|                 }); | 
|             }; | 
|             window.addEventListener('resize', handleResize); | 
|   | 
|             const mapFloorData = await Utils.fetchMapFloor(); | 
|             setFloorList(mapFloorData); | 
|             let defaultFloor = curFloor || mapFloorData?.[0]?.value; | 
|             setCurFloor(defaultFloor); | 
|             await Utils.fetchMapData(defaultFloor); | 
|             setDataFetched(true); | 
|             setModel(MapModel.OBSERVER_MODEL) | 
|             setTimeout(() => { | 
|                 player.adaptScreen(); | 
|                 Utils.mapNotify(intl.formatMessage({ id: 'map.load.success', defaultMessage: '欢迎使用WCS系统' })); | 
|             }, 200) | 
|         } | 
|         initialize(); | 
|     }, []); | 
|   | 
|     // resize | 
|     React.useEffect(() => { | 
|         if (!app) { | 
|             return; | 
|         } | 
|         const width = contentRef.current.offsetWidth; | 
|         const height = contentRef.current.offsetHeight; | 
|         app.renderer.resize(width, height); | 
|         if (model !== MapModel.OBSERVER_MODEL) { | 
|             player.showGridlines(); | 
|         } | 
|     }, [app, mapContainer, windowSize]) | 
|   | 
|     // fn switch model | 
|     const switchModel = (model) => { | 
|         Utils.removeSelectedEffect(); | 
|   | 
|         setCurSPrite(null); | 
|         setDrawerVisible(false); | 
|         setSpriteBySettings(null); | 
|         setSettingsVisible(false); | 
|         setBatchSprites([]); | 
|         setBatchDrawerVisible(false); | 
|   | 
|         switch (model) { | 
|             case MapModel.OBSERVER_MODEL: | 
|                 player.hideGridlines(); | 
|                 player.hideStarryBackground(); | 
|   | 
|                 setDeviceVisible(false); | 
|                 player.activateMapEvent(Utils.MapEvent.SELECTION_BOX, (selectedSprites, resetFn) => { | 
|                     setBatchSprites(selectedSprites); | 
|                 }); | 
|   | 
|                 mapContainer.children.forEach(child => { | 
|                     Utils.viewFeature(child, setCurSPrite); | 
|                 }) | 
|                 break | 
|             case MapModel.MOVABLE_MODEL: | 
|                 player.showGridlines(); | 
|                 player.hideStarryBackground(); | 
|   | 
|                 player.activateMapEvent(Utils.MapEvent.SELECTION_BOX, (selectedSprites, resetFn) => { | 
|                     Utils.spriteListBeMovable(selectedSprites, () => { | 
|                         resetFn(); | 
|                     }); | 
|                 }); | 
|   | 
|                 mapContainer.children.forEach(child => { | 
|                     Utils.beMovable(child); | 
|                 }) | 
|                 break | 
|             case MapModel.SETTINGS_MODEL: | 
|                 player.showGridlines(); | 
|                 player.showStarryBackground(); | 
|   | 
|                 setDeviceVisible(false); | 
|                 player.activateMapEvent(Utils.MapEvent.SELECTION_BOX, (selectedSprites, resetFn) => { | 
|                     setBatchSprites(selectedSprites); | 
|                 }); | 
|   | 
|                 mapContainer.children.forEach(child => { | 
|                     Utils.beSettings(child, setSpriteBySettings); | 
|                 }) | 
|                 break | 
|             default: | 
|                 break | 
|         } | 
|     } | 
|   | 
|     // model | 
|     React.useEffect(() => { | 
|         if (!mapContainer && !dataFetched) { | 
|             return; | 
|         } | 
|         switchModel(model); | 
|     }, [model]); | 
|   | 
|     // Add New Device | 
|     const onDrop = (sprite, type, x, y) => { | 
|         const { mapX, mapY } = Utils.getRealPosition(x, y, mapContainer); | 
|         sprite.x = mapX; | 
|         sprite.y = mapY; | 
|   | 
|         Utils.initSprite(sprite, type); | 
|         mapContainer.addChild(sprite); | 
|         Utils.beMovable(sprite); | 
|     }; | 
|   | 
|     // watch curSprite | 
|     React.useEffect(() => { | 
|         if (!mapContainer) { | 
|             return; | 
|         } | 
|         prevCurSpriteRef.current = curSprite; | 
|         if (curSprite && prevCurSprite !== curSprite) { | 
|             Utils.removeSelectedEffect(); | 
|         } | 
|         if (curSprite) { | 
|             if (model === MapModel.OBSERVER_MODEL) { | 
|                 Utils.showSelectedEffect(curSprite) | 
|                 setDrawerVisible(true) | 
|             } | 
|         } else { | 
|             Utils.removeSelectedEffect(); | 
|         } | 
|     }, [curSprite]); | 
|     const prevCurSprite = prevCurSpriteRef.current; | 
|   | 
|     // fn switch floor | 
|     const switchFloor = async (floor) => { | 
|         await Utils.fetchMapData(floor); | 
|   | 
|         switchModel(model); | 
|   | 
|         setTimeout(() => { | 
|             player.adaptScreen(); | 
|         }, 200) | 
|     } | 
|   | 
|     // watch curFloor | 
|     React.useEffect(() => { | 
|         if (!mapContainer && !dataFetched) { | 
|             return; | 
|         } | 
|         switchFloor(curFloor); | 
|         localStorage.setItem('curFloor', JSON.stringify(curFloor)); | 
|     }, [curFloor]); | 
|   | 
|     // watch spriteBySettings | 
|     React.useEffect(() => { | 
|         if (!mapContainer) { | 
|             return; | 
|         } | 
|         prevSpriteBySettingsRef.current = spriteBySettings; | 
|         if (spriteBySettings && prevSpriteBySettings !== spriteBySettings) { | 
|             Utils.removeSelectedEffect(); | 
|         } | 
|         if (spriteBySettings) { | 
|             Utils.showSelectedEffect(spriteBySettings) | 
|             setSettingsVisible(true); | 
|         } else { | 
|             Utils.removeSelectedEffect(); | 
|         } | 
|     }, [spriteBySettings]) | 
|     const prevSpriteBySettings = prevSpriteBySettingsRef.current; | 
|   | 
|     // watch batchSprites | 
|     React.useEffect(() => { | 
|         if (!mapContainer) { | 
|             return; | 
|         } | 
|         if (batchSprites?.length > 0) { | 
|             setBatchDrawerVisible(true) | 
|         } else { | 
|             player.clearSelectedSprites(); | 
|             setBatchDrawerVisible(false) | 
|         } | 
|     }, [batchSprites]) | 
|   | 
|     return ( | 
|         <> | 
|             {contextHolder} | 
|             <Layout className={styles.layout}> | 
|                 <Header className={styles.header}> | 
|                     <Row style={{ height: '100%' }}> | 
|                         <Col className={styles.headerCol} span={12} style={{}}> | 
|                             {dataFetched && ( | 
|                                 <MapSearch | 
|                                     model={model} | 
|                                     setModel={setModel} | 
|                                     ModelEnum={MapModel} | 
|                                     curFloor={curFloor} | 
|                                     curSprite={curSprite} | 
|                                     setCurSPrite={setCurSPrite} | 
|                                     setSpriteBySettings={setSpriteBySettings} | 
|                                 /> | 
|                             )} | 
|                         </Col> | 
|                         <Col span={12} style={{ backgroundColor: styles.dark ? '#2f3542' : '#4a69bd' }}> | 
|                             <Flex className={styles.flex} gap={'large'} justify={'flex-end'} align={'center'}> | 
|   | 
|                                 {model === MapModel.OBSERVER_MODEL && ( | 
|                                     <> | 
|                                         <Button | 
|                                             className='map-header-button' | 
|                                             size={'large'} | 
|                                             onClick={() => { | 
|                                                 switchFloor(curFloor) | 
|                                             }} | 
|                                         > | 
|                                             <FormattedMessage id='map.load' defaultMessage='加载地图' /> | 
|                                         </Button> | 
|                                     </> | 
|                                 )} | 
|   | 
|                                 {model !== MapModel.OBSERVER_MODEL && ( | 
|                                     <> | 
|                                         <Button | 
|                                             className='map-header-button' | 
|                                             size={'large'} | 
|                                             onClick={() => { | 
|                                                 Utils.clearMapData(intl); | 
|                                             }} | 
|                                         > | 
|                                             <FormattedMessage id='map.clear' defaultMessage='清除地图' /> | 
|                                         </Button> | 
|                                         <Button | 
|                                             className='map-header-button' | 
|                                             size={'large'} | 
|                                             onClick={() => { | 
|                                                 Utils.saveMapData(intl, curFloor); | 
|                                             }} | 
|                                         > | 
|                                             <FormattedMessage id='map.save' defaultMessage='保存地图' /> | 
|                                         </Button> | 
|                                     </> | 
|                                 )} | 
|   | 
|                                 <Select | 
|                                     className='map-header-select' | 
|                                     size={'large'} | 
|                                     defaultValue={MapModel.OBSERVER_MODEL} | 
|                                     style={{ | 
|                                         width: 180, | 
|                                     }} | 
|                                     onChange={setModel} | 
|                                     options={[ | 
|                                         { | 
|                                             value: MapModel.OBSERVER_MODEL, | 
|                                             label: intl.formatMessage({ id: 'map.model.observer', defaultMessage: '观察者模式' }), | 
|                                         }, | 
|                                         { | 
|                                             value: MapModel.MOVABLE_MODEL, | 
|                                             label: intl.formatMessage({ id: 'map.model.editor', defaultMessage: '编辑者模式' }), | 
|                                         }, | 
|                                     ]} | 
|                                 /> | 
|                             </Flex> | 
|                         </Col> | 
|                     </Row> | 
|                 </Header> | 
|                 <Content ref={contentRef} className={styles.content}> | 
|                     <div ref={mapRef} /> | 
|   | 
|                     {floorList.length > 0 && ( | 
|                         <MapFloor | 
|                             floorList={floorList} | 
|                             curFloor={curFloor} | 
|                             setCurFloor={setCurFloor} | 
|                         /> | 
|                     )} | 
|   | 
|                     <FloatButton.Group | 
|                         shape="square" | 
|                         style={{ | 
|                             left: 35, | 
|                             bottom: 35 | 
|                         }} | 
|                     > | 
|                         <FloatButton | 
|                             icon={<CompressOutlined />} | 
|                             onClick={() => { | 
|                                 player.adaptScreen(); | 
|                             }} | 
|                         /> | 
|                     </FloatButton.Group> | 
|   | 
|                     <FloatButton.Group | 
|                         hidden={model === MapModel.OBSERVER_MODEL} | 
|                         style={{ | 
|                             left: 35, | 
|                             bottom: window.innerHeight / 2 | 
|                         }} | 
|                         icon={<AppstoreAddOutlined />} | 
|                     > | 
|                         <FloatButton | 
|                             hidden={model === MapModel.OBSERVER_MODEL} | 
|                             type={deviceVisible ? 'primary' : 'default'} | 
|                             tooltip={<div><FormattedMessage id='map.device.add' defaultMessage='添加设备' /></div>} | 
|                             icon={<FileAddOutlined />} | 
|                             onClick={() => { | 
|                                 if (deviceVisible) { | 
|                                     setDeviceVisible(false); | 
|                                 } else { | 
|                                     setModel(MapModel.MOVABLE_MODEL); | 
|                                     setDeviceVisible(true); | 
|                                 } | 
|                             }} | 
|                         /> | 
|                         <FloatButton | 
|                             hidden={model === MapModel.OBSERVER_MODEL} | 
|                             type={model === MapModel.SETTINGS_MODEL ? 'primary' : 'default'} | 
|                             tooltip={<div><FormattedMessage id='map.device.oper' defaultMessage='参数设置' /></div>} | 
|                             icon={<SettingOutlined />} | 
|                             onClick={() => { | 
|                                 setModel(model === MapModel.SETTINGS_MODEL ? MapModel.MOVABLE_MODEL : MapModel.SETTINGS_MODEL) | 
|                             }} | 
|                         /> | 
|                     </FloatButton.Group> | 
|                 </Content> | 
|             </Layout > | 
|   | 
|             <MapDrawer | 
|                 open={drawerVisible} | 
|                 curSprite={curSprite} | 
|                 curFloor={curFloor} | 
|                 refCurr={mapRef.current} | 
|                 onCancel={() => { | 
|                     setCurSPrite(null); | 
|                     setDrawerVisible(false); | 
|                 }} | 
|             /> | 
|   | 
|             <BatchDrawer | 
|                 open={batchDrawerVisible} | 
|                 batchSprites={batchSprites} | 
|                 refCurr={mapRef.current} | 
|                 model={model} | 
|                 ModelEnum={MapModel} | 
|                 onCancel={() => { | 
|                     setBatchSprites([]); | 
|                     setBatchDrawerVisible(false); | 
|                 }} | 
|             /> | 
|   | 
|             <Edit | 
|                 open={deviceVisible} | 
|                 onCancel={() => { | 
|                     setDeviceVisible(false); | 
|                 }} | 
|                 refCurr={mapRef.current} | 
|                 onDrop={onDrop} | 
|             /> | 
|   | 
|             <Settings | 
|                 open={settingsVisible} | 
|                 curSprite={spriteBySettings} | 
|                 onCancel={() => { | 
|                     setSettingsVisible(false); | 
|                     setSpriteBySettings(null); | 
|                 }} | 
|                 setSpriteBySettings={setSpriteBySettings} | 
|                 refCurr={mapRef.current} | 
|             /> | 
|         </> | 
|     ) | 
| } | 
|   | 
| export default Map; |