Merge branch 'develop' of github.com:webzard-io/sunmao-ui into fix/codeeditor

This commit is contained in:
xzdry 2022-11-01 09:26:38 +08:00
commit 03aad6c651
8 changed files with 151 additions and 38 deletions

View File

@ -1,19 +1,47 @@
import { AppModel } from '../../src/AppModel/AppModel';
import { ComponentId } from '../../src/AppModel/IAppModel';
import { registry } from '../services';
import { AppSchema } from './mock';
import { AppSchema, PasteComponentWithChildrenSchema } from './mock';
import { genOperation } from '../../src/operations';
describe('Component operations', ()=> {
it('Change component id', ()=> {
const appModel = new AppModel(AppSchema.spec.components, registry);
describe('Component operations', () => {
it('Change component id', () => {
const appModel = new AppModel(AppSchema.spec.components, registry);
expect(appModel.getComponentById('text1' as ComponentId).id).toBe('text1');
const operation = genOperation(registry, 'modifyComponentId', {
componentId: 'text1',
newId: 'text2',
});
operation.do(appModel);
expect(appModel.getComponentById('text2' as ComponentId).id).toBe('text2');
})
})
expect(appModel.getComponentById('text1' as ComponentId)?.id).toBe('text1');
const operation = genOperation(registry, 'modifyComponentId', {
componentId: 'text1',
newId: 'text2',
});
operation.do(appModel);
expect(appModel.getComponentById('text2' as ComponentId)?.id).toBe('text2');
});
it(`Paste component with children and the children's slot trait is correct`, () => {
const appModel = new AppModel(
PasteComponentWithChildrenSchema.spec.components,
registry
);
const pasteComponent = [
appModel.getComponentById('stack5' as ComponentId)!.toSchema(),
appModel.getComponentById('text6' as ComponentId)!.toSchema(),
];
const operation = genOperation(registry, 'pasteComponent', {
parentId: 'stack3',
slot: 'content',
component: new AppModel(pasteComponent, registry).getComponentById(
'stack5' as ComponentId
)!,
copyTimes: 0,
});
operation.do(appModel);
expect(appModel.getComponentById('text6_copy0' as ComponentId)?.parent?.id).toBe(
'stack5_copy0'
);
expect(
appModel.getComponentById('text6_copy0' as ComponentId)?.traits[0].rawProperties
.container.id
).toBe('stack5_copy0');
});
});

View File

@ -15,3 +15,71 @@ export const AppSchema: Application = {
],
},
};
export const PasteComponentWithChildrenSchema: Application = {
version: 'sunmao/v1',
kind: 'Application',
metadata: {
name: 'some App',
},
spec: {
components: [
{
id: 'stack3',
type: 'core/v1/stack',
properties: {
spacing: 12,
direction: 'horizontal',
align: 'auto',
wrap: false,
justify: 'flex-start',
},
traits: [],
},
{
id: 'stack5',
type: 'core/v1/stack',
properties: {
spacing: 12,
direction: 'horizontal',
align: 'auto',
wrap: false,
justify: 'flex-start',
},
traits: [
{
type: 'core/v1/slot',
properties: {
container: {
id: 'stack3',
slot: 'content',
},
ifCondition: true,
},
},
],
},
{
id: 'text6',
type: 'core/v1/text',
properties: {
value: {
raw: 'text',
format: 'plain',
},
},
traits: [
{
type: 'core/v1/slot',
properties: {
container: {
id: 'stack5',
slot: 'content',
},
ifCondition: true,
},
},
],
},
],
},
};

View File

@ -7,11 +7,13 @@ import {
ComponentType,
IAppModel,
IComponentModel,
IFieldModel,
ModuleId,
SlotName,
} from './IAppModel';
import { genComponent } from './utils';
import mitt from 'mitt';
import { forEach } from 'lodash';
export class AppModel implements IAppModel {
topComponents: IComponentModel[] = [];
@ -143,8 +145,35 @@ export class AppModel implements IAppModel {
});
}
}
this.topComponents.forEach(parent => {
traverse(parent);
if (this.topComponents.length) {
this.topComponents.forEach(parent => {
traverse(parent);
});
} else {
// When all the components have slot trait, there is no topComponents.
// Just iterate them in order.
Object.values(this.componentMap).forEach(c => {
cb(c);
});
}
}
traverseAllFields(cb: (f: IFieldModel) => void) {
function traverseField(field: IFieldModel) {
cb(field);
const value = field.getValue();
if (typeof value === 'object') {
forEach(value, childField => {
traverseField(childField);
});
}
}
this.traverseTree(c => {
traverseField(c.properties);
c.traits.forEach(t => {
traverseField(t.properties);
});
});
}
}

View File

@ -68,18 +68,11 @@ 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.registry, this.appModel, this)
);
this.traits = schema.traits.map(t => new TraitModel(t, this.registry, this));
this.genStateExample();
this.parentId = this._slotTrait?.rawProperties.container.id;
this.parentSlot = this._slotTrait?.rawProperties.container.slot;
this.properties = new FieldModel(
schema.properties,
this.spec.spec.properties,
this.appModel,
this
);
this.properties = new FieldModel(schema.properties, this, this.spec.spec.properties);
}
get slots() {
@ -174,7 +167,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.registry, this.appModel, this);
const trait = new TraitModel(traitSchema, this.registry, this);
this.traits.push(trait);
this._isDirty = true;
this.genStateExample();
@ -243,7 +236,7 @@ export class ComponentModel implements IComponentModel {
}
this._isDirty = true;
this.appModel.changeComponentMapId(oldId, newId);
this.appModel.emitter.emit('idChange', { oldId, newId });
this.appModel.traverseAllFields(field => field.changeReferenceId(oldId, newId));
return this;
}

View File

@ -7,14 +7,12 @@ import { flattenDeep, isArray, isObject } from 'lodash';
import { isExpression } from '../validator/utils';
import {
ComponentId,
IAppModel,
IComponentModel,
ITraitModel,
IFieldModel,
ModuleId,
RefInfo,
ASTNode,
AppModelEventType,
} from './IAppModel';
import escodegen from 'escodegen';
import { JSONSchema7 } from 'json-schema';
@ -38,13 +36,11 @@ export class FieldModel implements IFieldModel {
constructor(
value: unknown,
public spec?: JSONSchema7 & CustomOptions,
private appModel?: IAppModel,
private componentModel?: IComponentModel,
public spec?: JSONSchema7 & CustomOptions,
private traitModel?: ITraitModel
) {
this.update(value);
this.appModel?.emitter.on('idChange', this.onReferenceIdChange.bind(this));
}
get rawValue() {
@ -81,11 +77,10 @@ export class FieldModel implements IFieldModel {
} else {
newValue = new FieldModel(
value[key],
this.componentModel,
(this.spec?.properties?.[key] || this.spec?.items) as
| (JSONSchema7 & CustomOptions)
| undefined,
this.appModel,
this.componentModel,
this.traitModel
);
}
@ -255,7 +250,7 @@ export class FieldModel implements IFieldModel {
return path.slice(1).join('.');
}
private onReferenceIdChange({ oldId, newId }: AppModelEventType['idChange']) {
changeReferenceId(oldId: ComponentId, newId: ComponentId) {
if (!this.componentModel) {
return;
}

View File

@ -62,6 +62,7 @@ export interface IAppModel {
changeComponentMapId(oldId: ComponentId, newId: ComponentId): void;
_bindComponentToModel(component: IComponentModel): void;
traverseTree(cb: (c: IComponentModel) => void): void;
traverseAllFields(cb: (f: IFieldModel) => void): void;
}
export interface IModuleModel {
@ -145,4 +146,5 @@ export interface IFieldModel {
traverse: (cb: (f: IFieldModel, key: string) => void) => void;
// ids of used components in the expression
refComponentInfos: Record<ComponentId | ModuleId, RefInfo>;
changeReferenceId(oldId: ComponentId, newId: ComponentId): void;
}

View File

@ -6,7 +6,6 @@ import {
ITraitModel,
IFieldModel,
TraitId,
IAppModel,
} from './IAppModel';
import { FieldModel } from './FieldModel';
import { genTrait } from './utils';
@ -24,7 +23,6 @@ export class TraitModel implements ITraitModel {
constructor(
trait: TraitSchema,
private registry: RegistryInterface,
private appModel: IAppModel,
public parent: IComponentModel
) {
this.schema = trait;
@ -35,9 +33,8 @@ export class TraitModel implements ITraitModel {
this.properties = new FieldModel(
trait.properties,
this.spec.spec.properties,
this.appModel,
this.parent,
this.spec.spec.properties,
this
);
}

View File

@ -80,6 +80,7 @@ export class EditorStore {
setComponents: action,
setHoverComponentId: action,
setDragOverComponentId: action,
setHoverComponentId: action,
});
this.eventBus.on('selectComponent', id => {