mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2024-11-21 03:15:49 +08:00
add model operations
This commit is contained in:
parent
5c59f7d88c
commit
c7cec729c0
@ -127,8 +127,8 @@ const PropsSchema = Type.Object({
|
||||
focusBorderColor: Type.Optional(Type.String()),
|
||||
isDisabled: Type.Optional(Type.Boolean()),
|
||||
isRequired: Type.Optional(Type.Boolean()),
|
||||
left: AppendElementPropertySchema,
|
||||
right: AppendElementPropertySchema,
|
||||
left: Type.Optional(AppendElementPropertySchema),
|
||||
right: Type.Optional(AppendElementPropertySchema),
|
||||
defaultValue: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { Application, ApplicationComponent } from '@sunmao-ui/core';
|
||||
import { ApplicationFixture } from '../../__fixture__/application';
|
||||
import { AdjustComponentOrderLeafOperation } from '../../src/operations/leaf/component/adjustComponentOrderLeafOperation';
|
||||
import { Application } from '@sunmao-ui/core';
|
||||
import { ApplicationModel } from '../../src/operations/AppModel/AppModel';
|
||||
import { ComponentType } from '../../src/operations/AppModel/IAppModel';
|
||||
import {
|
||||
ComponentId,
|
||||
ComponentType,
|
||||
SlotName,
|
||||
TraitType,
|
||||
} from '../../src/operations/AppModel/IAppModel';
|
||||
|
||||
const AppSchema: Application = {
|
||||
kind: 'Application',
|
||||
version: 'example/v1',
|
||||
@ -107,54 +111,82 @@ const AppSchema: Application = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'hstack3',
|
||||
type: 'chakra_ui/v1/hstack',
|
||||
properties: { spacing: '24px' },
|
||||
traits: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
describe('change component properties', () => {
|
||||
const appModel = new ApplicationModel(AppSchema.spec.components);
|
||||
const origin = appModel.json;
|
||||
const text1 = appModel.getComponentById('text1' as any);
|
||||
text1!.changeComponentProperty('value', { raw: 'hello', format: 'md' });
|
||||
const newSchema = appModel.json;
|
||||
|
||||
describe('change component properties', () => {
|
||||
const appModel = new ApplicationModel(AppSchema.spec.components);
|
||||
const origin = appModel.toJS();
|
||||
const text1 = appModel.getComponentById('text1' as any);
|
||||
text1!.changeComponentProperty('value', { raw: 'hello', format: 'md' });
|
||||
const newSchema = appModel.toJS();
|
||||
|
||||
it('change component properties', () => {
|
||||
expect(newSchema[2].properties.value).toEqual({ raw: 'hello', format: 'md' });
|
||||
it ('keep immutable after changing component properties', () => {
|
||||
expect(origin).not.toBe(newSchema);
|
||||
expect(origin[0]).toBe(newSchema[0]);
|
||||
expect(origin[1]).toBe(newSchema[1]);
|
||||
expect(origin[2]).not.toBe(newSchema[2]);
|
||||
})
|
||||
});
|
||||
|
||||
it('keep immutable after changing component properties', () => {
|
||||
expect(origin).not.toBe(newSchema);
|
||||
expect(origin[0]).toBe(newSchema[0]);
|
||||
expect(origin[1]).toBe(newSchema[1]);
|
||||
expect(origin[2]).not.toBe(newSchema[2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove component', () => {
|
||||
const appModel = new ApplicationModel(AppSchema.spec.components);
|
||||
const origin = appModel.toJS();
|
||||
appModel.removeComponent('text1' as any);
|
||||
const newSchema = appModel.toJS();
|
||||
it('remove component', () => {
|
||||
expect(origin.length - 1).toEqual(newSchema.length);
|
||||
expect(newSchema.some(c => c.id === 'text1')).toBe(false);
|
||||
});
|
||||
it('keep immutable after removing component', () => {
|
||||
expect(origin).not.toBe(newSchema);
|
||||
expect(origin[0]).toBe(newSchema[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create component', () => {
|
||||
const appModel = new ApplicationModel(AppSchema.spec.components);
|
||||
const origin = appModel.json;
|
||||
const origin = appModel.toJS();
|
||||
const newComponent = appModel.createComponent('core/v1/text' as ComponentType);
|
||||
expect(newComponent.id).toEqual('text5');
|
||||
it('create component', () => {
|
||||
expect(newComponent.id).toEqual('text5');
|
||||
});
|
||||
describe('append component to parent', () => {
|
||||
const parent = appModel.getComponentById('vstack1' as any)!;
|
||||
newComponent.appendTo('content' as any, parent);
|
||||
newComponent.appendTo(parent, 'content' as SlotName);
|
||||
expect(newComponent.parent).toBe(parent);
|
||||
expect(newComponent.parentId).toEqual('vstack1');
|
||||
expect(newComponent.parentSlot).toEqual('content');
|
||||
|
||||
it('create slot trait', () => {
|
||||
expect(newComponent.traits[0].type).toEqual('core/v1/slot');
|
||||
expect(newComponent.traits[0].properties).toEqual({
|
||||
expect(newComponent.traits[0].rawProperties).toEqual({
|
||||
container: { id: 'vstack1', slot: 'content' },
|
||||
});
|
||||
});
|
||||
it('update parent children', () => {
|
||||
expect(parent.children['content' as any]).toContain(newComponent);
|
||||
expect(newComponent.traits[0].properties).toEqual({
|
||||
expect(newComponent.traits[0].rawProperties).toEqual({
|
||||
container: { id: 'vstack1', slot: 'content' },
|
||||
});
|
||||
});
|
||||
it('update add model cache', () => {
|
||||
expect(appModel.allComponents[appModel.allComponents.length - 1]).toBe(newComponent);
|
||||
expect(appModel.allComponents[appModel.allComponents.length - 1]).toBe(
|
||||
newComponent
|
||||
);
|
||||
});
|
||||
it ('keep immutable after create component', () => {
|
||||
const newSchema = appModel.json;
|
||||
it('keep immutable after create component', () => {
|
||||
const newSchema = appModel.toJS();
|
||||
expect(origin).not.toBe(newSchema);
|
||||
expect(origin.length).toBe(newSchema.length - 1);
|
||||
expect(origin.every((v, i) => v === newSchema[i])).toBe(true);
|
||||
@ -163,6 +195,127 @@ describe('create component', () => {
|
||||
expect(newComponentSchema.traits[0].properties).toEqual({
|
||||
container: { id: 'vstack1', slot: 'content' },
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('append component', () => {
|
||||
const appModel = new ApplicationModel(AppSchema.spec.components);
|
||||
const origin = appModel.toJS();
|
||||
const text1 = appModel.getComponentById('text1' as any);
|
||||
it('append component to top level', () => {
|
||||
text1.appendTo()
|
||||
const newSchema = appModel.toJS();
|
||||
expect(newSchema[newSchema.length - 1].id).toBe('text1');
|
||||
expect(newSchema.length).toBe(origin.length);
|
||||
})
|
||||
})
|
||||
|
||||
describe('add trait', () => {
|
||||
const appModel = new ApplicationModel(AppSchema.spec.components);
|
||||
const origin = appModel.toJS();
|
||||
const text1 = appModel.getComponentById('text1' as any);
|
||||
text1!.addTrait('core/v1/state' as TraitType, { key: 'value' });
|
||||
const newSchema = appModel.toJS();
|
||||
|
||||
it('add trait', () => {
|
||||
expect(newSchema[2].traits[1].properties.key).toEqual('value');
|
||||
});
|
||||
it('keep immutable after adding trait', () => {
|
||||
expect(origin).not.toBe(newSchema);
|
||||
expect(origin[0]).toBe(newSchema[0]);
|
||||
expect(origin[1]).toBe(newSchema[1]);
|
||||
expect(origin[2]).not.toBe(newSchema[2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove trait', () => {
|
||||
const appModel = new ApplicationModel(AppSchema.spec.components);
|
||||
const origin = appModel.toJS();
|
||||
const text1 = appModel.getComponentById('text1' as any)!;
|
||||
text1!.removeTrait(text1.traits[0].id);
|
||||
const newSchema = appModel.toJS();
|
||||
it('remove trait', () => {
|
||||
expect(newSchema[2].traits.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('keep immutable after adding trait', () => {
|
||||
expect(origin).not.toBe(newSchema);
|
||||
expect(origin[0]).toBe(newSchema[0]);
|
||||
expect(origin[1]).toBe(newSchema[1]);
|
||||
expect(origin[2]).not.toBe(newSchema[2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('change component id', () => {
|
||||
const newId = 'newHstack1' as ComponentId;
|
||||
const appModel = new ApplicationModel(AppSchema.spec.components);
|
||||
const origin = appModel.toJS();
|
||||
const hStack1 = appModel.getComponentById('hstack1' as ComponentId)!;
|
||||
hStack1.changeId(newId);
|
||||
const newSchema = appModel.toJS();
|
||||
it('change component id', () => {
|
||||
expect(newSchema[0].id).toEqual(newId);
|
||||
});
|
||||
it('change children slot trait', () => {
|
||||
expect(newSchema[1].traits[0].properties.container).toEqual({
|
||||
id: newId,
|
||||
slot: 'content',
|
||||
});
|
||||
expect(newSchema[5].traits[0].properties.container).toEqual({
|
||||
id: newId,
|
||||
slot: 'content',
|
||||
});
|
||||
expect(newSchema[8].traits[0].properties.container).toEqual({
|
||||
id: newId,
|
||||
slot: 'content',
|
||||
});
|
||||
});
|
||||
|
||||
it('keep immutable after changing component id', () => {
|
||||
expect(origin).not.toBe(newSchema);
|
||||
expect(origin[0]).not.toBe(newSchema[0]);
|
||||
expect(origin[1]).not.toBe(newSchema[1]);
|
||||
expect(origin[2]).toBe(newSchema[2]);
|
||||
expect(origin[3]).toBe(newSchema[3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('move component', () => {
|
||||
const appModel = new ApplicationModel(AppSchema.spec.components);
|
||||
const origin = appModel.toJS();
|
||||
const hstack1 = appModel.getComponentById('hstack1' as ComponentId)!;
|
||||
const text1 = appModel.getComponentById('text1' as ComponentId)!;
|
||||
|
||||
it('can move component', () => {
|
||||
text1.moveAfter('text2' as ComponentId);
|
||||
const newSchema = appModel.toJS();
|
||||
expect(text1.parent!.children['content' as SlotName][0].id).toEqual('text2');
|
||||
expect(text1.parent!.children['content' as SlotName][1].id).toEqual('text1');
|
||||
expect(newSchema[2].id).toEqual('text2');
|
||||
expect(newSchema[3].id).toEqual('text1');
|
||||
});
|
||||
|
||||
it('can move top level component', () => {
|
||||
hstack1.moveAfter('hstack3' as ComponentId);
|
||||
const newSchema = appModel.toJS();
|
||||
expect(appModel.model[0].id).toEqual('hstack3');
|
||||
expect(appModel.model[1].id).toEqual('hstack1');
|
||||
expect(newSchema[8].id).toEqual('hstack3');
|
||||
expect(newSchema[9].id).toEqual('hstack1');
|
||||
});
|
||||
it('can move component to the first', () => {
|
||||
hstack1.moveAfter(null);
|
||||
const newSchema = appModel.toJS();
|
||||
expect(appModel.model[0].id).toEqual('hstack1');
|
||||
expect(newSchema[0].id).toEqual('hstack1');
|
||||
});
|
||||
|
||||
it('keep immutable after moving component', () => {
|
||||
const text2 = appModel.getComponentById('text2' as ComponentId)!;
|
||||
text2.moveAfter('text1' as ComponentId);
|
||||
const newSchema = appModel.toJS();
|
||||
expect(origin).not.toBe(newSchema);
|
||||
expect(origin.every((v, i) => v === newSchema[i])).toBe(true);
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { ApplicationComponent, ComponentTrait } from '@sunmao-ui/core';
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import { parseType } from '@sunmao-ui/runtime';
|
||||
import produce from 'immer';
|
||||
import { registry } from '../../setup';
|
||||
import { ComponentModel } from './ComponentModel';
|
||||
import {
|
||||
ComponentId,
|
||||
@ -9,14 +7,9 @@ import {
|
||||
IApplicationModel,
|
||||
IComponentModel,
|
||||
IModuleModel,
|
||||
ModuleId,
|
||||
ModuleType,
|
||||
SlotName,
|
||||
TraitType,
|
||||
} from './IAppModel';
|
||||
import { genComponent } from './utils';
|
||||
|
||||
const SlotTraitType: TraitType = 'core/v1/slot' as TraitType;
|
||||
export class ApplicationModel implements IApplicationModel {
|
||||
model: IComponentModel[] = [];
|
||||
modules: IModuleModel[] = [];
|
||||
@ -30,11 +23,46 @@ export class ApplicationModel implements IApplicationModel {
|
||||
}
|
||||
|
||||
updateSingleComponent(component: IComponentModel) {
|
||||
this.componentMap[component.id] = component;
|
||||
this.allComponents.push(component);
|
||||
this.componentMap[component.id] = component;
|
||||
}
|
||||
|
||||
resolveTree(components: ApplicationComponent[]) {
|
||||
toJS(): ApplicationComponent[] {
|
||||
this.schema = this.allComponents.map(c => {
|
||||
return c.toJS();
|
||||
});
|
||||
|
||||
return this.schema;
|
||||
}
|
||||
|
||||
createComponent(type: ComponentType, id?: ComponentId): IComponentModel {
|
||||
const component = genComponent(type, id || this.genId(type));
|
||||
return new ComponentModel(this, component);
|
||||
}
|
||||
|
||||
genId(type: ComponentType): ComponentId {
|
||||
const { name } = parseType(type);
|
||||
const componentsCount = this.allComponents.filter(
|
||||
component => component.type === type
|
||||
).length;
|
||||
return `${name}${componentsCount + 1}` as ComponentId;
|
||||
}
|
||||
|
||||
getComponentById(componentId: ComponentId): IComponentModel | undefined {
|
||||
return this.componentMap[componentId];
|
||||
}
|
||||
|
||||
removeComponent(componentId: ComponentId) {
|
||||
const comp = this.componentMap[componentId];
|
||||
delete this.componentMap[componentId];
|
||||
this.allComponents = this.allComponents.filter(c => c !== comp);
|
||||
if (comp.parentSlot && comp.parent) {
|
||||
const children = comp.parent.children[comp.parentSlot];
|
||||
comp.parent.children[comp.parentSlot] = children.filter(c => c !== comp);
|
||||
}
|
||||
}
|
||||
|
||||
private resolveTree(components: ApplicationComponent[]) {
|
||||
this.allComponents = components.map(c => {
|
||||
const comp = new ComponentModel(this, c);
|
||||
this.componentMap[c.id as ComponentId] = comp;
|
||||
@ -57,114 +85,4 @@ export class ApplicationModel implements IApplicationModel {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get json(): ApplicationComponent[] {
|
||||
// if (this.allComponents.length !== this.schema.length) {
|
||||
// return this.allComponents.map(c => c.json);
|
||||
// }
|
||||
|
||||
// if (this.allComponents.some(c => c.isDirty)) {
|
||||
// return this.allComponents.map(c => c.json);
|
||||
// }
|
||||
|
||||
// for (let i = 0; i < this.schema.length - 1; i++) {
|
||||
// const comp = this.schema[i];
|
||||
// const component = this.componentMap[comp.id as ComponentId];
|
||||
// if (component.isDirty) {
|
||||
// this.schema.splice(i + 1, 1, component.json);
|
||||
// }
|
||||
// }
|
||||
|
||||
this.schema = this.allComponents.map(c => {
|
||||
return c.json;
|
||||
});
|
||||
|
||||
return this.schema;
|
||||
}
|
||||
|
||||
createComponent(type: ComponentType): IComponentModel {
|
||||
const component = genComponent(type, this.genId(type), {}, []);
|
||||
return new ComponentModel(this, component);
|
||||
}
|
||||
|
||||
genId(type: ComponentType): ComponentId {
|
||||
const { name } = parseType(type);
|
||||
const componentsCount = this.allComponents.filter(
|
||||
component => component.type === type
|
||||
).length;
|
||||
return `${name}${componentsCount + 1}` as ComponentId;
|
||||
}
|
||||
|
||||
getComponentById(componentId: ComponentId): IComponentModel | undefined {
|
||||
return this.componentMap[componentId];
|
||||
}
|
||||
|
||||
findComponentIndex(componentId: ComponentId): number {
|
||||
return this.schema.findIndex(c => c.id === componentId);
|
||||
}
|
||||
|
||||
private updateSchema(schema: ApplicationComponent[]) {
|
||||
this.schema = schema;
|
||||
this.model = [];
|
||||
this.allComponents = [];
|
||||
this.componentMap = {};
|
||||
this.resolveTree(schema);
|
||||
}
|
||||
|
||||
// createComponent(
|
||||
// componentType: ComponentType,
|
||||
// componentId: ComponentId,
|
||||
// properties: Record<string, string>
|
||||
// ) {
|
||||
// const component = genComponent(componentType, componentId, properties);
|
||||
// const newSchema = produce(this.schema, draft => {
|
||||
// draft.push(component);
|
||||
// });
|
||||
// this.updateSchema(newSchema);
|
||||
// return newSchema;
|
||||
// }
|
||||
|
||||
removeComponent(componentId: ComponentId) {
|
||||
const newSchema = this.schema.filter(c => c.id !== componentId);
|
||||
this.updateSchema(newSchema);
|
||||
return newSchema;
|
||||
}
|
||||
// createModule: (moduleId: ModuleId, moduleType: ModuleType) => IModuleModel;
|
||||
// removeModule: (moduleId: ModuleId) => void;
|
||||
moveComponent(
|
||||
fromId: ComponentId,
|
||||
toId: ComponentId,
|
||||
slot: SlotName,
|
||||
afterId?: ComponentId
|
||||
) {
|
||||
const fromIndex = this.findComponentIndex(fromId);
|
||||
const afterIndex = afterId
|
||||
? this.findComponentIndex(afterId)
|
||||
: this.findComponentIndex(toId);
|
||||
this.changeTraitProperties(fromId, SlotTraitType, { container: { id: toId, slot } });
|
||||
|
||||
const newSchema = produce(this.schema, draft => {
|
||||
const target = draft.splice(fromIndex, 1)[0];
|
||||
draft.splice(fromIndex >= afterIndex ? afterIndex + 1 : afterIndex, 0, target);
|
||||
});
|
||||
this.updateSchema(newSchema);
|
||||
return this.schema;
|
||||
}
|
||||
|
||||
changeTraitProperties(
|
||||
componentId: ComponentId,
|
||||
traitType: TraitType,
|
||||
properties: Record<string, unknown>
|
||||
) {
|
||||
const componentIndex = this.findComponentIndex(componentId);
|
||||
const component = this.schema[componentIndex];
|
||||
const traitIndex = component?.traits.findIndex(t => t.type === traitType);
|
||||
if (traitIndex > -1) {
|
||||
const newSchema = produce(this.schema, draft => {
|
||||
draft[componentIndex].traits[traitIndex].properties = properties;
|
||||
});
|
||||
this.updateSchema(newSchema);
|
||||
}
|
||||
return this.schema;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,11 @@
|
||||
import { ApplicationComponent, RuntimeComponentSpec } from '@sunmao-ui/core';
|
||||
import { registry } from '../../setup';
|
||||
import { genComponent, genTrait, getPropertyObject } from './utils';
|
||||
import { genComponent, genTrait } from './utils';
|
||||
import {
|
||||
ComponentId,
|
||||
ComponentType,
|
||||
IApplicationModel,
|
||||
IComponentModel,
|
||||
IModuleModel,
|
||||
ModuleId,
|
||||
ModuleType,
|
||||
SlotName,
|
||||
StyleSlotName,
|
||||
MethodName,
|
||||
@ -17,6 +14,7 @@ import {
|
||||
IFieldModel,
|
||||
EventName,
|
||||
TraitType,
|
||||
TraitId,
|
||||
} from './IAppModel';
|
||||
import { TraitModel } from './TraitModel';
|
||||
import { FieldModel } from './FieldModel';
|
||||
@ -46,8 +44,8 @@ export class ComponentModel implements IComponentModel {
|
||||
// find slot trait
|
||||
this.traits.forEach(t => {
|
||||
if (t.type === 'core/v1/slot') {
|
||||
this.parentId = t.properties.container.id;
|
||||
this.parentSlot = t.properties.container.slot;
|
||||
this.parentId = t.rawProperties.container.id;
|
||||
this.parentSlot = t.rawProperties.container.slot;
|
||||
}
|
||||
});
|
||||
|
||||
@ -88,42 +86,32 @@ export class ComponentModel implements IComponentModel {
|
||||
return (this.spec ? this.spec.spec.styleSlots : []) as StyleSlotName[];
|
||||
}
|
||||
|
||||
get json(): ApplicationComponent {
|
||||
if (!this.isDirty) {
|
||||
return this.schema;
|
||||
get rawProperties() {
|
||||
const obj: Record<string, any> = {};
|
||||
for (const key in this.properties) {
|
||||
obj[key] = this.properties[key].value;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private get slotTrait() {
|
||||
return this.traits.find(t => t.type === SlotTraitType);
|
||||
}
|
||||
|
||||
toJS(): ApplicationComponent {
|
||||
if (this.isDirty) {
|
||||
this.isDirty = false;
|
||||
const newProperties = this.rawProperties;
|
||||
const newTraits = this.traits.map(t => t.toJS());
|
||||
const newSchema = genComponent(this.type, this.id, newProperties, newTraits);
|
||||
this.schema = newSchema;
|
||||
}
|
||||
this.isDirty = false;
|
||||
const newProperties = getPropertyObject(this.properties);
|
||||
const newTraits = this.traits.map(t => t.json);
|
||||
const newSchema = genComponent(this.type, this.id, newProperties, newTraits);
|
||||
this.schema = newSchema;
|
||||
return this.schema;
|
||||
|
||||
// if (this.isDirty || this.traits.length !== this.origin.traits.length) {
|
||||
// return {
|
||||
// ...this.origin,
|
||||
// traits: this.traits.map(t => t.json),
|
||||
// };
|
||||
// } else {
|
||||
// const isChanged = this.traits.some(t => t.isDirty);
|
||||
// if (isChanged) {
|
||||
// return {
|
||||
// ...this.origin,
|
||||
// traits: this.traits.map(t => t.json),
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// return this.origin;
|
||||
}
|
||||
|
||||
changeComponentProperty(propertyName: string, value: any) {
|
||||
this.properties[propertyName].update(value);
|
||||
this.isDirty = true;
|
||||
// const newSchema = produce(this.schema, draft => {
|
||||
// draft[componentIndex].properties[propertyName] = value;
|
||||
// });
|
||||
// this.updateSchema(newSchema)
|
||||
}
|
||||
|
||||
addTrait(traitType: TraitType, properties: Record<string, unknown>): ITraitModel {
|
||||
@ -134,22 +122,98 @@ export class ComponentModel implements IComponentModel {
|
||||
return trait;
|
||||
}
|
||||
|
||||
appendTo = (slot: SlotName, parent: IComponentModel) => {
|
||||
if (!parent.children[slot]) {
|
||||
parent.children[slot] = [];
|
||||
appendTo = (parent?: IComponentModel, slot?: SlotName) => {
|
||||
// remove from current position
|
||||
if (this.parent) {
|
||||
const slotChildren = this.parent.children[this.parentSlot!];
|
||||
slotChildren.splice(slotChildren.indexOf(this), 1);
|
||||
}
|
||||
|
||||
// update parent
|
||||
parent.children[slot].push(this);
|
||||
this.parent = parent;
|
||||
this.parentSlot = slot;
|
||||
this.parentId = parent.id;
|
||||
|
||||
if (parent && slot) {
|
||||
if (!parent.children[slot]) {
|
||||
parent.children[slot] = [];
|
||||
}
|
||||
|
||||
parent.children[slot].push(this);
|
||||
this.parent = parent;
|
||||
this.parentSlot = slot;
|
||||
this.parentId = parent.id;
|
||||
// update trait
|
||||
this.updateSlotTrait(parent.id, slot);
|
||||
} else {
|
||||
this.parent = null;
|
||||
this.parentSlot = null;
|
||||
this.parentId = null;
|
||||
if (this.slotTrait) {
|
||||
this.removeTrait(this.slotTrait?.id)
|
||||
}
|
||||
// remove from origin position in allComponents
|
||||
const oldIndex = this.appModel.allComponents.indexOf(this)
|
||||
if (oldIndex > -1){
|
||||
this.appModel.allComponents.splice(this.appModel.allComponents.indexOf(this), 1);
|
||||
}
|
||||
}
|
||||
// update app model
|
||||
this.appModel.updateSingleComponent(this);
|
||||
|
||||
// update trait
|
||||
this.addTrait(SlotTraitType, { container: { id: parent.id, slot } });
|
||||
this.isDirty = true;
|
||||
};
|
||||
|
||||
removeTrait(traitId: TraitId) {
|
||||
const traitIndex = this.traits.findIndex(t => t.id === traitId);
|
||||
if (traitIndex === -1) return;
|
||||
this.traits.splice(traitIndex, 1);
|
||||
this.isDirty = true;
|
||||
}
|
||||
|
||||
changeId(newId: ComponentId) {
|
||||
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.container.update({ id: newId, slot });
|
||||
slotTrait.isDirty = true;
|
||||
}
|
||||
child.isDirty = true;
|
||||
});
|
||||
}
|
||||
this.isDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
moveAfter(after: ComponentId | null) {
|
||||
let siblings: IComponentModel[] = [];
|
||||
if (this.parent) {
|
||||
siblings = this.parent.children[this.parentSlot as SlotName];
|
||||
} else {
|
||||
siblings = this.appModel.model;
|
||||
}
|
||||
// update model
|
||||
siblings.splice(siblings.indexOf(this), 1);
|
||||
const afterIndexInSiblings = after ? siblings.findIndex(c => c.id === after) + 1 : 0;
|
||||
siblings.splice(afterIndexInSiblings, 0, this);
|
||||
|
||||
// update allComponents schema
|
||||
const allComponents = this.appModel.allComponents;
|
||||
allComponents.splice(allComponents.indexOf(this), 1);
|
||||
// if moving to the first of siblings, move to the next after parent
|
||||
const afterTargetId = after || this.parent?.id;
|
||||
const afterIndexInAllComponents = afterTargetId
|
||||
? allComponents.findIndex(c => c.id === afterTargetId) + 1
|
||||
: 0;
|
||||
allComponents.splice(afterIndexInAllComponents, 0, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
updateSlotTrait(parent: ComponentId, slot: SlotName) {
|
||||
if (this.slotTrait) {
|
||||
this.slotTrait.properties.container.update({ id: parent, slot });
|
||||
this.slotTrait.isDirty = true;
|
||||
} else {
|
||||
this.addTrait(SlotTraitType, { container: { id: parent, slot } });
|
||||
}
|
||||
this.isDirty = true;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,9 @@ import { ApplicationComponent, ComponentTrait } from "@sunmao-ui/core";
|
||||
export type ComponentId = string & {
|
||||
kind: 'componentId';
|
||||
};
|
||||
export type TraitId = string & {
|
||||
kind: 'traitId';
|
||||
};
|
||||
export type ComponentType = string & {
|
||||
kind: 'componentType';
|
||||
};
|
||||
@ -35,16 +38,11 @@ export interface IApplicationModel {
|
||||
model: IComponentModel[];
|
||||
modules: IModuleModel[];
|
||||
allComponents: IComponentModel[];
|
||||
json: ApplicationComponent[];
|
||||
toJS(): ApplicationComponent[];
|
||||
createComponent(type: ComponentType, id?: ComponentId): IComponentModel
|
||||
getComponentById(id: ComponentId): IComponentModel | undefined;
|
||||
genId(type: ComponentType): ComponentId;
|
||||
// createComponent: (componentType: ComponentType, componentId: ComponentId, properties: Record<string, string>) => ApplicationComponent[];
|
||||
// createModule: (moduleId: ModuleId, moduleType: ModuleType) => IModuleModel;
|
||||
// removeComponent: (componentId: ComponentId) => ApplicationComponent[];
|
||||
// removeModule: (moduleId: ModuleId) => void;
|
||||
// findComponent: (componentId: ComponentId) => ApplicationComponent | undefined;
|
||||
// moveComponent: (fromId: ComponentId, toId: ComponentId, slot: SlotName, afterId: ComponentId) => void;
|
||||
|
||||
removeComponent(componentId: ComponentId): void;
|
||||
updateSingleComponent(component: IComponentModel): void;
|
||||
}
|
||||
|
||||
@ -57,7 +55,6 @@ export interface IModuleModel {
|
||||
export interface IComponentModel {
|
||||
appModel: IApplicationModel;
|
||||
id: ComponentId;
|
||||
get json (): ApplicationComponent;
|
||||
type: ComponentType;
|
||||
properties: Record<string, IFieldModel>;
|
||||
children: Record<SlotName, IComponentModel[]>;
|
||||
@ -71,24 +68,28 @@ export interface IComponentModel {
|
||||
methods: MethodName[];
|
||||
events: EventName[];
|
||||
isDirty: boolean;
|
||||
toJS(): ApplicationComponent;
|
||||
changeComponentProperty: (key: string, value: unknown) => void;
|
||||
appendTo: (slot: SlotName, parent: IComponentModel) => void;
|
||||
|
||||
// move component across level
|
||||
appendTo: (parent?: IComponentModel, slot?: SlotName) => void;
|
||||
// move in same level
|
||||
moveAfter: (after: ComponentId | null) => IComponentModel;
|
||||
changeId: (newId: ComponentId) => IComponentModel;
|
||||
addTrait: (traitType: TraitType, properties: Record<string, unknown>) => ITraitModel;
|
||||
// removeTrait: (traitType: TraitType) => void;
|
||||
// modifyProperty: (propertyName: string, value: any) => void;
|
||||
// modifyId: (newId: ComponentId) => void;
|
||||
removeTrait: (traitId: TraitId) => void;
|
||||
}
|
||||
|
||||
export interface ITraitModel {
|
||||
// trait id only exists in model, doesnt exist in schema
|
||||
id: TraitId
|
||||
parent: IComponentModel;
|
||||
type: TraitType;
|
||||
properties: Record<string, any>;
|
||||
propertiesMedatadata: Record<string, IFieldModel>;
|
||||
rawProperties: Record<string, any>;
|
||||
properties: Record<string, IFieldModel>;
|
||||
methods: MethodName[];
|
||||
stateKeys: StateKey[];
|
||||
isDirty: boolean;
|
||||
get json (): ComponentTrait;
|
||||
toJS(): ComponentTrait;
|
||||
}
|
||||
|
||||
export interface IFieldModel {
|
||||
|
@ -10,38 +10,47 @@ import {
|
||||
ITraitModel,
|
||||
IFieldModel,
|
||||
StateKey,
|
||||
TraitId,
|
||||
} from './IAppModel';
|
||||
import { FieldModel } from './FieldModel';
|
||||
import { genTrait, getPropertyObject } from './utils';
|
||||
import { genTrait } from './utils';
|
||||
|
||||
let traitIdCount = 0
|
||||
|
||||
export class TraitModel implements ITraitModel {
|
||||
private origin: ComponentTrait;
|
||||
private schema: ComponentTrait;
|
||||
private spec: RuntimeTraitSpec;
|
||||
|
||||
id: TraitId;
|
||||
type: TraitType;
|
||||
properties: Record<string, any>;
|
||||
propertiesMedatadata: Record<string, IFieldModel> = {};
|
||||
parent: IComponentModel;
|
||||
properties: Record<string, IFieldModel> = {};
|
||||
isDirty = false
|
||||
|
||||
constructor(trait: ComponentTrait, parent: IComponentModel) {
|
||||
this.origin = trait;
|
||||
constructor(trait: ComponentTrait, public parent: IComponentModel) {
|
||||
this.schema = trait;
|
||||
this.parent = parent;
|
||||
this.type = trait.type as TraitType;
|
||||
this.id = `${this.parent.id}_trait${traitIdCount++}` as TraitId;
|
||||
this.spec = registry.getTraitByType(this.type);
|
||||
|
||||
this.properties = trait.properties || {};
|
||||
for (const key in trait.properties) {
|
||||
this.propertiesMedatadata[key] = new FieldModel(trait.properties[key]);
|
||||
this.properties[key] = new FieldModel(trait.properties[key]);
|
||||
}
|
||||
this.propertiesMedatadata;
|
||||
this.properties;
|
||||
}
|
||||
|
||||
get json(): ComponentTrait {
|
||||
if (!this.isDirty) {
|
||||
return this.origin;
|
||||
get rawProperties() {
|
||||
const obj: Record<string, any> = {}
|
||||
for (const key in this.properties) {
|
||||
obj[key] = this.properties[key].value
|
||||
}
|
||||
return genTrait(this.type, getPropertyObject(this.properties));
|
||||
return obj
|
||||
}
|
||||
|
||||
toJS(): ComponentTrait {
|
||||
if (this.isDirty) {
|
||||
this.schema = genTrait(this.type, this.rawProperties)
|
||||
}
|
||||
return this.schema;
|
||||
}
|
||||
|
||||
get methods() {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ApplicationComponent, ComponentTrait } from '@sunmao-ui/core';
|
||||
import { registry } from '../../setup';
|
||||
import { FieldModel } from './FieldModel';
|
||||
|
||||
export function genComponent(
|
||||
type: string,
|
||||
@ -27,13 +26,3 @@ export function genTrait(
|
||||
properties,
|
||||
};
|
||||
}
|
||||
|
||||
export function getPropertyObject(
|
||||
properties: Record<string, FieldModel>
|
||||
): Record<string, unknown> {
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const key in properties) {
|
||||
result[key] = properties[key].value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import { eventBus } from '../eventBus';
|
||||
import { ApplicationModel } from './AppModel/AppModel';
|
||||
import { IUndoRedoManager, IOperation, OperationList } from './type';
|
||||
|
||||
export class AppModelManager implements IUndoRedoManager {
|
||||
@ -14,9 +13,6 @@ export class AppModelManager implements IUndoRedoManager {
|
||||
eventBus.on('componentsRefresh', components => {
|
||||
this.components = components;
|
||||
this.operationStack = new OperationList();
|
||||
(window as any).app = new ApplicationModel(this.components);
|
||||
(window as any).data = this.components;
|
||||
console.log((window as any).app)
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user