mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2024-11-21 03:15:49 +08:00
refactor oprationmanager
This commit is contained in:
parent
c7cec729c0
commit
22ef4d12b9
@ -30,6 +30,39 @@ const AppSchema: Application = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'text3',
|
||||
type: 'core/v1/text',
|
||||
properties: { value: { raw: 'VM1', format: 'plain' } },
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: { container: { id: 'vstack1', slot: 'content' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'text4',
|
||||
type: 'core/v1/text',
|
||||
properties: { value: { raw: '虚拟机', format: 'plain' } },
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: { container: { id: 'vstack1', slot: 'content' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'hstack2',
|
||||
type: 'chakra_ui/v1/hstack',
|
||||
properties: { spacing: '24px', align: '' },
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: { container: { id: 'hstack1', slot: 'content' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'text1',
|
||||
type: 'core/v1/text',
|
||||
@ -67,39 +100,6 @@ const AppSchema: Application = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'hstack2',
|
||||
type: 'chakra_ui/v1/hstack',
|
||||
properties: { spacing: '24px', align: '' },
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: { container: { id: 'hstack1', slot: 'content' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'text3',
|
||||
type: 'core/v1/text',
|
||||
properties: { value: { raw: 'VM1', format: 'plain' } },
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: { container: { id: 'vstack1', slot: 'content' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'text4',
|
||||
type: 'core/v1/text',
|
||||
properties: { value: { raw: '虚拟机', format: 'plain' } },
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: { container: { id: 'vstack1', slot: 'content' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'moduleContainer1',
|
||||
type: 'core/v1/moduleContainer',
|
||||
@ -128,14 +128,14 @@ describe('change component properties', () => {
|
||||
const newSchema = appModel.toJS();
|
||||
|
||||
it('change component properties', () => {
|
||||
expect(newSchema[2].properties.value).toEqual({ raw: 'hello', format: 'md' });
|
||||
expect(newSchema[5].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]);
|
||||
expect(origin[5]).not.toBe(newSchema[5]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -143,11 +143,18 @@ 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', () => {
|
||||
const newSchema = appModel.toJS();
|
||||
expect(origin.length - 1).toEqual(newSchema.length);
|
||||
expect(newSchema.some(c => c.id === 'text1')).toBe(false);
|
||||
});
|
||||
it('remove top level component', () => {
|
||||
appModel.removeComponent('hstack3' as any);
|
||||
const newSchema = appModel.toJS();
|
||||
expect(origin.length - 2).toEqual(newSchema.length);
|
||||
expect(newSchema.some(c => c.id === 'text1')).toBe(false);
|
||||
});
|
||||
const newSchema = appModel.toJS();
|
||||
it('keep immutable after removing component', () => {
|
||||
expect(origin).not.toBe(newSchema);
|
||||
expect(origin[0]).toBe(newSchema[0]);
|
||||
@ -180,8 +187,8 @@ describe('create component', () => {
|
||||
container: { id: 'vstack1', slot: 'content' },
|
||||
});
|
||||
});
|
||||
it('update add model cache', () => {
|
||||
expect(appModel.allComponents[appModel.allComponents.length - 1]).toBe(
|
||||
it('is in right place in allComponents', () => {
|
||||
expect(appModel.allComponents[4]).toBe(
|
||||
newComponent
|
||||
);
|
||||
});
|
||||
@ -189,8 +196,9 @@ describe('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);
|
||||
const newComponentSchema = newSchema[newSchema.length - 1];
|
||||
expect(origin[0]).toBe(newSchema[0]);
|
||||
expect(origin[1]).toBe(newSchema[1]);
|
||||
const newComponentSchema = newSchema[4];
|
||||
expect(newComponentSchema.id).toBe('text5');
|
||||
expect(newComponentSchema.traits[0].properties).toEqual({
|
||||
container: { id: 'vstack1', slot: 'content' },
|
||||
@ -219,13 +227,13 @@ describe('add trait', () => {
|
||||
const newSchema = appModel.toJS();
|
||||
|
||||
it('add trait', () => {
|
||||
expect(newSchema[2].traits[1].properties.key).toEqual('value');
|
||||
expect(newSchema[5].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]);
|
||||
expect(origin[5]).not.toBe(newSchema[5]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -236,14 +244,14 @@ describe('remove trait', () => {
|
||||
text1!.removeTrait(text1.traits[0].id);
|
||||
const newSchema = appModel.toJS();
|
||||
it('remove trait', () => {
|
||||
expect(newSchema[2].traits.length).toEqual(0);
|
||||
expect(newSchema[5].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]);
|
||||
expect(origin[5]).not.toBe(newSchema[5]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -262,7 +270,7 @@ describe('change component id', () => {
|
||||
id: newId,
|
||||
slot: 'content',
|
||||
});
|
||||
expect(newSchema[5].traits[0].properties.container).toEqual({
|
||||
expect(newSchema[4].traits[0].properties.container).toEqual({
|
||||
id: newId,
|
||||
slot: 'content',
|
||||
});
|
||||
@ -276,6 +284,8 @@ describe('change component id', () => {
|
||||
expect(origin).not.toBe(newSchema);
|
||||
expect(origin[0]).not.toBe(newSchema[0]);
|
||||
expect(origin[1]).not.toBe(newSchema[1]);
|
||||
expect(origin[4]).not.toBe(newSchema[4]);
|
||||
expect(origin[8]).not.toBe(newSchema[8]);
|
||||
expect(origin[2]).toBe(newSchema[2]);
|
||||
expect(origin[3]).toBe(newSchema[3]);
|
||||
});
|
||||
@ -292,8 +302,8 @@ describe('move component', () => {
|
||||
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');
|
||||
expect(newSchema[5].id).toEqual('text2');
|
||||
expect(newSchema[6].id).toEqual('text1');
|
||||
});
|
||||
|
||||
it('can move top level component', () => {
|
||||
@ -301,8 +311,8 @@ describe('move component', () => {
|
||||
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');
|
||||
expect(newSchema[0].id).toEqual('hstack3');
|
||||
expect(newSchema[1].id).toEqual('hstack1');
|
||||
});
|
||||
it('can move component to the first', () => {
|
||||
hstack1.moveAfter(null);
|
||||
|
@ -67,6 +67,7 @@ class EditorStore {
|
||||
});
|
||||
// listen the change by operations, and save newComponents
|
||||
eventBus.on('componentsChange', components => {
|
||||
console.log('componentsChange', components);
|
||||
this.setComponents(components);
|
||||
this.setCurrentComponentsVersion(this.currentComponentsVersion + 1)
|
||||
|
||||
|
@ -31,6 +31,7 @@ export const renderField = (properties: {
|
||||
if (typeof value !== 'object') {
|
||||
const ref = React.createRef<HTMLTextAreaElement>();
|
||||
const onBlur = () => {
|
||||
console.log('index', index)
|
||||
const operation = type
|
||||
? genOperation('modifyTraitProperty', {
|
||||
componentId: selectedComponentId,
|
||||
|
@ -11,22 +11,23 @@ type Props = {
|
||||
registry: Registry;
|
||||
component: ApplicationComponent;
|
||||
trait: ComponentTrait;
|
||||
traitIndex: number;
|
||||
onRemove: () => void;
|
||||
};
|
||||
|
||||
export const GeneralTraitForm: React.FC<Props> = props => {
|
||||
const { trait, component, onRemove, registry } = props;
|
||||
const { trait, traitIndex, component, onRemove, registry } = props;
|
||||
|
||||
const tImpl = registry.getTraitByType(trait.type);
|
||||
const properties = Object.assign(
|
||||
parseTypeBox(tImpl.spec.properties as TSchema),
|
||||
trait.properties
|
||||
);
|
||||
|
||||
const fields = Object.keys(properties || []).map((key: string, index: number) => {
|
||||
console.log('properties', properties)
|
||||
const fields = Object.keys(properties || []).map((key: string) => {
|
||||
const value = trait.properties[key];
|
||||
return renderField({
|
||||
index,
|
||||
index: traitIndex,
|
||||
key,
|
||||
value,
|
||||
fullKey: key,
|
||||
|
@ -31,10 +31,10 @@ export const GeneralTraitFormList: React.FC<Props> = props => {
|
||||
};
|
||||
|
||||
const traitFields = component.traits
|
||||
.filter(trait => {
|
||||
return !ignoreTraitsList.includes(trait.type);
|
||||
})
|
||||
.map((trait, index) => {
|
||||
if (ignoreTraitsList.includes(trait.type)) {
|
||||
return null
|
||||
}
|
||||
const onRemoveTrait = () => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
@ -49,6 +49,7 @@ export const GeneralTraitFormList: React.FC<Props> = props => {
|
||||
key={index}
|
||||
component={component}
|
||||
trait={trait}
|
||||
traitIndex={index}
|
||||
onRemove={onRemoveTrait}
|
||||
registry={registry}
|
||||
/>
|
||||
|
@ -34,10 +34,6 @@ export const StyleTraitForm: React.FC<Props> = props => {
|
||||
return registry.getComponentByType(component.type).spec.styleSlots;
|
||||
}, [component, registry]);
|
||||
|
||||
if (!styleSlots.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const styleTraitIndex = useMemo(() => {
|
||||
return component.traits.findIndex(t => t.type === 'core/v1/style');
|
||||
}, [component]);
|
||||
|
@ -73,6 +73,7 @@ export const KeyboardEventWrapper: React.FC<Props> = ({
|
||||
genOperation('pasteComponent', {
|
||||
parentId: selectedComponentId,
|
||||
slot: 'content',
|
||||
rootComponentId: pasteManager.current.rootComponentId,
|
||||
components: pasteManager.current.componentsCache,
|
||||
copyTimes: pasteManager.current.copyTimes,
|
||||
})
|
||||
|
@ -7,13 +7,13 @@ import {
|
||||
IApplicationModel,
|
||||
IComponentModel,
|
||||
IModuleModel,
|
||||
SlotName,
|
||||
} from './IAppModel';
|
||||
import { genComponent } from './utils';
|
||||
|
||||
export class ApplicationModel implements IApplicationModel {
|
||||
model: IComponentModel[] = [];
|
||||
modules: IModuleModel[] = [];
|
||||
allComponents: IComponentModel[] = [];
|
||||
private schema: ApplicationComponent[] = [];
|
||||
private componentMap: Record<ComponentId, IComponentModel> = {};
|
||||
|
||||
@ -22,8 +22,19 @@ export class ApplicationModel implements IApplicationModel {
|
||||
this.resolveTree(components);
|
||||
}
|
||||
|
||||
get allComponents(): IComponentModel[] {
|
||||
const result: IComponentModel[] = []
|
||||
this.traverseTree(c => {
|
||||
result.push(c)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
updateSingleComponent(component: IComponentModel) {
|
||||
this.allComponents.push(component);
|
||||
component.appModel = this;
|
||||
if (!component.parent && !this.model.includes(component)) {
|
||||
this.model.push(component)
|
||||
}
|
||||
this.componentMap[component.id] = component;
|
||||
}
|
||||
|
||||
@ -55,21 +66,23 @@ export class ApplicationModel implements IApplicationModel {
|
||||
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);
|
||||
} else {
|
||||
this.model.splice(this.model.indexOf(comp), 1);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private resolveTree(components: ApplicationComponent[]) {
|
||||
this.allComponents = components.map(c => {
|
||||
const allComponents = components.map(c => {
|
||||
const comp = new ComponentModel(this, c);
|
||||
this.componentMap[c.id as ComponentId] = comp;
|
||||
return comp;
|
||||
});
|
||||
|
||||
this.allComponents.forEach(child => {
|
||||
allComponents.forEach(child => {
|
||||
if (child.parentId && child.parentSlot) {
|
||||
const parent = this.componentMap[child.parentId];
|
||||
if (parent) {
|
||||
@ -85,4 +98,18 @@ export class ApplicationModel implements IApplicationModel {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private traverseTree(cb: (c: IComponentModel) => void) {
|
||||
function traverse(root: IComponentModel) {
|
||||
cb(root)
|
||||
for (const slot in root.children) {
|
||||
root.children[slot as SlotName].forEach(child => {
|
||||
traverse(child)
|
||||
})
|
||||
}
|
||||
}
|
||||
this.model.forEach((parent) => {
|
||||
traverse(parent);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -94,10 +94,34 @@ export class ComponentModel implements IComponentModel {
|
||||
return obj;
|
||||
}
|
||||
|
||||
get prevSilbling() {
|
||||
if (!this.parent) return null;
|
||||
const parentChildren = this.parent.children[this.parentSlot!];
|
||||
const index = parentChildren.indexOf(this);
|
||||
if (index === 0) return null;
|
||||
return parentChildren[index - 1];
|
||||
}
|
||||
|
||||
get nextSilbing() {
|
||||
if (!this.parent) return null;
|
||||
const parentChildren = this.parent.children[this.parentSlot!];
|
||||
const index = parentChildren.indexOf(this);
|
||||
if (index === parentChildren.length - 1) return null;
|
||||
return parentChildren[index + 1];
|
||||
}
|
||||
|
||||
private get slotTrait() {
|
||||
return this.traits.find(t => t.type === SlotTraitType);
|
||||
}
|
||||
|
||||
get allComponents(): IComponentModel[] {
|
||||
const result: IComponentModel[] = []
|
||||
this.traverseTree(c => {
|
||||
result.push(c)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
toJS(): ApplicationComponent {
|
||||
if (this.isDirty) {
|
||||
this.isDirty = false;
|
||||
@ -147,11 +171,6 @@ export class ComponentModel implements IComponentModel {
|
||||
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);
|
||||
@ -193,20 +212,38 @@ export class ComponentModel implements IComponentModel {
|
||||
// update model
|
||||
siblings.splice(siblings.indexOf(this), 1);
|
||||
const afterIndexInSiblings = after ? siblings.findIndex(c => c.id === after) + 1 : 0;
|
||||
console.log('afterIndexInSiblings', afterIndexInSiblings)
|
||||
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);
|
||||
console.log('siblings', siblings)
|
||||
return this;
|
||||
}
|
||||
|
||||
appendChild(child: IComponentModel, slot: SlotName) {
|
||||
if (!this.children[slot]) {
|
||||
this.children[slot] = [];
|
||||
}
|
||||
this.children[slot].push(child);
|
||||
child.parent = this;
|
||||
child.parentSlot = slot;
|
||||
child.parentId = this.id;
|
||||
child.appModel = this.appModel;
|
||||
child.updateSlotTrait(this.id, slot);
|
||||
this.traverseTree(c => {
|
||||
this.appModel.updateSingleComponent(c)
|
||||
})
|
||||
}
|
||||
|
||||
updateTrait(traitId: TraitId, properties: Record<string, unknown>) {
|
||||
const trait = this.traits.find(t => t.id === traitId);
|
||||
if (!trait) return;
|
||||
for (const property in properties) {
|
||||
trait.properties[property].update(properties[property]);
|
||||
trait.isDirty = true
|
||||
}
|
||||
console.log('new trait', trait)
|
||||
this.isDirty = true;
|
||||
}
|
||||
|
||||
updateSlotTrait(parent: ComponentId, slot: SlotName) {
|
||||
if (this.slotTrait) {
|
||||
this.slotTrait.properties.container.update({ id: parent, slot });
|
||||
@ -216,4 +253,16 @@ export class ComponentModel implements IComponentModel {
|
||||
}
|
||||
this.isDirty = true;
|
||||
}
|
||||
|
||||
private traverseTree(cb: (c: IComponentModel) => void) {
|
||||
function traverse(root: IComponentModel) {
|
||||
cb(root)
|
||||
for (const slot in root.children) {
|
||||
root.children[slot as SlotName].forEach(child => {
|
||||
traverse(child)
|
||||
})
|
||||
}
|
||||
}
|
||||
traverse(this)
|
||||
}
|
||||
}
|
||||
|
@ -68,15 +68,21 @@ export interface IComponentModel {
|
||||
methods: MethodName[];
|
||||
events: EventName[];
|
||||
isDirty: boolean;
|
||||
allComponents: IComponentModel[];
|
||||
toJS(): ApplicationComponent;
|
||||
changeComponentProperty: (key: string, value: unknown) => void;
|
||||
// move component across level
|
||||
appendTo: (parent?: IComponentModel, slot?: SlotName) => void;
|
||||
// move in same level
|
||||
moveAfter: (after: ComponentId | null) => IComponentModel;
|
||||
appendChild: (component: IComponentModel, slot: SlotName) => void;
|
||||
changeId: (newId: ComponentId) => IComponentModel;
|
||||
addTrait: (traitType: TraitType, properties: Record<string, unknown>) => ITraitModel;
|
||||
removeTrait: (traitId: TraitId) => void;
|
||||
updateTrait: (traitId: TraitId, properties: Record<string, unknown>) => void;
|
||||
updateSlotTrait: (parent: ComponentId, slot: SlotName) => void;
|
||||
nextSilbing: IComponentModel | null
|
||||
prevSilbling: IComponentModel | null
|
||||
}
|
||||
|
||||
export interface ITraitModel {
|
||||
@ -90,6 +96,7 @@ export interface ITraitModel {
|
||||
stateKeys: StateKey[];
|
||||
isDirty: boolean;
|
||||
toJS(): ComponentTrait;
|
||||
updateProperty: (key: string, value: any) => void;
|
||||
}
|
||||
|
||||
export interface IFieldModel {
|
||||
|
@ -1,7 +1,4 @@
|
||||
import {
|
||||
ComponentTrait,
|
||||
RuntimeTraitSpec,
|
||||
} from '@sunmao-ui/core';
|
||||
import { ComponentTrait, RuntimeTraitSpec } from '@sunmao-ui/core';
|
||||
import { registry } from '../../setup';
|
||||
import {
|
||||
IComponentModel,
|
||||
@ -15,7 +12,7 @@ import {
|
||||
import { FieldModel } from './FieldModel';
|
||||
import { genTrait } from './utils';
|
||||
|
||||
let traitIdCount = 0
|
||||
let traitIdCount = 0;
|
||||
|
||||
export class TraitModel implements ITraitModel {
|
||||
private schema: ComponentTrait;
|
||||
@ -23,7 +20,7 @@ export class TraitModel implements ITraitModel {
|
||||
id: TraitId;
|
||||
type: TraitType;
|
||||
properties: Record<string, IFieldModel> = {};
|
||||
isDirty = false
|
||||
isDirty = false;
|
||||
|
||||
constructor(trait: ComponentTrait, public parent: IComponentModel) {
|
||||
this.schema = trait;
|
||||
@ -38,17 +35,17 @@ export class TraitModel implements ITraitModel {
|
||||
this.properties;
|
||||
}
|
||||
|
||||
get rawProperties() {
|
||||
const obj: Record<string, any> = {}
|
||||
get rawProperties() {
|
||||
const obj: Record<string, any> = {};
|
||||
for (const key in this.properties) {
|
||||
obj[key] = this.properties[key].value
|
||||
obj[key] = this.properties[key].value;
|
||||
}
|
||||
return obj
|
||||
return obj;
|
||||
}
|
||||
|
||||
toJS(): ComponentTrait {
|
||||
if (this.isDirty) {
|
||||
this.schema = genTrait(this.type, this.rawProperties)
|
||||
this.schema = genTrait(this.type, this.rawProperties);
|
||||
}
|
||||
return this.schema;
|
||||
}
|
||||
@ -60,4 +57,14 @@ export class TraitModel implements ITraitModel {
|
||||
get stateKeys() {
|
||||
return (this.spec ? Object.keys(this.spec.spec.state) : []) as StateKey[];
|
||||
}
|
||||
|
||||
updateProperty(key: string, value: any) {
|
||||
if (this.properties[key]) {
|
||||
this.properties[key].update(value);
|
||||
} else {
|
||||
this.properties[key] = new FieldModel(value);
|
||||
}
|
||||
this.isDirty = true;
|
||||
this.parent.isDirty = true;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ export class AppModelManager implements IUndoRedoManager {
|
||||
eventBus.on('redo', () => this.redo());
|
||||
eventBus.on('operation', o => this.do(o));
|
||||
eventBus.on('componentsRefresh', components => {
|
||||
console.log('componentsRefresh', components);
|
||||
this.components = components;
|
||||
this.operationStack = new OperationList();
|
||||
});
|
||||
|
@ -2,10 +2,12 @@ import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import { getComponentAndChildrens } from './util';
|
||||
|
||||
export class PasteManager {
|
||||
rootComponentId = ''
|
||||
componentsCache: ApplicationComponent[] = [];
|
||||
copyTimes = 0;
|
||||
|
||||
setPasteComponents(componentId: string, allComponents: ApplicationComponent[]) {
|
||||
this.rootComponentId = componentId;
|
||||
const children = getComponentAndChildrens(componentId, allComponents);
|
||||
this.componentsCache = [...children];
|
||||
this.copyTimes = 0;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { ComponentId, SlotName } from '../AppModel/IAppModel';
|
||||
import {
|
||||
CreateComponentLeafOperation,
|
||||
CreateTraitLeafOperation,
|
||||
ModifyComponentPropertiesLeafOperation,
|
||||
} from '../leaf';
|
||||
import { BaseBranchOperation } from '../type';
|
||||
@ -27,6 +27,8 @@ export class CreateComponentBranchOperation extends BaseBranchOperation<CreateCo
|
||||
new CreateComponentLeafOperation({
|
||||
componentId: this.context.componentId!,
|
||||
componentType: this.context.componentType,
|
||||
parentId: this.context.parentId as ComponentId,
|
||||
slot: this.context.slot as SlotName,
|
||||
})
|
||||
);
|
||||
// add a slot trait if it has a parent
|
||||
@ -36,19 +38,6 @@ export class CreateComponentBranchOperation extends BaseBranchOperation<CreateCo
|
||||
c => c.id === this.context.parentId
|
||||
);
|
||||
|
||||
// add a slot trait if it has a direct child
|
||||
this.operationStack.insert(
|
||||
new CreateTraitLeafOperation({
|
||||
traitType: 'core/v1/slot',
|
||||
properties: {
|
||||
container: {
|
||||
id: this.context.parentId,
|
||||
slot: this.context.slot,
|
||||
},
|
||||
},
|
||||
componentId: this.context.componentId,
|
||||
})
|
||||
);
|
||||
if (!parentComponent) {
|
||||
console.warn("insert element has an invalid parent, it won't show in the view");
|
||||
} else if (parentComponent.type === 'core/v1/grid_layout') {
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { BaseBranchOperation } from '../type';
|
||||
import {
|
||||
ModifyComponentIdLeafOperation,
|
||||
ModifyComponentPropertiesLeafOperation,
|
||||
ModifyTraitPropertiesLeafOperation,
|
||||
UpdateSelectComponentLeafOperation,
|
||||
} from '../leaf';
|
||||
|
||||
@ -15,64 +12,8 @@ export type ModifyComponentIdBranchOperationContext = {
|
||||
|
||||
export class ModifyComponentIdBranchOperation extends BaseBranchOperation<ModifyComponentIdBranchOperationContext> {
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
const toReId = prev.find(c => c.id === this.context.componentId);
|
||||
if (!toReId) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
|
||||
const parentId = (
|
||||
toReId.traits.find(t => t.type === 'core/v1/slot')?.properties as
|
||||
| { id: string }
|
||||
| undefined
|
||||
)?.id;
|
||||
|
||||
prev.forEach(component => {
|
||||
if (component.id === parentId && component.type === 'core/v1/grid_layout') {
|
||||
this.operationStack.insert(
|
||||
new ModifyComponentPropertiesLeafOperation({
|
||||
componentId: component.id,
|
||||
properties: {
|
||||
layout: (prev: Array<ReactGridLayout.Layout>) => {
|
||||
return produce(prev, draft => {
|
||||
for (const layout of draft) {
|
||||
if (layout.i === this.context.componentId) {
|
||||
layout.i = this.context.newId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
const slotTraitIndex = component.traits.findIndex(
|
||||
trait => trait.type === 'core/v1/slot'
|
||||
);
|
||||
if (
|
||||
slotTraitIndex !== -1 &&
|
||||
(component.traits[slotTraitIndex].properties.container as { id: string }).id ===
|
||||
this.context.componentId
|
||||
) {
|
||||
// for direct children of the element, update their slot trait to new id
|
||||
this.operationStack.insert(
|
||||
new ModifyTraitPropertiesLeafOperation({
|
||||
componentId: component.id,
|
||||
traitIndex: slotTraitIndex,
|
||||
properties: {
|
||||
container: (prev: { id: string }) => {
|
||||
prev.id = this.context.newId;
|
||||
return prev;
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// update component id to new id
|
||||
this.operationStack.insert(new ModifyComponentIdLeafOperation(this.context));
|
||||
|
||||
// update selectid
|
||||
this.operationStack.insert(
|
||||
new UpdateSelectComponentLeafOperation({
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { ApplicationModel } from '../AppModel/AppModel';
|
||||
import { ComponentId } from '../AppModel/IAppModel';
|
||||
import {
|
||||
ModifyComponentPropertiesLeafOperation,
|
||||
RemoveComponentLeafOperation,
|
||||
@ -12,34 +14,10 @@ export type RemoveComponentBranchOperationContext = {
|
||||
|
||||
export class RemoveComponentBranchOperation extends BaseBranchOperation<RemoveComponentBranchOperationContext> {
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
// find component to remove
|
||||
const toRemove = prev.find(c => c.id === this.context.componentId);
|
||||
if (!toRemove) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
// check if it has a slot trait (mean it is a child component of an slot-based component)
|
||||
let parentId = (
|
||||
toRemove.traits.find(t => t.type === 'core/v1/slot')?.properties as
|
||||
| { id: string }
|
||||
| undefined
|
||||
)?.id;
|
||||
prev.forEach(component => {
|
||||
if (component.id === parentId && component.type !== 'core/v1/grid_layout') {
|
||||
// only need to modified layout from grid_layout component
|
||||
parentId = undefined;
|
||||
}
|
||||
const slotTrait = component.traits.find(trait => trait.type === 'core/v1/slot');
|
||||
if (
|
||||
slotTrait &&
|
||||
(slotTrait.properties.container as { id: string }).id === this.context.componentId
|
||||
) {
|
||||
// for direct children of the element, use Remove operation to delete them, it will cause a cascade deletion
|
||||
this.operationStack.insert(
|
||||
new RemoveComponentBranchOperation({ componentId: component.id })
|
||||
);
|
||||
}
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const parentId = appModel.getComponentById(
|
||||
this.context.componentId as ComponentId
|
||||
)?.parentId;
|
||||
|
||||
if (parentId) {
|
||||
// modify layout property of parent grid layout component
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { ApplicationModel } from '../../AppModel/AppModel';
|
||||
import { ComponentId } from '../../AppModel/IAppModel';
|
||||
import { BaseLeafOperation } from '../../type';
|
||||
export type AdjustComponentOrderLeafOperationContext = {
|
||||
orientation: 'up' | 'down';
|
||||
@ -7,133 +8,42 @@ export type AdjustComponentOrderLeafOperationContext = {
|
||||
};
|
||||
|
||||
export class AdjustComponentOrderLeafOperation extends BaseLeafOperation<AdjustComponentOrderLeafOperationContext> {
|
||||
private dest = -1;
|
||||
private index = -1;
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
this.index = draft.findIndex(c => c.id === this.context.componentId);
|
||||
if (this.index === -1) {
|
||||
console.warn('component not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const movedElement = draft[this.index];
|
||||
const slotTrait = movedElement.traits.find(t => t.type === 'core/v1/slot');
|
||||
if (!slotTrait) {
|
||||
// for top level element, find the next top level element;
|
||||
switch (this.context.orientation) {
|
||||
case 'up':
|
||||
for (this.dest = this.index - 1; this.dest >= 0; this.dest--) {
|
||||
const nextComponent = draft[this.dest];
|
||||
if (
|
||||
nextComponent.type !== 'core/v1/dummy' &&
|
||||
!nextComponent.traits.some(t => t.type === 'core/v1/slot')
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if found -1, means no any top level element is in the previous postion for target element
|
||||
break;
|
||||
case 'down':
|
||||
for (this.dest = this.index + 1; this.dest < draft.length; this.dest++) {
|
||||
const nextComponent = draft[this.dest];
|
||||
if (
|
||||
nextComponent.type !== 'core/v1/dummy' &&
|
||||
!nextComponent.traits.some(t => t.type === 'core/v1/slot')
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.dest === draft.length) {
|
||||
// mark dest as -1 due to not found element
|
||||
this.dest = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// for child element, search the element share the same parent with target element
|
||||
switch (this.context.orientation) {
|
||||
case 'up':
|
||||
for (this.dest = this.index - 1; this.dest >= 0; this.dest--) {
|
||||
const nextComponent = draft[this.dest];
|
||||
if (
|
||||
nextComponent.traits.some(
|
||||
t =>
|
||||
t.type === 'core/v1/slot' &&
|
||||
(t.properties.container as { id: string }).id ===
|
||||
(slotTrait.properties.container as { id: string }).id
|
||||
)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if found -1, means no any top level element is in the previous postion for target element
|
||||
break;
|
||||
case 'down':
|
||||
for (this.dest = this.index + 1; this.dest < draft.length; this.dest++) {
|
||||
const nextComponent = draft[this.dest];
|
||||
if (
|
||||
nextComponent.traits.some(
|
||||
t =>
|
||||
t.type === 'core/v1/slot' &&
|
||||
(t.properties.container as { id: string }).id ===
|
||||
(slotTrait.properties.container as { id: string }).id
|
||||
)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.dest === draft.length) {
|
||||
// mark dest as -1 due to not found element
|
||||
this.dest = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.dest === -1) {
|
||||
console.warn(`the element cannot move ${this.context.orientation}`);
|
||||
return;
|
||||
}
|
||||
const [highPos, lowPos] = [this.dest, this.index].sort();
|
||||
const lowComponent = draft.splice(lowPos, 1)[0];
|
||||
const highComponent = draft.splice(highPos, 1)[0];
|
||||
draft.splice(lowPos - 1, 0, highComponent);
|
||||
draft.splice(highPos, 0, lowComponent);
|
||||
});
|
||||
return this.move(prev, this.context.orientation);
|
||||
}
|
||||
redo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
if (this.index === -1) {
|
||||
console.warn("operation hasn't been executed, cannot redo");
|
||||
}
|
||||
if (this.dest === -1) {
|
||||
console.warn('cannot redo, the operation was failed executing');
|
||||
return;
|
||||
}
|
||||
const lowPos = Math.max(this.dest, this.index);
|
||||
const highPos = Math.min(this.dest, this.index);
|
||||
const lowComponent = draft.splice(lowPos, 1)[0];
|
||||
const highComponent = draft.splice(highPos, 1)[0];
|
||||
draft.splice(lowPos - 1, 0, highComponent);
|
||||
draft.splice(highPos, 0, lowComponent);
|
||||
});
|
||||
return this.do(prev)
|
||||
}
|
||||
|
||||
undo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
if (this.index === -1) {
|
||||
console.warn("cannot undo operation, the operation hasn't been executed.");
|
||||
}
|
||||
if (this.dest === -1) {
|
||||
console.warn('cannot undo, the operation was failed executing');
|
||||
return;
|
||||
}
|
||||
const lowPos = Math.max(this.dest, this.index);
|
||||
const highPos = Math.min(this.dest, this.index);
|
||||
const lowComponent = draft.splice(lowPos, 1)[0];
|
||||
const highComponent = draft.splice(highPos, 1)[0];
|
||||
draft.splice(lowPos - 1, 0, highComponent);
|
||||
draft.splice(highPos, 0, lowComponent);
|
||||
});
|
||||
return this.move(prev, this.context.orientation === 'up' ? 'down' : 'up');
|
||||
}
|
||||
|
||||
private move(prev: ApplicationComponent[], orientation: 'up' | 'down'): ApplicationComponent[] {
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
|
||||
switch (orientation) {
|
||||
case 'up':
|
||||
console.log('component.prevSilbling', component.prevSilbling)
|
||||
if (!component.prevSilbling) {
|
||||
console.warn('destination index out of bound');
|
||||
return prev;
|
||||
}
|
||||
component.moveAfter(component.prevSilbling?.prevSilbling?.id || null);
|
||||
break;
|
||||
case 'down':
|
||||
if (!component.nextSilbing) {
|
||||
console.warn('destination index out of bound');
|
||||
return prev;
|
||||
}
|
||||
component.moveAfter(component.nextSilbing?.id || null);
|
||||
break;
|
||||
}
|
||||
return appModel.toJS();
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,41 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { ApplicationModel } from '../../AppModel/AppModel';
|
||||
import { ComponentId, ComponentType, IComponentModel, SlotName } from '../../AppModel/IAppModel';
|
||||
import { BaseLeafOperation } from '../../type';
|
||||
import { genComponent } from '../../util';
|
||||
|
||||
export type CreateComponentLeafOperationContext = {
|
||||
componentType: string;
|
||||
componentId: string;
|
||||
parentId?: ComponentId;
|
||||
slot?: SlotName;
|
||||
};
|
||||
|
||||
export class CreateComponentLeafOperation extends BaseLeafOperation<CreateComponentLeafOperationContext> {
|
||||
private index!: number;
|
||||
private component!: ApplicationComponent;
|
||||
private component!: IComponentModel;
|
||||
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
this.component = genComponent(this.context.componentType, this.context.componentId);
|
||||
this.context.componentId = this.component.id;
|
||||
return produce(prev, draft => {
|
||||
draft.push(this.component);
|
||||
this.index = draft.length - 1;
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.createComponent(this.context.componentType as ComponentType, this.context.componentId as ComponentId);
|
||||
if (this.context.parentId) {
|
||||
const parent = appModel.getComponentById(this.context.parentId);
|
||||
if (parent) {
|
||||
component.appendTo(parent, this.context.slot);
|
||||
}
|
||||
} else {
|
||||
component.appendTo();
|
||||
}
|
||||
this.component = component;
|
||||
const newSchema = appModel.toJS()
|
||||
return newSchema
|
||||
}
|
||||
|
||||
redo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
draft.push(this.component);
|
||||
});
|
||||
return this.do(prev)
|
||||
}
|
||||
|
||||
undo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
draft.splice(this.index, 1);
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
appModel.removeComponent(this.component.id)
|
||||
return appModel.toJS()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { ApplicationModel } from '../../AppModel/AppModel';
|
||||
import { ComponentId } from '../../AppModel/IAppModel';
|
||||
import { BaseLeafOperation } from '../../type';
|
||||
|
||||
export type ModifyComponentIdLeafOperationContext = {
|
||||
@ -9,33 +10,22 @@ export type ModifyComponentIdLeafOperationContext = {
|
||||
|
||||
export class ModifyComponentIdLeafOperation extends BaseLeafOperation<ModifyComponentIdLeafOperationContext> {
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
const comp = draft.find(c => c.id === this.context.componentId);
|
||||
if (!comp) {
|
||||
console.warn('component not found');
|
||||
return;
|
||||
}
|
||||
comp.id = this.context.newId;
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
component.changeId(this.context.newId as ComponentId);
|
||||
return appModel.toJS();
|
||||
}
|
||||
redo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
const comp = draft.find(c => c.id === this.context.componentId);
|
||||
if (!comp) {
|
||||
console.warn('component not found');
|
||||
return;
|
||||
}
|
||||
comp.id = this.context.newId;
|
||||
});
|
||||
return this.do(prev);
|
||||
}
|
||||
undo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
const comp = draft.find(c => c.id === this.context.newId);
|
||||
if (!comp) {
|
||||
console.warn('component not found');
|
||||
return;
|
||||
}
|
||||
comp.id = this.context.componentId;
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.newId as ComponentId)!;
|
||||
component.changeId(this.context.componentId as ComponentId);
|
||||
return appModel.toJS();
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { BaseLeafOperation } from '../../type';
|
||||
import _ from 'lodash-es';
|
||||
import { tryOriginal } from '../../util';
|
||||
import { ApplicationModel } from '../../AppModel/AppModel';
|
||||
import { ComponentId } from '../../AppModel/IAppModel';
|
||||
export type ModifyComponentPropertiesLeafOperationContext = {
|
||||
componentId: string;
|
||||
properties: Record<string, any | (<T = any>(prev: T) => T)>;
|
||||
@ -11,47 +11,55 @@ export type ModifyComponentPropertiesLeafOperationContext = {
|
||||
export class ModifyComponentPropertiesLeafOperation extends BaseLeafOperation<ModifyComponentPropertiesLeafOperationContext> {
|
||||
private previousState: Record<string, any> = {};
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
const comp = draft.find(c => c.id === this.context.componentId);
|
||||
if (!comp) {
|
||||
console.warn('component not found');
|
||||
return;
|
||||
}
|
||||
for (const property in this.context.properties) {
|
||||
const appModel = new ApplicationModel(prev);
|
||||
// const component = appModel.createComponent(this.context.componentType as ComponentType, this.context.componentId as ComponentId);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (component) {
|
||||
for (const property in component.properties) {
|
||||
const oldValue = component.properties[property].value;
|
||||
// assign previous data
|
||||
this.previousState[property] = tryOriginal(comp.properties[property]);
|
||||
if (_.isFunction(this.context.properties[property])) {
|
||||
this.previousState[property] = oldValue;
|
||||
let newValue = this.context.properties[property];
|
||||
if (_.isFunction(newValue)) {
|
||||
// if modified value is a lazy function, execute it and assign
|
||||
this.context.properties[property] = this.context.properties[property](
|
||||
_.cloneDeep(comp.properties[property])
|
||||
);
|
||||
newValue = newValue(_.cloneDeep(oldValue));
|
||||
}
|
||||
comp.properties[property] = this.context.properties[property];
|
||||
component.changeComponentProperty(property, newValue);
|
||||
this.context.properties[property] = newValue;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
|
||||
const newSchema = appModel.toJS();
|
||||
return newSchema;
|
||||
}
|
||||
redo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
const comp = draft.find(c => c.id === this.context.componentId);
|
||||
if (!comp) {
|
||||
console.warn('component not found');
|
||||
return;
|
||||
}
|
||||
for (const property in this.context.properties) {
|
||||
comp.properties[property] = this.context.properties[property];
|
||||
}
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
|
||||
for (const property in this.context.properties) {
|
||||
component.changeComponentProperty(property, this.context.properties[property]);
|
||||
}
|
||||
return appModel.toJS();
|
||||
}
|
||||
undo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
const comp = draft.find(c => c.id === this.context.componentId);
|
||||
if (!comp) {
|
||||
console.warn('component not found');
|
||||
return;
|
||||
}
|
||||
for (const property in this.context.properties) {
|
||||
comp.properties[property] = this.previousState[property];
|
||||
}
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
|
||||
for (const property in this.previousState) {
|
||||
component.changeComponentProperty(property, this.previousState[property]);
|
||||
}
|
||||
|
||||
return appModel.toJS();
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +1,46 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { get } from 'lodash-es';
|
||||
import { resolveApplicationComponents } from '../../../utils/resolveApplicationComponents';
|
||||
import { ApplicationModel } from '../../AppModel/AppModel';
|
||||
import { ComponentId, IComponentModel, SlotName } from '../../AppModel/IAppModel';
|
||||
import { BaseLeafOperation } from '../../type';
|
||||
|
||||
export type PasteComponentLeafOperationContext = {
|
||||
parentId: string;
|
||||
slot: string;
|
||||
rootComponentId: string;
|
||||
components: ApplicationComponent[];
|
||||
copyTimes: number;
|
||||
};
|
||||
|
||||
export class PasteComponentLeafOperation extends BaseLeafOperation<PasteComponentLeafOperationContext> {
|
||||
private pastedComponents: ApplicationComponent[] = [];
|
||||
private componentCopy!: IComponentModel
|
||||
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
const { childrenMap } = resolveApplicationComponents(
|
||||
this.context.components
|
||||
);
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const targetParent = appModel.getComponentById(this.context.parentId as ComponentId);
|
||||
if (!targetParent) {
|
||||
return prev
|
||||
}
|
||||
const copyComponents = new ApplicationModel(this.context.components);
|
||||
const component = copyComponents.getComponentById(this.context.rootComponentId as ComponentId);
|
||||
if (!component){
|
||||
return prev;
|
||||
}
|
||||
component.allComponents.forEach((c) => {
|
||||
c.changeId(`${c.id}_copy${this.context.copyTimes}` as ComponentId)
|
||||
})
|
||||
targetParent.appendChild(component, this.context.slot as SlotName);
|
||||
this.componentCopy = component;
|
||||
|
||||
const rootTrait = this.context.components[0].traits.find(
|
||||
trait => trait.type === 'core/v1/slot'
|
||||
);
|
||||
const rootParentId = get(rootTrait, 'properties.container.id');
|
||||
|
||||
// map of old parentId to new parentId
|
||||
const newIdMap: Record<string, string> = { [rootParentId]: this.context.parentId };
|
||||
this.context.components.forEach(({ id }) => {
|
||||
if (prev.find(c => c.id === id)) {
|
||||
newIdMap[id] = `${id}_copy${this.context.copyTimes}`;
|
||||
} else {
|
||||
newIdMap[id] = id;
|
||||
}
|
||||
});
|
||||
|
||||
// update components slot
|
||||
childrenMap.forEach((slotMap, parentId) => {
|
||||
slotMap.forEach((children, slot) => {
|
||||
const newChildren = children.map(c => {
|
||||
const newComponent = updateComponentSlot(c, newIdMap[parentId], slot);
|
||||
return produce(newComponent, draft => {
|
||||
draft.id = newIdMap[c.id] || c.id;
|
||||
});
|
||||
});
|
||||
this.pastedComponents = this.pastedComponents.concat(newChildren);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return prev.concat(this.pastedComponents);
|
||||
return appModel.toJS();
|
||||
}
|
||||
|
||||
redo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return prev.concat(this.pastedComponents);
|
||||
return this.do(prev)
|
||||
}
|
||||
|
||||
|
||||
undo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
draft.splice(-this.pastedComponents.length);
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
appModel.removeComponent(this.componentCopy.id);
|
||||
return appModel.toJS()
|
||||
}
|
||||
}
|
||||
|
||||
function updateComponentSlot(
|
||||
component: ApplicationComponent,
|
||||
parentId: string,
|
||||
slot: string
|
||||
) {
|
||||
return produce(component, draft => {
|
||||
const newSlotTrait = {
|
||||
type: 'core/v1/slot',
|
||||
properties: { container: { id: parentId, slot } },
|
||||
};
|
||||
const oldSlotIndex = draft.traits.findIndex(trait => trait.type === 'core/v1/slot');
|
||||
if (oldSlotIndex === -1) {
|
||||
draft.traits.push(newSlotTrait);
|
||||
} else {
|
||||
draft.traits[oldSlotIndex] = newSlotTrait;
|
||||
}
|
||||
});
|
||||
return component;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { tryOriginal } from '../../../operations/util';
|
||||
import { ApplicationModel } from '../../AppModel/AppModel';
|
||||
import { ComponentId, IComponentModel, SlotName } from '../../AppModel/IAppModel';
|
||||
import { BaseLeafOperation } from '../../type';
|
||||
|
||||
export type RemoveComponentLeafOperationContext = {
|
||||
@ -8,34 +8,43 @@ export type RemoveComponentLeafOperationContext = {
|
||||
};
|
||||
|
||||
export class RemoveComponentLeafOperation extends BaseLeafOperation<RemoveComponentLeafOperationContext> {
|
||||
private deletedComponent!: ApplicationComponent;
|
||||
private deletedComponent?: IComponentModel;
|
||||
// FIXME: index is not a good type to remember a deleted resource
|
||||
private deletedIndex = -1;
|
||||
private beforeComponent?: IComponentModel;
|
||||
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
this.deletedIndex = prev.findIndex(
|
||||
c => c.id === this.context.componentId
|
||||
const appModel = new ApplicationModel(prev);
|
||||
this.deletedComponent = appModel.getComponentById(
|
||||
this.context.componentId as ComponentId
|
||||
);
|
||||
if (this.deletedIndex === -1) {
|
||||
console.warn('element not found');
|
||||
return prev;
|
||||
}
|
||||
return produce(prev, draft => {
|
||||
this.deletedComponent = tryOriginal(
|
||||
draft.splice(this.deletedIndex, 1)[0]
|
||||
);
|
||||
});
|
||||
this.beforeComponent = this.deletedComponent?.prevSilbling || undefined;
|
||||
console.log(this.beforeComponent)
|
||||
appModel.removeComponent(this.context.componentId as ComponentId);
|
||||
return appModel.toJS();
|
||||
}
|
||||
|
||||
redo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
draft.splice(this.deletedIndex, 1);
|
||||
});
|
||||
return this.do(prev);
|
||||
}
|
||||
|
||||
undo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
draft.splice(this.deletedIndex, 0, this.deletedComponent);
|
||||
});
|
||||
if (!this.deletedComponent) {
|
||||
return prev;
|
||||
}
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const parent = appModel.getComponentById(
|
||||
this.deletedComponent.parentId as ComponentId
|
||||
);
|
||||
if (parent) {
|
||||
parent.appendChild(
|
||||
this.deletedComponent,
|
||||
this.deletedComponent.parentSlot as SlotName
|
||||
);
|
||||
} else {
|
||||
appModel.updateSingleComponent(this.deletedComponent);
|
||||
}
|
||||
this.deletedComponent.moveAfter(this.beforeComponent?.id || null);
|
||||
console.log(appModel)
|
||||
return appModel.toJS();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { ApplicationModel } from '../../AppModel/AppModel';
|
||||
import { ComponentId, TraitId, TraitType } from '../../AppModel/IAppModel';
|
||||
import { BaseLeafOperation } from '../../type';
|
||||
|
||||
export type CreateTraitLeafOperationContext = {
|
||||
@ -9,36 +10,30 @@ export type CreateTraitLeafOperationContext = {
|
||||
};
|
||||
|
||||
export class CreateTraitLeafOperation extends BaseLeafOperation<CreateTraitLeafOperationContext> {
|
||||
private traitIndex!: number;
|
||||
private traitId!: TraitId;
|
||||
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
for (const component of draft) {
|
||||
if (component.id === this.context.componentId) {
|
||||
component.traits.push({
|
||||
type: this.context.traitType,
|
||||
properties: this.context.properties,
|
||||
});
|
||||
this.traitIndex = component.traits.length - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
return prev
|
||||
}
|
||||
const trait = component.addTrait(this.context.traitType as TraitType, this.context.properties);
|
||||
this.traitId = trait.id;
|
||||
return appModel.toJS()
|
||||
}
|
||||
|
||||
redo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return this.do(prev);
|
||||
}
|
||||
|
||||
undo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
for (let i = 0; i < draft.length; i++) {
|
||||
const component = draft[i];
|
||||
if (component.id === this.context.componentId) {
|
||||
component.traits.splice(this.traitIndex, 1);
|
||||
return;
|
||||
} else if (i === draft.length - 1) {
|
||||
console.warn('trait not found');
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.warn('component not found');
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
return prev
|
||||
}
|
||||
component.removeTrait(this.traitId);
|
||||
return appModel.toJS()
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import _ from 'lodash-es';
|
||||
import { tryOriginal } from '../../util';
|
||||
import { BaseLeafOperation } from '../../type';
|
||||
import { ApplicationModel } from '../../AppModel/AppModel';
|
||||
import { ComponentId } from '../../AppModel/IAppModel';
|
||||
|
||||
export type ModifyTraitPropertiesLeafOperationContext = {
|
||||
componentId: string;
|
||||
@ -13,65 +13,56 @@ export type ModifyTraitPropertiesLeafOperationContext = {
|
||||
export class ModifyTraitPropertiesLeafOperation extends BaseLeafOperation<ModifyTraitPropertiesLeafOperationContext> {
|
||||
private previousState: Record<string, any> = {};
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
const comp = draft.find(c => c.id === this.context.componentId);
|
||||
if (!comp) {
|
||||
console.warn('component not found');
|
||||
return;
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
const trait = component.traits[this.context.traitIndex];
|
||||
for (const property in this.context.properties) {
|
||||
const oldValue = trait.properties[property]?.value;
|
||||
this.previousState[property] = oldValue;
|
||||
let newValue = this.context.properties[property];
|
||||
if (_.isFunction(newValue)) {
|
||||
// if modified value is a lazy function, execute it and assign
|
||||
newValue = newValue(_.cloneDeep(oldValue));
|
||||
}
|
||||
const targetTrait = comp.traits[this.context.traitIndex];
|
||||
if (!targetTrait) {
|
||||
console.warn('trait not found');
|
||||
return;
|
||||
}
|
||||
for (const property in this.context.properties) {
|
||||
// assign previous data
|
||||
this.previousState[property] = tryOriginal(targetTrait.properties[property]);
|
||||
if (_.isFunction(this.context.properties[property])) {
|
||||
// execute lazy load value
|
||||
this.context.properties[property] = this.context.properties[property](
|
||||
_.cloneDeep(this.previousState[property])
|
||||
);
|
||||
}
|
||||
comp.traits[this.context.traitIndex].properties[property] =
|
||||
this.context.properties[property];
|
||||
}
|
||||
});
|
||||
this.context.properties[property] = newValue;
|
||||
trait.updateProperty(property, this.context.properties[property]);
|
||||
}
|
||||
|
||||
return appModel.toJS();
|
||||
}
|
||||
redo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
const comp = draft.find(c => c.id === this.context.componentId);
|
||||
if (!comp) {
|
||||
console.warn('component not found');
|
||||
return;
|
||||
}
|
||||
const targetTrait = comp.traits[this.context.traitIndex];
|
||||
if (!targetTrait) {
|
||||
console.warn('trait not found');
|
||||
return;
|
||||
}
|
||||
for (const property in this.context.properties) {
|
||||
comp.traits[this.context.traitIndex].properties[property] =
|
||||
this.context.properties[property];
|
||||
}
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
const trait = component.traits[this.context.traitIndex];
|
||||
|
||||
for (const property in this.context.properties) {
|
||||
trait.updateProperty(property, this.context.properties[property]);
|
||||
}
|
||||
|
||||
return appModel.toJS();
|
||||
}
|
||||
|
||||
undo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
return produce(prev, draft => {
|
||||
const comp = draft.find(c => c.id === this.context.componentId);
|
||||
if (!comp) {
|
||||
console.warn('component not found');
|
||||
return;
|
||||
}
|
||||
const targetTrait = comp.traits[this.context.traitIndex];
|
||||
if (!targetTrait) {
|
||||
console.warn('trait not found');
|
||||
return;
|
||||
}
|
||||
for (const property in this.context.properties) {
|
||||
targetTrait.properties[property] = this.previousState[property];
|
||||
}
|
||||
});
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
const trait = component.traits[this.context.traitIndex];
|
||||
|
||||
for (const property in this.previousState) {
|
||||
trait.updateProperty(property, this.previousState[property]);
|
||||
}
|
||||
|
||||
return appModel.toJS();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ApplicationComponent, ComponentTrait } from '@sunmao-ui/core';
|
||||
import produce from 'immer';
|
||||
import { tryOriginal } from '../../../operations/util';
|
||||
import { ApplicationComponent } from '@sunmao-ui/core';
|
||||
import { ApplicationModel } from '../../AppModel/AppModel';
|
||||
import { ComponentId, ITraitModel } from '../../AppModel/IAppModel';
|
||||
import { BaseLeafOperation } from '../../type';
|
||||
|
||||
export type RemoveTraitLeafOperationContext = {
|
||||
@ -9,60 +9,33 @@ export type RemoveTraitLeafOperationContext = {
|
||||
};
|
||||
|
||||
export class RemoveTraitLeafOperation extends BaseLeafOperation<RemoveTraitLeafOperationContext> {
|
||||
private deletedTrait!: ComponentTrait;
|
||||
private deletedTrait!: ITraitModel;
|
||||
do(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
const componentIndex = prev.findIndex(
|
||||
c => c.id === this.context.componentId
|
||||
);
|
||||
if (componentIndex === -1) {
|
||||
console.warn('component was removed');
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
return produce(prev, draft => {
|
||||
if (!draft[componentIndex].traits[this.context.index]) {
|
||||
console.warn('trait not foudn');
|
||||
return;
|
||||
}
|
||||
this.deletedTrait = tryOriginal(
|
||||
draft[componentIndex].traits.splice(this.context.index, 1)[0]
|
||||
);
|
||||
});
|
||||
|
||||
this.deletedTrait = component.traits[this.context.index];
|
||||
component.removeTrait(this.deletedTrait.id);
|
||||
return appModel.toJS();
|
||||
}
|
||||
|
||||
redo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
const componentIndex = prev.findIndex(
|
||||
c => c.id === this.context.componentId
|
||||
);
|
||||
if (componentIndex === -1) {
|
||||
console.warn('component was removed');
|
||||
return prev;
|
||||
}
|
||||
return produce(prev, draft => {
|
||||
if (!draft[componentIndex].traits[this.context.index]) {
|
||||
console.warn('trait not foudn');
|
||||
return;
|
||||
}
|
||||
draft[componentIndex].traits.splice(this.context.index, 1);
|
||||
});
|
||||
return this.do(prev);
|
||||
}
|
||||
|
||||
undo(prev: ApplicationComponent[]): ApplicationComponent[] {
|
||||
const componentIndex = prev.findIndex(
|
||||
c => c.id === this.context.componentId
|
||||
);
|
||||
if (componentIndex === -1) {
|
||||
console.warn('component was removed');
|
||||
const appModel = new ApplicationModel(prev);
|
||||
const component = appModel.getComponentById(this.context.componentId as ComponentId);
|
||||
if (!component) {
|
||||
console.warn('component not found');
|
||||
return prev;
|
||||
}
|
||||
return produce(prev, draft => {
|
||||
if (draft[componentIndex].traits.length < this.context.index) {
|
||||
console.warn('corrupted index');
|
||||
}
|
||||
draft[componentIndex].traits.splice(
|
||||
this.context.index,
|
||||
0,
|
||||
this.deletedTrait
|
||||
);
|
||||
});
|
||||
|
||||
component.addTrait(this.deletedTrait.type, this.deletedTrait.rawProperties);
|
||||
return appModel.toJS();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user