Skip to content

Commit

Permalink
fix: add prop value template & fix components popover (#201)
Browse files Browse the repository at this point in the history
* fix: clear value in CodeSetter

* fix: update variable path in codesetter

* fix: add prop value template

* fix: update  recommendedList for ComponentsPopover

* fix: update icon size in line mode for ComponentsPanel
  • Loading branch information
wwsun authored Aug 30, 2024
1 parent 179de0c commit c4e1ed6
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 79 deletions.
5 changes: 4 additions & 1 deletion apps/playground/src/helpers/mock-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ import { definePage } from "@music163/tango-boot";
import {
Page,
Section,
Box,
Button,
Input,
FormilyForm,
Expand All @@ -167,7 +168,9 @@ class App extends React.Component {
render() {
return (
<Page title={tango.stores.app.title} subTitle={<><Button>hello</Button></>}>
<Section tid="section0" />
<Section tid="section0">
<Box></Box>
</Section>
<Section tid="section1" title="Section Title">
your input: <Input tid="input1" defaultValue="hello" />
copy input: <Input value={tango.page.input1?.value} />
Expand Down
7 changes: 7 additions & 0 deletions apps/playground/src/helpers/prototypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ const prototypes: Dict<IComponentPrototype> = {
title: 'd',
setter: 'textSetter',
},
{
name: 'onClick',
title: '点击事件',
setter: 'eventSetter',
template: '(e) => {\n {{content}}\n}',
tip: '回调参数说明:e 为事件对象',
},
],
},
Columns: {
Expand Down
2 changes: 1 addition & 1 deletion apps/storybook/.storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SystemProvider } from 'coral-system';
import 'antd/dist/antd.css';

export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
actions: { argTypesRegex: '^on.*' },
controls: {
matchers: {
color: /(background|color)$/i,
Expand Down
29 changes: 29 additions & 0 deletions apps/storybook/src/ui/action-select.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { ActionSelect } from '@music163/tango-ui';

export default {
title: 'UI/ActionSelect',
component: ActionSelect,
};

const options = [
{ label: 'action1', value: 'action1' },
{ label: 'action2', value: 'action2' },
{ label: 'action3', value: 'action3' },
];

export const Basic = {
args: {
defaultText: '选择动作',
options,
onSelect: console.log,
},
};

export const showInput = {
args: {
text: '选择动作',
options,
showInput: true,
},
};
35 changes: 14 additions & 21 deletions packages/designer/src/components/components-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@ export const ComponentsPopover = observer(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
workspace.componentPrototypes.get(selectedNode?.name) ?? ({} as IComponentPrototype);

// 推荐使用的子组件
const insertedList = useMemo(
() =>
Array.isArray(prototype?.childrenName)
? prototype?.childrenName
: [prototype?.childrenName].filter(Boolean),
[prototype?.childrenName],
);

// 推荐使用的代码片段
const siblingList = useMemo(() => prototype?.siblingNames ?? [], [prototype.siblingNames]);
const recommendedList = useMemo(() => {
if (type === 'inner') {
return prototype?.childrenName
? Array.isArray(prototype?.childrenName)
? prototype.childrenName
: [prototype.childrenName]
: [];
}
// 默认推荐使用相同类型的组件作为兄弟节点
return prototype.siblingNames || [prototype.name];
}, [prototype.childrenName, prototype.siblingNames, prototype.name, type]);

const tipsTextMap = useMemo(
() => ({
Expand Down Expand Up @@ -82,22 +82,15 @@ export const ComponentsPopover = observer(
const menuList = JSON.parse(JSON.stringify(designer.menuData));

const commonList = menuList['common'] ?? [];
if (commonList?.length && siblingList?.length) {
commonList.unshift({
title: '代码片段',
items: siblingList,
});
}

if (commonList?.length && insertedList?.length) {
if (commonList?.length && recommendedList.length) {
commonList.unshift({
title: '推荐使用',
items: insertedList,
items: recommendedList,
});
}

return menuList;
}, [insertedList, siblingList, designer.menuData]);
}, [recommendedList, designer.menuData]);

const innerTypeProps =
// 手动触发 适用于 点击添加组件
Expand Down
58 changes: 27 additions & 31 deletions packages/designer/src/setters/event-setter.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { useCallback, useMemo, useState } from 'react';
import { css, Box, Text } from 'coral-system';
import { AutoComplete } from 'antd';
import { AutoComplete, Input } from 'antd';
import { ActionSelect } from '@music163/tango-ui';
import { FormItemComponentProps } from '@music163/tango-setting-form';
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
import { Dict, wrapCode } from '@music163/tango-helpers';
import { ExpressionPopover } from './expression-setter';
import { ExpressionPopover, getCallbackValue } from './expression-setter';
import { value2code } from '@music163/tango-core';

enum EventAction {
Expand All @@ -31,7 +31,7 @@ export type EventSetterProps = FormItemComponentProps<string>;
* 事件监听函数绑定器
*/
export function EventSetter(props: EventSetterProps) {
const { value, onChange, modalTitle } = props;
const { value, onChange, modalTitle, modalTip, template } = props;
const [type, setType] = useState<EventAction>(); // 事件类型
const [temp, setTemp] = useState(''); // 二级暂存值
const { actionVariables, routeOptions } = useWorkspaceData();
Expand Down Expand Up @@ -59,7 +59,9 @@ export function EventSetter(props: EventSetterProps) {
label: (
<ExpressionPopover
title={modalTitle}
subTitle={modalTip}
value={value}
template={template}
onOk={(nextValue) => {
handleChange(nextValue);
}}
Expand All @@ -74,30 +76,34 @@ export function EventSetter(props: EventSetterProps) {
{ label: '打开弹窗', value: EventAction.OpenModal },
{ label: '关闭弹窗', value: EventAction.CloseModal },
],
[modalTitle, value, actionVariables, handleChange],
[modalTitle, value, actionVariables, template, handleChange],
);

const onAction = (key: string) => {
setType(key as EventAction); // 记录事件类型
setTemp(''); // 重置二级选项值

switch (key) {
case EventAction.ConsoleLog:
handleChange('(...args) => console.log(...args)');
break;
case EventAction.NoAction:
handleChange(undefined);
break;
default:
break;
if (key === EventAction.NoAction) {
handleChange(undefined);
return;
}
};

const actionText = getActionText(type, temp, code);

return (
<Box css={wrapperStyle}>
<ActionSelect options={options} onSelect={onAction} text={actionText} />
<ActionSelect options={options} onSelect={onAction} defaultText="请选择动作类型" />
{type === EventAction.ConsoleLog && (
<Input
placeholder="输入 Console.log 日志内容"
value={temp}
onChange={(e) => setTemp(e.target.value)}
onBlur={() => {
if (temp) {
handleChange(getExpressionValue(type, temp));
}
}}
/>
)}
{type === EventAction.NavigateTo && (
<AutoComplete
placeholder="选择或输入页面路由"
Expand Down Expand Up @@ -142,25 +148,15 @@ export function EventSetter(props: EventSetterProps) {
}

const handlerMap: Dict = {
[EventAction.OpenModal]: 'openModal',
[EventAction.CloseModal]: 'closeModal',
[EventAction.NavigateTo]: 'navigateTo',
[EventAction.ConsoleLog]: 'console.log',
[EventAction.OpenModal]: 'tango.openModal',
[EventAction.CloseModal]: 'tango.closeModal',
[EventAction.NavigateTo]: 'tango.navigateTo',
};

function getActionText(type: EventAction, temp: string, fallbackCode: string) {
let text;
if (handlerMap[type]) {
text = getExpressionValue(type, temp);
} else if (fallbackCode) {
text = fallbackCode;
}
text = text || '请选择';
return text;
}

function getExpressionValue(type: EventAction, value = '') {
const handler = handlerMap[type];
if (handler) {
return `() => tango.${handler}("${value}")`;
return getCallbackValue(`${handler}("${value}");`);
}
}
47 changes: 35 additions & 12 deletions packages/designer/src/setters/expression-setter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
import { Box, Text, css } from 'coral-system';
import { Dropdown, Button } from 'antd';
import { isValidExpressionCode } from '@music163/tango-core';
import { getValue, IVariableTreeNode, noop } from '@music163/tango-helpers';
import { getValue, interpolate, IVariableTreeNode, noop } from '@music163/tango-helpers';
import { CloseCircleFilled, MenuOutlined } from '@ant-design/icons';
import {
Panel,
Expand Down Expand Up @@ -34,6 +34,19 @@ export const jsonValueValidate = (value: string) => {
}
};

/**
* 拼装回调函数
* @param value 回调函数体
* @param template 回调函数模板
* @returns
*/
export function getCallbackValue(value: string, template?: string) {
if (!value) {
return;
}
return template ? interpolate(template, { content: value }) : `() => {\n ${value}\n}`;
}

const suffixStyle = css`
display: flex;
align-items: center;
Expand Down Expand Up @@ -63,6 +76,7 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
modalTitle,
modalTip,
autoCompleteOptions,
template,
placeholder = '在这里输入代码',
value: valueProp,
status,
Expand Down Expand Up @@ -105,7 +119,7 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
<CloseCircleFilled
title="清空"
onClick={() => {
change('');
change(undefined);
}}
/>
)}
Expand Down Expand Up @@ -134,6 +148,7 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
subTitle={modalTip}
placeholder={placeholder}
autoCompleteOptions={autoCompleteOptions}
template={template}
newStoreTemplate={newStoreTemplate}
value={inputValue}
expressionType={expressionType}
Expand Down Expand Up @@ -166,6 +181,10 @@ export interface ExpressionPopoverProps extends InputCodeProps {
onOk?: (value: string) => void;
dataSource?: IVariableTreeNode[];
autoCompleteOptions?: string[];
/**
* 值的模板,一般用于定义函数模板
*/
template?: string;
/**
* 新建 store 的模板代码
*/
Expand All @@ -186,6 +205,7 @@ export function ExpressionPopover({
value,
dataSource,
autoCompleteOptions,
template,
newStoreTemplate = CODE_TEMPLATES.newStoreTemplate,
children,
expressionType,
Expand Down Expand Up @@ -245,19 +265,18 @@ export function ExpressionPopover({
autoCompleteOptions={autoCompleteOptions}
/>
{error ? (
<Text color="red" fontSize="12px" as="p">
<Text color="red" fontSize="12px" as="div">
出错了!输入的表达式存在语法错误,请修改后再提交!
</Text>
) : null}
{subTitle && (
<Text fontSize="12px" color="text3" as="p">
{subTitle}
<Box fontSize="12px" color="text2">
<Text display="block">说明:</Text>
{subTitle && <Text display="block">{subTitle}</Text>}
<Text display="block">
你可以在上面的代码输入框里输入常规的 javascript 代码,还可以直接使用 jsx
代码,但需要符合该属性的接受值定义。
</Text>
)}
<Text fontSize="12px" color="text3" as="p">
说明:你可以在上面的代码输入框里输入常规的 javascript 代码,还可以直接使用 jsx
代码,但需要符合该属性的接受值定义。
</Text>
</Box>
</Box>
<Panel
title="从变量列表中选中"
Expand Down Expand Up @@ -296,7 +315,11 @@ export function ExpressionPopover({
}
let str;
if (/^(stores|services)\./.test(node.key)) {
str = `tango.${node.key.replaceAll('.', '?.')}`;
// 从匹配到的第二个点开始替换为 ?.,因为第一个点是 stores 或 services
str = `tango.${node.key.replace(/(?<=\..*?)\./g, '?.')}`;
if (node.type === 'function') {
str = getCallbackValue(`${str}();`, template);
}
} else {
str = `${node.key}`;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/designer/src/sidebar/components-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ function MaterialGrid({ data }: MaterialProps) {
<IconFont className="material-icon" type={data.icon || 'icon-placeholder'} />
) : (
<Box className="material-icon">
<img src={icon} alt={data.name} />
<img src={icon} alt={data.name} height="32" />
</Box>
);

Expand Down
14 changes: 14 additions & 0 deletions packages/helpers/src/helpers/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,17 @@ export function parseDndId(str: string): DndIdParsedType {
id: str,
};
}

/**
* 替换模板中的变量
* @example interpolate('hello {{name}}', { name: 'world' }) -> 'hello world'
*
* @param template 带模板变量的字符串
* @param props 变量字典
* @returns 返回替换后的字符串
*/
export function interpolate(template: string, props: Record<string, any>) {
return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
return props[key];
});
}
5 changes: 5 additions & 0 deletions packages/helpers/src/types/prototype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ export interface IComponentProp<T = any> {
* 自动补全的提示值,仅对 ExpressionSetter 有效
*/
autoCompleteOptions?: string[];
/**
* value 的模板,一般用于函数类型的属性,便于 setter 用来拼装返回值
* @example "(arg1, arg2, arg3) => { {{content}}}"
*/
template?: string;
/**
* 设置器
*/
Expand Down
Loading

0 comments on commit c4e1ed6

Please sign in to comment.