mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-04-06 21:40:23 +08:00
feat(editor): chagne the component id reference properties' values when changing the component id
add the `isComponentId` into spec options to declare a property is whether refer to a component id ISSUES CLOSED: #106
This commit is contained in:
parent
d85ed7dc53
commit
b6b7083e15
@ -3,6 +3,7 @@ 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', () => {
|
||||
@ -82,6 +83,7 @@ describe('Field test', () => {
|
||||
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);
|
||||
|
||||
@ -92,5 +94,27 @@ describe('Field test', () => {
|
||||
format: 'plain',
|
||||
},
|
||||
});
|
||||
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,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -187,6 +187,63 @@ 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',
|
||||
@ -196,7 +253,31 @@ export const ChangeIdMockSchema: ComponentSchema[] = [
|
||||
format: 'plain',
|
||||
},
|
||||
},
|
||||
traits: [],
|
||||
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',
|
||||
@ -210,6 +291,16 @@ export const ChangeIdMockSchema: ComponentSchema[] = [
|
||||
isRequired: false,
|
||||
defaultValue: '',
|
||||
},
|
||||
traits: [],
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: {
|
||||
container: {
|
||||
id: 'stack',
|
||||
slot: 'content',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -58,7 +58,12 @@ export class ComponentModel implements IComponentModel {
|
||||
this.genStateExample();
|
||||
this.parentId = this._slotTrait?.rawProperties.container.id;
|
||||
this.parentSlot = this._slotTrait?.rawProperties.container.slot;
|
||||
this.properties = new FieldModel(schema.properties, this.appModel, this);
|
||||
this.properties = new FieldModel(
|
||||
schema.properties,
|
||||
this.spec.spec.properties,
|
||||
this.appModel,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
get slots() {
|
||||
@ -207,25 +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,4 +1,4 @@
|
||||
import { parseExpression, expChunkToString } 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';
|
||||
@ -8,11 +8,13 @@ import {
|
||||
ComponentId,
|
||||
IAppModel,
|
||||
IComponentModel,
|
||||
ITraitModel,
|
||||
IFieldModel,
|
||||
ModuleId,
|
||||
RefInfo,
|
||||
} from './IAppModel';
|
||||
import escodegen from 'escodegen';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
|
||||
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
|
||||
|
||||
@ -24,8 +26,10 @@ export class FieldModel implements IFieldModel {
|
||||
|
||||
constructor(
|
||||
value: unknown,
|
||||
public spec?: JSONSchema7 & SpecOptions,
|
||||
public appModel?: IAppModel,
|
||||
public componentModel?: IComponentModel
|
||||
public componentModel?: IComponentModel,
|
||||
public traitModel?: ITraitModel
|
||||
) {
|
||||
this.update(value);
|
||||
this.appModel?.emitter.on('idChange', this.onReferenceIdChange.bind(this));
|
||||
@ -63,7 +67,15 @@ export class FieldModel implements IFieldModel {
|
||||
(oldValue as FieldModel).updateValue(value[key], false);
|
||||
newValue = oldValue;
|
||||
} else {
|
||||
newValue = new FieldModel(value[key], this.appModel, this.componentModel);
|
||||
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)) {
|
||||
@ -170,32 +182,40 @@ export class FieldModel implements IFieldModel {
|
||||
}
|
||||
|
||||
onReferenceIdChange({ oldId, newId }: { oldId: ComponentId; newId: ComponentId }) {
|
||||
if (!(this.refs[oldId] && this.componentModel)) {
|
||||
if (!this.componentModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exps = parseExpression(this.value as string);
|
||||
const newExps = exps.map(exp => {
|
||||
const node = this.nodes[exp.toString()];
|
||||
|
||||
if (node) {
|
||||
const ref = this.refs[oldId];
|
||||
|
||||
ref.nodes.forEach(refNode => {
|
||||
refNode.name = newId;
|
||||
});
|
||||
|
||||
this.refs[newId] = ref;
|
||||
delete this.refs[oldId];
|
||||
|
||||
return [escodegen.generate(node)];
|
||||
if (this.spec?.isComponentId && this.value === oldId) {
|
||||
if (this.traitModel) {
|
||||
this.traitModel._isDirty = true;
|
||||
}
|
||||
this.componentModel._isDirty = true;
|
||||
this.update(newId);
|
||||
} else if (this.refs[oldId]) {
|
||||
const exps = parseExpression(this.value as string);
|
||||
const newExps = exps.map(exp => {
|
||||
const node = this.nodes[exp.toString()];
|
||||
|
||||
return exp;
|
||||
});
|
||||
const value = expChunkToString(newExps);
|
||||
if (node) {
|
||||
const ref = this.refs[oldId];
|
||||
|
||||
this.componentModel._isDirty = true;
|
||||
this.update(value);
|
||||
ref.nodes.forEach(refNode => {
|
||||
refNode.name = newId;
|
||||
});
|
||||
|
||||
this.refs[newId] = ref;
|
||||
delete this.refs[oldId];
|
||||
|
||||
return [escodegen.generate(node)];
|
||||
}
|
||||
|
||||
return exp;
|
||||
});
|
||||
const value = expChunkToString(newExps);
|
||||
|
||||
this.componentModel._isDirty = true;
|
||||
this.update(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +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';
|
||||
@ -133,6 +135,7 @@ export interface IFieldModel {
|
||||
// value: any;
|
||||
appModel?: IAppModel;
|
||||
componentModel?: IComponentModel;
|
||||
spec?: JSONSchema7 & SpecOptions;
|
||||
isDynamic: boolean;
|
||||
rawValue: any;
|
||||
update: (value: unknown) => void;
|
||||
|
@ -33,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.appModel, this.parent);
|
||||
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: {},
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user