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, Spin, AutoComplete } 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 Settings from './components/settings'
|
import * as Utils from './utils'
|
import Player from './player';
|
import MapSearch from './header/search';
|
import MapDrawer from './drawer';
|
|
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)'
|
},
|
select: {
|
color: 'red',
|
fontWeight: 'bold',
|
},
|
headerCol: {
|
paddingLeft: '50px'
|
}
|
};
|
});
|
|
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 [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 [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();
|
|
// init func
|
React.useEffect(() => {
|
const initialize = async () => {
|
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,
|
height: window.innerHeight,
|
});
|
};
|
window.addEventListener('resize', handleResize);
|
await Utils.fetchMapData(intl);
|
setDataFetched(true);
|
setModel(MapModel.OBSERVER_MODEL)
|
setTimeout(() => {
|
player.adaptScreen();
|
}, 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])
|
|
// model
|
React.useEffect(() => {
|
if (!mapContainer && !dataFetched) {
|
return;
|
}
|
switch (model) {
|
case MapModel.OBSERVER_MODEL:
|
|
player.hideGridlines();
|
player.hideStarryBackground();
|
|
player.activateMapEvent(null);
|
|
Utils.removeSelectedEffect();
|
setCurSPrite(null);
|
setDeviceVisible(false);
|
setSettingsVisible(false);
|
|
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);
|
|
mapContainer.children.forEach(child => {
|
Utils.beMovable(child, setDidClickSprite);
|
})
|
break
|
case MapModel.SETTINGS_MODEL:
|
|
player.showGridlines();
|
player.showStarryBackground();
|
|
player.activateMapEvent(null);
|
|
setDeviceVisible(false);
|
setDrawerVisible(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);
|
};
|
|
// 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;
|
|
// 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 = (values, fn) => {
|
fn();
|
// setSettingsVisible(false);
|
// setSpriteBySettings(null);
|
}
|
|
return (
|
<>
|
<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}
|
curSprite={curSprite}
|
setCurSPrite={setCurSPrite}
|
setSpriteBySettings={setSpriteBySettings}
|
/>
|
)}
|
</Col>
|
<Col span={12} style={{ backgroundColor: styles.dark ? '#2C3A47' : '#4a69bd' }}>
|
<Flex className={styles.flex} gap={'large'} justify={'flex-end'} align={'center'}>
|
|
{model === MapModel.OBSERVER_MODEL && (
|
<>
|
<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);
|
})
|
|
}}
|
>
|
<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);
|
}}
|
>
|
<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} style={{ position: "relative" }} >
|
<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 {
|
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 >
|
|
<MapDrawer
|
open={drawerVisible}
|
curSprite={curSprite}
|
refCurr={mapRef.current}
|
onCancel={() => {
|
setCurSPrite(null);
|
setDrawerVisible(false);
|
}}
|
/>
|
|
<Edit
|
open={deviceVisible}
|
onCancel={() => {
|
setDeviceVisible(false);
|
}}
|
refCurr={mapRef.current}
|
onDrop={onDrop}
|
/>
|
|
<Settings
|
open={settingsVisible}
|
curSprite={spriteBySettings}
|
onCancel={() => {
|
setSettingsVisible(false);
|
setSpriteBySettings(null);
|
}}
|
setSpriteBySettings={setSpriteBySettings}
|
setDidClickSprite={setDidClickSprite}
|
refCurr={mapRef.current}
|
onSubmit={settingsFinish}
|
/>
|
</>
|
)
|
}
|
|
export default Map;
|