#
Junjie
2024-03-19 77ac6b72ed82d51d0d45bf156ac1b5bb3cb15782
zy-asrs-flow/src/pages/map/index.jsx
@@ -1,15 +1,26 @@
import * as React from 'react'
import * as PIXI from 'pixi.js';
import { FormattedMessage, useIntl, useModel } from '@umijs/max';
import { Layout, Button, Flex, } from 'antd';
import { Layout, Button, Flex, Row, Col, FloatButton, Select, Spin } 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/edit'
import Edit from './components/device';
import Settings from './components/settings'
import * as Utils from './utils'
import Player from './player';
import MapDrawer from './drawer';
const useStyles = createStyles(({ token }) => {
    let dark = token.colorBgBase === '#000';
    return {
        dark: dark,
        layout: {
            overflow: 'hidden',
        },
@@ -27,38 +38,50 @@
        content: {
            backgroundColor: '#F8FAFB',
            height: 'calc(100vh - 120px)'
        },
        select: {
            color: 'red',
            fontWeight: 'bold',
        }
    };
});
export const MapModel = Object.freeze({
    OBSERVER_MODEL: "1",
    MOVABLE_MODEL: "2",
    SETTINGS_MODEL: "3",
})
let player;
const Map = () => {
    const intl = useIntl();
    const { initialState, setInitialState } = useModel('@@initialState');
    const { styles } = useStyles();
    const mapRef = React.useRef();
    const contentRef = React.useRef();
    const [editModalVisible, setEditModalVisible] = React.useState(false);
    const [app, setApp] = React.useState(() => {
        return new PIXI.Application({
            background: '#F8FAFB',
            antialias: true,
        })
    })
    const [mapContainer, setMapContainer] = React.useState(() => {
        const mapContainer = new PIXI.Container();
        mapContainer.name = "mapContainer";
        mapContainer.data = {};
        app.stage.addChild(mapContainer);
        return mapContainer;
    })
    const [model, setModel] = React.useState(() => MapModel.OBSERVER_MODEL);
    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 [didClickSprite, setDidClickSprite] = React.useState(false);
    const [spriteBySettings, setSpriteBySettings] = React.useState(null);
    const prevSpriteBySettingsRef = React.useRef();
    // init func
    React.useEffect(() => {
        player = new Player(mapRef.current, styles.dark, didClickSprite);
        setApp(player.app);
        setMapContainer(player.mapContainer);
        Utils.syncApp(player.app);
        Utils.syncMapContainer(player.mapContainer);
        const handleResize = () => {
            setWindowSize({
                width: window.innerWidth,
@@ -66,74 +89,251 @@
            });
        };
        window.addEventListener('resize', handleResize);
        setTimeout(() => {
            // app init
            app.stage.eventMode = 'auto';
            app.stage.hitArea = app.screen;
            globalThis.__PIXI_APP__ = app;
            mapRef.current.appendChild(app.view);
            const texture = PIXI.Texture.from('https://pixijs.com/assets/bunny.png');
            const bunny = new PIXI.Sprite(texture);
            bunny.anchor.set(0.5);
            bunny.x = app.screen.width / 2;
            bunny.y = app.screen.height / 2;
            mapContainer.addChild(bunny);
            app.ticker.add((delta) => {
                bunny.rotation -= 0.01 * delta;
            });
            return () => {
                app.destroy(true, true);
            }
        }, 300)
    }, []);
    // resize
    React.useEffect(() => {
        if (!app) {
            return;
        }
        const width = contentRef.current.offsetWidth;
        const height = contentRef.current.offsetHeight;
        app.renderer.resize(width, height);
    }, [windowSize]);
        if (model !== MapModel.OBSERVER_MODEL) {
            player.hideGridlines();
            player.showGridlines();
        }
    }, [app, mapContainer, windowSize])
    const editHandle = () => {
        setEditModalVisible(true);
    }
    // model
    React.useEffect(() => {
        if (!mapContainer) {
            return;
        }
        switch (model) {
            case MapModel.OBSERVER_MODEL:
    const onDrop = (sprite, x, y) => {
        const rect = app.view.getBoundingClientRect();
        sprite.anchor.set(0.5);
        sprite.x = x - rect.left;
        sprite.y = y - rect.top;
                player.hideGridlines();
                player.hideStarryBackground();
                player.activateMapEvent(null);
                Utils.removeSelectedEffect();
                setDeviceVisible(false);
                setSettingsVisible(false);
                mapContainer.children.forEach(child => {
                    child.off('pointerup');
                    child.off('pointermove');
                    child.off('pointerdown');
                    child.off('click');
                })
                break
            case MapModel.MOVABLE_MODEL:
                player.showGridlines();
                player.hideStarryBackground();
                player.activateMapEvent(Utils.MapEvent.SELECTION_BOX);
                Utils.removeSelectedEffect();
                setSpriteBySettings(null);
                setSettingsVisible(false);
                mapContainer.children.forEach(child => {
                    Utils.beMovable(child, setDidClickSprite);
                })
                break
            case MapModel.SETTINGS_MODEL:
                player.showGridlines();
                player.showStarryBackground();
                player.activateMapEvent(null);
                setDeviceVisible(false);
                mapContainer.children.forEach(child => {
                    Utils.beSettings(child, setSpriteBySettings, setDidClickSprite);
                })
                break
            default:
                break
        }
    }, [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, setDidClickSprite);
    };
    // didClickSprite, stop triggers both sprite click and play's selection boxs
    React.useEffect(() => {
        player.updateDidClickSprite(didClickSprite);
    }, [didClickSprite])
    // 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;
    const settingsFinish = () => {
        setSettingsVisible(false);
        setSpriteBySettings(null);
    }
    return (
        <>
            <Layout className={styles.layout}>
                <Header className={styles.header}>
                    <Flex className={styles.flex} gap={'large'} justify={'flex-end'} align={'center'}>
                        <Button onClick={editHandle} size={'large'}><FormattedMessage id='map.edit' defaultMessage='编辑地图' /></Button>
                    </Flex>
                    <Row style={{ height: '100%' }}>
                        <Col span={8} style={{ backgroundColor: '#dcdde1' }}>
                            <Select
                                defaultValue="agv"
                                style={{
                                    width: 120,
                                }}
                                size={'large'}
                                onChange={(value, option) => {
                                    console.log(value, option);
                                }}
                                options={[
                                    {
                                        value: 'agv',
                                        label: 'agv',
                                    },
                                    {
                                        value: 'crn',
                                        label: 'crn',
                                    },
                                ]}
                            />
                            <Select
                            // notFoundContent={loading ? <Spin size="small" /> : null}
                            />
                        </Col>
                        <Col span={16} style={{ backgroundColor: '#3C40C6' }}>
                            <Flex className={styles.flex} gap={'large'} justify={'flex-end'} align={'center'}>
                                <Select
                                    className={styles.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} style={{ position: "relative" }} />
                    <div ref={mapRef} style={{ position: "relative" }} >
                        <FloatButton.Group
                            shape="square"
                            style={{
                                left: 35,
                                bottom: 35
                            }}
                        >
                            <FloatButton
                                icon={<CompressOutlined />}
                            />
                            <FloatButton.BackTop visibilityHeight={0} />
                        </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 {
                                        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>
                </Content>
            </Layout>
            <Edit
                open={editModalVisible}
                onCancel={
                    () => {
                        setEditModalVisible(false);
                    }
                }
                open={deviceVisible}
                onCancel={() => {
                    setDeviceVisible(false);
                }}
                refCurr={mapRef.current}
                onDrop={onDrop}
                mapContainer={mapContainer}
            />
            <Settings
                open={settingsVisible}
                curSprite={spriteBySettings}
                onCancel={() => {
                    setSettingsVisible(false);
                    setSpriteBySettings(null);
                }}
                setSpriteBySettings={setSpriteBySettings}
                setDidClickSprite={setDidClickSprite}
                refCurr={mapRef.current}
                onSubmit={settingsFinish}
            />
        </>
    )