This commit is contained in:
hufeixiong
2022-03-31 17:58:43 +08:00
parent 0e8baf3502
commit 4c26ac805d
6 changed files with 331 additions and 109 deletions

View File

@@ -4,7 +4,7 @@ import { Col, Row, Select, InputNumber, Button, Modal, Form, Space, Input } from
import { FormMap, FormBaseType } from '../formTypes';
import { CreateOptionsRes } from 'dooringx-lib/dist/core/components/formTypes';
import { AnimateItem, IBlockType, IStoreData } from 'dooringx-lib/dist/core/store/storetype';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { PlusOutlined } from '@ant-design/icons';
export interface FormAnimateControlType extends FormBaseType {}
@@ -138,7 +138,7 @@ function AnimateControl(props: AnimateControlProps) {
const [customModal, setCustomModal] = useState(false);
const [form] = Form.useForm();
const [positionEnable, setPositionEnable] = useState<boolean[]>([]);
return (
<>
<Space
@@ -391,15 +391,39 @@ function AnimateControl(props: AnimateControlProps) {
forceRender
visible={customModal}
onOk={() => {
console.log('ok', form.getFieldsValue());
form.validateFields().then((res) => {
const values = { ...res };
// 根据禁用情况去除xy
const result = values.keyframes.map((v: any, i: number) => {
if (!positionEnable[i]) {
// 做删除处理
return {
...v,
positionX: null,
positionY: null,
};
}
return v;
});
values.keyframes = result;
props.config.animateFactory.addUserInputIntoCustom(values, props.config);
setPositionEnable([]);
setCustomModal(false);
});
}}
onCancel={() => {
form.resetFields();
setCustomModal(false);
}}
>
<Form form={form}>
<Form.Item labelCol={{ span: 6 }} name="displayName" label="自定义动画显示名称">
<Form labelCol={{ span: 11 }} form={form}>
<Form.Item
required
rules={[{ required: true, message: '请输入名称!' }]}
labelCol={{ span: 6 }}
name="displayName"
label="自定义动画显示名称"
>
<Input></Input>
</Form.Item>
<Form.Item
@@ -410,32 +434,108 @@ function AnimateControl(props: AnimateControlProps) {
>
<Input disabled></Input>
</Form.Item>
<Form.List name="users">
<Form.List name="keyframes">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
<Form.Item label="坐标x" {...restField} name={[name, 'xx']}>
<Input placeholder="First Name" />
</Form.Item>
<Form.Item label="坐标y" {...restField} name={[name, 'cc']}>
<Input placeholder="Last Name" />
</Form.Item>
<Form.Item label="旋转" {...restField} name={[name, '坐标']}>
<Input placeholder="First Name" />
</Form.Item>
<Form.Item label="缩放" {...restField} name={[name, '旋转']}>
<Input placeholder="Last Name" />
</Form.Item>
<Form.Item label="透明" {...restField} name={[name, '旋转']}>
<Input placeholder="Last Name" />
<Space
key={key}
style={{ display: 'flex', marginBottom: 8, flexWrap: 'wrap' }}
align="baseline"
>
<Form.Item
style={{ width: 180 }}
label="时间百分比"
{...restField}
name={[name, 'percent']}
initialValue={0}
>
<InputNumber min={0} max={100} formatter={(value) => `${value}%`} />
</Form.Item>
{positionEnable[key] && (
<>
<Form.Item
style={{ width: 180 }}
label="坐标x"
{...restField}
name={[name, 'positionX']}
initialValue={0}
>
<InputNumber min={0} />
</Form.Item>
<Form.Item
style={{ width: 180 }}
label="坐标y"
{...restField}
name={[name, 'positionY']}
initialValue={0}
>
<InputNumber min={0} />
</Form.Item>
</>
)}
<MinusCircleOutlined onClick={() => remove(name)} />
<Form.Item
style={{ width: 180 }}
label="旋转"
{...restField}
name={[name, 'rotate']}
initialValue={0}
>
<InputNumber formatter={(value) => `${value}°`} />
</Form.Item>
<Form.Item
style={{ width: 180 }}
label="缩放"
{...restField}
name={[name, 'scale']}
initialValue={100}
>
<InputNumber formatter={(value) => `${value}%`} />
</Form.Item>
<Form.Item
style={{ width: 180 }}
label="透明度"
{...restField}
name={[name, 'opacity']}
initialValue={100}
>
<InputNumber formatter={(value) => `${value}%`} />
</Form.Item>
<Button
onClick={() => {
setPositionEnable((pre) => {
pre[key] = !pre[key];
return [...pre];
});
}}
>
</Button>
<Button
danger
onClick={() => {
setPositionEnable((pre) => {
pre.splice(key, 1);
return [...pre];
});
remove(name);
}}
>
</Button>
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
<Button
type="dashed"
onClick={() => {
setPositionEnable((pre) => [...pre, true]);
add();
}}
block
icon={<PlusOutlined />}
>
</Button>
</Form.Item>

View File

@@ -6,7 +6,7 @@
* @FilePath: \dooringx\packages\dooringx-lib\src\config\index.tsx
*/
import React from 'react';
import { IBlockType, IStoreData } from '../core/store/storetype';
import { IBlockType, IMainStoreData, IStoreData } from '../core/store/storetype';
import { ComponentClass, FunctionComponent, ReactNode } from 'react';
import { ComponentItemFactory } from '../core/components/abstract';
import { marklineConfig } from '../core/markline/marklineConfig';
@@ -34,6 +34,7 @@ import { VerticalAlignMiddleOutlined } from '@ant-design/icons';
import { wrapperMoveState } from '../components/wrapperMove/event';
import { wrapperMoveState as iframeWrapperMoveState } from '../components/IframeWrapperMove/event';
import { TimeLineConfigType, TimeLineNeedleConfigType } from '../components/timeLine/timeline';
import { AnimateFactory } from '../core/AnimateFactory';
// 组件部分
/**
@@ -158,7 +159,7 @@ export interface InitConfig {
containerIcon: ReactNode;
}
export const defaultStore: IStoreData = {
export const defaultStore: IMainStoreData = {
container: {
width: 375,
height: 667,
@@ -173,6 +174,7 @@ export const defaultStore: IStoreData = {
title: 'dooring',
bodyColor: 'rgba(255,255,255,1)',
script: [],
customAnimate: [],
},
modalConfig: {},
};
@@ -337,6 +339,7 @@ export class UserConfig {
public componentRegister = new ComponentRegister();
public formRegister = new FormComponentRegister();
public storeChanger = new StoreChanger();
public animateFactory = new AnimateFactory();
public componentCache = {};
public asyncComponentUrlMap = {} as AsyncCacheComponentType;
public marklineConfig = marklineConfig;

View File

@@ -0,0 +1,178 @@
import UserConfig from '../../config';
import { CustomAnimateObj, IMainStoreData, IStoreData } from '../store/storetype';
import { deepCopy } from '../utils';
/**
*
* opacity: 100
percent: 0
positionX: 0
positionY: 0
rotate: 0
scale: 100
* @export 转换使用
* @interface TransformItemObj
*/
export interface TransformItemObj {
opacity: number;
percent: number;
positionX: number | null;
positionY: number | null;
rotate: number;
scale: number;
}
/**
*
*
* @export 用户输入对象
* @interface TransformItem
*/
export interface TransformItem {
displayName: string;
animateName: string;
keyframes: TransformItemObj[];
}
export class AnimateFactory {
constructor(public customAnimateName: Array<CustomAnimateObj> = []) {}
getCustomAnimateName() {
return this.customAnimateName;
}
getStyleSheets() {
return document.styleSheets;
}
/**
*
* 插入动画
* @param {string} ruleText
* @param {string} keyframeName 动画名称
* @memberof AnimateFactory
*/
inserKeyframeAnimate(ruleText: string, keyframeName: string) {
const sheets = this.getStyleSheets();
if (sheets.length === 0) {
let style = document.createElement('style');
style.appendChild(document.createTextNode(''));
document.head.appendChild(style);
}
const len = sheets.length;
let ss: number | null = null;
let st: number | null = null;
for (let i = 0; i < len; i++) {
for (let k = 0; k < sheets[i].cssRules.length; k++) {
const rule = sheets[i].cssRules[k] as CSSKeyframesRule;
const name = rule?.name;
if (name && name === keyframeName) {
// 删除该keyframe
ss = i;
st = k;
}
}
}
if (ss !== null && st !== null) {
sheets[ss].deleteRule(st);
}
let sheet = sheets[ss ? ss : sheets.length - 1] as CSSStyleSheet;
sheet.insertRule(ruleText, sheet.rules ? sheet.rules.length : sheet.cssRules.length);
}
/**
*
* 配置时使用
* @param {Array<CustomAnimateObj>} [customAnimateNameArr=[]]
* @memberof AnimateFactory
*/
addCustomAnimate(customAnimateNameArr: Array<CustomAnimateObj> = []) {
this.customAnimateName = [...this.customAnimateName, ...customAnimateNameArr];
}
/**
*
* 删除使用animateName 防止displayName重名 用完需要同步store
* @param {string} animateName
* @memberof AnimateFactory
*/
deleteCustomAnimate(animateName: string) {
this.customAnimateName = this.customAnimateName.filter((v) => v.animateName !== animateName);
}
/**
*
* 从配置项插入动画 导入设置
* @memberof AnimateFactory
*/
fromArrInsertKeyFrame(customAnimateName: Array<CustomAnimateObj> = this.customAnimateName) {
customAnimateName.forEach((v) => {
this.inserKeyframeAnimate(v.keyframe, v.animateName);
});
}
/**
*
* 将this.customAnimateName写入store
* @memberof AnimateFactory
*/
syncToStore(config: UserConfig) {
// 先判断global的位置
const store = config.getStore();
let data: IStoreData;
const isEdit = config.getStoreChanger().isEdit();
if (isEdit) {
const origin = config.getStoreChanger().getOrigin()!;
data = origin.data[origin.current];
} else {
data = store.getData();
}
const copy: IMainStoreData = deepCopy(data);
const originGlobal = copy.globalState as IMainStoreData['globalState'];
originGlobal.customAnimate = [...this.customAnimateName];
if (isEdit) {
config.getStoreChanger().updateOrigin(copy);
} else {
store.setData(copy);
}
}
/**
*
* 将用户输入转换为新的动画
* @param {TransformItem} item
* @memberof AnimateFactory
*/
addUserInputIntoCustom(item: TransformItem, config: UserConfig) {
// 先转换keyframe
const keyframeItem = item.keyframes.map((v) => {
if (v.positionX !== null && v.positionY !== null) {
// 带入xy 否则不计算xy
return `${v.percent}% {
transform:translate(${v.positionX}px, ${v.positionY}px) scale(${(v.scale / 100).toFixed(
2
)}) rotate(${v.rotate}deg);
}`;
} else {
return `${v.percent}% {
transform: scale(${(v.scale / 100).toFixed(2)}) rotate(${v.rotate}deg);
}`;
}
});
const keyframe = `@keyframes ${item.animateName} {
${keyframeItem.join(' ')}
}`;
const customAnimateNameArr: CustomAnimateObj[] = [
{
displayName: item.displayName,
keyframe,
animateName: item.animateName,
},
];
// 添加内置
this.addCustomAnimate(customAnimateNameArr);
// 插入动画
this.inserKeyframeAnimate(keyframe, item.animateName);
// 写入store
this.syncToStore(config);
}
}

View File

@@ -1,82 +0,0 @@
export interface CustomAnimateObj {
displayName: string;
animateName: string;
keyframe: string;
}
export class DynamicAnimate {
constructor(public customAnimateName: Array<CustomAnimateObj> = []) {}
getCustomAnimateName() {
return this.customAnimateName;
}
getStyleSheets() {
return document.styleSheets;
}
/**
*
* 插入动画
* @param {string} ruleText
* @param {string} keyframeName
* @memberof DynamicAnimate
*/
inserKeyframeAnimate(ruleText: string, keyframeName: string) {
const sheets = this.getStyleSheets();
if (sheets.length === 0) {
let style = document.createElement('style');
style.appendChild(document.createTextNode(''));
document.head.appendChild(style);
}
const len = sheets.length;
let ss: number | null = null;
let st: number | null = null;
for (let i = 0; i < len; i++) {
for (let k = 0; k < sheets[i].cssRules.length; k++) {
const rule = sheets[i].cssRules[k] as CSSKeyframesRule;
const name = rule?.name;
if (name && name === keyframeName) {
// 删除该keyframe
ss = i;
st = k;
}
}
}
if (ss !== null && st !== null) {
sheets[ss].deleteRule(st);
}
let sheet = sheets[ss ? ss : sheets.length - 1] as CSSStyleSheet;
sheet.insertRule(ruleText, sheet.rules ? sheet.rules.length : sheet.cssRules.length);
}
/**
*
* 配置时使用
* @param {Array<CustomAnimateObj>} [customAnimateName=[]]
* @memberof DynamicAnimate
*/
addCustomAnimate(customAnimateName: Array<CustomAnimateObj> = []) {
this.customAnimateName = [...this.customAnimateName, ...customAnimateName];
}
/**
*
* 删除使用animateName 防止displayName重名
* @param {string} animateName
* @memberof DynamicAnimate
*/
deleteCustomAnimate(animateName: string) {
this.customAnimateName = this.customAnimateName.filter((v) => v.animateName !== animateName);
}
/**
*
* 从配置项插入动画 导入设置
* @memberof DynamicAnimate
*/
fromArrInsertKeyFrame(customAnimateName: Array<CustomAnimateObj> = this.customAnimateName) {
customAnimateName.forEach((v) => {
this.inserKeyframeAnimate(v.keyframe, v.animateName);
});
}
}

View File

@@ -8,6 +8,15 @@
import { EventCenterMapType } from '../eventCenter';
export interface GlobalState {
[key: string]: any;
customAnimate: CustomAnimateObj[];
containerColor: string;
title: string;
bodyColor: string;
script: string[];
}
export interface IStoreData {
container: {
width: number;
@@ -19,6 +28,10 @@ export interface IStoreData {
globalState: Record<string, any>;
modalConfig: Record<string, any>;
}
export interface IMainStoreData extends IStoreData {
globalState: GlobalState;
}
export interface AnimateItem {
uid: string;
animationName: string;
@@ -26,8 +39,12 @@ export interface AnimateItem {
animationDelay: number;
animationIterationCount: string;
animationTimingFunction: string;
isCustom?: boolean;
customKeyFrame?: string;
}
export interface CustomAnimateObj {
displayName: string;
animateName: string;
keyframe: string;
}
export interface IBlockType {

View File

@@ -82,6 +82,12 @@ export class StoreChanger {
return this.map[ORIGIN];
}
/**
* 判断是否在编辑模式。
* 一次也没进行编辑时storeChanger中未存store所以只能判断去获取。
* @return {*}
* @memberof StoreChanger
*/
isEdit() {
if (storeChangerState.modalEditName !== '') {
return true;