feat: refactor doc

This commit is contained in:
Sheldon Guo 2025-03-27 17:15:34 +08:00
parent abcb81e63a
commit 483f22f56d
15 changed files with 608 additions and 101 deletions

View File

@ -22,6 +22,7 @@ import {
Select,
Space,
Tag,
Tooltip,
Typography,
} from 'antd';
import useAntdInputStyle from 'antd/es/input/style';
@ -35,7 +36,7 @@ import { Json } from '../input';
import { HelperAddition, HelperList } from './Helpers';
import { useHelperObservables } from './Helpers/hooks/useHelperObservables';
import { useStyles } from './style';
import { VariableHelperMapping, VariableProvider } from './VariableProvider';
import { useVariable, VariableHelperMapping, VariableProvider } from './VariableProvider';
import { XButton } from './XButton';
const { Text } = Typography;
@ -45,6 +46,22 @@ type ParseOptions = {
stringToDate?: boolean;
};
const findScopeOption = (options: DefaultOptionType[], path: string[]): DefaultOptionType | null => {
if (!options || !Array.isArray(options)) {
return null;
}
for (const option of options) {
if (option.value === path[0]) {
if (path.length === 1) {
return option;
}
return findScopeOption(option.children, path.slice(1));
}
}
return null;
};
function parseValue(value: any, options: ParseOptions = {}): string | string[] {
if (value == null || (Array.isArray(value) && value.length === 0)) {
return 'null';
@ -220,6 +237,7 @@ function _Input(props: VariableInputProps) {
} = props;
const scope = typeof props.scope === 'function' ? props.scope() : props.scope;
const { wrapSSR, hashId, componentCls, rootPrefixCls } = useStyles({ hideVariableButton });
const helperObservables = useHelperObservables();
@ -230,8 +248,6 @@ function _Input(props: VariableInputProps) {
const { t } = useTranslation();
const form = useForm();
const [options, setOptions] = React.useState<DefaultOptionType[]>([]);
const [variableType, setVariableType] = React.useState<string>();
const [showLastHelper, setShowLastHelper] = React.useState<boolean>(false);
const [variableText, setVariableText] = React.useState([]);
const [isFieldValue, setIsFieldValue] = React.useState(
hideVariableButton || (children && value != null ? true : false),
@ -246,17 +262,7 @@ function _Input(props: VariableInputProps) {
[value],
);
// useEffect(() => {
// const dispose = reaction(
// () => {
// return composeTemplate({ fullVariable, helpers: helperObservables.helpersObs.value });
// },
// (newVal) => {
// onChange(newVal);
// },
// );
// return dispose;
// }, [fullVariable, onChange]);
const selectedScopeOption = useMemo(() => findScopeOption(scope, variableSegments), [scope, variableSegments]);
const parsed = useMemo(() => parseValue(variableSegments, parseOptions), [parseOptions, variableSegments]);
const isConstant = typeof parsed === 'string';
@ -389,10 +395,6 @@ function _Input(props: VariableInputProps) {
const variableName = next.join('.');
const option = optionPath[optionPath.length - 1];
onChange(composeTemplate({ fullVariable: variableName, helpers: option?.helpers ?? [] }), optionPath);
if (Array.isArray(optionPath) && optionPath.length > 0) {
setVariableType(option.type ?? null);
setShowLastHelper(option.showLastHelper ?? false);
}
},
[type, variable, onChange],
);
@ -488,21 +490,16 @@ function _Input(props: VariableInputProps) {
className={cx('ant-input ant-input-outlined', { 'ant-input-disabled': disabled }, hashId)}
>
<Tag color="blue">
{variableText.map((item, index) => {
return (
<React.Fragment key={item}>
{index ? ' / ' : ''}
{item}
</React.Fragment>
);
})}
<VariableProvider
variableName={fullVariable}
variableType={variableType}
openLastHelper={showLastHelper}
variableExampleValue={selectedScopeOption?.example}
variableType={selectedScopeOption?.type}
openLastHelper={selectedScopeOption?.showLastHelper}
helperObservables={helperObservables}
onVariableTemplateChange={onChange}
>
<VariableTag variablePath={variableText} />
<HelperList />
{variableText.length > 0 && <HelperAddition />}
</VariableProvider>
@ -561,4 +558,22 @@ function _Input(props: VariableInputProps) {
);
}
const VariableTag = ({ variablePath }: { variablePath: string[] }) => {
const { value } = useVariable();
return (
<Tooltip title={value}>
<span>
{variablePath.map((item, index) => {
return (
<React.Fragment key={item}>
{index ? ' / ' : ''}
{item}
</React.Fragment>
);
})}
</span>
</Tooltip>
);
};
export const Input = observer(_Input, { displayName: 'VariableInput' });

View File

@ -27,6 +27,7 @@ interface VariableContextValue {
interface VariableProviderProps {
variableName: string;
variableExampleValue?: any;
variableType: string | null;
openLastHelper?: boolean;
children: React.ReactNode;
@ -122,6 +123,7 @@ const _VariableProvider: React.FC<VariableProviderProps> = ({
variableName,
children,
variableType,
variableExampleValue,
openLastHelper,
helperObservables,
onVariableTemplateChange,
@ -145,6 +147,10 @@ const _VariableProvider: React.FC<VariableProviderProps> = ({
useEffect(() => {
async function fetchValue() {
try {
if (variableExampleValue !== undefined) {
setValue(variableExampleValue);
return;
}
const val = await getValue(variableName);
if (val) {
setValue(val);
@ -157,7 +163,7 @@ const _VariableProvider: React.FC<VariableProviderProps> = ({
}
}
fetchValue();
}, [localVariables, variableName, variables, getValue]);
}, [localVariables, variableName, variables, getValue, variableExampleValue]);
const valueType =
helperObservables.helpersObs.value.length > 0

View File

@ -0,0 +1,194 @@
import { createForm } from '@formily/core';
import { observer, useField, useForm } from '@formily/react';
import { AntdSchemaComponentProvider, FormItem, Plugin, SchemaComponent, Variable } from '@nocobase/client';
import { mockApp } from '@nocobase/client/demo-utils';
import { createJSONTemplateParser } from '@nocobase/json-template-parser';
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
import { dayjs } from '@nocobase/utils/client';
import { Form } from 'antd';
import React, { useEffect, useState } from 'react';
const parser = createJSONTemplateParser();
// 定义变量作用域
const scope = [
{
label: 'Date',
value: '$nDate',
type: 'date',
children: [
{
label: 'Now',
value: 'now',
type: 'date',
tooltip: '当前时间2025-03-26 10:00:00',
},
{
label: 'Today',
value: 'today',
type: 'date',
tooltip: '今天2025-03-26',
},
{
label: 'This Quarter',
value: 'thisQuarter',
type: 'date',
tooltip: '本季度2025Q1',
},
{
label: 'This Week',
value: 'thisWeek',
type: 'date',
tooltip: '本周2025w12',
},
{
label: 'Today (DateOnly)',
value: 'today_dateOnly',
type: 'date',
tooltip: '今天无时区2025-03-26',
},
{
label: 'Today (Without TZ)',
value: 'today_withoutTz',
type: 'date',
tooltip: '今天无时区时间2025-03-26 00:00:00',
},
{
label: 'Today (UTC)',
value: 'today_utc',
type: 'date',
tooltip: '今天UTC时间2025-03-26T00:00:00.000Z',
},
],
},
];
// 定义表单属性
const useFormBlockProps = () => {
return {
form: createForm({
initialValues: {
input1: '{{ $nDate.now }}',
input2: '{{ $nDate.today | date_format: "YYYY-MM-DD" }}',
input3: '{{ $nDate.today | date_add: 7, "day" | date_format: "YYYY-MM-DD" }}',
input4: '{{ $nDate.today_dateOnly }}',
input5: '{{ $nDate.today_withoutTz }}',
input6: '{{ $nDate.today_utc }}',
},
}),
};
};
// 定义表单 schema
const schema = {
type: 'object',
'x-component': 'FormV2',
'x-use-component-props': 'useFormBlockProps',
properties: {
input1: {
type: 'string',
title: '基础变量选择',
'x-decorator': 'FormItem',
'x-component': 'Variable.Input',
'x-component-props': {
scope,
value: '{{ $nDate.now }}',
},
},
input2: {
type: 'string',
title: '带格式化的变量',
'x-decorator': 'FormItem',
'x-component': 'Variable.Input',
'x-component-props': {
scope,
value: '{{ $nDate.today | date_format: "YYYY-MM-DD" }}',
},
},
input3: {
type: 'string',
title: '带日期偏移的变量',
'x-decorator': 'FormItem',
'x-component': 'Variable.Input',
'x-component-props': {
scope,
value: '{{ $nDate.today | date_add: 7, "day" | date_format: "YYYY-MM-DD" }}',
},
},
input4: {
type: 'string',
title: 'DateOnly 格式',
'x-decorator': 'FormItem',
'x-component': 'Variable.Input',
'x-component-props': {
scope,
value: '{{ $nDate.today_dateOnly }}',
},
},
input5: {
type: 'string',
title: '无时区时间格式',
'x-decorator': 'FormItem',
'x-component': 'Variable.Input',
'x-component-props': {
scope,
value: '{{ $nDate.today_withoutTz }}',
},
},
input6: {
type: 'string',
title: 'UTC 时间格式',
'x-decorator': 'FormItem',
'x-component': 'Variable.Input',
'x-component-props': {
scope,
value: '{{ $nDate.today_utc }}',
},
},
},
};
// 独立组件示例
const Demo = () => {
const [value1, setValue1] = useState('{{$nDate.now}}');
const [value2, setValue2] = useState('{{$nDate.today | date_format: "YYYY-MM-DD"}}');
const [value3, setValue3] = useState('{{$nDate.today | date_add: 7, "day" | date_format: "YYYY-MM-DD"}}');
const [value4, setValue4] = useState('{{$nDate.today_dateOnly}}');
const [value5, setValue5] = useState('{{$nDate.today_withoutTz}}');
const [value6, setValue6] = useState('{{$nDate.today_utc}}');
return (
<div>
<Form.Item label="基础变量选择">
<Variable.Input scope={scope} value={value1} onChange={setValue1} />
</Form.Item>
<Form.Item label="带格式化的变量">
<Variable.Input scope={scope} value={value2} onChange={setValue2} />
</Form.Item>
<Form.Item label="带日期偏移的变量">
<Variable.Input scope={scope} value={value3} onChange={setValue3} />
</Form.Item>
<Form.Item label="DateOnly 格式">
<Variable.Input scope={scope} value={value4} onChange={setValue4} />
</Form.Item>
<Form.Item label="无时区时间格式">
<Variable.Input scope={scope} value={value5} onChange={setValue5} />
</Form.Item>
<Form.Item label="UTC 时间格式">
<Variable.Input scope={scope} value={value6} onChange={setValue6} />
</Form.Item>
</div>
);
};
// 插件定义
class DemoPlugin extends Plugin {
async load() {
this.app.router.add('root', { path: '/', Component: Demo });
}
}
// 创建应用实例
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
export default app.getRootComponent();

View File

@ -1,6 +1,6 @@
import { createForm } from '@formily/core';
import { observer, useField, useForm } from '@formily/react';
import { AntdSchemaComponentProvider, Plugin, SchemaComponent } from '@nocobase/client';
import { AntdSchemaComponentProvider, Plugin, SchemaComponent, Variable } from '@nocobase/client';
import { mockApp } from '@nocobase/client/demo-utils';
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
import React from 'react';

View File

@ -0,0 +1,55 @@
import { ISchema, Plugin, SchemaComponent, SchemaSettings } from '@nocobase/client';
import { mockApp } from '@nocobase/client/demo-utils';
import React from 'react';
const simpleSettings = new SchemaSettings({
name: 'simpleSettings',
items: [
{
name: 'delete',
type: 'remove',
},
],
});
const schema: ISchema = {
type: 'void',
name: 'root',
'x-decorator': 'DndContext',
'x-component': 'FormV2',
properties: {
username: {
type: 'string',
title: 'Username',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-settings': 'simpleSettings',
required: true,
},
nickname: {
type: 'string',
title: 'Nickname',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-settings': 'simpleSettings',
},
},
};
const Demo = () => {
return <SchemaComponent schema={schema} />;
};
class DemoPlugin extends Plugin {
async load() {
this.app.router.add('root', { path: '/', Component: Demo });
}
}
const app = mockApp({
designable: true,
plugins: [DemoPlugin],
schemaSettings: [simpleSettings],
});
export default app.getRootComponent();

View File

@ -0,0 +1,65 @@
import { createForm } from '@formily/core';
import { observer, useField, useForm } from '@formily/react';
import {
AntdSchemaComponentProvider,
FormItem,
Plugin,
SchemaComponent,
useApp,
useRequest,
Variable,
VariableEvaluateProvider,
} from '@nocobase/client';
import { mockApp } from '@nocobase/client/demo-utils';
import { createJSONTemplateParser } from '@nocobase/json-template-parser';
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
import { dayjs } from '@nocobase/utils/client';
import { Form } from 'antd';
import React, { useEffect, useState } from 'react';
const scope = [{ label: 'now', value: 'now', type: 'date' }];
const Demo = () => {
const app = useApp();
const [value, setValue] = useState('{{now | date_format: "YYYY-MM-DD"}}');
const [value2, setValue2] = useState('{{now |date_offset: "add", 1, "week" | date_format: "YYYY-MM-DD"}}');
const tmplateData = { now: dayjs().format() };
const { data } = useRequest(
() => {
return app.jsonTemplateParser.render(value, tmplateData);
},
{
refreshDeps: [value],
},
);
const { data: data2 } = useRequest(
() => {
return app.jsonTemplateParser.render(value2);
},
{
refreshDeps: [value2],
},
);
return (
<div>
<VariableEvaluateProvider data={tmplateData} context={{}}>
<Form.Item label="format now" extra={`输出值${data}`}>
<Variable.Input scope={scope} value={value} onChange={setValue}></Variable.Input>
</Form.Item>
<Form.Item label="offset and format" extra={`输出值${data2}`}>
<Variable.Input scope={scope} value={value2} onChange={setValue2}></Variable.Input>
</Form.Item>
</VariableEvaluateProvider>
</div>
);
};
class DemoPlugin extends Plugin {
async load() {
this.app.router.add('root', { path: '/', Component: Demo });
}
}
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
export default app.getRootComponent();

View File

@ -0,0 +1,39 @@
import { createForm } from '@formily/core';
import { observer, useField, useForm } from '@formily/react';
import { AntdSchemaComponentProvider, FormItem, Plugin, SchemaComponent, Variable } from '@nocobase/client';
import { mockApp } from '@nocobase/client/demo-utils';
import { createJSONTemplateParser } from '@nocobase/json-template-parser';
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
import { dayjs } from '@nocobase/utils/client';
import { Form } from 'antd';
import React, { useEffect, useState } from 'react';
const scope = [
{ label: 'v1', value: 'v1' },
{ label: 'now', value: 'now', type: 'date' },
];
const Demo = () => {
const [value, setValue] = useState('{{now}}');
const [value2, setValue2] = useState('{{v1}}');
return (
<div>
<Form.Item label="date" extra="变量类型为 date,可以匹配 date 类型的 helpers">
<Variable.Input scope={scope} value={value} onChange={setValue}></Variable.Input>
</Form.Item>
<Form.Item label="v1" extra="变量无类型,不能匹配任何 helpers">
<Variable.Input scope={scope} value={value2} onChange={setValue2}></Variable.Input>
</Form.Item>
</div>
);
};
class DemoPlugin extends Plugin {
async load() {
this.app.router.add('root', { path: '/', Component: Demo });
}
}
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
export default app.getRootComponent();

View File

@ -0,0 +1,74 @@
import { createForm } from '@formily/core';
import { observer, useField, useForm } from '@formily/react';
import { AntdSchemaComponentProvider, FormItem, Plugin, SchemaComponent, Variable } from '@nocobase/client';
import { mockApp } from '@nocobase/client/demo-utils';
import { createJSONTemplateParser } from '@nocobase/json-template-parser';
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
import { dayjs } from '@nocobase/utils/client';
import { Form } from 'antd';
import React, { useEffect, useState } from 'react';
const parser = createJSONTemplateParser();
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 30, 60, 90];
const scope = [
{
label: 'Date',
value: '$nDate',
type: 'date',
children: [{ label: 'now', value: 'now', type: 'date', disabled: true }],
},
];
const useFormBlockProps = () => {
return {
form: createForm({
initialValues: {
input: '{{ $nDate.now }}',
},
}),
};
};
const schema = {
type: 'object',
'x-component': 'FormV2',
'x-use-component-props': 'useFormBlockProps',
properties: {
input: {
type: 'string',
title: `输入项`,
'x-decorator': 'FormItem',
'x-component': 'Variable.Input',
'x-component-props': {
scope,
value: '{{ $nDate.now }}',
test: '{{ $nDate.now }}',
},
},
},
};
const Demo = () => {
const [value, setValue] = useState('{{$nDate.now}}');
const [value2, setValue2] = useState('{{$nDate.now | date_format: "YYYY-MM-DD"}}');
return (
<div>
<Form.Item label="单变量">
<Variable.Input scope={scope} value={value} onChange={setValue}></Variable.Input>
</Form.Item>
<Form.Item label="单变量 + helper">
<Variable.Input scope={scope} value={value2} onChange={setValue2}></Variable.Input>
</Form.Item>
</div>
);
};
class DemoPlugin extends Plugin {
async load() {
this.app.router.add('root', { path: '/', Component: Demo });
}
}
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
export default app.getRootComponent();

View File

@ -0,0 +1,53 @@
import { createForm } from '@formily/core';
import { observer, useField, useForm } from '@formily/react';
import {
AntdSchemaComponentProvider,
FormItem,
Plugin,
SchemaComponent,
useApp,
useRequest,
Variable,
VariableEvaluateProvider,
} from '@nocobase/client';
import { mockApp } from '@nocobase/client/demo-utils';
import { createJSONTemplateParser } from '@nocobase/json-template-parser';
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
import { dayjs } from '@nocobase/utils/client';
import { Form } from 'antd';
import React, { useEffect, useState } from 'react';
const scope = [
{ label: 'v1', value: 'v1' },
{ label: 'v2', value: 'v2', example: 'example-value-v2' },
];
const Demo = () => {
const app = useApp();
const [value, setValue] = useState('{{v1}}');
const [value2, setValue2] = useState('{{v2}}');
const tmplateData = { v1: 'value-v1' };
return (
<div>
<VariableEvaluateProvider data={tmplateData} context={{}}>
<Form.Item label="变量 v1" extra={`鼠标在变量上悬浮即可显示变量值`}>
<Variable.Input scope={scope} value={value} onChange={setValue}></Variable.Input>
</Form.Item>
<Form.Item label="变量 v2" extra={`鼠标在变量上悬浮即可显示变量值`}>
<Variable.Input scope={scope} value={value2} onChange={setValue2}></Variable.Input>
</Form.Item>
</VariableEvaluateProvider>
</div>
);
};
class DemoPlugin extends Plugin {
async load() {
this.app.router.add('root', { path: '/', Component: Demo });
}
}
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
export default app.getRootComponent();

View File

@ -4,6 +4,42 @@
## `Variable.Input`
### scope 定义
scope 可以定义变量的 label, value , type
变量的 type 会匹配对应的 helper 函数,对变量进行二次处理。目前内置了 date.format , date.offset 的 helpers。也就是说如果变量的类型是 date, 会匹配到对应的过滤器。如果变量是其他类型或者无类型,都无法匹配到过滤器。
```ts
const scope = [
{label: 'v1', value: 'v1'}
{label: 'now', value: 'now', type: 'date'}
]
```
<code src="./demos/scope.tsx"></code>
### helper 示例
<code src="./demos/helper-demo.tsx"></code>
### 变量值如何获取
1. VariableEvaluateProvider 上下文获取
提供 VariableEvaluateProvider 上下文,可以注入 data 属性
2. 从 scope 中的 example 属性获取
某些变量在前端无法获取到实际值(如服务端环境下的变量),但是有需要提供一个示例值方便应用 helpers可以在声明 scope 选项时带上示例值,如 `{label: 'v1' , value: 'v1', example: 'example-value-v1'}`
一下是一个完整的例子,变量 v1 由上下文提供值,变量 v2 由 example 提供值。鼠标悬浮在变量名称上即可查看对应变量的值。
<code src="./demos/variable-value.tsx"></code>
### 应用场景
#### 默认值
<code src="./demos/form-default-value.tsx"></code>
### 变量选中并已被禁用的效果
当变量被禁用时不影响已经选中的变量显示。在下面的例子中now 变量已被禁用:
<code src="./demos/selected-and-disable.tsx"></code>
```ts
import type { DefaultOptionType } from 'antd/lib/cascader';
type VariableInputProps = {

View File

@ -48,9 +48,10 @@ const getValue = async (params: {
}): Promise<any> => {
const { field, data, context } = params;
const path = field.split('.');
const dataKey = path.slice(1).join('.');
// Handle scope functions (starting with $)
if (path[0].startsWith('$')) {
const dataKey = path.slice(1).join('.');
const scopeKey = path[0];
const scopeFn = data[scopeKey];
if (typeof scopeFn === 'function') {
@ -62,11 +63,10 @@ const getValue = async (params: {
return null;
}
const value = get(data, dataKey);
// Handle function values
const value = get(data, field);
if (typeof value === 'function') {
return value();
}
return value;
};

View File

@ -68,9 +68,11 @@ export class JSONTemplateParser {
}
registerFilterGroup(group: FilterGroup): void {
if (this._filterGroups.find((g) => g.name === group.name)) return;
this._filterGroups.push(group);
}
registerFilter(filter: Helper): void {
if (this._filters.find((f) => f.name === filter.name)) return;
this._filters.push(filter);
this._engine.registerFilter(filter.name, filter.handler);
}

View File

@ -11,7 +11,7 @@ type ValueType = 'date' | 'string' | 'dateRange' | 'any';
export type Helper = {
name: string;
title: string;
handler: Function;
handler: (...args: any[]) => any;
group: string;
inputType: ValueType;
outputType: ValueType;

View File

@ -70,3 +70,15 @@ export function dateSubtract(initialValue: any, unit: any, number: number) {
return handler(initialValue);
}
}
export function dateOffset(initialValue: any, action: 'add' | 'subtract', number: number, unit: any) {
const handler = (value: any) => {
if (action === 'add') {
return dayjs.isDayjs(value) ? value.add(number, unit) : dayjs(value).add(number, unit);
} else if (action === 'subtract') {
return dayjs.isDayjs(value) ? value.subtract(number, unit) : dayjs(value).subtract(number, unit);
}
throw new Error('Invalid action');
};
return handler(initialValue);
}

View File

@ -9,7 +9,7 @@
import { Helper } from '@nocobase/json-template-parser';
import { first } from './array';
import { dateAdd, dateFormat, dateSubtract } from './date';
import { dateAdd, dateFormat, dateOffset, dateSubtract } from './date';
const NAMESPACE = 'variable-helpers';
function tval(text: string) {
@ -37,50 +37,33 @@ export const helpers: Helper[] = [
],
},
{
name: 'date_add',
title: 'add',
handler: dateAdd,
group: 'date',
inputType: 'date',
outputType: 'date',
sort: 2,
args: [],
uiSchema: [
{
name: 'unit',
title: tval('Unit'),
type: 'string',
required: true,
'x-component': 'Select',
enum: [
{ label: tval('Year'), value: 'year' },
{ label: tval('Quarter'), value: 'quarter' },
{ label: tval('Week'), value: 'week' },
{ label: tval('Day'), value: 'day' },
{ label: tval('Hour'), value: 'hour' },
{ label: tval('Minute'), value: 'minute' },
{ label: tval('Second'), value: 'second' },
],
},
{
name: 'number',
title: tval('Amount'),
type: 'number',
'x-component': 'InputNumber',
required: true,
},
],
},
{
name: 'date_subtract',
title: 'substract',
handler: dateSubtract,
name: 'date_offset',
title: 'offset',
handler: dateOffset,
group: 'date',
inputType: 'date',
outputType: 'date',
sort: 3,
args: [],
uiSchema: [
{
name: 'action',
title: tval('Action'),
type: 'string',
required: true,
'x-component': 'Select',
enum: [
{ label: tval('Add'), value: 'add' },
{ label: tval('Sbutract'), value: 'subtract' },
],
},
{
name: 'number',
title: tval('Amount'),
type: 'number',
'x-component': 'InputNumber',
required: true,
},
{
name: 'unit',
title: tval('Unit'),
@ -97,35 +80,8 @@ export const helpers: Helper[] = [
{ label: tval('Second'), value: 'second' },
],
},
{
name: 'number',
title: tval('Amount'),
type: 'number',
'x-component': 'InputNumber',
required: true,
},
],
},
{
name: 'array_first',
title: 'first',
handler: first,
sort: 4,
args: [],
group: 'array',
inputType: 'string',
outputType: 'any',
},
{
name: 'array_last',
title: 'last',
sort: 5,
args: [],
handler: first,
group: 'array',
inputType: 'string',
outputType: 'any',
},
];
export const helperGroups = [