import * as React from 'react' 
 | 
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'; 
 | 
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 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); 
 | 
            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({ 
 | 
                    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(() => { 
 | 
        curFloorRef.current = curFloor; 
 | 
        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; 
 |