change pkg

This commit is contained in:
yehuozhili
2021-07-09 01:41:03 +08:00
commit 968a072537
115 changed files with 19556 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
import { IBlockType } from '../core/store/storetype';
import { CSSProperties, PropsWithChildren, useEffect, useMemo, useRef, useState } from 'react';
import { innerDrag } from '../core/innerDrag';
import { BlockResizer } from '../core/resizeHandler';
import { contextMenuEvent } from '../core/contextMenu';
import React from 'react';
import { transfer } from '../core/transfer';
import { UserConfig } from '../config';
import styles from '../index.less';
interface BlockProps {
data: IBlockType;
context: 'edit' | 'preview';
config: UserConfig;
}
/**
*
* 用来从component里拿到渲染进行渲染,由于异步拉代码,所以需要等待代码拉取完毕
* @param {*} props
* @returns
*/
function Blocks(props: PropsWithChildren<BlockProps>) {
const [state, setState] = useState<JSX.Element | null>(null);
const [previewState, setPreviewState] = useState({
top: props.data.top,
left: props.data.left,
height: props.data.height,
width: props.data.width,
});
useEffect(() => {
const fn = () => props.config.getComponentRegister().getComp(props.data.name);
const data = fn();
let unregist = () => {};
let newdata = { ...props.data };
if (props.context === 'preview') {
newdata = {
...props.data,
top: previewState.top,
left: previewState.left,
height: previewState.height,
width: previewState.width,
};
}
if (data) {
setState(data.render(newdata, props.context, props.config.getStore(), props.config));
} else {
const callback = () => {
const tmp = fn();
setState(tmp.render(newdata, props.context, props.config.getStore(), props.config));
unregist();
};
unregist = props.config.getComponentRegister().on(props.data.name, callback);
}
return () => {
unregist();
};
}, [props.data, props.context, props.config, previewState]);
const ref = useRef<HTMLDivElement>(null);
const innerDragData = useMemo(() => {
return { ...innerDrag(props.data, ref) };
}, [props.data]);
useEffect(() => {
const fn = () => {
const { top, left, width, height } = transfer(
props.data.top,
props.data.left,
props.data.height,
props.data.width,
props.data.fixed
);
setPreviewState({ top, left, width, height });
};
fn();
window.addEventListener('resize', fn);
return () => {
window.removeEventListener('resize', fn);
};
}, [
previewState.height,
previewState.left,
previewState.top,
previewState.width,
props.data.height,
props.data.left,
props.data.top,
props.data.width,
props.data.fixed,
]);
const animatecss = useMemo(() => {
const animate = props.data.animate;
if (Object.keys(animate).length > 0) {
return `animate__animated ${animate.animate ?? ''} ${animate.delay ?? ''} ${
animate.speed ?? ''
}`;
}
return '';
}, [
props.data.animate.animate,
props.data.animate.delay,
// props.data.animate.duration,
props.data.animate.speed,
]);
const animateCount = useMemo(() => {
const animate = props.data.animate;
if (Object.keys(animate).length > 0) {
return { animationIterationCount: animate.animationIterationCount };
}
return { animationIterationCount: 1 };
}, [props.data.animate.animationIterationCount]);
const render = useMemo(() => {
// 如果是编辑模式下,则需要包裹不能选中层,位移层,缩放控制层,平面移动层。
if (state && props.context === 'edit') {
const style: CSSProperties = props.data.canDrag ? { pointerEvents: 'none' } : {};
return (
<div
ref={ref}
className={
props.data.focus && props.data.position !== 'static' ? styles.yh_block_focus : ''
}
style={{
position: props.data.position,
top: props.data.top,
left: props.data.left,
width: props.data.width,
height: props.data.height,
zIndex: props.data.zIndex,
display: props.data.display,
}}
{...innerDragData}
onContextMenu={(e) => {
if (props.data.name !== 'modalMask') {
contextMenuEvent(e, ref);
}
}}
>
{props.data.position !== 'static' && (
<div className={animatecss} style={{ ...style, ...animateCount }}>
{state}
</div>
)}
{/* 这里暂不考虑布局影响 */}
{props.data.position === 'static' && props.data.display !== 'inline' && (
<div
className={animatecss}
style={{
pointerEvents: 'none',
width: '100%',
height: '100%',
...animateCount,
}}
>
{state}
</div>
)}
{props.data.position === 'static' && props.data.display === 'inline' && (
<span style={{ pointerEvents: 'none' }}>{state}</span>
)}
<BlockResizer data={props.data} rect={ref}></BlockResizer>
</div>
);
} else {
return (
<div
className={animatecss}
style={{
position: props.data.fixed ? 'fixed' : props.data.position,
top: previewState.top,
left: previewState.left,
width: previewState.width,
height: previewState.height,
zIndex: props.data.zIndex,
display: props.data.display,
...animateCount,
}}
>
{state}
</div>
);
}
}, [
state,
props.context,
props.data,
innerDragData,
previewState.top,
previewState.left,
previewState.width,
previewState.height,
]);
return render;
}
export default Blocks;

View File

@@ -0,0 +1,131 @@
import { containerDragResolve } from '../core/crossDrag';
import { containerFocusRemove } from '../core/focusHandler';
import { innerContainerDrag } from '../core/innerDrag';
import { NormalMarkLineRender } from '../core/markline';
import { scaleState } from '../core/scale/state';
import { IStoreData } from '../core/store/storetype';
import { wrapperMoveState } from './wrapperMove/event';
import { CSSProperties, PropsWithChildren, useMemo } from 'react';
import Blocks from './blocks';
import { containerResizer } from '../core/resizeHandler/containerResizer';
import React from 'react';
import UserConfig from '../config';
import styles from '../index.less';
import { getRealHeight } from '../core/transfer';
import { IconFont } from '../core/utils/icon';
interface ContainerProps {
state: IStoreData;
context: 'edit' | 'preview';
config: UserConfig;
editContainerStyle?: CSSProperties;
previewContainerStyle?: CSSProperties;
}
function Container(props: PropsWithChildren<ContainerProps>) {
const { editContainerStyle, previewContainerStyle } = props;
const transform = useMemo(() => {
if (props.context === 'edit') {
return `scale(${scaleState.value}) translate(${wrapperMoveState.needX}px, ${wrapperMoveState.needY}px)`;
} else {
return undefined;
}
}, [props.context]);
const bgColor = () => {
const isEdit = props.config.getStoreChanger().isEdit();
if (isEdit) {
return 'rgba(255,255,255,1)';
} else {
return props.state.globalState.containerColor;
}
};
return (
<>
{props.context === 'edit' && (
<>
<div
style={{
position: 'absolute',
height: `${props.state.container.height + 60}px`,
width: `${props.state.container.width}px`,
transform: `scale(${scaleState.value}) translate(${wrapperMoveState.needX}px, ${wrapperMoveState.needY}px)`,
}}
>
<div style={{ display: 'flex' }}>
<div
id="yh-container"
className={styles.yh_container}
style={{
height: `${props.state.container.height}px`,
width: `${props.state.container.width}px`,
backgroundColor: bgColor(),
position: 'relative',
overflow: 'hidden',
...editContainerStyle,
}}
{...(props.context === 'edit' ? containerDragResolve : null)}
{...(props.context === 'edit' ? innerContainerDrag() : null)}
{...(props.context === 'edit' ? containerFocusRemove() : null)}
>
{props.context === 'edit' && <NormalMarkLineRender></NormalMarkLineRender>}
{props.state.block.map((v) => {
return (
<Blocks
config={props.config}
key={v.id}
data={v}
context={props.context}
></Blocks>
);
})}
</div>
</div>
<div
style={{
height: '50px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: `${props.state.container.width}px`,
}}
>
<IconFont
type="icon-suofang"
onMouseDown={containerResizer.onMousedown}
style={{ fontSize: '20px', cursor: 's-resize' }}
></IconFont>
{/* <BoxPlotFilled
onMouseDown={containerResizer.onMousedown}
style={{ fontSize: '20px', cursor: 's-resize' }}
rotate={90}
/> */}
</div>
</div>
</>
)}
{props.context === 'preview' && (
<div
id="yh-container-preview"
className={styles.yh_container_preview}
style={{
height: `${getRealHeight(props.state.container.height)}px`,
width: `100%`,
position: 'relative' as 'absolute' | 'relative',
overflow: 'hidden',
backgroundColor: bgColor(),
transform: transform,
...previewContainerStyle,
}}
>
{props.state.block.map((v) => {
return (
<Blocks key={v.id} config={props.config} data={v} context={props.context}></Blocks>
);
})}
</div>
)}
</>
);
}
export default Container;

View File

@@ -0,0 +1,281 @@
import {
CompressOutlined,
DeleteOutlined,
FullscreenExitOutlined,
FullscreenOutlined,
GatewayOutlined,
MenuOutlined,
SyncOutlined,
UnorderedListOutlined,
} from '@ant-design/icons';
import { Button, Divider, Form, Input, List, Modal, Popconfirm, Popover } from 'antd';
import React, { CSSProperties, PropsWithChildren, useState } from 'react';
import { UserConfig } from '..';
import { IBlockType, IStoreData } from '../core/store/storetype';
import { deepCopy, arrayMove, changeItem, changeLayer, focusEle } from '../core/utils';
import { SortEnd, SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import { wrapperMoveState } from './wrapperMove/event';
export interface ControlProps {
config: UserConfig;
style?: CSSProperties;
}
const DragHandle = SortableHandle(() => <MenuOutlined />);
const SortableItem = SortableElement(
({ value }: { value: { value: IBlockType; config: UserConfig } }) => (
<div
style={{
userSelect: 'none',
display: 'flex',
alignItems: 'center',
width: 430,
}}
>
<div style={{ width: 30, textAlign: 'center', cursor: 'move' }}>
<DragHandle></DragHandle>
</div>
<Divider type="vertical"></Divider>
<div style={{ width: 100, textAlign: 'center' }}>
{value.config.getComponentRegister().getMap()[value.value.name].display}
</div>
<Divider type="vertical"></Divider>
<div style={{ width: 50, textAlign: 'center' }}>{value.value.id.slice(-6)}</div>
<Divider type="vertical"></Divider>
<div style={{ width: 50, textAlign: 'center' }}>{value.value.position}</div>
<Divider type="vertical"></Divider>
<div style={{ width: 200 }}>
<Popconfirm
title="确认变更为绝对定位吗?"
onConfirm={() => {
changeItem(value.config.getStore(), value.value.id, 'position', 'absolute');
}}
>
<Button type="link" title="切换绝对定位" icon={<FullscreenOutlined />}></Button>
</Popconfirm>
<Popconfirm
title="确认变更为静态定位吗?"
onConfirm={() => {
changeItem(value.config.getStore(), value.value.id, 'position', 'static');
}}
>
<Button type="link" title="切换静态定位" icon={<FullscreenExitOutlined />}></Button>
</Popconfirm>
<Button
type="link"
title="选中聚焦"
icon={<CompressOutlined />}
onClick={() => {
focusEle(value.config.getStore(), value.value.id);
}}
></Button>
<Popconfirm
title="确认删除操作吗?"
onConfirm={() => {
changeLayer(value.config.getStore(), value.value.id, 'delete');
}}
>
<Button icon={<DeleteOutlined />} title="删除" type="link"></Button>
</Popconfirm>
</div>
</div>
)
);
const SortableList = SortableContainer(
({ items }: { items: { data: IBlockType[]; config: UserConfig } }) => {
return (
<div>
{items.data.map((value, index: number) => (
<SortableItem key={value.id} index={index} value={{ value, config: items.config }} />
))}
</div>
);
}
);
export function Control(props: PropsWithChildren<ControlProps>) {
const { style } = props;
const [visible, setVisible] = useState(false);
const [configVisible, setConfigVisible] = useState(false);
const [form] = Form.useForm();
const data = props.config.getStore().getData().block;
const onSortEnd = (sort: SortEnd) => {
const { oldIndex, newIndex } = sort;
const newblocks: IBlockType[] = arrayMove(data, oldIndex, newIndex);
// 这里要判断是否edit ,如果edit时只要看第一个是不是container不是则不移动
const isEdit = props.config.getStoreChanger().isEdit();
if (isEdit) {
const firstType = newblocks[0].name;
if (firstType !== 'modalMask') {
return;
}
}
const store = props.config.getStore();
const cloneData: IStoreData = deepCopy(store.getData());
cloneData.block = newblocks;
store.setData(cloneData);
};
const content =
data.length === 0 ? (
<div></div>
) : (
<div style={{ maxHeight: 300, overflow: 'auto' }}>
<SortableList
distance={2}
useDragHandle
items={{
data,
config: props.config,
}}
onSortEnd={onSortEnd}
axis="y"
></SortableList>
</div>
);
return (
<>
<div
className="ant-menu"
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
...style,
}}
>
<Popover style={{ minWidth: '208px' }} content={content} trigger="click">
<Button icon={<UnorderedListOutlined />}></Button>
</Popover>
{/* <Button icon={<FolderOpenOutlined />}></Button> */}
<Popover
placement="left"
content={
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
}}
>
<Button
onClick={() => {
setVisible(true);
}}
>
</Button>
<Button
onClick={() => {
setConfigVisible(true);
}}
>
</Button>
</div>
}
>
<Button icon={<GatewayOutlined />}></Button>
</Popover>
<Button
icon={<SyncOutlined />}
onClick={() => {
wrapperMoveState.needX = 0;
wrapperMoveState.needY = 0;
props.config.getStore().forceUpdate();
}}
></Button>
</div>
<Modal
title="弹窗配置"
visible={configVisible}
onOk={() => setConfigVisible(false)}
onCancel={() => setConfigVisible(false)}
footer={null}
>
<List>
{props.config.getStoreChanger().getState().modalEditName !== '' && (
<div>退</div>
)}
{props.config.getStoreChanger().getState().modalEditName === '' &&
Object.keys(props.config.getStore().getData().modalMap).map((v) => {
return (
<List.Item
key={v}
actions={[
<Popconfirm
title="是否切换至该弹窗并进行编辑?"
onConfirm={() => {
props.config.getStoreChanger().updateModal(props.config.getStore(), v);
setConfigVisible(false);
}}
okText={'是'}
cancelText={'否'}
>
<Button type="link"></Button>
</Popconfirm>,
<Popconfirm
title="您确定要删除这个弹窗吗?"
onConfirm={() => {
props.config.getStoreChanger().removeModal(props.config.getStore(), v);
setConfigVisible(false);
}}
okText={'是'}
cancelText={'否'}
>
<Button type="link"></Button>
</Popconfirm>,
]}
>
{v}
</List.Item>
);
})}
{props.config.getStoreChanger().getState().modalEditName === '' &&
Object.keys(props.config.getStore().getData().modalMap).length === 0 && (
<div style={{ textAlign: 'center' }}></div>
)}
</List>
</Modal>
<Modal
onOk={() => {
form
.validateFields()
.then((values) => {
form.resetFields();
const modalName = values.modalName;
props.config.getStoreChanger().newModalMap(props.config.getStore(), modalName);
setVisible(false);
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
title="新增弹窗"
onCancel={() => setVisible(false)}
visible={visible}
>
<Form layout="vertical" name="basic" form={form}>
<Form.Item
label="弹窗名称"
name="modalName"
rules={[{ required: true, message: '请输入弹窗名称!' }]}
>
<Input />
</Form.Item>
</Form>
</Modal>
</>
);
}
export default Control;

View File

@@ -0,0 +1,185 @@
/*
* @Author: yehuozhili
* @Date: 2021-02-04 10:32:45
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-06 23:55:37
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\components\leftConfig.tsx
*/
import React, { ReactNode, useEffect, useMemo, useState } from 'react';
import { Input, Menu } from 'antd';
import { dragEventResolve, LeftRegistComponentMapItem } from '../core/crossDrag';
import UserConfig from '../config';
import { DoubleLeftOutlined, DoubleRightOutlined, SearchOutlined } from '@ant-design/icons';
import styles from '../index.less';
interface LeftConfigProps {
config: UserConfig;
}
/**
*
* 注册加载左侧组件方法,由于异步拉取,所以要异步加载
* 不同tab页可以使用不同type区分
* @param {*} props
* @returns
*/
function LeftConfig(props: LeftConfigProps) {
const [menuSelect, setMenuSelect] = useState('0');
const [leftRender, setLeftRender] = useState<ReactNode | null>(null);
const leftMapRenderListCategory = useMemo(() => {
return props.config.getConfig().leftRenderListCategory;
}, [props.config]);
const [search, setSearch] = useState('');
useEffect(() => {
let cache: LeftRegistComponentMapItem[] = [];
const type = leftMapRenderListCategory[parseInt(menuSelect, 10)]?.type;
const isCustom = leftMapRenderListCategory[parseInt(menuSelect, 10)]?.custom;
if (!isCustom) {
const config = props.config.getConfig();
cache = config.leftAllRegistMap.filter((k) => k.type === type);
cache.forEach((v) => props.config.asyncRegistComponent(v.component));
setLeftRender(
<div className={styles.leftco}>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '10px',
}}
>
<div
style={{
width: 100,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
height: 32,
lineHeight: '32px',
marginRight: '10px',
fontSize: '14px',
fontFamily: 'PingFangSC-Medium, PingFang SC',
fontWeight: 600,
userSelect: 'none',
}}
>
{leftMapRenderListCategory[parseInt(menuSelect, 10)].displayName}
</div>
<Input
style={{
borderRadius: '40px',
}}
allowClear
value={search}
onChange={(e) => {
setSearch(e.target.value);
}}
prefix={<SearchOutlined />}
/>
</div>
{search &&
search !== '' &&
cache
.reduce<LeftRegistComponentMapItem[]>((prev, next) => {
//筛选搜索条件name或者displayName存在即显示
if (next.displayName.includes(search) || next.component.includes(search)) {
prev.push(next);
}
return prev;
}, [])
.map((v, index) => (
<div className={styles.coitem} key={index} {...dragEventResolve(v)}>
<div className={styles.redbox}>
{v.imgCustom ? v.imgCustom : <img src={v.img}></img>}
</div>
<div
style={{
textAlign: 'center',
lineHeight: '20px',
height: '20px',
overflow: 'hidden',
}}
>
{v.displayName}
</div>
</div>
))}
{(!search || search === '') &&
cache.map((v, index) => (
<div className={styles.coitem} key={index} {...dragEventResolve(v)}>
<div className={styles.redbox}>
{v.imgCustom ? v.imgCustom : <img src={v.img}></img>}
</div>
<div
style={{
textAlign: 'center',
lineHeight: '20px',
height: '20px',
overflow: 'hidden',
}}
>
{v.displayName}
</div>
</div>
))}
</div>
);
} else {
const render = leftMapRenderListCategory[parseInt(menuSelect, 10)]?.customRender;
setLeftRender(<div className={styles.leftco}>{render}</div>);
}
}, [menuSelect, props.config, leftMapRenderListCategory, search]);
const [isCollapse, setCollapse] = useState(false);
const [renderCollapse, setRenderCollaspe] = useState(false);
return (
<div style={{ display: 'flex', height: '100%' }}>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Menu style={{ flex: 1 }} defaultSelectedKeys={[menuSelect]} mode="vertical">
{leftMapRenderListCategory.map((v, i) => {
return (
<Menu.Item key={i} onClick={() => setMenuSelect(i + '')} icon={v.icon}></Menu.Item>
);
})}
</Menu>
<Menu selectedKeys={[]}>
<Menu.Item
key="1"
onClick={() =>
setCollapse((pre) => {
if (pre) {
setTimeout(() => {
setRenderCollaspe(false);
}, 300);
return !pre;
} else {
setRenderCollaspe(true);
return !pre;
}
})
}
className={styles.menu_footer}
icon={isCollapse ? <DoubleRightOutlined /> : <DoubleLeftOutlined />}
></Menu.Item>
</Menu>
</div>
<div
className={`${styles.yhLeftrender} ant-menu scrollbar`}
style={{
width: isCollapse ? 0 : 270,
paddingRight: isCollapse ? 0 : 7, // 这个是滚动条宽度
overflowX: 'hidden',
}}
>
{!renderCollapse && leftRender}
</div>
</div>
);
}
export default LeftConfig;

View File

@@ -0,0 +1,112 @@
import { IStoreData } from '../core/store/storetype';
import React, { useMemo } from 'react';
import Blocks from './blocks';
import UserConfig from '../config';
import * as ReactDOM from 'react-dom';
import { deepCopy } from '../core/utils';
interface ModalRenderProps {
data: IStoreData;
name: string; //传递的modal名字
config: UserConfig; //需要拿到componentRegister
parentDom: HTMLDivElement;
rootDom: HTMLDivElement;
}
export const unmountMap: Map<string, Function> = new Map();
export function ModalRender(props: ModalRenderProps) {
//先获取数据
const storeData: IStoreData = useMemo(() => {
const z = props.data.modalMap[props.name];
if (z) {
const data = deepCopy(z);
//需要把第一个mask扔了手动写一个
data.block.shift();
return data;
}
return { block: [] };
}, [props.data.modalMap, props.name]);
const { parentDom, rootDom } = props;
//这里还要添加个关闭函数,
const unmount = useMemo(() => {
return () => {
if (parentDom && rootDom) {
ReactDOM.unmountComponentAtNode(parentDom);
rootDom.removeChild(parentDom);
rootDom.parentElement?.removeChild(rootDom);
}
};
}, [parentDom, rootDom]);
unmountMap.set(props.name, unmount);
return (
<>
<div
className="yh-container-modal"
style={{
height: `100%`,
width: `100%`,
position: 'fixed',
overflow: 'hidden',
}}
>
{storeData.block.map((v) => {
return <Blocks key={v.id} config={props.config} data={v} context={'preview'}></Blocks>;
})}
<div
onClick={() => {
unmount();
}}
style={{
backgroundColor: '#716f6f9e',
width: '100%',
height: '100%',
}}
></div>
</div>
</>
);
}
let wrap: HTMLDivElement | null;
export const createModal = (name: string, data: IStoreData, config: UserConfig) => {
if (wrap) {
wrap = null;
}
if (!wrap) {
wrap = document.createElement('div');
wrap.style.cssText = `line-height:
1.5;text-align:
center;color: #333;
box-sizing: border-box;
margin: 0;
padding: 0;
list-style: none;
position: fixed;
z-index: 100000;
width: 100%;
height:100%;
top:0;
left: 0;`;
if (wrap) {
document.body.appendChild(wrap);
}
}
const divs = document.createElement('div');
wrap.appendChild(divs);
ReactDOM.render(
<ModalRender
name={name}
data={data}
config={config}
parentDom={divs}
rootDom={wrap}
></ModalRender>,
divs
);
};

View File

@@ -0,0 +1,55 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 05:40:37
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-08 20:44:45
* @FilePath: \DooringV2\packages\dooringx-lib\src\components\preview.tsx
*/
import Container from './container';
import React, { ReactElement, ReactNode, useEffect, useState } from 'react';
import UserConfig from '../config';
function Preview(props: { config: UserConfig; loadText?: ReactNode }): ReactElement {
const isEdit = props.config.getStoreChanger().isEdit();
/// 这里需要在渲染组件之前必须把所有config加载完成否则会导致先运行的函数无法运行
const [loading, setLoading] = useState(true);
useEffect(() => {
// 链接数据
props.config
.getDataCenter()
.initAddToDataMap(props.config.getStore().getData(), props.config.getStoreChanger());
// 链接事件
props.config
.getEventCenter()
.syncEventMap(props.config.getStore().getData(), props.config.getStoreChanger());
setTimeout(() => {
setLoading(false);
});
}, [props.config]);
if (isEdit) {
// 正常情况不会走这
const state = props.config.getStoreChanger().getOrigin()!.now;
return (
<>
<Container config={props.config} context="preview" state={state}></Container>
</>
);
} else {
if (loading) {
return <div>{props.loadText ? props.loadText : 'loading'}</div>;
} else {
return (
<>
<Container
config={props.config}
context="preview"
state={props.config.getStore().getData()}
></Container>
</>
);
}
}
}
export default Preview;

View File

@@ -0,0 +1,239 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 05:42:13
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-05 23:35:05
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\components\rightConfig.tsx
*/
import { CreateOptionsRes } from '../core/components/formTypes';
import { IBlockType, IStoreData } from '../core/store/storetype';
import { store } from '../runtime/store';
import { PropsWithChildren, useEffect, useMemo, useState } from 'react';
import React from 'react';
import { Tabs, Input, Row, Col } from 'antd';
import UserConfig from '../config';
import { RGBColor, SketchPicker } from 'react-color';
import { rgba2Obj } from '../core/utils';
import deepcopy from 'deepcopy';
interface RightConfigProps {
state: IStoreData;
config: UserConfig;
}
/**
*
* 这里一个需要异步拿取当前注册组件的配置项,另外需要异步加载所需的配置项。
* @param {*} props
* @returns
*/
function RightConfig(props: PropsWithChildren<RightConfigProps>) {
const [menuSelect, setMenuSelect] = useState('0');
const [current, setCurrent] = useState<IBlockType | null>(null);
const rightMapRenderListCategory = useMemo(() => {
return props.config.getConfig().rightRenderListCategory;
}, [props.config]);
useEffect(() => {
const fn = () => {
let item: IBlockType | undefined;
store.getData().block.some((v) => {
if (v.focus) {
item = v;
}
return v.focus === true;
});
if (item) {
setCurrent({ ...item });
} else {
setCurrent(null);
}
};
const unregist = store.subscribe(fn);
return () => {
unregist();
};
}, []);
const render = useMemo(() => {
return (type: string, current: IBlockType) => {
const fn = () => props.config.getComponentRegister().getComp(current.name);
const data = fn();
// 这里不可能拿不到组件,因为点击的那个组件已经渲染出来了
if (data) {
const renderList = data.props[type];
if (renderList) {
return renderList.map((v, i) => {
const Component = props.config.getFormRegister().formMap[v.type];
if (!Component) {
console.error(`you might forgot to regist form component ${v.type}`);
return null;
}
return (
<Component
key={i}
data={v as CreateOptionsRes<any, any>}
current={current}
config={props.config}
></Component>
);
});
} else {
return <div></div>;
}
}
return null;
};
}, [props.config]);
const initColor = useMemo(() => {
return props.config.getStoreChanger().isEdit()
? rgba2Obj(props.config.getStoreChanger().getOrigin()?.now.globalState.containerColor)
: rgba2Obj(props.config.getStore().getData().globalState.containerColor);
}, [props.config]);
const [color, setColor] = useState<RGBColor>(initColor);
const [colorPickerVisible, setColorPickerVisible] = useState(false);
const initTitle = useMemo(() => {
const title = props.config.getStoreChanger().isEdit()
? props.config.getStoreChanger().getOrigin()?.now.globalState.title
: props.config.getStore().getData().globalState.title;
return title;
}, [props.config]);
const [title, setTitle] = useState(initTitle);
const customGlobal = props.config.getConfig().rightGlobalCustom;
return (
<div
className="ant-menu"
style={{
height: '100%',
width: '400px',
overflow: 'auto',
padding: '0 10px',
lineHeight: 1.5715,
}}
>
{current && (
<Tabs
activeKey={menuSelect}
style={{ width: '100%' }}
onChange={(e) => {
setMenuSelect(e);
}}
>
{rightMapRenderListCategory.map((v, i) => {
return (
<Tabs.TabPane tab={v.icon} key={i + ''}>
<div
className="scrollbar"
style={{
height: 'calc(100vh - 110px)',
overflow: 'auto',
}}
>
{v.custom && v.customRender && v.customRender(v.type, current)}
{!v.custom && render(v.type, current)}
</div>
</Tabs.TabPane>
);
})}
</Tabs>
)}
{!current && !customGlobal && (
<div style={{ padding: '20px' }}>
<Row style={{ padding: '10 0 20px 0', fontWeight: 'bold' }}></Row>
<Row style={{ padding: '10px 0' }}>
<Col span={6}></Col>
<Col span={18}>
<Input
value={title}
onChange={(e) => {
const val = e.target.value;
setTitle(val);
const isEdit = props.config.getStoreChanger().isEdit();
if (isEdit) {
const originData: IStoreData = deepcopy(
props.config.getStoreChanger().getOrigin()!.now
);
originData.globalState.title = val;
props.config.getStoreChanger().updateOrigin(originData);
} else {
const originData = deepcopy(props.config.getStore().getData());
originData.globalState.title = val;
props.config.getStore().setData(originData);
}
}}
/>
</Col>
</Row>
<Row style={{ padding: '10px 0' }}>
<Col span={6}></Col>
<Col span={18}>
{
<div style={{ position: 'relative' }}>
<div
onClick={() => {
setColorPickerVisible((pre) => !pre);
}}
style={{
background: '#fff',
borderRadius: '1px',
boxShadow: '0 0 0 1px rgba(0,0,0,.1)',
cursor: 'pointer',
display: 'inline-block',
}}
>
<div
style={{
width: '20px',
height: '20px',
borderRadius: '2px',
background: `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`,
}}
/>
</div>
{colorPickerVisible && (
<>
<div style={{ position: 'absolute', zIndex: 2000 }}>
<SketchPicker
color={color}
onChange={(c) => {
const newcolor = c.rgb;
setColor(newcolor);
const isEdit = props.config.getStoreChanger().isEdit();
if (isEdit) {
const originData: IStoreData = deepcopy(
props.config.getStoreChanger().getOrigin()!.now
);
originData.globalState.containerColor = `rgba(${newcolor.r}, ${newcolor.g}, ${newcolor.b}, ${newcolor.a})`;
props.config.getStoreChanger().updateOrigin(originData);
} else {
const originData = deepcopy(props.config.getStore().getData());
originData.globalState.containerColor = `rgba(${newcolor.r}, ${newcolor.g}, ${newcolor.b}, ${newcolor.a})`;
props.config.getStore().setData(originData);
}
}}
></SketchPicker>
</div>
<div
style={{
position: 'fixed',
top: '0px',
right: '0px',
bottom: '0px',
left: '0px',
zIndex: 1000,
}}
onClick={() => setColorPickerVisible(false)}
/>
</>
)}
</div>
}
</Col>
</Row>
</div>
)}
{!current && customGlobal && customGlobal}
</div>
);
}
export default RightConfig;

View File

@@ -0,0 +1,68 @@
/*
* @Author: yehuozhili
* @Date: 2021-02-21 22:17:29
* @LastEditors: yehuozhili
* @LastEditTime: 2021-04-05 18:24:27
* @FilePath: \dooringv2\src\components\wrapperMove\event.ts
*/
import { store } from '../../runtime/store';
import { RefObject } from 'react';
import { containerResizer } from '../../core/resizeHandler/containerResizer';
import { contextMenuState } from '../../core/contextMenu';
export interface WrapperMoveStateProps {
isDrag: boolean;
startX: number;
startY: number;
needX: number;
needY: number;
ref: null | RefObject<HTMLDivElement>;
}
export const wrapperMoveState: WrapperMoveStateProps = {
isDrag: false,
startX: 0,
startY: 0,
needX: 0,
needY: 0,
ref: null,
};
export const wrapperEvent = (ref: RefObject<HTMLDivElement>) => {
return {
onMouseDown: (e: React.MouseEvent) => {
// e.preventDefault();// 不能使用preventDefault 否则弹窗输入框焦点无法触发
contextMenuState.unmountContextMenu();
if (e.target !== ref.current) {
} else {
wrapperMoveState.isDrag = true;
wrapperMoveState.startX = e.clientX;
wrapperMoveState.startY = e.clientY;
if (ref.current) {
ref.current.style.cursor = 'grab';
wrapperMoveState.ref = ref;
}
}
},
onMouseMove: (e: React.MouseEvent) => {
e.preventDefault();
if (wrapperMoveState.isDrag) {
const diffX = e.clientX - wrapperMoveState.startX;
const diffY = e.clientY - wrapperMoveState.startY;
wrapperMoveState.needX = wrapperMoveState.needX + diffX;
wrapperMoveState.needY = wrapperMoveState.needY + diffY;
wrapperMoveState.startX = e.clientX;
wrapperMoveState.startY = e.clientY;
store.forceUpdate();
}
containerResizer.onMouseMove(e);
},
};
};
export const wrapperMoveMouseUp = () => {
if (wrapperMoveState.ref && wrapperMoveState.ref.current) {
wrapperMoveState.ref.current.style.cursor = 'default';
}
containerResizer.onMouseUp();
wrapperMoveState.isDrag = false;
};

View File

@@ -0,0 +1,44 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 04:58:51
* @FilePath: \dooring-v2\src\core\wrapperMove\index.tsx
*/
import { AllHTMLAttributes, CSSProperties, PropsWithChildren, useRef } from 'react';
import { wrapperEvent } from './event';
import { onWheelEvent } from '../../core/scale';
import React from 'react';
export interface ContainerWrapperProps extends AllHTMLAttributes<HTMLDivElement> {
classNames?: string;
style?: CSSProperties;
}
function ContainerWrapper(props: PropsWithChildren<ContainerWrapperProps>) {
const { children, style, classNames, ...rest } = props;
const ref = useRef<HTMLDivElement>(null);
return (
<div
className={`ant-menu ${classNames}`}
ref={ref}
style={{
backgroundColor: '#f0f0f0',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flex: 1,
position: 'relative',
overflow: 'hidden',
...style,
}}
{...wrapperEvent(ref)}
{...onWheelEvent}
{...rest}
>
{children}
</div>
);
}
export default ContainerWrapper;

View File

@@ -0,0 +1,531 @@
/*
* @Author: yehuozhili
* @Date: 2021-02-25 21:16:58
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-08 20:42:22
* @FilePath: \DooringV2\packages\dooringx-lib\src\config\index.tsx
*/
import { IBlockType, IStoreData } from '../core/store/storetype';
import { store } from '../runtime/store';
import { formRegister, componentRegister, commander, storeChanger } from '../runtime';
import { ComponentClass, FunctionComponent, ReactNode } from 'react';
import { ComponentItemFactory } from '../core/components/abstract';
import { marklineConfig } from '../core/markline/marklineConfig';
import { CommanderItem } from '../core/command/commanderType';
import { contextMenuState } from '../core/contextMenu';
import { formComponentRegisterFn } from '../core/components/formComponentRegister';
import { deepCopy } from '../core/utils';
import { LeftRegistComponentMapItem } from '../core/crossDrag';
import { FunctionCenterType } from '../core/functionCenter';
import { EventCenter } from '../core/eventCenter';
import { DataCenter } from '../core/dataCenter';
import { createModal, unmountMap } from '../components/modalRender';
import { scaleState } from '../core/scale/state';
import { CommanderItemFactory } from '../core/command/abstract';
import MmodalMask from '../core/components/defaultFormComponents/modalMask';
import MmodalContainer from '../core/components/defaultFormComponents/modalContainer';
// 组件部分
/**
*
* @urlFn 组件异步加载函数
* @component 组件默认导出
* @export
* @interface CacheComponentValueType
*/
export interface CacheComponentValueType {
urlFn?: () => Promise<any>;
component?: ComponentItemFactory;
}
export type CacheComponentType = Record<string, CacheComponentValueType> | {};
export type AsyncCacheComponentType = Record<string, () => Promise<any>>;
/**
*
*
* @export 左侧的图标 custom 自定义渲染
* @interface LeftMapRenderListPropsItemCategory
*/
export interface LeftMapRenderListPropsItemCategory {
type: string;
icon: ReactNode;
custom?: boolean;
customRender?: ReactNode;
displayName?: string;
}
/**
*
*
* @export 右侧的图标配置
* @interface RightMapRenderListPropsItemCategory
*/
export interface RightMapRenderListPropsItemCategory {
type: string;
icon: ReactNode;
custom?: boolean;
customRender?: (type: string, current: IBlockType) => ReactNode;
}
// 设置部分
export interface InitConfig {
/**
* 初始化store
* @type {IStoreData[]}
* @memberof InitConfig
*/
initStoreData: IStoreData[];
/**
* 左边tab页组件渲染包括异步路径 { type: 'basic', component: 'button', img: 'http://xxxx/1.jpg' ,url:'' },
* @memberof InitConfig
*/
leftAllRegistMap: LeftRegistComponentMapItem[];
/**
* 左边tab页图标配置
* type icon custom customRender
* @memberof InitConfig
*/
leftRenderListCategory: LeftMapRenderListPropsItemCategory[];
/**
* 右边tab页图标配置
* type icon custom customRender
* @memberof InitConfig
*/
rightRenderListCategory: RightMapRenderListPropsItemCategory[];
/**
*
* 右侧全局自定义
* @type {ReactNode}
* @memberof InitConfig
*/
rightGlobalCustom: ReactNode;
/**
* 组件加载缓存判定,用来设置不异步加载的组件
* @memberof InitConfig
*/
initComponentCache: CacheComponentType;
/**
*
* 内置函数配置
* @memberof InitConfig
*/
initFunctionMap: FunctionCenterType;
/**
*
* 内置数据中心配置数据
* @memberof InitConfig
*/
initDataCenterMap: Record<string, any>;
/**
*
* commander 指令集合
* @type {Array<CommanderItemFactory>}
* @memberof InitConfig
*/
initCommandModule: Array<CommanderItemFactory>;
/**
*
* 右侧配置项
* @type {(Record<
* string,
* FunctionComponent<any> | ComponentClass<any, any>
* >)}
* @memberof InitConfig
*/
initFormComponents: Record<string, FunctionComponent<any> | ComponentClass<any, any>>;
}
export const defaultStore: IStoreData = {
container: {
width: 375,
height: 667,
},
block: [],
modalMap: {},
dataSource: {
defaultKey: 'defaultValue',
},
globalState: {
containerColor: 'rgba(255,255,255,1)',
title: 'dooring',
},
};
export const defaultConfig: InitConfig = {
initStoreData: [defaultStore],
leftAllRegistMap: [],
leftRenderListCategory: [],
rightGlobalCustom: null,
rightRenderListCategory: [],
initComponentCache: {
modalMask: { component: MmodalMask }, // 这2个组件不配置显示
modalContainer: { component: MmodalContainer },
},
initFunctionMap: {
: {
fn: (_ctx, next, config, args) => {
const modalName = args['_modal'];
const storeData = config.getStore().getData();
createModal(modalName, storeData, config);
next();
},
config: [
{
name: '弹窗名称',
data: ['modal'],
options: {
receive: '_modal',
multi: false,
},
},
],
},
: {
fn: (_ctx, next, _config, args) => {
const modalName = args['_modal'];
const fn = unmountMap.get(modalName);
fn ? fn() : '';
next();
},
config: [
{
name: '弹窗名称',
data: ['modal'],
options: {
receive: '_modal',
multi: false,
},
},
],
},
},
initDataCenterMap: {},
initCommandModule: [],
initFormComponents: {},
};
/**
*
* 部分无法合并属性如果b传了会以b为准
* initstore不合并
* leftallregistmap合并
* leftRenderListCategory合并
* rightRenderListCategory合并
* rightGlobalCustom 不合并
* initComponentCache合并
* initFunctionMap合并
* initDataCenterMap合并
* initCommandModule合并
* initFormComponents合并
*
* @export InitConfig
*/
export function userConfigMerge(a: Partial<InitConfig>, b?: Partial<InitConfig>): InitConfig {
const mergeConfig: InitConfig = {
initStoreData: [defaultStore],
leftAllRegistMap: [],
leftRenderListCategory: [],
rightRenderListCategory: [],
initComponentCache: {},
initFunctionMap: {},
initDataCenterMap: {},
initCommandModule: [],
rightGlobalCustom: null,
initFormComponents: {},
};
if (!b) {
return userConfigMerge(mergeConfig, a);
}
mergeConfig.initStoreData = b.initStoreData
? [...b.initStoreData]
: a.initStoreData
? [...a.initStoreData]
: [defaultStore];
mergeConfig.rightGlobalCustom = b.rightGlobalCustom ? b.rightGlobalCustom : a.rightGlobalCustom;
mergeConfig.leftAllRegistMap = b.leftAllRegistMap
? a.leftAllRegistMap
? [...a.leftAllRegistMap, ...b.leftAllRegistMap]
: [...b.leftAllRegistMap]
: a.leftAllRegistMap
? [...a.leftAllRegistMap]
: [];
mergeConfig.leftRenderListCategory = b.leftRenderListCategory
? a.leftRenderListCategory
? [...a.leftRenderListCategory, ...b.leftRenderListCategory]
: [...b.leftRenderListCategory]
: a.leftRenderListCategory
? [...a.leftRenderListCategory]
: [...defaultConfig.leftRenderListCategory];
mergeConfig.rightRenderListCategory = b.rightRenderListCategory
? a.rightRenderListCategory
? [...a.rightRenderListCategory, ...b.rightRenderListCategory]
: [...b.rightRenderListCategory]
: a.rightRenderListCategory
? [...a.rightRenderListCategory]
: [...defaultConfig.rightRenderListCategory];
mergeConfig.initComponentCache = {
...a.initComponentCache,
...b.initComponentCache,
};
mergeConfig.initFunctionMap = {
...a.initFunctionMap,
...b.initFunctionMap,
};
mergeConfig.initFormComponents = {
...a.initFormComponents,
...b.initFormComponents,
};
mergeConfig.initDataCenterMap = {
...a.initDataCenterMap,
...b.initDataCenterMap,
};
mergeConfig.initCommandModule = b.initCommandModule
? a.initCommandModule
? [...a.initCommandModule, ...b.initCommandModule]
: [...b.initCommandModule]
: a.initCommandModule
? [...a.initCommandModule]
: [];
return mergeConfig;
}
/**
*
*
* @export 用户配置项
* @class UserConfig
*/
export class UserConfig {
public initConfig: InitConfig;
public store = store;
public componentRegister = componentRegister;
public componentCache = {};
public asyncComponentUrlMap = {} as AsyncCacheComponentType;
public marklineConfig = marklineConfig;
public commanderRegister = commander;
public contextMenuState = contextMenuState;
public formRegister = formRegister;
public storeChanger = storeChanger;
public eventCenter: EventCenter;
public dataCenter: DataCenter;
public scaleState = scaleState;
constructor(initConfig?: Partial<InitConfig>) {
const mergeConfig = userConfigMerge(defaultConfig, initConfig);
this.initConfig = mergeConfig;
this.eventCenter = new EventCenter({}, mergeConfig.initFunctionMap);
this.dataCenter = new DataCenter(mergeConfig.initDataCenterMap);
this.init();
// 右侧配置项注册 初始注册组件暂时固定
}
toRegist() {
const modules = this.initConfig.initFormComponents;
formComponentRegisterFn(formRegister, modules);
const cache = this.initConfig.initComponentCache;
this.componentCache = cache;
// 拿到组件缓存后先同步加载map上组件
Object.values(cache).forEach((v) => {
if ((v as CacheComponentValueType).component) {
this.registComponent((v as CacheComponentValueType).component!);
}
});
// 异步组件注册地址
this.initConfig.leftAllRegistMap.forEach((v) => {
if (v.urlFn) {
//@ts-ignore
this.asyncComponentUrlMap[v.component] = v.urlFn;
}
});
// 注册画布上组件
this.store.getData().block.forEach((v) => {
this.asyncRegistComponent(v.name);
});
// 注册data
this.dataCenter = new DataCenter(this.initConfig.initDataCenterMap);
//数据需要加上store上的
this.dataCenter.initAddToDataMap(this.store.getData(), this.storeChanger);
// 修改事件与数据初始
this.eventCenter = new EventCenter({}, this.initConfig.initFunctionMap);
// 注册画布事件
this.eventCenter.syncEventMap(this.store.getData(), this.storeChanger);
}
init() {
this.store.resetToInitData(deepCopy(this.initConfig.initStoreData), true);
this.toRegist();
}
getStoreJSON() {
return JSON.stringify(this.store.getData());
}
parseStoreJson(json: string) {
return JSON.parse(json);
}
resetData(data: IStoreData[]) {
this.store.resetToInitData(data, true);
this.toRegist();
}
getScaleState() {
return this.scaleState;
}
getDataCenter() {
return this.dataCenter;
}
getEventCenter() {
return this.eventCenter;
}
getStoreChanger() {
return this.storeChanger;
}
getConfig() {
return this.initConfig;
}
getStore() {
return this.store;
}
getComponentRegister() {
return this.componentRegister;
}
getContextMenuState() {
return this.contextMenuState;
}
getFormRegister() {
return this.formRegister;
}
getCommanderRegister() {
return this.commanderRegister;
}
/**
*
* 以默认设置重置配置项
* @param {Partial<InitConfig>} v
* @memberof UserConfig
*/
resetConfig(v: Partial<InitConfig>) {
const mergeConfig = userConfigMerge(defaultConfig, v);
this.initConfig = mergeConfig;
this.init();
store.forceUpdate();
}
/**
* 会重置配置,请修改配置后增加
* 异步增加左侧tab页
* @memberof UserConfig
*/
addLeftCategory(v: LeftMapRenderListPropsItemCategory[]) {
const obj = {} as InitConfig;
obj.leftRenderListCategory = v;
this.initConfig = userConfigMerge(this.initConfig, obj);
this.init();
store.forceUpdate();
}
/**
* 会重置配置,请修改配置后增加
* 异步增加右侧tab页
* @memberof UserConfig
*/
addRightCategory(v: RightMapRenderListPropsItemCategory[]) {
const obj = {} as InitConfig;
obj.rightRenderListCategory = v;
this.initConfig = userConfigMerge(this.initConfig, obj);
this.init();
store.forceUpdate();
}
/**
* 会重置配置,请修改配置后增加
* 异步增加组件map
* @memberof UserConfig
*/
addCoRegistMap(v: LeftRegistComponentMapItem) {
const obj = {} as InitConfig;
obj.leftAllRegistMap = [v];
this.initConfig = userConfigMerge(this.initConfig, obj);
this.init();
store.forceUpdate();
}
/**
*会重置配置,请修改配置后增加
* 异步修改config 重置store
* @memberof UserConfig
*/
setConfig(v: Partial<InitConfig>) {
this.initConfig = userConfigMerge(this.initConfig, v);
this.init();
store.forceUpdate();
}
/**
*
* 同步注册指令
* @param {CommanderItem} command
* @memberof UserConfig
*/
registCommander(command: CommanderItem) {
this.commanderRegister.register(command);
}
/**
*
* 用于修改markline配置
* @returns
* @memberof UserConfig
*/
getMarklineConfig() {
return this.marklineConfig;
}
getComponentCache() {
return this.componentCache;
}
/**
*
* 同步注册组件,不会检测缓存是否存在
* @param {ComponentItemFactory} item
* @memberof UserConfig
*/
registComponent(item: ComponentItemFactory) {
this.componentRegister.register(item);
}
/**
*
* 异步注册组件,会判定缓存是否存在
* @param {string} name
* @memberof UserConfig
*/
async asyncRegistComponent(name: string) {
//判定缓存
if (
!(this.componentCache as Record<string, CacheComponentValueType>)[name] &&
this.asyncComponentUrlMap[name]
) {
const chunk = await this.asyncComponentUrlMap[name]();
const chunkDefault = chunk.default;
this.componentRegister.register(chunkDefault);
(this.componentCache as Record<string, CacheComponentValueType>)[name] = {
component: chunkDefault,
};
this.componentRegister.emitEvent(name);
}
}
}
export default UserConfig;

View File

@@ -0,0 +1,19 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 04:39:30
* @FilePath: \dooring-v2\src\core\command\abstract.ts
*/
import { CommanderItem } from './commanderType';
export class CommanderItemFactory implements CommanderItem {
constructor(
public name: CommanderItem['name'],
public keyboard: CommanderItem['keyboard'],
public excute: CommanderItem['excute'],
public display: CommanderItem['display'],
public init: CommanderItem['init'] = () => {},
public destroy: CommanderItem['destroy'] = () => {}
) {}
}

View File

@@ -0,0 +1,17 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 04:39:43
* @FilePath: \dooring-v2\src\core\command\commanderType.ts
*/
import Store from '../store';
export interface CommanderItem {
init: () => void;
display: string;
name: string;
keyboard: string;
excute: (store: Store, options?: any) => void;
destroy: () => void;
}

View File

@@ -0,0 +1,84 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-08 20:42:15
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\command\index.ts
*/
import Store from '../store';
import { CommanderItem } from './commanderType';
import { keycodeFilter } from './keycode';
class CommanderWrapper {
constructor(public store: Store, public commandMap: Record<string, CommanderItem> = {}) {}
getList() {
return this.commandMap;
}
register(item: CommanderItem) {
item.init();
if (this.commandMap[item.name]) {
// console.error(`${item.name} commander has registed`);
return;
}
this.commandMap[item.name] = item;
//注册快捷键快捷键执行调用excute
const remove = this.registerKeyBoard(item);
//改写销毁方法
const origindestroy = item.destroy;
const newdestroy = () => {
remove();
origindestroy();
};
item.destroy = newdestroy;
}
registerKeyBoard(current: CommanderItem) {
if (current.keyboard.length === 0) {
return () => {};
}
const onKeydown = (e: KeyboardEvent) => {
if (document.activeElement !== document.body && e.target !== document.body) {
return;
}
const { shiftKey, altKey, ctrlKey, metaKey, key } = e;
const keyString: string[] = [];
if (ctrlKey || metaKey) keyString.push('Control');
if (shiftKey) keyString.push('Shift');
if (altKey) keyString.push('Alt');
if (key !== 'Control' && key !== 'Shift' && key !== 'Alt') {
const res = keycodeFilter(key);
if (res !== undefined) {
keyString.push(res);
} else {
keyString.push(key);
}
}
const keyname = keyString.join('+');
if (current.keyboard === keyname) {
current.excute(this.store);
e.stopPropagation();
e.preventDefault();
}
};
window.addEventListener('keydown', onKeydown);
return () => window.removeEventListener('keydown', onKeydown);
}
unRegister(name: string) {
if (!this.commandMap[name]) {
console.error(`${name} commander not found`);
return;
}
const item = this.commandMap[name];
item.destroy();
delete this.commandMap[item.name];
}
exec(name: string, options?: any) {
if (!this.commandMap[name]) {
console.error(`${name} commander not found`);
return;
}
this.commandMap[name].excute(this.store, options);
}
}
export default CommanderWrapper;

View File

@@ -0,0 +1,225 @@
export default {
Cancel: 3,
Help: 6,
Backspace: 8,
Tab: 9,
Clear: 12,
Enter: 13,
Shift: 16,
Control: 17,
Alt: 18,
Pause: 19,
CapsLock: 20,
Escape: 27,
Convert: 28,
NonConvert: 29,
Accept: 30,
ModeChange: 31,
' ': 32,
PageUp: 33,
PageDown: 34,
End: 35,
Home: 36,
ArrowLeft: 37,
ArrowUp: 38,
ArrowRight: 39,
ArrowDown: 40,
Select: 41,
Print: 42,
Execute: 43,
PrintScreen: 44,
Insert: 45,
Delete: 46,
0: 48,
')': 48,
1: 49,
'!': 49,
2: 50,
'@': 50,
3: 51,
'#': 51,
4: 52,
$: 52,
5: 53,
'%': 53,
6: 54,
'^': 54,
7: 55,
'&': 55,
8: 56,
'*': 56,
9: 57,
'(': 57,
a: 65,
A: 65,
b: 66,
B: 66,
c: 67,
C: 67,
d: 68,
D: 68,
e: 69,
E: 69,
f: 70,
F: 70,
g: 71,
G: 71,
h: 72,
H: 72,
i: 73,
I: 73,
j: 74,
J: 74,
k: 75,
K: 75,
l: 76,
L: 76,
m: 77,
M: 77,
n: 78,
N: 78,
o: 79,
O: 79,
p: 80,
P: 80,
q: 81,
Q: 81,
r: 82,
R: 82,
s: 83,
S: 83,
t: 84,
T: 84,
u: 85,
U: 85,
v: 86,
V: 86,
w: 87,
W: 87,
x: 88,
X: 88,
y: 89,
Y: 89,
z: 90,
Z: 90,
OS: 91,
ContextMenu: 93,
F1: 112,
F2: 113,
F3: 114,
F4: 115,
F5: 116,
F6: 117,
F7: 118,
F8: 119,
F9: 120,
F10: 121,
F11: 122,
F12: 123,
F13: 124,
F14: 125,
F15: 126,
F16: 127,
F17: 128,
F18: 129,
F19: 130,
F20: 131,
F21: 132,
F22: 133,
F23: 134,
F24: 135,
NumLock: 144,
ScrollLock: 145,
VolumeMute: 181,
VolumeDown: 182,
VolumeUp: 183,
';': 186,
':': 186,
'=': 187,
'+': 187,
',': 188,
'<': 188,
'-': 189,
_: 189,
'.': 190,
'>': 190,
'/': 191,
'?': 191,
'`': 192,
'~': 192,
'[': 219,
'{': 219,
'\\': 220,
'|': 220,
']': 221,
'}': 221,
"'": 222,
'"': 222,
Meta: 224,
AltGraph: 225,
Attn: 246,
CrSel: 247,
ExSel: 248,
EraseEof: 249,
Play: 250,
ZoomOut: 251,
};
const keymap: Record<string, string> = {
a: 'a',
A: 'a',
b: 'b',
B: 'b',
c: 'c',
C: 'c',
d: 'd',
D: 'd',
e: 'e',
E: 'e',
f: 'f',
F: 'f',
g: 'g',
G: 'g',
h: 'h',
H: 'h',
i: 'i',
I: 'i',
j: 'j',
J: 'j',
k: 'k',
K: 'k',
l: 'l',
L: 'l',
m: 'm',
M: 'm',
n: 'n',
N: 'n',
o: 'o',
O: 'o',
p: 'p',
P: 'p',
q: 'q',
Q: 'q',
r: 'r',
R: 'r',
s: 's',
S: 's',
t: 't',
T: 't',
u: 'u',
U: 'u',
v: 'v',
V: 'v',
w: 'w',
W: 'w',
x: 'x',
X: 'x',
y: 'y',
Y: 'y',
z: 'z',
Z: 'z',
};
export const keycodeFilter = (key: string) => {
return keymap[key];
};

View File

@@ -0,0 +1,20 @@
/*
* @Author: yehuozhili
* @Date: 2021-07-04 14:18:18
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-04 14:21:33
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\command\runtime.ts
*/
import CommanderWrapper from '.';
import { CommanderItemFactory } from './abstract';
export const registCommandFn = (module: CommanderItemFactory[], commander: CommanderWrapper) => {
module.forEach((v) => {
commander.register(v);
});
};
export const unRegistCommandFn = (module: CommanderItemFactory[], commander: CommanderWrapper) => {
module.forEach((v) => {
commander.unRegister(v.name);
});
};

View File

@@ -0,0 +1,22 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-04-04 23:14:00
* @FilePath: \dooringv2\src\core\components\abstract.ts
*/
import { ComponentItem } from './componentItem';
export class ComponentItemFactory implements ComponentItem {
constructor(
public name: ComponentItem['name'],
public display: ComponentItem['display'],
public props: ComponentItem['props'],
public initData: ComponentItem['initData'],
public render: ComponentItem['render'],
public resize: ComponentItem['resize'] = true,
public needPosition: ComponentItem['needPosition'] = true,
public init: ComponentItem['init'] = () => {},
public destroy: ComponentItem['destroy'] = () => {}
) {}
}

View File

@@ -0,0 +1,35 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-06 23:45:21
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\core\components\componentItem.ts
*/
import UserConfig from '../../config';
import Store from '../store';
import { IBlockType } from '../store/storetype';
import { CreateOptionsResAll } from './formTypes';
/**
*
* 包装部分配置,渲染配置,条件渲染,属性
* @export
* @interface ComponentItem
*/
export interface ComponentItem {
init: () => void;
name: string; // map上key名
display: string; //显示名称
resize: boolean;
needPosition: boolean; //是否要使用拖拽的点
initData: Partial<IBlockType>; //初始值
props: Record<string, CreateOptionsResAll[]>; // 配置属性
render: (data: IBlockType, context: any, store: Store, config: UserConfig) => JSX.Element;
destroy: () => void;
}
export type ComponentRenderConfigProps = {
data: IBlockType;
context: any;
store: Store;
config: UserConfig;
};

View File

@@ -0,0 +1,34 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 16:36:42
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\components\createBlock.ts
*/
import { IBlockType } from '../store/storetype';
import { createUid } from '../utils';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { ComponentItem } from './componentItem';
export function createBlock(top: number, left: number, ComponentItem: ComponentItem): IBlockType {
return {
id: createUid(ComponentItem.name),
name: ComponentItem.name,
top,
left,
zIndex: ComponentItem.initData.zIndex || 0,
props: ComponentItem.initData.props || {},
resize: ComponentItem.initData.resize || ComponentItem.resize,
focus: false,
position: ComponentItem.initData.position || 'absolute',
display: ComponentItem.initData.display || 'block',
width: ComponentItem.initData.width,
height: ComponentItem.initData.height,
syncList: ComponentItem.initData.syncList || [],
canDrag: ComponentItem.initData.canDrag ?? true,
eventMap: ComponentItem.initData.eventMap || {},
functionList: ComponentItem.initData.functionList || [],
animate: ComponentItem.initData.animate || {},
fixed: ComponentItem.initData.fixed || false,
};
}

View File

@@ -0,0 +1,34 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-05 19:21:36
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-05 23:56:07
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\components\defaultFormComponents\modalContainer.tsx
*/
import React from 'react';
import { ComponentItemFactory } from '../abstract';
const MmodalContainer = new ComponentItemFactory(
'modalContainer',
'模态框容器',
{},
{
props: {},
width: 300,
height: 300,
},
(data) => {
return (
<div
style={{
zIndex: data.zIndex,
width: data.width,
height: data.height,
backgroundColor: 'white',
}}
></div>
);
}
);
export default MmodalContainer;

View File

@@ -0,0 +1,59 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-04 20:35:11
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-05 23:55:53
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\components\defaultFormComponents\modalMask.tsx
*/
import { SaveOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import React from 'react';
import { ComponentItemFactory } from '../abstract';
const MmodalMask = new ComponentItemFactory(
'modalMask',
'模态框遮罩',
{},
{
props: {},
position: 'fixed',
top: 0,
left: 0,
zIndex: 999,
width: '100%',
height: '100%',
canDrag: false,
},
(_, context, store, config) => {
const container = store.getData().container;
return (
<div
style={{
width: context === 'preview' ? '100%' : container.width,
height: context === 'preview' ? '100%' : container.height,
backgroundColor: '#716f6f9e',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{context === 'edit' && (
<Button
type="primary"
shape="circle"
title="save"
style={{ position: 'absolute', right: '10px', top: '10px' }}
icon={<SaveOutlined></SaveOutlined>}
onClick={() => {
config.getStoreChanger().closeModal(config.getStore());
}}
></Button>
)}
</div>
);
},
true,
false
);
export default MmodalMask;

View File

@@ -0,0 +1,70 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: chentianshang
* @LastEditTime: 2021-07-06 09:53:29
* @FilePath: /DooringV2/packages/dooring-v2-lib/src/core/components/formComponentRegister.ts
*/
import { ComponentClass, FunctionComponent } from 'react';
import { CreateOptionsRes } from './formTypes';
export interface ContainerConfigItem {
type: string;
option: CreateOptionsRes<any, any>;
}
export const formComponentRegisterFn = (
formComponent: FormComponentRegister,
modules: Record<string, FunctionComponent<any> | ComponentClass<any, any>>
) => {
Object.keys(modules).forEach((v) => {
formComponent.register(v, modules[v]);
});
};
/**
*
* 拿到form组件地址和状态
* 获取配置container配置项和普通组件配置项
* @export
* @class FormComponentRegister
*/
export class FormComponentRegister {
constructor(
public formMap: Record<string, FunctionComponent<any> | ComponentClass<any, any>> = {},
public listener: Function[] = [],
public eventMap: Record<string, Function[]> = {},
public containerConfig: Array<ContainerConfigItem> = []
) {}
getMap() {
return this.formMap;
}
getComp(name: string) {
return this.formMap[name];
}
getConfig() {
return this.containerConfig;
}
setConfig(config: Array<ContainerConfigItem>) {
this.containerConfig = config;
}
/**
*
* 同步注册方法
* @memberof FormComponentRegister
*/
register(name: string, ele: FunctionComponent<any> | ComponentClass<any, any>) {
this.formMap[name] = ele;
}
emit() {
this.listener.forEach((v) => v());
}
on(event: string, fn: Function) {
if (!this.eventMap[event]) {
this.eventMap[event] = [];
}
this.eventMap[event].push(fn);
return () => this.eventMap[event].filter((v) => v !== fn);
}
}

View File

@@ -0,0 +1,27 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-06 23:45:02
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\core\components\formTypes.ts
*/
export interface CreateOptionsResAll {
type: string;
option: any;
}
export interface CreateOptionsRes<T, K extends keyof T> {
type: keyof T;
option: T[K];
}
export function createPannelOptions<T, K extends keyof T>(
type: K,
option: T[K]
): CreateOptionsRes<T, K> {
return {
type,
option,
};
}

View File

@@ -0,0 +1,79 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 04:43:53
* @FilePath: \dooring-v2\src\core\components\index.ts
*/
import { ComponentItem } from './componentItem';
/**
*
* 注册组件需要异步的,由注册时效果决定。
* 主要是存放所有已注册组件。可以在其render时提供对应context
* @class ComponentRegister
*/
class ComponentRegister {
constructor(
public componentMap: Record<string, ComponentItem> = {},
public componentList: ComponentItem[] = [],
public listener: Function[] = [],
public eventMap: Record<string, Function[]> = {}
) {}
getMap() {
return this.componentMap;
}
getList() {
return this.componentList;
}
getComp(name: string) {
return this.componentMap[name];
}
subscribe(fn: Function) {
this.listener.push(fn);
return () => this.listener.filter((v) => v !== fn);
}
emit() {
this.listener.forEach((v) => v());
}
on(event: string, fn: Function) {
if (!this.eventMap[event]) {
this.eventMap[event] = [];
}
this.eventMap[event].push(fn);
return () => this.eventMap[event].filter((v) => v !== fn);
}
emitEvent(event: string) {
if (!this.eventMap[event]) {
return;
}
this.eventMap[event].forEach((v) => v());
}
register(item: ComponentItem) {
if (this.componentMap[item.name]) {
// console.error(`${item.name} component has registed`);
return;
}
this.componentMap[item.name] = item;
this.componentList.push(item);
this.emit();
item.init();
}
unRegister(name: string) {
if (!this.componentMap[name]) {
console.error(`${name} component not found`);
return;
}
const item = this.componentMap[name];
item.destroy();
this.emit();
this.componentList = this.componentList.filter((v) => v !== item);
delete this.componentMap[item.name];
}
}
export default ComponentRegister;

View File

@@ -0,0 +1,132 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-05 22:46:15
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\contextMenu\index.tsx
*/
import { Button } from 'antd';
import React, { RefObject, useState } from 'react';
import { ReactElement } from 'react';
import ReactDOM, { unmountComponentAtNode } from 'react-dom';
import { scaleState } from '../scale/state';
import { isMac } from '../utils';
const ContextMenu = () => {
const handleclick = () => {
unmountContextMenu();
};
const forceUpdate = useState(0)[1];
contextMenuState.forceUpdate = () => {
forceUpdate((pre) => pre + 1);
};
return (
<div
style={{
left: contextMenuState.left,
top: contextMenuState.top,
position: 'fixed',
background: 'rgb(24, 23, 23)',
}}
>
<Button
onClick={() => {
handleclick();
}}
>
</Button>
</div>
);
};
export interface ContextMenuStateProps {
left: number;
top: number;
menu: HTMLElement | null;
parent: HTMLDivElement | null;
contextMenu: ReactElement;
unmountContextMenu: () => void;
observer: null | MutationObserver;
initLeft: number;
initTop: number;
forceUpdate: Function;
state: boolean;
}
export const contextMenuState: ContextMenuStateProps = {
left: 0,
top: 0,
menu: null,
parent: null,
contextMenu: <ContextMenu />,
unmountContextMenu: unmountContextMenu,
observer: null,
initLeft: 0,
initTop: 0,
forceUpdate: () => {},
state: false,
};
export function contextMenuEvent(
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
ref: RefObject<HTMLDivElement>
) {
e.preventDefault();
contextMenuState.unmountContextMenu();
const config: MutationObserverInit = {
attributes: true,
};
const callback: MutationCallback = (mutationsList) => {
if (isMac()) {
//mac 有bug
contextMenuState.unmountContextMenu();
} else {
for (let mutation of mutationsList) {
if (mutation.type === 'attributes') {
const scale = scaleState.value;
const curLeft = parseFloat((mutation.target as HTMLDivElement).style.left);
const curTop = parseFloat((mutation.target as HTMLDivElement).style.top);
const diffL = (curLeft - contextMenuState.initLeft) * scale;
const diffT = (curTop - contextMenuState.initTop) * scale;
contextMenuState.initLeft = curLeft;
contextMenuState.initTop = curTop;
contextMenuState.left = contextMenuState.left + diffL;
contextMenuState.top = contextMenuState.top + diffT;
contextMenuState.forceUpdate();
}
}
}
};
contextMenuState.state = true;
contextMenuState.observer = new MutationObserver(callback);
if (ref.current) {
//记录初始值
contextMenuState.initTop = parseFloat(ref.current.style.top);
contextMenuState.initLeft = parseFloat(ref.current.style.left);
contextMenuState.observer.observe(ref.current, config);
}
contextMenuState.left = e.clientX;
contextMenuState.top = e.clientY;
if (!contextMenuState.menu) {
contextMenuState.menu = document.createElement('div');
document.body && document.body.appendChild(contextMenuState.menu);
}
if (!contextMenuState.parent) {
contextMenuState.parent = document.createElement('div');
}
contextMenuState.menu.appendChild(contextMenuState.parent);
ReactDOM.render(contextMenuState.contextMenu, contextMenuState.parent);
}
export function unmountContextMenu() {
contextMenuState.state = false;
if (contextMenuState.observer) {
contextMenuState.observer.disconnect();
}
if (contextMenuState.menu && contextMenuState.parent) {
unmountComponentAtNode(contextMenuState.parent);
contextMenuState.menu.removeChild(contextMenuState.parent);
contextMenuState.parent = null;
}
}
export default ContextMenu;

View File

@@ -0,0 +1,84 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-04 15:21:54
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\crossDrag\index.ts
*/
import { store } from '../../runtime/store';
import { componentRegister } from '../../runtime';
import { DragEvent, ReactNode } from 'react';
import { createBlock } from '../components/createBlock';
import { IBlockType } from '../store/storetype';
import { deepCopy } from '../utils';
/**
*
* @export
* @interface LeftRegistComponentMapItem
* @img 图片地址
* @urlFn 组件异步加载函数
*/
export interface LeftRegistComponentMapItem {
type: string;
component: string;
img: string;
imgCustom?: ReactNode;
displayName: string;
urlFn?: () => Promise<any>;
}
let currentDrag: LeftRegistComponentMapItem | null = null;
export const dragEventResolve = function (item: LeftRegistComponentMapItem) {
return {
draggable: true,
onDragStart: () => {
currentDrag = item;
},
onDragOver: (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
},
onDrop: () => {},
onDragEnd: () => {},
};
};
export const containerDragResolve = {
onDragStart: () => {},
onDragOver: (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
},
onDrop: (e: DragEvent<HTMLDivElement>) => {
const offsetX = e.nativeEvent.offsetX;
const offestY = e.nativeEvent.offsetY;
//drop后修改store
if (currentDrag) {
// 还需要拿到注册的组件状态
const origin = componentRegister.getComp(currentDrag.component);
if (!origin) {
console.log(currentDrag.component, 'wait the chunk pull compeletely and retry');
return;
}
const target = e.target as HTMLElement;
let newblock: IBlockType;
if (!origin.needPosition) {
newblock = createBlock(
origin.initData.top ?? offestY,
origin.initData.left ?? offsetX,
origin
);
} else {
if (target.id !== 'yh-container') {
newblock = createBlock(offestY + target.offsetTop, offsetX + target.offsetLeft, origin);
} else {
newblock = createBlock(offestY, offsetX, origin);
}
}
const data = deepCopy(store.getData());
data.block.push(newblock);
store.setData({ ...data });
}
currentDrag = null;
},
onDragEnd: () => {},
};

View File

@@ -0,0 +1,123 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-13 11:20:55
* @LastEditors: yehuozhili
* @LastEditTime: 2021-05-04 20:34:36
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\core\dataCenter\index.ts
*/
import UserConfig from '../../config';
import { IStoreData } from '../store/storetype';
import { StoreChanger } from '../storeChanger';
/**
*
* 用来管理页面数据,包括全局数据,做全局设置变量时可以加上
* 使用Record<string,any>结构,每个组件的数据需要抛出并设定键进行通信。
* @export
* @class DataCenter
*/
export class DataCenter {
public asyncMap: Record<string, Function> = {};
constructor(public dataMap: Record<string, any> = {}) {}
/**
*
* 拿到map
* @return {*}
* @memberof DataCenter
*/
getDataMap() {
return this.dataMap;
}
/**
*
* 用于设置map数据
* 在异步注册时会触发get的回调动态不需要持久化
* @memberof DataCenter
*/
setToMap(data: Record<string, any>) {
this.dataMap = Object.assign(this.dataMap, data);
Object.keys(data).forEach((v) => {
if (this.asyncMap[v]) {
this.asyncMap[v]();
delete this.asyncMap[v];
}
});
}
/**
*
* 静态设置map 和异步无关 静态需要持久化datacenter存入store
* 该更新不放在redo undo处
* @param {Record<string, any>} data
* @memberof DataCenter
*/
staticSetToMap(data: Record<string, any>, config: UserConfig) {
this.dataMap = data;
const storeChanger = config.getStoreChanger();
const store = config.getStore();
const storeCurrentData = store.getData();
const sign = storeChanger.isEdit();
if (sign) {
const originData = storeChanger.getOrigin();
if (originData) {
const currentData = originData.data[originData.current];
currentData.dataSource = data;
}
} else {
storeCurrentData.dataSource = data;
}
}
/**
*
* 初始收集使用 -> to datacenter
* @param {IStoreData} data
* @memberof DataCenter
*/
initAddToDataMap(data: IStoreData, storeChanger: StoreChanger) {
const sign = storeChanger.isEdit();
//这里只能初始触发,一般不会走编辑状态,否则逻辑可能会有问题
if (sign) {
// 编辑状态收集orgin
const originData = storeChanger.getOrigin();
if (originData) {
const currentData = originData.data[originData.current];
this.dataMap = currentData.dataSource;
}
} else {
this.dataMap = data.dataSource;
}
}
/**
*
* 获取值可异步
* @param {string} name
* @memberof DataCenter
*/
getValue(name: string) {
const value = this.dataMap[name];
if (value) {
return Promise.resolve(value);
}
return new Promise((resolve) => {
this.asyncMap[name] = () => {
resolve(this.getValue(name));
};
});
}
/**
*
* 获取值不可异步
* @param {string} name
* @memberof DataCenter
*/
get(name: string) {
const value = this.dataMap[name];
return value;
}
}

View File

@@ -0,0 +1,72 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-08 20:22:43
* @LastEditors: yehuozhili
* @LastEditTime: 2021-06-28 16:08:56
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\eventCenter\eventQuene.ts
*/
import { EventCenterMapItem, EventCenterUserSelect } from '.';
import UserConfig from '../../config';
import { FunctionCenterFunction } from '../functionCenter';
export class EventQuene {
available: number;
waiters: Array<{
fn: FunctionCenterFunction;
args: any;
eventList: {
arr: Array<EventCenterMapItem>;
displayName: string;
userSelect: Array<EventCenterUserSelect>;
};
iname: EventCenterMapItem;
}>;
context: Record<string, any>;
config: UserConfig;
constructor(available: number = 1, config: UserConfig, context = {}) {
this.available = available;
this.waiters = [];
this._continue = this._continue.bind(this);
this.context = context;
this.config = config;
}
take(
task: FunctionCenterFunction,
args: Record<string, any>,
eventList: {
arr: Array<EventCenterMapItem>;
displayName: string;
userSelect: Array<EventCenterUserSelect>;
},
iname: EventCenterMapItem
) {
if (this.available > 0) {
this.available--;
task(this.context, this.leave.bind(this), this.config, args, eventList, iname);
} else {
this.waiters.push({ fn: task, args: args, eventList, iname });
}
}
leave() {
this.available++;
if (this.waiters.length > 0) {
this._continue();
}
}
_continue() {
if (this.available > 0) {
this.available--;
let task = this.waiters.shift();
if (task?.fn) {
task.fn(
this.context,
this.leave.bind(this),
this.config,
task.args,
task.eventList,
task.iname
);
}
}
}
}

View File

@@ -0,0 +1,149 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-06 19:33:17
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-04 14:41:48
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\eventCenter\index.ts
*/
import UserConfig from '../../config';
import { FunctionCenter, FunctionCenterType } from '../functionCenter';
import { FunctionDataType } from '../functionCenter/config';
import { IStoreData } from '../store/storetype';
import { StoreChanger } from '../storeChanger';
import { EventQuene } from './eventQuene';
// 每个组件制作时可以抛出多个事件事件名为id+自定义name
// 每个组件可以抛出多个函数,存在函数中心
export interface EventCenterMapItem {
name: string; // 函数名
args: Record<string, any>; // 输入参数都会变成对象传来,
data: Record<string, FunctionDataType>; // 用户选的种类 键是每个配置项名
}
export interface EventCenterUserSelect {
uid: string;
value: string;
detail: Record<string, any>;
}
export type EventCenterMapType = Record<
string,
{
arr: Array<EventCenterMapItem>;
displayName: string;
userSelect: Array<EventCenterUserSelect>;
}
>;
export class EventCenter {
/**
* 该map需要存入store,值为函数的key的数组
* @param {Record<string, Array<string>>} [eventMap={}]
* @memberof EventCenter
*/
public functionCenter: FunctionCenter;
constructor(public eventMap: EventCenterMapType = {}, configFunction?: FunctionCenterType) {
this.functionCenter = new FunctionCenter(configFunction);
}
getFunctionCenter() {
return this.functionCenter;
}
getEventMap() {
return this.eventMap;
}
resetEventMap() {
this.eventMap = {};
}
/**
*
* 重置map进行收集事件 主要就是收集eventMap字段
* 这个应该优化在换store情况下。
* @param {IStoreData} data
* @memberof EventCenter
*/
syncEventMap(data: IStoreData, storeChanger: StoreChanger) {
// 需要判断是否在弹窗状态。如果在弹窗状态数据以storeChanger为准否则就以store为准
const sign = storeChanger.isEdit();
this.eventMap = {};
if (sign) {
const originData = storeChanger.getOrigin();
if (originData) {
const currentData = originData.data[originData.current];
// 收集源block数据
currentData.block.forEach((v) => {
this.eventMap = Object.assign(this.eventMap, v.eventMap);
});
//收集源modal数据
Object.keys(currentData.modalMap).forEach((v) => {
currentData.modalMap[v].block.forEach((k) => {
this.eventMap = Object.assign(this.eventMap, k.eventMap);
});
});
//收集当前modal数据
data.block.forEach((v) => {
this.eventMap = Object.assign(this.eventMap, v.eventMap);
});
}
} else {
data.block.forEach((v) => {
this.eventMap = Object.assign(this.eventMap, v.eventMap);
});
Object.keys(data.modalMap).forEach((v) => {
data.modalMap[v].block.forEach((k) => {
this.eventMap = Object.assign(this.eventMap, k.eventMap);
});
});
}
}
/**
*
* 手动更新状态eventMap
* @param {string} name
* @memberof EventCenter
*/
manualUpdateMap(name: string, displayName: string, arr?: Array<EventCenterMapItem>) {
if (!this.eventMap[name]) {
this.eventMap[name] = {
arr: [],
displayName: displayName,
userSelect: [],
};
}
if (arr && this.eventMap[name].displayName) {
this.eventMap[name].arr = arr;
} else if (arr && this.eventMap[name]) {
this.eventMap[name] = {
displayName,
arr,
userSelect: [],
};
}
}
/**
*
* 执行事件链
* @param {string} name
* @memberof EventCenter
*/
async runEventQueue(name: string, config: UserConfig) {
const eventList = this.eventMap[name];
if (!eventList) {
console.error(`未查询到该事件${name}`);
return;
}
const arr = new EventQuene(1, config);
//如果组件异步加载,那么函数会过段时间载入,等同于异步函数
// 函数中心需要处理未找到时的异步处理情况
if (Array.isArray(eventList.arr)) {
for (let i of eventList.arr) {
const fn = await this.functionCenter.getFunction(i.name);
arr.take(fn, i.args, eventList, i);
}
}
}
}

View File

@@ -0,0 +1,60 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 12:10:16
* @FilePath: \dooring-v2\src\core\focusHandler\index.tsx
*/
import { store } from '../../runtime/store';
import { innerDragState } from '../innerDrag/state';
import { IBlockType } from '../store/storetype';
import { deepCopy } from '../utils';
import { selectRangeMouseDown } from '../selectRange';
import { unmountContextMenu } from '../contextMenu';
import { focusState } from './state';
export function containerFocusRemove() {
const onMouseDown = (e: React.MouseEvent) => {
const clonedata = deepCopy(store.getData());
const newBlock = clonedata.block.map((v: IBlockType) => {
v.focus = false;
return v;
});
focusState.blocks = [];
store.setData({ ...clonedata, block: newBlock });
if (!innerDragState.item) {
selectRangeMouseDown(e);
}
unmountContextMenu();
};
return {
onMouseDown,
};
}
export function blockFocus(e: React.MouseEvent, item: IBlockType) {
const clonedata = deepCopy(store.getData());
if (e.shiftKey) {
const newBlock = clonedata.block.map((v: IBlockType) => {
if (v.id === item.id) {
v.focus = true;
focusState.blocks.push(item);
}
return v;
});
store.setData({ ...clonedata, block: newBlock });
} else {
let blocks: IBlockType[] = [];
const newBlock = clonedata.block.map((v: IBlockType) => {
if (v.id === item.id) {
blocks.push(item);
v.focus = true;
} else {
v.focus = false;
}
return v;
});
focusState.blocks = blocks;
store.setData({ ...clonedata, block: newBlock });
}
}

View File

@@ -0,0 +1,14 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 12:06:20
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 12:06:50
* @FilePath: \dooring-v2\src\core\focusHandler\state.ts
*/
import { IBlockType } from '../store/storetype';
export interface FocusStateType {
blocks: IBlockType[];
}
export const focusState: FocusStateType = {
blocks: [],
};

View File

@@ -0,0 +1,27 @@
/*
* @Author: yehuozhili
* @Date: 2021-06-25 10:03:21
* @LastEditors: yehuozhili
* @LastEditTime: 2021-06-28 19:29:21
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\core\functionCenter\config.ts
*/
// 设定函数配置项格式,
export type FunctionDataType = keyof FunctionDataMap;
export type FunctionNameType = string;
export type FuncitonOptionConfigType = {
receive: string;
multi: boolean;
};
export interface FunctionDataMap {
dataSource: FuncitonOptionConfigType;
modal: FuncitonOptionConfigType;
input: FuncitonOptionConfigType;
ctx: FuncitonOptionConfigType;
}
// data 如果是''则在datasource,input,ctx选择
export type FunctionConfigType = {
name: FunctionNameType; // 会放到左侧展示 唯一!
data: FunctionDataType[];
options: FuncitonOptionConfigType;
}[];

View File

@@ -0,0 +1,128 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-08 19:59:01
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 01:46:17
* @FilePath: \dooringv2\packages\dooringx-lib\src\core\functionCenter\index.ts
*/
import UserConfig from '../../config';
import { EventCenterMapItem, EventCenterUserSelect } from '../eventCenter';
import { FunctionConfigType } from './config';
/**
*
* ctx可在事件链中传递函数完成后调用next执行下一个函数
* @export
*/
export type FunctionCenterFunction = (
ctx: Record<string, any>,
next: Function,
config: UserConfig,
args: Record<string, any>,
eventList: {
arr: Array<EventCenterMapItem>;
displayName: string;
userSelect: Array<EventCenterUserSelect>;
},
iname: EventCenterMapItem
) => void;
export type FunctionCenterType = Record<
string,
{ fn: FunctionCenterFunction; config: FunctionConfigType }
>;
/**
*
* 初始化时可以加载初始已配好的函数
* @export
* @class FunctionCenter
*/
export class FunctionCenter {
/**
*
* 该map 用于获取函数未获取到时,异步拉取
* @memberof FunctionCenter
*/
public asyncMap: Record<string, Function> = {};
public configMap: Record<string, FunctionConfigType> = {};
public funcitonMap: Record<string, FunctionCenterFunction> = {};
constructor(public initConfig: FunctionCenterType = {}) {
this.init(initConfig);
}
init(initConfig: FunctionCenterType) {
this.reset();
this.funcitonMap = Object.keys(initConfig).reduce<Record<string, FunctionCenterFunction>>(
(prev, next) => {
prev[next] = initConfig[next].fn;
return prev;
},
{}
);
this.configMap = Object.keys(initConfig).reduce<Record<string, FunctionConfigType>>(
(prev, next) => {
prev[next] = initConfig[next].config;
return prev;
},
{}
);
}
reset() {
this.funcitonMap = {};
this.configMap = {};
}
getFunctionMap() {
return this.funcitonMap;
}
getConfigMap() {
return this.configMap;
}
/**
*
* 注册函数,同名覆盖,返回删除函数
* @param {string} name
* @param {FunctionCenterFunction} fn
* @return {*}
* @memberof FunctionCenter
*/
register(name: string, fn: FunctionCenterFunction, config: FunctionConfigType) {
// 注册时需要通知asyncmap已经拿到
this.funcitonMap[name] = fn;
if (this.asyncMap[name]) {
this.asyncMap[name]();
}
this.configMap[name] = config;
return () => {
delete this.funcitonMap[name];
delete this.configMap[name];
};
}
/**
*
* 获取函数,包含异步获取函数
* @param {string} name
* @return {*} {Promise<FunctionCenterFunction>}
* @memberof FunctionCenter
*/
getFunction(name: string): Promise<FunctionCenterFunction> {
//如果拿不到,可能是异步,进行监听回调
const fn = this.funcitonMap[name];
if (fn) {
return Promise.resolve(fn);
}
return new Promise((resolve) => {
console.warn(`waiting the function now ${name} `);
this.asyncMap[name] = () => {
delete this.asyncMap[name];
resolve(this.getFunction(name));
};
});
}
}

View File

@@ -0,0 +1,124 @@
import { store } from '../../runtime/store';
import { RefObject } from 'react';
import { blockFocus, containerFocusRemove } from '../focusHandler';
import { marklineConfig } from '../markline/marklineConfig';
import { resizerMouseMove, resizerMouseUp } from '../resizeHandler';
import { scaleState } from '../scale/state';
import { selectRangeMouseMove, selectData, selectRangeMouseUp } from '../selectRange';
import { IBlockType } from '../store/storetype';
import { deepCopy, isMac } from '../utils';
import { wrapperMoveMouseUp } from '../../components/wrapperMove/event';
import { contextMenuState } from '../contextMenu';
import { innerDragState } from './state';
export const innerDrag = function (item: IBlockType, ref: RefObject<HTMLDivElement>) {
return {
onMouseDown: (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (!item.canDrag) {
containerFocusRemove().onMouseDown(e);
return;
}
blockFocus(e, item);
if (item.id && innerDragState.lastClick && item.id !== innerDragState.lastClick.id) {
contextMenuState.unmountContextMenu();
}
innerDragState.lastClick = item;
if (item.position === 'static') {
return;
}
if (ref.current) {
ref.current.style.cursor = 'move';
ref.current.style.willChange = 'left,right,width,height';
}
innerDragState.startX = e.clientX;
innerDragState.startY = e.clientY;
innerDragState.item = item;
innerDragState.isDrag = true;
innerDragState.ref = ref;
innerDragState.current = store.getIndex();
},
};
};
export const innerContainerDrag = function () {
let lastblock: null | IBlockType;
const onMouseMove = (e: React.MouseEvent) => {
e.preventDefault();
if (isMac() && contextMenuState.state) {
//mac有bug
return;
}
const id = innerDragState.item?.id;
if (id && innerDragState.isDrag) {
const current = store.getData().block.find((v) => v.id === id);
if (current?.position === 'static') {
return;
}
let { clientX: moveX, clientY: moveY } = e;
const { startX, startY } = innerDragState;
const scale = scaleState.value;
let durX = (moveX - startX) / scale;
let durY = (moveY - startY) / scale;
let newblock: IBlockType[];
if (lastblock !== innerDragState.item) {
const cloneblock: IBlockType[] = deepCopy(store.getData().block);
lastblock = innerDragState.item;
newblock = cloneblock.map((v) => {
if (v.focus && v.position !== 'static') {
v.left = v.left + durX;
v.top = v.top + durY;
}
return v;
});
} else {
newblock = store.getData().block.map((v) => {
if (v.focus && v.position !== 'static') {
v.left = v.left + durX;
v.top = v.top + durY;
}
return v;
});
}
innerDragState.startX = moveX;
innerDragState.startY = moveY;
store.setData({ ...store.getData(), block: newblock });
}
resizerMouseMove(e);
if (selectData.selectDiv) {
selectRangeMouseMove(e);
}
};
return {
onMouseMove,
};
};
export const innerContainerDragUp = function () {
const onMouseUp = (e: React.MouseEvent) => {
e.preventDefault();
wrapperMoveMouseUp();
selectRangeMouseUp(e);
if (innerDragState.ref && innerDragState.ref.current) {
innerDragState.ref.current.style.cursor = 'default';
innerDragState.ref.current.style.willChange = 'auto';
}
resizerMouseUp();
if (innerDragState.current) {
const endindex = store.getIndex();
store.getStoreList().splice(innerDragState.current, endindex - innerDragState.current);
store.setIndex(innerDragState.current);
}
innerDragState.ref = null;
innerDragState.isDrag = false;
innerDragState.item = null;
innerDragState.current = 0;
marklineConfig.marklineUnfocus = null;
store.forceupdate();
};
return {
onMouseUp,
};
};

View File

@@ -0,0 +1,30 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 12:09:11
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 12:09:46
* @FilePath: \dooring-v2\src\core\innerDrag\state.ts
*/
import { RefObject } from 'react';
import { IBlockType } from '../store/storetype';
export interface innerDragStateType {
startX: number;
startY: number;
item: null | IBlockType;
isDrag: boolean;
ref: RefObject<HTMLDivElement> | null;
current: number;
lastClick: null | IBlockType;
}
export const innerDragState: innerDragStateType = {
startX: 0,
startY: 0,
item: null,
isDrag: false,
ref: null,
current: 0,
lastClick: null,
};

View File

@@ -0,0 +1,79 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:49:50
* @FilePath: \dooring-v2\src\core\markline\calcRender.ts
*/
import { store } from '../../runtime/store';
import { innerDragState } from '../innerDrag/state';
import { scaleState } from '../scale/state';
import { grideModeRender, gridModeDisplay } from './gridMode';
import { switchMarklineDisplay } from './normalMode';
import { resizeCurrentCalculate } from './resizeMarkline';
import { marklineConfig } from './marklineConfig';
export interface LinesTypes {
x: number[];
y: number[];
}
export function marklineCalRender() {
//focus可能好几个做对比的是拖拽那个
const lines: LinesTypes = { x: [], y: [] };
if (innerDragState.item?.position === 'static' || innerDragState.item?.position === 'relative') {
return lines;
}
const item = innerDragState.item;
const ref = innerDragState.ref;
if (item && ref && ref.current && innerDragState.isDrag) {
const focus = store.getData().block.find((v) => v.id === item.id)!;
if (!marklineConfig.marklineUnfocus) {
marklineConfig.marklineUnfocus = store
.getData()
.block.filter(
(v) => v.focus === false && v.position !== 'static' && v.position !== 'relative'
);
}
const { width, height } = ref.current.getBoundingClientRect();
// left 和top 被深拷贝过,最新的值需要即时获取
const left = focus?.left;
const top = focus?.top;
if (typeof left !== 'number' || typeof top !== 'number') {
return lines; //莫名可能没有这2值
}
const scale = scaleState.value;
const wwidth = width / scale;
const wheight = height / scale;
marklineConfig.marklineUnfocus.forEach((v) => {
let l = v?.left;
let t = v?.top;
if (typeof l !== 'number' || typeof t !== 'number') {
console.warn(`${v} component miss top or left`);
} else {
// 如果不是由外层容器决定的则没有这2属性
const w = v.width;
const h = v.height;
// 只有满足要求的才进行push
if (marklineConfig.mode === 'normal') {
switchMarklineDisplay(l, t, w, h, left, top, wwidth, wheight, lines, focus);
}
}
});
if (marklineConfig.mode === 'grid' && marklineConfig.isAbsorb) {
gridModeDisplay(left, top, focus);
}
}
if (marklineConfig.mode === 'grid') {
grideModeRender(lines);
}
resizeCurrentCalculate(lines);
return lines;
}

View File

@@ -0,0 +1,82 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:49:43
* @FilePath: \dooring-v2\src\core\markline\gridMode.ts
*/
import { store } from '../../runtime/store';
import { IBlockType } from '../store/storetype';
import { deepCopy } from '../utils';
import { LinesTypes } from './calcRender';
import { marklineConfig } from './marklineConfig';
export function gridModeDisplay(left: number, top: number, focus: IBlockType) {
// 有吸附走吸附只吸top和left宽高不需要
// 无吸附拖拽时显示所有网格。
const container = store.getData().container;
const containerWidth = container.width;
const containerHeight = container.height;
const indent = marklineConfig.gridIndent;
const diff = marklineConfig.indent;
// 网格按照宽高除以下,每间隔一定距离给个线
// 横线
for (let i = 0; i < containerHeight; i++) {
const tmp = i * indent;
if (Math.abs(top - tmp) < diff) {
focus.top = tmp;
break;
} else if (tmp + diff > top) {
break;
}
}
// 竖线
for (let i = 0; i < containerWidth; i++) {
const tmp = i * indent;
if (Math.abs(left - tmp) < diff) {
focus.left = tmp;
break;
} else if (tmp + diff > left) {
break;
}
}
}
export interface lastGridStatusProps {
lastWidth: number;
lastHeight: number;
lastIndent: number;
lastLine: LinesTypes;
}
export const lastGridStatus: lastGridStatusProps = {
lastWidth: 0,
lastHeight: 0,
lastIndent: 0,
lastLine: { x: [], y: [] },
};
export function grideModeRender(lines: LinesTypes) {
const container = store.getData().container;
const containerWidth = container.width;
const containerHeight = container.height;
const indent = marklineConfig.gridIndent;
if (
lastGridStatus.lastWidth === containerWidth &&
lastGridStatus.lastHeight === containerHeight &&
lastGridStatus.lastIndent === indent
) {
lines.x = lastGridStatus.lastLine.x;
lines.y = lastGridStatus.lastLine.y;
} else {
for (let i = 0; i < containerWidth; i++) {
lines.x.push(i * indent);
}
for (let i = 0; i < containerHeight; i++) {
lines.y.push(i * indent);
}
lastGridStatus.lastLine = deepCopy(lines);
lastGridStatus.lastWidth = containerWidth;
lastGridStatus.lastHeight = containerHeight;
lastGridStatus.lastIndent = indent;
}
}

View File

@@ -0,0 +1,80 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:45:22
* @FilePath: \dooring-v2\src\core\markline\index.tsx
*/
import React from 'react';
import { useMemo } from 'react';
import { IBlockType } from '../store/storetype';
import { marklineCalRender } from './calcRender';
// 主要逻辑需要注入组件内拖拽
export interface MarklineConfigType {
indent: number;
isAbsorb: boolean;
mode: 'normal' | 'grid';
gridIndent: number;
resizeIndent: number;
marklineUnfocus: null | IBlockType[];
}
// 间隔距离执行吸附
export const marklineConfig: MarklineConfigType = {
indent: 2,
isAbsorb: true,
mode: 'normal',
gridIndent: 50,
resizeIndent: 0,
marklineUnfocus: null,
};
export function MarklineX(props: any) {
return (
<div
className="yh-markline"
style={{
borderTop: '1px dashed black',
position: 'absolute',
width: '100%',
top: props.top,
display: props.display,
zIndex: 9999,
}}
></div>
);
}
export function MarklineY(props: any) {
return (
<div
className="yh-markline"
style={{
borderLeft: '1px dashed black',
position: 'absolute',
height: '100%',
left: props.left,
display: props.display,
zIndex: 9999,
}}
></div>
);
}
export function NormalMarkLineRender() {
const lines = marklineCalRender();
const render = useMemo(() => {
return (
<>
{lines.x.map((v, i) => {
return <MarklineX key={i} top={v}></MarklineX>;
})}
{lines.y.map((v, i) => {
return <MarklineY key={i} left={v}></MarklineY>;
})}
</>
);
}, [lines]);
return <>{render}</>;
}

View File

@@ -0,0 +1,27 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 11:49:13
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:49:31
* @FilePath: \dooring-v2\src\core\markline\marklineConfig.ts
*/
import { IBlockType } from '../store/storetype';
export interface MarklineConfigType {
indent: number;
isAbsorb: boolean;
mode: 'normal' | 'grid';
gridIndent: number;
resizeIndent: number;
marklineUnfocus: null | IBlockType[];
}
// 间隔距离执行吸附
export const marklineConfig: MarklineConfigType = {
indent: 2,
isAbsorb: true,
mode: 'normal',
gridIndent: 50,
resizeIndent: 0,
marklineUnfocus: null,
};

View File

@@ -0,0 +1,279 @@
import { IBlockType } from '../store/storetype';
import { LinesTypes } from './calcRender';
import { marklineConfig } from './marklineConfig';
export function switchMarklineDisplay(
l: number,
t: number,
w: number | string | undefined,
h: number | string | undefined,
left: number,
top: number,
width: number,
height: number,
lines: LinesTypes,
focus: IBlockType
) {
// 做吸附只能选择一个接近的吸,所以只匹配一个即可。
// 头对头
if (marklineConfig.isAbsorb) {
if (Math.abs(top - t) < marklineConfig.indent) {
lines.x.push(t);
focus.top = t;
}
// 中对头
else if (Math.abs(top + height / 2 - t) < marklineConfig.indent) {
lines.x.push(t);
focus.top = t - height / 2;
}
// 尾对头
else if (Math.abs(top + height - t) < marklineConfig.indent) {
lines.x.push(t);
focus.top = t - height;
} else if (h && typeof h === 'number') {
// 头对中
if (Math.abs(t + h / 2 - top) < marklineConfig.indent) {
lines.x.push(t + h / 2);
focus.top = t + h / 2;
}
// 中对中
else if (Math.abs(t + h / 2 - top - height / 2) < marklineConfig.indent) {
lines.x.push(t + h / 2);
focus.top = t + h / 2 - height / 2;
}
// 尾对中
else if (Math.abs(t + h / 2 - top - height) < marklineConfig.indent) {
lines.x.push(t + h / 2);
focus.top = t + h / 2 - height;
}
// 头对尾
else if (Math.abs((t + h - top) / 2) < marklineConfig.indent) {
lines.x.push(t + h);
focus.top = t + h;
}
// 中对尾
else if (Math.abs(t + h - top - height / 2) < marklineConfig.indent) {
lines.x.push(t + h);
focus.top = t + h - height / 2;
}
// 尾对尾
else if (Math.abs((t + h - top - height) / 2) < marklineConfig.indent) {
lines.x.push(t + h);
focus.top = t + h - height;
}
}
// x轴方向
// 头对头
if (Math.abs(left - l) < marklineConfig.indent) {
lines.y.push(l);
focus.left = l;
}
// 中对头
else if (Math.abs(left + width / 2 - l) < marklineConfig.indent) {
lines.y.push(l);
focus.left = l - width / 2;
}
// 尾对头
else if (Math.abs(left + width - l) < marklineConfig.indent) {
lines.y.push(l);
focus.left = l - width;
} else if (w && typeof w === 'number') {
// 头对中
if (Math.abs(l + w / 2 - left) < marklineConfig.indent) {
lines.y.push(l + w / 2);
focus.left = l + w / 2;
}
// 中对中
else if (Math.abs(l + w / 2 - left - width / 2) < marklineConfig.indent) {
lines.y.push(l + w / 2);
focus.left = l + w / 2 - width / 2;
}
// 尾对中
else if (Math.abs(l + w / 2 - left - width) < marklineConfig.indent) {
lines.y.push(l + w / 2);
focus.left = l + w / 2 - width;
}
// 头对尾
else if (Math.abs((l + w - left) / 2) < marklineConfig.indent) {
lines.y.push(l + w);
focus.left = l + w;
}
// 中对尾
else if (Math.abs(l + w - left - width / 2) < marklineConfig.indent) {
lines.y.push(l + w);
focus.left = l + w - width / 2;
}
// 尾对尾
else if (Math.abs((l + w - left - width) / 2) < marklineConfig.indent) {
lines.y.push(l + w);
focus.left = l + w - width;
}
}
} else {
if (Math.abs(top - t) < marklineConfig.indent) {
lines.x.push(t);
}
// 中对头
if (Math.abs(top + height / 2 - t) < marklineConfig.indent) {
lines.x.push(t);
}
// 尾对头
if (Math.abs(top + height - t) < marklineConfig.indent) {
lines.x.push(t);
}
if (h && typeof h === 'number') {
// 头对中
if (Math.abs(t + h / 2 - top) < marklineConfig.indent) {
lines.x.push(t + h / 2);
}
// 中对中
if (Math.abs(t + h / 2 - top - height / 2) < marklineConfig.indent) {
lines.x.push(t + h / 2);
}
// 尾对中
if (Math.abs(t + h / 2 - top - height) < marklineConfig.indent) {
lines.x.push(t + h / 2);
}
// 头对尾
if (Math.abs((t + h - top) / 2) < marklineConfig.indent) {
lines.x.push(t + h);
}
// 中对尾
if (Math.abs(t + h - top - height / 2) < marklineConfig.indent) {
lines.x.push(t + h);
}
// 尾对尾
if (Math.abs((t + h - top - height) / 2) < marklineConfig.indent) {
lines.x.push(t + h);
}
}
// x轴方向
// 头对头
if (Math.abs(left - l) < marklineConfig.indent) {
lines.y.push(l);
}
// 中对头
if (Math.abs(left + width / 2 - l) < marklineConfig.indent) {
lines.y.push(l);
}
// 尾对头
if (Math.abs(left + width - l) < marklineConfig.indent) {
lines.y.push(l);
}
if (w && typeof w === 'number') {
// 头对中
if (Math.abs(l + w / 2 - left) < marklineConfig.indent) {
lines.y.push(l + w / 2);
}
// 中对中
if (Math.abs(l + w / 2 - left - width / 2) < marklineConfig.indent) {
lines.y.push(l + w / 2);
}
// 尾对中
if (Math.abs(l + w / 2 - left - width) < marklineConfig.indent) {
lines.y.push(l + w / 2);
}
// 头对尾
if (Math.abs((l + w - left) / 2) < marklineConfig.indent) {
lines.y.push(l + w);
}
// 中对尾
if (Math.abs(l + w - left - width / 2) < marklineConfig.indent) {
lines.y.push(l + w);
}
// 尾对尾
if (Math.abs((l + w - left - width) / 2) < marklineConfig.indent) {
lines.y.push(l + w);
}
}
}
}
export function switchMarklineResizeDisplay(
l: number,
t: number,
w: number | string | undefined,
h: number | string | undefined,
left: number,
top: number,
width: number,
height: number,
lines: LinesTypes
) {
// 头对头
if (Math.abs(top - t) <= marklineConfig.resizeIndent) {
lines.x.push(t);
}
// 中对头
if (Math.abs(top + height / 2 - t) <= marklineConfig.resizeIndent) {
lines.x.push(t);
}
// 尾对头
if (Math.abs(top + height - t) <= marklineConfig.resizeIndent) {
lines.x.push(t);
}
if (h && typeof h === 'number') {
// 头对中
if (Math.abs(t + h / 2 - top) <= marklineConfig.resizeIndent) {
lines.x.push(t + h / 2);
}
// 中对中
if (Math.abs(t + h / 2 - top - height / 2) <= marklineConfig.resizeIndent) {
lines.x.push(t + h / 2);
}
// 尾对中
if (Math.abs(t + h / 2 - top - height) <= marklineConfig.resizeIndent) {
lines.x.push(t + h / 2);
}
// 头对尾
if (Math.abs((t + h - top) / 2) <= marklineConfig.resizeIndent) {
lines.x.push(t + h);
}
// 中对尾
if (Math.abs(t + h - top - height / 2) <= marklineConfig.resizeIndent) {
lines.x.push(t + h);
}
// 尾对尾
if (Math.abs((t + h - top - height) / 2) <= marklineConfig.resizeIndent) {
lines.x.push(t + h);
}
}
// x轴方向
// 头对头
if (Math.abs(left - l) <= marklineConfig.resizeIndent) {
lines.y.push(l);
}
// 中对头
if (Math.abs(left + width / 2 - l) <= marklineConfig.resizeIndent) {
lines.y.push(l);
}
// 尾对头
if (Math.abs(left + width - l) <= marklineConfig.resizeIndent) {
lines.y.push(l);
}
if (w && typeof w === 'number') {
// 头对中
if (Math.abs(l + w / 2 - left) <= marklineConfig.resizeIndent) {
lines.y.push(l + w / 2);
}
// 中对中
if (Math.abs(l + w / 2 - left - width / 2) <= marklineConfig.resizeIndent) {
lines.y.push(l + w / 2);
}
// 尾对中
if (Math.abs(l + w / 2 - left - width) <= marklineConfig.resizeIndent) {
lines.y.push(l + w / 2);
}
// 头对尾
if (Math.abs((l + w - left) / 2) <= marklineConfig.resizeIndent) {
lines.y.push(l + w);
}
// 中对尾
if (Math.abs(l + w - left - width / 2) <= marklineConfig.resizeIndent) {
lines.y.push(l + w);
}
// 尾对尾
if (Math.abs((l + w - left - width) / 2) <= marklineConfig.resizeIndent) {
lines.y.push(l + w);
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* @Author: yehuozhili
* @Date: 2021-02-18 11:52:38
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:49:58
* @FilePath: \dooring-v2\src\core\markline\resizeMarkline.ts
*/
import { store } from '../../runtime/store';
import { resizeState } from '../resizeHandler';
import { scaleState } from '../scale/state';
import { LinesTypes } from './calcRender';
import { switchMarklineResizeDisplay } from './normalMode';
import { marklineConfig } from './marklineConfig';
export function resizeCurrentCalculate(lines: LinesTypes) {
const id = resizeState.item?.id;
if (resizeState.ref?.current && id) {
const newblock = store.getData().block;
const unfocus = newblock.filter((v) => v.id !== id);
const { width, height } = resizeState.ref.current.getBoundingClientRect();
const focus = store.getData().block.find((v) => v.id === id)!;
const { left, top } = focus;
const scale = scaleState.value;
const wwidth = width / scale;
const wheight = height / scale;
unfocus.forEach((v) => {
const { left: l, top: t } = v;
// 如果不是由外层容器决定的则没有这2属性
const w = v.width;
const h = v.height;
const ww = w && typeof w === 'number' ? w / scale : w;
const wh = h && typeof h === 'number' ? h / scale : h;
// 只有满足要求的才进行push
if (marklineConfig.mode === 'normal') {
switchMarklineResizeDisplay(l, t, ww, wh, left, top, wwidth, wheight, lines);
}
});
}
}

View File

@@ -0,0 +1,46 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-09 15:19:36
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 04:52:57
* @FilePath: \dooring-v2\src\core\resizeHandler\containerResizer.ts
*/
import { store } from '../../runtime/store';
import { scaleState } from '../scale/state';
import { IStoreData } from '../store/storetype';
import { deepCopy } from '../utils';
export const containerState = {
isDrag: false,
startY: 0,
startIndex: 0,
};
export const containerResizer = {
onMousedown: (e: React.MouseEvent) => {
containerState.isDrag = true;
containerState.startY = e.clientY;
containerState.startIndex = store.getIndex();
},
onMouseMove: (e: React.MouseEvent) => {
if (containerState.isDrag) {
const scale = scaleState.value;
const diff = ((e.clientY - containerState.startY) / scale) * 2;
const clonedata: IStoreData = deepCopy(store.getData());
const height = clonedata.container.height;
let tmpHeight = height + diff < 600 ? 600 : height + diff;
clonedata.container.height = tmpHeight;
store.setData(clonedata);
containerState.startY = e.clientY;
}
},
onMouseUp: () => {
if (containerState.isDrag) {
containerState.isDrag = false;
const endIndex = store.getIndex();
store.getStoreList().splice(containerState.startIndex, endIndex - containerState.startIndex);
store.setIndex(containerState.startIndex);
}
},
};

View File

@@ -0,0 +1,204 @@
import { store } from '../../runtime/store';
import { RefObject, useMemo } from 'react';
import { scaleState } from '../scale/state';
import { IBlockType } from '../store/storetype';
import { deepCopy } from '../utils';
import React from 'react';
import classnames from 'classnames';
import styles from '../../index.less';
interface BlockResizerProps {
data: IBlockType;
rect: RefObject<HTMLDivElement>;
}
interface resizeStateType {
startX: number;
startY: number;
item: null | IBlockType;
isResize: boolean;
direction: directionType;
ref: RefObject<HTMLDivElement> | null;
current: number;
}
type directionType =
| 'top'
| 'topleft'
| 'topright'
| 'left'
| 'bottomleft'
| 'bottom'
| 'bottomright'
| 'right';
export const resizeState: resizeStateType = {
startX: 0,
startY: 0,
item: null,
isResize: false,
direction: 'bottom',
ref: null,
current: 0,
};
const onMouseDown = (
e: React.MouseEvent,
direction: directionType,
item: IBlockType,
ref: RefObject<HTMLDivElement>
) => {
e.stopPropagation();
resizeState.isResize = true;
resizeState.item = item;
resizeState.startX = e.clientX;
resizeState.startY = e.clientY;
resizeState.direction = direction;
resizeState.ref = ref;
resizeState.current = store.getIndex();
};
export const resizerMouseUp = () => {
resizeState.isResize = false;
resizeState.item = null;
if (resizeState.current) {
const endindex = store.getIndex();
store.getStoreList().splice(resizeState.current, endindex - resizeState.current);
store.setIndex(resizeState.current);
}
resizeState.current = 0;
};
const changePosition = (v: IBlockType, durX: number, durY: number) => {
const direction = resizeState.direction;
const { width, height } = resizeState.ref!.current!.getBoundingClientRect();
const scale = scaleState.value;
let tmpy = height / scale - durY;
let tmpx = width / scale - durX;
switch (direction) {
case 'right':
v.width = width / scale + durX;
break;
case 'bottom':
v.height = height / scale + durY;
break;
case 'left':
v.left = width / scale > 0 ? v.left + durX : v.left;
v.width = tmpx > 0 ? tmpx : 0;
break;
case 'top':
v.top = height / scale > 0 ? v.top + durY : v.top;
v.height = tmpy > 0 ? tmpy : 0;
break;
case 'bottomright':
v.width = width / scale + durX;
v.height = height / scale + durY;
break;
case 'topright':
v.width = width / scale + durX;
v.top = height / scale > 0 ? v.top + durY : v.top;
v.height = tmpy > 0 ? tmpy : 0;
break;
case 'topleft':
v.top = height / scale > 0 ? v.top + durY : v.top;
v.height = tmpy > 0 ? tmpy : 0;
v.left = width / scale > 0 ? v.left + durX : v.left;
v.width = tmpx > 0 ? tmpx : 0;
break;
case 'bottomleft':
v.left = width / scale > 0 ? v.left + durX : v.left;
v.width = tmpx > 0 ? tmpx : 0;
v.height = height / scale + durY;
break;
default:
break;
}
};
export const resizerMouseMove = (e: React.MouseEvent) => {
//根据direction修改位置
if (resizeState.isResize && resizeState.item) {
let { clientX: moveX, clientY: moveY } = e;
const { startX, startY } = resizeState;
const scale = scaleState.value;
let durX = (moveX - startX) / scale;
let durY = (moveY - startY) / scale;
const clonedata = deepCopy(store.getData());
const newblock: IBlockType[] = clonedata.block.map((v: IBlockType) => {
if (v.id === resizeState.item!.id) {
changePosition(v, durX, durY);
}
return v;
});
resizeState.startX = moveX;
resizeState.startY = moveY;
store.setData({ ...clonedata, block: newblock });
}
};
export function BlockResizer(props: BlockResizerProps) {
const render = useMemo(() => {
if (props.data.focus && props.data.resize) {
return (
<>
<div
className={classnames(styles.resizepoint, styles.top)}
onMouseDown={(e) => {
onMouseDown(e, 'top', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.topleft)}
onMouseDown={(e) => {
onMouseDown(e, 'topleft', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.left)}
onMouseDown={(e) => {
onMouseDown(e, 'left', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.topright)}
onMouseDown={(e) => {
onMouseDown(e, 'topright', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.bottomleft)}
onMouseDown={(e) => {
onMouseDown(e, 'bottomleft', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.bottom)}
onMouseDown={(e) => {
onMouseDown(e, 'bottom', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.right)}
onMouseDown={(e) => {
onMouseDown(e, 'right', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
<div
className={classnames(styles.resizepoint, styles.bottomright)}
onMouseDown={(e) => {
onMouseDown(e, 'bottomright', props.data, props.rect);
}}
onMouseUp={resizerMouseUp}
></div>
</>
);
} else {
return null;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.data.focus, props.data.resize]);
return <>{render}</>;
}

View File

@@ -0,0 +1,5 @@
import { unmountContextMenu } from '../contextMenu';
export const scaleCancelFn = () => {
unmountContextMenu();
};

View File

@@ -0,0 +1,54 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-04-05 22:18:43
* @FilePath: \dooringv2\src\core\scale\index.ts
*/
import { store } from '../../runtime/store';
import Store from '../store';
import { scaleCancelFn } from './cancel';
import { scaleState } from './state';
export const onWheelEvent = {
onWheel: (e: React.WheelEvent<HTMLDivElement>) => {
const dom = document.querySelector('.ant-modal-mask');
if (dom) {
//出现弹窗禁止滚动
return;
}
if (e.deltaY > 0) {
scaleCancelFn();
if (scaleState.value < scaleState.maxValue) {
scaleState.value = scaleState.value + 0.1;
store.forceUpdate();
}
} else {
scaleCancelFn();
//往上滚缩小
if (scaleState.value > scaleState.minValue) {
scaleState.value = scaleState.value - 0.1;
store.forceUpdate();
}
}
},
};
export const scaleFn = {
increase(number: number = 0.1, store: Store) {
if (scaleState.value < scaleState.maxValue) {
scaleCancelFn();
scaleState.value = scaleState.value + number;
store.forceUpdate();
}
return scaleState.value;
},
decrease(number: number = 0.1, store: Store) {
scaleCancelFn();
if (scaleState.value > scaleState.minValue) {
scaleState.value = scaleState.value - number;
store.forceUpdate();
}
return scaleState.value;
},
};

View File

@@ -0,0 +1,12 @@
/*
* @Author: yehuozhili
* @Date: 2021-07-07 10:28:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 23:16:31
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\scale\state.ts
*/
export const scaleState = {
value: 0.8,
maxValue: 1.3,
minValue: 0.4,
};

View File

@@ -0,0 +1,100 @@
import { store } from '../../runtime/store';
import { IStoreData } from '../store/storetype';
import { deepCopy } from '../utils';
import { focusState } from '../focusHandler/state';
import { scaleState } from '../scale/state';
import style from '../../index.less';
export interface SelectDataProps {
selectDiv: HTMLDivElement | null;
posx: number;
posy: number;
startX: number;
startY: number;
}
export const selectData: SelectDataProps = {
selectDiv: null,
posx: 0,
posy: 0,
startX: 0,
startY: 0,
};
export function selectRangeMouseDown(e: React.MouseEvent) {
if (!selectData.selectDiv) {
selectData.selectDiv = document.createElement('div');
}
if (selectData.selectDiv) {
selectData.startX = e.nativeEvent.offsetX;
selectData.startY = e.nativeEvent.offsetY;
selectData.posx = e.clientX;
selectData.posy = e.clientY;
selectData.selectDiv.className = style.yhTempDiv;
selectData.selectDiv.style.left = e.clientX + 'px';
selectData.selectDiv.style.top = e.clientY + 'px';
selectData.selectDiv.style.position = 'fixed';
document.body.appendChild(selectData.selectDiv);
selectData.selectDiv.onmouseup = (e) => selectRangeMouseUp(e);
selectData.selectDiv.onmousemove = (e) => selectRangeMouseMove(e);
}
}
export function selectRangeMouseMove(ev: React.MouseEvent | MouseEvent) {
if (selectData.selectDiv) {
selectData.selectDiv.style.left = Math.min(ev.clientX, selectData.posx) + 'px';
selectData.selectDiv.style.top = Math.min(ev.clientY, selectData.posy) + 'px';
selectData.selectDiv.style.width = Math.abs(selectData.posx - ev.clientX) + 'px';
selectData.selectDiv.style.height = Math.abs(selectData.posy - ev.clientY) + 'px';
}
}
function typeGuard(e: React.MouseEvent | MouseEvent): e is React.MouseEvent {
return !(e instanceof Event);
}
function selectFocus(left: number, top: number, width: number, height: number) {
if (width === 0 || height === 0) {
return;
}
const clonedata: IStoreData = deepCopy(store.getData());
const blocks = clonedata.block;
let change = false;
const maxleft = left + width;
const maxtop = top + height;
blocks.forEach((v) => {
const l = v.left;
const t = v.top;
if (l >= left && l <= maxleft && t >= top && t <= maxtop) {
change = true;
v.focus = true;
focusState.blocks.push(v);
}
});
if (change) {
store.setData(clonedata);
}
}
export function selectRangeMouseUp(e: React.MouseEvent | MouseEvent) {
if (selectData.selectDiv) {
// 这里需要判定区域
// 如果是react触发 left和top就是起始值和终止值的最小值
// 如果是原生触发left和top是起始点减去其宽高
let left = 0;
let top = 0;
const { width, height } = selectData.selectDiv.getBoundingClientRect();
const scale = scaleState.value;
const wwidth = width / scale;
const wheight = height / scale;
if (typeGuard(e)) {
left = Math.min(e.nativeEvent.offsetX, selectData.startX);
top = Math.min(e.nativeEvent.offsetY, selectData.startY);
} else {
left = selectData.startX - wwidth;
top = selectData.startY - wheight;
}
selectFocus(left, top, wwidth, wheight);
selectData.selectDiv.parentNode!.removeChild(selectData.selectDiv);
selectData.selectDiv = null;
}
}

View File

@@ -0,0 +1,143 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-19 17:07:10
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\core\store\index.ts
*/
import { IStoreData } from './storetype';
import { storeChangerState } from '../storeChanger/state';
export const initialData: IStoreData = {
container: {
width: 375,
height: 600,
},
block: [],
modalMap: {},
dataSource: {},
globalState: {},
};
class Store {
constructor(
public storeDataList: IStoreData[] = [initialData],
public listeners: Array<Function> = [],
public current: number = 0,
public forceupdate: Function = () => {}
) {}
getData() {
return this.storeDataList[this.current];
}
getStoreList() {
return this.storeDataList;
}
getListeners() {
return this.listeners;
}
getIndex() {
return this.current;
}
/**
*
* 注意重置需要注册事件
* @param {IStoreData[]} initData
* @param {boolean} [check=false]
* @memberof Store
*/
resetToInitData(initData: IStoreData[], check = false) {
this.storeDataList = initData;
this.current = 0;
//如果是编辑模式,需要修改
if (storeChangerState.modalEditName !== '' && check) {
storeChangerState.modalEditName = '';
}
this.emit();
}
/**
*
* 注意重置需要注册事件
* @param {IStoreData[]} initData
* @param {number} current
* @param {boolean} [check=false]
* @memberof Store
*/
resetToCustomData(initData: IStoreData[], current: number, check = false) {
this.storeDataList = initData;
this.current = current;
//如果是编辑模式,需要修改
if (storeChangerState.modalEditName !== '' && check) {
storeChangerState.modalEditName = '';
}
this.emit();
}
resetListeners() {
this.listeners = [];
}
replaceList(list: IStoreData[]) {
this.storeDataList = list;
}
setForceUpdate(fn: Function) {
this.forceupdate = fn;
}
forceUpdate() {
this.forceupdate();
}
setIndex(num: number) {
this.current = num;
}
redo() {
const maxLength = this.storeDataList.length;
if (this.current + 1 < maxLength) {
this.current = this.current + 1;
this.emit();
}
}
undo() {
if (this.current > 0) {
this.current = this.current - 1;
this.emit();
}
}
cleanRedundant(index: number) {
this.storeDataList = this.storeDataList.slice(0, index + 1);
}
setData(data: IStoreData) {
// 如果current不是最后那个说明后面的被undo过的如果要新增那么需要清除之前的
let flag = true;
if (this.current + 1 !== this.storeDataList.length) {
this.cleanRedundant(this.current);
flag = false;
}
this.current = this.current + 1;
this.storeDataList[this.current] = data;
if (flag && this.current + 1 !== this.storeDataList.length) {
this.storeDataList.length = this.current + 1;
}
this.emit();
}
emit() {
this.listeners.forEach((fn) => {
fn(this.getData());
});
}
subscribe(listener: Function) {
this.listeners.push(listener);
return () => (this.listeners = this.listeners.filter((v) => v !== listener));
}
}
export default Store;

View File

@@ -0,0 +1,48 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:29:09
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 17:00:07
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\store\storetype.ts
*/
import { EventCenterMapType } from '../eventCenter';
export interface IStoreData {
container: {
width: number;
height: number;
};
block: Array<IBlockType>;
modalMap: Record<string, IStoreData>;
dataSource: Record<string, any>;
globalState: Record<string, any>;
}
export interface IBlockType {
id: string;
name: string;
top: number;
left: number;
zIndex: number;
position: 'absolute' | 'relative' | 'fixed' | 'static' | 'sticky';
width?: number | string;
height?: number | string;
display?: 'inline-block' | 'block' | 'inline';
focus: boolean;
resize: boolean;
canDrag: boolean;
props: Record<string, any>;
syncList: Array<string>;
eventMap: EventCenterMapType; //调用的event 与对应的函数名 如果要增加参数则类型不能是Array<string>,需要[{name:string,...args}]
functionList: Array<string>; //抛出的函数名
animate: {
animate?: string; //动画名
animationIterationCount?: any;
speed?: //动画速度
'animate__slow' | 'animate__slower' | 'animate__fast' | 'animate__faster' | '';
delay?: //首次延迟
'animate__delay-2s' | 'animate__delay-3s' | 'animate__delay-4s' | 'animate__delay-5s' | '';
};
fixed: boolean; // 用于制作fixed组件
}

View File

@@ -0,0 +1,239 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-05 14:55:31
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 16:59:54
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\storeChanger\index.ts
*/
import { message } from 'antd';
import Store from '../store';
import { IStoreData } from '../store/storetype';
import { createUid, deepCopy } from '../utils';
import { storeChangerState } from './state';
export type StoreChangerMap = Record<
'ORIGIN',
{
data: Array<IStoreData>;
current: number;
now: IStoreData;
} | null
>;
function createDefaultModalBlock(): IStoreData['block'] {
return [
{
id: createUid('modal-mask'),
name: 'modalMask',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 0,
props: {},
resize: true,
focus: false,
position: 'fixed',
display: 'block',
syncList: [],
canDrag: false,
eventMap: {},
functionList: [],
animate: {},
fixed: false,
},
{
id: createUid('modal-container'),
name: 'modalContainer',
top: 100,
left: 35,
zIndex: 0,
props: {},
resize: true,
focus: true,
position: 'absolute',
display: 'block',
width: 300,
height: 300,
syncList: [],
canDrag: true,
eventMap: {},
functionList: [],
animate: {},
fixed: false,
},
];
}
// 用来存储主store
const ORIGIN = 'ORIGIN';
const defaultModalStore: () => IStoreData = () => {
const newblock = createDefaultModalBlock();
return {
container: {
width: 375,
height: 600,
},
block: newblock,
modalMap: {},
dataSource: {},
globalState: {},
};
};
export class StoreChanger {
public map: StoreChangerMap;
constructor() {
this.map = { ORIGIN: null };
}
getState() {
return storeChangerState;
}
getOrigin() {
return this.map[ORIGIN];
}
isEdit() {
if (storeChangerState.modalEditName !== '') {
return true;
}
return false;
}
isInModalMap(store: Store, name: string) {
const modalNameList = Object.keys(store.getData().modalMap);
if (modalNameList.includes(name)) {
return true;
}
return false;
}
initStoreChanger() {
storeChangerState.modalEditName = '';
this.map = { ORIGIN: null };
}
/**
*
* 更新origin内容用于编辑模式下更新全局属性
* 需要判断是否在编辑模式,否则会报错
* @memberof StoreChanger
*/
updateOrigin(data: IStoreData) {
const origin = this.getOrigin();
if (origin!.data.length === origin!.current + 1) {
//说明为末尾,
origin!.data.push(data);
} else {
//替换下一个索引
origin!.data[origin!.current + 1] = data;
}
origin!.now = data;
origin!.current = origin!.current + 1;
}
/**
*
* 保存现阶段store将store替换为新modal数据
* @memberof StoreChanger
*/
newModalMap(store: Store, name: string) {
const sign = this.isEdit();
if (sign) {
message.error('请保存弹窗后编辑其他弹窗');
return;
}
//新建modal name不能重名否则直接报错
const sign2 = this.isInModalMap(store, name);
if (sign2) {
message.error(`已有重名弹窗:${name}`);
return;
}
storeChangerState.modalEditName = name;
this.map[ORIGIN] = {
data: store.getStoreList(),
current: store.getIndex(),
now: store.getStoreList()[store.getIndex()],
};
store.resetToInitData([defaultModalStore()]);
}
/**
*
* 存储modal到主store的map中切换主store
* @param {Store} store
* @memberof StoreChanger
*/
closeModal(store: Store) {
const sign = this.isEdit();
if (!sign) {
message.error('您并没有正在编辑弹窗');
return;
}
const main = this.map[ORIGIN];
const tmpModalData = deepCopy(store.getData());
if (main) {
store.resetToCustomData(main.data, main.current);
const cloneData: IStoreData = deepCopy(store.getData());
cloneData.modalMap[storeChangerState.modalEditName] = tmpModalData;
store.setData(cloneData);
storeChangerState.modalEditName = '';
}
}
/**
*
* 在已经保存的map中获取如果正在编辑别的弹窗则报错。
* @param {Store} store store必须为主store
* @param {string} name
* @memberof StoreChanger
*/
updateModal(store: Store, name: string) {
const sign = this.isEdit();
if (sign) {
message.error('请保存弹窗后编辑其他弹窗');
return;
}
const sign2 = this.isInModalMap(store, name);
if (!sign2) {
message.error(`未找到该弹窗:${name}`);
return;
}
storeChangerState.modalEditName = name;
const modalData = store.getData().modalMap[name];
this.map[ORIGIN] = {
data: store.getStoreList(),
current: store.getIndex(),
now: store.getStoreList()[store.getIndex()],
};
store.resetToInitData([modalData]);
}
/**
*
* 删除弹窗,不能处于编辑弹窗状态
* @param {Store} store
* @param {string} name
* @returns
* @memberof StoreChanger
*/
removeModal(store: Store, name: string) {
const sign = this.isEdit();
if (sign) {
message.error('请保存弹窗后删除其他弹窗');
return;
}
const sign2 = this.isInModalMap(store, name);
if (!sign2) {
message.error(`未找到该弹窗:${name}`);
return;
}
const cloneData: IStoreData = deepCopy(store.getData());
delete cloneData.modalMap[name];
store.setData(cloneData);
}
}

View File

@@ -0,0 +1,11 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-05 15:40:04
* @LastEditors: yehuozhili
* @LastEditTime: 2021-04-05 15:40:47
* @FilePath: \dooringv2\src\core\storeChanger\state.ts
*/
export const storeChangerState = {
modalEditName: '',
};

View File

@@ -0,0 +1,117 @@
/*
* @Author: yehuozhili
* @Date: 2021-04-21 22:59:57
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 19:50:12
* @FilePath: \DooringV2\packages\dooringx-lib\src\core\transfer\index.ts
*/
/**
*
*
* @export 这里的转换可能有问题需要都使用mobileWidth去转换
* @param {number} top
* @param {number} left
* @param {(string | number | undefined)} height
* @param {(string | number | undefined)} width
* @param {boolean} isFixed
* @returns
*/
export function transfer(
top: number,
left: number,
height: string | number | undefined,
width: string | number | undefined,
isFixed: boolean
) {
if (isFixed) {
// 由于是375x667基准所以top大于667的那么top为底部高度
let newtop = 0;
const newleft = getRealWidth(left);
let newheight: string | number | undefined;
let newwidth: string | number | undefined;
if (typeof height === 'string' || typeof height === 'undefined') {
newheight = height;
} else {
newheight = getRealHeight(height);
}
if (typeof width === 'string' || typeof width === 'undefined') {
newwidth = width;
} else {
newwidth = getRealWidth(width);
}
if (top >= 667) {
if (typeof newheight === 'number') {
newtop = getRealHeight() - newheight;
} else {
// 如果没有高度或者高度是百分比,则定位会有问题
newtop = getRealHeight();
}
} else {
if (typeof height === 'number' && top >= 667 - height && typeof newheight === 'number') {
// 这种是距离底部比高多 按底部计算
newtop = getRealHeight() - newheight;
} else {
newtop = getRealHeight(top);
}
}
return {
top: newtop,
left: newleft,
height: newheight,
width: newwidth,
};
} else {
const newtop = getRealHeight(top);
const newleft = getRealWidth(left);
let newheight: string | number | undefined;
let newwidth: string | number | undefined;
if (typeof height === 'string' || typeof height === 'undefined') {
newheight = height;
} else {
newheight = getRealHeight(height);
}
if (typeof width === 'string' || typeof width === 'undefined') {
newwidth = width;
} else {
newwidth = getRealWidth(width);
}
return {
top: newtop,
left: newleft,
height: newheight,
width: newwidth,
};
}
}
export function getCurrentMobileInfo() {
let userAgentMatched = window.navigator.userAgent.match(
/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
);
const width = userAgentMatched
? window.innerWidth
: window.innerWidth < 500
? window.innerWidth
: 375;
const height = userAgentMatched
? window.screen.availHeight
: window.screen.availHeight < 667
? window.screen.availHeight
: 667;
return [width, height];
}
export function getRealWidth(w: number | string = 375) {
const width = typeof w === 'string' ? parseFloat(w) : w;
return (getCurrentMobileInfo()[0] / 375) * width;
}
export function getRealHeight(H: number | string = 667) {
const height = typeof H === 'string' ? parseFloat(H) : H;
return (getCurrentMobileInfo()[0] / 375) * height;
}

View File

@@ -0,0 +1,9 @@
import { createFromIconfontCN } from '@ant-design/icons';
export const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_2607370_myr2zkz3ku.js', // 在 iconfont.cn 上生成
extraCommonProps: {
fill: 'currentColor',
stroke: 'currentColor',
},
});

View File

@@ -0,0 +1,281 @@
import { message } from 'antd';
import { RGBColor } from 'react-color';
import * as uuid from 'uuid';
import Store from '../store';
import { IBlockType, IStoreData } from '../store/storetype';
import { specialCoList } from './special';
import deepCopys from 'deepcopy';
import { FunctionDataMap } from '../functionCenter/config';
import UserConfig from '../../config';
export function deepCopy(obj: any) {
return deepCopys(obj);
}
export function swap(indexa: number, indexb: number, arr: Array<any>) {
arr[indexa] = arr.splice(indexb, 1, arr[indexa])[0];
return arr;
}
// 将rgba字符串对象转化为rgba对象
export function rgba2Obj(rgba = '') {
let reg = /rgba\(\s*?(\d+)\s*?,\s*?(\d+)\s*?,\s*?(\d+)\s*?,\s*?(\d+)\s*?\)/g;
let rgbaObj: RGBColor = { r: 0, g: 0, b: 0, a: 0 };
rgba.replace(reg, (_m, r, g, b, a) => {
rgbaObj = { r, g, b, a };
return rgba;
});
return rgbaObj;
}
export function createUid(name?: string) {
if (name) {
return name + '-' + uuid.v4();
} else {
return uuid.v4();
}
}
export const isMac = () => {
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
if (isMac) {
return true;
}
return false;
};
export const changeItem = (
store: Store,
id: string,
property: keyof IBlockType,
value: IBlockType[keyof IBlockType]
) => {
const clonedata: IStoreData = deepCopy(store.getData());
let canchange = true;
clonedata.block.forEach((v) => {
if (v.id === id) {
if (specialCoList.includes(v.name)) {
message.error('该组件不可调整');
canchange = false;
}
v[property] = value as never;
}
});
if (canchange) {
store.setData(clonedata);
}
};
/**
*
* 清除所有聚焦,选中某个元素
* @param {Store} store
* @param {string} id
*/
export const focusEle = (store: Store, id: string) => {
const clonedata: IStoreData = deepCopy(store.getData());
clonedata.block.forEach((v) => {
if (v.id === id) {
v.focus = true;
} else {
v.focus = false;
}
});
store.setData(clonedata);
};
export const changeLayer = (store: Store, id: string, action: 'up' | 'down' | 'delete') => {
const clonedata: IStoreData = deepCopy(store.getData());
let index = -1;
switch (action) {
case 'up':
clonedata.block.forEach((v, i) => {
if (v.id === id) {
if (specialCoList.includes(v.name)) {
message.error('该组件不可调整');
return;
} else {
index = i;
}
}
});
if (index > 0) {
// 查看上一个元素
const item = clonedata.block[index - 1];
if (specialCoList.includes(item.name)) {
return;
}
swap(index, index - 1, clonedata.block);
store.setData(clonedata);
}
return;
case 'down':
clonedata.block.forEach((v, i) => {
if (v.id === id) {
if (specialCoList.includes(v.name)) {
message.error('该组件不可调整');
return;
} else {
index = i;
}
}
});
if (index > -1 && index + 1 < clonedata.block.length) {
const item = clonedata.block[index + 1];
if (specialCoList.includes(item.name)) {
return;
}
swap(index, index + 1, clonedata.block);
store.setData(clonedata);
}
return;
case 'delete':
let candelete = true;
clonedata.block = clonedata.block.filter((v) => {
if (v.id === id) {
if (specialCoList.includes(v.name)) {
candelete = false;
}
return false;
}
return true;
});
if (candelete) {
store.setData(clonedata);
} else {
message.error('该组件无法删除');
}
return;
}
};
/**
*
* @param {*} array
* @param {*} from
* @param {*} to
*/
export const arrayMove = (array: any, from: number, to: number) => {
array = [...array];
arrayMoveMutate(array, from, to);
return array;
};
/**
*
* @param {*} length
* @param {*} index
*/
const indexSub = (arrLength: number, toIndex: number) => {
return toIndex < 0 ? arrLength + toIndex : toIndex;
// return resIndex;
};
/**
* 数组换位
* @param {Array} array The array with the item to move. / [1,2,3]
* @param {Number} from Index of item to move. If negative, it will begin that many elements from the end / 0 / -1 / 2
* @param {Number} to Index of where to move the item. If negative, it will begin that many elements from the end / 0 / -1 / 2
* returns A new array with the item moved to the new position [1,2,3] -> [1,3,2]
*/
const arrayMoveMutate = (array: [], from: number, to: number) => {
const arrLength = array.length;
const startIndex = indexSub(arrLength, from);
if (startIndex >= 0 && startIndex < arrLength) {
const endIndex = indexSub(arrLength, to);
const [item] = array.splice(from, 1);
array.splice(endIndex, 0, item);
}
};
/**
*
* 这个函数将返回值全部统一成数组// modal的不走此方法
* @param {keyof FunctionDataMap} v
* @param {Record<string, any>} args
* @param {string} name
* @param {UserConfig} config
* @param {Record<string, any>} ctx
* @return {Array<string, any>}
*/
export const changeUserValue = (
v: keyof FunctionDataMap,
args: Record<string, any>,
name: string,
config: UserConfig,
ctx: Record<string, any>
) => {
const userChoose = args[name];
switch (v) {
case 'ctx':
if (Array.isArray(userChoose)) {
return userChoose.reduce((pr: Array<string>, ne: string) => {
const val = ctx[ne];
pr.push(val);
return pr;
}, []);
}
return [];
case 'dataSource':
const dataCenter = config.getDataCenter().getDataMap();
if (Array.isArray(userChoose)) {
return userChoose.reduce((pr: Array<string>, ne: string) => {
const val = dataCenter[ne];
pr.push(val);
return pr;
}, []);
}
return [];
default:
if (Array.isArray(userChoose)) {
return userChoose;
}
return [];
}
};
/**
*
* 这个函数将返回值全部统一成对象 modal的不走此方法
* @param {keyof FunctionDataMap} v
* @param {Record<string, any>} args
* @param {string} name
* @param {UserConfig} config
* @param {Record<string, any>} ctx
* @return {Record<string, any>}
*/
export const changeUserValueRecord = (
v: keyof FunctionDataMap,
args: Record<string, any>,
name: string,
config: UserConfig,
ctx: Record<string, any>
) => {
const userChoose = args[name];
switch (v) {
case 'ctx':
if (Array.isArray(userChoose)) {
return userChoose.reduce((pr: Record<string, any>, ne: string) => {
const val = ctx[ne];
return Object.assign(pr, { [ne]: val });
}, {});
}
return {};
case 'dataSource':
const dataCenter = config.getDataCenter().getDataMap();
if (Array.isArray(userChoose)) {
return userChoose.reduce((pr: Record<string, any>, ne: string) => {
const val = dataCenter[ne];
return Object.assign(pr, { [ne]: val });
}, {});
}
return {};
default:
if (Array.isArray(userChoose)) {
return userChoose.reduce((pr: Record<string, any>, ne: string) => {
return Object.assign(pr, { [ne]: ne });
}, {});
}
return {};
}
};

View File

@@ -0,0 +1 @@
export const specialCoList = ['modalMask'];

View File

@@ -0,0 +1,98 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 05:35:15
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-04 17:52:02
* @FilePath: \DooringV2\packages\dooring-v2-lib\src\hooks\index.ts
*/
import { store } from '../runtime/store';
import { useEffect, useMemo, useState } from 'react';
import UserConfig from '../config';
import { ComponentRenderConfigProps } from '../core/components/componentItem';
import { registCommandFn, unRegistCommandFn } from '../core/command/runtime';
export function useStoreState(
config: UserConfig,
extraFn: Function = () => {},
everyFn: Function = () => {}
) {
const [state, setState] = useState(store.getData());
const forceUpdate = useState(0)[1];
useEffect(() => {
const unRegister = store.subscribe(() => {
setState(store.getData());
config.getEventCenter().syncEventMap(store.getData(), config.getStoreChanger());
extraFn();
});
store.setForceUpdate(() => forceUpdate((v) => v + 1));
const commandModules = config.getConfig().initCommandModule;
const commander = config.getCommanderRegister();
registCommandFn(commandModules, commander);
return () => {
unRegister();
unRegistCommandFn(commandModules, commander);
};
}, [config, extraFn]);
useEffect(() => {
everyFn();
}, [everyFn]);
// 去除默认滚动
useEffect(() => {
const fn1 = function (event: Event) {
if ((event as MouseEvent).ctrlKey === true || (event as MouseEvent).metaKey) {
event.preventDefault();
}
};
const fn2 = function (event: Event) {
if ((event as MouseEvent).ctrlKey === true || (event as MouseEvent).metaKey) {
event.preventDefault();
}
};
window.addEventListener('mousewheel', fn1, { passive: false });
//firefox
window.addEventListener('DOMMouseScroll', fn2, { passive: false });
return () => {
window.removeEventListener('mousewheel', fn1);
window.removeEventListener('mousewheel', fn2);
};
}, []);
return [state];
}
/**
*
* 组件动态注册eventMap与eventCenter
* @export
* @param {ComponentRenderConfigProps} props render参数传来的
* @param {string} eventName 同一个组件名称不能重复
* @returns
*/
export function useDynamicAddEventCenter(
props: ComponentRenderConfigProps,
eventName: string,
displayName: string
) {
const eventCenter = useMemo(() => {
return props.config.getEventCenter();
}, [props.config]);
useEffect(() => {
const data = props.store.getData();
const map = props.data.eventMap;
const storeItem = data.block.find((v) => v.id === props.data.id);
if (storeItem) {
if (!map[eventName]) {
//动态store加属性需要通过hook
storeItem.eventMap[eventName] = {
arr: [],
displayName,
userSelect: [],
};
eventCenter.manualUpdateMap(eventName, displayName);
}
}
}, [eventCenter, props.data.eventMap, props.data.id, props.store]);
return;
}

View File

@@ -0,0 +1,173 @@
.yhLeftrender {
overflow: auto;
.leftco {
padding: 10px;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
.coitem {
margin: 5px;
.redbox {
height: 68px;
width: 68px;
background: var(--redbox-color);
// line-height: 20px;
display: flex;
align-items: center;
justify-content: center;
#icon-checkbox,
#icon-tabs,
#icon-jiantou path {
fill: currentColor;
}
}
}
}
}
.yh_container {
// ::selection {
// color: inherit;
// background-color: inherit;
// }
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
:global(.am-list-item) {
background-color: rgba(255, 255, 255, 0);
}
:global(.am-list-body) {
background-color: rgba(255, 255, 255, 0);
}
:global(.ant-input) {
background-color: rgba(255, 255, 255, 0);
}
}
.yh_container_preview {
:global(.am-list-item) {
background-color: rgba(255, 255, 255, 0);
}
:global(.am-list-body) {
background-color: rgba(255, 255, 255, 0);
}
:global(.ant-input) {
background-color: rgba(255, 255, 255, 0);
}
}
.yh_block_focus {
&::before {
position: absolute;
top: -3px;
left: -3px;
right: -3px;
bottom: -3px;
content: '';
border: 2px dashed #2196f3;
}
}
.yhTempDiv {
background-color: #7165fa2b;
}
.resizepoint {
position: absolute;
height: 6px;
width: 6px;
background-color: #2196f3;
cursor: pointer;
border-radius: 50%;
&.left {
left: -6px;
top: calc(50% - 3px);
&:hover {
cursor: e-resize;
}
}
&.right {
right: -6px;
top: calc(50% - 3px);
&:hover {
cursor: e-resize;
}
}
&.top {
top: -6px;
left: calc(50% - 3px);
&:hover {
cursor: s-resize;
}
}
&.bottom {
bottom: -6px;
left: calc(50% - 3px);
&:hover {
cursor: s-resize;
}
}
&.topleft {
top: -6px;
left: -6px;
&:hover {
cursor: nw-resize;
}
}
&.bottomleft {
bottom: -6px;
left: -6px;
&:hover {
cursor: ne-resize;
}
}
&.bottomright {
bottom: -6px;
right: -6px;
&:hover {
cursor: nw-resize;
}
}
&.topright {
top: -6px;
right: -6px;
&:hover {
cursor: ne-resize;
}
}
}
.menu_footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
bottom: 0;
}

View File

@@ -0,0 +1,60 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:22:18
* @LastEditors: yehuozhili
* @LastEditTime: 2021-07-07 03:38:44
* @FilePath: \dooringv2\packages\dooringx-lib\src\index.tsx
*/
// 初始化store用
export { useStoreState } from './hooks';
// 动态添加事件使用
export { useDynamicAddEventCenter } from './hooks';
// 画布组件
export { default as Container } from './components/container';
// 左边组件
export { default as LeftConfig } from './components/leftConfig';
// 预览组件
export { default as Preview } from './components/preview';
// 右侧配置组件
export { default as RightConfig } from './components/rightConfig';
// 画布外层
export { default as ContainerWrapper } from './components/wrapperMove';
// 控制全局 弹窗等
export { default as Control } from './components/control';
// 这个放到外层容器属性里 ...innerContainerDragUp()
export { innerContainerDragUp } from './core/innerDrag';
// 用于修改store
export { store } from './runtime/store';
// 用于获取运行中的实例
export { commander, componentRegister, formRegister, storeChanger } from './runtime';
export { unmountContextMenu } from './core/contextMenu';
// 用户的设置 包括可以获取store commander
export { default as UserConfig } from './config';
// 合并配置项
export { userConfigMerge } from './config';
//制作放大缩小的函数
export { scaleFn } from './core/scale/index';
// 以下导出用于制作插件
// 用于制作组件的函数
export { ComponentItemFactory } from './core/components/abstract';
// 用于制作组件配置项的函数
export { createPannelOptions } from './core/components/formTypes';
// 用于config类型定义
export { InitConfig } from './config';
// 用于制作函数转换
export { changeUserValueRecord } from './core/utils/index';
export { changeUserValue } from './core/utils/index';
// 用于制作快捷键
export { CommanderItemFactory } from './core/command/abstract';
export { defaultStore } from './config';
//state
export { focusState } from './core/focusHandler/state';
//utils
export { deepCopy, rgba2Obj, swap, createUid, arrayMove } from './core/utils';
export { specialCoList } from './core/utils/special';

View File

@@ -0,0 +1,17 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 04:33:52
* @LastEditors: yehuozhili
* @LastEditTime: 2021-05-30 15:51:50
* @FilePath: \dooringv2\packages\dooring-v2-lib\src\runtime\index.ts
*/
import CommanderWrapper from '../core/command';
import ComponentRegister from '../core/components';
import { FormComponentRegister } from '../core/components/formComponentRegister';
import { StoreChanger } from '../core/storeChanger';
import { store } from './store';
export const commander = new CommanderWrapper(store);
export const componentRegister = new ComponentRegister();
export const formRegister = new FormComponentRegister();
export const storeChanger = new StoreChanger();

View File

@@ -0,0 +1,11 @@
/*
* @Author: yehuozhili
* @Date: 2021-03-14 11:32:30
* @LastEditors: yehuozhili
* @LastEditTime: 2021-03-14 11:32:48
* @FilePath: \dooring-v2\src\runtime\store.ts
*/
// 单独提出来为了避免循环引用
import Store from '../core/store';
export const store = new Store();