Skip to content

Commit

Permalink
feat: add c2d2c implementation (#51)
Browse files Browse the repository at this point in the history
* 🐛 fix: docs error

* 📝 docs: update doc

* 🐛 fix: getDefaultValueFromSchema 实现不一致

* ✨ chore: add c2d2c implementation

* ✨ feat: use sub package of lodash

* ➕ chore: add lodash.uniq

* ✅ test: 添加 c2d2c 测试用例

* 🎉 test: fix test case

---------

Co-authored-by: rdmclin2 <rdmclin2@gmial.com>
  • Loading branch information
rdmclin2 and rdmclin2 committed Aug 1, 2023
1 parent 60f968a commit c0b1c5f
Show file tree
Hide file tree
Showing 9 changed files with 507 additions and 16 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,17 @@
"highlight.js": "~10.5.0",
"immer": "^9.0.21",
"leva": "^0.9.35",
"lodash-es": "^4.17.21",
"lodash.flatten": "^4.4.0",
"lodash.get": "^4.4.2",
"lodash.isempty": "^4.4.0",
"lodash.isequal": "^4.5.0",
"lodash.isnil": "^4.0.0",
"lodash.merge": "^4.6.2",
"lodash.omitby": "^4.6.0",
"lodash.template": "^4.5.0",
"lodash.union": "^4.6.0",
"lodash.unionby": "^4.8.0",
"lodash.uniq": "^4.5.0",
"mockjs": "^1.1.0",
"polished": "^4.2.2",
"prettier": "^2.8.8",
Expand Down Expand Up @@ -118,7 +120,6 @@
"@testing-library/user-event": "^14.4.3",
"@types/color": "^3.0.3",
"@types/json-schema": "^7.0.12",
"@types/lodash-es": "^4.17.8",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@umijs/lint": "^4.0.72",
Expand Down
2 changes: 1 addition & 1 deletion src/SortableList/store/listDataReducer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UniqueIdentifier } from '@dnd-kit/core';
import { arrayMove } from '@dnd-kit/sortable';
import { produce } from 'immer';
import { merge } from 'lodash-es';
import merge from 'lodash.merge';

import type { KeyManager } from '../type';

Expand Down
63 changes: 63 additions & 0 deletions src/types/c2d2c.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export interface BoundBox {
width: number;
height: number;
}

export type SizeFollow = 'sketch' | 'self';

/**
* 组件尺寸控制
*/
export interface ComponentSize extends Partial<BoundBox> {
widthFollow?: SizeFollow;
heightFollow?: SizeFollow;
}

export type VerticalType = 'top' | 'center' | 'bottom';
export type HorizontalType = 'left' | 'center' | 'right';

export interface Alignment {
/**
* 横向位置
*/
vertical: VerticalType;
/**
* 纵向位置
*/
horizontal: HorizontalType;
/**
* 是否翻转相应的坐标系
*/
verticalFlipped?: boolean;
}

/**
* C2D 组件预设值
*/
export interface C2DPresetValue {
/**
* 组件名 componentName
*/
componentName: string;
/**
* 组件属性
*/
props: any;
/**
* 对齐方式
*/
alignment?: Alignment;
/**
* 组件大小
*/
size?: ComponentSize;
/**
* 如果存在配置属性,那么存在这里持久化保存
*/
config?: any;
}

export interface ReactNodeElement {
$$__type: 'element';
$$__body: C2DPresetValue;
}
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './c2d2c';
export * from './catogory';
export * from './field';
export * from './schema';
138 changes: 138 additions & 0 deletions src/utils/c2d2c.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { JSONSchema } from '@/types/schema';
import isEmpty from 'lodash.isempty';
import isNil from 'lodash.isnil';
import omitBy from 'lodash.omitby';
import uniq from 'lodash.uniq';
import { ReactNodeElement } from '../types';

/**
* 从schema 获取预设值
* @param schema
*/
export const getDefaultValueFromSchema = (schema: JSONSchema) => {
if (schema.type === 'object') {
if (!schema.properties) return;
return Object.fromEntries(
Object.entries(schema.properties).map(([key, value]) => [key, value.default]),
);
}
if (schema.type === 'null') return null;
return schema.default;
};

/**
* 获取组件库导入代码
*/
export const generateImportCode = (pkg: string, components: string[]) => {
return `import { ${uniq(components).join(', ')} } from '${pkg}';`;
};

/**
* 将 prop 转换成字符串
*/
export const createPropString = (
key: string,
// eslint-disable-next-line @typescript-eslint/ban-types
value: string | number | boolean | symbol | object | undefined | Function | any[],
) => {
switch (typeof value) {
case 'undefined':
return '';
case 'object':
// 数组
if (value instanceof Array) {
return `${key}={${JSON.stringify(value)}}`;
}

// eslint-disable-next-line no-case-declarations
const clearValue = omitBy(value, isNil);
// 如果 object 里不存在任何值,返回空
if (Object.values(clearValue).length === 0) return '';

// eslint-disable-next-line no-case-declarations
const genObjStr = () => {
// 如果包含 $$__type 属性,说明是 ReactNode 或 icon
if ((value as ReactNodeElement).$$__type) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return genChildrenJSXCode(value as ReactNodeElement);
}

return JSON.stringify(clearValue, null);
};

return `${key}={${genObjStr()}}`;
case 'boolean':
if (value) return `${key}`;

return `${key}={${value}}`;
case 'number':
return `${key}={${value}}`;
case 'string':
if (isEmpty(value)) return '';

return `${key}="${value}"`;
case 'function':
return `${key}={${value.toString()}}`;
case 'symbol':
return `${key}={Symbol.for('${value.description}')}`;
}
};

/**
* 生成 React JSX 代码
* @param component
* @param props
* @param PropStringFn
*/
export const generateJSXCode = (
component: string,
props: Record<string, any>,
PropStringFn = createPropString,
) => {
if (!props) {
return `<${component} />`;
}

const inline = !props.children;

const propsStr = Object.entries(props)
// 针对 children 有值的情况下,在 props 上过滤掉 children
.filter((v) => (inline ? v : v[0] !== 'children'))
.map((entry) => PropStringFn(entry[0], entry[1]))
// 过滤空的对象
.filter((v) => v)
.join(' ');

if (inline) return `<${component} ${propsStr}/>`;

// eslint-disable-next-line @typescript-eslint/no-use-before-define
return `<${component} ${propsStr}>${genChildrenJSXCode(props.children)}</${component}>`;
};

/**
* 生成子 JSX 代码
* @param children
*/
const genChildrenJSXCode = (children: string | ReactNodeElement | ReactNodeElement[]): string => {
// children 为字符串的场景
if (typeof children === 'string') {
return children;
}

const renderChildNode = (child: ReactNodeElement) => {
const { $$__type, $$__body } = child;

switch ($$__type) {
// children 为子组件的场景
case 'element':
return generateJSXCode($$__body.componentName, $$__body.props);
// TODO: children 为 Icon 的场景
}
};

if (children instanceof Array) {
return children.map(renderChildNode).join('\n');
}

return renderChildNode(children);
};
2 changes: 1 addition & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './autoId';
export * from './schema';
export * from './c2d2c';
12 changes: 0 additions & 12 deletions src/utils/schema.ts

This file was deleted.

Loading

0 comments on commit c0b1c5f

Please sign in to comment.