From 17150b54d35c3ab02b2082ac4e9fc34858d43d77 Mon Sep 17 00:00:00 2001
From: Junjie <xjj@123>
Date: 星期一, 11 三月 2024 08:38:41 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'
---
zy-asrs-flow/src/locales/en-US.ts | 1
zy-asrs-flow/src/pages/map/player.js | 263 +++++++++++-
zy-asrs-flow/src/pages/map/index.css | 4
zy-asrs-flow/src/locales/en-US/map.ts | 17
zy-asrs-flow/public/img/map/star.png | 0
zy-asrs-flow/src/pages/map/components/settings.jsx | 430 +++++++++++++++++++++
zy-asrs-flow/src/pages/map/index.jsx | 232 +++++++++--
zy-asrs-flow/src/pages/map/components/device.jsx | 38 +
zy-asrs-flow/src/pages/map/utils.js | 195 +++++++++
9 files changed, 1,076 insertions(+), 104 deletions(-)
diff --git a/zy-asrs-flow/public/img/map/star.png b/zy-asrs-flow/public/img/map/star.png
new file mode 100644
index 0000000..a2b74e8
--- /dev/null
+++ b/zy-asrs-flow/public/img/map/star.png
Binary files differ
diff --git a/zy-asrs-flow/src/locales/en-US.ts b/zy-asrs-flow/src/locales/en-US.ts
index 654c3e5..7e1627b 100644
--- a/zy-asrs-flow/src/locales/en-US.ts
+++ b/zy-asrs-flow/src/locales/en-US.ts
@@ -18,6 +18,7 @@
'common.realname':'Real Name',
'common.idcard':'ID Number',
'common.introduction':'Introduction',
+ 'common.execute':'Execute',
'':'',
'':'',
'':'',
diff --git a/zy-asrs-flow/src/locales/en-US/map.ts b/zy-asrs-flow/src/locales/en-US/map.ts
index 0926568..dfb20d9 100644
--- a/zy-asrs-flow/src/locales/en-US/map.ts
+++ b/zy-asrs-flow/src/locales/en-US/map.ts
@@ -2,10 +2,27 @@
'map.edit': 'Edit Model',
'map.edit.close': 'Exit Edit',
'map.device.add': 'Add New Device',
+ 'map.device.oper': 'Device Settings',
+ 'map.model.observer': 'Observer Pattern',
+ 'map.model.editor': 'Editor Pattern',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
+ '': '',
+ 'map.settings.type': 'Type',
+ 'map.settings.position': 'Position',
+ 'map.settings.scale': 'Scale',
+ 'map.settings.rotation': 'Rotation',
+ 'map.settings.copy': 'Copy',
+ 'map.settings.left': 'Left',
+ 'map.settings.right': 'Right',
+ 'map.settings.top': 'Top',
+ 'map.settings.bottom': 'Bottom',
+ '': '',
+ '': '',
+ '': '',
+ '': '',
}
\ No newline at end of file
diff --git a/zy-asrs-flow/src/pages/map/components/device.jsx b/zy-asrs-flow/src/pages/map/components/device.jsx
index 81c69af..89fc6b9 100644
--- a/zy-asrs-flow/src/pages/map/components/device.jsx
+++ b/zy-asrs-flow/src/pages/map/components/device.jsx
@@ -33,30 +33,32 @@
});
import agv from '/public/img/map/agv.svg'
+import { set } from 'lodash';
const Device = (props) => {
const { styles } = useStyles();
const [dragging, setDragging] = useState(false);
const [dragSprite, setDragSprite] = useState(null);
+ const [dragSpriteType, setDragSpriteType] = useState(null);
+
+ const onDragStart = (e, type) => {
+ setDragging(true);
+ setDragSpriteType(type);
+ const sprite = PIXI.Sprite.from(agv);
+ setDragSprite(sprite);
+ };
useEffect(() => {
const handleMouseMove = (e) => {
if (dragging) {
- props.onDrop(dragSprite, e.clientX, e.clientY);
+ props.onDrop(dragSprite, dragSpriteType, e.clientX, e.clientY);
setDragging(false);
+ setDragSpriteType(null);
}
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, [dragging, props.onDrop, props.onCancel]);
-
- const onDragStart = (e) => {
- setDragging(true)
- props.onCancel();
- const sprite = PIXI.Sprite.from(agv);
- sprite.anchor.set(0.5);
- setDragSprite(sprite);
- };
return (
<>
@@ -83,7 +85,7 @@
width='50px'
preview={false}
draggable="true"
- onDragStart={onDragStart}
+ onDragStart={(e) => onDragStart(e, 'AGV')}
/>
<div>AGV</div>
</Col>
@@ -93,7 +95,7 @@
width='50px'
preview={false}
draggable="true"
- onDragStart={onDragStart}
+ onDragStart={(e) => onDragStart(e, 'AGV')}
/>
<div>AGV</div>
</Col>
@@ -103,7 +105,7 @@
width='50px'
preview={false}
draggable="true"
- onDragStart={onDragStart}
+ onDragStart={(e) => onDragStart(e, 'AGV')}
/>
<div>AGV</div>
</Col>
@@ -115,7 +117,7 @@
width='50px'
preview={false}
draggable="true"
- onDragStart={onDragStart}
+ onDragStart={(e) => onDragStart(e, 'AGV')}
/>
<div>AGV</div>
</Col>
@@ -125,7 +127,7 @@
width='50px'
preview={false}
draggable="true"
- onDragStart={onDragStart}
+ onDragStart={(e) => onDragStart(e, 'AGV')}
/>
<div>AGV</div>
</Col>
@@ -135,7 +137,7 @@
width='50px'
preview={false}
draggable="true"
- onDragStart={onDragStart}
+ onDragStart={(e) => onDragStart(e, 'AGV')}
/>
<div>AGV</div>
</Col>
@@ -147,7 +149,7 @@
width='50px'
preview={false}
draggable="true"
- onDragStart={onDragStart}
+ onDragStart={(e) => onDragStart(e, 'AGV')}
/>
<div>AGV</div>
</Col>
@@ -157,7 +159,7 @@
width='50px'
preview={false}
draggable="true"
- onDragStart={onDragStart}
+ onDragStart={(e) => onDragStart(e, 'AGV')}
/>
<div>AGV</div>
</Col>
@@ -167,7 +169,7 @@
width='50px'
preview={false}
draggable="true"
- onDragStart={onDragStart}
+ onDragStart={(e) => onDragStart(e, 'AGV')}
/>
<div>AGV</div>
</Col>
diff --git a/zy-asrs-flow/src/pages/map/components/settings.jsx b/zy-asrs-flow/src/pages/map/components/settings.jsx
new file mode 100644
index 0000000..3763c34
--- /dev/null
+++ b/zy-asrs-flow/src/pages/map/components/settings.jsx
@@ -0,0 +1,430 @@
+import React, { useState, useRef, useEffect } from 'react';
+import { Col, Form, Input, Row, Checkbox, Slider, Select, Drawer, Space, Button, InputNumber, Switch } from 'antd';
+import { FormattedMessage, useIntl, useModel } from '@umijs/max';
+import { createStyles } from 'antd-style';
+import './index.css';
+import * as Utils from '../utils'
+import * as PIXI from 'pixi.js';
+import moment from 'moment';
+import Http from '@/utils/http';
+
+const useStyles = createStyles(({ token, css }) => {
+
+})
+
+const SpriteSettings = (props) => {
+ const intl = useIntl();
+ const { styles } = useStyles();
+ const { curSprite } = props;
+ const [form] = Form.useForm();
+
+ useEffect(() => {
+
+ }, []);
+
+ useEffect(() => {
+ form.resetFields();
+ if (curSprite) {
+ form.setFieldsValue({
+ x: curSprite.position.x,
+ y: curSprite.position.y,
+ scale: Math.max(curSprite.scale.x, curSprite.scale.y),
+ scaleSlider: Math.max(curSprite.scale.x, curSprite.scale.y),
+ rotation: curSprite.rotation * 180 / Math.PI,
+ rotationSlider: curSprite.rotation * 180 / Math.PI,
+ })
+ }
+ }, [form, props])
+
+ const handleCancel = () => {
+ props.onCancel();
+ };
+
+ const handleOk = () => {
+ form.submit();
+ }
+
+ const handleFinish = async (values) => {
+ props.onSubmit({ ...values });
+ }
+
+ const formValuesChange = (changeList) => {
+ if (changeList && changeList.length > 0) {
+ changeList.forEach(change => {
+ const { name: nameList, value } = change;
+ nameList.forEach(name => {
+ switch (name) {
+ case 'x':
+ curSprite.position.x = value;
+ break;
+ case 'y':
+ curSprite.position.x = value;
+ break;
+ case 'scaleSlider':
+ form.setFieldsValue({
+ scale: value
+ })
+ curSprite.scale.set(value);
+ break;
+ case 'scale':
+ form.setFieldsValue({
+ scaleSlider: value
+ })
+ curSprite.scale.set(value);
+ break;
+ case 'rotationSlider':
+ form.setFieldsValue({
+ rotation: value
+ })
+ curSprite.rotation = value * Math.PI / 180;
+ break;
+ case 'rotation':
+ form.setFieldsValue({
+ rotationSlider: value
+ })
+ curSprite.rotation = value * Math.PI / 180;
+ break;
+ default:
+ break;
+ }
+ Utils.removeSelectedEffect();
+ Utils.showSelectedEffect(curSprite);
+ })
+ })
+ }
+ }
+
+ const onFinishFailed = (errorInfo) => {
+ };
+
+ return (
+ <>
+ <Drawer
+ open={props.open}
+ onClose={handleCancel}
+ getContainer={props.refCurr}
+ rootStyle={{ position: "absolute" }}
+ mask={false}
+ width={570}
+ extra={
+ <Space>
+ <Button onClick={handleCancel}>
+ <FormattedMessage id='common.cancel' defaultMessage='鍙栨秷' />
+ </Button>
+ <Button onClick={handleOk} type="primary">
+ <FormattedMessage id='common.submit' defaultMessage='淇濆瓨' />
+ </Button>
+ </Space>
+ }
+ >
+ <Form
+ form={form}
+ onFieldsChange={formValuesChange}
+ initialValues={{
+ }}
+ onFinish={handleFinish}
+ onFinishFailed={onFinishFailed}
+ autoComplete="off"
+ style={{
+ maxWidth: 600,
+ }}
+ size='default' // small | default | large
+ variant='filled' // outlined | borderless | filled
+ labelWrap // label 鎹㈣
+ disabled={false}
+ layout='horizontal'
+ >
+ <Row gutter={[24, 16]}>
+
+ {/* */}
+ <Col span={24}>
+ <Row gutter={24}>
+ <Col span={18}>
+ <Form.Item
+ label={intl.formatMessage({ id: 'map.settings.type', defaultMessage: '绫诲瀷' })}
+ labelCol={{ span: 4 }}
+ >
+ <span className="ant-form-text">China</span>
+ </Form.Item>
+ </Col>
+ </Row>
+ </Col>
+
+ {/* position */}
+ <Col span={24}>
+ <Row gutter={24}>
+ <Col span={18}>
+ <Form.Item
+ label={intl.formatMessage({ id: 'map.settings.position', defaultMessage: '鍧愭爣' })}
+ labelCol={{ span: 4 }}
+ >
+ <Space.Compact>
+ <Form.Item
+ name='x'
+ noStyle
+ rules={[
+ {
+ required: true,
+ },
+ ]}
+ >
+ <InputNumber
+ addonBefore={<Space.Compact>x</Space.Compact>}
+ style={{
+ width: '50%',
+ }}
+ />
+ </Form.Item>
+ <Form.Item
+ name='y'
+ noStyle
+ rules={[
+ {
+ required: true,
+ },
+ ]}
+ >
+ <InputNumber
+ addonBefore={<Space.Compact>y</Space.Compact>}
+ style={{
+ width: '50%',
+ }}
+ />
+ </Form.Item>
+ </Space.Compact>
+ </Form.Item>
+ </Col>
+ </Row>
+ </Col>
+
+ {/* scale */}
+ <Col span={24}>
+ <Row gutter={24}>
+ <Col span={18}>
+ <Form.Item
+ label={intl.formatMessage({ id: 'map.settings.scale', defaultMessage: '缂╂斁' })}
+ name="scaleSlider"
+ labelCol={{ span: 4 }}
+ >
+ <Slider
+ min={0.1}
+ max={10}
+ step={0.1}
+ marks={{
+ 0.1: '0.1',
+ 1: '1',
+ 10: '10',
+ }}
+ />
+ </Form.Item>
+ </Col>
+ <Col span={6}>
+ <Form.Item
+ name="scale"
+ labelCol={{ span: 4 }}
+ >
+ <InputNumber
+ changeOnWheel
+ min={0.1} max={10} defaultValue={1} step={0.1}
+ rules={[
+ {
+ required: true,
+ },
+ ]}
+ />
+ </Form.Item>
+ </Col>
+ </Row>
+ </Col>
+
+ {/* rotation */}
+ <Col span={24}>
+ <Row gutter={24}>
+ <Col span={18}>
+ <Form.Item
+ label={intl.formatMessage({ id: 'map.settings.rotation', defaultMessage: '瑙掑害' })}
+ name="rotationSlider"
+ labelCol={{ span: 4 }}
+ >
+ <Slider
+ min={0}
+ max={360}
+ step={1}
+ marks={{
+ 0: '0掳',
+ 90: '90掳',
+ 180: '180掳',
+ 270: '270掳',
+ 360: '360掳',
+ }}
+ />
+ </Form.Item>
+ </Col>
+ <Col span={6}>
+ <Form.Item
+ name="rotation"
+ labelCol={{ span: 4 }}
+ >
+ <InputNumber
+ changeOnWheel
+ min={0} max={360} defaultValue={0}
+ rules={[
+ {
+ required: true,
+ },
+ ]}
+ />
+ </Form.Item>
+ </Col>
+ </Row>
+ </Col>
+
+ {/* 澶嶅埗 */}
+ <Col span={24}>
+ <Row gutter={24}>
+ <Col span={18}>
+ <Form.Item
+ label={intl.formatMessage({ id: 'map.settings.copy', defaultMessage: '澶嶅埗' })}
+ labelCol={{ span: 4 }}
+ >
+ <Space.Compact>
+ <Form.Item
+ name="copyDire"
+ >
+ <Select
+ defaultValue="left"
+ style={{ width: 80 }}
+ options={[
+ { value: 'left', label: intl.formatMessage({ id: 'map.settings.left', defaultMessage: '宸�' }) },
+ { value: 'right', label: intl.formatMessage({ id: 'map.settings.right', defaultMessage: '鍙�' }) },
+ { value: 'top', label: intl.formatMessage({ id: 'map.settings.top', defaultMessage: '涓�' }) },
+ { value: 'bottom', label: intl.formatMessage({ id: 'map.settings.bottom', defaultMessage: '涓�' }) },
+ ]}
+ />
+ </Form.Item>
+ <Form.Item
+ name='copyCount'
+ noStyle
+ rules={[
+ {
+ required: true,
+ },
+ ]}
+ >
+ <InputNumber
+ addonBefore={<Space.Compact></Space.Compact>}
+ style={{
+ width: '50%',
+ }}
+ min={1} defaultValue={1} step={1}
+ />
+ </Form.Item>
+ <Form.Item>
+ <Button>
+ <FormattedMessage id='common.execute' defaultMessage='鎵ц' />
+ </Button>
+ </Form.Item>
+ </Space.Compact>
+ </Form.Item>
+ </Col>
+ </Row>
+ </Col>
+
+
+ {/* <Col span={12}>
+ <Form.Item
+ label="Username"
+ name="username"
+ hasFeedback
+ validateTrigger="onBlur"
+ validateDebounce={1000}
+ rules={[
+ {
+ required: false,
+ }
+ ]}
+ >
+ <Input disabled={false} />
+ </Form.Item>
+ </Col>
+ <Col span={24}>
+ <Form.Item
+ label="Switch"
+ valuePropName="checked"
+ >
+ <Switch />
+ </Form.Item>
+ </Col>
+ <Col span={24}>
+ <Form.Item label="Memo">
+ <Input.TextArea />
+ </Form.Item>
+ </Col>
+ <Col span={24}>
+ <Form.Item label="Address">
+ <Space.Compact>
+ <Form.Item
+ name={['address', 'province']}
+ noStyle
+ rules={[
+ {
+ required: false,
+ message: 'Province is required',
+ },
+ ]}
+ >
+ <Select placeholder="Select province">
+ <Option value="Zhejiang">Zhejiang</Option>
+ <Option value="Jiangsu">Jiangsu</Option>
+ </Select>
+ </Form.Item>
+ <Form.Item
+ name={['address', 'street']}
+ noStyle
+ rules={[
+ {
+ required: false,
+ message: 'Street is required',
+ },
+ ]}
+ >
+ <Input
+ style={{
+ width: '50%',
+ }}
+ placeholder="Input street"
+ />
+ </Form.Item>
+ </Space.Compact>
+ </Form.Item>
+ </Col>
+ <Col span={24}>
+ <Form.Item
+ name="phone"
+ label="Phone Number"
+ rules={[
+ {
+ required: false,
+ message: 'Please input your phone number!',
+ },
+ ]}
+ >
+ <Input
+ addonBefore={prefixSelector}
+ style={{
+ width: '100%',
+ }}
+ />
+ </Form.Item>
+ </Col> */}
+
+
+
+ </Row>
+ </Form>
+ </Drawer >
+ </>
+ )
+}
+
+export default SpriteSettings;
\ No newline at end of file
diff --git a/zy-asrs-flow/src/pages/map/index.css b/zy-asrs-flow/src/pages/map/index.css
index bdd99ce..6c27554 100644
--- a/zy-asrs-flow/src/pages/map/index.css
+++ b/zy-asrs-flow/src/pages/map/index.css
@@ -11,6 +11,10 @@
background: transparent;
}
+.ant-float-btn-group {
+ position: absolute;
+}
+
* {
box-sizing: border-box;
}
diff --git a/zy-asrs-flow/src/pages/map/index.jsx b/zy-asrs-flow/src/pages/map/index.jsx
index 7956a63..00e3809 100644
--- a/zy-asrs-flow/src/pages/map/index.jsx
+++ b/zy-asrs-flow/src/pages/map/index.jsx
@@ -1,16 +1,18 @@
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 } from 'antd';
+import { Layout, Button, Flex, Row, Col, FloatButton, Select } 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 Edit from './components/device';
+import Settings from './components/settings'
import * as Utils from './utils'
import Player from './player';
@@ -36,28 +38,44 @@
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 [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 [mapEditModel, setMapEditModel] = React.useState(false);
+ 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);
+ player = new Player(mapRef.current, styles.dark, didClickSprite);
setApp(player.app);
setMapContainer(player.mapContainer);
Utils.syncApp(player.app);
@@ -72,6 +90,7 @@
window.addEventListener('resize', handleResize);
}, []);
+ // resize
React.useEffect(() => {
if (!app) {
return;
@@ -81,23 +100,101 @@
app.renderer.resize(width, height);
}, [app, mapContainer, windowSize])
+ // model
React.useEffect(() => {
if (!mapContainer) {
return;
}
- if (mapEditModel) {
- player.showGridlines();
- } else {
- player.hideGridlines();
- }
- }, [mapEditModel]);
+ switch (model) {
+ case MapModel.OBSERVER_MODEL:
- const onDrop = (sprite, x, y) => {
+ 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 (
<>
@@ -107,49 +204,77 @@
<Col span={8} style={{ backgroundColor: '#3C40C6' }}></Col>
<Col span={16} style={{ backgroundColor: '#3C40C6' }}>
<Flex className={styles.flex} gap={'large'} justify={'flex-end'} align={'center'}>
- <Button onClick={() => setMapEditModel(!mapEditModel)} size={'large'}>
- {!mapEditModel
- ? <span style={{ fontWeight: 'bold' }}><FormattedMessage id='map.edit' defaultMessage='缂栬緫鍦板浘' /></span>
- : <span style={{ color: 'red', fontWeight: 'bold' }}><FormattedMessage id='map.edit.close' defaultMessage='閫�鍑虹紪杈�' /></span>
- }
- </Button>
+ <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" }} />
-
- <FloatButton.Group
- shape="square"
- style={{
- left: 35,
- bottom: 35
- }}
- >
- <FloatButton
- icon={<CompressOutlined />}
- />
- <FloatButton.BackTop visibilityHeight={0} />
- </FloatButton.Group>
-
- <FloatButton.Group
- hidden={!mapEditModel}
- trigger="hover"
- style={{
- right: 35,
- bottom: 35
- }}
- icon={<AppstoreAddOutlined />}
- >
- <FloatButton
- tooltip={<div><FormattedMessage id='map.device.add' defaultMessage='娣诲姞璁惧' /></div>}
- icon={<FileAddOutlined />}
- onClick={() => {
- setDeviceVisible(true);
+ <div ref={mapRef} style={{ position: "relative" }} >
+ <FloatButton.Group
+ shape="square"
+ style={{
+ left: 35,
+ bottom: 35
}}
- />
- </FloatButton.Group>
+ >
+ <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>
@@ -161,6 +286,17 @@
refCurr={mapRef.current}
onDrop={onDrop}
/>
+
+ <Settings
+ open={settingsVisible}
+ curSprite={spriteBySettings}
+ onCancel={() => {
+ setSettingsVisible(false);
+ setSpriteBySettings(null);
+ }}
+ refCurr={mapRef.current}
+ onSubmit={settingsFinish}
+ />
</>
)
}
diff --git a/zy-asrs-flow/src/pages/map/player.js b/zy-asrs-flow/src/pages/map/player.js
index 4f2e7b3..35edc66 100644
--- a/zy-asrs-flow/src/pages/map/player.js
+++ b/zy-asrs-flow/src/pages/map/player.js
@@ -1,10 +1,14 @@
import * as PIXI from 'pixi.js';
import * as TWEEDLE from 'tweedle.js';
+import * as Utils from './utils'
+import star from '/public/img/map/star.png'
export default class Player {
- constructor(dom, dark) {
+ constructor(dom, dark, didClickSprite) {
+ // not dynamic
this.darkModel = dark;
+ this.didClickSprite = didClickSprite;
// init
this.app = generatePixiApp(dark);
dom.appendChild(this.app.view);
@@ -13,26 +17,136 @@
this.mapContainer = generatePixiContainer('mapContainer');
this.app.stage.addChild(this.mapContainer);
- this.app.view.addEventListener('contextmenu', (event) => {
- event.preventDefault();
- });
- this.scale = 1; // 缂╂斁
- this.pan = false; // 骞崇Щ
-
- // func
- this.app.view.addEventListener('mousedown', (event) => {
- // 鍙抽敭
- if (event.button === 2) {
- this.mapPan(event);
- }
- })
+ // this.activateMapEvent(null);
this.activateMapScale();
+ this.activateMapPan();
this.showCoordinates();
this.appTicker();
}
+ activateMapEvent = (leftEvent, rightEvent) => {
+ if (this.mapEvent) {
+ this.mapContainer.parent.off('mousedown');
+ this.mapEvent = null;
+ if (this.selectedSprites && this.selectedSprites.length > 0) {
+ this.selectedSprites.forEach(child => {
+ Utils.unMarkSprite(child);
+ })
+ }
+ }
+ this.mapEvent = (event) => {
+ if (leftEvent && event.button === 0) {
+ switch (leftEvent) {
+ case Utils.MapEvent.SELECTION_BOX:
+ this.mapSelect(event);
+ break
+ default:
+ break
+ }
+ }
+ if (rightEvent && event.button === 2) {
+ switch (rightEvent) {
+ default:
+ break
+ }
+ }
+ }
+ this.mapContainer.parent.on('mousedown', this.mapEvent)
+ }
+
+ mapSelect = (event) => {
+ let isSelecting = false;
+ if (!this.selectionBox) {
+ this.selectionBox = new PIXI.Graphics();
+ this.app.stage.addChild(this.selectionBox);
+ }
+
+ // select start pos
+ const startPoint = new PIXI.Point();
+ this.app.renderer.events.mapPositionToPoint(startPoint, event.clientX, event.clientY);
+ let selectionStart = { x: startPoint.x, y: startPoint.y };
+
+ isSelecting = true;
+
+ const handleMouseMove = (event) => {
+ if (isSelecting && !this.didClickSprite) {
+ // select end pos
+ const endPoint = new PIXI.Point();
+ this.app.renderer.events.mapPositionToPoint(endPoint, event.clientX, event.clientY);
+ const selectionEnd = { x: endPoint.x, y: endPoint.y }
+
+ const width = Math.abs(selectionEnd.x - selectionStart.x);
+ const height = Math.abs(selectionEnd.y - selectionStart.y);
+
+ this.selectionBox.clear();
+ this.selectionBox.lineStyle(2, 0xCCCCCC, 1);
+ this.selectionBox.beginFill(0xCCCCCC, 0.2);
+ this.selectionBox.drawRect(Math.min(selectionStart.x, selectionEnd.x), Math.min(selectionStart.y, selectionEnd.y), width, height);
+ this.selectionBox.endFill();
+ }
+ }
+
+ this.mapContainer.parent.on('mousemove', handleMouseMove);
+
+ this.mapContainer.parent.on('mouseup', (event) => {
+ if (isSelecting) {
+ // sprite show style which be selected
+ if (this.selectedSprites && this.selectedSprites.length > 0) {
+ this.selectedSprites.forEach(child => {
+ Utils.unMarkSprite(child);
+ })
+ }
+ this.selectedSprites = [];
+
+ this.mapContainer.children.forEach(child => {
+ if (Utils.isSpriteInSelectionBox(child, this.selectionBox)) {
+ this.selectedSprites.push(child);
+ Utils.markSprite(child);
+ }
+ })
+ isSelecting = false;
+ this.selectionBox.clear();
+
+ // sprites batch move
+ Utils.spriteListBeMovable(this.selectedSprites, this.scale, () => {
+ this.activateMapEvent(Utils.MapEvent.SELECTION_BOX);
+ });
+
+ }
+
+ this.mapContainer.parent.off('mousemove', handleMouseMove);
+ });
+ }
+
+ activateMapPan = () => {
+ const mapPanHandle = (event) => {
+ if (event.button === 2) {
+ this.pan = true;
+ let previousPosition = { x: event.clientX, y: event.clientY };
+ const mouseMoveHandler = (event) => {
+ if (this.pan) {
+ const dx = event.clientX - previousPosition.x;
+ const dy = event.clientY - previousPosition.y;
+
+ this.mapContainer.position.x += dx;
+ this.mapContainer.position.y += dy;
+
+ previousPosition = { x: event.clientX, y: event.clientY };
+ }
+ };
+ this.app.view.addEventListener('mousemove', mouseMoveHandler);
+ this.app.view.addEventListener('mouseup', () => {
+ this.app.view.removeEventListener('mousemove', mouseMoveHandler);
+ this.pan = false;
+ });
+ }
+ }
+ this.app.view.addEventListener('mousedown', mapPanHandle);
+ }
+
activateMapScale = () => {
+ this.scale = 1; // 缂╂斁
this.app.view.addEventListener('wheel', (event) => {
event.preventDefault();
const delta = Math.sign(event.deltaY);
@@ -46,27 +160,6 @@
this.mapContainer.children.forEach(child => {
// child.scale.set(1 / this.scale); // 闃叉鍥炬爣鍙樺皬
})
- });
- }
-
- mapPan = (event) => {
- this.pan = true;
- let previousPosition = { x: event.clientX, y: event.clientY };
- const mouseMoveHandler = (event) => {
- if (this.pan) {
- const dx = event.clientX - previousPosition.x;
- const dy = event.clientY - previousPosition.y;
-
- this.mapContainer.position.x += dx;
- this.mapContainer.position.y += dy;
-
- previousPosition = { x: event.clientX, y: event.clientY };
- }
- };
- this.app.view.addEventListener('mousemove', mouseMoveHandler);
- this.app.view.addEventListener('mouseup', () => {
- this.app.view.removeEventListener('mousemove', mouseMoveHandler);
- this.pan = false;
});
}
@@ -127,6 +220,101 @@
}
}
+ showStarryBackground = () => {
+ if (!this.starryContainer) {
+ this.starryContainer = generatePixiContainer('starryContainer');
+ this.app.stage.addChild(this.starryContainer);
+ }
+
+ const starTexture = PIXI.Texture.from(star);
+
+ const starAmount = 300;
+ let cameraZ = 0;
+ const fov = 20;
+ const baseSpeed = 0.025;
+ let speed = 0;
+ let warpSpeed = 1;
+ const starStretch = 5;
+ const starBaseSize = 0.05;
+
+ const stars = [];
+
+ for (let i = 0; i < starAmount; i++) {
+ const star = {
+ sprite: new PIXI.Sprite(starTexture),
+ z: 0,
+ x: 0,
+ y: 0,
+ };
+ star.sprite.anchor.x = 0.5;
+ star.sprite.anchor.y = 0.7;
+ star.sprite.tint = 0x8395a7; // filter
+ randomizeStar(star, true);
+ this.starryContainer.addChild(star.sprite);
+ stars.push(star);
+ }
+
+ this.starryInterval = setInterval(() => {
+ warpSpeed = warpSpeed > 0 ? 0 : 1;
+ }, 5000);
+
+
+ this.starryTicker = (delta) => {
+ speed += (warpSpeed - speed) / 20;
+ cameraZ += delta * 10 * (speed + baseSpeed);
+ for (let i = 0; i < starAmount; i++) {
+ const star = stars[i];
+ if (star.z < cameraZ) randomizeStar(star);
+
+ const z = star.z - cameraZ;
+ star.sprite.x = star.x * (fov / z) * this.app.renderer.screen.width + this.app.renderer.screen.width / 2;
+ star.sprite.y = star.y * (fov / z) * this.app.renderer.screen.width + this.app.renderer.screen.height / 2;
+
+ const dxCenter = star.sprite.x - this.app.renderer.screen.width / 2;
+ const dyCenter = star.sprite.y - this.app.renderer.screen.height / 2;
+ const distanceCenter = Math.sqrt(dxCenter * dxCenter + dyCenter * dyCenter);
+ const distanceScale = Math.max(0, (2000 - z) / 2000);
+ star.sprite.scale.x = distanceScale * starBaseSize;
+
+ star.sprite.scale.y = distanceScale * starBaseSize + distanceScale * speed * starStretch * distanceCenter / this.app.renderer.screen.width;
+ star.sprite.rotation = Math.atan2(dyCenter, dxCenter) + Math.PI / 2;
+ }
+ }
+
+ this.app.ticker.add(this.starryTicker);
+
+ function randomizeStar(star, initial) {
+ star.z = initial ? Math.random() * 2000 : cameraZ + Math.random() * 1000 + 2000;
+
+ const deg = Math.random() * Math.PI * 2;
+ const distance = Math.random() * 50 + 1;
+ star.x = Math.cos(deg) * distance;
+ star.y = Math.sin(deg) * distance;
+ }
+ }
+
+ hideStarryBackground = () => {
+ if(this.starryTicker) {
+ this.app.ticker.remove(this.starryTicker);
+ this.starryTicker = null;
+ }
+
+ if (this.starryInterval) {
+ clearInterval(this.starryInterval);
+ this.starryInterval = null;
+ }
+
+ if (this.starryContainer) {
+ this.starryContainer.removeChildren();
+ this.app.stage.removeChild(this.starryContainer);
+ this.starryContainer = null;
+ }
+ }
+
+ updateDidClickSprite = (value) => {
+ this.didClickSprite = value;
+ }
+
appTicker = () => {
TWEEDLE.Group.shared.update();
}
@@ -138,8 +326,11 @@
background: dark ? '#f1f2f6' : '#f1f2f6',
antialias: true,
})
- app.stage.eventMode = 'auto';
+ app.stage.eventMode = 'static';
app.stage.hitArea = app.screen;
+ app.view.addEventListener('contextmenu', (event) => {
+ event.preventDefault();
+ });
return app;
}
diff --git a/zy-asrs-flow/src/pages/map/utils.js b/zy-asrs-flow/src/pages/map/utils.js
index 3abb061..c01b8d8 100644
--- a/zy-asrs-flow/src/pages/map/utils.js
+++ b/zy-asrs-flow/src/pages/map/utils.js
@@ -1,8 +1,8 @@
-
-
+import * as PIXI from 'pixi.js';
let app = null;
let mapContainer = null;
+let effectTick, effectHalfCircle, effectRectangle;
export function syncApp(param) {
app = param;
@@ -12,10 +12,201 @@
mapContainer = param;
}
+export const MapEvent = Object.freeze({
+ SELECTION_BOX: Symbol.for(0),
+})
+
export const getRealPosition = (x, y, mapContainer) => {
const rect = app.view.getBoundingClientRect();
return {
mapX: (x - rect.left) / mapContainer.scale.x - mapContainer.x / mapContainer.scale.x,
mapY: (y - rect.top) / mapContainer.scale.y - mapContainer.y / mapContainer.scale.y
}
+}
+
+export const initSprite = (sprite, type) => {
+ sprite.anchor.set(0.5);
+ sprite.cursor = 'pointer';
+ sprite.eventMode = 'static';
+ sprite.data = {
+ type: type
+ };
+}
+
+// sprite be movable from sprite click event
+export const beMovable = (sprite, setDidClickSprite) => {
+ sprite.off('pointerup');
+ sprite.off('pointermove');
+ sprite.off('pointerdown');
+ sprite.off('click');
+
+ sprite.on("pointerdown", onDragStart);
+
+ let dragTarget;
+ function onDragStart(event) {
+ setDidClickSprite(true);
+ dragTarget = event.currentTarget;
+ mapContainer.parent.off('pointermove');
+ mapContainer.parent.on('pointermove', onDragMove, dragTarget);
+
+ mapContainer.parent.off('pointerup');
+ mapContainer.parent.on('pointerup', onDragEnd.bind(mapContainer));
+ }
+
+ function onDragMove(event) {
+ if (this) {
+ this.parent.toLocal(event.global, null, this.position);
+ }
+ }
+
+ function onDragEnd() {
+ if (dragTarget) {
+ setDidClickSprite(false);
+ this.parent.off('pointermove');
+ this.parent.off('pointerup');
+ dragTarget.alpha = 1;
+ dragTarget = null;
+ }
+ }
+
+}
+
+// sprite be beSettings from sprite click event
+export const beSettings = (sprite, setSpriteBySettings, setDidClickSprite) => {
+ sprite.off('pointerup');
+ sprite.off('pointermove');
+ sprite.off('pointerdown');
+ sprite.off('click');
+
+ sprite.on("click", onClick);
+
+ function onClick(event) {
+ setSpriteBySettings(sprite);
+ // setDidClickSprite(true);
+ }
+}
+
+// sprites be movable from select box
+// the scale was dynamic
+export const spriteListBeMovable = (selectedSprites, scale, resetFn) => {
+ if (selectedSprites && selectedSprites.length > 0) {
+ let batchMove = false;
+ let batchMoveStartPos = null;
+
+ const batchMoving = (event) => {
+ if (batchMove && batchMoveStartPos) {
+ // offset move val
+ var mouseMovement = {
+ x: (event.global.x - batchMoveStartPos.x) / scale,
+ y: (event.global.y - batchMoveStartPos.y) / scale
+ };
+ for (let sprite of selectedSprites) {
+ sprite.position.x = sprite.data.batchMoveStartPos.x + mouseMovement.x;
+ sprite.position.y = sprite.data.batchMoveStartPos.y + mouseMovement.y;
+ }
+ }
+ }
+
+ const batchMoveEnd = (event) => {
+ batchMove = false;
+ batchMoveStartPos = null;
+ selectedSprites.forEach(child => {
+ unMarkSprite(child);
+ })
+ selectedSprites = [];
+ mapContainer.parent.off('mousedown');
+ mapContainer.parent.off('mousemove');
+ mapContainer.parent.off('mouseup');
+
+ resetFn();
+ }
+
+ const batchMoveStart = (event) => {
+ batchMoveStartPos = { x: event.data.global.clone().x, y: event.data.global.clone().y };
+ selectedSprites.forEach(child => {
+ child.data.batchMoveStartPos = { x: child.position.x, y: child.position.y };
+ })
+
+ batchMove = true;
+ mapContainer.parent.off('mousemove');
+ mapContainer.parent.on('mousemove', batchMoving);
+
+ mapContainer.parent.off('mouseup');
+ mapContainer.parent.on('mouseup', batchMoveEnd);
+ }
+
+ mapContainer.parent.off('mousedown')
+ mapContainer.parent.on('mousedown', batchMoveStart);
+ }
+}
+
+export const isSpriteInSelectionBox = (sprite, selectionBox) => {
+ const spriteBounds = sprite.getBounds();
+ const boxBounds = selectionBox.getBounds();
+
+ return spriteBounds.x + spriteBounds.width > boxBounds.x
+ && spriteBounds.x < boxBounds.x + boxBounds.width
+ && spriteBounds.y + spriteBounds.height > boxBounds.y
+ && spriteBounds.y < boxBounds.y + boxBounds.height;
+}
+
+export const showSelectedEffect = (sprite) => {
+ const { width, height } = sprite;
+ const scale = sprite.scale.x;
+ const sideLen = (Math.max(width, height) + 10) * scale;
+ const color = 0x273c75;
+
+ effectHalfCircle = new PIXI.Graphics();
+ effectHalfCircle.beginFill(color);
+ effectHalfCircle.lineStyle(2 * scale, color);
+ effectHalfCircle.arc(0, 0, sideLen, 0, Math.PI);
+ effectHalfCircle.endFill();
+ effectHalfCircle.position.set(sprite.x, sprite.y);
+ effectHalfCircle.scale.set(1 / scale);
+
+ effectRectangle = new PIXI.Graphics();
+ effectRectangle.lineStyle(2 * scale, color, 1);
+ effectRectangle.drawRoundedRect(0, 0, sideLen, sideLen, 16 * scale);
+ effectRectangle.endFill();
+ effectRectangle.mask = effectHalfCircle;
+
+ const scaledWidth = sideLen * (1 / scale);
+ const scaledHeight = sideLen * (1 / scale);
+
+ effectRectangle.scale.set(1 / scale);
+ effectRectangle.position.set(sprite.x - scaledWidth / 2, sprite.y - scaledHeight / 2);
+
+ mapContainer.addChild(effectRectangle);
+ mapContainer.addChild(effectHalfCircle);
+
+ let phase = 0;
+ effectTick = (delta) => {
+ phase += delta / 10;
+ phase %= (Math.PI * 2);
+ effectHalfCircle.rotation = phase;
+ };
+
+ app.ticker.add(effectTick);
+}
+
+export const removeSelectedEffect = () => {
+ if (effectTick) {
+ app.ticker.remove(effectTick);
+ }
+ if (effectHalfCircle) {
+ mapContainer.removeChild(effectHalfCircle);
+ effectHalfCircle = null;
+ }
+ if (effectRectangle) {
+ mapContainer.removeChild(effectRectangle);
+ effectRectangle = null;
+ }
+}
+
+export const markSprite = (sprite) => {
+ sprite.alpha = 0.5;
+}
+
+export const unMarkSprite = (sprite) => {
+ sprite.alpha = 1;
}
\ No newline at end of file
--
Gitblit v1.9.1