fix(editor): make the form update correctly

This commit is contained in:
MrWindlike 2022-01-26 17:24:22 +08:00
parent 940cdb619c
commit f96dc66881
9 changed files with 128 additions and 83 deletions

View File

@ -5,18 +5,18 @@ import { simple as simpleWalk } from 'acorn-walk';
import { flattenDeep, isArray, isObject } from 'lodash-es';
import { ComponentId, IFieldModel, ModuleId } from './IAppModel';
const regExp = new RegExp('.*{{.*}}.*');
const regExp = /.*{{.*}}.*/;
export class FieldModel implements IFieldModel {
isDynamic = false;
refs: Record<ComponentId | ModuleId, string[]> = {};
private value: unknown | Array<IFieldModel> | Record<string, IFieldModel>;
constructor(value: unknown) {
constructor (value: unknown) {
this.update(value);
}
get rawValue() {
get rawValue () {
if (isObject(this.value)) {
if (isArray(this.value)) {
return this.value.map(field => field.rawValue);
@ -32,21 +32,30 @@ export class FieldModel implements IFieldModel {
return this.value;
}
update(value: unknown) {
update (value: unknown, shouldExtendValues = true) {
if (isObject(value)) {
if (!isObject(this.value)) {
this.value = isArray(value) ? [] : {};
}
const isArrayValue = isArray(value);
const isOldValueObject = isObject(this.value);
for (const key in value) {
const val = (value as Record<string, unknown>)[key];
const _thisValue = this.value as Record<string, IFieldModel>;
if (!_thisValue[key]) {
_thisValue[key] = new FieldModel(val);
this.value = (Object.keys(value) as Array<keyof typeof value>).reduce((result, key) => {
const oldValue: IFieldModel | null = isObject(this.value) ? this.value[key] : null;
let newValue: FieldModel;
if (oldValue) {
(oldValue as IFieldModel).update(value[key], false);
newValue = oldValue;
} else {
_thisValue[key].update(val);
newValue = new FieldModel(value[key]);
}
}
if (isArray(result)) {
result.push(newValue);
} else {
result[key] = newValue;
}
return result;
}, (isArrayValue ? [] : (shouldExtendValues && isOldValueObject ? this.value : {})) as Record<string, IFieldModel>);
} else {
this.value = value;
}
@ -54,19 +63,19 @@ export class FieldModel implements IFieldModel {
this.parseReferences();
}
getProperty(key: string | number): FieldModel | undefined {
getProperty (key: string | number): FieldModel | undefined {
if (typeof this.value === 'object') {
return (this.value as any)[key];
}
return undefined;
}
getValue() {
return this.value
getValue () {
return this.value;
}
traverse(cb: (f: IFieldModel, key: string) => void) {
function _traverse(field: FieldModel, key: string) {
traverse (cb: (f: IFieldModel, key: string) => void) {
function _traverse (field: FieldModel, key: string) {
if (isObject(field.value)) {
for (const _key in field.value) {
const val = field.getProperty(_key);
@ -81,7 +90,7 @@ export class FieldModel implements IFieldModel {
_traverse(this, '');
}
private parseReferences() {
private parseReferences () {
if (!this.isDynamic || typeof this.value !== 'string') return;
const exps = flattenDeep(
@ -102,13 +111,13 @@ export class FieldModel implements IFieldModel {
const str = exp.slice(node.start, node.end);
let path = str.replace(lastIdentifier, '');
if (path.startsWith('.')) {
path = path.slice(1, path.length)
path = path.slice(1, path.length);
}
this.refs[lastIdentifier].push(path);
break;
default:
}
},
}
});
});
}

View File

@ -2,7 +2,7 @@ import {
ComponentSchema,
TraitSchema,
MethodSchema,
RuntimeTrait,
RuntimeTrait
} from '@sunmao-ui/core';
export type ComponentId = string & {
@ -116,7 +116,7 @@ export interface ITraitModel {
export interface IFieldModel {
// value: any;
isDynamic: boolean;
update: (value: unknown) => void;
update: (value: unknown, shouldExtendValues: boolean) => void;
getProperty: (key: string) => IFieldModel | void;
getValue: () => unknown | void | IFieldModel;
traverse: (cb: (f: IFieldModel, key: string) => void) => void;

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { flatten } from 'lodash-es';
import { observer } from 'mobx-react-lite';
import { FormControl, FormLabel, Input, Textarea, VStack } from '@chakra-ui/react';
@ -29,30 +29,49 @@ export const renderField = (properties: {
services: EditorServices;
}) => {
const { value, type, fullKey, selectedComponentId, index, services } = properties;
// eslint-disable-next-line react-hooks/rules-of-hooks
const [textareaValue, setTextareaValue] = useState(value as string);
const { eventBus, registry } = services;
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (typeof value !== 'object') {
setTextareaValue(value as string);
}
}, [value]);
if (typeof value !== 'object') {
const ref = React.createRef<HTMLTextAreaElement>();
const onBlur = () => {
const operation = type
? genOperation(registry, 'modifyTraitProperty', {
componentId: selectedComponentId,
traitIndex: index,
properties: {
[fullKey]: ref.current?.value,
},
})
componentId: selectedComponentId,
traitIndex: index,
properties: {
[fullKey]: ref.current?.value,
},
})
: genOperation(registry, 'modifyComponentProperty', {
componentId: selectedComponentId,
properties: {
[fullKey]: ref.current?.value,
},
});
componentId: selectedComponentId,
properties: {
[fullKey]: ref.current?.value,
},
});
eventBus.send('operation', operation);
};
const onChange = (event: any) => {
setTextareaValue(event.target.value);
};
return (
<FormControl key={`${selectedComponentId}-${fullKey}`}>
<FormLabel>{fullKey}</FormLabel>
<Textarea ref={ref} onBlur={onBlur} defaultValue={value as string} />
<Textarea
ref={ref}
onChange={onChange}
onBlur={onBlur}
value={textareaValue}
/>
</FormControl>
);
} else {

View File

@ -7,7 +7,7 @@ import {
Input,
Select,
Switch,
VStack,
VStack
} from '@chakra-ui/react';
import { Static } from '@sinclair/typebox';
import { CloseIcon } from '@chakra-ui/icons';
@ -33,16 +33,24 @@ export const EventHandlerForm: React.FC<Props> = observer(props => {
const { components } = editorStore;
const [methods, setMethods] = useState<string[]>([]);
const updateMethods = useCallback(
(componentId: string) => {
const type = components.find(c => c.id === componentId)?.type;
if (type) {
const componentSpec = registry.getComponentByType(type).spec;
setMethods(Object.keys(componentSpec.methods));
}
},
[components, registry]
);
const formik = useFormik({
initialValues: handler,
onSubmit: values => {
onChange(values);
}
});
const updateMethods = useCallback((componentId: string) => {
const type = components.find(c => c.id === componentId)?.type;
if (type) {
const componentSpec = registry.getComponentByType(type).spec;
setMethods(Object.keys(componentSpec.methods));
}
}, [components, registry]);
useEffect(() => {
formik.setValues(handler);
}, [handler]);
useEffect(() => {
if (handler.componentId) {
@ -54,25 +62,19 @@ export const EventHandlerForm: React.FC<Props> = observer(props => {
updateMethods(e.target.value);
};
const formik = useFormik({
initialValues: handler,
onSubmit: values => {
onChange(values);
},
});
const typeField = (
<FormControl>
<FormLabel>Event Type</FormLabel>
<Select
name="type"
placeholder="Select Event Type"
onChange={formik.handleChange}
onBlur={() => formik.submitForm()}
onChange={formik.handleChange}
placeholder="Select Event Type"
value={formik.values.type}
>
{eventTypes.map(e => (
<option key={e} value={e}>
<option key={e}
value={e}>
{e}
</option>
))}
@ -84,16 +86,17 @@ export const EventHandlerForm: React.FC<Props> = observer(props => {
<FormLabel>Target Component</FormLabel>
<Select
name="componentId"
placeholder="Select Target Component"
onBlur={() => formik.submitForm()}
onChange={e => {
onTargetComponentChange(e);
formik.handleChange(e);
}}
onBlur={() => formik.submitForm()}
placeholder="Select Target Component"
value={formik.values.componentId}
>
{components.map(c => (
<option key={c.id} value={c.id}>
<option key={c.id}
value={c.id}>
{c.id}
</option>
))}
@ -105,13 +108,14 @@ export const EventHandlerForm: React.FC<Props> = observer(props => {
<FormLabel>Method</FormLabel>
<Select
name="method.name"
placeholder="Select Method"
onChange={formik.handleChange}
onBlur={() => formik.submitForm()}
onChange={formik.handleChange}
placeholder="Select Method"
value={formik.values.method.name}
>
{methods.map(m => (
<option key={m} value={m}>
<option key={m}
value={m}>
{m}
</option>
))}
@ -123,7 +127,7 @@ export const EventHandlerForm: React.FC<Props> = observer(props => {
<FormControl>
<FormLabel>Parameters</FormLabel>
<KeyValueEditor
initValue={formik.values.method.parameters}
value={formik.values.method.parameters}
onChange={json => {
formik.setFieldValue('method.parameters', json);
formik.submitForm();
@ -137,8 +141,8 @@ export const EventHandlerForm: React.FC<Props> = observer(props => {
<FormLabel>Wait Type</FormLabel>
<Select
name="wait.type"
onChange={formik.handleChange}
onBlur={() => formik.submitForm()}
onChange={formik.handleChange}
value={formik.values.wait?.type}
>
<option value="delay">delay</option>
@ -153,8 +157,8 @@ export const EventHandlerForm: React.FC<Props> = observer(props => {
<FormLabel>Wait Time</FormLabel>
<Input
name="wait.time"
onChange={formik.handleChange}
onBlur={() => formik.submitForm()}
onChange={formik.handleChange}
value={formik.values.wait?.time}
/>
</FormControl>
@ -164,16 +168,17 @@ export const EventHandlerForm: React.FC<Props> = observer(props => {
<FormControl>
<FormLabel>Disabled</FormLabel>
<Switch
name="disabled"
isChecked={formik.values.disabled}
onChange={formik.handleChange}
name="disabled"
onBlur={() => formik.submitForm()}
onChange={formik.handleChange}
/>
</FormControl>
);
return (
<Box position="relative" width="100%">
<Box position="relative"
width="100%">
<VStack className={formWrapperCSS}>
{hideEventType ? null : typeField}
{targetField}
@ -184,15 +189,15 @@ export const EventHandlerForm: React.FC<Props> = observer(props => {
{disabledField}
</VStack>
<IconButton
position="absolute"
right="4"
top="4"
aria-label="remove event handler"
variant="ghost"
colorScheme="red"
size="xs"
icon={<CloseIcon />}
onClick={onRemove}
position="absolute"
right="4"
size="xs"
top="4"
variant="ghost"
/>
</Box>
);

View File

@ -92,7 +92,7 @@ export const FetchTraitForm: React.FC<Props> = props => {
<FormControl>
<FormLabel>Body</FormLabel>
<KeyValueEditor
initValue={formik.values.body}
value={formik.values.body}
onChange={json => {
formik.setFieldValue('body', json);
formik.submitForm();
@ -105,7 +105,7 @@ export const FetchTraitForm: React.FC<Props> = props => {
<FormControl>
<FormLabel>Headers</FormLabel>
<KeyValueEditor
initValue={formik.values.headers}
value={formik.values.headers}
onChange={json => {
formik.setFieldValue('headers', json);
formik.submitForm();

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { FieldProps } from './fields';
import {
NumberInput,
@ -14,6 +14,10 @@ const NumberField: React.FC<Props> = props => {
const { formData, onChange } = props;
const [value, setValue] = useState(String(formData));
useEffect(() => {
setValue(String(formData));
}, [formData]);
return (
<NumberInput
value={value}

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { FieldProps } from './fields';
import { Input, Select } from '@chakra-ui/react';
@ -8,6 +8,10 @@ const StringField: React.FC<Props> = props => {
const { schema, formData, onChange } = props;
const [value, setValue] = useState(formData);
useEffect(() => {
setValue(formData);
}, [formData]);
// enum
if (Array.isArray(schema.enum)) {
return (

View File

@ -52,7 +52,7 @@ export const ModuleMetaDataForm: React.FC<ModuleMetaDataFormProps> = observer(
<FormControl>
<FormLabel>Module StateMap</FormLabel>
<KeyValueEditor
initValue={formik.values.stateMap}
value={formik.values.stateMap}
onChange={json => {
formik.setFieldValue('stateMap', json);
formik.submitForm();

View File

@ -2,18 +2,22 @@ import { CloseIcon } from '@chakra-ui/icons';
import { Button, HStack, IconButton, Input, VStack } from '@chakra-ui/react';
import produce from 'immer';
import { fromPairs, toPairs } from 'lodash-es';
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
type Props = {
onChange: (json: Record<string, string>) => void;
initValue?: Record<string, string>;
value?: Record<string, string>;
};
export const KeyValueEditor: React.FC<Props> = props => {
const [rows, setRows] = useState<Array<[string, string]>>(() => {
return toPairs(props.initValue);
return toPairs(props.value);
});
useEffect(() => {
setRows(toPairs(props.value));
}, [props.value]);
const emitDataChange = (newRows: Array<[string, string]>) => {
const json = fromPairs(newRows);
props.onChange(json);