mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-02-23 17:49:49 +08:00
Merge pull request #257 from webzard-io/auto-complete
Auto complete: part 2
This commit is contained in:
commit
744b795753
@ -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();
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
Button,
|
||||
} 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';
|
||||
@ -88,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)) {
|
||||
@ -118,6 +120,10 @@ const SchemaField: React.FC<Props> = props => {
|
||||
Component = NullField;
|
||||
} else if ('anyOf' in schema || 'oneOf' in schema) {
|
||||
Component = MultiSchemaField;
|
||||
} else if (
|
||||
[AnyKind, UnknownKind].includes((schema as unknown as { kind: symbol }).kind)
|
||||
) {
|
||||
Component = widgets.expression;
|
||||
} else {
|
||||
console.info('Found unsupported schema', schema);
|
||||
}
|
||||
|
@ -9,7 +9,13 @@ const UnsupportedField: React.FC<Props> = props => {
|
||||
return (
|
||||
<div>
|
||||
Unsupported field schema
|
||||
<p>
|
||||
<b>schema:</b>
|
||||
</p>
|
||||
<pre>{JSON.stringify(schema, null, 2)}</pre>
|
||||
<p>
|
||||
<b>value:</b>
|
||||
</p>
|
||||
<pre>{JSON.stringify(formData, null, 2)}</pre>
|
||||
</div>
|
||||
);
|
||||
|
@ -29,6 +29,7 @@ export function getCodeMode(schema: Schema): boolean {
|
||||
case 'array':
|
||||
case 'object':
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
return true;
|
||||
default:
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -22,6 +22,19 @@ describe('parseExpression function', () => {
|
||||
[' input1.value '],
|
||||
'!',
|
||||
]);
|
||||
|
||||
const multiline = parseExpression(`{{
|
||||
{ id: 1 }
|
||||
}}`);
|
||||
expect(multiline).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"
|
||||
{ id: 1 }
|
||||
",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('can parse $listItem expression', () => {
|
||||
|
@ -33,7 +33,7 @@ export class StateManager {
|
||||
|
||||
clear = () => {
|
||||
this.store = reactive<Record<string, any>>({});
|
||||
}
|
||||
};
|
||||
|
||||
evalExp = (expChunk: ExpChunk, scopeObject = {}): unknown => {
|
||||
if (typeof expChunk === 'string') {
|
||||
@ -43,7 +43,10 @@ export class StateManager {
|
||||
const evalText = expChunk.map(ex => this.evalExp(ex, scopeObject)).join('');
|
||||
let evaled;
|
||||
try {
|
||||
evaled = new Function(`with(this) { return ${evalText} }`).call({
|
||||
evaled = new Function(
|
||||
// trim leading space and newline
|
||||
`with(this) { return ${evalText.replace(/^\s+/g, '')} }`
|
||||
).call({
|
||||
...this.store,
|
||||
...this.dependencies,
|
||||
...scopeObject,
|
||||
@ -178,8 +181,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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user