mirror of
https://github.com/NetEase/tango.git
synced 2026-08-29 02:01:34 +08:00
fix: add showSearch and showGroups to SettingForm & update internal icons (#73)
* fix: add internal setters & fix ui * feat: add showSearch and showGroups to SettingForm * fix: update icons * fix: use antd form --------- Co-authored-by: wwsun <ww.sww@outlook.com>
This commit is contained in:
@@ -68,6 +68,7 @@ export default function App() {
|
||||
<Box px="l">
|
||||
<Toolbar>
|
||||
<Toolbar.Item key="routeSwitch" placement="left" />
|
||||
<Toolbar.Item key="history" placement="left" />
|
||||
<Toolbar.Item key="preview" placement="left" />
|
||||
<Toolbar.Item key="modeSwitch" placement="right" />
|
||||
<Toolbar.Item key="togglePanel" placement="right" />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { EditableVariableTree, FormModel, SettingForm } from '@music163/tango-setting-form';
|
||||
import { ComponentPrototypeType, getValue } from '@music163/tango-helpers';
|
||||
import { createServices } from '@music163/request';
|
||||
import React from 'react';
|
||||
import { FormModel, SettingForm } from '@music163/tango-setting-form';
|
||||
import { ComponentPrototypeType } from '@music163/tango-helpers';
|
||||
import { Box } from 'coral-system';
|
||||
import { JsonView } from '@music163/tango-ui';
|
||||
import { toJS } from 'mobx';
|
||||
@@ -9,51 +8,9 @@ import { observer } from 'mobx-react-lite';
|
||||
import { Card } from 'antd';
|
||||
|
||||
export default {
|
||||
title: 'SettingForm/ Setters',
|
||||
title: 'SettingForm',
|
||||
};
|
||||
|
||||
const modelVariables = [
|
||||
{
|
||||
title: 'stores',
|
||||
key: 'stores',
|
||||
selectable: false,
|
||||
children: [
|
||||
{
|
||||
title: 'app',
|
||||
key: 'stores.app',
|
||||
selectable: false,
|
||||
children: [
|
||||
{ title: 'app.title', key: 'stores.app.title', raw: '"hello"' },
|
||||
{ title: 'app.age', key: 'stores.app.age', raw: '20' },
|
||||
{ title: 'app.detail', key: 'stores.app.detail', raw: '{ foo: "foo" }' },
|
||||
{ title: 'app.list', key: 'stores.app.list', type: 'function', raw: '() => {}' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'user',
|
||||
key: 'stores.user',
|
||||
selectable: false,
|
||||
children: [
|
||||
{ title: 'name', key: 'stores.user.name' },
|
||||
{ title: 'age', key: 'stores.user.age' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'services',
|
||||
key: 'services',
|
||||
selectable: false,
|
||||
children: [
|
||||
{
|
||||
title: 'listUsers',
|
||||
key: 'services.listUsers',
|
||||
type: 'function',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const prototype: ComponentPrototypeType = {
|
||||
name: 'Test',
|
||||
exportType: 'namedExport',
|
||||
@@ -257,47 +214,6 @@ const prototype: ComponentPrototypeType = {
|
||||
],
|
||||
};
|
||||
|
||||
const deerService = createServices(
|
||||
{
|
||||
listMy: {
|
||||
url: '/my/upload/list',
|
||||
},
|
||||
listFav: {
|
||||
url: '/my/star/list',
|
||||
},
|
||||
listPub: {
|
||||
url: '/list',
|
||||
},
|
||||
},
|
||||
{
|
||||
baseURL: 'https://febase-openapi.fn.netease.com/deer/api/deer/pic',
|
||||
withCredentials: false, // 解决跨域时必须非*问题
|
||||
headers: {
|
||||
'Febase-Auth': 'dskPVkIRnQ2dEn1DbyxURmUj4rl4BsCh53xFAsJnvVs=',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const context = {
|
||||
stores: {
|
||||
app: {
|
||||
title: 'Sample App',
|
||||
age: 20,
|
||||
detail: {
|
||||
foo: 'foo',
|
||||
bar: 'bar',
|
||||
biz: {
|
||||
x: 'xxx',
|
||||
},
|
||||
},
|
||||
newKey: 'xxx',
|
||||
},
|
||||
foo: {
|
||||
test: 'test string',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 表单值预览
|
||||
*/
|
||||
@@ -310,9 +226,7 @@ export function Basic() {
|
||||
const model = new FormModel(
|
||||
{
|
||||
router: 'www.163.com',
|
||||
expression: {
|
||||
foo: 'foo',
|
||||
},
|
||||
expression: `{ foo: 'foo' }`,
|
||||
object: {
|
||||
name: 'Alice',
|
||||
},
|
||||
@@ -324,19 +238,7 @@ export function Basic() {
|
||||
|
||||
return (
|
||||
<Box display="flex">
|
||||
<SettingForm
|
||||
model={model}
|
||||
remoteServices={{
|
||||
ImageService: deerService as any,
|
||||
}}
|
||||
prototype={prototype}
|
||||
evaluateContext={{
|
||||
tango: context,
|
||||
__UNSAFE_TANGO_CURRENT_PAGE_RENDER_RUNTIME__: { routeData: { params: {}, query: {} } },
|
||||
}}
|
||||
modelVariables={modelVariables}
|
||||
expressionVariables={modelVariables}
|
||||
/>
|
||||
<SettingForm model={model} prototype={prototype} />
|
||||
<Box position="relative">
|
||||
<Card title="表单状态预览" style={{ position: 'sticky', top: 0 }}>
|
||||
<FormValuePreview model={model} />
|
||||
@@ -346,6 +248,14 @@ export function Basic() {
|
||||
);
|
||||
}
|
||||
|
||||
export function Lite() {
|
||||
return (
|
||||
<Box width={320} border="solid">
|
||||
<SettingForm showSearch={false} showGroups={false} prototype={prototype} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function NoExpressionSwitch() {
|
||||
const model = new FormModel({});
|
||||
return (
|
||||
@@ -354,30 +264,3 @@ export function NoExpressionSwitch() {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function Binding() {
|
||||
const [data, setData] = useState<any>({});
|
||||
return (
|
||||
<Box>
|
||||
<Box as="code" bg="highlight" color="white" fontSize="24px">
|
||||
selected: {data?.key}
|
||||
</Box>
|
||||
<EditableVariableTree
|
||||
dataSource={modelVariables as any}
|
||||
getPreviewValue={(node) => {
|
||||
if (!node || !node.key) {
|
||||
return;
|
||||
}
|
||||
if (node.type === 'function') {
|
||||
return node.raw;
|
||||
}
|
||||
const keyPath = node.key.replaceAll('?', '');
|
||||
return getValue(context, keyPath);
|
||||
}}
|
||||
onSelect={setData}
|
||||
onAddVariable={console.log}
|
||||
onSave={console.log}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { ToggleButton, IconFont } from '@music163/tango-ui';
|
||||
import { ToggleButton, UndoOutlined, RedoOutlined } from '@music163/tango-ui';
|
||||
import { Box, Flex } from 'coral-system';
|
||||
|
||||
export default {
|
||||
@@ -11,24 +11,24 @@ export function Basic() {
|
||||
<Box>
|
||||
<Flex gap="l" bg="#efefef" p="l">
|
||||
<ToggleButton>
|
||||
<IconFont type="icon-undo" />
|
||||
<UndoOutlined />
|
||||
</ToggleButton>
|
||||
<ToggleButton selected>
|
||||
<IconFont type="icon-redo" />
|
||||
<RedoOutlined />
|
||||
</ToggleButton>
|
||||
<ToggleButton disabled>
|
||||
<IconFont type="icon-redo" />
|
||||
<RedoOutlined />
|
||||
</ToggleButton>
|
||||
</Flex>
|
||||
<Flex gap="l" bg="#222" p="l">
|
||||
<ToggleButton shape="ghost">
|
||||
<IconFont type="icon-undo" />
|
||||
<UndoOutlined />
|
||||
</ToggleButton>
|
||||
<ToggleButton shape="ghost" selected>
|
||||
<IconFont type="icon-redo" />
|
||||
<RedoOutlined />
|
||||
</ToggleButton>
|
||||
<ToggleButton shape="ghost" disabled>
|
||||
<IconFont type="icon-undo" />
|
||||
<UndoOutlined />
|
||||
</ToggleButton>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
@@ -168,4 +168,6 @@ const setters: IFormItemCreateOptions[] = [
|
||||
{ name: 'borderSetter', component: BorderSetter },
|
||||
];
|
||||
|
||||
setters.forEach(register);
|
||||
export function registerBuiltinSetters() {
|
||||
setters.forEach(register);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import { SettingForm, FormModel, SettingFormProps } from '@music163/tango-settin
|
||||
import { Panel } from '@music163/tango-ui';
|
||||
import { clone } from '@music163/tango-helpers';
|
||||
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
|
||||
import './setters';
|
||||
import { registerBuiltinSetters } from './setters';
|
||||
|
||||
registerBuiltinSetters();
|
||||
|
||||
export interface SettingPanelProps extends SettingFormProps {
|
||||
title?: React.ReactNode;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { observer, useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
||||
import { Box, css } from 'coral-system';
|
||||
import { Button, FormProps, Empty, Space, Dropdown } from 'antd';
|
||||
import { Button, FormProps, Empty, Space, Dropdown, Form, Select, Input } from 'antd';
|
||||
import { PlayCircleOutlined } from '@ant-design/icons';
|
||||
import { Form, InputCode, Panel, JsonView, Search } from '@music163/tango-ui';
|
||||
import { InputCode, Panel, JsonView, Search } from '@music163/tango-ui';
|
||||
import {
|
||||
isNil,
|
||||
getVariableContent,
|
||||
@@ -37,15 +37,6 @@ enum ServiceFunctionMethodType {
|
||||
// DELETE = 'DELETE',
|
||||
}
|
||||
|
||||
/**
|
||||
* requestType
|
||||
* headers: Content-Type
|
||||
*/
|
||||
enum DataServiceRequestType {
|
||||
'application/json' = 'json',
|
||||
'application/x-www-form-urlencoded' = 'x-www-form-urlencoded',
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 api 路径转换为默认的驼峰方法名
|
||||
*/
|
||||
@@ -79,12 +70,6 @@ const httpMethods = Object.keys(ServiceFunctionMethodType).map((key) => ({
|
||||
value: key,
|
||||
}));
|
||||
|
||||
// requestType 类型
|
||||
const requestTypes = Object.keys(DataServiceRequestType).map((key) => ({
|
||||
label: key,
|
||||
value: DataServiceRequestType[key],
|
||||
}));
|
||||
|
||||
const detailFormStyle = css`
|
||||
.ant-form-item {
|
||||
margin-bottom: 12px;
|
||||
@@ -261,8 +246,8 @@ function ServiceDetailForm({
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
labelCol={6}
|
||||
wrapperCol={17}
|
||||
labelCol={{ span: 6 }}
|
||||
wrapperCol={{ span: 17 }}
|
||||
colon={false}
|
||||
layout="horizontal"
|
||||
initialValues={initialValues}
|
||||
@@ -285,12 +270,9 @@ function ServiceDetailForm({
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
component="select"
|
||||
componentProps={{
|
||||
options: serviceModules,
|
||||
disabled: disabled || isModifyMode,
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Select options={serviceModules} disabled={disabled || isModifyMode} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="方法名"
|
||||
name="name"
|
||||
@@ -308,11 +290,6 @@ function ServiceDetailForm({
|
||||
},
|
||||
},
|
||||
]}
|
||||
component="input"
|
||||
componentProps={{
|
||||
placeholder: '请输入数据服务调用名称',
|
||||
disabled: disabled || isModifyMode,
|
||||
}}
|
||||
extra={
|
||||
!disabled &&
|
||||
!isModifyMode && (
|
||||
@@ -331,38 +308,15 @@ function ServiceDetailForm({
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Form.Item
|
||||
label="路径"
|
||||
name="url"
|
||||
rules={[{ required: true, type: 'url' }]}
|
||||
component="input"
|
||||
componentProps={{
|
||||
placeholder: '请输入数据服务调用名称',
|
||||
disabled: disabled || isModifyMode,
|
||||
}}
|
||||
/>
|
||||
<Form.Item
|
||||
label="方法"
|
||||
name="method"
|
||||
component="select"
|
||||
componentProps={{
|
||||
options: httpMethods,
|
||||
placeholder: '请选择 HTTP 请求方法',
|
||||
disabled,
|
||||
}}
|
||||
/>
|
||||
<Form.Item
|
||||
label="编码类型"
|
||||
name="requestType"
|
||||
tooltip="设置请求头的 Content-Type,若不设置,则默认变为为 JSON"
|
||||
component="select"
|
||||
componentProps={{
|
||||
options: requestTypes,
|
||||
placeholder: '请求头Content-Type,默认为JSON',
|
||||
disabled,
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Input placeholder="请输入数据服务调用名称" disabled={disabled || isModifyMode} />
|
||||
</Form.Item>
|
||||
<Form.Item label="路径" name="url" rules={[{ required: true, type: 'url' }]}>
|
||||
<Input placeholder="请输入数据服务调用名称" disabled={disabled || isModifyMode} />
|
||||
</Form.Item>
|
||||
<Form.Item label="方法" name="method">
|
||||
<Select options={httpMethods} placeholder="请选择 HTTP 请求方法" disabled={disabled} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="格式化响应"
|
||||
name="formatter"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Button, Space } from 'antd';
|
||||
import { useBoolean } from '@music163/tango-helpers';
|
||||
import { Panel, Form, IconButton } from '@music163/tango-ui';
|
||||
import { Button, Space, Form, Input } from 'antd';
|
||||
import { FileAddOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { useBoolean } from '@music163/tango-helpers';
|
||||
import { Panel, IconButton } from '@music163/tango-ui';
|
||||
import { observer, useWorkspace, useWorkspaceData } from '@music163/tango-context';
|
||||
import { EditableVariableTree, EditableVariableTreeProps } from '../components';
|
||||
|
||||
@@ -90,9 +90,10 @@ interface AddStoreFormProps {
|
||||
|
||||
function AddStoreForm({ existNames = [], onCancel, onSubmit }: AddStoreFormProps) {
|
||||
return (
|
||||
<Panel title="添加模型" subTitle="模型可以用来组织一组变量">
|
||||
<Panel title="添加模型" subTitle="模型可以用来组织一组变量" bodyProps={{ p: 'l' }}>
|
||||
<Form
|
||||
autoComplete="off"
|
||||
colon={false}
|
||||
onFinish={(values) => {
|
||||
onSubmit && onSubmit(values);
|
||||
}}
|
||||
@@ -112,11 +113,9 @@ function AddStoreForm({ existNames = [], onCancel, onSubmit }: AddStoreFormProps
|
||||
},
|
||||
},
|
||||
]}
|
||||
component="input"
|
||||
componentProps={{
|
||||
placeholder: '请输入模型名称',
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Input placeholder="请输入模型名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label=" ">
|
||||
<Space>
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box } from 'coral-system';
|
||||
import { ToggleButton, IconFont } from '@music163/tango-ui';
|
||||
import { ToggleButton, RedoOutlined, UndoOutlined } from '@music163/tango-ui';
|
||||
import { observer, useWorkspace } from '@music163/tango-context';
|
||||
|
||||
export const HistoryTool = observer(() => {
|
||||
@@ -16,7 +16,7 @@ export const HistoryTool = observer(() => {
|
||||
workspace.selectSource.clear();
|
||||
}}
|
||||
>
|
||||
<IconFont type="icon-undo" />
|
||||
<UndoOutlined />
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
shape="ghost"
|
||||
@@ -27,7 +27,7 @@ export const HistoryTool = observer(() => {
|
||||
workspace.selectSource.clear();
|
||||
}}
|
||||
>
|
||||
<IconFont type="icon-redo" />
|
||||
<RedoOutlined />
|
||||
</ToggleButton>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Button, Popconfirm, Modal, ModalProps } from 'antd';
|
||||
import { Button, Popconfirm, Modal, ModalProps, Form, Input } from 'antd';
|
||||
import { Box, css, Group, Text } from 'coral-system';
|
||||
import { FileOutlined, DeleteOutlined, CopyOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import { array2object, callAll } from '@music163/tango-helpers';
|
||||
import { IPageConfigData } from '@music163/tango-core';
|
||||
import { Form, ToggleButton } from '@music163/tango-ui';
|
||||
import { ToggleButton } from '@music163/tango-ui';
|
||||
import { observer, useWorkspace } from '@music163/tango-context';
|
||||
|
||||
export const RouteSwitchTool = observer(() => {
|
||||
@@ -250,17 +250,13 @@ function CopyViewModalForm({
|
||||
}}
|
||||
>
|
||||
<Form
|
||||
wrapperCol={20}
|
||||
wrapperCol={{ span: 20 }}
|
||||
form={form}
|
||||
initialValues={{ name: current?.name, path: current?.path }}
|
||||
>
|
||||
<Form.Item
|
||||
label="页面标题"
|
||||
name="name"
|
||||
required
|
||||
component="input"
|
||||
componentProps={{ placeholder: '请输入标题' }}
|
||||
/>
|
||||
<Form.Item label="页面标题" name="name" required>
|
||||
<Input placeholder="请输入标题" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="页面路由"
|
||||
name="path"
|
||||
@@ -281,11 +277,9 @@ function CopyViewModalForm({
|
||||
message: '非法的路由地址',
|
||||
},
|
||||
]}
|
||||
component="input"
|
||||
componentProps={{
|
||||
placeholder: '输入页面路由',
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Input placeholder="请输入页面路由" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import React from 'react';
|
||||
import { observer, useDesigner } from '@music163/tango-context';
|
||||
import { Box } from 'coral-system';
|
||||
import { IconFont, ToggleButton } from '@music163/tango-ui';
|
||||
import {
|
||||
OpenPanelFilledLeftOutlined,
|
||||
OpenPanelFilledRightOutlined,
|
||||
OpenPanelLeftOutlined,
|
||||
OpenPanelRightOutlined,
|
||||
ToggleButton,
|
||||
} from '@music163/tango-ui';
|
||||
|
||||
export const TogglePanelTool = observer(() => {
|
||||
const designer = useDesigner();
|
||||
@@ -14,20 +20,14 @@ export const TogglePanelTool = observer(() => {
|
||||
designer.setActiveSidebarPanel(designer.activeSidebarPanel ? '' : 'outline');
|
||||
}}
|
||||
>
|
||||
<IconFont
|
||||
type={
|
||||
designer.activeSidebarPanel ? 'icon-open-panel-filled-left' : 'icon-open-panel-left'
|
||||
}
|
||||
/>
|
||||
{designer.activeSidebarPanel ? <OpenPanelFilledLeftOutlined /> : <OpenPanelLeftOutlined />}
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
tooltip="隐藏/显示右侧面板"
|
||||
shape="ghost"
|
||||
onClick={() => designer.toggleRightPanel()}
|
||||
>
|
||||
<IconFont
|
||||
type={designer.showRightPanel ? 'icon-open-panel-filled-right' : 'icon-open-panel-right'}
|
||||
/>
|
||||
{designer.showRightPanel ? <OpenPanelFilledRightOutlined /> : <OpenPanelRightOutlined />}
|
||||
</ToggleButton>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -8,14 +8,15 @@ import {
|
||||
useBoolean,
|
||||
} from '@music163/tango-helpers';
|
||||
import { isWrappedByExpressionContainer } from '@music163/tango-core';
|
||||
import { IconFont, ToggleButton } from '@music163/tango-ui';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { InputProps, Tooltip } from 'antd';
|
||||
import { ToggleButton, CodeOutlined } from '@music163/tango-ui';
|
||||
import { InputProps } from 'antd';
|
||||
import { useFormModel, useFormVariable } from './context';
|
||||
import { FormControl } from './form-ui';
|
||||
import { Box, Text } from 'coral-system';
|
||||
|
||||
export interface FormItemProps extends ComponentPropType {
|
||||
extra?: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface FormItemComponentProps<T = any> {
|
||||
@@ -76,6 +77,7 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
||||
getVisible: getVisibleProp,
|
||||
getSetterProps: getSetterPropsProp,
|
||||
extra,
|
||||
footer,
|
||||
}: FormItemProps) {
|
||||
const { disableSwitchExpressionSetter } = useFormVariable();
|
||||
const model = useFormModel();
|
||||
@@ -106,7 +108,7 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
||||
|
||||
let expProps = {};
|
||||
|
||||
// FIXME: 重新考虑这段代码的位置
|
||||
// FIXME: 重新考虑这段代码的位置,外置这个逻辑
|
||||
if (['expressionSetter', 'actionSetter', 'eventSetter'].includes(setter) || isVariable) {
|
||||
expProps = {
|
||||
modalTitle: title,
|
||||
@@ -141,7 +143,8 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
||||
docs={docs}
|
||||
error={field.error}
|
||||
extra={
|
||||
<>
|
||||
<Box>
|
||||
{extra}
|
||||
{!disableVariableSetter ? (
|
||||
<ToggleButton
|
||||
borderRadius="s"
|
||||
@@ -153,12 +156,12 @@ export function createFormItem(options: IFormItemCreateOptions) {
|
||||
selected={isVariable}
|
||||
onClick={() => toggleIsVariable()}
|
||||
>
|
||||
<IconFont type="icon-brackets-curly" />
|
||||
<CodeOutlined />
|
||||
</ToggleButton>
|
||||
) : null}
|
||||
{extra}
|
||||
</>
|
||||
</Box>
|
||||
}
|
||||
footer={footer}
|
||||
data-setter={setterName}
|
||||
data-field={name}
|
||||
>
|
||||
@@ -187,10 +190,6 @@ export function register(config: IFormItemCreateOptions) {
|
||||
});
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
color: 'red',
|
||||
};
|
||||
|
||||
export function SettingFormItem(props: FormItemProps) {
|
||||
const { setter } = props;
|
||||
const Comp = REGISTERED_FORM_ITEM_MAP[setter];
|
||||
@@ -199,10 +198,10 @@ export function SettingFormItem(props: FormItemProps) {
|
||||
return (
|
||||
<Fallback
|
||||
{...props}
|
||||
extra={
|
||||
<Tooltip title={`${props.name}: invalid setter ${props.setter}`}>
|
||||
<QuestionCircleOutlined color="red" style={iconStyle} />
|
||||
</Tooltip>
|
||||
footer={
|
||||
<Text color="red" mt="m">
|
||||
{props.setter} is invalid
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { isNil, isString } from '@music163/tango-helpers';
|
||||
export interface FormControlProps extends Omit<FormLabelProps, 'type'> {
|
||||
visible: boolean;
|
||||
extra?: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
error?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
@@ -18,6 +19,7 @@ export function FormControl({
|
||||
tip,
|
||||
docs,
|
||||
extra,
|
||||
footer,
|
||||
error,
|
||||
children,
|
||||
...rest
|
||||
@@ -29,6 +31,7 @@ export function FormControl({
|
||||
{extra}
|
||||
</Box>
|
||||
<Box>{children}</Box>
|
||||
{footer}
|
||||
{!!error && (
|
||||
<Box mt="m" color="red">
|
||||
{error}
|
||||
|
||||
@@ -83,7 +83,15 @@ export interface SettingFormProps {
|
||||
*/
|
||||
prototype?: ComponentPrototypeType;
|
||||
/**
|
||||
* 选项分组信息
|
||||
* 是否显示搜索框
|
||||
*/
|
||||
showSearch?: boolean;
|
||||
/**
|
||||
* 是否显示分组导航
|
||||
*/
|
||||
showGroups?: boolean;
|
||||
/**
|
||||
* 选项分组信息,用于对表单项进行分组展示,如未提供,则使用内置的分组信息
|
||||
*/
|
||||
groupOptions?: IFormTabsGroupOption[];
|
||||
/**
|
||||
@@ -98,6 +106,8 @@ export function SettingForm({
|
||||
model: modelProp,
|
||||
defaultValue,
|
||||
onChange = noop,
|
||||
showSearch = true,
|
||||
showGroups = true,
|
||||
groupOptions = internalGroups,
|
||||
}: SettingFormProps) {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
@@ -107,10 +117,8 @@ export function SettingForm({
|
||||
const componentPropGroups = useMemo(() => {
|
||||
return normalizeComponentProps(componentProps);
|
||||
}, [componentProps]);
|
||||
|
||||
const filterProps = useMemo(() => {
|
||||
if (!keyword) {
|
||||
return componentPropGroups[tabKey] || [];
|
||||
}
|
||||
const computedProps = mapTreeData(componentProps, (node) => {
|
||||
const { getProp, ...rest } = node;
|
||||
return {
|
||||
@@ -118,8 +126,19 @@ export function SettingForm({
|
||||
...getProp?.(model),
|
||||
};
|
||||
});
|
||||
|
||||
// 没有进行搜索的情况
|
||||
if (!keyword) {
|
||||
if (showGroups) {
|
||||
return componentPropGroups[tabKey] || [];
|
||||
} else {
|
||||
return computedProps;
|
||||
}
|
||||
}
|
||||
|
||||
// 用户进行搜索的情况
|
||||
return filterComponentProps(computedProps, keyword);
|
||||
}, [keyword, componentProps, tabKey, model]);
|
||||
}, [keyword, componentProps, tabKey, model, showGroups, componentPropGroups]);
|
||||
|
||||
const renderProps = useCallback(
|
||||
(props: ComponentPropType[]) =>
|
||||
@@ -155,15 +174,17 @@ export function SettingForm({
|
||||
<FormModelProvider value={model}>
|
||||
<Box className="SettingForm" mb="xl" css={formStyle}>
|
||||
<Box className="SettingFormHeader" position="sticky" top="0" bg="white" zIndex="2">
|
||||
<Box className="SettingFormSearch" px="l" py="m">
|
||||
<Search
|
||||
placeholder="搜索属性"
|
||||
onChange={(val) => {
|
||||
setKeyword(val?.trim());
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{!keyword && (
|
||||
{showSearch && (
|
||||
<Box className="SettingFormSearch" px="l" py="m">
|
||||
<Search
|
||||
placeholder="搜索属性"
|
||||
onChange={(val) => {
|
||||
setKeyword(val?.trim());
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{!keyword && showGroups && (
|
||||
<Box className="SettingFormNav">
|
||||
<Tabs activeKey={tabKey} onChange={setTabKey}>
|
||||
{groupOptions.map((group) => (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { register } from './form-item';
|
||||
import { ExpressionSetter } from './setter';
|
||||
import { ExpressionSetter, TextSetter } from './setter';
|
||||
|
||||
// 预注册基础 Setter
|
||||
register({
|
||||
@@ -7,6 +7,11 @@ register({
|
||||
component: ExpressionSetter,
|
||||
});
|
||||
|
||||
register({
|
||||
name: 'textSetter',
|
||||
component: TextSetter,
|
||||
});
|
||||
|
||||
export * from './form';
|
||||
export * from './form-item';
|
||||
export * from './form-object';
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Input } from 'antd';
|
||||
import { InputCode } from '@music163/tango-ui';
|
||||
import { FormItemComponentProps } from './form-item';
|
||||
|
||||
// TODO: 内置基本的 ExpressionSetter,默认全部 fallback 到此 setter
|
||||
export function ExpressionSetter(props: FormItemComponentProps<string>) {
|
||||
return <div>exp setter</div>;
|
||||
export function ExpressionSetter({ value, onChange, ...rest }: FormItemComponentProps<string>) {
|
||||
return <InputCode onChange={(val) => onChange?.(val)} value={value || ''} {...rest} />;
|
||||
}
|
||||
|
||||
export function TextSetter({ onChange, ...rest }: FormItemComponentProps<string>) {
|
||||
return <Input onChange={(e) => onChange?.(e.target.value)} {...rest} />;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
"@music163/tango-helpers": "^0.1.8",
|
||||
"@uiw/react-codemirror": "^4.21.21",
|
||||
"antd": "^4.24.2",
|
||||
"antd-easy-form": "^1.1.3",
|
||||
"coral-system": "^1.0.5",
|
||||
"eslint-linter-browserify": "^8.51.0",
|
||||
"react-json-view": "^1.21.3",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const CodeOutlinedSvg = () => (
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="m31 16-7 7-1.41-1.41 5.58-5.59-5.58-5.59 1.41-1.41z" />
|
||||
<path d="m1 16 7-7 1.41 1.41-5.58 5.59 5.58 5.59-1.41 1.41z" />
|
||||
<path
|
||||
d="m5.91 15h20.17v2h-20.17z"
|
||||
transform="matrix(.25881905 -.96592583 .96592583 .25881905 -3.6 27.31)"
|
||||
/>
|
||||
<path d="m0 0h32v32h-32z" fill="none" transform="matrix(0 -1 1 0 0 32)" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const CodeOutlined = createIcon(CodeOutlinedSvg, 'CodeOutlined');
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import Icon from '@ant-design/icons';
|
||||
import type { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';
|
||||
|
||||
export function createIcon(component: React.ComponentType<any>, displayName?: string) {
|
||||
const CustomIcon = (props: Partial<CustomIconComponentProps>) => (
|
||||
<Icon component={component} {...props} />
|
||||
);
|
||||
if (displayName) {
|
||||
CustomIcon.displayName = displayName;
|
||||
}
|
||||
return CustomIcon;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export * from './create-icon';
|
||||
export * from './code-outlined';
|
||||
export * from './open-panel-filled-left-outlined';
|
||||
export * from './open-panel-filled-right-outlined';
|
||||
export * from './open-panel-left-outlined';
|
||||
export * from './open-panel-right-outlined';
|
||||
export * from './undo-outlined';
|
||||
export * from './redo-outlined';
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const OpenPanelFilledLeftOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m28 4h-24a2 2 0 0 0 -2 2v20a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2v-20a2 2 0 0 0 -2-2zm0 22h-16v-20h16z" />
|
||||
<path d="m0 0h32v32h-32z" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const OpenPanelFilledLeftOutlined = createIcon(
|
||||
OpenPanelFilledLeftOutlinedSvg,
|
||||
'OpenPanelFilledLeftOutlined',
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const OpenPanelFilledRightOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m28 4h-24a2 2 0 0 0 -2 2v20a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2v-20a2 2 0 0 0 -2-2zm-24 2h16v20h-16z" />
|
||||
<path d="m0 0h32v32h-32z" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const OpenPanelFilledRightOutlined = createIcon(
|
||||
OpenPanelFilledRightOutlinedSvg,
|
||||
'OpenPanelFilledRightOutlined',
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const OpenPanelLeftOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m28 4h-24a2 2 0 0 0 -2 2v20a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2v-20a2 2 0 0 0 -2-2zm-24 2h6v20h-6zm24 20h-16v-20h16z" />
|
||||
<path d="m0 0h32v32h-32z" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const OpenPanelLeftOutlined = createIcon(OpenPanelLeftOutlinedSvg, 'OpenPanelLeftOutlined');
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const OpenPanelRightOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m28 4h-24a2 2 0 0 0 -2 2v20a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2v-20a2 2 0 0 0 -2-2zm-24 2h16v20h-16zm24 20h-6v-20h6z" />
|
||||
<path d="m0 0h32v32h-32z" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const OpenPanelRightOutlined = createIcon(
|
||||
OpenPanelRightOutlinedSvg,
|
||||
'OpenPanelRightOutlined',
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const RedoOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m12 10h12.1851l-3.5874-3.5859 1.4023-1.4141 6 6-6 6-1.4023-1.4146 3.5844-3.5854h-12.1821a6 6 0 0 0 0 12h8v2h-8a8 8 0 0 1 0-16z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const RedoOutlined = createIcon(RedoOutlinedSvg, 'RedoOutlined');
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { createIcon } from './create-icon';
|
||||
|
||||
const UndoOutlinedSvg = () => (
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m20 10h-12.1851l3.5874-3.5859-1.4023-1.4141-6 6 6 6 1.4023-1.4146-3.5844-3.5854h12.1821a6 6 0 0 1 0 12h-8v2h8a8 8 0 0 0 0-16z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const UndoOutlined = createIcon(UndoOutlinedSvg, 'UndoOutlined');
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './icons';
|
||||
export * from './action-select';
|
||||
export * from './code-editor';
|
||||
export * from './chat-input';
|
||||
@@ -19,4 +20,3 @@ export * from './tabs';
|
||||
export * from './select-action';
|
||||
export * from './copy-clipboard';
|
||||
export * from './tag-select';
|
||||
export { Form } from 'antd-easy-form';
|
||||
@@ -5885,14 +5885,6 @@ ansi-to-html@^0.6.11:
|
||||
dependencies:
|
||||
entities "^2.0.0"
|
||||
|
||||
antd-easy-form@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmmirror.com/antd-easy-form/-/antd-easy-form-1.1.3.tgz#84fa26f05310d8d34e80b617801e88097da57a6d"
|
||||
integrity sha512-jkTzV9qP8ECbSqwrU0/VxuFPsmBnHWCCLlFu3HA26/jQNXtBTKEuS5c26LGfCXwpuf/Z1YBtA6qWlVESASIUBQ==
|
||||
dependencies:
|
||||
"@ant-design/icons" "^4.8.0"
|
||||
react-is "^18.2.0"
|
||||
|
||||
antd@^4.24.2:
|
||||
version "4.24.14"
|
||||
resolved "https://registry.npmmirror.com/antd/-/antd-4.24.14.tgz#af19c2c1ead71954a00b1e9c2f1ecbc84bd7a20d"
|
||||
|
||||
Reference in New Issue
Block a user