| | |
| | | import * as React from 'react' |
| | | import * as PIXI from 'pixi.js'; |
| | | import * as TWEEDLE from 'tweedle.js'; |
| | | import { FormattedMessage, useIntl, useModel } from '@umijs/max'; |
| | | import { Layout, Button, Flex, Row, Col, FloatButton, Select, notification, Segmented } from 'antd'; |
| | | import { Layout, Button, Flex, Row, Col, FloatButton, Select, notification, Segmented, message, Popconfirm } from 'antd'; |
| | | const { Header, Content } = Layout; |
| | | import { |
| | | AppstoreAddOutlined, |
| | | FileAddOutlined, |
| | | CompressOutlined, |
| | | SettingOutlined, |
| | | CloseOutlined |
| | | } from '@ant-design/icons'; |
| | | import './index.css' |
| | | import { createStyles } from 'antd-style'; |
| | | import Edit from './components/device'; |
| | | import Http from '@/utils/http'; |
| | | 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'; |
| | |
| | | }, |
| | | content: { |
| | | backgroundColor: '#F8FAFB', |
| | | height: 'calc(100vh - 120px)' |
| | | height: 'calc(100vh - 120px)', |
| | | position: 'relative' |
| | | }, |
| | | select: { |
| | | color: 'red', |
| | |
| | | SETTINGS_MODEL: "3", |
| | | }) |
| | | |
| | | let player; |
| | | let player, websocket; |
| | | |
| | | const Map = () => { |
| | | const intl = useIntl(); |
| | |
| | | }); |
| | | const [app, setApp] = React.useState(null); |
| | | const [mapContainer, setMapContainer] = React.useState(null); |
| | | const [didClickSprite, setDidClickSprite] = React.useState(false); |
| | | 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 hasFloor = true; |
| | | // const [hasFloor, setHasFloor] = React.useState(true); |
| | | const [curFloor, setCurFloor] = React.useState(1); |
| | | const [floorList, setFloorList] = React.useState([]); |
| | | const [curFloor, setCurFloor] = React.useState(() => { |
| | | const storedValue = localStorage.getItem('curFloor'); |
| | | return storedValue !== null ? JSON.parse(storedValue) : null; |
| | | }); |
| | | const curFloorRef = React.useRef(curFloor); |
| | | 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, didClickSprite); |
| | | 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(); |
| | | websocket.onMessage = (data) => { |
| | | Utils.updateMapStatusInRealTime(data, () => curFloorRef.current, setCurSPrite); |
| | | } |
| | | |
| | | const handleResize = () => { |
| | | setWindowSize({ |
| | |
| | | }); |
| | | }; |
| | | window.addEventListener('resize', handleResize); |
| | | await Utils.fetchMapData(intl); |
| | | |
| | | 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(() => { |
| | |
| | | }, 200) |
| | | } |
| | | initialize(); |
| | | |
| | | return () => { |
| | | websocket.onMessage = (data) => { } |
| | | if (websocket) { |
| | | websocket.close(); |
| | | } |
| | | } |
| | | }, []); |
| | | |
| | | // resize |
| | |
| | | } |
| | | }, [app, mapContainer, windowSize]) |
| | | |
| | | // model |
| | | React.useEffect(() => { |
| | | if (!mapContainer && !dataFetched) { |
| | | return; |
| | | } |
| | | // 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(); |
| | | |
| | | player.activateMapEvent(null); |
| | | |
| | | Utils.removeSelectedEffect(); |
| | | setCurSPrite(null); |
| | | setDeviceVisible(false); |
| | | setSettingsVisible(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); |
| | | |
| | | Utils.removeSelectedEffect(); |
| | | setSpriteBySettings(null); |
| | | setSettingsVisible(false); |
| | | setDrawerVisible(false); |
| | | player.activateMapEvent(Utils.MapEvent.SELECTION_BOX, (selectedSprites, resetFn) => { |
| | | Utils.spriteListBeMovable(selectedSprites, () => { |
| | | resetFn(); |
| | | }); |
| | | }); |
| | | |
| | | mapContainer.children.forEach(child => { |
| | | Utils.beMovable(child, setDidClickSprite); |
| | | Utils.beMovable(child); |
| | | }) |
| | | break |
| | | case MapModel.SETTINGS_MODEL: |
| | | |
| | | player.showGridlines(); |
| | | player.showStarryBackground(); |
| | | |
| | | player.activateMapEvent(null); |
| | | |
| | | setDeviceVisible(false); |
| | | setDrawerVisible(false); |
| | | player.activateMapEvent(Utils.MapEvent.SELECTION_BOX, (selectedSprites, resetFn) => { |
| | | setBatchSprites(selectedSprites); |
| | | }); |
| | | |
| | | mapContainer.children.forEach(child => { |
| | | Utils.beSettings(child, setSpriteBySettings, setDidClickSprite); |
| | | Utils.beSettings(child, setSpriteBySettings); |
| | | }) |
| | | break |
| | | default: |
| | | break |
| | | } |
| | | } |
| | | |
| | | // model |
| | | React.useEffect(() => { |
| | | if (!mapContainer && !dataFetched) { |
| | | return; |
| | | } |
| | | switchModel(model); |
| | | }, [model]); |
| | | |
| | | // Add New Device |
| | |
| | | |
| | | Utils.initSprite(sprite, type); |
| | | mapContainer.addChild(sprite); |
| | | Utils.beMovable(sprite, setDidClickSprite); |
| | | Utils.beMovable(sprite); |
| | | }; |
| | | |
| | | // watch curSprite |
| | |
| | | }, [curSprite]); |
| | | const prevCurSprite = prevCurSpriteRef.current; |
| | | |
| | | const clearLockPathConfirm = (e) => { |
| | | clearLockPath(curFloor); |
| | | }; |
| | | |
| | | const clearLockPath = async (floor) => { |
| | | const hide = message.loading(intl.formatMessage({ id: 'page.clearing', defaultMessage: '正在清空' })); |
| | | try { |
| | | const resp = await Http.doGet('api/map/clearLockPath', { lev: floor }); |
| | | if (resp.code === 200) { |
| | | message.success(intl.formatMessage({ id: 'page.clearing.success', defaultMessage: '清空成功' })); |
| | | return true; |
| | | } else { |
| | | message.error(resp.msg); |
| | | return false; |
| | | } |
| | | } catch (error) { |
| | | message.error(intl.formatMessage({ id: 'page.clearing.fail', defaultMessage: '清空失败请重试!' })); |
| | | return false; |
| | | } finally { |
| | | hide(); |
| | | } |
| | | } |
| | | |
| | | // fn switch floor |
| | | const switchFloor = async (floor) => { |
| | | await Utils.fetchMapData(floor); |
| | | |
| | | switchModel(model); |
| | | |
| | | setTimeout(() => { |
| | | player.adaptScreen(); |
| | | }, 200) |
| | | } |
| | | |
| | | // watch curFloor |
| | | React.useEffect(() => { |
| | | curFloorRef.current = curFloor; |
| | | if (!mapContainer && !dataFetched) { |
| | | return; |
| | | } |
| | | switchFloor(curFloor); |
| | | localStorage.setItem('curFloor', JSON.stringify(curFloor)); |
| | | }, [curFloor]); |
| | | |
| | | // didClickSprite, stop triggers both sprite click and play's selection boxs |
| | | React.useEffect(() => { |
| | | player.updateDidClickSprite(didClickSprite); |
| | | }, [didClickSprite]) |
| | | |
| | | // watch spriteBySettings |
| | | React.useEffect(() => { |
| | |
| | | }, [spriteBySettings]) |
| | | const prevSpriteBySettings = prevSpriteBySettingsRef.current; |
| | | |
| | | const settingsFinish = (values, fn) => { |
| | | fn(); |
| | | // setSettingsVisible(false); |
| | | // setSpriteBySettings(null); |
| | | } |
| | | // watch batchSprites |
| | | React.useEffect(() => { |
| | | if (!mapContainer) { |
| | | return; |
| | | } |
| | | if (batchSprites?.length > 0) { |
| | | setBatchDrawerVisible(true) |
| | | } else { |
| | | player.clearSelectedSprites(); |
| | | setBatchDrawerVisible(false) |
| | | } |
| | | }, [batchSprites]) |
| | | |
| | | return ( |
| | | <> |
| | |
| | | model={model} |
| | | setModel={setModel} |
| | | ModelEnum={MapModel} |
| | | curFloor={curFloor} |
| | | curSprite={curSprite} |
| | | setCurSPrite={setCurSPrite} |
| | | setSpriteBySettings={setSpriteBySettings} |
| | |
| | | |
| | | {model === MapModel.OBSERVER_MODEL && ( |
| | | <> |
| | | <Popconfirm |
| | | title="清空路径" |
| | | description="此操作可能导致小车碰撞,确认清空路径吗?" |
| | | onConfirm={clearLockPathConfirm} |
| | | okText="确认" |
| | | cancelText="取消" |
| | | > |
| | | <Button |
| | | className='map-header-button' |
| | | size={'large'} |
| | | > |
| | | <FormattedMessage id='map.clearLockPath' defaultMessage='清空路径' /> |
| | | </Button> |
| | | </Popconfirm> |
| | | |
| | | |
| | | <Button |
| | | className='map-header-button' |
| | | size={'large'} |
| | | onClick={async () => { |
| | | await Utils.fetchMapData(intl); |
| | | |
| | | player.hideGridlines(); |
| | | player.hideStarryBackground(); |
| | | |
| | | player.activateMapEvent(null); |
| | | |
| | | Utils.removeSelectedEffect(); |
| | | setCurSPrite(null); |
| | | setDeviceVisible(false); |
| | | setSettingsVisible(false); |
| | | setDrawerVisible(false); |
| | | |
| | | mapContainer.children.forEach(child => { |
| | | Utils.viewFeature(child, setCurSPrite); |
| | | }) |
| | | |
| | | onClick={() => { |
| | | switchFloor(curFloor) |
| | | }} |
| | | > |
| | | <FormattedMessage id='map.load' defaultMessage='加载地图' /> |
| | |
| | | className='map-header-button' |
| | | size={'large'} |
| | | onClick={() => { |
| | | Utils.saveMapData(intl); |
| | | Utils.saveMapData(intl, curFloor); |
| | | }} |
| | | > |
| | | <FormattedMessage id='map.save' defaultMessage='保存地图' /> |
| | |
| | | </Row> |
| | | </Header> |
| | | <Content ref={contentRef} className={styles.content}> |
| | | <div ref={mapRef} style={{ position: "relative" }} > |
| | | {hasFloor && ( |
| | | <MapFloor |
| | | curFloor={curFloor} |
| | | setCurFloor={setCurFloor} |
| | | /> |
| | | )} |
| | | <div ref={mapRef} /> |
| | | |
| | | <FloatButton.Group |
| | | shape="square" |
| | | style={{ |
| | | left: 35, |
| | | bottom: 35 |
| | | {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 |
| | | icon={<CompressOutlined />} |
| | | onClick={() => { |
| | | player.adaptScreen(); |
| | | }} |
| | | /> |
| | | </FloatButton.Group> |
| | | /> |
| | | </FloatButton.Group> |
| | | |
| | | <FloatButton.Group |
| | | <FloatButton.Group |
| | | hidden={model === MapModel.OBSERVER_MODEL} |
| | | style={{ |
| | | left: 35, |
| | | bottom: window.innerHeight / 2 |
| | | }} |
| | | icon={<AppstoreAddOutlined />} |
| | | > |
| | | <FloatButton |
| | | hidden={model === MapModel.OBSERVER_MODEL} |
| | | style={{ |
| | | left: 35, |
| | | bottom: window.innerHeight / 2 |
| | | 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); |
| | | } |
| | | }} |
| | | 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 { |
| | | setDeviceVisible(true); |
| | | setModel(MapModel.MOVABLE_MODEL); |
| | | } |
| | | }} |
| | | /> |
| | | <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> |
| | | </div> |
| | | /> |
| | | <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); |
| | | }} |
| | | /> |
| | | |
| | |
| | | setSpriteBySettings(null); |
| | | }} |
| | | setSpriteBySettings={setSpriteBySettings} |
| | | setDidClickSprite={setDidClickSprite} |
| | | refCurr={mapRef.current} |
| | | onSubmit={settingsFinish} |
| | | /> |
| | | </> |
| | | ) |