support expression editor in object values

This commit is contained in:
Yanzhen Yu 2022-02-04 16:32:40 +08:00
parent 8a375861e6
commit 46fa447432
5 changed files with 49 additions and 24 deletions

View File

@ -3,10 +3,9 @@ import * as acorn from 'acorn';
import * as acornLoose from 'acorn-loose';
import { simple as simpleWalk } from 'acorn-walk';
import { flattenDeep, isArray, isObject } from 'lodash-es';
import { isExpression } from '../validator/utils';
import { ComponentId, IFieldModel, ModuleId } from './IAppModel';
const regExp = /.*{{.*}}.*/;
export class FieldModel implements IFieldModel {
isDynamic = false;
refs: Record<ComponentId | ModuleId, string[]> = {};
@ -62,13 +61,13 @@ export class FieldModel implements IFieldModel {
(isArrayValue
? []
: shouldExtendValues && isOldValueObject
? this.value
: {}) as Record<string, IFieldModel>
? this.value
: {}) as Record<string, IFieldModel>
);
} else {
this.value = value;
}
this.isDynamic = typeof value === 'string' && regExp.test(value);
this.isDynamic = isExpression(value);
this.parseReferences();
}

View File

@ -9,6 +9,7 @@ import {
} from '@chakra-ui/react';
import { isEmpty } from 'lodash-es';
import { AnyKind, UnknownKind } from '@sinclair/typebox';
import { isExpression as _isExpression } from '../../../validator/utils';
import { FieldProps, getCodeMode, getDisplayLabel } from './fields';
import { widgets } from './widgets/widgets';
import StringField from './StringField';
@ -89,7 +90,7 @@ const SchemaField: React.FC<Props> = props => {
const { schema, label, formData, onChange, registry, stateManager } = props;
const [isExpression, setIsExpression] = useState(
// FIXME: regexp copied from FieldModel.ts, is this a stable way to check expression?
() => typeof formData === 'string' && /.*{{.*}}.*/.test(formData)
() => _isExpression(formData)
);
if (isEmpty(schema)) {

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import {
toNumber,
isString,
@ -11,6 +11,7 @@ import {
} from 'lodash-es';
import { FieldProps } from '../fields';
import { ExpressionEditor } from '../../../CodeEditor';
import { isExpression } from '../../../../validator/utils';
type Props = FieldProps;
@ -89,27 +90,52 @@ const customTreeTypeDefCreator = (dataTree: Record<string, Record<string, unknow
return { ...def };
};
const getDefaultCode = (value: unknown): { defaultCode: string; type: string } => {
const type = typeof value;
if (type === 'object' || type === 'boolean') {
value = JSON.stringify(value, null, 2);
} else {
value = String(value);
}
return {
defaultCode: value as string,
type,
};
};
const getParsedValue = (raw: string, type: string) => {
if (isExpression(raw)) {
return raw;
}
if (type === 'object' || type === 'boolean') {
try {
return JSON.parse(raw);
} catch (error) {
// TODO: handle error
return {};
}
}
if (type === 'number') {
return toNumber(raw);
}
return raw;
};
export const ExpressionWidget: React.FC<Props> = props => {
const { formData, onChange, stateManager } = props;
const [defs, setDefs] = useState<any>();
useEffect(() => {
setDefs([customTreeTypeDefCreator(stateManager.store)]);
}, [stateManager]);
const { defaultCode, type } = useMemo(() => {
return getDefaultCode(formData);
}, [formData]);
return (
<ExpressionEditor
// TODO: better serialization
defaultCode={String(formData)}
defaultCode={defaultCode}
onBlur={_v => {
// TODO: move into expression editor?
let v: string | number | boolean = _v;
if (isNumeric(v)) {
v = toNumber(v);
} else if (v === 'true') {
v = true;
} else if (v === 'false') {
v = false;
}
const v = getParsedValue(_v, type);
onChange(v);
}}
defs={defs}

View File

@ -1,4 +1,3 @@
export function isExpression (str: unknown) {
const regExp = new RegExp('.*{{.*}}.*');
return typeof str === 'string' && regExp.test(str)
export function isExpression(str: unknown) {
return typeof str === 'string' && /[\s\S]*{{[\s\S]*}}[\s\S]*/m.test(str);
}

View File

@ -33,7 +33,7 @@ export class StateManager {
clear = () => {
this.store = reactive<Record<string, any>>({});
}
};
evalExp = (expChunk: ExpChunk, scopeObject = {}): unknown => {
if (typeof expChunk === 'string') {
@ -178,8 +178,8 @@ export const parseExpression = (exp: string, parseListItem = false): ExpChunk[]
let item;
while ((item = tokens.shift())) {
if (item == '}}') return result;
result.push(item == '{{' ? build(tokens) : item);
if (item === '}}') return result;
result.push(item === '{{' ? build(tokens) : item);
}
return result;
}