mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-08 15:09:27 +08:00
feat: add helper type inference
This commit is contained in:
parent
2151143dbf
commit
0a687d0791
@ -8,14 +8,11 @@
|
||||
*/
|
||||
|
||||
import { observable } from '@formily/reactive';
|
||||
import { extractTemplateElements, createJSONTemplateParser } from '@nocobase/json-template-parser';
|
||||
import { Helper as _Helper, createJSONTemplateParser, extractTemplateElements } from '@nocobase/json-template-parser';
|
||||
|
||||
type Helper = {
|
||||
name: string;
|
||||
type Helper = _Helper & {
|
||||
argsMap: Record<string, any>;
|
||||
config: any;
|
||||
args: string[];
|
||||
handler: (value: any, ...args: any[]) => any;
|
||||
config: _Helper;
|
||||
};
|
||||
|
||||
type RawHelper = {
|
||||
|
@ -230,6 +230,7 @@ function _Input(props: VariableInputProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm();
|
||||
const [options, setOptions] = React.useState<DefaultOptionType[]>([]);
|
||||
const [variableType, setVariableType] = React.useState<string>();
|
||||
const [variableText, setVariableText] = React.useState([]);
|
||||
const [isFieldValue, setIsFieldValue] = React.useState(
|
||||
hideVariableButton || (children && value != null ? true : false),
|
||||
@ -382,6 +383,9 @@ function _Input(props: VariableInputProps) {
|
||||
return;
|
||||
}
|
||||
onChange(`{{${next.join('.')}}}`, optionPath);
|
||||
if (Array.isArray(optionPath) && optionPath.length > 0) {
|
||||
setVariableType(optionPath[optionPath.length - 1]?.type ?? null);
|
||||
}
|
||||
},
|
||||
[type, variable, onChange],
|
||||
);
|
||||
@ -487,7 +491,7 @@ function _Input(props: VariableInputProps) {
|
||||
})}
|
||||
<VariableProvider
|
||||
variableName={fullVariable}
|
||||
variableHelperMapping={variableHelperMapping}
|
||||
variableType={variableType}
|
||||
onVariableTemplateChange={onChange}
|
||||
>
|
||||
<HelperList />
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { reaction } from '@formily/reactive';
|
||||
import { observer } from '@formily/reactive-react';
|
||||
import { composeTemplate, extractTemplateElements, Helper } from '@nocobase/json-template-parser';
|
||||
import { get, isArray } from 'lodash';
|
||||
import minimatch from 'minimatch';
|
||||
@ -19,14 +20,15 @@ import { useHelperObservables } from './Helpers/hooks/useHelperObservables';
|
||||
interface VariableContextValue {
|
||||
value: any;
|
||||
helperObservables?: ReturnType<typeof useHelperObservables>;
|
||||
variableHelperMapping: VariableHelperMapping;
|
||||
variableType: string;
|
||||
valueType: string;
|
||||
variableName: string;
|
||||
}
|
||||
|
||||
interface VariableProviderProps {
|
||||
variableName: string;
|
||||
variableType: string | null;
|
||||
children: React.ReactNode;
|
||||
variableHelperMapping?: VariableHelperMapping;
|
||||
helperObservables?: ReturnType<typeof useHelperObservables>;
|
||||
onVariableTemplateChange?: (val) => void;
|
||||
}
|
||||
@ -61,27 +63,11 @@ function escapeGlob(str: string): string {
|
||||
* @param mapping The variable helper mapping configuration
|
||||
* @returns boolean indicating if the filter is allowed for the variable
|
||||
*/
|
||||
export function isHelperAllowedForVariable(
|
||||
variableName: string,
|
||||
helperName: string,
|
||||
mapping?: VariableHelperMapping,
|
||||
): boolean {
|
||||
if (!mapping?.rules) {
|
||||
return true; // If no rules defined, allow all filters
|
||||
export function isHelperAllowedForVariable(helperName: string, valueType: string): boolean {
|
||||
if (valueType) {
|
||||
const matched = minimatch(helperName, `${valueType}.*`);
|
||||
return matched;
|
||||
}
|
||||
|
||||
// Check each rule
|
||||
for (const rule of mapping.rules) {
|
||||
// Check if variable matches the pattern
|
||||
// We don't escape the pattern since it's meant to be a glob pattern
|
||||
// But we escape the variable name since it's a literal value
|
||||
const matched = minimatch(variableName, rule.variable);
|
||||
if (matched) {
|
||||
// Check if filter matches any of the allowed patterns
|
||||
return rule.helpers.some((pattern) => minimatch(helperName, pattern));
|
||||
}
|
||||
}
|
||||
|
||||
// If no matching rule found and strictMode is true, deny the filter
|
||||
return false;
|
||||
}
|
||||
@ -118,7 +104,8 @@ export function getSupportedFiltersForVariable(
|
||||
const VariableContext = createContext<VariableContextValue>({
|
||||
variableName: '',
|
||||
value: null,
|
||||
variableHelperMapping: { rules: [] },
|
||||
variableType: null,
|
||||
valueType: '',
|
||||
});
|
||||
|
||||
export function useCurrentVariable(): VariableContextValue {
|
||||
@ -129,10 +116,10 @@ export function useCurrentVariable(): VariableContextValue {
|
||||
return context;
|
||||
}
|
||||
|
||||
export const VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
const _VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
variableName,
|
||||
children,
|
||||
variableHelperMapping,
|
||||
variableType,
|
||||
onVariableTemplateChange,
|
||||
}) => {
|
||||
const [value, setValue] = useState(null);
|
||||
@ -171,28 +158,30 @@ export const VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
fetchValue();
|
||||
}, [localVariables, variableName, variables]);
|
||||
|
||||
const valueType =
|
||||
helperObservables.helpersObs.value.length > 0
|
||||
? helperObservables.helpersObs.value[helperObservables.helpersObs.value.length - 1].config.outputType
|
||||
: variableType;
|
||||
|
||||
return (
|
||||
<VariableContext.Provider value={{ variableName, value, helperObservables, variableHelperMapping }}>
|
||||
<VariableContext.Provider value={{ variableName, value, valueType, helperObservables, variableType }}>
|
||||
{children}
|
||||
</VariableContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const VariableProvider = observer(_VariableProvider, { displayName: 'VariableProvider' });
|
||||
|
||||
export function useVariable() {
|
||||
const context = useContext(VariableContext);
|
||||
const { value, variableName, variableHelperMapping } = context;
|
||||
const { value, variableName, valueType } = context;
|
||||
|
||||
const isHelperAllowed = (filterName: string) => {
|
||||
return isHelperAllowedForVariable(variableName, filterName, variableHelperMapping);
|
||||
};
|
||||
|
||||
const getSupportedFilters = (allHelpers: Helper[]) => {
|
||||
return getSupportedFiltersForVariable(variableName, variableHelperMapping, allHelpers);
|
||||
const isHelperAllowed = (helperName: string) => {
|
||||
return isHelperAllowedForVariable(helperName, valueType);
|
||||
};
|
||||
|
||||
return {
|
||||
...context,
|
||||
isHelperAllowed,
|
||||
getSupportedFilters,
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
import { createForm } from '@formily/core';
|
||||
import { observer, useField, useForm } from '@formily/react';
|
||||
import { AntdSchemaComponentProvider, Plugin, SchemaComponent } from '@nocobase/client';
|
||||
import { mockApp } from '@nocobase/client/demo-utils';
|
||||
import PluginVariableFiltersClient from '@nocobase/plugin-variable-helpers/client';
|
||||
import React from 'react';
|
||||
const scope = [
|
||||
{ label: 'v1', value: 'v1' },
|
||||
{ label: 'Date', value: '$nDate', children: [{ label: 'Now', value: 'now' }] },
|
||||
];
|
||||
|
||||
const useFormBlockProps = () => {
|
||||
return {
|
||||
form: createForm({
|
||||
initialValues: {},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
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,
|
||||
variableHelperMapping: {
|
||||
rules: [
|
||||
{
|
||||
variable: '$nDate.*',
|
||||
helpers: ['date.*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'void',
|
||||
title: `输出`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'OutPut',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const OutPut = observer(() => {
|
||||
const form = useForm();
|
||||
return <div>Current input value: {form.values.input}</div>;
|
||||
});
|
||||
|
||||
const Demo = () => {
|
||||
return (
|
||||
<AntdSchemaComponentProvider>
|
||||
<SchemaComponent schema={schema} scope={{ useFormBlockProps }} components={{ OutPut }} />
|
||||
</AntdSchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
|
||||
class DemoPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('root', { path: '/', Component: Demo });
|
||||
}
|
||||
}
|
||||
|
||||
const app = mockApp({ plugins: [DemoPlugin, PluginVariableFiltersClient] });
|
||||
|
||||
export default app.getRootComponent();
|
@ -7,7 +7,7 @@ import React from 'react';
|
||||
|
||||
const scope = [
|
||||
{ label: 'v1', value: 'v1' },
|
||||
{ label: 'Date', value: '$nDate', children: [{ label: 'Now', value: 'now' }] },
|
||||
{ label: 'Date', value: '$nDate', children: [{ label: 'Now', value: 'now', type: 'date' }] },
|
||||
];
|
||||
|
||||
const useFormBlockProps = () => {
|
||||
@ -30,14 +30,6 @@ const schema = {
|
||||
'x-component': 'Variable.Input',
|
||||
'x-component-props': {
|
||||
scope,
|
||||
variableHelperMapping: {
|
||||
rules: [
|
||||
{
|
||||
variable: '$nDate.*',
|
||||
helpers: ['date.*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
|
@ -7,11 +7,10 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { DateVariableContext } from '../date';
|
||||
import { useImmer } from 'use-immer';
|
||||
import { Updater } from 'use-immer';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import merge from 'lodash/merge';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { Updater, useImmer } from 'use-immer';
|
||||
import { DateVariableContext } from '../date';
|
||||
|
||||
type VariablesEvalContextType = {
|
||||
$dateV2: DateVariableContext;
|
||||
@ -54,3 +53,17 @@ export const useVariablesContext = () => {
|
||||
}
|
||||
return variablesCtx;
|
||||
};
|
||||
|
||||
const scopes = [
|
||||
{
|
||||
label: 'date',
|
||||
value: '$nDate',
|
||||
children: [
|
||||
{
|
||||
label: 'Now',
|
||||
value: 'now',
|
||||
helpers: ['date.*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -7,7 +7,8 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
export * from './parser';
|
||||
export * from './filters';
|
||||
export * from './utils';
|
||||
export * from './escape';
|
||||
export * from './filters';
|
||||
export * from './parser';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
|
21
packages/core/json-template-parser/src/types.ts
Normal file
21
packages/core/json-template-parser/src/types.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
type ValueType = 'date' | 'string' | 'dateRange' | 'any';
|
||||
export type Helper = {
|
||||
name: string;
|
||||
title: string;
|
||||
handler: Function;
|
||||
group: string;
|
||||
inputType: ValueType;
|
||||
outputType: ValueType;
|
||||
sort: number;
|
||||
args: string[];
|
||||
uiSchema?: any[];
|
||||
};
|
@ -9,14 +9,9 @@
|
||||
|
||||
import { escape, escapeSpecialChars, revertEscape } from '../escape';
|
||||
import { createJSONTemplateParser } from '../parser';
|
||||
|
||||
import { Helper } from '../types';
|
||||
const parser = createJSONTemplateParser();
|
||||
const engine = parser.engine;
|
||||
export type Helper = {
|
||||
name: string;
|
||||
handler: any;
|
||||
args: string[];
|
||||
};
|
||||
|
||||
export function extractTemplateVariable(template: string): string | null {
|
||||
const escapedTemplate = escape(template ?? '');
|
||||
|
@ -7,6 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { Helper } from '@nocobase/json-template-parser';
|
||||
import { first } from './array';
|
||||
import { dateAdd, dateFormat, dateSubtract } from './date';
|
||||
const NAMESPACE = 'variable-helpers';
|
||||
@ -15,13 +16,16 @@ function tval(text: string) {
|
||||
return `{{t(${JSON.stringify(text)}, ${JSON.stringify({ ns: NAMESPACE, nsMode: 'fallback' })})}}`;
|
||||
}
|
||||
|
||||
export const helpers = [
|
||||
export const helpers: Helper[] = [
|
||||
{
|
||||
name: 'date_format',
|
||||
title: 'format',
|
||||
handler: dateFormat,
|
||||
group: 'date',
|
||||
inputType: 'date',
|
||||
outputType: 'string',
|
||||
sort: 1,
|
||||
args: [],
|
||||
uiSchema: [
|
||||
{
|
||||
name: 'format',
|
||||
@ -37,7 +41,10 @@ export const helpers = [
|
||||
title: 'add',
|
||||
handler: dateAdd,
|
||||
group: 'date',
|
||||
inputType: 'date',
|
||||
outputType: 'date',
|
||||
sort: 2,
|
||||
args: [],
|
||||
uiSchema: [
|
||||
{
|
||||
name: 'unit',
|
||||
@ -69,7 +76,10 @@ export const helpers = [
|
||||
title: 'substract',
|
||||
handler: dateSubtract,
|
||||
group: 'date',
|
||||
inputType: 'date',
|
||||
outputType: 'date',
|
||||
sort: 3,
|
||||
args: [],
|
||||
uiSchema: [
|
||||
{
|
||||
name: 'unit',
|
||||
@ -101,14 +111,20 @@ export const helpers = [
|
||||
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',
|
||||
},
|
||||
];
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user