Browse Source

Refactor propagation of collection map state

pull/6359/head
mohamed yahia 1 year ago
parent
commit
a9fec3d2f7
  1. 8
      packages/core/src/data_sources/model/collection_component/CollectionComponent.ts
  2. 2
      packages/core/src/data_sources/model/collection_component/CollectionVariable.ts
  3. 60
      packages/core/src/dom_components/model/Component.ts
  4. 2
      packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts
  5. 32
      packages/core/src/dom_components/model/Components.ts
  6. 29
      packages/core/src/dom_components/model/DynamicValueWatcher.ts
  7. 33
      packages/core/src/dom_components/model/types.ts

8
packages/core/src/data_sources/model/collection_component/CollectionComponent.ts

@ -150,6 +150,7 @@ function getCollectionItems(
[keyCollectionsStateMap]: collectionsStateMap,
isCollectionItem: true,
draggable: false,
deepPropagate: [setCollectionStateMap(collectionsStateMap)],
},
opt,
);
@ -164,6 +165,13 @@ function getCollectionItems(
return components;
}
function setCollectionStateMap(collectionsStateMap: CollectionsStateMap) {
return (cmp: Component) => {
cmp.set('isCollectionItem', true);
cmp.set(keyCollectionsStateMap, collectionsStateMap);
};
}
function getDataSourceItems(dataSource: any, em: EditorModel) {
let items: any[] = [];
switch (true) {

2
packages/core/src/data_sources/model/collection_component/CollectionVariable.ts

@ -64,6 +64,8 @@ function resolveCollectionVariable(
em: EditorModel,
) {
const { collectionName = keyInnerCollectionState, variableType, path } = collectionVariableDefinition;
if (!collectionsStateMap) return;
const collectionItem = collectionsStateMap[collectionName];
if (!collectionItem) {

60
packages/core/src/dom_components/model/Component.ts

@ -26,6 +26,7 @@ import {
ComponentDefinitionDefined,
ComponentOptions,
ComponentProperties,
DeepPropagationArray,
DragMode,
ResetComponentsOptions,
SymbolToUpOptions,
@ -55,9 +56,9 @@ import {
import { ComponentDynamicValueWatcher } from './ComponentDynamicValueWatcher';
import { DynamicWatchersOptions } from './DynamicValueWatcher';
export interface IComponent extends ExtractMethods<Component> {}
export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {}
export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions {}
export interface IComponent extends ExtractMethods<Component> { }
export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions { }
export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions { }
const escapeRegExp = (str: string) => {
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
@ -128,6 +129,8 @@ export const keyIsCollectionItem = '__is_collection_item';
* @property {Array<String>} [propagate=[]] Indicates an array of properties which will be inhereted by all NEW appended children.
* For example if you create a component likes this: `{ removable: false, draggable: false, propagate: ['removable', 'draggable'] }`
* and append some new component inside, the new added component will get the exact same properties indicated in the `propagate` array (and the `propagate` property itself). Default: `[]`
* @property {Array<String|Function>} [deepPropagate=[]] Indicates an array of properties or functions that will be inherited by all descendant
* components, including those nested within multiple levels of child components.
* @property {Array<Object>} [toolbar=null] Set an array of items to show up inside the toolbar when the component is selected (move, clone, delete).
* Eg. `toolbar: [ { attributes: {class: 'fa fa-arrows'}, command: 'tlb-move' }, ... ]`.
* By default, when `toolbar` property is falsy the editor will add automatically commands `core:component-exit` (select parent component, added if there is one), `tlb-move` (added if `draggable`) , `tlb-clone` (added if `copyable`), `tlb-delete` (added if `removable`).
@ -174,6 +177,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
attributes: {},
traits: ['id', 'title'],
propagate: '',
deepPropagate: '',
dmode: '',
toolbar: null,
delegate: null,
@ -225,12 +229,12 @@ export default class Component extends StyleableModel<ComponentProperties> {
return this.frame?.getPage();
}
preInit() {}
preInit() { }
/**
* Hook method, called once the model is created
*/
init() {}
init() { }
/**
* Hook method, called when the model has been updated (eg. updated some model's property)
@ -238,12 +242,12 @@ export default class Component extends StyleableModel<ComponentProperties> {
* @param {*} value Property value, if triggered after some property update
* @param {*} previous Property previous value, if triggered after some property update
*/
updated(property: string, value: any, previous: any) {}
updated(property: string, value: any, previous: any) { }
/**
* Hook method, called once the model has been removed
*/
removed() {}
removed() { }
em!: EditorModel;
opt!: ComponentOptions;
@ -261,6 +265,8 @@ export default class Component extends StyleableModel<ComponentProperties> {
* @ts-ignore */
collection!: Components;
componentDVListener: ComponentDynamicValueWatcher;
initialParent?: Component;
accumulatedPropagatedProps: DeepPropagationArray = [];
constructor(props: ComponentProperties = {}, opt: ComponentOptions) {
const componentDVListener = new ComponentDynamicValueWatcher(undefined, {
@ -298,11 +304,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
}
opt.em = em;
this.opt = {
...opt,
collectionsStateMap: props[keyCollectionsStateMap],
isCollectionItem: !!props['isCollectionItem'],
};
this.opt = { ...opt };
this.em = em!;
this.config = opt.config || {};
this.addAttributes({
@ -311,6 +313,8 @@ export default class Component extends StyleableModel<ComponentProperties> {
this.ccid = Component.createId(this, opt);
this.preInit();
this.initClasses();
this.listenTo(this, `change:${keyCollectionsStateMap}`, this.handleCollectionsMapStateChange);
this.propagateDeeplyFromParent();
this.initComponents();
this.initTraits();
this.initToolbar();
@ -319,7 +323,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
this.listenTo(this, 'change:tagName', this.tagUpdated);
this.listenTo(this, 'change:attributes', this.attrUpdated);
this.listenTo(this, 'change:attributes:id', this._idUpdated);
this.listenTo(this, `change:${keyCollectionsStateMap}`, this._collectionsStateUpdated);
this.on('change:toolbar', this.__emitUpdateTlb);
this.on('change', this.__onChange);
this.on(keyUpdateInside, this.__propToParent);
@ -382,6 +385,28 @@ export default class Component extends StyleableModel<ComponentProperties> {
return super.set(evaluatedProps, options);
}
propagateDeeplyFromParent() {
const parent = this.parent();
if (!parent) return;
const parentDeepPropagate = parent.accumulatedPropagatedProps;
// Execute functions and set inherited properties
if (parentDeepPropagate) {
const newAttr: Partial<ComponentProperties> = {};
parentDeepPropagate.forEach((prop) => {
if (typeof prop === 'string' && isUndefined(this.get(prop))) {
newAttr[prop] = parent.get(prop as string);
} else if (typeof prop === 'function') {
prop(this); // Execute function on current component
}
});
this.set({ ...newAttr });
}
this.accumulatedPropagatedProps = [...(this.get('deepPropagate') ?? []), ...parentDeepPropagate];
}
__postAdd(opts: { recursive?: boolean } = {}) {
const { em } = this;
const um = em?.UndoManager;
@ -1072,6 +1097,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
return coll as any;
} else {
coll.reset(undefined, opts);
// @ts-ignore
return components ? this.append(components, opts) : ([] as any);
}
}
@ -1118,6 +1144,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
* // -> Component
*/
parent(opts: any = {}): Component | undefined {
if (!this.collection && this.initialParent) return this.initialParent;
const coll = this.collection || (opts.prev && this.prevColl);
return coll ? coll.parent : undefined;
}
@ -1616,6 +1643,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
.toArray()
.map((cmp) => cmp.toJSON());
}
delete obj.deepPropagate;
if (!opts.fromUndo) {
const symbol = obj[keySymbol];
@ -1989,10 +2017,10 @@ export default class Component extends StyleableModel<ComponentProperties> {
selector && selector.set({ name: id, label: id });
}
_collectionsStateUpdated(m: any, v: CollectionsStateMap, opts = {}) {
private handleCollectionsMapStateChange(m: any, v: CollectionsStateMap, opts = {}) {
this.componentDVListener.updateCollectionStateMap(v);
this.components().forEach((child) => {
child.set(keyCollectionsStateMap, v);
this.components()?.forEach((child) => {
child.set?.(keyCollectionsStateMap, v);
});
}

2
packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts

@ -15,7 +15,7 @@ export class ComponentDynamicValueWatcher {
private component: Component | undefined,
options: {
em: EditorModel;
collectionsStateMap: CollectionsStateMap;
collectionsStateMap?: CollectionsStateMap;
},
) {
this.propertyWatcher = new DynamicValueWatcher(component, this.createPropertyUpdater(), options);

32
packages/core/src/dom_components/model/Components.ts

@ -1,5 +1,5 @@
import { isEmpty, isArray, isString, isFunction, each, includes, extend, flatten, keys } from 'underscore';
import Component, { keyCollectionsStateMap } from './Component';
import Component from './Component';
import { AddOptions, Collection } from '../../common';
import { DomComponentsConfig } from '../config/config';
import EditorModel from '../../editor/model/Editor';
@ -17,7 +17,6 @@ import ComponentText from './ComponentText';
import ComponentWrapper from './ComponentWrapper';
import { ComponentsEvents, ParseStringOptions } from '../types';
import { isSymbolInstance, isSymbolRoot, updateSymbolComps } from './SymbolUtils';
import { CollectionsStateMap } from '../../data_sources/model/collection_component/types';
export const getComponentIds = (cmp?: Component | Component[] | Components, res: string[] = []) => {
if (!cmp) return [];
@ -88,8 +87,6 @@ export interface ComponentsOptions {
em: EditorModel;
config?: DomComponentsConfig;
domc?: ComponentManager;
collectionsStateMap?: CollectionsStateMap;
isCollectionItem?: boolean;
}
interface AddComponentOptions extends AddOptions {
@ -331,20 +328,7 @@ Component> {
*/
processDef(mdl: Component | ComponentDefinition | ComponentDefinitionDefined) {
// Avoid processing Models
if (mdl.cid && mdl.ccid) {
const componentCollectionsStateMap = mdl.get(keyCollectionsStateMap);
const parentCollectionsStateMap = this.opt.collectionsStateMap;
mdl.set(keyCollectionsStateMap, {
...componentCollectionsStateMap,
...parentCollectionsStateMap,
});
if (!mdl.get('isCollectionItem') && this.opt.isCollectionItem) {
mdl.set('isCollectionItem', this.opt.isCollectionItem);
}
return mdl;
}
if (mdl.cid && mdl.ccid) return mdl;
const { em, config = {} } = this;
const { processor } = config;
let model = mdl;
@ -392,18 +376,12 @@ Component> {
extend(model, res.props);
}
return {
...(this.opt.isCollectionItem && {
isCollectionItem: this.opt.isCollectionItem,
[keyCollectionsStateMap]: {
...this.opt.collectionsStateMap,
},
}),
...model,
};
return model;
}
onAdd(model: Component, c?: any, opts: { temporary?: boolean } = {}) {
model.initialParent = this.parent;
model.propagateDeeplyFromParent();
const { domc, em } = this;
const style = model.getStyle();
const avoidInline = em && em.getConfig().avoidInlineStyle;

29
packages/core/src/dom_components/model/DynamicValueWatcher.ts

@ -14,24 +14,26 @@ export interface DynamicWatchersOptions {
export class DynamicValueWatcher {
private dynamicVariableListeners: { [key: string]: DynamicVariableListenerManager } = {};
private em: EditorModel;
private collectionsStateMap?: CollectionsStateMap;
constructor(
private component: Component | undefined,
private updateFn: (component: Component | undefined, key: string, value: any) => void,
private options: {
options: {
em: EditorModel;
collectionsStateMap?: CollectionsStateMap;
},
) {}
) {
this.em = options.em;
this.collectionsStateMap = options.collectionsStateMap;
}
bindComponent(component: Component) {
this.component = component;
}
updateCollectionStateMap(collectionsStateMap: CollectionsStateMap) {
this.options = {
...this.options,
collectionsStateMap,
};
this.collectionsStateMap = collectionsStateMap;
const collectionVariablesKeys = this.getDynamicValuesOfType(CollectionVariableType);
const collectionVariablesObject = collectionVariablesKeys.reduce(
@ -71,7 +73,7 @@ export class DynamicValueWatcher {
}
private updateListeners(values: { [key: string]: any }) {
const em = this.options.em;
const { em, collectionsStateMap } = this;
this.removeListeners(Object.keys(values));
const propsKeys = Object.keys(values);
for (let index = 0; index < propsKeys.length; index++) {
@ -80,9 +82,12 @@ export class DynamicValueWatcher {
continue;
}
const { variable } = evaluateDynamicValueDefinition(values[key], this.options);
const { variable } = evaluateDynamicValueDefinition(values[key], {
em,
collectionsStateMap,
});
this.dynamicVariableListeners[key] = new DynamicVariableListenerManager({
em: em,
em,
dataVariable: variable,
updateValueFromDataVariable: (value: any) => {
this.updateFn.bind(this)(this.component, key, value);
@ -92,6 +97,7 @@ export class DynamicValueWatcher {
}
private evaluateValues(values: ObjectAny) {
const { em, collectionsStateMap } = this;
const evaluatedValues: {
[key: string]: any;
} = { ...values };
@ -101,7 +107,10 @@ export class DynamicValueWatcher {
if (!isDynamicValueDefinition(values[key])) {
continue;
}
const { value } = evaluateDynamicValueDefinition(values[key], this.options);
const { value } = evaluateDynamicValueDefinition(values[key], {
em,
collectionsStateMap,
});
evaluatedValues[key] = value;
}

33
packages/core/src/dom_components/model/types.ts

@ -83,6 +83,8 @@ export interface ComponentDelegateProps {
layer?: (cmp: Component) => Component | Nullable;
}
export type DeepPropagationArray = (keyof ComponentProperties | ((component: Component) => void))[];
export interface ComponentProperties {
/**
* Component type, eg. `text`, `image`, `video`, etc.
@ -231,6 +233,37 @@ export interface ComponentProperties {
*/
propagate?: (keyof ComponentProperties)[];
/**
* @property {Array<String|Function>} [deepPropagate=[]]
*
* Indicates an array of properties or functions that will be inherited by all descendant
* components, including those nested within multiple levels of child components.
*
* **Properties:**
* The names of properties (as strings) that will be inherited by all descendants.
*
* **Functions:**
* Functions that will be executed on each descendant component during the propagation process.
* Functions can optionally receive the current component as an argument,
* allowing them to interact with or modify the component's properties or behavior.
*
* **Example:**
*
* ```typescript
* {
* removable: false,
* draggable: false,
* deepPropagate: ['removable', 'draggable', applyDefaultStyles]
* }
* ```
*
* In this example:
* - `removable` and `draggable` properties will be inherited by all descendants.
* - `applyDefaultStyles` is a function that will be executed on each descendant
* component during the propagation process.
*/
deepPropagate?: DeepPropagationArray;
/**
* Set an array of items to show up inside the toolbar when the component is selected (move, clone, delete).
* Eg. `toolbar: [ { attributes: {class: 'fa fa-arrows'}, command: 'tlb-move' }, ... ]`.

Loading…
Cancel
Save