Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add prop value template #201

Merged
merged 5 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
wwsun marked this conversation as resolved.
Show resolved Hide resolved
}
} 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
Loading