mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-04-06 21:40:23 +08:00
Merge pull request #448 from webzard-io/feat/windlike-patch
feat: change the reference properties when changing the component id
This commit is contained in:
commit
db7a6936f3
@ -1,52 +1,68 @@
|
||||
import { AppModel } from '../../src/AppModel/AppModel';
|
||||
import { FieldModel } from '../../src/AppModel/FieldModel';
|
||||
import { ComponentId } from '../../src/AppModel/IAppModel';
|
||||
import { registry } from '../services';
|
||||
import { ChangeIdMockSchema } from './mock';
|
||||
import { CORE_VERSION, CoreTraitName } from '@sunmao-ui/shared';
|
||||
|
||||
describe('Field test', () => {
|
||||
it('parse static property', () => {
|
||||
const field = new FieldModel('Hello, world!');
|
||||
expect(field.isDynamic).toEqual(false);
|
||||
expect(field.refs).toEqual({});
|
||||
expect(field.refComponentInfos).toEqual({});
|
||||
expect(field.rawValue).toEqual('Hello, world!');
|
||||
});
|
||||
|
||||
it('parse expression', () => {
|
||||
const field = new FieldModel('{{input.value}} + {{list[0].text}}');
|
||||
expect(field.isDynamic).toEqual(true);
|
||||
expect(field.refs).toEqual({ input: ['value'], list: ['[0]', '[0].text'] });
|
||||
expect(field.refComponentInfos['input' as ComponentId].refProperties).toEqual([
|
||||
'value',
|
||||
]);
|
||||
expect(field.refComponentInfos['list' as ComponentId].refProperties).toEqual([
|
||||
'[0]',
|
||||
'[0].text',
|
||||
]);
|
||||
expect(field.rawValue).toEqual('{{input.value}} + {{list[0].text}}');
|
||||
});
|
||||
|
||||
it('parse inline variable in expression', () => {
|
||||
const field = new FieldModel('{{ [].length }}');
|
||||
expect(field.isDynamic).toEqual(true);
|
||||
expect(field.refs).toEqual({});
|
||||
expect(field.refComponentInfos).toEqual({});
|
||||
});
|
||||
|
||||
it('parse object property', () => {
|
||||
const field = new FieldModel({ raw: '{{input.value}}', format: 'md' });
|
||||
expect(field.isDynamic).toEqual(false);
|
||||
expect(field.refs).toEqual({});
|
||||
expect(field.refComponentInfos).toEqual({});
|
||||
expect(field.rawValue).toEqual({ raw: '{{input.value}}', format: 'md' });
|
||||
expect(field.getProperty('raw')!.rawValue).toEqual('{{input.value}}');
|
||||
expect(field.getProperty('raw')!.isDynamic).toEqual(true);
|
||||
expect(field.getProperty('raw')!.refs).toEqual({ input: ['value'] });
|
||||
expect(
|
||||
field.getProperty('raw')!.refComponentInfos['input' as ComponentId].refProperties
|
||||
).toEqual(['value']);
|
||||
expect(field.getProperty('format')!.rawValue).toEqual('md');
|
||||
expect(field.getProperty('format')!.isDynamic).toEqual(false);
|
||||
expect(field.getProperty('format')!.refs).toEqual({});
|
||||
expect(field.getProperty('format')!.refComponentInfos).toEqual({});
|
||||
});
|
||||
|
||||
it('parse array property', () => {
|
||||
const field = new FieldModel({ data: [1, '{{fetch.data}}'] });
|
||||
expect(field.isDynamic).toEqual(false);
|
||||
expect(field.refs).toEqual({});
|
||||
expect(field.refComponentInfos).toEqual({});
|
||||
expect(field.rawValue).toEqual({ data: [1, '{{fetch.data}}'] });
|
||||
expect(field.getProperty('data')!.rawValue).toEqual([1, '{{fetch.data}}']);
|
||||
expect(field.getProperty('data')!.isDynamic).toEqual(false);
|
||||
expect(field.getProperty('data')!.refs).toEqual({});
|
||||
expect(field.getProperty('data')!.refComponentInfos).toEqual({});
|
||||
expect(field.getProperty('data')!.getProperty(0)!.rawValue).toEqual(1);
|
||||
expect(field.getProperty('data')!.getProperty(0)!.isDynamic).toEqual(false);
|
||||
expect(field.getProperty('data')!.getProperty(1)!.rawValue).toEqual('{{fetch.data}}');
|
||||
expect(field.getProperty('data')!.getProperty(1)!.isDynamic).toEqual(true);
|
||||
expect(field.getProperty('data')!.getProperty(1)!.refs).toEqual({ fetch: ['data'] });
|
||||
expect(
|
||||
field.getProperty('data')!.getProperty(1)!.refComponentInfos['fetch' as ComponentId]
|
||||
.refProperties
|
||||
).toEqual(['data']);
|
||||
});
|
||||
|
||||
it('update array property', () => {
|
||||
@ -70,4 +86,50 @@ describe('Field test', () => {
|
||||
field.update({ value: 'text' });
|
||||
expect(field.rawValue).toEqual({ data: { a: 2 }, value: 'text' });
|
||||
});
|
||||
|
||||
it('change component id', () => {
|
||||
const appModel = new AppModel(ChangeIdMockSchema, registry);
|
||||
const input = appModel.getComponentById('input' as ComponentId);
|
||||
const text = appModel.getComponentById('text' as ComponentId);
|
||||
const button = appModel.getComponentById('button' as ComponentId);
|
||||
|
||||
input!.changeId('input1' as ComponentId);
|
||||
|
||||
expect(input!.id).toEqual('input1' as ComponentId);
|
||||
expect(text!.properties.rawValue).toEqual({
|
||||
value: {
|
||||
raw: "pre {{(function () {\n const object = { value: input1.value + input1.notExistKey };\n return '-' + object.value + '-';\n}());}} end",
|
||||
format: 'plain',
|
||||
},
|
||||
string: 'Please input here',
|
||||
expressionString: "{{ 'input' }}",
|
||||
array: ['input'],
|
||||
expressionArray: "{{['input']}}",
|
||||
object: { input: 'input' },
|
||||
expressionObject: "{{{'input': 'input'}}}",
|
||||
});
|
||||
expect(
|
||||
button!.traits.find(
|
||||
trait => trait.type === `${CORE_VERSION}/${CoreTraitName.Event}`
|
||||
)!.properties.rawValue
|
||||
).toEqual({
|
||||
handlers: [
|
||||
{
|
||||
type: 'onClick',
|
||||
componentId: 'input1',
|
||||
method: {
|
||||
name: 'setInputValue',
|
||||
parameters: {
|
||||
value: 'Hello',
|
||||
},
|
||||
},
|
||||
disabled: false,
|
||||
wait: {
|
||||
type: 'delay',
|
||||
time: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -185,3 +185,128 @@ export const EventHandlerMockSchema: ComponentSchema[] = [
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const ChangeIdMockSchema: ComponentSchema[] = [
|
||||
{
|
||||
id: 'stack',
|
||||
type: 'core/v1/stack',
|
||||
properties: {
|
||||
spacing: 12,
|
||||
direction: 'horizontal',
|
||||
align: 'auto',
|
||||
wrap: '',
|
||||
justify: '',
|
||||
},
|
||||
traits: [],
|
||||
},
|
||||
{
|
||||
id: 'button',
|
||||
type: 'chakra_ui/v1/button',
|
||||
properties: {
|
||||
text: {
|
||||
raw: 'text',
|
||||
format: 'plain',
|
||||
},
|
||||
isLoading: false,
|
||||
colorScheme: 'blue',
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/event',
|
||||
properties: {
|
||||
handlers: [
|
||||
{
|
||||
type: 'onClick',
|
||||
componentId: 'input',
|
||||
method: {
|
||||
name: 'setInputValue',
|
||||
parameters: {
|
||||
value: 'Hello',
|
||||
},
|
||||
},
|
||||
disabled: false,
|
||||
wait: {
|
||||
type: 'delay',
|
||||
time: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: {
|
||||
container: {
|
||||
id: 'stack',
|
||||
slot: 'content',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'text',
|
||||
type: 'core/v1/text',
|
||||
properties: {
|
||||
value: {
|
||||
raw: "pre {{(function () {\n const object = { value: input.value + input.notExistKey };\n return '-' + object.value + '-';\n}());}} end",
|
||||
format: 'plain',
|
||||
},
|
||||
string: 'Please input here',
|
||||
expressionString: "{{ 'input' }}",
|
||||
array: ['input'],
|
||||
expressionArray: "{{['input']}}",
|
||||
object: { input: 'input' },
|
||||
expressionObject: "{{{'input': 'input'}}}",
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/style',
|
||||
properties: {
|
||||
styles: [
|
||||
{
|
||||
styleSlot: 'content',
|
||||
style: '',
|
||||
cssProperties: {
|
||||
padding: '8px 0px 9px 0px ',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: {
|
||||
container: {
|
||||
id: 'stack',
|
||||
slot: 'content',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'input',
|
||||
type: 'chakra_ui/v1/input',
|
||||
properties: {
|
||||
variant: 'outline',
|
||||
placeholder: 'Please input value',
|
||||
size: 'md',
|
||||
focusBorderColor: '',
|
||||
isDisabled: false,
|
||||
isRequired: false,
|
||||
defaultValue: '',
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: {
|
||||
container: {
|
||||
id: 'stack',
|
||||
slot: 'content',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -48,6 +48,7 @@
|
||||
"acorn-walk": "^8.2.0",
|
||||
"ajv": "^8.8.2",
|
||||
"codemirror": "^5.63.3",
|
||||
"escodegen": "^2.0.0",
|
||||
"formik": "^2.2.9",
|
||||
"framer-motion": "^4",
|
||||
"immer": "^9.0.6",
|
||||
@ -65,6 +66,7 @@
|
||||
"@sunmao-ui/vite-plugins": "^1.0.2",
|
||||
"@swc/core": "^1.2.121",
|
||||
"@types/codemirror": "^5.60.5",
|
||||
"@types/escodegen": "^0.0.7",
|
||||
"@types/json-schema": "^7.0.7",
|
||||
"@types/lodash-es": "^4.17.5",
|
||||
"@types/tern": "^0.23.4",
|
||||
|
@ -11,9 +11,11 @@ import {
|
||||
SlotName,
|
||||
} from './IAppModel';
|
||||
import { genComponent } from './utils';
|
||||
import mitt from 'mitt';
|
||||
|
||||
export class AppModel implements IAppModel {
|
||||
topComponents: IComponentModel[] = [];
|
||||
emitter: IAppModel['emitter'] = mitt();
|
||||
// modules: IModuleModel[] = [];
|
||||
private schema: ComponentSchema[] = [];
|
||||
private componentMap: Record<ComponentId, IComponentModel> = {};
|
||||
@ -66,7 +68,7 @@ export class AppModel implements IAppModel {
|
||||
|
||||
createComponent(type: ComponentType, id?: ComponentId): IComponentModel {
|
||||
const component = genComponent(this.registry, type, id || this.genId(type));
|
||||
return new ComponentModel(this, component, this.registry);
|
||||
return new ComponentModel(component, this.registry, this);
|
||||
}
|
||||
|
||||
getComponentById(componentId: ComponentId): IComponentModel | undefined {
|
||||
@ -109,7 +111,7 @@ export class AppModel implements IAppModel {
|
||||
if (this.componentMap[c.id as ComponentId]) {
|
||||
throw new Error(`Duplicate component id: ${c.id}`);
|
||||
} else {
|
||||
const comp = new ComponentModel(this, c, this.registry);
|
||||
const comp = new ComponentModel(c, this.registry, this);
|
||||
this.componentMap[c.id as ComponentId] = comp;
|
||||
return comp;
|
||||
}
|
||||
|
@ -42,9 +42,9 @@ export class ComponentModel implements IComponentModel {
|
||||
_isDirty = false;
|
||||
|
||||
constructor(
|
||||
public appModel: IAppModel,
|
||||
private schema: ComponentSchema,
|
||||
private registry: RegistryInterface
|
||||
private registry: RegistryInterface,
|
||||
public appModel: IAppModel
|
||||
) {
|
||||
this.schema = schema;
|
||||
|
||||
@ -52,11 +52,18 @@ export class ComponentModel implements IComponentModel {
|
||||
this.type = schema.type as ComponentType;
|
||||
this.spec = this.registry.getComponentByType(this.type) as any;
|
||||
|
||||
this.traits = schema.traits.map(t => new TraitModel(t, this, this.registry));
|
||||
this.traits = schema.traits.map(
|
||||
t => new TraitModel(t, this.registry, this.appModel, this)
|
||||
);
|
||||
this.genStateExample();
|
||||
this.parentId = this._slotTrait?.rawProperties.container.id;
|
||||
this.parentSlot = this._slotTrait?.rawProperties.container.slot;
|
||||
this.properties = new FieldModel(schema.properties);
|
||||
this.properties = new FieldModel(
|
||||
schema.properties,
|
||||
this.spec.spec.properties,
|
||||
this.appModel,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
get slots() {
|
||||
@ -151,7 +158,7 @@ export class ComponentModel implements IComponentModel {
|
||||
|
||||
addTrait(traitType: TraitType, properties: Record<string, unknown>): ITraitModel {
|
||||
const traitSchema = genTrait(traitType, properties);
|
||||
const trait = new TraitModel(traitSchema, this, this.registry);
|
||||
const trait = new TraitModel(traitSchema, this.registry, this.appModel, this);
|
||||
this.traits.push(trait);
|
||||
this._isDirty = true;
|
||||
this.genStateExample();
|
||||
@ -205,24 +212,23 @@ export class ComponentModel implements IComponentModel {
|
||||
changeId(newId: ComponentId) {
|
||||
const oldId = this.id;
|
||||
const isIdExist = !!this.appModel.getComponentById(newId);
|
||||
|
||||
if (isIdExist) {
|
||||
throw Error(`Id ${newId} already exist`);
|
||||
}
|
||||
|
||||
this.id = newId;
|
||||
for (const slot in this.children) {
|
||||
const slotChildren = this.children[slot as SlotName];
|
||||
|
||||
slotChildren.forEach(child => {
|
||||
child.parentId = newId;
|
||||
const slotTrait = child.traits.find(t => t.type === SlotTraitType);
|
||||
if (slotTrait) {
|
||||
slotTrait.properties.update({ container: { id: newId, slot } });
|
||||
slotTrait._isDirty = true;
|
||||
}
|
||||
child._isDirty = true;
|
||||
});
|
||||
}
|
||||
this._isDirty = true;
|
||||
this.appModel.changeComponentMapId(oldId, newId);
|
||||
this.appModel.emitter.emit('idChange', { oldId, newId });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,38 @@
|
||||
import { parseExpression } from '@sunmao-ui/shared';
|
||||
import { parseExpression, expChunkToString, SpecOptions } from '@sunmao-ui/shared';
|
||||
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';
|
||||
import {
|
||||
ComponentId,
|
||||
IAppModel,
|
||||
IComponentModel,
|
||||
ITraitModel,
|
||||
IFieldModel,
|
||||
ModuleId,
|
||||
RefInfo,
|
||||
ASTNode,
|
||||
AppModelEventType,
|
||||
} from './IAppModel';
|
||||
import escodegen from 'escodegen';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
|
||||
export class FieldModel implements IFieldModel {
|
||||
isDynamic = false;
|
||||
refs: Record<ComponentId | ModuleId, string[]> = {};
|
||||
refComponentInfos: Record<ComponentId | ModuleId, RefInfo> = {};
|
||||
private astNodes: Record<string, ASTNode> = {};
|
||||
private value: unknown | Array<IFieldModel> | Record<string, IFieldModel>;
|
||||
|
||||
constructor(value: unknown) {
|
||||
constructor(
|
||||
value: unknown,
|
||||
public spec?: JSONSchema7 & SpecOptions,
|
||||
private appModel?: IAppModel,
|
||||
private componentModel?: IComponentModel,
|
||||
private traitModel?: ITraitModel
|
||||
) {
|
||||
this.update(value);
|
||||
this.appModel?.emitter.on('idChange', this.onReferenceIdChange.bind(this));
|
||||
}
|
||||
|
||||
get rawValue() {
|
||||
@ -47,7 +67,15 @@ export class FieldModel implements IFieldModel {
|
||||
(oldValue as FieldModel).updateValue(value[key], false);
|
||||
newValue = oldValue;
|
||||
} else {
|
||||
newValue = new FieldModel(value[key]);
|
||||
newValue = new FieldModel(
|
||||
value[key],
|
||||
(this.spec?.properties?.[key] || this.spec?.items) as
|
||||
| (JSONSchema7 & SpecOptions)
|
||||
| undefined,
|
||||
this.appModel,
|
||||
this.componentModel,
|
||||
this.traitModel
|
||||
);
|
||||
}
|
||||
|
||||
if (isArray(result)) {
|
||||
@ -109,23 +137,44 @@ export class FieldModel implements IFieldModel {
|
||||
parseExpression(this.value as string).filter(exp => typeof exp !== 'string')
|
||||
);
|
||||
|
||||
this.refComponentInfos = {};
|
||||
this.astNodes = {};
|
||||
|
||||
exps.forEach(exp => {
|
||||
let lastIdentifier: ComponentId = '' as ComponentId;
|
||||
simpleWalk((acornLoose as typeof acorn).parse(exp, { ecmaVersion: 2020 }), {
|
||||
Expression: node => {
|
||||
switch (node.type) {
|
||||
const node = (acornLoose as typeof acorn).parse(exp, { ecmaVersion: 2020 });
|
||||
|
||||
this.astNodes[exp] = node as ASTNode;
|
||||
|
||||
simpleWalk(node, {
|
||||
Expression: expressionNode => {
|
||||
switch (expressionNode.type) {
|
||||
case 'Identifier':
|
||||
const key = exp.slice(node.start, node.end) as ComponentId;
|
||||
this.refs[key] = [];
|
||||
const key = exp.slice(
|
||||
expressionNode.start,
|
||||
expressionNode.end
|
||||
) as ComponentId;
|
||||
|
||||
if (this.refComponentInfos[key]) {
|
||||
this.refComponentInfos[key].componentIdASTNodes.push(
|
||||
expressionNode as ASTNode
|
||||
);
|
||||
} else {
|
||||
this.refComponentInfos[key] = {
|
||||
componentIdASTNodes: [expressionNode as ASTNode],
|
||||
refProperties: [],
|
||||
};
|
||||
}
|
||||
lastIdentifier = key;
|
||||
|
||||
break;
|
||||
case 'MemberExpression':
|
||||
const str = exp.slice(node.start, node.end);
|
||||
const str = exp.slice(expressionNode.start, expressionNode.end);
|
||||
let path = str.replace(lastIdentifier, '');
|
||||
if (path.startsWith('.')) {
|
||||
path = path.slice(1, path.length);
|
||||
}
|
||||
this.refs[lastIdentifier]?.push(path);
|
||||
this.refComponentInfos[lastIdentifier]?.refProperties.push(path);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@ -133,4 +182,50 @@ export class FieldModel implements IFieldModel {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private onReferenceIdChange({ oldId, newId }: AppModelEventType['idChange']) {
|
||||
if (!this.componentModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.spec?.isComponentId && this.value === oldId) {
|
||||
// the normal string property but the `isComponentId` which like the event trait's `componentId` property
|
||||
// just simply change its value
|
||||
if (this.traitModel) {
|
||||
this.traitModel._isDirty = true;
|
||||
}
|
||||
this.componentModel._isDirty = true;
|
||||
this.update(newId);
|
||||
} else if (this.refComponentInfos[oldId]) {
|
||||
// the component vars in the expressions
|
||||
// change the AST nodes values of the related component vars
|
||||
// then generate the new expression
|
||||
const exps = parseExpression(this.value as string);
|
||||
const newExps = exps.map(exp => {
|
||||
const node = this.astNodes[exp.toString()];
|
||||
|
||||
if (node) {
|
||||
const ref = this.refComponentInfos[oldId];
|
||||
|
||||
ref.componentIdASTNodes.forEach(refNode => {
|
||||
refNode.name = newId;
|
||||
});
|
||||
|
||||
this.refComponentInfos[newId] = ref;
|
||||
delete this.refComponentInfos[oldId];
|
||||
|
||||
return [escodegen.generate(node)];
|
||||
}
|
||||
|
||||
return exp;
|
||||
});
|
||||
const value = expChunkToString(newExps);
|
||||
|
||||
if (this.traitModel) {
|
||||
this.traitModel._isDirty = true;
|
||||
}
|
||||
this.componentModel._isDirty = true;
|
||||
this.update(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,10 @@ import {
|
||||
MethodSchema,
|
||||
RuntimeTrait,
|
||||
} from '@sunmao-ui/core';
|
||||
import { SpecOptions } from '@sunmao-ui/shared';
|
||||
import { Emitter } from 'mitt';
|
||||
import { Node } from 'acorn';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
|
||||
export type ComponentId = string & {
|
||||
kind: 'componentId';
|
||||
@ -36,7 +40,12 @@ export type EventName = string & {
|
||||
kind: 'eventName';
|
||||
};
|
||||
|
||||
export type AppModelEventType = {
|
||||
idChange: { oldId: ComponentId; newId: ComponentId };
|
||||
};
|
||||
|
||||
export interface IAppModel {
|
||||
emitter: Emitter<AppModelEventType>;
|
||||
topComponents: IComponentModel[];
|
||||
// modules: IModuleModel[];
|
||||
moduleIds: ModuleId[];
|
||||
@ -116,14 +125,22 @@ export interface ITraitModel {
|
||||
updateProperty: (key: string, value: any) => void;
|
||||
}
|
||||
|
||||
export type ASTNode = Node & { name: string };
|
||||
|
||||
export type RefInfo = {
|
||||
componentIdASTNodes: ASTNode[];
|
||||
refProperties: string[];
|
||||
};
|
||||
|
||||
export interface IFieldModel {
|
||||
// value: any;
|
||||
spec?: JSONSchema7 & SpecOptions;
|
||||
isDynamic: boolean;
|
||||
rawValue: any;
|
||||
update: (value: unknown) => void;
|
||||
getProperty: (key: string) => IFieldModel | void;
|
||||
getValue: () => unknown | void | IFieldModel;
|
||||
traverse: (cb: (f: IFieldModel, key: string) => void) => void;
|
||||
rawValue: any;
|
||||
// ids of used components in the expression
|
||||
refs: Record<ComponentId | ModuleId, string[]>;
|
||||
refComponentInfos: Record<ComponentId | ModuleId, RefInfo>;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
ITraitModel,
|
||||
IFieldModel,
|
||||
TraitId,
|
||||
IAppModel,
|
||||
} from './IAppModel';
|
||||
import { FieldModel } from './FieldModel';
|
||||
import { genTrait } from './utils';
|
||||
@ -22,8 +23,9 @@ export class TraitModel implements ITraitModel {
|
||||
|
||||
constructor(
|
||||
trait: TraitSchema,
|
||||
public parent: IComponentModel,
|
||||
private registry: RegistryInterface
|
||||
private registry: RegistryInterface,
|
||||
private appModel: IAppModel,
|
||||
public parent: IComponentModel
|
||||
) {
|
||||
this.schema = trait;
|
||||
this.parent = parent;
|
||||
@ -31,7 +33,13 @@ export class TraitModel implements ITraitModel {
|
||||
this.id = `${this.parent.id}_trait${traitIdCount++}` as TraitId;
|
||||
this.spec = this.registry.getTraitByType(this.type);
|
||||
|
||||
this.properties = new FieldModel(trait.properties);
|
||||
this.properties = new FieldModel(
|
||||
trait.properties,
|
||||
this.spec.spec.properties,
|
||||
this.appModel,
|
||||
this.parent,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
get rawProperties() {
|
||||
|
@ -136,7 +136,8 @@ export class SchemaValidator implements ISchemaValidator {
|
||||
.addKeyword('category')
|
||||
.addKeyword('widgetOptions')
|
||||
.addKeyword('conditions')
|
||||
.addKeyword('name');
|
||||
.addKeyword('name')
|
||||
.addKeyword('isComponentId');
|
||||
|
||||
this.validatorMap = {
|
||||
components: {},
|
||||
|
@ -67,9 +67,9 @@ class ExpressionValidatorRule implements PropertiesValidatorRule {
|
||||
|
||||
// validate expression
|
||||
properties.traverse((fieldModel, key) => {
|
||||
Object.keys(fieldModel.refs).forEach((id: string) => {
|
||||
Object.keys(fieldModel.refComponentInfos).forEach((id: string) => {
|
||||
const targetComponent = appModel.getComponentById(id as ComponentId);
|
||||
const paths = fieldModel.refs[id as ComponentId];
|
||||
const paths = fieldModel.refComponentInfos[id as ComponentId].refProperties;
|
||||
|
||||
if (targetComponent) {
|
||||
// case 1: id is a component
|
||||
|
@ -4,7 +4,7 @@ import { implementRuntimeTrait } from '../../utils/buildKit';
|
||||
|
||||
const ContainerPropertySpec = Type.Object(
|
||||
{
|
||||
id: Type.String(),
|
||||
id: Type.String({ isComponentId: true }),
|
||||
slot: Type.String(),
|
||||
},
|
||||
// don't show this property in the editor
|
||||
|
@ -4,6 +4,7 @@ import { CORE_VERSION, CoreWidgetName } from '../constants/core';
|
||||
const BaseEventSpecObject = {
|
||||
componentId: Type.String({
|
||||
title: 'Component ID',
|
||||
isComponentId: true,
|
||||
}),
|
||||
method: Type.Object(
|
||||
{
|
||||
|
@ -11,4 +11,6 @@ export type SpecOptions<WidgetOptions = Record<string, any>> = {
|
||||
name?: string;
|
||||
// conditional render
|
||||
conditions?: Condition[];
|
||||
// is a reference of component id
|
||||
isComponentId?: boolean;
|
||||
};
|
||||
|
@ -82,3 +82,15 @@ export const parseExpression = (rawExp: string, parseListItem = false): ExpChunk
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const expChunkToString = (exps: ExpChunk[]): string => {
|
||||
return exps
|
||||
.map(expOrExpChunk => {
|
||||
if (expOrExpChunk instanceof Array) {
|
||||
return `{{${expChunkToString(expOrExpChunk)}}}`;
|
||||
}
|
||||
|
||||
return expOrExpChunk;
|
||||
})
|
||||
.join('');
|
||||
};
|
||||
|
@ -3594,6 +3594,11 @@
|
||||
dependencies:
|
||||
"@types/ms" "*"
|
||||
|
||||
"@types/escodegen@^0.0.7":
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/escodegen/-/escodegen-0.0.7.tgz#a1c3e3dfd76da89f01d7d196eebe227ebe4b6eec"
|
||||
integrity sha512-46oENdSRNEJXCNrPJoC3vRolZJpfeEm7yvATkd2bCncKFG0PUEyfBCaoacfpcXH4Y5RRuqdVj3J7TI+wwn2SbQ==
|
||||
|
||||
"@types/estree@*":
|
||||
version "0.0.50"
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz"
|
||||
@ -5965,7 +5970,7 @@ escape-string-regexp@^4.0.0:
|
||||
|
||||
escodegen@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd"
|
||||
integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==
|
||||
dependencies:
|
||||
esprima "^4.0.1"
|
||||
|
Loading…
x
Reference in New Issue
Block a user