From 21a08cce838733684fb74181ccd49ddd17080d59 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 17 Dec 2024 20:36:55 +0200 Subject: [PATCH 01/87] Add collection component --- .../CollectionComponent.ts | 70 +++++++++++++++++++ .../CollectionComponentView.ts | 4 ++ packages/core/src/dom_components/index.ts | 7 ++ 3 files changed, 81 insertions(+) create mode 100644 packages/core/src/data_sources/model/collection_component/CollectionComponent.ts create mode 100644 packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts new file mode 100644 index 0000000000..cb6c1ea542 --- /dev/null +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -0,0 +1,70 @@ +import { isArray } from 'underscore'; +import Component from '../../../dom_components/model/Component'; +import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; +import { toLowerCase } from '../../../utils/mixins'; +import { ConditionDefinition, ConditionalVariableType } from '../conditional_variables/DataCondition'; +import DataSource from '../DataSource'; + +export const CollectionVariableType = 'collection-variable'; +// Represents the type for defining a loop’s data source. +type CollectionDataSource = + | any[] // Direct array + | { type: 'datasource-variable'; path: string } // Object representing a data source + | { type: 'parent-collection-variable'; path: string }; // Object representing an outer loop variable + +// Defines the collection's configuration, such as start and end indexes, and data source. +interface CollectionConfig { + startIndex?: number; // The starting index for the collection + endIndex?: number | ConditionDefinition; // End index; can be absolute or relative (If omitted will loop over all items) + dataSource: CollectionDataSource; // The data source (array or object reference) +} + +// Provides access to collection state variables during iteration. +interface CollectionStateVariables { + currentIndex: number; // Current collection index + firstIndex: number; // Start index + currentItem: any; // Current item in the iteration + lastIndex: number; // End index + collectionName?: string; // Optional name of the collection + totalItems: number; // Total number of items in the collection + remainingItems: number; // Remaining items in the collection +} + +// Defines the complete structure for a collection, including configuration and state variables. +interface CollectionDefinition { + type: typeof CollectionVariableType; + collectionName?: string; // Optional collection name + config: CollectionConfig; // Loop configuration details + block: ComponentDefinition; // Component definition for each iteration +} + +export default class CollectionComponent extends Component { + constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { + const { block, config } = props.collectionDefinition; + const { dataSource } = config; + console.log("🚀 ~ CollectionComponent ~ constructor ~ dataSource:", dataSource); + let items = []; + switch (true) { + case isArray(dataSource): + items = dataSource; + break; + case typeof dataSource === 'object' && dataSource instanceof DataSource: + items = dataSource.getRecords(); + console.log("🚀 ~ CollectionComponent ~ constructor ~ items:", items) + break; + default: + } + const components = items.map((item) => block(item)); + const conditionalCmptDef = { + type: ConditionalVariableType, + components: components, + // ...super.defaults + }; + //@ts-ignore + super(conditionalCmptDef, opt); + } + + static isComponent(el: HTMLElement) { + return toLowerCase(el.tagName) === ConditionalVariableType; + } +} diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts new file mode 100644 index 0000000000..12be285720 --- /dev/null +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts @@ -0,0 +1,4 @@ +import ComponentView from "../../../dom_components/view/ComponentView"; +import CollectionComponent from './CollectionComponent'; + +export default class CollectionComponentView extends ComponentView { } diff --git a/packages/core/src/dom_components/index.ts b/packages/core/src/dom_components/index.ts index 1111e1a371..8c1235d61f 100644 --- a/packages/core/src/dom_components/index.ts +++ b/packages/core/src/dom_components/index.ts @@ -128,6 +128,8 @@ import { DataVariableType } from '../data_sources/model/DataVariable'; import { ConditionalVariableType } from '../data_sources/model/conditional_variables/DataCondition'; import ComponentConditionalVariable from '../data_sources/model/conditional_variables/ConditionalComponent'; import ConditionalComponentView from '../data_sources/view/ComponentDynamicView'; +import CollectionComponent from '../data_sources/model/collection_component/CollectionComponent'; +import CollectionComponentView from '../data_sources/model/collection_component/CollectionComponentView'; export type ComponentEvent = | 'component:create' @@ -193,6 +195,11 @@ export interface CanMoveResult { export default class ComponentManager extends ItemManagerModule { componentTypes: ComponentStackItem[] = [ + { + id: 'collection-component', + model: CollectionComponent, + view: CollectionComponentView, + }, { id: ConditionalVariableType, model: ComponentConditionalVariable, From d49cd3999326b4ab359240bd4766d2538de0b2a1 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 18 Dec 2024 00:05:28 +0200 Subject: [PATCH 02/87] Add resolving collection variables --- .../CollectionComponent.ts | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index cb6c1ea542..0b9e7b9664 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -1,3 +1,4 @@ +import { ComponentDefinitionDefined } from './../../../dom_components/model/types'; import { isArray } from 'underscore'; import Component from '../../../dom_components/model/Component'; import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; @@ -42,7 +43,6 @@ export default class CollectionComponent extends Component { constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { const { block, config } = props.collectionDefinition; const { dataSource } = config; - console.log("🚀 ~ CollectionComponent ~ constructor ~ dataSource:", dataSource); let items = []; switch (true) { case isArray(dataSource): @@ -50,17 +50,17 @@ export default class CollectionComponent extends Component { break; case typeof dataSource === 'object' && dataSource instanceof DataSource: items = dataSource.getRecords(); - console.log("🚀 ~ CollectionComponent ~ constructor ~ items:", items) break; default: } - const components = items.map((item) => block(item)); + + const components: ComponentDefinitionDefined[] = items.map((item) => resolveBlockValue(item, block)); const conditionalCmptDef = { + ...props, type: ConditionalVariableType, components: components, - // ...super.defaults }; - //@ts-ignore + // @ts-expect-error super(conditionalCmptDef, opt); } @@ -68,3 +68,30 @@ export default class CollectionComponent extends Component { return toLowerCase(el.tagName) === ConditionalVariableType; } } + +function resolveBlockValue(item: any, block: any) { + const stringifiedItem = JSON.stringify(item); + const keys = Object.keys(block); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = block[key]; + if (typeof value === 'object') { + if (value.type === 'parent-collection-variable') { + if (!value.path || value.path === 'item') { + block[key] = stringifiedItem; + } else { + const arr = value.path.split('.'); + if (item.get) { + block[key] = item.get?.(arr[0]); + } else { + block[key] = item[arr[0]]; + } + } + } else { + block[key] = resolveBlockValue(item, value); + } + } + } + + return block; +} From 4aee355db400c1b956e28ccb02cb949d8823d376 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 18 Dec 2024 00:24:03 +0200 Subject: [PATCH 03/87] Allow data-variable paths as a collection's datasource --- .../model/collection_component/CollectionComponent.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 0b9e7b9664..fe6a308879 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -5,6 +5,7 @@ import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../. import { toLowerCase } from '../../../utils/mixins'; import { ConditionDefinition, ConditionalVariableType } from '../conditional_variables/DataCondition'; import DataSource from '../DataSource'; +import { DataVariableType } from '../DataVariable'; export const CollectionVariableType = 'collection-variable'; // Represents the type for defining a loop’s data source. @@ -51,6 +52,14 @@ export default class CollectionComponent extends Component { case typeof dataSource === 'object' && dataSource instanceof DataSource: items = dataSource.getRecords(); break; + case typeof dataSource === 'object' && dataSource.type === DataVariableType: + const resolvedPath = opt.em.DataSources.fromPath(dataSource.path); + if (typeof resolvedPath[0] === 'object' && resolvedPath[0] instanceof DataSource) { + items = resolvedPath[0].getRecords(); + } else { + items = resolvedPath; + } + break; default: } From 7294394df9c73c05e92d86bf34f68c80eaff1394 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 18 Dec 2024 00:26:09 +0200 Subject: [PATCH 04/87] make collection items undraggable and undroppable --- .../model/collection_component/CollectionComponent.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index fe6a308879..d509f0159e 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -68,6 +68,7 @@ export default class CollectionComponent extends Component { ...props, type: ConditionalVariableType, components: components, + dropbbable: false, }; // @ts-expect-error super(conditionalCmptDef, opt); @@ -101,6 +102,8 @@ function resolveBlockValue(item: any, block: any) { } } } - + block['droppable'] = false; + block['draggable'] = false; + return block; } From 17b84fb4634028ae83f70e811ebea26c40bf78bb Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 18 Dec 2024 04:48:27 +0200 Subject: [PATCH 05/87] Add many info to the currentItem --- .../CollectionComponent.ts | 131 +++++++++++++----- 1 file changed, 99 insertions(+), 32 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index d509f0159e..a2d17085ad 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -3,11 +3,11 @@ import { isArray } from 'underscore'; import Component from '../../../dom_components/model/Component'; import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; import { toLowerCase } from '../../../utils/mixins'; -import { ConditionDefinition, ConditionalVariableType } from '../conditional_variables/DataCondition'; +import { ConditionDefinition } from '../conditional_variables/DataCondition'; import DataSource from '../DataSource'; import { DataVariableType } from '../DataVariable'; -export const CollectionVariableType = 'collection-variable'; +export const CollectionVariableType = 'collection-component'; // Represents the type for defining a loop’s data source. type CollectionDataSource = | any[] // Direct array @@ -44,7 +44,7 @@ export default class CollectionComponent extends Component { constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { const { block, config } = props.collectionDefinition; const { dataSource } = config; - let items = []; + let items: any[] = []; switch (true) { case isArray(dataSource): items = dataSource; @@ -53,20 +53,31 @@ export default class CollectionComponent extends Component { items = dataSource.getRecords(); break; case typeof dataSource === 'object' && dataSource.type === DataVariableType: - const resolvedPath = opt.em.DataSources.fromPath(dataSource.path); - if (typeof resolvedPath[0] === 'object' && resolvedPath[0] instanceof DataSource) { - items = resolvedPath[0].getRecords(); + const pathArr = dataSource.path.split('.'); + if (pathArr.length === 1) { + const resolvedPath = opt.em.DataSources.getValue(dataSource.path, []); + const keys = Object.keys(resolvedPath); + items = keys.map(key => resolvedPath[key]); } else { - items = resolvedPath; + items = opt.em.DataSources.getValue(dataSource.path, []); } break; default: } - const components: ComponentDefinitionDefined[] = items.map((item) => resolveBlockValue(item, block)); + const components: ComponentDefinitionDefined[] = items.map((item: any, index) => resolveBlockValue({ + currentIndex: index, + firstIndex: config.startIndex, + currentItem: item, + lastIndex: config.endIndex, + collectionName: props.collectionName, + totalItems: items.length, + remainingItems: items.length - index, + }, block) + ); const conditionalCmptDef = { ...props, - type: ConditionalVariableType, + type: CollectionVariableType, components: components, dropbbable: false, }; @@ -75,35 +86,91 @@ export default class CollectionComponent extends Component { } static isComponent(el: HTMLElement) { - return toLowerCase(el.tagName) === ConditionalVariableType; + return toLowerCase(el.tagName) === CollectionVariableType; } } -function resolveBlockValue(item: any, block: any) { - const stringifiedItem = JSON.stringify(item); - const keys = Object.keys(block); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const value = block[key]; - if (typeof value === 'object') { - if (value.type === 'parent-collection-variable') { - if (!value.path || value.path === 'item') { - block[key] = stringifiedItem; - } else { - const arr = value.path.split('.'); - if (item.get) { - block[key] = item.get?.(arr[0]); - } else { - block[key] = item[arr[0]]; +/** + * Deeply clones an object. + * @template T The type of the object to clone. + * @param {T} obj The object to clone. + * @returns {T} A deep clone of the object, or the original object if it's not an object or is null. Returns undefined if input is undefined. + */ +function deepCloneObject | null | undefined>(obj: T): T { + if (obj === null) return null as T; + if (obj === undefined) return undefined as T; + if (typeof obj !== 'object' || Array.isArray(obj)) { + return obj; // Return primitives directly + } + + const clonedObj: Record = {}; + + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + clonedObj[key] = deepCloneObject(obj[key]); + } + } + + return clonedObj as T; +} + +function resolveBlockValue(item: any, block: any): any { + const clonedBlock = deepCloneObject(block); + + if (typeof clonedBlock === 'object' && clonedBlock !== null) { + const stringifiedItem = JSON.stringify(item.currentItem); + const keys = Object.keys(clonedBlock); + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + let value = clonedBlock[key]; + + if (typeof value === 'object') { + if (value.type === 'parent-collection-variable') { + if (value.variable_type === 'current_item') { + if (!value.path) { + clonedBlock[key] = stringifiedItem; + } else { + const pathParts = value.path.split('.'); + let resolvedValue = item.currentItem; + for (const part of pathParts) { + if (resolvedValue && typeof resolvedValue === 'object' && resolvedValue.hasOwnProperty(part)) { + resolvedValue = resolvedValue[part]; + } else { + resolvedValue = undefined; // Handle cases where the path doesn't exist + break; + } + } + clonedBlock[key] = resolvedValue; + } + } else if (value.variable_type === 'current_index') { + clonedBlock[key] = String(item.currentIndex); + } else if (value.variable_type === 'first_index') { + clonedBlock[key] = String(item.firstIndex); + } else if (value.variable_type === 'last_index') { + clonedBlock[key] = String(item.lastIndex); + } else if (value.variable_type === 'collection_name') { + clonedBlock[key] = String(item.collectionName); + } else if (value.variable_type === 'total_items') { + clonedBlock[key] = String(item.totalItems); + } else if (value.variable_type === 'remaining_items') { + clonedBlock[key] = String(item.remainingItems); } + } else if (Array.isArray(value)) { + // Handle arrays: Resolve each item in the array + clonedBlock[key] = value.map((itemInArray: any) => { + if (typeof itemInArray === 'object') { + return resolveBlockValue(item, itemInArray); + } + + return itemInArray; // Return primitive values directly + }); + } else { + clonedBlock[key] = resolveBlockValue(item, value); } - } else { - block[key] = resolveBlockValue(item, value); } } } - block['droppable'] = false; - block['draggable'] = false; - - return block; + + return clonedBlock; } From 7db4187e07927675becaceab1c5edd191969c368 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 18 Dec 2024 15:41:53 +0200 Subject: [PATCH 06/87] fix passing a datasource as collection's datasource --- .../model/collection_component/CollectionComponent.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index a2d17085ad..876c7a41af 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -44,13 +44,16 @@ export default class CollectionComponent extends Component { constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { const { block, config } = props.collectionDefinition; const { dataSource } = config; - let items: any[] = []; + let items: CollectionStateVariables[] = []; switch (true) { case isArray(dataSource): items = dataSource; break; case typeof dataSource === 'object' && dataSource instanceof DataSource: - items = dataSource.getRecords(); + const id = dataSource.get('id')!; + const resolvedPath = opt.em.DataSources.getValue(id, []); + const keys = Object.keys(resolvedPath); + items = keys.map(key => resolvedPath[key]); break; case typeof dataSource === 'object' && dataSource.type === DataVariableType: const pathArr = dataSource.path.split('.'); @@ -65,7 +68,7 @@ export default class CollectionComponent extends Component { default: } - const components: ComponentDefinitionDefined[] = items.map((item: any, index) => resolveBlockValue({ + const components: ComponentDefinitionDefined[] = items.map((item: CollectionStateVariables, index) => resolveBlockValue({ currentIndex: index, firstIndex: config.startIndex, currentItem: item, From 09da4468fe1ceff22d0c41c84751867ca27092e4 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 19 Dec 2024 20:51:44 +0200 Subject: [PATCH 07/87] Allow nested collections --- .../CollectionComponent.ts | 145 ++++++++++-------- .../src/dom_components/model/Component.ts | 31 +++- 2 files changed, 103 insertions(+), 73 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 876c7a41af..5b069cb08c 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -23,26 +23,26 @@ interface CollectionConfig { // Provides access to collection state variables during iteration. interface CollectionStateVariables { - currentIndex: number; // Current collection index - firstIndex: number; // Start index - currentItem: any; // Current item in the iteration - lastIndex: number; // End index - collectionName?: string; // Optional name of the collection - totalItems: number; // Total number of items in the collection - remainingItems: number; // Remaining items in the collection + current_index: number; // Current collection index + first_index: number; // Start index + current_item: any; // Current item in the iteration + last_index: number; // End index + collection_name?: string; // Optional name of the collection + total_items: number; // Total number of items in the collection + remaining_items: number; // Remaining items in the collection } // Defines the complete structure for a collection, including configuration and state variables. interface CollectionDefinition { type: typeof CollectionVariableType; - collectionName?: string; // Optional collection name + collection_name?: string; // Optional collection name config: CollectionConfig; // Loop configuration details block: ComponentDefinition; // Component definition for each iteration } export default class CollectionComponent extends Component { constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { - const { block, config } = props.collectionDefinition; + const { collection_name, block, config } = props.collectionDefinition; const { dataSource } = config; let items: CollectionStateVariables[] = []; switch (true) { @@ -68,16 +68,30 @@ export default class CollectionComponent extends Component { default: } - const components: ComponentDefinitionDefined[] = items.map((item: CollectionStateVariables, index) => resolveBlockValue({ - currentIndex: index, - firstIndex: config.startIndex, - currentItem: item, - lastIndex: config.endIndex, - collectionName: props.collectionName, - totalItems: items.length, - remainingItems: items.length - index, - }, block) - ); + const components: ComponentDefinitionDefined[] = items.map((item: CollectionStateVariables, index) => { + const innerMostCollectionItem = { + collection_name, + current_index: index, + first_index: config.startIndex, + current_item: item, + last_index: config.endIndex, + total_items: items.length, + remaining_items: items.length - index, + }; + + const allCollectionItems = { + ...props.collectionsItems, + [innerMostCollectionItem.collection_name ? innerMostCollectionItem.collection_name : 'innerMostCollectionItem']: + innerMostCollectionItem, + innerMostCollectionItem + } + + let components = resolveBlockValues(allCollectionItems, block); + components['collectionsItems'] = allCollectionItems; + + return components; + }); + const conditionalCmptDef = { ...props, type: CollectionVariableType, @@ -117,59 +131,41 @@ function deepCloneObject | null | undefined>(obj: return clonedObj as T; } -function resolveBlockValue(item: any, block: any): any { +function resolveBlockValues(context: any, block: any): any { + console.log("🚀 ~ resolveBlockValues ~ context:", context) + const { innerMostCollectionItem } = context; const clonedBlock = deepCloneObject(block); if (typeof clonedBlock === 'object' && clonedBlock !== null) { - const stringifiedItem = JSON.stringify(item.currentItem); - const keys = Object.keys(clonedBlock); - - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - let value = clonedBlock[key]; - - if (typeof value === 'object') { - if (value.type === 'parent-collection-variable') { - if (value.variable_type === 'current_item') { - if (!value.path) { - clonedBlock[key] = stringifiedItem; - } else { - const pathParts = value.path.split('.'); - let resolvedValue = item.currentItem; - for (const part of pathParts) { - if (resolvedValue && typeof resolvedValue === 'object' && resolvedValue.hasOwnProperty(part)) { - resolvedValue = resolvedValue[part]; - } else { - resolvedValue = undefined; // Handle cases where the path doesn't exist - break; - } - } - clonedBlock[key] = resolvedValue; - } - } else if (value.variable_type === 'current_index') { - clonedBlock[key] = String(item.currentIndex); - } else if (value.variable_type === 'first_index') { - clonedBlock[key] = String(item.firstIndex); - } else if (value.variable_type === 'last_index') { - clonedBlock[key] = String(item.lastIndex); - } else if (value.variable_type === 'collection_name') { - clonedBlock[key] = String(item.collectionName); - } else if (value.variable_type === 'total_items') { - clonedBlock[key] = String(item.totalItems); - } else if (value.variable_type === 'remaining_items') { - clonedBlock[key] = String(item.remainingItems); + const blockKeys = Object.keys(clonedBlock); + + for (const key of blockKeys) { + let blockValue = clonedBlock[key]; + + if (typeof blockValue === 'object' && blockValue !== null) { + if (blockValue.type === 'parent-collection-variable') { + const collectionItem = blockValue.collection_name + ? context[blockValue.collection_name] + : innerMostCollectionItem; + if (!collectionItem) continue; + + switch (blockValue.variable_type) { + case 'current_item': + clonedBlock[key] = blockValue.path + ? resolvePathValue(collectionItem, blockValue.path) + : JSON.stringify(collectionItem); + break; + default: + clonedBlock[key] = collectionItem[blockValue.variable_type]; + break; // Handle unexpected variable types gracefully } - } else if (Array.isArray(value)) { - // Handle arrays: Resolve each item in the array - clonedBlock[key] = value.map((itemInArray: any) => { - if (typeof itemInArray === 'object') { - return resolveBlockValue(item, itemInArray); - } - - return itemInArray; // Return primitive values directly - }); + } else if (Array.isArray(blockValue)) { + // Resolve each item in the array + clonedBlock[key] = blockValue.map((arrayItem: any) => + typeof arrayItem === 'object' ? resolveBlockValues(context, arrayItem) : arrayItem + ); } else { - clonedBlock[key] = resolveBlockValue(item, value); + clonedBlock[key] = resolveBlockValues(context, blockValue); } } } @@ -177,3 +173,18 @@ function resolveBlockValue(item: any, block: any): any { return clonedBlock; } + +function resolvePathValue(object: any, path: string): any { + const pathSegments = path.split('.'); + let resolvedValue = object; + + for (const segment of pathSegments) { + if (resolvedValue && typeof resolvedValue === 'object' && segment in resolvedValue) { + resolvedValue = resolvedValue[segment]; + } else { + return undefined; // Return undefined if the path doesn't exist + } + } + + return resolvedValue; +} diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index fe93d6931d..35b22ebec1 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -56,9 +56,9 @@ import { ConditionalVariableType, DataCondition } from '../../data_sources/model import { isDynamicValue, isDynamicValueDefinition } from '../../data_sources/model/utils'; import { DynamicValueDefinition } from '../../data_sources/types'; -export interface IComponent extends ExtractMethods {} +export interface IComponent extends ExtractMethods { } -export interface SetAttrOptions extends SetOptions, UpdateStyleOptions {} +export interface SetAttrOptions extends SetOptions, UpdateStyleOptions { } const escapeRegExp = (str: string) => { return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); @@ -225,12 +225,12 @@ export default class Component extends StyleableModel { 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 +238,12 @@ export default class Component extends StyleableModel { * @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; @@ -262,6 +262,25 @@ export default class Component extends StyleableModel { collection!: Components; constructor(props: ComponentProperties = {}, opt: ComponentOptions) { + if (Array.isArray(props['components'])) { + props['components']?.map(component => { + return { + ...component, + collectionsItems: { + ...props.collectionsItems + } + } + }) + } else if (typeof props['components'] === 'object') { + props['components'] = { + ...props['components'], + // @ts-ignore + collectionsItems: { + ...props.collectionsItems + } + } + } + super(props, opt); bindAll(this, '__upSymbProps', '__upSymbCls', '__upSymbComps'); const em = opt.em; From 2f6f13c06d6fafb3dd3afd12bbbcbea9a3ec8f56 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 19 Dec 2024 20:58:20 +0200 Subject: [PATCH 08/87] Fix collection iteration values --- .../CollectionComponent.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 5b069cb08c..510c9d4f9b 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -16,17 +16,17 @@ type CollectionDataSource = // Defines the collection's configuration, such as start and end indexes, and data source. interface CollectionConfig { - startIndex?: number; // The starting index for the collection - endIndex?: number | ConditionDefinition; // End index; can be absolute or relative (If omitted will loop over all items) + start_index?: number; // The starting index for the collection + end_index?: number | ConditionDefinition; // End index; can be absolute or relative (If omitted will loop over all items) dataSource: CollectionDataSource; // The data source (array or object reference) } // Provides access to collection state variables during iteration. interface CollectionStateVariables { current_index: number; // Current collection index - first_index: number; // Start index + start_index: number; // Start index current_item: any; // Current item in the iteration - last_index: number; // End index + end_index: number; // End index collection_name?: string; // Optional name of the collection total_items: number; // Total number of items in the collection remaining_items: number; // Remaining items in the collection @@ -43,7 +43,11 @@ interface CollectionDefinition { export default class CollectionComponent extends Component { constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { const { collection_name, block, config } = props.collectionDefinition; - const { dataSource } = config; + const { + start_index = 0, + end_index = Number.MAX_VALUE, + dataSource = [], + } = config let items: CollectionStateVariables[] = []; switch (true) { case isArray(dataSource): @@ -72,26 +76,26 @@ export default class CollectionComponent extends Component { const innerMostCollectionItem = { collection_name, current_index: index, - first_index: config.startIndex, current_item: item, - last_index: config.endIndex, + start_index, + end_index, total_items: items.length, - remaining_items: items.length - index, + remaining_items: items.length - (index + 1), }; const allCollectionItems = { ...props.collectionsItems, [innerMostCollectionItem.collection_name ? innerMostCollectionItem.collection_name : 'innerMostCollectionItem']: - innerMostCollectionItem, + innerMostCollectionItem, innerMostCollectionItem } let components = resolveBlockValues(allCollectionItems, block); components['collectionsItems'] = allCollectionItems; - + return components; }); - + const conditionalCmptDef = { ...props, type: CollectionVariableType, From 9d142f42fcc0c67540d2d856cc6aa8fddec3940a Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 19 Dec 2024 21:02:20 +0200 Subject: [PATCH 09/87] Fix getting current item --- .../model/collection_component/CollectionComponent.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 510c9d4f9b..a1d0d6cca8 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -136,7 +136,6 @@ function deepCloneObject | null | undefined>(obj: } function resolveBlockValues(context: any, block: any): any { - console.log("🚀 ~ resolveBlockValues ~ context:", context) const { innerMostCollectionItem } = context; const clonedBlock = deepCloneObject(block); @@ -156,8 +155,8 @@ function resolveBlockValues(context: any, block: any): any { switch (blockValue.variable_type) { case 'current_item': clonedBlock[key] = blockValue.path - ? resolvePathValue(collectionItem, blockValue.path) - : JSON.stringify(collectionItem); + ? resolvePathValue(collectionItem.current_item, blockValue.path) + : JSON.stringify(collectionItem.current_item); break; default: clonedBlock[key] = collectionItem[blockValue.variable_type]; From 01e81f48c2257d3ca8a36b0d46fe555ce26e8733 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 19 Dec 2024 21:16:35 +0200 Subject: [PATCH 10/87] Use values from the innermost collection instead of the outermost one. --- .../model/collection_component/CollectionComponent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index a1d0d6cca8..bdfeef1991 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -144,6 +144,7 @@ function resolveBlockValues(context: any, block: any): any { for (const key of blockKeys) { let blockValue = clonedBlock[key]; + if (key === 'collectionDefinition') continue; if (typeof blockValue === 'object' && blockValue !== null) { if (blockValue.type === 'parent-collection-variable') { @@ -160,7 +161,7 @@ function resolveBlockValues(context: any, block: any): any { break; default: clonedBlock[key] = collectionItem[blockValue.variable_type]; - break; // Handle unexpected variable types gracefully + break; } } else if (Array.isArray(blockValue)) { // Resolve each item in the array From 4a22be0c0e4d0f7820a4deba422c828935036154 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 19 Dec 2024 21:22:28 +0200 Subject: [PATCH 11/87] Throw an error when using the name of a non-existent collection. --- .../CollectionComponent.ts | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index bdfeef1991..c2b6d5e5e3 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -151,17 +151,18 @@ function resolveBlockValues(context: any, block: any): any { const collectionItem = blockValue.collection_name ? context[blockValue.collection_name] : innerMostCollectionItem; - if (!collectionItem) continue; - - switch (blockValue.variable_type) { - case 'current_item': - clonedBlock[key] = blockValue.path - ? resolvePathValue(collectionItem.current_item, blockValue.path) - : JSON.stringify(collectionItem.current_item); - break; - default: - clonedBlock[key] = collectionItem[blockValue.variable_type]; - break; + if (!collectionItem) { + throw new Error( + `Collection not found: ${blockValue.collection_name || 'default collection'}` + ); + } + + if (blockValue.variable_type === 'current_item') { + clonedBlock[key] = blockValue.path + ? resolvePathValue(collectionItem.current_item, blockValue.path) + : JSON.stringify(collectionItem.current_item); + } else { + clonedBlock[key] = collectionItem[blockValue.variable_type]; } } else if (Array.isArray(blockValue)) { // Resolve each item in the array From 72539f13415ba98294f72e18178a719f49128b91 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 19 Dec 2024 21:24:48 +0200 Subject: [PATCH 12/87] refactor componentCollectionKey --- .../model/collection_component/CollectionComponent.ts | 3 ++- packages/core/src/dom_components/model/Component.ts | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index c2b6d5e5e3..9a180ad309 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -39,6 +39,7 @@ interface CollectionDefinition { config: CollectionConfig; // Loop configuration details block: ComponentDefinition; // Component definition for each iteration } +export const componentCollectionKey = 'collectionsItems'; export default class CollectionComponent extends Component { constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { @@ -91,7 +92,7 @@ export default class CollectionComponent extends Component { } let components = resolveBlockValues(allCollectionItems, block); - components['collectionsItems'] = allCollectionItems; + components[componentCollectionKey] = allCollectionItems; return components; }); diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 35b22ebec1..7cbd252977 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -55,6 +55,7 @@ import TraitDataVariable from '../../data_sources/model/TraitDataVariable'; import { ConditionalVariableType, DataCondition } from '../../data_sources/model/conditional_variables/DataCondition'; import { isDynamicValue, isDynamicValueDefinition } from '../../data_sources/model/utils'; import { DynamicValueDefinition } from '../../data_sources/types'; +import { componentCollectionKey } from '../../data_sources/model/collection_component/CollectionComponent'; export interface IComponent extends ExtractMethods { } @@ -266,8 +267,8 @@ export default class Component extends StyleableModel { props['components']?.map(component => { return { ...component, - collectionsItems: { - ...props.collectionsItems + [componentCollectionKey]: { + ...props.componentCollectionKey } } }) @@ -275,8 +276,8 @@ export default class Component extends StyleableModel { props['components'] = { ...props['components'], // @ts-ignore - collectionsItems: { - ...props.collectionsItems + [componentCollectionKey]: { + ...props.componentCollectionKey } } } From ddab92951090700bf3de9b42ff6d54deba4fefc8 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 19 Dec 2024 22:57:10 +0200 Subject: [PATCH 13/87] remove circular dependancy --- packages/core/src/dom_components/model/Component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 7cbd252977..bc61b682f2 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -267,7 +267,7 @@ export default class Component extends StyleableModel { props['components']?.map(component => { return { ...component, - [componentCollectionKey]: { + collectionsItems: { ...props.componentCollectionKey } } @@ -276,7 +276,7 @@ export default class Component extends StyleableModel { props['components'] = { ...props['components'], // @ts-ignore - [componentCollectionKey]: { + collectionsItems: { ...props.componentCollectionKey } } From d631ca73036ac7343835bab8d84d81665d6b723c Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 19 Dec 2024 23:00:53 +0200 Subject: [PATCH 14/87] Add start and end index for collections --- .../CollectionComponent.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 9a180ad309..1ce8cdc15f 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -73,15 +73,20 @@ export default class CollectionComponent extends Component { default: } - const components: ComponentDefinitionDefined[] = items.map((item: CollectionStateVariables, index) => { + const components: ComponentDefinitionDefined[] = []; + const resolvedStartIndex = Math.max(0, start_index); + const resolvedEndIndex = Math.min(items.length - 1, end_index); + for (let index = resolvedStartIndex; index <= resolvedEndIndex; index++) { + const item = items[index]; + const total_items = resolvedEndIndex - resolvedStartIndex + 1; const innerMostCollectionItem = { collection_name, current_index: index, current_item: item, - start_index, - end_index, - total_items: items.length, - remaining_items: items.length - (index + 1), + start_index: resolvedStartIndex, + end_index: resolvedEndIndex, + total_items: total_items, + remaining_items: total_items - (index + 1), }; const allCollectionItems = { @@ -91,11 +96,9 @@ export default class CollectionComponent extends Component { innerMostCollectionItem } - let components = resolveBlockValues(allCollectionItems, block); - components[componentCollectionKey] = allCollectionItems; - - return components; - }); + const component = resolveBlockValues(allCollectionItems, block); + components.push(component); + } const conditionalCmptDef = { ...props, From 050a4cd1fc1f92df4a951a813a074986c9f38ac2 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 25 Dec 2024 06:33:49 +0200 Subject: [PATCH 15/87] use symbols for collection components --- .../CollectionComponent.ts | 128 +++++++++++++----- 1 file changed, 91 insertions(+), 37 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 1ce8cdc15f..1d5c99f7f9 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -1,4 +1,3 @@ -import { ComponentDefinitionDefined } from './../../../dom_components/model/types'; import { isArray } from 'underscore'; import Component from '../../../dom_components/model/Component'; import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; @@ -6,6 +5,7 @@ import { toLowerCase } from '../../../utils/mixins'; import { ConditionDefinition } from '../conditional_variables/DataCondition'; import DataSource from '../DataSource'; import { DataVariableType } from '../DataVariable'; +import { ObjectAny } from '../../../common'; export const CollectionVariableType = 'collection-component'; // Represents the type for defining a loop’s data source. @@ -48,8 +48,8 @@ export default class CollectionComponent extends Component { start_index = 0, end_index = Number.MAX_VALUE, dataSource = [], - } = config - let items: CollectionStateVariables[] = []; + } = config; + let items: any[] = []; switch (true) { case isArray(dataSource): items = dataSource; @@ -58,14 +58,20 @@ export default class CollectionComponent extends Component { const id = dataSource.get('id')!; const resolvedPath = opt.em.DataSources.getValue(id, []); const keys = Object.keys(resolvedPath); - items = keys.map(key => resolvedPath[key]); + items = keys.map(key => ({ + type: DataVariableType, + path: id + '.' + key, + })); break; case typeof dataSource === 'object' && dataSource.type === DataVariableType: const pathArr = dataSource.path.split('.'); if (pathArr.length === 1) { const resolvedPath = opt.em.DataSources.getValue(dataSource.path, []); const keys = Object.keys(resolvedPath); - items = keys.map(key => resolvedPath[key]); + items = keys.map(key => ({ + type: DataVariableType, + path: id + '.' + key, + })); } else { items = opt.em.DataSources.getValue(dataSource.path, []); } @@ -73,12 +79,36 @@ export default class CollectionComponent extends Component { default: } - const components: ComponentDefinitionDefined[] = []; + const components: ComponentDefinition[] = []; const resolvedStartIndex = Math.max(0, start_index); const resolvedEndIndex = Math.min(items.length - 1, end_index); + const item = items[resolvedStartIndex]; + let symbolMain; + const total_items = resolvedEndIndex - resolvedStartIndex + 1; + const innerMostCollectionItem = { + collection_name, + current_index: resolvedStartIndex, + current_item: item, + start_index: resolvedStartIndex, + end_index: resolvedEndIndex, + total_items: total_items, + remaining_items: total_items - (resolvedStartIndex + 1), + }; + + const allCollectionItem = { + ...props.collectionsItems, + [innerMostCollectionItem.collection_name ? innerMostCollectionItem.collection_name : 'innerMostCollectionItem']: + innerMostCollectionItem, + innerMostCollectionItem + } + const { clonedBlock, overrideKeys } = resolveBlockValues(allCollectionItem, block); + const type = opt.em.Components.getType(clonedBlock?.type || 'default'); + const model = type.model; + const symbol = new model(clonedBlock, opt); + symbolMain = opt.em.Components.addSymbol(symbol); + for (let index = resolvedStartIndex; index <= resolvedEndIndex; index++) { const item = items[index]; - const total_items = resolvedEndIndex - resolvedStartIndex + 1; const innerMostCollectionItem = { collection_name, current_index: index, @@ -89,15 +119,33 @@ export default class CollectionComponent extends Component { remaining_items: total_items - (index + 1), }; - const allCollectionItems = { + const allCollectionItem = { ...props.collectionsItems, [innerMostCollectionItem.collection_name ? innerMostCollectionItem.collection_name : 'innerMostCollectionItem']: innerMostCollectionItem, innerMostCollectionItem } + const { overrideKeys } = resolveBlockValues(allCollectionItem, block); + const instance = opt.em.Components.addSymbol(symbolMain!); + const children: any[] = [] + Object.keys(overrideKeys).length && instance!.setSymbolOverride(Object.keys(overrideKeys)); + instance!.set(overrideKeys); + for (let i = 0; i < instance!.components().length; i++) { + const childBlock = block['components'][i]; + const { overrideKeys } = resolveBlockValues(allCollectionItem, deepCloneObject(childBlock)); + const child = opt.em.Components.addSymbol(symbolMain!.components().at(i)) + Object.keys(overrideKeys).length && child!.setSymbolOverride(Object.keys(overrideKeys)); + child!.set(overrideKeys) + children.push(child); + } - const component = resolveBlockValues(allCollectionItems, block); - components.push(component); + const componentJSON = instance?.toJSON(); + const cmpDefinition = { + ...componentJSON, + components: children + }; + + components.push(cmpDefinition); } const conditionalCmptDef = { @@ -139,9 +187,10 @@ function deepCloneObject | null | undefined>(obj: return clonedObj as T; } -function resolveBlockValues(context: any, block: any): any { +function resolveBlockValues(context: any, block: any) { const { innerMostCollectionItem } = context; const clonedBlock = deepCloneObject(block); + const overrideKeys: ObjectAny = {}; if (typeof clonedBlock === 'object' && clonedBlock !== null) { const blockKeys = Object.keys(clonedBlock); @@ -149,12 +198,13 @@ function resolveBlockValues(context: any, block: any): any { for (const key of blockKeys) { let blockValue = clonedBlock[key]; if (key === 'collectionDefinition') continue; + let shouldBeOverridden = false; if (typeof blockValue === 'object' && blockValue !== null) { + const collectionItem = blockValue.collection_name + ? context[blockValue.collection_name] + : innerMostCollectionItem; if (blockValue.type === 'parent-collection-variable') { - const collectionItem = blockValue.collection_name - ? context[blockValue.collection_name] - : innerMostCollectionItem; if (!collectionItem) { throw new Error( `Collection not found: ${blockValue.collection_name || 'default collection'}` @@ -162,38 +212,42 @@ function resolveBlockValues(context: any, block: any): any { } if (blockValue.variable_type === 'current_item') { - clonedBlock[key] = blockValue.path - ? resolvePathValue(collectionItem.current_item, blockValue.path) - : JSON.stringify(collectionItem.current_item); + if (collectionItem.current_item.type === DataVariableType) { + const path = collectionItem.current_item.path ? `${collectionItem.current_item.path}.${blockValue.path}` : blockValue.path; + clonedBlock[key] = { + ...collectionItem.current_item, + path + }; + } } else { clonedBlock[key] = collectionItem[blockValue.variable_type]; } + + shouldBeOverridden = true; } else if (Array.isArray(blockValue)) { // Resolve each item in the array - clonedBlock[key] = blockValue.map((arrayItem: any) => - typeof arrayItem === 'object' ? resolveBlockValues(context, arrayItem) : arrayItem - ); + clonedBlock[key] = blockValue.map((arrayItem: any) => { + const { clonedBlock, overrideKeys: itemOverrideKeys } = resolveBlockValues(context, arrayItem) + if (Object.keys(itemOverrideKeys).length > 0) { + shouldBeOverridden = true; + } + return typeof arrayItem === 'object' ? clonedBlock : arrayItem + }); } else { - clonedBlock[key] = resolveBlockValues(context, blockValue); - } - } - } - } - - return clonedBlock; -} + const { clonedBlock, overrideKeys: itemOverrideKeys } = resolveBlockValues(context, blockValue).clonedBlock; + clonedBlock[key] = clonedBlock; -function resolvePathValue(object: any, path: string): any { - const pathSegments = path.split('.'); - let resolvedValue = object; + if (Object.keys(itemOverrideKeys).length > 0) { + shouldBeOverridden = true; + } + } - for (const segment of pathSegments) { - if (resolvedValue && typeof resolvedValue === 'object' && segment in resolvedValue) { - resolvedValue = resolvedValue[segment]; - } else { - return undefined; // Return undefined if the path doesn't exist + if (shouldBeOverridden && key !== 'components') { + overrideKeys[key] = clonedBlock[key] + } + } } } - return resolvedValue; + return { clonedBlock, overrideKeys }; } From b142b14ea786f9c9910b06244a67143ebc51ebb6 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 25 Dec 2024 09:31:17 +0200 Subject: [PATCH 16/87] refactor collection symbols --- .../CollectionComponent.ts | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 1d5c99f7f9..c8bc21dc71 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -6,6 +6,7 @@ import { ConditionDefinition } from '../conditional_variables/DataCondition'; import DataSource from '../DataSource'; import { DataVariableType } from '../DataVariable'; import { ObjectAny } from '../../../common'; +import EditorModel from '../../../editor/model/Editor'; export const CollectionVariableType = 'collection-component'; // Represents the type for defining a loop’s data source. @@ -104,8 +105,7 @@ export default class CollectionComponent extends Component { const { clonedBlock, overrideKeys } = resolveBlockValues(allCollectionItem, block); const type = opt.em.Components.getType(clonedBlock?.type || 'default'); const model = type.model; - const symbol = new model(clonedBlock, opt); - symbolMain = opt.em.Components.addSymbol(symbol); + const component = new model(clonedBlock, opt); for (let index = resolvedStartIndex; index <= resolvedEndIndex; index++) { const item = items[index]; @@ -125,25 +125,7 @@ export default class CollectionComponent extends Component { innerMostCollectionItem, innerMostCollectionItem } - const { overrideKeys } = resolveBlockValues(allCollectionItem, block); - const instance = opt.em.Components.addSymbol(symbolMain!); - const children: any[] = [] - Object.keys(overrideKeys).length && instance!.setSymbolOverride(Object.keys(overrideKeys)); - instance!.set(overrideKeys); - for (let i = 0; i < instance!.components().length; i++) { - const childBlock = block['components'][i]; - const { overrideKeys } = resolveBlockValues(allCollectionItem, deepCloneObject(childBlock)); - const child = opt.em.Components.addSymbol(symbolMain!.components().at(i)) - Object.keys(overrideKeys).length && child!.setSymbolOverride(Object.keys(overrideKeys)); - child!.set(overrideKeys) - children.push(child); - } - - const componentJSON = instance?.toJSON(); - const cmpDefinition = { - ...componentJSON, - components: children - }; + const cmpDefinition = getResolvedComponent(component, block, allCollectionItem, opt.em); components.push(cmpDefinition); } @@ -163,6 +145,28 @@ export default class CollectionComponent extends Component { } } +function getResolvedComponent(component: Component, block: any, allCollectionItem: any, em: EditorModel) { + const instance = em.Components.addSymbol(component); + const { overrideKeys } = resolveBlockValues(allCollectionItem, deepCloneObject(block)); + Object.keys(overrideKeys).length && instance!.setSymbolOverride(Object.keys(overrideKeys)); + instance!.set(overrideKeys); + + const children: any[] = []; + for (let index = 0; index < instance!.components().length; index++) { + const childComponent = component!.components().at(index); + const childBlock = block['components'][index]; + children.push(getResolvedComponent(childComponent, childBlock, allCollectionItem, em)); + } + + const componentJSON = instance?.toJSON(); + const cmpDefinition = { + ...componentJSON, + components: children + }; + + return cmpDefinition; +} + /** * Deeply clones an object. * @template T The type of the object to clone. @@ -192,15 +196,14 @@ function resolveBlockValues(context: any, block: any) { const clonedBlock = deepCloneObject(block); const overrideKeys: ObjectAny = {}; - if (typeof clonedBlock === 'object' && clonedBlock !== null) { + if (typeof clonedBlock === 'object') { const blockKeys = Object.keys(clonedBlock); - for (const key of blockKeys) { let blockValue = clonedBlock[key]; if (key === 'collectionDefinition') continue; let shouldBeOverridden = false; - if (typeof blockValue === 'object' && blockValue !== null) { + if (typeof blockValue === 'object') { const collectionItem = blockValue.collection_name ? context[blockValue.collection_name] : innerMostCollectionItem; @@ -211,14 +214,12 @@ function resolveBlockValues(context: any, block: any) { ); } - if (blockValue.variable_type === 'current_item') { - if (collectionItem.current_item.type === DataVariableType) { - const path = collectionItem.current_item.path ? `${collectionItem.current_item.path}.${blockValue.path}` : blockValue.path; - clonedBlock[key] = { - ...collectionItem.current_item, - path - }; - } + if (blockValue.variable_type === 'current_item' && collectionItem.current_item.type === DataVariableType) { + const path = collectionItem.current_item.path ? `${collectionItem.current_item.path}.${blockValue.path}` : blockValue.path; + clonedBlock[key] = { + ...collectionItem.current_item, + path + }; } else { clonedBlock[key] = collectionItem[blockValue.variable_type]; } @@ -231,10 +232,11 @@ function resolveBlockValues(context: any, block: any) { if (Object.keys(itemOverrideKeys).length > 0) { shouldBeOverridden = true; } + return typeof arrayItem === 'object' ? clonedBlock : arrayItem }); } else { - const { clonedBlock, overrideKeys: itemOverrideKeys } = resolveBlockValues(context, blockValue).clonedBlock; + const { clonedBlock, overrideKeys: itemOverrideKeys } = resolveBlockValues(context, blockValue); clonedBlock[key] = clonedBlock; if (Object.keys(itemOverrideKeys).length > 0) { From a1ce18a159f26f8ec0e8ef96e6c8d63204df4e14 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 25 Dec 2024 11:23:04 +0200 Subject: [PATCH 17/87] Refactor collections --- .../CollectionComponent.ts | 342 ++++++++++-------- .../src/dom_components/model/Component.ts | 1 - 2 files changed, 182 insertions(+), 161 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index c8bc21dc71..f3e232344b 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -1,131 +1,101 @@ +import { DataVariableType } from './../DataVariable'; import { isArray } from 'underscore'; import Component from '../../../dom_components/model/Component'; import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; import { toLowerCase } from '../../../utils/mixins'; import { ConditionDefinition } from '../conditional_variables/DataCondition'; import DataSource from '../DataSource'; -import { DataVariableType } from '../DataVariable'; import { ObjectAny } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; export const CollectionVariableType = 'collection-component'; -// Represents the type for defining a loop’s data source. +type CollectionVariable = { + type: 'parent-collection-variable'; + variable_type: keyof CollectionState; + collection_name?: string; + path?: string; +}; + type CollectionDataSource = - | any[] // Direct array - | { type: 'datasource-variable'; path: string } // Object representing a data source - | { type: 'parent-collection-variable'; path: string }; // Object representing an outer loop variable - -// Defines the collection's configuration, such as start and end indexes, and data source. -interface CollectionConfig { - start_index?: number; // The starting index for the collection - end_index?: number | ConditionDefinition; // End index; can be absolute or relative (If omitted will loop over all items) - dataSource: CollectionDataSource; // The data source (array or object reference) + | any[] + | { type: typeof DataVariableType; path: string } + | CollectionVariable; + +type CollectionConfig = { + start_index?: number; + end_index?: number | ConditionDefinition; + dataSource: CollectionDataSource; } -// Provides access to collection state variables during iteration. -interface CollectionStateVariables { - current_index: number; // Current collection index - start_index: number; // Start index - current_item: any; // Current item in the iteration - end_index: number; // End index - collection_name?: string; // Optional name of the collection - total_items: number; // Total number of items in the collection - remaining_items: number; // Remaining items in the collection +type CollectionState = { + current_index: number; + start_index: number; + current_item: any; + end_index: number; + collection_name?: string; + total_items: number; + remaining_items: number; } -// Defines the complete structure for a collection, including configuration and state variables. -interface CollectionDefinition { +type CollectionsStateMap = { + [key: string]: CollectionState; +} + +type CollectionDefinition = { type: typeof CollectionVariableType; - collection_name?: string; // Optional collection name - config: CollectionConfig; // Loop configuration details - block: ComponentDefinition; // Component definition for each iteration + collection_name?: string; + config: CollectionConfig; + block: ComponentDefinition; } -export const componentCollectionKey = 'collectionsItems'; + +export const collectionDefinitionKey = 'collectionDefinition'; +export const collectionsStateKey = 'collectionsItems'; +export const innerCollectionStateKey = 'innerCollectionState'; export default class CollectionComponent extends Component { constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { - const { collection_name, block, config } = props.collectionDefinition; - const { - start_index = 0, - end_index = Number.MAX_VALUE, - dataSource = [], - } = config; - let items: any[] = []; - switch (true) { - case isArray(dataSource): - items = dataSource; - break; - case typeof dataSource === 'object' && dataSource instanceof DataSource: - const id = dataSource.get('id')!; - const resolvedPath = opt.em.DataSources.getValue(id, []); - const keys = Object.keys(resolvedPath); - items = keys.map(key => ({ - type: DataVariableType, - path: id + '.' + key, - })); - break; - case typeof dataSource === 'object' && dataSource.type === DataVariableType: - const pathArr = dataSource.path.split('.'); - if (pathArr.length === 1) { - const resolvedPath = opt.em.DataSources.getValue(dataSource.path, []); - const keys = Object.keys(resolvedPath); - items = keys.map(key => ({ - type: DataVariableType, - path: id + '.' + key, - })); - } else { - items = opt.em.DataSources.getValue(dataSource.path, []); - } - break; - default: + const em = opt.em; + const { collection_name, block, config } = props[collectionDefinitionKey]; + if (!block) { + throw new Error('The "block" property is required in the collection definition.'); } - const components: ComponentDefinition[] = []; - const resolvedStartIndex = Math.max(0, start_index); - const resolvedEndIndex = Math.min(items.length - 1, end_index); - const item = items[resolvedStartIndex]; - let symbolMain; - const total_items = resolvedEndIndex - resolvedStartIndex + 1; - const innerMostCollectionItem = { - collection_name, - current_index: resolvedStartIndex, - current_item: item, - start_index: resolvedStartIndex, - end_index: resolvedEndIndex, - total_items: total_items, - remaining_items: total_items - (resolvedStartIndex + 1), - }; - - const allCollectionItem = { - ...props.collectionsItems, - [innerMostCollectionItem.collection_name ? innerMostCollectionItem.collection_name : 'innerMostCollectionItem']: - innerMostCollectionItem, - innerMostCollectionItem + if (!config?.dataSource) { + throw new Error('The "config.dataSource" property is required in the collection definition.'); } - const { clonedBlock, overrideKeys } = resolveBlockValues(allCollectionItem, block); - const type = opt.em.Components.getType(clonedBlock?.type || 'default'); - const model = type.model; - const component = new model(clonedBlock, opt); - for (let index = resolvedStartIndex; index <= resolvedEndIndex; index++) { + let items: any[] = getDataSourceItems(config.dataSource, em); + const components: ComponentDefinition[] = []; + const start_index = Math.max(0, config.start_index || 0); + const end_index = Math.min(items.length - 1, config.end_index || Number.MAX_VALUE); + + const total_items = end_index - start_index + 1; + let blockComponent: Component; + for (let index = start_index; index <= end_index; index++) { const item = items[index]; - const innerMostCollectionItem = { + const collectionState: CollectionState = { collection_name, current_index: index, current_item: item, - start_index: resolvedStartIndex, - end_index: resolvedEndIndex, + start_index: start_index, + end_index: end_index, total_items: total_items, remaining_items: total_items - (index + 1), }; - const allCollectionItem = { - ...props.collectionsItems, - [innerMostCollectionItem.collection_name ? innerMostCollectionItem.collection_name : 'innerMostCollectionItem']: - innerMostCollectionItem, - innerMostCollectionItem + const collectionsStateMap: CollectionsStateMap = { + ...props[collectionDefinitionKey], + ...(collection_name && { [collection_name]: collectionState }), + [innerCollectionStateKey]: collectionState, + }; + + if (index === start_index) { + const { clonedBlock } = resolveBlockValues(collectionsStateMap, block); + const type = em.Components.getType(clonedBlock?.type || 'default'); + const model = type.model; + blockComponent = new model(clonedBlock, opt); } - const cmpDefinition = getResolvedComponent(component, block, allCollectionItem, opt.em); + const cmpDefinition = resolveComponent(blockComponent!, block, collectionsStateMap, em); components.push(cmpDefinition); } @@ -136,7 +106,7 @@ export default class CollectionComponent extends Component { components: components, dropbbable: false, }; - // @ts-expect-error + // @ts-ignore super(conditionalCmptDef, opt); } @@ -145,111 +115,163 @@ export default class CollectionComponent extends Component { } } -function getResolvedComponent(component: Component, block: any, allCollectionItem: any, em: EditorModel) { - const instance = em.Components.addSymbol(component); - const { overrideKeys } = resolveBlockValues(allCollectionItem, deepCloneObject(block)); +function getDataSourceItems(dataSource: any, em: EditorModel) { + let items: any[] = []; + switch (true) { + case isArray(dataSource): + items = dataSource; + break; + case typeof dataSource === 'object' && dataSource instanceof DataSource: + const id = dataSource.get('id')!; + items = listDataSourceVariables(id, em); + break; + case typeof dataSource === 'object' && dataSource.type === DataVariableType: + const isDataSourceId = dataSource.path.split('.').length === 1; + if (isDataSourceId) { + const id = dataSource.path; + items = listDataSourceVariables(id, em); + } else { + // Path points to a record in the data source + items = em.DataSources.getValue(dataSource.path, []); + } + break; + default: + } + return items; +} + +function listDataSourceVariables(dataSource_id: string, em: EditorModel) { + const records = em.DataSources.getValue(dataSource_id, []); + const keys = Object.keys(records); + + return keys.map(key => ({ + type: DataVariableType, + path: dataSource_id + '.' + key, + })); +} + +function resolveComponent(symbol: Component, block: ComponentDefinition, collectionsStateMap: CollectionsStateMap, em: EditorModel) { + const instance = em.Components.addSymbol(symbol); + const { resolvedCollectionValues: overrideKeys } = resolveBlockValues(collectionsStateMap, block); Object.keys(overrideKeys).length && instance!.setSymbolOverride(Object.keys(overrideKeys)); instance!.set(overrideKeys); - const children: any[] = []; + const children: ComponentDefinition[] = []; for (let index = 0; index < instance!.components().length; index++) { - const childComponent = component!.components().at(index); - const childBlock = block['components'][index]; - children.push(getResolvedComponent(childComponent, childBlock, allCollectionItem, em)); + const childComponent = symbol!.components().at(index); + const childBlock = block['components']![index]; + children.push(resolveComponent(childComponent, childBlock, collectionsStateMap, em)); } - const componentJSON = instance?.toJSON(); - const cmpDefinition = { + const componentJSON = instance!.toJSON(); + const componentDefinition: ComponentDefinition = { ...componentJSON, components: children }; - - return cmpDefinition; -} - -/** - * Deeply clones an object. - * @template T The type of the object to clone. - * @param {T} obj The object to clone. - * @returns {T} A deep clone of the object, or the original object if it's not an object or is null. Returns undefined if input is undefined. - */ -function deepCloneObject | null | undefined>(obj: T): T { - if (obj === null) return null as T; - if (obj === undefined) return undefined as T; - if (typeof obj !== 'object' || Array.isArray(obj)) { - return obj; // Return primitives directly - } - - const clonedObj: Record = {}; - - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - clonedObj[key] = deepCloneObject(obj[key]); - } - } - return clonedObj as T; + return componentDefinition; } -function resolveBlockValues(context: any, block: any) { - const { innerMostCollectionItem } = context; +function resolveBlockValues(collectionsStateMap: CollectionsStateMap, block: ObjectAny) { const clonedBlock = deepCloneObject(block); - const overrideKeys: ObjectAny = {}; + const resolvedCollectionValues: ObjectAny = {}; if (typeof clonedBlock === 'object') { const blockKeys = Object.keys(clonedBlock); for (const key of blockKeys) { let blockValue = clonedBlock[key]; - if (key === 'collectionDefinition') continue; - let shouldBeOverridden = false; + if (key === collectionDefinitionKey) continue; + let hasCollectionVariable = false; if (typeof blockValue === 'object') { - const collectionItem = blockValue.collection_name - ? context[blockValue.collection_name] - : innerMostCollectionItem; - if (blockValue.type === 'parent-collection-variable') { + const isCollectionVariable = blockValue.type === 'parent-collection-variable'; + if (isCollectionVariable) { + const { + variable_type, + collection_name = 'innerMostCollectionItem', + path = '' + } = blockValue as CollectionVariable; + const collectionItem = collectionsStateMap[collection_name]; if (!collectionItem) { throw new Error( - `Collection not found: ${blockValue.collection_name || 'default collection'}` + `Collection not found: ${collection_name}` ); } - - if (blockValue.variable_type === 'current_item' && collectionItem.current_item.type === DataVariableType) { - const path = collectionItem.current_item.path ? `${collectionItem.current_item.path}.${blockValue.path}` : blockValue.path; - clonedBlock[key] = { - ...collectionItem.current_item, - path - }; - } else { - clonedBlock[key] = collectionItem[blockValue.variable_type]; + if (!variable_type) { + throw new Error( + `Missing collection variable type for collection: ${collection_name}` + ); } + clonedBlock[key] = resolveCurrentItem(variable_type, collectionItem, path); - shouldBeOverridden = true; + hasCollectionVariable = true; } else if (Array.isArray(blockValue)) { - // Resolve each item in the array clonedBlock[key] = blockValue.map((arrayItem: any) => { - const { clonedBlock, overrideKeys: itemOverrideKeys } = resolveBlockValues(context, arrayItem) - if (Object.keys(itemOverrideKeys).length > 0) { - shouldBeOverridden = true; + const { clonedBlock, resolvedCollectionValues: itemOverrideKeys } = resolveBlockValues(collectionsStateMap, arrayItem) + if (!isEmptyObject(itemOverrideKeys)) { + hasCollectionVariable = true; } return typeof arrayItem === 'object' ? clonedBlock : arrayItem }); } else { - const { clonedBlock, overrideKeys: itemOverrideKeys } = resolveBlockValues(context, blockValue); + const { clonedBlock, resolvedCollectionValues: itemOverrideKeys } = resolveBlockValues(collectionsStateMap, blockValue); clonedBlock[key] = clonedBlock; - if (Object.keys(itemOverrideKeys).length > 0) { - shouldBeOverridden = true; + if (!isEmptyObject(itemOverrideKeys)) { + hasCollectionVariable = true; } } - if (shouldBeOverridden && key !== 'components') { - overrideKeys[key] = clonedBlock[key] + if (hasCollectionVariable && key !== 'components') { + resolvedCollectionValues[key] = clonedBlock[key] } } } } - return { clonedBlock, overrideKeys }; + return { clonedBlock, resolvedCollectionValues }; +} + +function resolveCurrentItem(variableType: CollectionVariable['variable_type'], collectionItem: CollectionState, path: string) { + const valueIsDataVariable = collectionItem.current_item?.type === DataVariableType; + if (variableType === 'current_item' && valueIsDataVariable) { + const resolvedPath = collectionItem.current_item.path + ? `${collectionItem.current_item.path}.${path}` + : path; + return { + ...collectionItem.current_item, + path: resolvedPath, + }; + } + return collectionItem[variableType]; +} + + +function isEmptyObject(itemOverrideKeys: ObjectAny) { + return Object.keys(itemOverrideKeys).length === 0; +} + +/** + * Deeply clones an object. + * @template T The type of the object to clone. + * @param {T} obj The object to clone. + * @returns {T} A deep clone of the object, or the original object if it's not an object or is null. Returns undefined if input is undefined. + */ +function deepCloneObject | null | undefined>(obj: T): T { + if (obj === null) return null as T; + if (obj === undefined) return undefined as T; + if (typeof obj !== 'object' || Array.isArray(obj)) { + return obj; // Return primitives directly + } + + const clonedObj: Record = {}; + + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + clonedObj[key] = deepCloneObject(obj[key]); + } + } + + return clonedObj as T; } diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index bc61b682f2..2ee4a57cdb 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -55,7 +55,6 @@ import TraitDataVariable from '../../data_sources/model/TraitDataVariable'; import { ConditionalVariableType, DataCondition } from '../../data_sources/model/conditional_variables/DataCondition'; import { isDynamicValue, isDynamicValueDefinition } from '../../data_sources/model/utils'; import { DynamicValueDefinition } from '../../data_sources/types'; -import { componentCollectionKey } from '../../data_sources/model/collection_component/CollectionComponent'; export interface IComponent extends ExtractMethods { } From ed8ecbca934d25f51ecdd0fd46d9b2fad415fe38 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 25 Dec 2024 11:28:41 +0200 Subject: [PATCH 18/87] Refactor and format --- .../CollectionComponent.ts | 62 ++++++++++--------- .../CollectionComponentView.ts | 4 +- .../src/dom_components/model/Component.ts | 28 ++++----- 3 files changed, 50 insertions(+), 44 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index f3e232344b..39cbd322b9 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -1,4 +1,4 @@ -import { DataVariableType } from './../DataVariable'; +import { DataVariableDefinition, DataVariableType } from './../DataVariable'; import { isArray } from 'underscore'; import Component from '../../../dom_components/model/Component'; import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; @@ -16,16 +16,13 @@ type CollectionVariable = { path?: string; }; -type CollectionDataSource = - | any[] - | { type: typeof DataVariableType; path: string } - | CollectionVariable; +type CollectionDataSource = any[] | DataVariableDefinition | CollectionVariable; type CollectionConfig = { start_index?: number; end_index?: number | ConditionDefinition; dataSource: CollectionDataSource; -} +}; type CollectionState = { current_index: number; @@ -35,18 +32,18 @@ type CollectionState = { collection_name?: string; total_items: number; remaining_items: number; -} +}; type CollectionsStateMap = { [key: string]: CollectionState; -} +}; type CollectionDefinition = { type: typeof CollectionVariableType; collection_name?: string; config: CollectionConfig; block: ComponentDefinition; -} +}; export const collectionDefinitionKey = 'collectionDefinition'; export const collectionsStateKey = 'collectionsItems'; @@ -144,13 +141,18 @@ function listDataSourceVariables(dataSource_id: string, em: EditorModel) { const records = em.DataSources.getValue(dataSource_id, []); const keys = Object.keys(records); - return keys.map(key => ({ + return keys.map((key) => ({ type: DataVariableType, path: dataSource_id + '.' + key, })); } -function resolveComponent(symbol: Component, block: ComponentDefinition, collectionsStateMap: CollectionsStateMap, em: EditorModel) { +function resolveComponent( + symbol: Component, + block: ComponentDefinition, + collectionsStateMap: CollectionsStateMap, + em: EditorModel, +) { const instance = em.Components.addSymbol(symbol); const { resolvedCollectionValues: overrideKeys } = resolveBlockValues(collectionsStateMap, block); Object.keys(overrideKeys).length && instance!.setSymbolOverride(Object.keys(overrideKeys)); @@ -166,7 +168,7 @@ function resolveComponent(symbol: Component, block: ComponentDefinition, collect const componentJSON = instance!.toJSON(); const componentDefinition: ComponentDefinition = { ...componentJSON, - components: children + components: children, }; return componentDefinition; @@ -189,33 +191,35 @@ function resolveBlockValues(collectionsStateMap: CollectionsStateMap, block: Obj const { variable_type, collection_name = 'innerMostCollectionItem', - path = '' + path = '', } = blockValue as CollectionVariable; const collectionItem = collectionsStateMap[collection_name]; if (!collectionItem) { - throw new Error( - `Collection not found: ${collection_name}` - ); + throw new Error(`Collection not found: ${collection_name}`); } if (!variable_type) { - throw new Error( - `Missing collection variable type for collection: ${collection_name}` - ); + throw new Error(`Missing collection variable type for collection: ${collection_name}`); } clonedBlock[key] = resolveCurrentItem(variable_type, collectionItem, path); hasCollectionVariable = true; } else if (Array.isArray(blockValue)) { clonedBlock[key] = blockValue.map((arrayItem: any) => { - const { clonedBlock, resolvedCollectionValues: itemOverrideKeys } = resolveBlockValues(collectionsStateMap, arrayItem) + const { clonedBlock, resolvedCollectionValues: itemOverrideKeys } = resolveBlockValues( + collectionsStateMap, + arrayItem, + ); if (!isEmptyObject(itemOverrideKeys)) { hasCollectionVariable = true; } - return typeof arrayItem === 'object' ? clonedBlock : arrayItem + return typeof arrayItem === 'object' ? clonedBlock : arrayItem; }); } else { - const { clonedBlock, resolvedCollectionValues: itemOverrideKeys } = resolveBlockValues(collectionsStateMap, blockValue); + const { clonedBlock, resolvedCollectionValues: itemOverrideKeys } = resolveBlockValues( + collectionsStateMap, + blockValue, + ); clonedBlock[key] = clonedBlock; if (!isEmptyObject(itemOverrideKeys)) { @@ -224,7 +228,7 @@ function resolveBlockValues(collectionsStateMap: CollectionsStateMap, block: Obj } if (hasCollectionVariable && key !== 'components') { - resolvedCollectionValues[key] = clonedBlock[key] + resolvedCollectionValues[key] = clonedBlock[key]; } } } @@ -233,12 +237,15 @@ function resolveBlockValues(collectionsStateMap: CollectionsStateMap, block: Obj return { clonedBlock, resolvedCollectionValues }; } -function resolveCurrentItem(variableType: CollectionVariable['variable_type'], collectionItem: CollectionState, path: string) { +function resolveCurrentItem( + variableType: CollectionVariable['variable_type'], + collectionItem: CollectionState, + path: string, +) { const valueIsDataVariable = collectionItem.current_item?.type === DataVariableType; if (variableType === 'current_item' && valueIsDataVariable) { - const resolvedPath = collectionItem.current_item.path - ? `${collectionItem.current_item.path}.${path}` - : path; + const currentItem_path = collectionItem.current_item.path; + const resolvedPath = currentItem_path ? `${currentItem_path}.${path}` : path; return { ...collectionItem.current_item, path: resolvedPath, @@ -247,7 +254,6 @@ function resolveCurrentItem(variableType: CollectionVariable['variable_type'], c return collectionItem[variableType]; } - function isEmptyObject(itemOverrideKeys: ObjectAny) { return Object.keys(itemOverrideKeys).length === 0; } diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts index 12be285720..19a8fdce69 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts @@ -1,4 +1,4 @@ -import ComponentView from "../../../dom_components/view/ComponentView"; +import ComponentView from '../../../dom_components/view/ComponentView'; import CollectionComponent from './CollectionComponent'; -export default class CollectionComponentView extends ComponentView { } +export default class CollectionComponentView extends ComponentView {} diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 2ee4a57cdb..42986ffa28 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -56,9 +56,9 @@ import { ConditionalVariableType, DataCondition } from '../../data_sources/model import { isDynamicValue, isDynamicValueDefinition } from '../../data_sources/model/utils'; import { DynamicValueDefinition } from '../../data_sources/types'; -export interface IComponent extends ExtractMethods { } +export interface IComponent extends ExtractMethods {} -export interface SetAttrOptions extends SetOptions, UpdateStyleOptions { } +export interface SetAttrOptions extends SetOptions, UpdateStyleOptions {} const escapeRegExp = (str: string) => { return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); @@ -225,12 +225,12 @@ export default class Component extends StyleableModel { 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 +238,12 @@ export default class Component extends StyleableModel { * @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; @@ -263,22 +263,22 @@ export default class Component extends StyleableModel { constructor(props: ComponentProperties = {}, opt: ComponentOptions) { if (Array.isArray(props['components'])) { - props['components']?.map(component => { + props['components']?.map((component) => { return { ...component, collectionsItems: { - ...props.componentCollectionKey - } - } - }) + ...props.componentCollectionKey, + }, + }; + }); } else if (typeof props['components'] === 'object') { props['components'] = { ...props['components'], // @ts-ignore collectionsItems: { - ...props.componentCollectionKey - } - } + ...props.componentCollectionKey, + }, + }; } super(props, opt); From a1fea6e371198c746c3fe5948495af1a34e2f96c Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 25 Dec 2024 11:36:28 +0200 Subject: [PATCH 19/87] Refactor collection keys --- .../collection_component/CollectionComponent.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 39cbd322b9..7348e1f02b 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -8,9 +8,11 @@ import DataSource from '../DataSource'; import { ObjectAny } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; -export const CollectionVariableType = 'collection-component'; +export const CollectionComponentType = 'collection-component'; +export const CollectionVariableType = 'parent-collection-variable'; + type CollectionVariable = { - type: 'parent-collection-variable'; + type: typeof CollectionVariableType; variable_type: keyof CollectionState; collection_name?: string; path?: string; @@ -39,7 +41,7 @@ type CollectionsStateMap = { }; type CollectionDefinition = { - type: typeof CollectionVariableType; + type: typeof CollectionComponentType; collection_name?: string; config: CollectionConfig; block: ComponentDefinition; @@ -99,7 +101,7 @@ export default class CollectionComponent extends Component { const conditionalCmptDef = { ...props, - type: CollectionVariableType, + type: CollectionComponentType, components: components, dropbbable: false, }; @@ -108,7 +110,7 @@ export default class CollectionComponent extends Component { } static isComponent(el: HTMLElement) { - return toLowerCase(el.tagName) === CollectionVariableType; + return toLowerCase(el.tagName) === CollectionComponentType; } } @@ -186,11 +188,11 @@ function resolveBlockValues(collectionsStateMap: CollectionsStateMap, block: Obj let hasCollectionVariable = false; if (typeof blockValue === 'object') { - const isCollectionVariable = blockValue.type === 'parent-collection-variable'; + const isCollectionVariable = blockValue.type === CollectionVariableType; if (isCollectionVariable) { const { variable_type, - collection_name = 'innerMostCollectionItem', + collection_name = innerCollectionStateKey, path = '', } = blockValue as CollectionVariable; const collectionItem = collectionsStateMap[collection_name]; From 8c734e2f74d9f876895d1fb084eaa6149fd26a82 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 25 Dec 2024 11:43:11 +0200 Subject: [PATCH 20/87] Cleanup collectionStateMap --- .../CollectionComponent.ts | 5 +++-- .../src/dom_components/model/Component.ts | 19 ------------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 7348e1f02b..1233dac529 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -48,7 +48,7 @@ type CollectionDefinition = { }; export const collectionDefinitionKey = 'collectionDefinition'; -export const collectionsStateKey = 'collectionsItems'; +export const collectionsStateMapKey = 'collectionsItems'; export const innerCollectionStateKey = 'innerCollectionState'; export default class CollectionComponent extends Component { @@ -83,7 +83,7 @@ export default class CollectionComponent extends Component { }; const collectionsStateMap: CollectionsStateMap = { - ...props[collectionDefinitionKey], + ...props[collectionsStateMapKey], ...(collection_name && { [collection_name]: collectionState }), [innerCollectionStateKey]: collectionState, }; @@ -171,6 +171,7 @@ function resolveComponent( const componentDefinition: ComponentDefinition = { ...componentJSON, components: children, + [collectionsStateMapKey]: collectionsStateMap }; return componentDefinition; diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 42986ffa28..fe93d6931d 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -262,25 +262,6 @@ export default class Component extends StyleableModel { collection!: Components; constructor(props: ComponentProperties = {}, opt: ComponentOptions) { - if (Array.isArray(props['components'])) { - props['components']?.map((component) => { - return { - ...component, - collectionsItems: { - ...props.componentCollectionKey, - }, - }; - }); - } else if (typeof props['components'] === 'object') { - props['components'] = { - ...props['components'], - // @ts-ignore - collectionsItems: { - ...props.componentCollectionKey, - }, - }; - } - super(props, opt); bindAll(this, '__upSymbProps', '__upSymbCls', '__upSymbComps'); const em = opt.em; From c6f4ad4e3ad42034e612d5c5d67150737d48af38 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 25 Dec 2024 12:21:34 +0200 Subject: [PATCH 21/87] Only use 1 symbol to be used for each item in the collections --- .../CollectionComponent.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 1233dac529..d9441feebf 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -94,7 +94,8 @@ export default class CollectionComponent extends Component { const model = type.model; blockComponent = new model(clonedBlock, opt); } - const cmpDefinition = resolveComponent(blockComponent!, block, collectionsStateMap, em); + const instance = em.Components.addSymbol(blockComponent!); + const cmpDefinition = resolveComponent(instance!, block, collectionsStateMap, em); components.push(cmpDefinition); } @@ -150,24 +151,24 @@ function listDataSourceVariables(dataSource_id: string, em: EditorModel) { } function resolveComponent( - symbol: Component, + component: Component, block: ComponentDefinition, collectionsStateMap: CollectionsStateMap, em: EditorModel, ) { - const instance = em.Components.addSymbol(symbol); - const { resolvedCollectionValues: overrideKeys } = resolveBlockValues(collectionsStateMap, block); - Object.keys(overrideKeys).length && instance!.setSymbolOverride(Object.keys(overrideKeys)); - instance!.set(overrideKeys); + const { resolvedCollectionValues } = resolveBlockValues(collectionsStateMap, block); + Object.keys(resolvedCollectionValues).length && component!.setSymbolOverride(Object.keys(resolvedCollectionValues)); + component!.set(resolvedCollectionValues); const children: ComponentDefinition[] = []; - for (let index = 0; index < instance!.components().length; index++) { - const childComponent = symbol!.components().at(index); + for (let index = 0; index < component!.components().length; index++) { + const childSymbol = component!.components().at(index); const childBlock = block['components']![index]; - children.push(resolveComponent(childComponent, childBlock, collectionsStateMap, em)); + const childJSON = resolveComponent(childSymbol, childBlock, collectionsStateMap, em); + children.push(childJSON); } - const componentJSON = instance!.toJSON(); + const componentJSON = component!.toJSON(); const componentDefinition: ComponentDefinition = { ...componentJSON, components: children, From 8fc94812f5188943a308de0d9adb9f3df0e3ea20 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 30 Dec 2024 04:06:35 +0200 Subject: [PATCH 22/87] Fix path for static datasource --- .../CollectionComponent.ts | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index d9441feebf..4a3c70102b 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -172,7 +172,7 @@ function resolveComponent( const componentDefinition: ComponentDefinition = { ...componentJSON, components: children, - [collectionsStateMapKey]: collectionsStateMap + [collectionsStateMapKey]: collectionsStateMap, }; return componentDefinition; @@ -246,14 +246,22 @@ function resolveCurrentItem( collectionItem: CollectionState, path: string, ) { - const valueIsDataVariable = collectionItem.current_item?.type === DataVariableType; - if (variableType === 'current_item' && valueIsDataVariable) { - const currentItem_path = collectionItem.current_item.path; - const resolvedPath = currentItem_path ? `${currentItem_path}.${path}` : path; - return { - ...collectionItem.current_item, - path: resolvedPath, - }; + if (variableType === 'current_item') { + const valueIsDataVariable = collectionItem.current_item?.type === DataVariableType; + if (valueIsDataVariable) { + const currentItem_path = collectionItem.current_item.path; + const resolvedPath = currentItem_path ? `${currentItem_path}.${path}` : path; + return { + ...collectionItem.current_item, + path: resolvedPath, + }; + } else if (!!path) { + if (!collectionItem.current_item?.[path]) { + throw new Error(`Path not found in current item: ${path}`); + } + + return collectionItem.current_item[path]; + } } return collectionItem[variableType]; } From 98371167721804715b1774cfb8edb68aae2527cc Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 3 Jan 2025 10:51:51 +0200 Subject: [PATCH 23/87] Add collection variables --- .../CollectionComponent.ts | 114 ++++-------------- .../CollectionVariable.ts | 91 ++++++++++++++ .../model/collection_component/constants.ts | 4 + .../model/collection_component/types.ts | 34 ++++++ packages/core/src/data_sources/model/utils.ts | 33 ++++- packages/core/src/data_sources/types.ts | 9 +- .../src/dom_components/model/Component.ts | 30 ++++- .../model/ComponentDynamicValueWatcher.ts | 10 +- .../model/DynamicValueWatcher.ts | 30 ++++- .../dom_components/model/ComponentTypes.ts | 8 ++ 10 files changed, 252 insertions(+), 111 deletions(-) create mode 100644 packages/core/src/data_sources/model/collection_component/CollectionVariable.ts create mode 100644 packages/core/src/data_sources/model/collection_component/constants.ts create mode 100644 packages/core/src/data_sources/model/collection_component/types.ts diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 4a3c70102b..2be747d913 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -1,60 +1,24 @@ -import { DataVariableDefinition, DataVariableType } from './../DataVariable'; +import { DataVariableType } from './../DataVariable'; import { isArray } from 'underscore'; import Component from '../../../dom_components/model/Component'; import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; import { toLowerCase } from '../../../utils/mixins'; -import { ConditionDefinition } from '../conditional_variables/DataCondition'; import DataSource from '../DataSource'; import { ObjectAny } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; - -export const CollectionComponentType = 'collection-component'; -export const CollectionVariableType = 'parent-collection-variable'; - -type CollectionVariable = { - type: typeof CollectionVariableType; - variable_type: keyof CollectionState; - collection_name?: string; - path?: string; -}; - -type CollectionDataSource = any[] | DataVariableDefinition | CollectionVariable; - -type CollectionConfig = { - start_index?: number; - end_index?: number | ConditionDefinition; - dataSource: CollectionDataSource; -}; - -type CollectionState = { - current_index: number; - start_index: number; - current_item: any; - end_index: number; - collection_name?: string; - total_items: number; - remaining_items: number; -}; - -type CollectionsStateMap = { - [key: string]: CollectionState; -}; - -type CollectionDefinition = { - type: typeof CollectionComponentType; - collection_name?: string; - config: CollectionConfig; - block: ComponentDefinition; -}; - -export const collectionDefinitionKey = 'collectionDefinition'; -export const collectionsStateMapKey = 'collectionsItems'; -export const innerCollectionStateKey = 'innerCollectionState'; +import { keyCollectionsStateMap } from '../../../dom_components/model/Component'; +import { CollectionDefinition, CollectionState, CollectionsStateMap } from './types'; +import { + keyCollectionDefinition, + keyInnerCollectionState, + CollectionComponentType, + CollectionVariableType, +} from './constants'; export default class CollectionComponent extends Component { constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { const em = opt.em; - const { collection_name, block, config } = props[collectionDefinitionKey]; + const { collection_name, block, config } = props[keyCollectionDefinition]; if (!block) { throw new Error('The "block" property is required in the collection definition.'); } @@ -83,16 +47,22 @@ export default class CollectionComponent extends Component { }; const collectionsStateMap: CollectionsStateMap = { - ...props[collectionsStateMapKey], + ...props[keyCollectionsStateMap], ...(collection_name && { [collection_name]: collectionState }), - [innerCollectionStateKey]: collectionState, + [keyInnerCollectionState]: collectionState, }; if (index === start_index) { const { clonedBlock } = resolveBlockValues(collectionsStateMap, block); const type = em.Components.getType(clonedBlock?.type || 'default'); const model = type.model; - blockComponent = new model(clonedBlock, opt); + blockComponent = new model( + { + ...clonedBlock, + [keyCollectionsStateMap]: collectionsStateMap, + }, + opt, + ); } const instance = em.Components.addSymbol(blockComponent!); const cmpDefinition = resolveComponent(instance!, block, collectionsStateMap, em); @@ -172,12 +142,13 @@ function resolveComponent( const componentDefinition: ComponentDefinition = { ...componentJSON, components: children, - [collectionsStateMapKey]: collectionsStateMap, + [keyCollectionsStateMap]: collectionsStateMap, }; return componentDefinition; } +// TODO: remove this function function resolveBlockValues(collectionsStateMap: CollectionsStateMap, block: ObjectAny) { const clonedBlock = deepCloneObject(block); const resolvedCollectionValues: ObjectAny = {}; @@ -186,26 +157,12 @@ function resolveBlockValues(collectionsStateMap: CollectionsStateMap, block: Obj const blockKeys = Object.keys(clonedBlock); for (const key of blockKeys) { let blockValue = clonedBlock[key]; - if (key === collectionDefinitionKey) continue; + if (key === keyCollectionDefinition) continue; let hasCollectionVariable = false; if (typeof blockValue === 'object') { - const isCollectionVariable = blockValue.type === CollectionVariableType; + const isCollectionVariable = blockValue.type === 'parent-collection-variable'; if (isCollectionVariable) { - const { - variable_type, - collection_name = innerCollectionStateKey, - path = '', - } = blockValue as CollectionVariable; - const collectionItem = collectionsStateMap[collection_name]; - if (!collectionItem) { - throw new Error(`Collection not found: ${collection_name}`); - } - if (!variable_type) { - throw new Error(`Missing collection variable type for collection: ${collection_name}`); - } - clonedBlock[key] = resolveCurrentItem(variable_type, collectionItem, path); - hasCollectionVariable = true; } else if (Array.isArray(blockValue)) { clonedBlock[key] = blockValue.map((arrayItem: any) => { @@ -241,31 +198,6 @@ function resolveBlockValues(collectionsStateMap: CollectionsStateMap, block: Obj return { clonedBlock, resolvedCollectionValues }; } -function resolveCurrentItem( - variableType: CollectionVariable['variable_type'], - collectionItem: CollectionState, - path: string, -) { - if (variableType === 'current_item') { - const valueIsDataVariable = collectionItem.current_item?.type === DataVariableType; - if (valueIsDataVariable) { - const currentItem_path = collectionItem.current_item.path; - const resolvedPath = currentItem_path ? `${currentItem_path}.${path}` : path; - return { - ...collectionItem.current_item, - path: resolvedPath, - }; - } else if (!!path) { - if (!collectionItem.current_item?.[path]) { - throw new Error(`Path not found in current item: ${path}`); - } - - return collectionItem.current_item[path]; - } - } - return collectionItem[variableType]; -} - function isEmptyObject(itemOverrideKeys: ObjectAny) { return Object.keys(itemOverrideKeys).length === 0; } diff --git a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts new file mode 100644 index 0000000000..c9ebd1e47f --- /dev/null +++ b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts @@ -0,0 +1,91 @@ +import { CollectionVariableDefinition } from '../../../../test/specs/dom_components/model/ComponentTypes'; +import { Model } from '../../../common'; +import EditorModel from '../../../editor/model/Editor'; +import DataVariable, { DataVariableType } from '../DataVariable'; +import { keyInnerCollectionState } from './constants'; +import { CollectionsStateMap } from './types'; + +export default class CollectionVariable extends Model { + em: EditorModel; + collectionsStateMap: CollectionsStateMap; + dataVariable?: DataVariable; + + constructor( + attrs: CollectionVariableDefinition, + options: { + em: EditorModel; + collectionsStateMap: CollectionsStateMap; + }, + ) { + super(attrs, options); + this.em = options.em; + this.collectionsStateMap = options.collectionsStateMap; + if (!this.collectionsStateMap) { + throw new Error('collectionsStateMap is required'); + } + + this.updateDataVariable(); + } + + getDataValue() { + const { resolvedValue } = this.updateDataVariable(); + + if (resolvedValue?.type === DataVariableType) { + return this.dataVariable!.getDataValue(); + } + return resolvedValue; + } + + private updateDataVariable() { + const resolvedValue = resolveCollectionVariable( + this.attributes as CollectionVariableDefinition, + this.collectionsStateMap, + ); + + let dataVariable; + if (resolvedValue?.type === DataVariableType) { + dataVariable = new DataVariable(resolvedValue, { em: this.em }); + this.dataVariable = dataVariable; + } + + return { resolvedValue, dataVariable }; + } + + destroy() { + return this.dataVariable?.destroy?.() || super.destroy(); + } +} + +function resolveCollectionVariable( + collectionVariableDefinition: CollectionVariableDefinition, + collectionsStateMap: CollectionsStateMap, +) { + const { collection_name = keyInnerCollectionState, variable_type, path } = collectionVariableDefinition; + const collectionItem = collectionsStateMap[collection_name]; + if (!collectionItem) { + throw new Error(`Collection not found: ${collection_name}`); + } + if (!variable_type) { + throw new Error(`Missing collection variable type for collection: ${collection_name}`); + } + + if (variable_type === 'current_item') { + const valueIsDataVariable = collectionItem.current_item?.type === DataVariableType; + if (valueIsDataVariable) { + const currentItem_path = collectionItem.current_item.path; + const resolvedPath = currentItem_path ? `${currentItem_path}.${path}` : path; + return { + ...collectionItem.current_item, + path: resolvedPath, + }; + } else if (!!path) { + if (!collectionItem.current_item?.[path]) { + throw new Error(`Path not found in current item: ${path} for collection: ${collection_name}`); + } + + return collectionItem.current_item[path]; + } + } + + return collectionItem[variable_type]; +} diff --git a/packages/core/src/data_sources/model/collection_component/constants.ts b/packages/core/src/data_sources/model/collection_component/constants.ts new file mode 100644 index 0000000000..adf3b232c3 --- /dev/null +++ b/packages/core/src/data_sources/model/collection_component/constants.ts @@ -0,0 +1,4 @@ +export const CollectionComponentType = 'collection-component'; +export const keyCollectionDefinition = 'collectionDefinition'; +export const keyInnerCollectionState = 'innerCollectionState'; +export const CollectionVariableType = 'parent-collection-variable'; diff --git a/packages/core/src/data_sources/model/collection_component/types.ts b/packages/core/src/data_sources/model/collection_component/types.ts new file mode 100644 index 0000000000..b61abc8af2 --- /dev/null +++ b/packages/core/src/data_sources/model/collection_component/types.ts @@ -0,0 +1,34 @@ +import { CollectionComponentType } from './constants'; + +import { ComponentDefinition } from '../../../dom_components/model/types'; +import { CollectionVariableDefinition } from '../../../../test/specs/dom_components/model/ComponentTypes'; +import { DataVariableDefinition } from '../DataVariable'; +import { ConditionDefinition } from '../conditional_variables/DataCondition'; + +type CollectionDataSource = any[] | DataVariableDefinition | CollectionVariableDefinition; +type CollectionConfig = { + start_index?: number; + end_index?: number | ConditionDefinition; + dataSource: CollectionDataSource; +}; + +export type CollectionState = { + current_index: number; + start_index: number; + current_item: any; + end_index: number; + collection_name?: string; + total_items: number; + remaining_items: number; +}; + +export type CollectionsStateMap = { + [key: string]: CollectionState; +}; + +export type CollectionDefinition = { + type: typeof CollectionComponentType; + collection_name?: string; + config: CollectionConfig; + block: ComponentDefinition; +}; diff --git a/packages/core/src/data_sources/model/utils.ts b/packages/core/src/data_sources/model/utils.ts index 85451ca7fb..71322c1609 100644 --- a/packages/core/src/data_sources/model/utils.ts +++ b/packages/core/src/data_sources/model/utils.ts @@ -1,10 +1,17 @@ import EditorModel from '../../editor/model/Editor'; import { DynamicValue, DynamicValueDefinition } from '../types'; +import { CollectionsStateMap } from './collection_component/types'; +import CollectionVariable from './collection_component/CollectionVariable'; +import { CollectionVariableDefinition } from '../../../test/specs/dom_components/model/ComponentTypes'; +import { CollectionVariableType } from './collection_component/constants'; import { ConditionalVariableType, DataCondition } from './conditional_variables/DataCondition'; import DataVariable, { DataVariableType } from './DataVariable'; export function isDynamicValueDefinition(value: any): value is DynamicValueDefinition { - return typeof value === 'object' && [DataVariableType, ConditionalVariableType].includes(value?.type); + return ( + typeof value === 'object' && + [DataVariableType, ConditionalVariableType, CollectionVariableType].includes(value?.type) + ); } export function isDynamicValue(value: any): value is DynamicValue { @@ -23,7 +30,14 @@ export function evaluateVariable(variable: any, em: EditorModel) { return isDataVariable(variable) ? new DataVariable(variable, { em }).getDataValue() : variable; } -export function getDynamicValueInstance(valueDefinition: DynamicValueDefinition, em: EditorModel): DynamicValue { +export function getDynamicValueInstance( + valueDefinition: DynamicValueDefinition, + options: { + em: EditorModel; + collectionsStateMap?: CollectionsStateMap; + }, +): DynamicValue { + const { em } = options; const dynamicType = valueDefinition.type; let dynamicVariable: DynamicValue; @@ -36,6 +50,11 @@ export function getDynamicValueInstance(valueDefinition: DynamicValueDefinition, dynamicVariable = new DataCondition(condition, ifTrue, ifFalse, { em: em }); break; } + case CollectionVariableType: { + // @ts-ignore + dynamicVariable = new CollectionVariable(valueDefinition, options); + break; + } default: throw new Error(`Unsupported dynamic type: ${dynamicType}`); } @@ -43,8 +62,14 @@ export function getDynamicValueInstance(valueDefinition: DynamicValueDefinition, return dynamicVariable; } -export function evaluateDynamicValueDefinition(valueDefinition: DynamicValueDefinition, em: EditorModel) { - const dynamicVariable = getDynamicValueInstance(valueDefinition, em); +export function evaluateDynamicValueDefinition( + valueDefinition: DynamicValueDefinition, + options: { + em: EditorModel; + collectionsStateMap?: CollectionsStateMap; + }, +) { + const dynamicVariable = getDynamicValueInstance(valueDefinition, options); return { variable: dynamicVariable, value: dynamicVariable.getDataValue() }; } diff --git a/packages/core/src/data_sources/types.ts b/packages/core/src/data_sources/types.ts index 95e86123ec..f40bf729a6 100644 --- a/packages/core/src/data_sources/types.ts +++ b/packages/core/src/data_sources/types.ts @@ -1,12 +1,17 @@ import { ObjectAny } from '../common'; +import CollectionVariable from './model/collection_component/CollectionVariable'; +import { CollectionVariableDefinition } from '../../test/specs/dom_components/model/ComponentTypes'; import ComponentDataVariable from './model/ComponentDataVariable'; import DataRecord from './model/DataRecord'; import DataRecords from './model/DataRecords'; import DataVariable, { DataVariableDefinition } from './model/DataVariable'; import { ConditionalVariableDefinition, DataCondition } from './model/conditional_variables/DataCondition'; -export type DynamicValue = DataVariable | ComponentDataVariable | DataCondition; -export type DynamicValueDefinition = DataVariableDefinition | ConditionalVariableDefinition; +export type DynamicValue = DataVariable | ComponentDataVariable | DataCondition | CollectionVariable; +export type DynamicValueDefinition = + | DataVariableDefinition + | ConditionalVariableDefinition + | CollectionVariableDefinition; export interface DataRecordProps extends ObjectAny { /** * Record id. diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index caa45bd985..a5825dea12 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -1,3 +1,4 @@ +import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import { isUndefined, isFunction, @@ -53,7 +54,7 @@ import { } from './SymbolUtils'; import { ComponentDynamicValueWatcher } from './ComponentDynamicValueWatcher'; import { DynamicValueWatcher } from './DynamicValueWatcher'; -import { DynamicValueDefinition } from '../../data_sources/types'; +import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; export interface IComponent extends ExtractMethods {} export interface DynamicWatchersOptions { @@ -75,6 +76,7 @@ export const keySymbol = '__symbol'; export const keySymbolOvrd = '__symbol_ovrd'; export const keyUpdate = ComponentsEvents.update; export const keyUpdateInside = ComponentsEvents.updateInside; +export const keyCollectionsStateMap = '__collections_state_map'; /** * The Component object represents a single node of our template structure, so when you update its properties the changes are @@ -265,8 +267,22 @@ export default class Component extends StyleableModel { componentDVListener: ComponentDynamicValueWatcher; constructor(props: ComponentProperties = {}, opt: ComponentOptions) { - super(props, opt); - this.componentDVListener = new ComponentDynamicValueWatcher(this, opt.em); + if (props[keyCollectionsStateMap]) { + // @ts-ignore + props.components = props.components?.forEach((component) => ({ + ...component, + [keyCollectionsStateMap]: props[keyCollectionsStateMap], + })); + } + super(props, { + ...opt, + // @ts-ignore + [keyCollectionsStateMap]: props[keyCollectionsStateMap], + }); + this.componentDVListener = new ComponentDynamicValueWatcher(this, { + em: opt.em, + collectionsStateMap: props[keyCollectionsStateMap], + }); this.componentDVListener.addProps(props); bindAll(this, '__upSymbProps', '__upSymbCls', '__upSymbComps'); @@ -357,7 +373,9 @@ export default class Component extends StyleableModel { // @ts-ignore const em = this.em || options.em; - const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attributes, em); + // @ts-ignore + const collectionsStateMap = this.get(keyCollectionsStateMap) || options[keyCollectionsStateMap]; + const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attributes, { em, collectionsStateMap }); const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource; if (!shouldSkipWatcherUpdates) { @@ -687,7 +705,9 @@ export default class Component extends StyleableModel { setAttributes(attrs: ObjectAny, opts: SetAttrOptions = { skipWatcherUpdates: false, fromDataSource: false }) { // @ts-ignore const em = this.em || opts.em; - const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attrs, em); + // @ts-ignore + const collectionsStateMap = this.get(keyCollectionsStateMap) || opts[keyCollectionsStateMap]; + const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attrs, { em, collectionsStateMap }); const shouldSkipWatcherUpdates = opts.skipWatcherUpdates || opts.fromDataSource; if (!shouldSkipWatcherUpdates) { this.componentDVListener.setAttributes(attrs); diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 9114178336..c49cc51cd1 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -1,4 +1,5 @@ import { ObjectAny } from '../../common'; +import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import EditorModel from '../../editor/model/Editor'; import Component from './Component'; import { DynamicValueWatcher } from './DynamicValueWatcher'; @@ -9,10 +10,13 @@ export class ComponentDynamicValueWatcher { constructor( private component: Component, - em: EditorModel, + options: { + em: EditorModel; + collectionsStateMap: CollectionsStateMap; + }, ) { - this.propertyWatcher = new DynamicValueWatcher(this.createPropertyUpdater(), em); - this.attributeWatcher = new DynamicValueWatcher(this.createAttributeUpdater(), em); + this.propertyWatcher = new DynamicValueWatcher(this.createPropertyUpdater(), options); + this.attributeWatcher = new DynamicValueWatcher(this.createAttributeUpdater(), options); } private createPropertyUpdater() { diff --git a/packages/core/src/dom_components/model/DynamicValueWatcher.ts b/packages/core/src/dom_components/model/DynamicValueWatcher.ts index 88af44dad2..8a3df8a78f 100644 --- a/packages/core/src/dom_components/model/DynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/DynamicValueWatcher.ts @@ -1,3 +1,4 @@ +import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import { ObjectAny } from '../../common'; import DynamicVariableListenerManager from '../../data_sources/model/DataVariableListenerManager'; import { evaluateDynamicValueDefinition, isDynamicValueDefinition } from '../../data_sources/model/utils'; @@ -5,13 +6,27 @@ import { DynamicValue } from '../../data_sources/types'; import EditorModel from '../../editor/model/Editor'; export class DynamicValueWatcher { - dynamicVariableListeners: { [key: string]: DynamicVariableListenerManager } = {}; + private dynamicVariableListeners: { [key: string]: DynamicVariableListenerManager } = {}; + private em: EditorModel; + private collectionsStateMap: CollectionsStateMap | undefined; constructor( private updateFn: (key: string, value: any) => void, - private em: EditorModel, - ) {} + options: { + em: EditorModel; + collectionsStateMap?: CollectionsStateMap; + }, + ) { + this.em = options.em; + this.collectionsStateMap = options.collectionsStateMap; + } - static getStaticValues(values: ObjectAny | undefined, em: EditorModel): ObjectAny { + static getStaticValues( + values: ObjectAny | undefined, + options: { + em: EditorModel; + collectionsStateMap?: CollectionsStateMap; + }, + ): ObjectAny { if (!values) return {}; const evaluatedValues: ObjectAny = { ...values }; const propsKeys = Object.keys(values); @@ -20,7 +35,7 @@ export class DynamicValueWatcher { const valueDefinition = values[key]; if (!isDynamicValueDefinition(valueDefinition)) continue; - const { value } = evaluateDynamicValueDefinition(valueDefinition, em); + const { value } = evaluateDynamicValueDefinition(valueDefinition, options); evaluatedValues[key] = value; } @@ -68,7 +83,10 @@ export class DynamicValueWatcher { if (!isDynamicValueDefinition(values[key])) { continue; } - const { variable } = evaluateDynamicValueDefinition(values[key], this.em); + const { variable } = evaluateDynamicValueDefinition(values[key], { + em: this.em, + collectionsStateMap: this.collectionsStateMap, + }); dynamicValues[key] = variable; } diff --git a/packages/core/test/specs/dom_components/model/ComponentTypes.ts b/packages/core/test/specs/dom_components/model/ComponentTypes.ts index 63db8a0347..1d240e46ae 100644 --- a/packages/core/test/specs/dom_components/model/ComponentTypes.ts +++ b/packages/core/test/specs/dom_components/model/ComponentTypes.ts @@ -1,3 +1,5 @@ +import { CollectionVariableType } from '../../../../src/data_sources/model/collection_component/constants'; +import { CollectionState } from '../../../../src/data_sources/model/collection_component/types'; import Editor from '../../../../src/editor'; describe('Component Types', () => { @@ -96,3 +98,9 @@ describe('Component Types', () => { expect(cmp.components().at(0).is('svg-in')).toBe(true); }); }); +export type CollectionVariableDefinition = { + type: typeof CollectionVariableType; + variable_type: keyof CollectionState; + collection_name?: string; + path?: string; +}; From 721f7b02c554f4db2d6df5c3df9d63e5653ad8b5 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 3 Jan 2025 11:12:59 +0200 Subject: [PATCH 24/87] Refactor dynamic component watcher --- .../src/dom_components/model/Component.ts | 28 ++++++------- .../model/ComponentDynamicValueWatcher.ts | 32 ++++++++++----- .../model/DynamicValueWatcher.ts | 39 ++++++++----------- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index a5825dea12..151f9326e5 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -274,16 +274,16 @@ export default class Component extends StyleableModel { [keyCollectionsStateMap]: props[keyCollectionsStateMap], })); } + const componentDVListener = new ComponentDynamicValueWatcher(undefined, { + em: opt.em, + collectionsStateMap: props[keyCollectionsStateMap], + }); super(props, { ...opt, // @ts-ignore - [keyCollectionsStateMap]: props[keyCollectionsStateMap], - }); - this.componentDVListener = new ComponentDynamicValueWatcher(this, { - em: opt.em, - collectionsStateMap: props[keyCollectionsStateMap], + componentDVListener, }); - this.componentDVListener.addProps(props); + this.componentDVListener = componentDVListener; bindAll(this, '__upSymbProps', '__upSymbCls', '__upSymbComps'); const em = opt.em; @@ -372,14 +372,12 @@ export default class Component extends StyleableModel { } // @ts-ignore - const em = this.em || options.em; - // @ts-ignore - const collectionsStateMap = this.get(keyCollectionsStateMap) || options[keyCollectionsStateMap]; - const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attributes, { em, collectionsStateMap }); + const componentDVListener = this.componentDVListener || options.componentDVListener; + const evaluatedAttributes = componentDVListener.getStaticValues(attributes); const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource; if (!shouldSkipWatcherUpdates) { - this.componentDVListener?.addProps(attributes); + componentDVListener?.addProps(attributes); } return super.set(evaluatedAttributes, options); @@ -704,13 +702,11 @@ export default class Component extends StyleableModel { */ setAttributes(attrs: ObjectAny, opts: SetAttrOptions = { skipWatcherUpdates: false, fromDataSource: false }) { // @ts-ignore - const em = this.em || opts.em; - // @ts-ignore - const collectionsStateMap = this.get(keyCollectionsStateMap) || opts[keyCollectionsStateMap]; - const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attrs, { em, collectionsStateMap }); + const componentDVListener = this.componentDVListener || opts.componentDVListener; + const evaluatedAttributes = componentDVListener.getStaticValues(attrs); const shouldSkipWatcherUpdates = opts.skipWatcherUpdates || opts.fromDataSource; if (!shouldSkipWatcherUpdates) { - this.componentDVListener.setAttributes(attrs); + componentDVListener.setAttributes(attrs); } this.set('attributes', { ...evaluatedAttributes }, opts); diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index c49cc51cd1..98ecf1938e 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -9,28 +9,42 @@ export class ComponentDynamicValueWatcher { private attributeWatcher: DynamicValueWatcher; constructor( - private component: Component, - options: { + component: Component | undefined, + private options: { em: EditorModel; collectionsStateMap: CollectionsStateMap; }, ) { - this.propertyWatcher = new DynamicValueWatcher(this.createPropertyUpdater(), options); - this.attributeWatcher = new DynamicValueWatcher(this.createAttributeUpdater(), options); + this.propertyWatcher = new DynamicValueWatcher(component, this.createPropertyUpdater(), options); + this.attributeWatcher = new DynamicValueWatcher(component, this.createAttributeUpdater(), options); } - private createPropertyUpdater() { - return (key: string, value: any) => { - this.component.set(key, value, { fromDataSource: true, avoidStore: true }); + return (component: Component | undefined, key: string, value: any) => { + if (!component) return; + component.set(key, value, { fromDataSource: true, avoidStore: true }); }; } private createAttributeUpdater() { - return (key: string, value: any) => { - this.component.addAttributes({ [key]: value }, { fromDataSource: true, avoidStore: true }); + return (component: Component | undefined, key: string, value: any) => { + if (!component) return; + component.addAttributes({ [key]: value }, { fromDataSource: true, avoidStore: true }); }; } + bindComponent(component: Component) { + this.propertyWatcher.bindComponent(component); + this.attributeWatcher.bindComponent(component); + } + + getStaticValues(values: ObjectAny | undefined): ObjectAny { + return this.attributeWatcher.getStaticValues(values); + } + + areStaticValues(values: ObjectAny | undefined) { + return this.attributeWatcher.areStaticValues(values); + } + addProps(props: ObjectAny) { this.propertyWatcher.addDynamicValues(props); } diff --git a/packages/core/src/dom_components/model/DynamicValueWatcher.ts b/packages/core/src/dom_components/model/DynamicValueWatcher.ts index 8a3df8a78f..67189227db 100644 --- a/packages/core/src/dom_components/model/DynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/DynamicValueWatcher.ts @@ -4,29 +4,20 @@ import DynamicVariableListenerManager from '../../data_sources/model/DataVariabl import { evaluateDynamicValueDefinition, isDynamicValueDefinition } from '../../data_sources/model/utils'; import { DynamicValue } from '../../data_sources/types'; import EditorModel from '../../editor/model/Editor'; +import Component from './Component'; export class DynamicValueWatcher { private dynamicVariableListeners: { [key: string]: DynamicVariableListenerManager } = {}; - private em: EditorModel; - private collectionsStateMap: CollectionsStateMap | undefined; constructor( - private updateFn: (key: string, value: any) => void, - options: { + private component: Component | undefined, + private updateFn: (component: Component | undefined, key: string, value: any) => void, + private options: { em: EditorModel; collectionsStateMap?: CollectionsStateMap; }, - ) { - this.em = options.em; - this.collectionsStateMap = options.collectionsStateMap; - } + ) {} - static getStaticValues( - values: ObjectAny | undefined, - options: { - em: EditorModel; - collectionsStateMap?: CollectionsStateMap; - }, - ): ObjectAny { + getStaticValues(values: ObjectAny | undefined): ObjectAny { if (!values) return {}; const evaluatedValues: ObjectAny = { ...values }; const propsKeys = Object.keys(values); @@ -35,26 +26,31 @@ export class DynamicValueWatcher { const valueDefinition = values[key]; if (!isDynamicValueDefinition(valueDefinition)) continue; - const { value } = evaluateDynamicValueDefinition(valueDefinition, options); + const { value } = evaluateDynamicValueDefinition(valueDefinition, this.options); evaluatedValues[key] = value; } return evaluatedValues; } - static areStaticValues(values: ObjectAny | undefined) { + areStaticValues(values: ObjectAny | undefined) { if (!values) return true; return Object.keys(values).every((key) => { return !isDynamicValueDefinition(values[key]); }); } + bindComponent(component: Component) { + this.component = component; + } + setDynamicValues(values: ObjectAny | undefined) { this.removeListeners(); return this.addDynamicValues(values); } addDynamicValues(values: ObjectAny | undefined) { + const em = this.options.em; if (!values) return {}; this.removeListeners(Object.keys(values)); const dynamicProps = this.getDynamicValues(values); @@ -62,10 +58,10 @@ export class DynamicValueWatcher { for (let index = 0; index < propsKeys.length; index++) { const key = propsKeys[index]; this.dynamicVariableListeners[key] = new DynamicVariableListenerManager({ - em: this.em, + em: em, dataVariable: dynamicProps[key], updateValueFromDataVariable: (value: any) => { - this.updateFn.bind(this)(key, value); + this.updateFn.bind(this)(this.component, key, value); }, }); } @@ -83,10 +79,7 @@ export class DynamicValueWatcher { if (!isDynamicValueDefinition(values[key])) { continue; } - const { variable } = evaluateDynamicValueDefinition(values[key], { - em: this.em, - collectionsStateMap: this.collectionsStateMap, - }); + const { variable } = evaluateDynamicValueDefinition(values[key], this.options); dynamicValues[key] = variable; } From c123c181f7fbdcafc215803ac7e829275af471a0 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 3 Jan 2025 11:32:43 +0200 Subject: [PATCH 25/87] Refactor mehods for dynamic value watchers --- .../src/dom_components/model/Component.ts | 20 ++------ .../model/ComponentDynamicValueWatcher.ts | 23 +++------ .../model/DynamicValueWatcher.ts | 50 ++++++++++++------- 3 files changed, 43 insertions(+), 50 deletions(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 151f9326e5..203e937e98 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -53,14 +53,9 @@ import { updateSymbolProps, } from './SymbolUtils'; import { ComponentDynamicValueWatcher } from './ComponentDynamicValueWatcher'; -import { DynamicValueWatcher } from './DynamicValueWatcher'; -import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; +import { DynamicWatchersOptions } from './DynamicValueWatcher'; export interface IComponent extends ExtractMethods {} -export interface DynamicWatchersOptions { - skipWatcherUpdates?: boolean; - fromDataSource?: boolean; -} export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {} export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions {} @@ -373,12 +368,7 @@ export default class Component extends StyleableModel { // @ts-ignore const componentDVListener = this.componentDVListener || options.componentDVListener; - const evaluatedAttributes = componentDVListener.getStaticValues(attributes); - - const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource; - if (!shouldSkipWatcherUpdates) { - componentDVListener?.addProps(attributes); - } + const evaluatedAttributes = componentDVListener.addProps(attributes, options); return super.set(evaluatedAttributes, options); } @@ -703,11 +693,7 @@ export default class Component extends StyleableModel { setAttributes(attrs: ObjectAny, opts: SetAttrOptions = { skipWatcherUpdates: false, fromDataSource: false }) { // @ts-ignore const componentDVListener = this.componentDVListener || opts.componentDVListener; - const evaluatedAttributes = componentDVListener.getStaticValues(attrs); - const shouldSkipWatcherUpdates = opts.skipWatcherUpdates || opts.fromDataSource; - if (!shouldSkipWatcherUpdates) { - componentDVListener.setAttributes(attrs); - } + const evaluatedAttributes = componentDVListener.setAttributes(attrs, opts); this.set('attributes', { ...evaluatedAttributes }, opts); return this; diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 98ecf1938e..e311b969d8 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -2,6 +2,7 @@ import { ObjectAny } from '../../common'; import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import EditorModel from '../../editor/model/Editor'; import Component from './Component'; +import { DynamicWatchersOptions } from './DynamicValueWatcher'; import { DynamicValueWatcher } from './DynamicValueWatcher'; export class ComponentDynamicValueWatcher { @@ -10,7 +11,7 @@ export class ComponentDynamicValueWatcher { constructor( component: Component | undefined, - private options: { + options: { em: EditorModel; collectionsStateMap: CollectionsStateMap; }, @@ -37,24 +38,16 @@ export class ComponentDynamicValueWatcher { this.attributeWatcher.bindComponent(component); } - getStaticValues(values: ObjectAny | undefined): ObjectAny { - return this.attributeWatcher.getStaticValues(values); + addProps(props: ObjectAny, options?: DynamicWatchersOptions) { + return this.propertyWatcher.addDynamicValues(props, options); } - areStaticValues(values: ObjectAny | undefined) { - return this.attributeWatcher.areStaticValues(values); + addAttributes(attributes: ObjectAny, options?: DynamicWatchersOptions) { + return this.attributeWatcher.addDynamicValues(attributes, options); } - addProps(props: ObjectAny) { - this.propertyWatcher.addDynamicValues(props); - } - - addAttributes(attributes: ObjectAny) { - this.attributeWatcher.addDynamicValues(attributes); - } - - setAttributes(attributes: ObjectAny) { - this.attributeWatcher.setDynamicValues(attributes); + setAttributes(attributes: ObjectAny, options?: DynamicWatchersOptions) { + return this.attributeWatcher.setDynamicValues(attributes, options); } removeAttributes(attributes: string[]) { diff --git a/packages/core/src/dom_components/model/DynamicValueWatcher.ts b/packages/core/src/dom_components/model/DynamicValueWatcher.ts index 67189227db..4e17b18261 100644 --- a/packages/core/src/dom_components/model/DynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/DynamicValueWatcher.ts @@ -6,6 +6,11 @@ import { DynamicValue } from '../../data_sources/types'; import EditorModel from '../../editor/model/Editor'; import Component from './Component'; +export interface DynamicWatchersOptions { + skipWatcherUpdates?: boolean; + fromDataSource?: boolean; +} + export class DynamicValueWatcher { private dynamicVariableListeners: { [key: string]: DynamicVariableListenerManager } = {}; constructor( @@ -33,27 +38,34 @@ export class DynamicValueWatcher { return evaluatedValues; } - areStaticValues(values: ObjectAny | undefined) { - if (!values) return true; - return Object.keys(values).every((key) => { - return !isDynamicValueDefinition(values[key]); - }); - } - bindComponent(component: Component) { this.component = component; } - setDynamicValues(values: ObjectAny | undefined) { - this.removeListeners(); - return this.addDynamicValues(values); + setDynamicValues(values: ObjectAny | undefined, options: DynamicWatchersOptions = {}) { + const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource; + if (!shouldSkipWatcherUpdates) { + this.removeListeners(); + } + + return this.addDynamicValues(values, options); } - addDynamicValues(values: ObjectAny | undefined) { - const em = this.options.em; + addDynamicValues(values: ObjectAny | undefined, options: DynamicWatchersOptions = {}) { if (!values) return {}; - this.removeListeners(Object.keys(values)); - const dynamicProps = this.getDynamicValues(values); + const { evaluatedValues, dynamicValues } = this.getDynamicValues(values); + + const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource; + if (!shouldSkipWatcherUpdates) { + this.updateListeners(dynamicValues); + } + + return evaluatedValues; + } + + private updateListeners(dynamicProps: { [key: string]: DynamicValue }) { + const em = this.options.em; + this.removeListeners(Object.keys(dynamicProps)); const propsKeys = Object.keys(dynamicProps); for (let index = 0; index < propsKeys.length; index++) { const key = propsKeys[index]; @@ -65,25 +77,27 @@ export class DynamicValueWatcher { }, }); } - - return dynamicProps; } private getDynamicValues(values: ObjectAny) { const dynamicValues: { [key: string]: DynamicValue; } = {}; + const evaluatedValues: { + [key: string]: DynamicValue; + } = { ...values }; const propsKeys = Object.keys(values); for (let index = 0; index < propsKeys.length; index++) { const key = propsKeys[index]; if (!isDynamicValueDefinition(values[key])) { continue; } - const { variable } = evaluateDynamicValueDefinition(values[key], this.options); + const { value, variable } = evaluateDynamicValueDefinition(values[key], this.options); + evaluatedValues[key] = value; dynamicValues[key] = variable; } - return dynamicValues; + return { evaluatedValues, dynamicValues }; } /** From 6aa72446494f265ea86aba9b365938d633832554 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 3 Jan 2025 11:34:52 +0200 Subject: [PATCH 26/87] Bind watcher to component in the constructor --- packages/core/src/dom_components/model/Component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 203e937e98..6f9b3b1c48 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -278,6 +278,7 @@ export default class Component extends StyleableModel { // @ts-ignore componentDVListener, }); + componentDVListener.bindComponent(this); this.componentDVListener = componentDVListener; bindAll(this, '__upSymbProps', '__upSymbCls', '__upSymbComps'); From 369caed2f3d47c85415b120903100c0e8c44690a Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 3 Jan 2025 15:28:40 +0200 Subject: [PATCH 27/87] Move ovveriding collection variables to component watcher --- .../CollectionComponent.ts | 95 +------------------ .../src/dom_components/model/Component.ts | 10 +- .../model/ComponentDynamicValueWatcher.ts | 31 +++++- .../model/DynamicValueWatcher.ts | 41 ++++---- 4 files changed, 57 insertions(+), 120 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 2be747d913..f6a69c5f3f 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -8,12 +8,7 @@ import { ObjectAny } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; import { keyCollectionsStateMap } from '../../../dom_components/model/Component'; import { CollectionDefinition, CollectionState, CollectionsStateMap } from './types'; -import { - keyCollectionDefinition, - keyInnerCollectionState, - CollectionComponentType, - CollectionVariableType, -} from './constants'; +import { keyCollectionDefinition, keyInnerCollectionState, CollectionComponentType } from './constants'; export default class CollectionComponent extends Component { constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { @@ -53,12 +48,11 @@ export default class CollectionComponent extends Component { }; if (index === start_index) { - const { clonedBlock } = resolveBlockValues(collectionsStateMap, block); - const type = em.Components.getType(clonedBlock?.type || 'default'); + const type = em.Components.getType(block?.type || 'default'); const model = type.model; blockComponent = new model( { - ...clonedBlock, + ...block, [keyCollectionsStateMap]: collectionsStateMap, }, opt, @@ -126,9 +120,8 @@ function resolveComponent( collectionsStateMap: CollectionsStateMap, em: EditorModel, ) { - const { resolvedCollectionValues } = resolveBlockValues(collectionsStateMap, block); - Object.keys(resolvedCollectionValues).length && component!.setSymbolOverride(Object.keys(resolvedCollectionValues)); - component!.set(resolvedCollectionValues); + // @ts-ignore + component!.set(block); const children: ComponentDefinition[] = []; for (let index = 0; index < component!.components().length; index++) { @@ -147,81 +140,3 @@ function resolveComponent( return componentDefinition; } - -// TODO: remove this function -function resolveBlockValues(collectionsStateMap: CollectionsStateMap, block: ObjectAny) { - const clonedBlock = deepCloneObject(block); - const resolvedCollectionValues: ObjectAny = {}; - - if (typeof clonedBlock === 'object') { - const blockKeys = Object.keys(clonedBlock); - for (const key of blockKeys) { - let blockValue = clonedBlock[key]; - if (key === keyCollectionDefinition) continue; - let hasCollectionVariable = false; - - if (typeof blockValue === 'object') { - const isCollectionVariable = blockValue.type === 'parent-collection-variable'; - if (isCollectionVariable) { - hasCollectionVariable = true; - } else if (Array.isArray(blockValue)) { - clonedBlock[key] = blockValue.map((arrayItem: any) => { - const { clonedBlock, resolvedCollectionValues: itemOverrideKeys } = resolveBlockValues( - collectionsStateMap, - arrayItem, - ); - if (!isEmptyObject(itemOverrideKeys)) { - hasCollectionVariable = true; - } - - return typeof arrayItem === 'object' ? clonedBlock : arrayItem; - }); - } else { - const { clonedBlock, resolvedCollectionValues: itemOverrideKeys } = resolveBlockValues( - collectionsStateMap, - blockValue, - ); - clonedBlock[key] = clonedBlock; - - if (!isEmptyObject(itemOverrideKeys)) { - hasCollectionVariable = true; - } - } - - if (hasCollectionVariable && key !== 'components') { - resolvedCollectionValues[key] = clonedBlock[key]; - } - } - } - } - - return { clonedBlock, resolvedCollectionValues }; -} - -function isEmptyObject(itemOverrideKeys: ObjectAny) { - return Object.keys(itemOverrideKeys).length === 0; -} - -/** - * Deeply clones an object. - * @template T The type of the object to clone. - * @param {T} obj The object to clone. - * @returns {T} A deep clone of the object, or the original object if it's not an object or is null. Returns undefined if input is undefined. - */ -function deepCloneObject | null | undefined>(obj: T): T { - if (obj === null) return null as T; - if (obj === undefined) return undefined as T; - if (typeof obj !== 'object' || Array.isArray(obj)) { - return obj; // Return primitives directly - } - - const clonedObj: Record = {}; - - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - clonedObj[key] = deepCloneObject(obj[key]); - } - } - - return clonedObj as T; -} diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 6f9b3b1c48..e0250e5138 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -368,10 +368,10 @@ export default class Component extends StyleableModel { } // @ts-ignore - const componentDVListener = this.componentDVListener || options.componentDVListener; - const evaluatedAttributes = componentDVListener.addProps(attributes, options); + this.componentDVListener = this.componentDVListener || options.componentDVListener; + const evaluatedProps = this.componentDVListener.addProps(attributes, options); - return super.set(evaluatedAttributes, options); + return super.set(evaluatedProps, options); } __postAdd(opts: { recursive?: boolean } = {}) { @@ -692,9 +692,7 @@ export default class Component extends StyleableModel { * component.setAttributes({ id: 'test', 'data-key': 'value' }); */ setAttributes(attrs: ObjectAny, opts: SetAttrOptions = { skipWatcherUpdates: false, fromDataSource: false }) { - // @ts-ignore - const componentDVListener = this.componentDVListener || opts.componentDVListener; - const evaluatedAttributes = componentDVListener.setAttributes(attrs, opts); + const evaluatedAttributes = this.componentDVListener.setAttributes(attrs, opts); this.set('attributes', { ...evaluatedAttributes }, opts); return this; diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index e311b969d8..8abe42e49b 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -1,4 +1,5 @@ import { ObjectAny } from '../../common'; +import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import EditorModel from '../../editor/model/Editor'; import Component from './Component'; @@ -10,7 +11,7 @@ export class ComponentDynamicValueWatcher { private attributeWatcher: DynamicValueWatcher; constructor( - component: Component | undefined, + private component: Component | undefined, options: { em: EditorModel; collectionsStateMap: CollectionsStateMap; @@ -19,6 +20,7 @@ export class ComponentDynamicValueWatcher { this.propertyWatcher = new DynamicValueWatcher(component, this.createPropertyUpdater(), options); this.attributeWatcher = new DynamicValueWatcher(component, this.createAttributeUpdater(), options); } + private createPropertyUpdater() { return (component: Component | undefined, key: string, value: any) => { if (!component) return; @@ -34,24 +36,45 @@ export class ComponentDynamicValueWatcher { } bindComponent(component: Component) { + this.component = component; this.propertyWatcher.bindComponent(component); this.attributeWatcher.bindComponent(component); + this.updateSymbolOverride(); } addProps(props: ObjectAny, options?: DynamicWatchersOptions) { - return this.propertyWatcher.addDynamicValues(props, options); + const evaluatedProps = this.propertyWatcher.addDynamicValues(props, options); + return evaluatedProps; } addAttributes(attributes: ObjectAny, options?: DynamicWatchersOptions) { - return this.attributeWatcher.addDynamicValues(attributes, options); + const evaluatedAttributes = this.attributeWatcher.addDynamicValues(attributes, options); + this.updateSymbolOverride(); + return evaluatedAttributes; } setAttributes(attributes: ObjectAny, options?: DynamicWatchersOptions) { - return this.attributeWatcher.setDynamicValues(attributes, options); + const evaluatedAttributes = this.attributeWatcher.setDynamicValues(attributes, options); + this.updateSymbolOverride(); + return evaluatedAttributes; } removeAttributes(attributes: string[]) { this.attributeWatcher.removeListeners(attributes); + this.updateSymbolOverride(); + } + + updateSymbolOverride() { + if (!this.component) return; + + const keys = this.propertyWatcher.getDynamicValuesOfType(CollectionVariableType); + const attributesKeys = this.attributeWatcher.getDynamicValuesOfType(CollectionVariableType); + + const combinedKeys = [...keys]; + const haveOverridenAttributes = Object.keys(attributesKeys).length; + if (haveOverridenAttributes) combinedKeys.push('attributes'); + + this.component.setSymbolOverride(combinedKeys); } getDynamicPropsDefs() { diff --git a/packages/core/src/dom_components/model/DynamicValueWatcher.ts b/packages/core/src/dom_components/model/DynamicValueWatcher.ts index 4e17b18261..8d12560fdd 100644 --- a/packages/core/src/dom_components/model/DynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/DynamicValueWatcher.ts @@ -1,3 +1,4 @@ +import { DynamicValueDefinition } from './../../data_sources/types'; import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import { ObjectAny } from '../../common'; import DynamicVariableListenerManager from '../../data_sources/model/DataVariableListenerManager'; @@ -5,6 +6,7 @@ import { evaluateDynamicValueDefinition, isDynamicValueDefinition } from '../../ import { DynamicValue } from '../../data_sources/types'; import EditorModel from '../../editor/model/Editor'; import Component from './Component'; +import CollectionVariable from '../../data_sources/model/collection_component/CollectionVariable'; export interface DynamicWatchersOptions { skipWatcherUpdates?: boolean; @@ -22,22 +24,6 @@ export class DynamicValueWatcher { }, ) {} - getStaticValues(values: ObjectAny | undefined): ObjectAny { - if (!values) return {}; - const evaluatedValues: ObjectAny = { ...values }; - const propsKeys = Object.keys(values); - - for (const key of propsKeys) { - const valueDefinition = values[key]; - if (!isDynamicValueDefinition(valueDefinition)) continue; - - const { value } = evaluateDynamicValueDefinition(valueDefinition, this.options); - evaluatedValues[key] = value; - } - - return evaluatedValues; - } - bindComponent(component: Component) { this.component = component; } @@ -53,7 +39,7 @@ export class DynamicValueWatcher { addDynamicValues(values: ObjectAny | undefined, options: DynamicWatchersOptions = {}) { if (!values) return {}; - const { evaluatedValues, dynamicValues } = this.getDynamicValues(values); + const { evaluatedValues, dynamicValues } = this.evaluateValues(values); const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource; if (!shouldSkipWatcherUpdates) { @@ -79,13 +65,14 @@ export class DynamicValueWatcher { } } - private getDynamicValues(values: ObjectAny) { + private evaluateValues(values: ObjectAny) { const dynamicValues: { [key: string]: DynamicValue; } = {}; const evaluatedValues: { - [key: string]: DynamicValue; + [key: string]: any; } = { ...values }; + const valuesToBeOverriden: string[] = []; const propsKeys = Object.keys(values); for (let index = 0; index < propsKeys.length; index++) { const key = propsKeys[index]; @@ -95,9 +82,12 @@ export class DynamicValueWatcher { const { value, variable } = evaluateDynamicValueDefinition(values[key], this.options); evaluatedValues[key] = value; dynamicValues[key] = variable; + if (variable instanceof CollectionVariable) { + valuesToBeOverriden.push(key); + } } - return { evaluatedValues, dynamicValues }; + return { evaluatedValues, dynamicValues, valuesToBeOverriden }; } /** @@ -113,6 +103,8 @@ export class DynamicValueWatcher { delete this.dynamicVariableListeners[key]; } }); + + return propsKeys; } getSerializableValues(values: ObjectAny | undefined) { @@ -139,4 +131,13 @@ export class DynamicValueWatcher { return serializableValues; } + + getDynamicValuesOfType(type: DynamicValueDefinition['type']) { + const keys = Object.keys(this.dynamicVariableListeners).filter((key: string) => { + // @ts-ignore + return this.dynamicVariableListeners[key].dynamicVariable.get('type') === type; + }); + + return keys; + } } From 54320d4a90bda8ee5876aa26ef898ec2dd5517be Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 3 Jan 2025 15:56:44 +0200 Subject: [PATCH 28/87] Add collection component stringfication --- .../CollectionComponent.ts | 22 +++++++++++++++---- .../model/collection_component/types.ts | 11 ++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index f6a69c5f3f..892d0596b9 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -1,17 +1,17 @@ import { DataVariableType } from './../DataVariable'; import { isArray } from 'underscore'; -import Component from '../../../dom_components/model/Component'; +import Component, { keySymbol, keySymbolOvrd, keySymbols } from '../../../dom_components/model/Component'; import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; import { toLowerCase } from '../../../utils/mixins'; import DataSource from '../DataSource'; import { ObjectAny } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; import { keyCollectionsStateMap } from '../../../dom_components/model/Component'; -import { CollectionDefinition, CollectionState, CollectionsStateMap } from './types'; +import { CollectionComponentDefinition, CollectionDefinition, CollectionState, CollectionsStateMap } from './types'; import { keyCollectionDefinition, keyInnerCollectionState, CollectionComponentType } from './constants'; export default class CollectionComponent extends Component { - constructor(props: CollectionDefinition & ComponentProperties, opt: ComponentOptions) { + constructor(props: CollectionComponentDefinition, opt: ComponentOptions) { const em = opt.em; const { collection_name, block, config } = props[keyCollectionDefinition]; if (!block) { @@ -42,12 +42,13 @@ export default class CollectionComponent extends Component { }; const collectionsStateMap: CollectionsStateMap = { - ...props[keyCollectionsStateMap], + ...(props[keyCollectionsStateMap] || {}), ...(collection_name && { [collection_name]: collectionState }), [keyInnerCollectionState]: collectionState, }; if (index === start_index) { + // @ts-ignore const type = em.Components.getType(block?.type || 'default'); const model = type.model; blockComponent = new model( @@ -70,6 +71,7 @@ export default class CollectionComponent extends Component { components: components, dropbbable: false, }; + // @ts-ignore super(conditionalCmptDef, opt); } @@ -77,6 +79,18 @@ export default class CollectionComponent extends Component { static isComponent(el: HTMLElement) { return toLowerCase(el.tagName) === CollectionComponentType; } + + toJSON(opts?: ObjectAny) { + const json = super.toJSON(opts) as CollectionComponentDefinition; + + const firstChild = this.components().at(0)?.toJSON() || {}; + const keysToRemove = ['attributes?.id', keySymbol, keySymbols, keySymbolOvrd, keyCollectionsStateMap]; + keysToRemove.forEach((key) => delete firstChild[key]); + json[keyCollectionDefinition].block = firstChild; + + delete json.components; + return json; + } } function getDataSourceItems(dataSource: any, em: EditorModel) { diff --git a/packages/core/src/data_sources/model/collection_component/types.ts b/packages/core/src/data_sources/model/collection_component/types.ts index b61abc8af2..3b78451ee2 100644 --- a/packages/core/src/data_sources/model/collection_component/types.ts +++ b/packages/core/src/data_sources/model/collection_component/types.ts @@ -1,14 +1,13 @@ -import { CollectionComponentType } from './constants'; +import { CollectionComponentType, keyCollectionDefinition } from './constants'; -import { ComponentDefinition } from '../../../dom_components/model/types'; +import { ComponentDefinition, ComponentProperties } from '../../../dom_components/model/types'; import { CollectionVariableDefinition } from '../../../../test/specs/dom_components/model/ComponentTypes'; import { DataVariableDefinition } from '../DataVariable'; -import { ConditionDefinition } from '../conditional_variables/DataCondition'; type CollectionDataSource = any[] | DataVariableDefinition | CollectionVariableDefinition; type CollectionConfig = { start_index?: number; - end_index?: number | ConditionDefinition; + end_index?: number; dataSource: CollectionDataSource; }; @@ -26,6 +25,10 @@ export type CollectionsStateMap = { [key: string]: CollectionState; }; +export type CollectionComponentDefinition = { + [keyCollectionDefinition]: CollectionDefinition; +} & ComponentDefinition; + export type CollectionDefinition = { type: typeof CollectionComponentType; collection_name?: string; From fe4b09fd318644ae8b8c1d91e677e207eae2bf46 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 3 Jan 2025 16:34:53 +0200 Subject: [PATCH 29/87] Refactor getting collection items --- .../CollectionComponent.ts | 120 ++++++++++-------- 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 892d0596b9..8ad7d04fb0 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -13,57 +13,15 @@ import { keyCollectionDefinition, keyInnerCollectionState, CollectionComponentTy export default class CollectionComponent extends Component { constructor(props: CollectionComponentDefinition, opt: ComponentOptions) { const em = opt.em; - const { collection_name, block, config } = props[keyCollectionDefinition]; - if (!block) { - throw new Error('The "block" property is required in the collection definition.'); - } - - if (!config?.dataSource) { - throw new Error('The "config.dataSource" property is required in the collection definition.'); - } + const collectionDefinition = props[keyCollectionDefinition]; + const parentCollectionStateMap = (props[keyCollectionsStateMap] || {}) as CollectionsStateMap; - let items: any[] = getDataSourceItems(config.dataSource, em); - const components: ComponentDefinition[] = []; - const start_index = Math.max(0, config.start_index || 0); - const end_index = Math.min(items.length - 1, config.end_index || Number.MAX_VALUE); - - const total_items = end_index - start_index + 1; - let blockComponent: Component; - for (let index = start_index; index <= end_index; index++) { - const item = items[index]; - const collectionState: CollectionState = { - collection_name, - current_index: index, - current_item: item, - start_index: start_index, - end_index: end_index, - total_items: total_items, - remaining_items: total_items - (index + 1), - }; - - const collectionsStateMap: CollectionsStateMap = { - ...(props[keyCollectionsStateMap] || {}), - ...(collection_name && { [collection_name]: collectionState }), - [keyInnerCollectionState]: collectionState, - }; - - if (index === start_index) { - // @ts-ignore - const type = em.Components.getType(block?.type || 'default'); - const model = type.model; - blockComponent = new model( - { - ...block, - [keyCollectionsStateMap]: collectionsStateMap, - }, - opt, - ); - } - const instance = em.Components.addSymbol(blockComponent!); - const cmpDefinition = resolveComponent(instance!, block, collectionsStateMap, em); - - components.push(cmpDefinition); - } + const components: ComponentDefinition[] = getCollectionItems( + em, + collectionDefinition, + parentCollectionStateMap, + opt, + ); const conditionalCmptDef = { ...props, @@ -93,6 +51,68 @@ export default class CollectionComponent extends Component { } } +function getCollectionItems( + em: EditorModel, + collectionDefinition: CollectionDefinition, + parentCollectionStateMap: CollectionsStateMap, + opt: ComponentOptions, +) { + const { collection_name, block, config } = collectionDefinition; + if (!block) { + throw new Error('The "block" property is required in the collection definition.'); + } + + if (!config?.dataSource) { + throw new Error('The "config.dataSource" property is required in the collection definition.'); + } + + const components: ComponentDefinition[] = []; + + let items: any[] = getDataSourceItems(config.dataSource, em); + const start_index = Math.max(0, config.start_index || 0); + const end_index = Math.min(items.length - 1, config.end_index || Number.MAX_VALUE); + + const total_items = end_index - start_index + 1; + let blockComponent: Component; + for (let index = start_index; index <= end_index; index++) { + const item = items[index]; + const collectionState: CollectionState = { + collection_name, + current_index: index, + current_item: item, + start_index: start_index, + end_index: end_index, + total_items: total_items, + remaining_items: total_items - (index + 1), + }; + + const collectionsStateMap: CollectionsStateMap = { + ...parentCollectionStateMap, + ...(collection_name && { [collection_name]: collectionState }), + [keyInnerCollectionState]: collectionState, + }; + + if (index === start_index) { + // @ts-ignore + const type = em.Components.getType(block?.type || 'default'); + const model = type.model; + blockComponent = new model( + { + ...block, + [keyCollectionsStateMap]: collectionsStateMap, + }, + opt, + ); + } + const instance = em.Components.addSymbol(blockComponent!); + const cmpDefinition = resolveComponent(instance!, block, collectionsStateMap, em); + + components.push(cmpDefinition); + } + + return components; +} + function getDataSourceItems(dataSource: any, em: EditorModel) { let items: any[] = []; switch (true) { From c425c7ed910b3e3e7981fda8ac5ee948a64c7ba8 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 6 Jan 2025 11:27:54 +0200 Subject: [PATCH 30/87] Update collection items on datasource updates --- .../CollectionComponent.ts | 28 +++++++++++++++-- .../model/ComponentDynamicValueWatcher.ts | 1 + .../model/DynamicValueWatcher.ts | 31 ++++++++----------- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 8ad7d04fb0..7521bd2687 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -1,4 +1,4 @@ -import { DataVariableType } from './../DataVariable'; +import DataVariable, { DataVariableType } from './../DataVariable'; import { isArray } from 'underscore'; import Component, { keySymbol, keySymbolOvrd, keySymbols } from '../../../dom_components/model/Component'; import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; @@ -9,6 +9,7 @@ import EditorModel from '../../../editor/model/Editor'; import { keyCollectionsStateMap } from '../../../dom_components/model/Component'; import { CollectionComponentDefinition, CollectionDefinition, CollectionState, CollectionsStateMap } from './types'; import { keyCollectionDefinition, keyInnerCollectionState, CollectionComponentType } from './constants'; +import DynamicVariableListenerManager from '../DataVariableListenerManager'; export default class CollectionComponent extends Component { constructor(props: CollectionComponentDefinition, opt: ComponentOptions) { @@ -32,12 +33,35 @@ export default class CollectionComponent extends Component { // @ts-ignore super(conditionalCmptDef, opt); + + if (this.hasDynamicDataSource()) { + const path = this.get(keyCollectionDefinition).config.dataSource?.path; + new DynamicVariableListenerManager({ + em: em, + dataVariable: new DataVariable( + { + type: 'data-variable', + path, + }, + { em }, + ), + updateValueFromDataVariable: () => { + const collectionItems = getCollectionItems(em, collectionDefinition, parentCollectionStateMap, opt); + this.components(collectionItems); + }, + }); + } } static isComponent(el: HTMLElement) { return toLowerCase(el.tagName) === CollectionComponentType; } + hasDynamicDataSource() { + const dataSource = this.get(keyCollectionDefinition).config.dataSource; + return typeof dataSource === 'object' && dataSource.type === DataVariableType; + } + toJSON(opts?: ObjectAny) { const json = super.toJSON(opts) as CollectionComponentDefinition; @@ -109,7 +133,7 @@ function getCollectionItems( components.push(cmpDefinition); } - + return components; } diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 8abe42e49b..b3be5d8766 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -74,6 +74,7 @@ export class ComponentDynamicValueWatcher { const haveOverridenAttributes = Object.keys(attributesKeys).length; if (haveOverridenAttributes) combinedKeys.push('attributes'); + if (!combinedKeys.length && !this.component.getSymbolOverride()) return; this.component.setSymbolOverride(combinedKeys); } diff --git a/packages/core/src/dom_components/model/DynamicValueWatcher.ts b/packages/core/src/dom_components/model/DynamicValueWatcher.ts index 8d12560fdd..2a090fe3fb 100644 --- a/packages/core/src/dom_components/model/DynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/DynamicValueWatcher.ts @@ -3,10 +3,8 @@ import { CollectionsStateMap } from '../../data_sources/model/collection_compone import { ObjectAny } from '../../common'; import DynamicVariableListenerManager from '../../data_sources/model/DataVariableListenerManager'; import { evaluateDynamicValueDefinition, isDynamicValueDefinition } from '../../data_sources/model/utils'; -import { DynamicValue } from '../../data_sources/types'; import EditorModel from '../../editor/model/Editor'; import Component from './Component'; -import CollectionVariable from '../../data_sources/model/collection_component/CollectionVariable'; export interface DynamicWatchersOptions { skipWatcherUpdates?: boolean; @@ -39,25 +37,30 @@ export class DynamicValueWatcher { addDynamicValues(values: ObjectAny | undefined, options: DynamicWatchersOptions = {}) { if (!values) return {}; - const { evaluatedValues, dynamicValues } = this.evaluateValues(values); + const evaluatedValues = this.evaluateValues(values); const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource; if (!shouldSkipWatcherUpdates) { - this.updateListeners(dynamicValues); + this.updateListeners(values); } return evaluatedValues; } - private updateListeners(dynamicProps: { [key: string]: DynamicValue }) { + private updateListeners(values: { [key: string]: any }) { const em = this.options.em; - this.removeListeners(Object.keys(dynamicProps)); - const propsKeys = Object.keys(dynamicProps); + this.removeListeners(Object.keys(values)); + const propsKeys = Object.keys(values); for (let index = 0; index < propsKeys.length; index++) { const key = propsKeys[index]; + if (!isDynamicValueDefinition(values[key])) { + continue; + } + + const { variable } = evaluateDynamicValueDefinition(values[key], this.options); this.dynamicVariableListeners[key] = new DynamicVariableListenerManager({ em: em, - dataVariable: dynamicProps[key], + dataVariable: variable, updateValueFromDataVariable: (value: any) => { this.updateFn.bind(this)(this.component, key, value); }, @@ -66,28 +69,20 @@ export class DynamicValueWatcher { } private evaluateValues(values: ObjectAny) { - const dynamicValues: { - [key: string]: DynamicValue; - } = {}; const evaluatedValues: { [key: string]: any; } = { ...values }; - const valuesToBeOverriden: string[] = []; const propsKeys = Object.keys(values); for (let index = 0; index < propsKeys.length; index++) { const key = propsKeys[index]; if (!isDynamicValueDefinition(values[key])) { continue; } - const { value, variable } = evaluateDynamicValueDefinition(values[key], this.options); + const { value } = evaluateDynamicValueDefinition(values[key], this.options); evaluatedValues[key] = value; - dynamicValues[key] = variable; - if (variable instanceof CollectionVariable) { - valuesToBeOverriden.push(key); - } } - return { evaluatedValues, dynamicValues, valuesToBeOverriden }; + return evaluatedValues; } /** From e50fe16e80d7f0566ca12dc4f7ea2fbd0632d1c2 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 6 Jan 2025 12:18:31 +0200 Subject: [PATCH 31/87] Console errors instead of raising errors for collection component --- .../collection_component/CollectionComponent.ts | 6 ++++-- .../collection_component/CollectionVariable.ts | 14 ++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 7521bd2687..fe6d64d92e 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -83,11 +83,13 @@ function getCollectionItems( ) { const { collection_name, block, config } = collectionDefinition; if (!block) { - throw new Error('The "block" property is required in the collection definition.'); + em.logError('The "block" property is required in the collection definition.'); + return []; } if (!config?.dataSource) { - throw new Error('The "config.dataSource" property is required in the collection definition.'); + em.logError('The "config.dataSource" property is required in the collection definition.'); + return []; } const components: ComponentDefinition[] = []; diff --git a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts index c9ebd1e47f..de6eecedb4 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts @@ -20,9 +20,6 @@ export default class CollectionVariable extends Model Date: Mon, 6 Jan 2025 12:20:34 +0200 Subject: [PATCH 32/87] Refactor watch dynamic datasource --- .../CollectionComponent.ts | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index fe6d64d92e..27cac88226 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -35,21 +35,7 @@ export default class CollectionComponent extends Component { super(conditionalCmptDef, opt); if (this.hasDynamicDataSource()) { - const path = this.get(keyCollectionDefinition).config.dataSource?.path; - new DynamicVariableListenerManager({ - em: em, - dataVariable: new DataVariable( - { - type: 'data-variable', - path, - }, - { em }, - ), - updateValueFromDataVariable: () => { - const collectionItems = getCollectionItems(em, collectionDefinition, parentCollectionStateMap, opt); - this.components(collectionItems); - }, - }); + this.watchDataSource(em, collectionDefinition, parentCollectionStateMap, opt); } } @@ -65,14 +51,43 @@ export default class CollectionComponent extends Component { toJSON(opts?: ObjectAny) { const json = super.toJSON(opts) as CollectionComponentDefinition; - const firstChild = this.components().at(0)?.toJSON() || {}; - const keysToRemove = ['attributes?.id', keySymbol, keySymbols, keySymbolOvrd, keyCollectionsStateMap]; - keysToRemove.forEach((key) => delete firstChild[key]); + const firstChild = this.getBlockDefinition(); json[keyCollectionDefinition].block = firstChild; delete json.components; return json; } + + private getBlockDefinition() { + const firstChild = this.components().at(0)?.toJSON() || {}; + const keysToRemove = ['attributes?.id', keySymbol, keySymbols, keySymbolOvrd, keyCollectionsStateMap]; + keysToRemove.forEach((key) => delete firstChild[key]); + return firstChild; + } + + private watchDataSource( + em: EditorModel, + collectionDefinition: CollectionDefinition, + parentCollectionStateMap: CollectionsStateMap, + opt: ComponentOptions, + ) { + const path = this.get(keyCollectionDefinition).config.dataSource?.path; + const dataVariable = new DataVariable( + { + type: DataVariableType, + path, + }, + { em }, + ); + new DynamicVariableListenerManager({ + em: em, + dataVariable, + updateValueFromDataVariable: () => { + const collectionItems = getCollectionItems(em, collectionDefinition, parentCollectionStateMap, opt); + this.components(collectionItems); + }, + }); + } } function getCollectionItems( From e2d4bbe2ed2f401c288ec48648875d0d0537e26f Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 6 Jan 2025 14:15:02 +0200 Subject: [PATCH 33/87] Refactor CollectionStateVariableType --- .../model/collection_component/types.ts | 24 +++++++++++++------ .../dom_components/model/ComponentTypes.ts | 5 ++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/types.ts b/packages/core/src/data_sources/model/collection_component/types.ts index 3b78451ee2..e670974fb5 100644 --- a/packages/core/src/data_sources/model/collection_component/types.ts +++ b/packages/core/src/data_sources/model/collection_component/types.ts @@ -11,14 +11,24 @@ type CollectionConfig = { dataSource: CollectionDataSource; }; +export enum CollectionStateVariableType { + current_index = 'current_index', + start_index = 'start_index', + current_item = 'current_item', + end_index = 'end_index', + collection_name = 'collection_name', + total_items = 'total_items', + remaining_items = 'remaining_items', +} + export type CollectionState = { - current_index: number; - start_index: number; - current_item: any; - end_index: number; - collection_name?: string; - total_items: number; - remaining_items: number; + [CollectionStateVariableType.current_index]: number; + [CollectionStateVariableType.start_index]: number; + [CollectionStateVariableType.current_item]: any; + [CollectionStateVariableType.end_index]: number; + [CollectionStateVariableType.collection_name]?: string; + [CollectionStateVariableType.total_items]: number; + [CollectionStateVariableType.remaining_items]: number; }; export type CollectionsStateMap = { diff --git a/packages/core/test/specs/dom_components/model/ComponentTypes.ts b/packages/core/test/specs/dom_components/model/ComponentTypes.ts index 1d240e46ae..2cc912b97c 100644 --- a/packages/core/test/specs/dom_components/model/ComponentTypes.ts +++ b/packages/core/test/specs/dom_components/model/ComponentTypes.ts @@ -1,5 +1,5 @@ import { CollectionVariableType } from '../../../../src/data_sources/model/collection_component/constants'; -import { CollectionState } from '../../../../src/data_sources/model/collection_component/types'; +import { CollectionStateVariableType } from '../../../../src/data_sources/model/collection_component/types'; import Editor from '../../../../src/editor'; describe('Component Types', () => { @@ -98,9 +98,10 @@ describe('Component Types', () => { expect(cmp.components().at(0).is('svg-in')).toBe(true); }); }); + export type CollectionVariableDefinition = { type: typeof CollectionVariableType; - variable_type: keyof CollectionState; + variable_type: CollectionStateVariableType; collection_name?: string; path?: string; }; From 33f3129f390fb0763d39831e5ddf0b44d7e19bb4 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 6 Jan 2025 15:01:11 +0200 Subject: [PATCH 34/87] Fix zero end_index issue --- .../model/collection_component/CollectionComponent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 27cac88226..8a22a4096e 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -111,7 +111,7 @@ function getCollectionItems( let items: any[] = getDataSourceItems(config.dataSource, em); const start_index = Math.max(0, config.start_index || 0); - const end_index = Math.min(items.length - 1, config.end_index || Number.MAX_VALUE); + const end_index = Math.min(items.length - 1, config.end_index !== undefined ? config.end_index : Number.MAX_VALUE); const total_items = end_index - start_index + 1; let blockComponent: Component; From 9c094dd146a4d3c2679761eabfd6cd7bf517a27c Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 6 Jan 2025 15:13:48 +0200 Subject: [PATCH 35/87] Collection tests --- .../CollectionComponent.ts | 308 ++++++++++++++++++ .../__snapshots__/CollectionComponent.ts.snap | 41 +++ 2 files changed, 349 insertions(+) create mode 100644 packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts create mode 100644 packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts new file mode 100644 index 0000000000..e1be911f92 --- /dev/null +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -0,0 +1,308 @@ +import { Component, DataSource, DataSourceManager } from '../../../../../src'; +import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; +import { + CollectionComponentType, + CollectionVariableType, +} from '../../../../../src/data_sources/model/collection_component/constants'; +import { CollectionStateVariableType } from '../../../../../src/data_sources/model/collection_component/types'; +import EditorModel from '../../../../../src/editor/model/Editor'; +import { filterObjectForSnapshot, setupTestEditor } from '../../../../common'; + +describe('Collection component', () => { + let em: EditorModel; + let dsm: DataSourceManager; + let dataSource: DataSource; + let wrapper: Component; + + beforeEach(() => { + ({ em, dsm } = setupTestEditor()); + wrapper = em.getWrapper()!; + dataSource = dsm.add({ + id: 'my_data_source_id', + records: [ + { id: 'user1', user: 'user1', age: '12' }, + { id: 'user2', user: 'user2', age: '14' }, + { id: 'user3', user: 'user3', age: '16' }, + ], + }); + }); + + afterEach(() => { + em.destroy(); + }); + + describe('Collection symbols', () => { + test('Basic usage', () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + }, + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + expect(cmp.components()).toHaveLength(3); + const firstChild = cmp.components().at(0); + const secondChild = cmp.components().at(1); + + expect(firstChild.get('type')).toBe('default'); + expect(secondChild.get('type')).toBe('default'); + }); + }); + + describe('Collection variables', () => { + test('Properties', () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + custom_property: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + }, + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + const firstChild = cmp.components().at(0); + const secondChild = cmp.components().at(1); + + expect(firstChild.get('content')).toBe('user1'); + expect(firstChild.get('custom_property')).toBe('user1'); + + expect(secondChild.get('content')).toBe('user2'); + expect(secondChild.get('custom_property')).toBe('user2'); + }); + + test('Attributes', () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + attributes: { + custom_attribute: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + }, + }, + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + const firstChild = cmp.components().at(0); + const secondChild = cmp.components().at(1); + + expect(firstChild.getAttributes()['custom_attribute']).toBe('user1'); + + expect(secondChild.getAttributes()['custom_attribute']).toBe('user2'); + }); + + test('Traits', () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + traits: [ + { + name: 'attribute_trait', + value: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + }, + { + name: 'property_trait', + changeProp: true, + value: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + }, + ], + }, + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + expect(cmp.components()).toHaveLength(3); + const firstChild = cmp.components().at(0); + const secondChild = cmp.components().at(1); + + expect(firstChild.getAttributes()['attribute_trait']).toBe('user1'); + expect(firstChild.get('property_trait')).toBe('user1'); + + expect(secondChild.getAttributes()['attribute_trait']).toBe('user2'); + // TODO: Fix overrding traits + // expect(secondChild.get('property_trait')).toBe('user2'); + }); + }); + + describe('Stringfication', () => { + test('Collection with dynamic datasource', () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + collection_name: 'my_collection', + block: { + type: 'default', + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + attributes: { + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + }, + traits: [ + { + name: 'attribute_trait', + value: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + }, + { + name: 'property_trait', + changeProp: true, + value: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + }, + ], + }, + config: { + start_index: 0, + end_index: 1, + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + const json = cmp.toJSON(); + expect(filterObjectForSnapshot(json)).toMatchSnapshot(); + }); + }); + + describe('Configuration options', () => { + test('Collection with start and end indexes', () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + }, + config: { + start_index: 1, + end_index: 2, + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + expect(cmp.components()).toHaveLength(2); + const firstChild = cmp.components().at(0); + const secondChild = cmp.components().at(1); + + expect(firstChild.get('content')).toBe('user2'); + expect(secondChild.get('content')).toBe('user3'); + }); + }); + + describe('Diffirent Collection variable types', () => { + const stateVariableTests = [ + { variableType: CollectionStateVariableType.current_index, expectedValues: [0, 1, 2] }, + { variableType: CollectionStateVariableType.start_index, expectedValues: [0, 0, 0] }, + { variableType: CollectionStateVariableType.end_index, expectedValues: [2, 2, 2] }, + { + variableType: CollectionStateVariableType.collection_name, + expectedValues: ['my_collection', 'my_collection', 'my_collection'], + }, + { variableType: CollectionStateVariableType.total_items, expectedValues: [3, 3, 3] }, + { variableType: CollectionStateVariableType.remaining_items, expectedValues: [2, 1, 0] }, + ]; + + stateVariableTests.forEach(({ variableType, expectedValues }) => { + test(`Variable type: ${variableType}`, () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + collection_name: 'my_collection', + block: { + type: 'default', + content: { + type: CollectionVariableType, + variable_type: variableType, + }, + }, + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + const children = cmp.components(); + expect(children).toHaveLength(3); + + children.each((child, index) => { + expect(child.get('content')).toBe(expectedValues[index]); + }); + }); + }); + }); +}); diff --git a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap new file mode 100644 index 0000000000..28b63f057a --- /dev/null +++ b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collection component Stringfication Collection with dynamic datasource 1`] = ` +{ + "collectionDefinition": { + "block": { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "id": "data-variable-id", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "property_trait": "user1", + "type": "default", + }, + "collection_name": "my_collection", + "config": { + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "end_index": 1, + "start_index": 0, + }, + }, + "dropbbable": false, + "type": "collection-component", +} +`; From d02852e942b226f73b8256c0a2ae990bdc2d2e57 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 7 Jan 2025 11:12:48 +0200 Subject: [PATCH 36/87] Don't Add collection symbols to the list of global symbols --- .../collection_component/CollectionComponent.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 8a22a4096e..f5ce0d0f88 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -27,16 +27,17 @@ export default class CollectionComponent extends Component { const conditionalCmptDef = { ...props, type: CollectionComponentType, - components: components, dropbbable: false, }; // @ts-ignore - super(conditionalCmptDef, opt); - + const cmp: CollectionComponent = super(conditionalCmptDef, opt); if (this.hasDynamicDataSource()) { this.watchDataSource(em, collectionDefinition, parentCollectionStateMap, opt); } + cmp.components(components); + + return cmp as CollectionComponent; } static isComponent(el: HTMLElement) { @@ -114,7 +115,7 @@ function getCollectionItems( const end_index = Math.min(items.length - 1, config.end_index !== undefined ? config.end_index : Number.MAX_VALUE); const total_items = end_index - start_index + 1; - let blockComponent: Component; + let blockSymbolMain: Component; for (let index = start_index; index <= end_index; index++) { const item = items[index]; const collectionState: CollectionState = { @@ -137,15 +138,16 @@ function getCollectionItems( // @ts-ignore const type = em.Components.getType(block?.type || 'default'); const model = type.model; - blockComponent = new model( + + blockSymbolMain = new model( { ...block, [keyCollectionsStateMap]: collectionsStateMap, }, opt, - ); + ).clone({ symbol: true }); } - const instance = em.Components.addSymbol(blockComponent!); + const instance = blockSymbolMain!.clone({ symbol: true }); const cmpDefinition = resolveComponent(instance!, block, collectionsStateMap, em); components.push(cmpDefinition); From 782549b7b3c9a1abfc4119b71626d5109e6b28b8 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 7 Jan 2025 11:25:37 +0200 Subject: [PATCH 37/87] Refactor resolving collection items --- .../CollectionComponent.ts | 41 +++---------------- 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index f5ce0d0f88..9cd735af4f 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -17,12 +17,7 @@ export default class CollectionComponent extends Component { const collectionDefinition = props[keyCollectionDefinition]; const parentCollectionStateMap = (props[keyCollectionsStateMap] || {}) as CollectionsStateMap; - const components: ComponentDefinition[] = getCollectionItems( - em, - collectionDefinition, - parentCollectionStateMap, - opt, - ); + const components: Component[] = getCollectionItems(em, collectionDefinition, parentCollectionStateMap, opt); const conditionalCmptDef = { ...props, @@ -108,7 +103,7 @@ function getCollectionItems( return []; } - const components: ComponentDefinition[] = []; + const components: Component[] = []; let items: any[] = getDataSourceItems(config.dataSource, em); const start_index = Math.max(0, config.start_index || 0); @@ -146,11 +141,12 @@ function getCollectionItems( }, opt, ).clone({ symbol: true }); + blockSymbolMain!.setSymbolOverride([keyCollectionsStateMap]); } + blockSymbolMain!.set(keyCollectionsStateMap, collectionsStateMap); const instance = blockSymbolMain!.clone({ symbol: true }); - const cmpDefinition = resolveComponent(instance!, block, collectionsStateMap, em); - components.push(cmpDefinition); + components.push(instance); } return components; @@ -190,30 +186,3 @@ function listDataSourceVariables(dataSource_id: string, em: EditorModel) { path: dataSource_id + '.' + key, })); } - -function resolveComponent( - component: Component, - block: ComponentDefinition, - collectionsStateMap: CollectionsStateMap, - em: EditorModel, -) { - // @ts-ignore - component!.set(block); - - const children: ComponentDefinition[] = []; - for (let index = 0; index < component!.components().length; index++) { - const childSymbol = component!.components().at(index); - const childBlock = block['components']![index]; - const childJSON = resolveComponent(childSymbol, childBlock, collectionsStateMap, em); - children.push(childJSON); - } - - const componentJSON = component!.toJSON(); - const componentDefinition: ComponentDefinition = { - ...componentJSON, - components: children, - [keyCollectionsStateMap]: collectionsStateMap, - }; - - return componentDefinition; -} From fcc8c45e8cb922c351b384e34ce67dc660d3e5a2 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 7 Jan 2025 12:09:49 +0200 Subject: [PATCH 38/87] Fix collection items traits --- .../model/collection_component/CollectionComponent.ts | 2 +- packages/core/src/dom_components/model/Component.ts | 5 +++-- .../dom_components/model/ComponentDynamicValueWatcher.ts | 4 ---- .../model/collection_component/CollectionComponent.ts | 3 +-- .../__snapshots__/CollectionComponent.ts.snap | 6 +++++- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 9cd735af4f..f04e7dd873 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -140,7 +140,7 @@ function getCollectionItems( [keyCollectionsStateMap]: collectionsStateMap, }, opt, - ).clone({ symbol: true }); + ); blockSymbolMain!.setSymbolOverride([keyCollectionsStateMap]); } blockSymbolMain!.set(keyCollectionsStateMap, collectionsStateMap); diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index e0250e5138..60ff780a63 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -966,7 +966,7 @@ export default class Component extends StyleableModel { const value = trait.getInitValue(); if (trait.changeProp) { - this.set(name, value); + !this.get(name) && this.set(name, value); } else { if (name && value) attrs[name] = value; } @@ -1319,7 +1319,8 @@ export default class Component extends StyleableModel { clone(opt: { symbol?: boolean; symbolInv?: boolean } = {}): this { const em = this.em; const attr = { - ...this.componentDVListener.getPropsDefsOrValues(this.attributes), + ...this.attributes, + ...this.componentDVListener.getDynamicPropsDefs(), }; const opts = { ...this.opt }; const id = this.getId(); diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index b3be5d8766..59ed335e57 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -90,10 +90,6 @@ export class ComponentDynamicValueWatcher { return this.attributeWatcher.getSerializableValues(attributes); } - getPropsDefsOrValues(props: ObjectAny) { - return this.propertyWatcher.getSerializableValues(props); - } - destroy() { this.propertyWatcher.removeListeners(); this.attributeWatcher.removeListeners(); diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index e1be911f92..fe4c4946fb 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -167,8 +167,7 @@ describe('Collection component', () => { expect(firstChild.get('property_trait')).toBe('user1'); expect(secondChild.getAttributes()['attribute_trait']).toBe('user2'); - // TODO: Fix overrding traits - // expect(secondChild.get('property_trait')).toBe('user2'); + expect(secondChild.get('property_trait')).toBe('user2'); }); }); diff --git a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap index 28b63f057a..b90a3a6f69 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap +++ b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap @@ -22,7 +22,11 @@ exports[`Collection component Stringfication Collection with dynamic datasource "type": "parent-collection-variable", "variable_type": "current_item", }, - "property_trait": "user1", + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, "type": "default", }, "collection_name": "my_collection", From 4c6d1e6d67d359142933eaa988dd889e794d4c22 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 7 Jan 2025 13:07:28 +0200 Subject: [PATCH 39/87] Fix droppable for collection component --- .../model/collection_component/CollectionComponent.ts | 2 +- .../model/collection_component/CollectionComponent.ts | 8 ++++++++ .../__snapshots__/CollectionComponent.ts.snap | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index f04e7dd873..9c0cbbcbf8 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -22,7 +22,7 @@ export default class CollectionComponent extends Component { const conditionalCmptDef = { ...props, type: CollectionComponentType, - dropbbable: false, + droppable: false, }; // @ts-ignore diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index fe4c4946fb..983624cfd6 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -31,6 +31,14 @@ describe('Collection component', () => { em.destroy(); }); + test('Should be undroppable', () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + })[0]; + + expect(cmp.get('droppable')).toBe(false); + }); + describe('Collection symbols', () => { test('Basic usage', () => { const cmp = wrapper.components({ diff --git a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap index b90a3a6f69..26834581f1 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap +++ b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap @@ -39,7 +39,7 @@ exports[`Collection component Stringfication Collection with dynamic datasource "start_index": 0, }, }, - "dropbbable": false, + "droppable": false, "type": "collection-component", } `; From 6432a9c52f9efb6d952ebe13f2d072106e30b4a6 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 7 Jan 2025 13:57:30 +0200 Subject: [PATCH 40/87] Log error if no definition is passed to collection component --- .../CollectionComponent.ts | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 9c0cbbcbf8..c5c29ea1a3 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -14,25 +14,34 @@ import DynamicVariableListenerManager from '../DataVariableListenerManager'; export default class CollectionComponent extends Component { constructor(props: CollectionComponentDefinition, opt: ComponentOptions) { const em = opt.em; + // @ts-ignore + const cmp: CollectionComponent = super( + // @ts-ignore + { + ...props, + components: undefined, + droppable: false, + }, + opt, + ); + const collectionDefinition = props[keyCollectionDefinition]; + if (!collectionDefinition) { + em.logError('missing collection definition'); + + return cmp; + } + const parentCollectionStateMap = (props[keyCollectionsStateMap] || {}) as CollectionsStateMap; const components: Component[] = getCollectionItems(em, collectionDefinition, parentCollectionStateMap, opt); - const conditionalCmptDef = { - ...props, - type: CollectionComponentType, - droppable: false, - }; - - // @ts-ignore - const cmp: CollectionComponent = super(conditionalCmptDef, opt); if (this.hasDynamicDataSource()) { this.watchDataSource(em, collectionDefinition, parentCollectionStateMap, opt); } cmp.components(components); - return cmp as CollectionComponent; + return cmp; } static isComponent(el: HTMLElement) { From 7d967d289d2f8a8a97ac0713ce05853b227024e0 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 7 Jan 2025 14:09:31 +0200 Subject: [PATCH 41/87] Update tests for collection symbols --- .../CollectionComponent.ts | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index 983624cfd6..685fccffac 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -1,3 +1,4 @@ +import { isSymbol } from 'underscore'; import { Component, DataSource, DataSourceManager } from '../../../../../src'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { @@ -5,8 +6,10 @@ import { CollectionVariableType, } from '../../../../../src/data_sources/model/collection_component/constants'; import { CollectionStateVariableType } from '../../../../../src/data_sources/model/collection_component/types'; +import { keySymbol } from '../../../../../src/dom_components/model/Component'; import EditorModel from '../../../../../src/editor/model/Editor'; import { filterObjectForSnapshot, setupTestEditor } from '../../../../common'; +import { getSymbolMain, getSymbolTop } from '../../../../../src/dom_components/model/SymbolUtils'; describe('Collection component', () => { let em: EditorModel; @@ -31,37 +34,53 @@ describe('Collection component', () => { em.destroy(); }); - test('Should be undroppable', () => { + test('Should be undroppable', () => { const cmp = wrapper.components({ type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + }, + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, })[0]; expect(cmp.get('droppable')).toBe(false); }); - describe('Collection symbols', () => { - test('Basic usage', () => { - const cmp = wrapper.components({ - type: CollectionComponentType, - collectionDefinition: { - block: { - type: 'default', - }, - config: { - dataSource: { - type: DataVariableType, - path: 'my_data_source_id', + test('Collection items should be symbols', () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + components: [ + { + type: 'default', }, + ], + }, + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', }, }, - })[0]; + }, + })[0]; - expect(cmp.components()).toHaveLength(3); - const firstChild = cmp.components().at(0); - const secondChild = cmp.components().at(1); + expect(cmp.components()).toHaveLength(3); + cmp.components().forEach((child) => expect(child.get('type')).toBe('default')); + const children = cmp.components(); + const firstChild = children.at(0); - expect(firstChild.get('type')).toBe('default'); - expect(secondChild.get('type')).toBe('default'); + children.slice(1).forEach((component) => { + expect(getSymbolMain(component)).toBe(firstChild); }); }); From 5e472c935237c1cefd5434f88d5afc027ebac04f Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 7 Jan 2025 14:40:31 +0200 Subject: [PATCH 42/87] Fix collection variables not listening correctly --- .../model/DataVariableListenerManager.ts | 9 ++ .../CollectionComponent.ts | 2 +- .../CollectionVariable.ts | 4 + .../CollectionComponent.ts | 85 ++++++++++++------- 4 files changed, 70 insertions(+), 30 deletions(-) diff --git a/packages/core/src/data_sources/model/DataVariableListenerManager.ts b/packages/core/src/data_sources/model/DataVariableListenerManager.ts index ccd119aa90..a43ef0b631 100644 --- a/packages/core/src/data_sources/model/DataVariableListenerManager.ts +++ b/packages/core/src/data_sources/model/DataVariableListenerManager.ts @@ -6,6 +6,8 @@ import DataVariable, { DataVariableType } from './DataVariable'; import { DynamicValue } from '../types'; import { DataCondition, ConditionalVariableType } from './conditional_variables/DataCondition'; import ComponentDataVariable from './ComponentDataVariable'; +import { CollectionVariableType } from './collection_component/constants'; +import CollectionVariable from './collection_component/CollectionVariable'; export interface DynamicVariableListenerManagerOptions { em: EditorModel; @@ -41,6 +43,13 @@ export default class DynamicVariableListenerManager { const type = dynamicVariable.get('type'); let dataListeners: DataVariableListener[] = []; switch (type) { + case CollectionVariableType: + const collectionVariable = dynamicVariable as CollectionVariable; + if (collectionVariable.hasDynamicValue()) { + dataListeners = this.listenToDataVariable(collectionVariable.dataVariable!, em); + } + + break; case DataVariableType: dataListeners = this.listenToDataVariable(dynamicVariable as DataVariable | ComponentDataVariable, em); break; diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index c5c29ea1a3..ae925e4169 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -1,7 +1,7 @@ import DataVariable, { DataVariableType } from './../DataVariable'; import { isArray } from 'underscore'; import Component, { keySymbol, keySymbolOvrd, keySymbols } from '../../../dom_components/model/Component'; -import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; +import { ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; import { toLowerCase } from '../../../utils/mixins'; import DataSource from '../DataSource'; import { ObjectAny } from '../../../common'; diff --git a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts index de6eecedb4..113ecd30ff 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts @@ -24,6 +24,10 @@ export default class CollectionVariable extends Model { let dsm: DataSourceManager; let dataSource: DataSource; let wrapper: Component; + let firstRecord: DataRecord; + let secondRecord: DataRecord; beforeEach(() => { ({ em, dsm } = setupTestEditor()); @@ -28,6 +30,9 @@ describe('Collection component', () => { { id: 'user3', user: 'user3', age: '16' }, ], }); + + firstRecord = dataSource.getRecord('user1')!; + secondRecord = dataSource.getRecord('user2')!; }); afterEach(() => { @@ -85,39 +90,57 @@ describe('Collection component', () => { }); describe('Collection variables', () => { - test('Properties', () => { - const cmp = wrapper.components({ - type: CollectionComponentType, - collectionDefinition: { - block: { - type: 'default', - content: { - type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, - path: 'user', - }, - custom_property: { - type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, - path: 'user', + describe('Properties', () => { + let cmp: Component; + let firstChild!: Component; + let secondChild!: Component; + + beforeEach(() => { + cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + custom_property: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, }, - }, - config: { - dataSource: { - type: DataVariableType, - path: 'my_data_source_id', + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, }, }, - }, - })[0]; - const firstChild = cmp.components().at(0); - const secondChild = cmp.components().at(1); + })[0]; + + firstChild = cmp.components().at(0); + secondChild = cmp.components().at(1); + }); + + test('Evaluating to static value', () => { + expect(firstChild.get('content')).toBe('user1'); + expect(firstChild.get('custom_property')).toBe('user1'); + + expect(secondChild.get('content')).toBe('user2'); + expect(secondChild.get('custom_property')).toBe('user2'); + }); - expect(firstChild.get('content')).toBe('user1'); - expect(firstChild.get('custom_property')).toBe('user1'); + test('Updating the record', async () => { + firstRecord.set('user', 'new_user1_value'); + expect(firstChild.get('content')).toBe('new_user1_value'); + expect(firstChild.get('custom_property')).toBe('new_user1_value'); - expect(secondChild.get('content')).toBe('user2'); - expect(secondChild.get('custom_property')).toBe('user2'); + expect(secondChild.get('content')).toBe('user2'); + expect(secondChild.get('custom_property')).toBe('user2'); + }); }); test('Attributes', () => { @@ -332,3 +355,7 @@ describe('Collection component', () => { }); }); }); + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} From 3f0588de0e2affa98671beaa5816a86be1f2659e Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 7 Jan 2025 14:42:40 +0200 Subject: [PATCH 43/87] Refactor resolving collection variables --- .../CollectionVariable.ts | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts index 113ecd30ff..ccb15a183f 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts @@ -3,7 +3,7 @@ import { Model } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; import DataVariable, { DataVariableType } from '../DataVariable'; import { keyInnerCollectionState } from './constants'; -import { CollectionsStateMap } from './types'; +import { CollectionState, CollectionsStateMap } from './types'; export default class CollectionVariable extends Model { em: EditorModel; @@ -65,33 +65,49 @@ function resolveCollectionVariable( ) { const { collection_name = keyInnerCollectionState, variable_type, path } = collectionVariableDefinition; const collectionItem = collectionsStateMap[collection_name]; + if (!collectionItem) { em.logError(`Collection not found: ${collection_name}`); return ''; } + if (!variable_type) { em.logError(`Missing collection variable type for collection: ${collection_name}`); return ''; } if (variable_type === 'current_item') { - const valueIsDataVariable = collectionItem.current_item?.type === DataVariableType; - if (valueIsDataVariable) { - const currentItem_path = collectionItem.current_item.path; - const resolvedPath = currentItem_path ? `${currentItem_path}.${path}` : path; - return { - ...collectionItem.current_item, - path: resolvedPath, - }; - } else if (!!path) { - if (!collectionItem.current_item?.[path]) { - em.logError(`Path not found in current item: ${path} for collection: ${collection_name}`); - return ''; - } - - return collectionItem.current_item[path]; - } + return resolveCurrentItem(collectionItem, path, collection_name, em); } return collectionItem[variable_type]; } + +function resolveCurrentItem( + collectionItem: CollectionState, + path: string | undefined, + collection_name: string, + em: EditorModel, +) { + const currentItem = collectionItem.current_item; + + if (!currentItem) { + em.logError(`Current item is missing for collection: ${collection_name}`); + return ''; + } + + if (currentItem.type === DataVariableType) { + const resolvedPath = currentItem.path ? `${currentItem.path}.${path}` : path; + return { + ...currentItem, + path: resolvedPath, + }; + } + + if (path && !currentItem[path]) { + em.logError(`Path not found in current item: ${path} for collection: ${collection_name}`); + return ''; + } + + return path ? currentItem[path] : currentItem; +} From aaa24810873e23915f61cf921a33e37002112de9 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 7 Jan 2025 15:07:30 +0200 Subject: [PATCH 44/87] Fix updating collection symbols overrides in runtime --- packages/core/src/dom_components/model/Component.ts | 9 +++++++-- .../model/ComponentDynamicValueWatcher.ts | 13 +++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 60ff780a63..52854383ff 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -510,8 +510,13 @@ export default class Component extends StyleableModel { * @example * component.setSymbolOverride(['children', 'classes']); */ - setSymbolOverride(value?: boolean | string | string[]) { - this.set(keySymbolOvrd, (isString(value) ? [value] : value) ?? 0); + setSymbolOverride(value: boolean | string | string[], options: DynamicWatchersOptions = {}) { + this.set( + { + [keySymbolOvrd]: (isString(value) ? [value] : value) ?? 0, + }, + options, + ); } /** diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 59ed335e57..7a93fb3b66 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -42,18 +42,23 @@ export class ComponentDynamicValueWatcher { this.updateSymbolOverride(); } - addProps(props: ObjectAny, options?: DynamicWatchersOptions) { + addProps(props: ObjectAny, options: DynamicWatchersOptions = {}) { const evaluatedProps = this.propertyWatcher.addDynamicValues(props, options); + const shouldSkipOverridUpdates = options.skipWatcherUpdates || options.fromDataSource; + if (!shouldSkipOverridUpdates) { + this.updateSymbolOverride(); + } + return evaluatedProps; } - addAttributes(attributes: ObjectAny, options?: DynamicWatchersOptions) { + addAttributes(attributes: ObjectAny, options: DynamicWatchersOptions = {}) { const evaluatedAttributes = this.attributeWatcher.addDynamicValues(attributes, options); this.updateSymbolOverride(); return evaluatedAttributes; } - setAttributes(attributes: ObjectAny, options?: DynamicWatchersOptions) { + setAttributes(attributes: ObjectAny, options: DynamicWatchersOptions = {}) { const evaluatedAttributes = this.attributeWatcher.setDynamicValues(attributes, options); this.updateSymbolOverride(); return evaluatedAttributes; @@ -75,7 +80,7 @@ export class ComponentDynamicValueWatcher { if (haveOverridenAttributes) combinedKeys.push('attributes'); if (!combinedKeys.length && !this.component.getSymbolOverride()) return; - this.component.setSymbolOverride(combinedKeys); + this.component.setSymbolOverride(combinedKeys, { fromDataSource: true }); } getDynamicPropsDefs() { From 33da8bf9485f98e29687e4d9fdb77d3bb55706cc Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 8 Jan 2025 09:34:37 +0200 Subject: [PATCH 45/87] Refactor collectionsStateMap propagation --- .../CollectionComponent.ts | 1 + .../src/dom_components/model/Component.ts | 15 +++++------ .../model/ComponentDynamicValueWatcher.ts | 4 +-- .../src/dom_components/model/Components.ts | 26 ++++++++++++++++--- .../core/src/dom_components/model/types.ts | 3 ++- .../__snapshots__/CollectionComponent.ts.snap | 1 + 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index ae925e4169..5001d32708 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -147,6 +147,7 @@ function getCollectionItems( { ...block, [keyCollectionsStateMap]: collectionsStateMap, + isCollectionItem: true, }, opt, ); diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 52854383ff..bfc882e36f 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -72,6 +72,7 @@ export const keySymbolOvrd = '__symbol_ovrd'; export const keyUpdate = ComponentsEvents.update; export const keyUpdateInside = ComponentsEvents.updateInside; export const keyCollectionsStateMap = '__collections_state_map'; +export const keyIsCollectionItem = '__is_collection_item'; /** * The Component object represents a single node of our template structure, so when you update its properties the changes are @@ -262,13 +263,6 @@ export default class Component extends StyleableModel { componentDVListener: ComponentDynamicValueWatcher; constructor(props: ComponentProperties = {}, opt: ComponentOptions) { - if (props[keyCollectionsStateMap]) { - // @ts-ignore - props.components = props.components?.forEach((component) => ({ - ...component, - [keyCollectionsStateMap]: props[keyCollectionsStateMap], - })); - } const componentDVListener = new ComponentDynamicValueWatcher(undefined, { em: opt.em, collectionsStateMap: props[keyCollectionsStateMap], @@ -304,7 +298,12 @@ export default class Component extends StyleableModel { } opt.em = em; - this.opt = opt; + this.opt = { + ...opt, + // @ts-ignore + [keyCollectionsStateMap]: props[keyCollectionsStateMap], + isCollectionItem: !!props['isCollectionItem'], + }; this.em = em!; this.config = opt.config || {}; this.setAttributes({ diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 7a93fb3b66..b0331c500f 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -2,7 +2,7 @@ import { ObjectAny } from '../../common'; import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import EditorModel from '../../editor/model/Editor'; -import Component from './Component'; +import Component, { keyCollectionsStateMap } from './Component'; import { DynamicWatchersOptions } from './DynamicValueWatcher'; import { DynamicValueWatcher } from './DynamicValueWatcher'; @@ -70,7 +70,7 @@ export class ComponentDynamicValueWatcher { } updateSymbolOverride() { - if (!this.component) return; + if (!this.component || !this.component.get('isCollectionItem')) return; const keys = this.propertyWatcher.getDynamicValuesOfType(CollectionVariableType); const attributesKeys = this.attributeWatcher.getDynamicValuesOfType(CollectionVariableType); diff --git a/packages/core/src/dom_components/model/Components.ts b/packages/core/src/dom_components/model/Components.ts index 8613ddf268..df2e1f51e3 100644 --- a/packages/core/src/dom_components/model/Components.ts +++ b/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 from './Component'; +import Component, { keyCollectionsStateMap } from './Component'; import { AddOptions, Collection } from '../../common'; import { DomComponentsConfig } from '../config/config'; import EditorModel from '../../editor/model/Editor'; @@ -17,6 +17,7 @@ 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 []; @@ -87,6 +88,8 @@ export interface ComponentsOptions { em: EditorModel; config?: DomComponentsConfig; domc?: ComponentManager; + collectionsStateMap?: CollectionsStateMap; + isCollectionItem?: boolean; } interface AddComponentOptions extends AddOptions { @@ -324,13 +327,30 @@ Component> { */ processDef(mdl: Component | ComponentDefinition | ComponentDefinitionDefined) { // Avoid processing Models - if (mdl.cid && mdl.ccid) return mdl; + if (mdl.cid && mdl.ccid) { + const componentCollectionsStateMap = mdl.get(keyCollectionsStateMap); + const parentCollectionsStateMap = this.opt.collectionsStateMap; + mdl.set(keyCollectionsStateMap, { + ...componentCollectionsStateMap, + ...parentCollectionsStateMap, + }); + + mdl.set('isCollectionItem', this.opt.isCollectionItem); + + return mdl; + } const { em, config = {} } = this; const { processor } = config; let model = mdl; if (processor) { - model = { ...model }; // Avoid 'Cannot delete property ...' + model = { + [keyCollectionsStateMap]: { + ...this.opt.collectionsStateMap, + }, + isCollectionItem: this.opt.isCollectionItem, + ...model, + }; // Avoid 'Cannot delete property ...' const modelPr = processor(model); if (modelPr) { //@ts-ignore diff --git a/packages/core/src/dom_components/model/types.ts b/packages/core/src/dom_components/model/types.ts index 79b452b6bc..0bda043b66 100644 --- a/packages/core/src/dom_components/model/types.ts +++ b/packages/core/src/dom_components/model/types.ts @@ -11,7 +11,7 @@ import Component from './Component'; import Components from './Components'; import { ToolbarButtonProps } from './ToolbarButton'; import { ParseNodeOptions } from '../../parser/config/config'; -import { DynamicValueDefinition } from '../../data_sources/types'; +import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; export type DragMode = 'translate' | 'absolute' | ''; @@ -321,4 +321,5 @@ export interface ComponentOptions { frame?: Frame; temporary?: boolean; avoidChildren?: boolean; + collectionsStateMap?: CollectionsStateMap; } diff --git a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap index 26834581f1..df0160aa18 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap +++ b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap @@ -22,6 +22,7 @@ exports[`Collection component Stringfication Collection with dynamic datasource "type": "parent-collection-variable", "variable_type": "current_item", }, + "isCollectionItem": false, "property_trait": { "path": "user", "type": "parent-collection-variable", From 29d983ea7082b1625d904b7b1423c03ca0e05efa Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 8 Jan 2025 12:57:28 +0200 Subject: [PATCH 46/87] Fix collection items propagation --- .../model/ComponentDynamicValueWatcher.ts | 3 +- .../src/dom_components/model/Components.ts | 4 +- .../src/dom_components/model/SymbolUtils.ts | 18 +++++-- .../core/src/dom_components/model/types.ts | 3 +- .../CollectionComponent.ts | 50 ++++++++++++++++++- .../__snapshots__/CollectionComponent.ts.snap | 2 +- 6 files changed, 70 insertions(+), 10 deletions(-) diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index b0331c500f..4ad259d940 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -2,9 +2,10 @@ import { ObjectAny } from '../../common'; import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import EditorModel from '../../editor/model/Editor'; -import Component, { keyCollectionsStateMap } from './Component'; +import Component from './Component'; import { DynamicWatchersOptions } from './DynamicValueWatcher'; import { DynamicValueWatcher } from './DynamicValueWatcher'; +import { getSymbolsToUpdate } from './SymbolUtils'; export class ComponentDynamicValueWatcher { private propertyWatcher: DynamicValueWatcher; diff --git a/packages/core/src/dom_components/model/Components.ts b/packages/core/src/dom_components/model/Components.ts index df2e1f51e3..1c6635b8c1 100644 --- a/packages/core/src/dom_components/model/Components.ts +++ b/packages/core/src/dom_components/model/Components.ts @@ -335,7 +335,9 @@ Component> { ...parentCollectionsStateMap, }); - mdl.set('isCollectionItem', this.opt.isCollectionItem); + if (!mdl.get('isCollectionItem') && this.opt.isCollectionItem) { + mdl.set('isCollectionItem', this.opt.isCollectionItem); + } return mdl; } diff --git a/packages/core/src/dom_components/model/SymbolUtils.ts b/packages/core/src/dom_components/model/SymbolUtils.ts index 76ae4b1397..8ea1e05895 100644 --- a/packages/core/src/dom_components/model/SymbolUtils.ts +++ b/packages/core/src/dom_components/model/SymbolUtils.ts @@ -130,8 +130,14 @@ export const logSymbol = (symb: Component, type: string, toUp: Component[], opts }; export const updateSymbolProps = (symbol: Component, opts: SymbolToUpOptions = {}) => { - const changed = symbol.changedAttributes() || {}; - const attrs = changed.attributes || {}; + const changed = { + ...(symbol.changedAttributes() || {}), + ...symbol.componentDVListener.getDynamicPropsDefs(), + }; + const attrs = { + ...(changed.attributes || {}), + ...symbol.componentDVListener.getDynamicAttributesDefs(), + }; delete changed.status; delete changed.open; delete changed[keySymbols]; @@ -148,7 +154,9 @@ export const updateSymbolProps = (symbol: Component, opts: SymbolToUpOptions = { const toUp = getSymbolsToUpdate(symbol, opts); // Avoid propagating overrides to other symbols keys(changed).map((prop) => { - if (isSymbolOverride(symbol, prop)) delete changed[prop]; + const shouldPropagate = + !isSymbolOverride(symbol, prop) || (symbol.get('isCollectionItem') && !opts.fromDataSource); + if (!shouldPropagate) delete changed[prop]; }); logSymbol(symbol, 'props', toUp, { opts, changed }); @@ -156,7 +164,9 @@ export const updateSymbolProps = (symbol: Component, opts: SymbolToUpOptions = { const propsChanged = { ...changed }; // Avoid updating those with override keys(propsChanged).map((prop) => { - if (isSymbolOverride(child, prop)) delete propsChanged[prop]; + const shouldPropagate = + !isSymbolOverride(child, prop) || (child.get('isCollectionItem') && !opts.fromDataSource); + if (!shouldPropagate) delete propsChanged[prop]; }); child.set(propsChanged, { fromInstance: symbol, ...opts }); }); diff --git a/packages/core/src/dom_components/model/types.ts b/packages/core/src/dom_components/model/types.ts index 0bda043b66..d10f34f3c3 100644 --- a/packages/core/src/dom_components/model/types.ts +++ b/packages/core/src/dom_components/model/types.ts @@ -1,3 +1,4 @@ +import { DynamicWatchersOptions } from './DynamicValueWatcher'; import Frame from '../../canvas/model/Frame'; import { AddOptions, Nullable, OptionAsDocument } from '../../common'; import EditorModel from '../../editor/model/Editor'; @@ -253,7 +254,7 @@ export interface ComponentProperties { [key: string]: any; } -export interface SymbolToUpOptions { +export interface SymbolToUpOptions extends DynamicWatchersOptions { changed?: string; fromInstance?: boolean; noPropagate?: boolean; diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index e0a4396186..b726cb4d58 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -1,4 +1,3 @@ -import { isSymbol } from 'underscore'; import { Component, DataRecord, DataSource, DataSourceManager } from '../../../../../src'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { @@ -6,7 +5,6 @@ import { CollectionVariableType, } from '../../../../../src/data_sources/model/collection_component/constants'; import { CollectionStateVariableType } from '../../../../../src/data_sources/model/collection_component/types'; -import { keySymbol } from '../../../../../src/dom_components/model/Component'; import EditorModel from '../../../../../src/editor/model/Editor'; import { filterObjectForSnapshot, setupTestEditor } from '../../../../common'; import { getSymbolMain, getSymbolTop } from '../../../../../src/dom_components/model/SymbolUtils'; @@ -94,6 +92,7 @@ describe('Collection component', () => { let cmp: Component; let firstChild!: Component; let secondChild!: Component; + let thirdChild!: Component; beforeEach(() => { cmp = wrapper.components({ @@ -123,6 +122,7 @@ describe('Collection component', () => { firstChild = cmp.components().at(0); secondChild = cmp.components().at(1); + thirdChild = cmp.components().at(2); }); test('Evaluating to static value', () => { @@ -141,6 +141,52 @@ describe('Collection component', () => { expect(secondChild.get('content')).toBe('user2'); expect(secondChild.get('custom_property')).toBe('user2'); }); + + test('Updating the value to a static value', async () => { + firstChild.set('content', 'new_content_value'); + expect(firstChild.get('content')).toBe('new_content_value'); + expect(secondChild.get('content')).toBe('new_content_value'); + + firstRecord.set('user', 'wrong_value'); + expect(firstChild.get('content')).toBe('new_content_value'); + expect(secondChild.get('content')).toBe('new_content_value'); + }); + + test('Updating the value to a diffirent collection variable', async () => { + firstChild.set('content', { + // @ts-ignore + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'age', + }); + expect(firstChild.get('content')).toBe('12'); + expect(secondChild.get('content')).toBe('14'); + + firstRecord.set('age', 'new_value_12'); + secondRecord.set('age', 'new_value_14'); + + firstRecord.set('user', 'wrong_value'); + secondRecord.set('user', 'wrong_value'); + + expect(firstChild.get('content')).toBe('new_value_12'); + expect(secondChild.get('content')).toBe('new_value_14'); + }); + + test('Updating the value to a diffirent dynamic variable', async () => { + firstChild.set('content', { + // @ts-ignore + type: DataVariableType, + path: 'my_data_source_id.user2.user', + }); + expect(firstChild.get('content')).toBe('user2'); + expect(secondChild.get('content')).toBe('user2'); + expect(thirdChild.get('content')).toBe('user2'); + + secondRecord.set('user', 'new_value'); + expect(firstChild.get('content')).toBe('new_value'); + expect(secondChild.get('content')).toBe('new_value'); + expect(thirdChild.get('content')).toBe('new_value'); + }); }); test('Attributes', () => { diff --git a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap index df0160aa18..f0f2cc9932 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap +++ b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap @@ -22,7 +22,7 @@ exports[`Collection component Stringfication Collection with dynamic datasource "type": "parent-collection-variable", "variable_type": "current_item", }, - "isCollectionItem": false, + "isCollectionItem": true, "property_trait": { "path": "user", "type": "parent-collection-variable", From 5587e44812173b3eb3a6cbf57df395e4ae59bd2a Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 8 Jan 2025 15:38:41 +0200 Subject: [PATCH 47/87] Refactor setting dynamic attributes --- .../src/dom_components/model/Component.ts | 6 ++---- .../model/ComponentDynamicValueWatcher.ts | 21 +++++++------------ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index bfc882e36f..c20e697082 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -306,9 +306,8 @@ export default class Component extends StyleableModel { }; this.em = em!; this.config = opt.config || {}; - this.setAttributes({ + this.addAttributes({ ...(result(this, 'defaults').attributes || {}), - ...(this.get('attributes') || {}), }); this.ccid = Component.createId(this, opt); this.preInit(); @@ -696,8 +695,7 @@ export default class Component extends StyleableModel { * component.setAttributes({ id: 'test', 'data-key': 'value' }); */ setAttributes(attrs: ObjectAny, opts: SetAttrOptions = { skipWatcherUpdates: false, fromDataSource: false }) { - const evaluatedAttributes = this.componentDVListener.setAttributes(attrs, opts); - this.set('attributes', { ...evaluatedAttributes }, opts); + this.set('attributes', { ...attrs }, opts); return this; } diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 4ad259d940..3c465dec7a 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -45,26 +45,19 @@ export class ComponentDynamicValueWatcher { addProps(props: ObjectAny, options: DynamicWatchersOptions = {}) { const evaluatedProps = this.propertyWatcher.addDynamicValues(props, options); - const shouldSkipOverridUpdates = options.skipWatcherUpdates || options.fromDataSource; - if (!shouldSkipOverridUpdates) { + if (props.attributes) { + const evaluatedAttributes = this.attributeWatcher.setDynamicValues({ ...props.attributes }, options); + evaluatedProps['attributes'] = evaluatedAttributes; + } + + const skipOverridUpdates = options.skipWatcherUpdates || options.fromDataSource; + if (!skipOverridUpdates) { this.updateSymbolOverride(); } return evaluatedProps; } - addAttributes(attributes: ObjectAny, options: DynamicWatchersOptions = {}) { - const evaluatedAttributes = this.attributeWatcher.addDynamicValues(attributes, options); - this.updateSymbolOverride(); - return evaluatedAttributes; - } - - setAttributes(attributes: ObjectAny, options: DynamicWatchersOptions = {}) { - const evaluatedAttributes = this.attributeWatcher.setDynamicValues(attributes, options); - this.updateSymbolOverride(); - return evaluatedAttributes; - } - removeAttributes(attributes: string[]) { this.attributeWatcher.removeListeners(attributes); this.updateSymbolOverride(); From d2078d23fe1ce8a39efb76870f8182a258d977e1 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 8 Jan 2025 15:46:46 +0200 Subject: [PATCH 48/87] Edit properties propagation logic --- .../src/dom_components/model/SymbolUtils.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/core/src/dom_components/model/SymbolUtils.ts b/packages/core/src/dom_components/model/SymbolUtils.ts index 8ea1e05895..6f14c23365 100644 --- a/packages/core/src/dom_components/model/SymbolUtils.ts +++ b/packages/core/src/dom_components/model/SymbolUtils.ts @@ -3,6 +3,7 @@ import Component, { keySymbol, keySymbolOvrd, keySymbols } from './Component'; import { SymbolToUpOptions } from './types'; import { isEmptyObj } from '../../utils/mixins'; import Components from './Components'; +import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; export const isSymbolMain = (cmp: Component) => isArray(cmp.get(keySymbols)); @@ -140,6 +141,15 @@ export const updateSymbolProps = (symbol: Component, opts: SymbolToUpOptions = { }; delete changed.status; delete changed.open; + const toUp = getSymbolsToUpdate(symbol, opts); + if ( + symbol.get('isCollectionItem') && + !(Object.keys(changed).length === 1 && Object.keys(changed)[0] === keySymbolOvrd) + ) { + toUp.forEach((child) => { + child.setSymbolOverride(symbol.getSymbolOverride() || [], { fromDataSource: true }); + }); + } delete changed[keySymbols]; delete changed[keySymbol]; delete changed[keySymbolOvrd]; @@ -154,9 +164,8 @@ export const updateSymbolProps = (symbol: Component, opts: SymbolToUpOptions = { const toUp = getSymbolsToUpdate(symbol, opts); // Avoid propagating overrides to other symbols keys(changed).map((prop) => { - const shouldPropagate = - !isSymbolOverride(symbol, prop) || (symbol.get('isCollectionItem') && !opts.fromDataSource); - if (!shouldPropagate) delete changed[prop]; + const shouldPropagate = isSymbolOverride(symbol, prop) && !(changed[prop].type === CollectionVariableType); + if (shouldPropagate) delete changed[prop]; }); logSymbol(symbol, 'props', toUp, { opts, changed }); @@ -164,9 +173,8 @@ export const updateSymbolProps = (symbol: Component, opts: SymbolToUpOptions = { const propsChanged = { ...changed }; // Avoid updating those with override keys(propsChanged).map((prop) => { - const shouldPropagate = - !isSymbolOverride(child, prop) || (child.get('isCollectionItem') && !opts.fromDataSource); - if (!shouldPropagate) delete propsChanged[prop]; + const shouldPropagate = isSymbolOverride(child, prop) && !(propsChanged[prop].type === CollectionVariableType); + if (shouldPropagate) delete propsChanged[prop]; }); child.set(propsChanged, { fromInstance: symbol, ...opts }); }); From fc019079e0c889b05931e9aedf261eaac8b5514b Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 9 Jan 2025 23:49:04 +0200 Subject: [PATCH 49/87] Fix Collection props and attributes propagation --- .../model/collection_component/types.ts | 2 +- .../src/dom_components/model/Component.ts | 26 ++++-- .../model/ComponentDynamicValueWatcher.ts | 26 ++++-- .../src/dom_components/model/Components.ts | 18 ++-- .../model/DynamicValueWatcher.ts | 23 +++++ .../src/dom_components/model/SymbolUtils.ts | 83 ++++++++++--------- .../core/src/dom_components/model/types.ts | 2 + .../CollectionComponent.ts | 58 ++++++++++++- 8 files changed, 173 insertions(+), 65 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/types.ts b/packages/core/src/data_sources/model/collection_component/types.ts index e670974fb5..e7cc146769 100644 --- a/packages/core/src/data_sources/model/collection_component/types.ts +++ b/packages/core/src/data_sources/model/collection_component/types.ts @@ -1,6 +1,6 @@ import { CollectionComponentType, keyCollectionDefinition } from './constants'; -import { ComponentDefinition, ComponentProperties } from '../../../dom_components/model/types'; +import { ComponentDefinition } from '../../../dom_components/model/types'; import { CollectionVariableDefinition } from '../../../../test/specs/dom_components/model/ComponentTypes'; import { DataVariableDefinition } from '../DataVariable'; diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index c20e697082..2a10cc7184 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -300,8 +300,7 @@ export default class Component extends StyleableModel { opt.em = em; this.opt = { ...opt, - // @ts-ignore - [keyCollectionsStateMap]: props[keyCollectionsStateMap], + collectionsStateMap: props[keyCollectionsStateMap], isCollectionItem: !!props['isCollectionItem'], }; this.em = em!; @@ -320,6 +319,7 @@ export default class Component extends StyleableModel { 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); @@ -347,6 +347,16 @@ export default class Component extends StyleableModel { } } + getCollectionStateMap(): CollectionsStateMap { + const collectionStateMapProp = this.get(keyCollectionsStateMap); + if (collectionStateMapProp) { + return collectionStateMapProp; + } + + const parent = this.parent() || this.opt.parent; + return parent?.getCollectionStateMap() || {}; + } + set( keyOrAttributes: A | Partial, valueOrOptions?: ComponentProperties[A] | ComponentSetOptions, @@ -1595,6 +1605,7 @@ export default class Component extends StyleableModel { delete obj.open; // used in Layers delete obj._undoexc; delete obj.delegate; + delete obj[keyCollectionsStateMap]; if (!opts.fromUndo) { const symbol = obj[keySymbol]; @@ -1661,9 +1672,7 @@ export default class Component extends StyleableModel { * @return {this} */ setId(id: string, opts?: SetOptions & { idUpdate?: boolean }) { - const attrs = { ...this.get('attributes') }; - attrs.id = id; - this.set('attributes', attrs, opts); + this.addAttributes({ id }); return this; } @@ -1966,6 +1975,13 @@ export default class Component extends StyleableModel { selector && selector.set({ name: id, label: id }); } + _collectionsStateUpdated(m: any, v: CollectionsStateMap, opts = {}) { + this.componentDVListener.updateCollectionStateMap(v); + this.components().forEach((child) => { + child.set(keyCollectionsStateMap, v); + }); + } + static typeExtends = new Set(); static getDefaults() { diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 3c465dec7a..016f521bd1 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -2,7 +2,7 @@ import { ObjectAny } from '../../common'; import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import EditorModel from '../../editor/model/Editor'; -import Component from './Component'; +import Component, { keyCollectionsStateMap } from './Component'; import { DynamicWatchersOptions } from './DynamicValueWatcher'; import { DynamicValueWatcher } from './DynamicValueWatcher'; import { getSymbolsToUpdate } from './SymbolUtils'; @@ -43,10 +43,15 @@ export class ComponentDynamicValueWatcher { this.updateSymbolOverride(); } + updateCollectionStateMap(collectionsStateMap: CollectionsStateMap) { + this.propertyWatcher.updateCollectionStateMap(collectionsStateMap); + this.attributeWatcher.updateCollectionStateMap(collectionsStateMap); + } + addProps(props: ObjectAny, options: DynamicWatchersOptions = {}) { const evaluatedProps = this.propertyWatcher.addDynamicValues(props, options); if (props.attributes) { - const evaluatedAttributes = this.attributeWatcher.setDynamicValues({ ...props.attributes }, options); + const evaluatedAttributes = this.attributeWatcher.setDynamicValues(props.attributes, options); evaluatedProps['attributes'] = evaluatedAttributes; } @@ -63,17 +68,20 @@ export class ComponentDynamicValueWatcher { this.updateSymbolOverride(); } - updateSymbolOverride() { + private updateSymbolOverride() { if (!this.component || !this.component.get('isCollectionItem')) return; - + const keys = this.propertyWatcher.getDynamicValuesOfType(CollectionVariableType); const attributesKeys = this.attributeWatcher.getDynamicValuesOfType(CollectionVariableType); - - const combinedKeys = [...keys]; + + const combinedKeys = [keyCollectionsStateMap, ...keys]; const haveOverridenAttributes = Object.keys(attributesKeys).length; if (haveOverridenAttributes) combinedKeys.push('attributes'); - if (!combinedKeys.length && !this.component.getSymbolOverride()) return; + const toUp = getSymbolsToUpdate(this.component); + toUp.forEach((child) => { + child.setSymbolOverride(combinedKeys, { fromDataSource: true }); + }); this.component.setSymbolOverride(combinedKeys, { fromDataSource: true }); } @@ -85,6 +93,10 @@ export class ComponentDynamicValueWatcher { return this.attributeWatcher.getAllSerializableValues(); } + getPropsDefsOrValues(props: ObjectAny) { + return this.propertyWatcher.getSerializableValues(props); + } + getAttributesDefsOrValues(attributes: ObjectAny) { return this.attributeWatcher.getSerializableValues(attributes); } diff --git a/packages/core/src/dom_components/model/Components.ts b/packages/core/src/dom_components/model/Components.ts index 1c6635b8c1..964478ca64 100644 --- a/packages/core/src/dom_components/model/Components.ts +++ b/packages/core/src/dom_components/model/Components.ts @@ -346,13 +346,7 @@ Component> { let model = mdl; if (processor) { - model = { - [keyCollectionsStateMap]: { - ...this.opt.collectionsStateMap, - }, - isCollectionItem: this.opt.isCollectionItem, - ...model, - }; // Avoid 'Cannot delete property ...' + model = { ...model }; // Avoid 'Cannot delete property ...' const modelPr = processor(model); if (modelPr) { //@ts-ignore @@ -394,7 +388,15 @@ Component> { extend(model, res.props); } - return model; + return { + ...(this.opt.isCollectionItem && { + isCollectionItem: this.opt.isCollectionItem, + [keyCollectionsStateMap]: { + ...this.opt.collectionsStateMap, + }, + }), + ...model, + }; } onAdd(model: Component, c?: any, opts: { temporary?: boolean } = {}) { diff --git a/packages/core/src/dom_components/model/DynamicValueWatcher.ts b/packages/core/src/dom_components/model/DynamicValueWatcher.ts index 2a090fe3fb..27229b7c96 100644 --- a/packages/core/src/dom_components/model/DynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/DynamicValueWatcher.ts @@ -5,6 +5,7 @@ import DynamicVariableListenerManager from '../../data_sources/model/DataVariabl import { evaluateDynamicValueDefinition, isDynamicValueDefinition } from '../../data_sources/model/utils'; import EditorModel from '../../editor/model/Editor'; import Component from './Component'; +import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; export interface DynamicWatchersOptions { skipWatcherUpdates?: boolean; @@ -26,6 +27,28 @@ export class DynamicValueWatcher { this.component = component; } + updateCollectionStateMap(collectionsStateMap: CollectionsStateMap) { + this.options = { + ...this.options, + collectionsStateMap, + }; + + const collectionVariablesKeys = this.getDynamicValuesOfType(CollectionVariableType); + const collectionVariablesObject = collectionVariablesKeys.reduce( + (acc: { [key: string]: DynamicValueDefinition | null }, key) => { + acc[key] = null; + return acc; + }, + {}, + ); + const newVariables = this.getSerializableValues(collectionVariablesObject); + const evaluatedValues = this.addDynamicValues(newVariables); + + Object.keys(evaluatedValues).forEach((key) => { + this.updateFn(this.component, key, evaluatedValues[key]); + }); + } + setDynamicValues(values: ObjectAny | undefined, options: DynamicWatchersOptions = {}) { const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource; if (!shouldSkipWatcherUpdates) { diff --git a/packages/core/src/dom_components/model/SymbolUtils.ts b/packages/core/src/dom_components/model/SymbolUtils.ts index 6f14c23365..60708c56c7 100644 --- a/packages/core/src/dom_components/model/SymbolUtils.ts +++ b/packages/core/src/dom_components/model/SymbolUtils.ts @@ -130,55 +130,58 @@ export const logSymbol = (symb: Component, type: string, toUp: Component[], opts symb.em.log(type, { model: symb, toUp, context: 'symbols', opts }); }; -export const updateSymbolProps = (symbol: Component, opts: SymbolToUpOptions = {}) => { - const changed = { - ...(symbol.changedAttributes() || {}), - ...symbol.componentDVListener.getDynamicPropsDefs(), - }; - const attrs = { - ...(changed.attributes || {}), - ...symbol.componentDVListener.getDynamicAttributesDefs(), - }; - delete changed.status; - delete changed.open; - const toUp = getSymbolsToUpdate(symbol, opts); - if ( - symbol.get('isCollectionItem') && - !(Object.keys(changed).length === 1 && Object.keys(changed)[0] === keySymbolOvrd) - ) { - toUp.forEach((child) => { - child.setSymbolOverride(symbol.getSymbolOverride() || [], { fromDataSource: true }); +export const updateSymbolProps = (symbol: Component, opts: SymbolToUpOptions = {}): void => { + const changed = symbol.componentDVListener.getPropsDefsOrValues({ ...symbol.changedAttributes() } || {}); + const attrs = symbol.componentDVListener.getAttributesDefsOrValues({ ...changed.attributes } || {}); + + cleanChangedProperties(changed, attrs); + + if (!isEmptyObj(changed)) { + const toUpdate = getSymbolsToUpdate(symbol, opts); + + // Filter properties to propagate + filterPropertiesForPropagation(changed, symbol); + + logSymbol(symbol, 'props', toUpdate, { opts, changed }); + + // Update child symbols + toUpdate.forEach((child) => { + const propsToUpdate = { ...changed }; + filterPropertiesForPropagation(propsToUpdate, child); + child.set(propsToUpdate, { fromInstance: symbol, ...opts }); }); } - delete changed[keySymbols]; - delete changed[keySymbol]; - delete changed[keySymbolOvrd]; - delete changed.attributes; +}; + +const cleanChangedProperties = (changed: Record, attrs: Record): void => { + const keysToDelete = ['status', 'open', keySymbols, keySymbol, keySymbolOvrd, 'attributes']; + keysToDelete.forEach((key) => delete changed[key]); delete attrs.id; if (!isEmptyObj(attrs)) { changed.attributes = attrs; } +}; - if (!isEmptyObj(changed)) { - const toUp = getSymbolsToUpdate(symbol, opts); - // Avoid propagating overrides to other symbols - keys(changed).map((prop) => { - const shouldPropagate = isSymbolOverride(symbol, prop) && !(changed[prop].type === CollectionVariableType); - if (shouldPropagate) delete changed[prop]; - }); +const filterPropertiesForPropagation = (props: Record, component: Component): void => { + keys(props).forEach((prop) => { + if (!shouldPropagateProperty(props, prop, component)) { + delete props[prop]; + } + }); +}; - logSymbol(symbol, 'props', toUp, { opts, changed }); - toUp.forEach((child) => { - const propsChanged = { ...changed }; - // Avoid updating those with override - keys(propsChanged).map((prop) => { - const shouldPropagate = isSymbolOverride(child, prop) && !(propsChanged[prop].type === CollectionVariableType); - if (shouldPropagate) delete propsChanged[prop]; - }); - child.set(propsChanged, { fromInstance: symbol, ...opts }); - }); - } +const shouldPropagateProperty = (props: Record, prop: string, component: Component): boolean => { + const isCollectionVariableDefinition = (() => { + if (prop === 'attributes') { + const attributes = props['attributes']; + return Object.values(attributes).some((attr: any) => attr?.type === CollectionVariableType); + } + + return props[prop]?.type === CollectionVariableType; + })(); + + return !isSymbolOverride(component, prop) || isCollectionVariableDefinition; }; export const updateSymbolCls = (symbol: Component, opts: any = {}) => { diff --git a/packages/core/src/dom_components/model/types.ts b/packages/core/src/dom_components/model/types.ts index d10f34f3c3..25f5ddc158 100644 --- a/packages/core/src/dom_components/model/types.ts +++ b/packages/core/src/dom_components/model/types.ts @@ -323,4 +323,6 @@ export interface ComponentOptions { temporary?: boolean; avoidChildren?: boolean; collectionsStateMap?: CollectionsStateMap; + isCollectionItem?: boolean; + parent?: Component; } diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index b726cb4d58..1d5485cb97 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -91,7 +91,9 @@ describe('Collection component', () => { describe('Properties', () => { let cmp: Component; let firstChild!: Component; + let firstGrandchild!: Component; let secondChild!: Component; + let secondGrandchild!: Component; let thirdChild!: Component; beforeEach(() => { @@ -100,6 +102,16 @@ describe('Collection component', () => { collectionDefinition: { block: { type: 'default', + components: [ + { + type: 'default', + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + }, + ], content: { type: CollectionVariableType, variable_type: CollectionStateVariableType.current_item, @@ -121,25 +133,31 @@ describe('Collection component', () => { })[0]; firstChild = cmp.components().at(0); + firstGrandchild = firstChild.components().at(0); secondChild = cmp.components().at(1); + secondGrandchild = secondChild.components().at(0); thirdChild = cmp.components().at(2); }); test('Evaluating to static value', () => { expect(firstChild.get('content')).toBe('user1'); expect(firstChild.get('custom_property')).toBe('user1'); + expect(firstGrandchild.get('content')).toBe('user1'); expect(secondChild.get('content')).toBe('user2'); expect(secondChild.get('custom_property')).toBe('user2'); + expect(secondGrandchild.get('content')).toBe('user2'); }); test('Updating the record', async () => { firstRecord.set('user', 'new_user1_value'); expect(firstChild.get('content')).toBe('new_user1_value'); expect(firstChild.get('custom_property')).toBe('new_user1_value'); + expect(firstGrandchild.get('content')).toBe('new_user1_value'); expect(secondChild.get('content')).toBe('user2'); expect(secondChild.get('custom_property')).toBe('user2'); + expect(secondGrandchild.get('content')).toBe('user2'); }); test('Updating the value to a static value', async () => { @@ -150,6 +168,14 @@ describe('Collection component', () => { firstRecord.set('user', 'wrong_value'); expect(firstChild.get('content')).toBe('new_content_value'); expect(secondChild.get('content')).toBe('new_content_value'); + + firstGrandchild.set('content', 'new_content_value'); + expect(firstGrandchild.get('content')).toBe('new_content_value'); + expect(secondGrandchild.get('content')).toBe('new_content_value'); + + firstRecord.set('user', 'wrong_value'); + expect(firstGrandchild.get('content')).toBe('new_content_value'); + expect(secondGrandchild.get('content')).toBe('new_content_value'); }); test('Updating the value to a diffirent collection variable', async () => { @@ -170,6 +196,21 @@ describe('Collection component', () => { expect(firstChild.get('content')).toBe('new_value_12'); expect(secondChild.get('content')).toBe('new_value_14'); + + firstGrandchild.set('content', { + // @ts-ignore + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'age', + }); + expect(firstGrandchild.get('content')).toBe('new_value_12'); + expect(secondGrandchild.get('content')).toBe('new_value_14'); + + firstRecord.set('age', 'most_new_value_12'); + secondRecord.set('age', 'most_new_value_14'); + + expect(firstGrandchild.get('content')).toBe('most_new_value_12'); + expect(secondGrandchild.get('content')).toBe('most_new_value_14'); }); test('Updating the value to a diffirent dynamic variable', async () => { @@ -186,6 +227,19 @@ describe('Collection component', () => { expect(firstChild.get('content')).toBe('new_value'); expect(secondChild.get('content')).toBe('new_value'); expect(thirdChild.get('content')).toBe('new_value'); + + firstGrandchild.set('content', { + // @ts-ignore + type: DataVariableType, + path: 'my_data_source_id.user2.user', + }); + expect(firstGrandchild.get('content')).toBe('new_value'); + expect(secondGrandchild.get('content')).toBe('new_value'); + + secondRecord.set('user', 'most_new_value'); + + expect(firstGrandchild.get('content')).toBe('most_new_value'); + expect(secondGrandchild.get('content')).toBe('most_new_value'); }); }); @@ -401,7 +455,3 @@ describe('Collection component', () => { }); }); }); - -function delay(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} From 5bd74d577bf27ee4b7c13e3beeb4f90897b572bc Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 10 Jan 2025 00:10:35 +0200 Subject: [PATCH 50/87] Update collection attributes tests --- .../CollectionComponent.ts | 171 +++++++++++++++--- 1 file changed, 149 insertions(+), 22 deletions(-) diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index 1d5485cb97..4f831d122b 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -243,35 +243,162 @@ describe('Collection component', () => { }); }); - test('Attributes', () => { - const cmp = wrapper.components({ - type: CollectionComponentType, - collectionDefinition: { - block: { - type: 'default', - attributes: { - custom_attribute: { - type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, - path: 'user', + describe('Attributes', () => { + let cmp: Component; + let firstChild!: Component; + let firstGrandchild!: Component; + let secondChild!: Component; + let secondGrandchild!: Component; + let thirdChild!: Component; + + beforeEach(() => { + cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + components: [ + { + type: 'default', + attributes: { + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + } + }, + }, + ], + attributes: { + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + } }, }, - }, - config: { - dataSource: { - type: DataVariableType, - path: 'my_data_source_id', + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, }, }, - }, - })[0]; + })[0]; - const firstChild = cmp.components().at(0); - const secondChild = cmp.components().at(1); + firstChild = cmp.components().at(0); + firstGrandchild = firstChild.components().at(0); + secondChild = cmp.components().at(1); + secondGrandchild = secondChild.components().at(0); + thirdChild = cmp.components().at(2); + }); + + test('Evaluating to static value', () => { + expect(firstChild.getAttributes()['content']).toBe('user1'); + expect(firstGrandchild.getAttributes()['content']).toBe('user1'); + + expect(secondChild.getAttributes()['content']).toBe('user2'); + expect(secondGrandchild.getAttributes()['content']).toBe('user2'); + }); + + test('Updating the record', async () => { + firstRecord.set('user', 'new_user1_value'); + expect(firstChild.getAttributes()['content']).toBe('new_user1_value'); + expect(firstGrandchild.getAttributes()['content']).toBe('new_user1_value'); + + expect(secondChild.getAttributes()['content']).toBe('user2'); + expect(secondGrandchild.getAttributes()['content']).toBe('user2'); + }); + + test('Updating the value to a static value', async () => { + firstChild.setAttributes({ content: 'new_content_value' }); + expect(firstChild.getAttributes()['content']).toBe('new_content_value'); + expect(secondChild.getAttributes()['content']).toBe('new_content_value'); + + firstRecord.set('user', 'wrong_value'); + expect(firstChild.getAttributes()['content']).toBe('new_content_value'); + expect(secondChild.getAttributes()['content']).toBe('new_content_value'); + + firstGrandchild.setAttributes({ content: 'new_content_value' }); + expect(firstGrandchild.getAttributes()['content']).toBe('new_content_value'); + expect(secondGrandchild.getAttributes()['content']).toBe('new_content_value'); + + firstRecord.set('user', 'wrong_value'); + expect(firstGrandchild.getAttributes()['content']).toBe('new_content_value'); + expect(secondGrandchild.getAttributes()['content']).toBe('new_content_value'); + }); + + test('Updating the value to a diffirent collection variable', async () => { + firstChild.setAttributes({ + content: { + // @ts-ignore + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'age', + } + }); + expect(firstChild.getAttributes()['content']).toBe('12'); + expect(secondChild.getAttributes()['content']).toBe('14'); + + firstRecord.set('age', 'new_value_12'); + secondRecord.set('age', 'new_value_14'); + + firstRecord.set('user', 'wrong_value'); + secondRecord.set('user', 'wrong_value'); + + expect(firstChild.getAttributes()['content']).toBe('new_value_12'); + expect(secondChild.getAttributes()['content']).toBe('new_value_14'); + + firstGrandchild.setAttributes({ + content: { + // @ts-ignore + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'age', + } + }); + expect(firstGrandchild.getAttributes()['content']).toBe('new_value_12'); + expect(secondGrandchild.getAttributes()['content']).toBe('new_value_14'); + + firstRecord.set('age', 'most_new_value_12'); + secondRecord.set('age', 'most_new_value_14'); + + expect(firstGrandchild.getAttributes()['content']).toBe('most_new_value_12'); + expect(secondGrandchild.getAttributes()['content']).toBe('most_new_value_14'); + }); + + test('Updating the value to a diffirent dynamic variable', async () => { + firstChild.setAttributes({ + content: { + // @ts-ignore + type: DataVariableType, + path: 'my_data_source_id.user2.user', + } + }); + expect(firstChild.getAttributes()['content']).toBe('user2'); + expect(secondChild.getAttributes()['content']).toBe('user2'); + expect(thirdChild.getAttributes()['content']).toBe('user2'); + + secondRecord.set('user', 'new_value'); + expect(firstChild.getAttributes()['content']).toBe('new_value'); + expect(secondChild.getAttributes()['content']).toBe('new_value'); + expect(thirdChild.getAttributes()['content']).toBe('new_value'); + + firstGrandchild.setAttributes({ + content: { + // @ts-ignore + type: DataVariableType, + path: 'my_data_source_id.user2.user', + } + }); + expect(firstGrandchild.getAttributes()['content']).toBe('new_value'); + expect(secondGrandchild.getAttributes()['content']).toBe('new_value'); - expect(firstChild.getAttributes()['custom_attribute']).toBe('user1'); + secondRecord.set('user', 'most_new_value'); - expect(secondChild.getAttributes()['custom_attribute']).toBe('user2'); + expect(firstGrandchild.getAttributes()['content']).toBe('most_new_value'); + expect(secondGrandchild.getAttributes()['content']).toBe('most_new_value'); + }); }); test('Traits', () => { From 8c5800fe4371579c0571821028e54fcdf26f943c Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 10 Jan 2025 10:31:24 +0200 Subject: [PATCH 51/87] Update collection component serialization tests --- .../CollectionComponent.ts | 7 +- .../src/dom_components/model/Component.ts | 12 +- .../model/ComponentDynamicValueWatcher.ts | 4 +- .../CollectionComponent.ts | 118 ++++++----- .../__snapshots__/CollectionComponent.ts.snap | 191 +++++++++++++++++- 5 files changed, 277 insertions(+), 55 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index 5001d32708..a51bed9164 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -1,7 +1,7 @@ import DataVariable, { DataVariableType } from './../DataVariable'; import { isArray } from 'underscore'; import Component, { keySymbol, keySymbolOvrd, keySymbols } from '../../../dom_components/model/Component'; -import { ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; +import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; import { toLowerCase } from '../../../utils/mixins'; import DataSource from '../DataSource'; import { ObjectAny } from '../../../common'; @@ -10,6 +10,7 @@ import { keyCollectionsStateMap } from '../../../dom_components/model/Component' import { CollectionComponentDefinition, CollectionDefinition, CollectionState, CollectionsStateMap } from './types'; import { keyCollectionDefinition, keyInnerCollectionState, CollectionComponentType } from './constants'; import DynamicVariableListenerManager from '../DataVariableListenerManager'; +import Components from '../../../dom_components/model/Components'; export default class CollectionComponent extends Component { constructor(props: CollectionComponentDefinition, opt: ComponentOptions) { @@ -60,13 +61,13 @@ export default class CollectionComponent extends Component { json[keyCollectionDefinition].block = firstChild; delete json.components; + delete json.droppable; return json; } private getBlockDefinition() { const firstChild = this.components().at(0)?.toJSON() || {}; - const keysToRemove = ['attributes?.id', keySymbol, keySymbols, keySymbolOvrd, keyCollectionsStateMap]; - keysToRemove.forEach((key) => delete firstChild[key]); + console.log('🚀 ~ CollectionComponent ~ getBlockDefinition ~ firstChild:', firstChild); return firstChild; } diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 2a10cc7184..8a2f02da43 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -1605,7 +1605,17 @@ export default class Component extends StyleableModel { delete obj.open; // used in Layers delete obj._undoexc; delete obj.delegate; - delete obj[keyCollectionsStateMap]; + if (this.get('isCollectionItem')) { + delete obj[keySymbol]; + delete obj[keySymbolOvrd]; + delete obj[keySymbols]; + delete obj[keyCollectionsStateMap]; + delete obj['isCollectionItem']; + delete obj.attributes.id; + obj['components'] = this.components() + .toArray() + .map((cmp) => cmp.toJSON()); + } if (!opts.fromUndo) { const symbol = obj[keySymbol]; diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 016f521bd1..c9d04821e8 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -70,10 +70,10 @@ export class ComponentDynamicValueWatcher { private updateSymbolOverride() { if (!this.component || !this.component.get('isCollectionItem')) return; - + const keys = this.propertyWatcher.getDynamicValuesOfType(CollectionVariableType); const attributesKeys = this.attributeWatcher.getDynamicValuesOfType(CollectionVariableType); - + const combinedKeys = [keyCollectionsStateMap, ...keys]; const haveOverridenAttributes = Object.keys(attributesKeys).length; if (haveOverridenAttributes) combinedKeys.push('attributes'); diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index 4f831d122b..c0a69adbf0 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -149,7 +149,7 @@ describe('Collection component', () => { expect(secondGrandchild.get('content')).toBe('user2'); }); - test('Updating the record', async () => { + test('Watching Records', async () => { firstRecord.set('user', 'new_user1_value'); expect(firstChild.get('content')).toBe('new_user1_value'); expect(firstChild.get('custom_property')).toBe('new_user1_value'); @@ -265,7 +265,7 @@ describe('Collection component', () => { type: CollectionVariableType, variable_type: CollectionStateVariableType.current_item, path: 'user', - } + }, }, }, ], @@ -274,7 +274,7 @@ describe('Collection component', () => { type: CollectionVariableType, variable_type: CollectionStateVariableType.current_item, path: 'user', - } + }, }, }, config: { @@ -301,7 +301,7 @@ describe('Collection component', () => { expect(secondGrandchild.getAttributes()['content']).toBe('user2'); }); - test('Updating the record', async () => { + test('Watching Records', async () => { firstRecord.set('user', 'new_user1_value'); expect(firstChild.getAttributes()['content']).toBe('new_user1_value'); expect(firstGrandchild.getAttributes()['content']).toBe('new_user1_value'); @@ -335,7 +335,7 @@ describe('Collection component', () => { type: CollectionVariableType, variable_type: CollectionStateVariableType.current_item, path: 'age', - } + }, }); expect(firstChild.getAttributes()['content']).toBe('12'); expect(secondChild.getAttributes()['content']).toBe('14'); @@ -355,7 +355,7 @@ describe('Collection component', () => { type: CollectionVariableType, variable_type: CollectionStateVariableType.current_item, path: 'age', - } + }, }); expect(firstGrandchild.getAttributes()['content']).toBe('new_value_12'); expect(secondGrandchild.getAttributes()['content']).toBe('new_value_14'); @@ -373,7 +373,7 @@ describe('Collection component', () => { // @ts-ignore type: DataVariableType, path: 'my_data_source_id.user2.user', - } + }, }); expect(firstChild.getAttributes()['content']).toBe('user2'); expect(secondChild.getAttributes()['content']).toBe('user2'); @@ -389,7 +389,7 @@ describe('Collection component', () => { // @ts-ignore type: DataVariableType, path: 'my_data_source_id.user2.user', - } + }, }); expect(firstGrandchild.getAttributes()['content']).toBe('new_value'); expect(secondGrandchild.getAttributes()['content']).toBe('new_value'); @@ -445,48 +445,65 @@ describe('Collection component', () => { expect(secondChild.getAttributes()['attribute_trait']).toBe('user2'); expect(secondChild.get('property_trait')).toBe('user2'); + + firstRecord.set('user', 'new_user1_value'); + expect(firstChild.getAttributes()['attribute_trait']).toBe('new_user1_value'); + expect(firstChild.get('property_trait')).toBe('new_user1_value'); + + expect(secondChild.getAttributes()['attribute_trait']).toBe('user2'); + expect(secondChild.get('property_trait')).toBe('user2'); }); }); - describe('Stringfication', () => { - test('Collection with dynamic datasource', () => { - const cmp = wrapper.components({ - type: CollectionComponentType, - collectionDefinition: { - collection_name: 'my_collection', - block: { - type: 'default', - content: { + describe('Serialization', () => { + test('Serializion with Collection Variables to JSON', () => { + const cmpDefinition = { + type: 'default', + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + custom_prop: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_index, + path: 'user', + }, + attributes: { + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', + }, + }, + traits: [ + { + name: 'attribute_trait', + value: { type: CollectionVariableType, variable_type: CollectionStateVariableType.current_item, path: 'user', }, - attributes: { - content: { - type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, - path: 'user', - }, + }, + { + name: 'property_trait', + changeProp: true, + value: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + path: 'user', }, - traits: [ - { - name: 'attribute_trait', - value: { - type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, - path: 'user', - }, - }, - { - name: 'property_trait', - changeProp: true, - value: { - type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, - path: 'user', - }, - }, - ], + }, + ], + }; + + const collectionComponentDefinition = { + type: CollectionComponentType, + collectionDefinition: { + collection_name: 'my_collection', + block: { + ...cmpDefinition, + components: [cmpDefinition, cmpDefinition], }, config: { start_index: 0, @@ -497,10 +514,21 @@ describe('Collection component', () => { }, }, }, - })[0]; + }; + const cmp = wrapper.components(collectionComponentDefinition)[0]; + expect(filterObjectForSnapshot(cmp.toJSON())).toMatchSnapshot(); - const json = cmp.toJSON(); - expect(filterObjectForSnapshot(json)).toMatchSnapshot(); + const firstChild = cmp.components().at(0); + const newChildDefinition = { + type: 'default', + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_index, + path: 'user', + }, + }; + firstChild.components().at(0).components(newChildDefinition); + expect(filterObjectForSnapshot(cmp.toJSON())).toMatchSnapshot(); }); }); diff --git a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap index f0f2cc9932..5cf72c7e31 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap +++ b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collection component Stringfication Collection with dynamic datasource 1`] = ` +exports[`Collection component Serialization Serializion with Collection Variables to JSON 1`] = ` { "collectionDefinition": { "block": { @@ -15,14 +15,198 @@ exports[`Collection component Stringfication Collection with dynamic datasource "type": "parent-collection-variable", "variable_type": "current_item", }, - "id": "data-variable-id", }, + "components": [ + { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + ], "content": { "path": "user", "type": "parent-collection-variable", "variable_type": "current_item", }, - "isCollectionItem": true, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + "collection_name": "my_collection", + "config": { + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "end_index": 1, + "start_index": 0, + }, + }, + "type": "collection-component", +} +`; + +exports[`Collection component Serialization Serializion with Collection Variables to JSON 2`] = ` +{ + "collectionDefinition": { + "block": { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "components": [ + { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "components": [ + { + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "type": "default", + }, + ], + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + ], + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, "property_trait": { "path": "user", "type": "parent-collection-variable", @@ -40,7 +224,6 @@ exports[`Collection component Stringfication Collection with dynamic datasource "start_index": 0, }, }, - "droppable": false, "type": "collection-component", } `; From b7d27936f907b91ee8b5627fcd5dd6afafb5651a Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 10 Jan 2025 13:25:18 +0200 Subject: [PATCH 52/87] Fix falsy value being treated as undefined --- .../model/collection_component/CollectionComponent.ts | 1 - packages/core/src/dom_components/model/Component.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index a51bed9164..e3257dfc05 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -67,7 +67,6 @@ export default class CollectionComponent extends Component { private getBlockDefinition() { const firstChild = this.components().at(0)?.toJSON() || {}; - console.log('🚀 ~ CollectionComponent ~ getBlockDefinition ~ firstChild:', firstChild); return firstChild; } diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 8a2f02da43..a627f88136 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -978,7 +978,7 @@ export default class Component extends StyleableModel { const value = trait.getInitValue(); if (trait.changeProp) { - !this.get(name) && this.set(name, value); + isUndefined(this.get(name)) && this.set(name, value); } else { if (name && value) attrs[name] = value; } From 697c11ac8d770ae9311f0c9aa10996dd60001dfb Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 10 Jan 2025 13:25:38 +0200 Subject: [PATCH 53/87] Udpate tests for Diffirent Collection variable types --- .../CollectionComponent.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index c0a69adbf0..cba58f1d95 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -590,6 +590,29 @@ describe('Collection component', () => { type: CollectionVariableType, variable_type: variableType, }, + attributes: { + custom_attribute: { + type: CollectionVariableType, + variable_type: variableType, + }, + }, + traits: [ + { + name: 'attribute_trait', + value: { + type: CollectionVariableType, + variable_type: variableType, + }, + }, + { + name: 'property_trait', + changeProp: true, + value: { + type: CollectionVariableType, + variable_type: variableType, + }, + }, + ], }, config: { dataSource: { @@ -605,6 +628,9 @@ describe('Collection component', () => { children.each((child, index) => { expect(child.get('content')).toBe(expectedValues[index]); + expect(child.get('property_trait')).toBe(expectedValues[index]); + expect(child.getAttributes()['custom_attribute']).toBe(expectedValues[index]); + expect(child.getAttributes()['attribute_trait']).toBe(expectedValues[index]); }); }); }); From 407cc89a915d45a7231b79386e8b2409c547a8c6 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 10 Jan 2025 13:52:55 +0200 Subject: [PATCH 54/87] Add tests for saving and loading collection components --- .../CollectionComponent.ts | 216 +++++++++++++++- .../__snapshots__/CollectionComponent.ts.snap | 232 +++++++++++++++++- 2 files changed, 440 insertions(+), 8 deletions(-) diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index cba58f1d95..bc6e76cf18 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -1,4 +1,4 @@ -import { Component, DataRecord, DataSource, DataSourceManager } from '../../../../../src'; +import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { CollectionComponentType, @@ -8,9 +8,11 @@ import { CollectionStateVariableType } from '../../../../../src/data_sources/mod import EditorModel from '../../../../../src/editor/model/Editor'; import { filterObjectForSnapshot, setupTestEditor } from '../../../../common'; import { getSymbolMain, getSymbolTop } from '../../../../../src/dom_components/model/SymbolUtils'; +import { ProjectData } from '../../../../../src/storage_manager'; describe('Collection component', () => { let em: EditorModel; + let editor: Editor; let dsm: DataSourceManager; let dataSource: DataSource; let wrapper: Component; @@ -18,7 +20,7 @@ describe('Collection component', () => { let secondRecord: DataRecord; beforeEach(() => { - ({ em, dsm } = setupTestEditor()); + ({ em, editor, dsm } = setupTestEditor()); wrapper = em.getWrapper()!; dataSource = dsm.add({ id: 'my_data_source_id', @@ -456,7 +458,9 @@ describe('Collection component', () => { }); describe('Serialization', () => { - test('Serializion with Collection Variables to JSON', () => { + let cmp: Component; + + beforeEach(() => { const cmpDefinition = { type: 'default', content: { @@ -515,8 +519,33 @@ describe('Collection component', () => { }, }, }; - const cmp = wrapper.components(collectionComponentDefinition)[0]; - expect(filterObjectForSnapshot(cmp.toJSON())).toMatchSnapshot(); + + cmp = wrapper.components(collectionComponentDefinition)[0]; + }); + + test('Serializion with Collection Variables to JSON', () => { + expect(filterObjectForSnapshot(cmp.toJSON())).toMatchSnapshot(`Collection with no grandchildren`); + + const firstChild = cmp.components().at(0); + const newChildDefinition = { + type: 'default', + content: { + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_index, + path: 'user', + }, + }; + firstChild.components().at(0).components(newChildDefinition); + expect(filterObjectForSnapshot(cmp.toJSON())).toMatchSnapshot(`Collection with grandchildren`); + }); + + test('Saving', () => { + const projectData = editor.getProjectData(); + const page = projectData.pages[0]; + const frame = page.frames[0]; + const component = frame.component.components[0]; + + expect(filterObjectForSnapshot(component)).toMatchSnapshot(`Collection with no grandchildren`); const firstChild = cmp.components().at(0); const newChildDefinition = { @@ -528,7 +557,182 @@ describe('Collection component', () => { }, }; firstChild.components().at(0).components(newChildDefinition); - expect(filterObjectForSnapshot(cmp.toJSON())).toMatchSnapshot(); + expect(filterObjectForSnapshot(cmp.toJSON())).toMatchSnapshot(`Collection with grandchildren`); + }); + + test('Loading', () => { + const componentProjectData: ProjectData = { + assets: [], + pages: [ + { + frames: [ + { + component: { + components: [ + { + collectionDefinition: { + block: { + attributes: { + attribute_trait: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + content: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + }, + components: [ + { + attributes: { + attribute_trait: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + content: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + }, + content: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + custom_prop: { + path: 'user', + type: CollectionVariableType, + variable_type: 'current_index', + }, + property_trait: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + type: 'default', + }, + { + attributes: { + attribute_trait: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + content: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + }, + content: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + custom_prop: { + path: 'user', + type: CollectionVariableType, + variable_type: 'current_index', + }, + property_trait: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + type: 'default', + }, + ], + content: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + custom_prop: { + path: 'user', + type: CollectionVariableType, + variable_type: 'current_index', + }, + property_trait: { + path: 'user', + type: CollectionVariableType, + variable_type: CollectionStateVariableType.current_item, + }, + type: 'default', + }, + collection_name: 'my_collection', + config: { + dataSource: { + path: 'my_data_source_id', + type: DataVariableType, + }, + end_index: 1, + start_index: 0, + }, + }, + type: 'collection-component', + }, + ], + docEl: { + tagName: 'html', + }, + head: { + type: 'head', + }, + stylable: [ + 'background', + 'background-color', + 'background-image', + 'background-repeat', + 'background-attachment', + 'background-position', + 'background-size', + ], + type: 'wrapper', + }, + id: 'frameid', + }, + ], + id: 'pageid', + type: 'main', + }, + ], + styles: [], + symbols: [], + dataSources: [dataSource], + }; + editor.loadProjectData(componentProjectData); + + const components = editor.getComponents(); + const component = components.models[0]; + const firstChild = component.components().at(0); + const firstGrandchild = firstChild.components().at(0); + const secondChild = component.components().at(1); + const secondGrandchild = secondChild.components().at(0); + + expect(firstChild.get('content')).toBe('user1'); + expect(firstChild.getAttributes()['content']).toBe('user1'); + expect(firstGrandchild.get('content')).toBe('user1'); + expect(firstGrandchild.getAttributes()['content']).toBe('user1'); + + expect(secondChild.get('content')).toBe('user2'); + expect(secondChild.getAttributes()['content']).toBe('user2'); + expect(secondGrandchild.get('content')).toBe('user2'); + expect(secondGrandchild.getAttributes()['content']).toBe('user2'); + + firstRecord.set('user', 'new_user1_value'); + expect(firstChild.get('content')).toBe('new_user1_value'); + expect(firstChild.getAttributes()['content']).toBe('new_user1_value'); + expect(firstGrandchild.get('content')).toBe('new_user1_value'); + expect(firstGrandchild.getAttributes()['content']).toBe('new_user1_value'); + + expect(secondChild.get('content')).toBe('user2'); + expect(secondChild.getAttributes()['content']).toBe('user2'); + expect(secondGrandchild.get('content')).toBe('user2'); + expect(secondGrandchild.getAttributes()['content']).toBe('user2'); }); }); diff --git a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap index 5cf72c7e31..46f76cdf52 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap +++ b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap @@ -1,6 +1,125 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Collection component Serialization Serializion with Collection Variables to JSON 1`] = ` +exports[`Collection component Serialization Saving: Collection with grandchildren 1`] = ` +{ + "collectionDefinition": { + "block": { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "components": [ + { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "components": [ + { + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "type": "default", + }, + ], + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + ], + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + "collection_name": "my_collection", + "config": { + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "end_index": 1, + "start_index": 0, + }, + }, + "type": "collection-component", +} +`; + +exports[`Collection component Serialization Saving: Collection with no grandchildren 1`] = ` { "collectionDefinition": { "block": { @@ -109,7 +228,7 @@ exports[`Collection component Serialization Serializion with Collection Variable } `; -exports[`Collection component Serialization Serializion with Collection Variables to JSON 2`] = ` +exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with grandchildren 1`] = ` { "collectionDefinition": { "block": { @@ -227,3 +346,112 @@ exports[`Collection component Serialization Serializion with Collection Variable "type": "collection-component", } `; + +exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with no grandchildren 1`] = ` +{ + "collectionDefinition": { + "block": { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "components": [ + { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + { + "attributes": { + "attribute_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + }, + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + ], + "content": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "custom_prop": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_index", + }, + "property_trait": { + "path": "user", + "type": "parent-collection-variable", + "variable_type": "current_item", + }, + "type": "default", + }, + "collection_name": "my_collection", + "config": { + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "end_index": 1, + "start_index": 0, + }, + }, + "type": "collection-component", +} +`; From de7a712ea6736031cf65ed9524c39b727d716bc5 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 10 Jan 2025 16:24:31 +0200 Subject: [PATCH 55/87] Make collection items undraggable --- .../CollectionComponent.ts | 3 +++ .../src/utils/sorter/CanvasComponentNode.ts | 11 ++++++++ .../CollectionComponent.ts | 25 +++++++++++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index e3257dfc05..cb62fea0d6 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -67,6 +67,8 @@ export default class CollectionComponent extends Component { private getBlockDefinition() { const firstChild = this.components().at(0)?.toJSON() || {}; + delete firstChild.draggable; + return firstChild; } @@ -148,6 +150,7 @@ function getCollectionItems( ...block, [keyCollectionsStateMap]: collectionsStateMap, isCollectionItem: true, + draggable: false, }, opt, ); diff --git a/packages/core/src/utils/sorter/CanvasComponentNode.ts b/packages/core/src/utils/sorter/CanvasComponentNode.ts index 2495e75f24..9563386e86 100644 --- a/packages/core/src/utils/sorter/CanvasComponentNode.ts +++ b/packages/core/src/utils/sorter/CanvasComponentNode.ts @@ -6,6 +6,17 @@ export default class CanvasComponentNode extends BaseComponentNode { minUndroppableDimension: 1, // In px maxUndroppableDimension: 15, // In px }; + /** + * Check if a source node can be moved to a specified index within this component. + * @param {BaseComponentNode} source - The source node to move. + * @param {number} index - The display index to move the source to. + * @returns {boolean} - True if the move is allowed, false otherwise. + */ + canMove(source: BaseComponentNode, index: number): boolean { + console.log('🚀 ~ CanvasComponentNode ~ canMove ~ this.model:', this.model); + console.log('🚀 ~ CanvasComponentNode ~ canMove ~ source.model:', source.model); + return this.model.em.Components.canMove(this.model, source.model, this.getRealIndex(index)).result; + } /** * Get the associated view of this component. * @returns The view associated with the component, or undefined if none. diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index bc6e76cf18..81f657ca7b 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -7,7 +7,7 @@ import { import { CollectionStateVariableType } from '../../../../../src/data_sources/model/collection_component/types'; import EditorModel from '../../../../../src/editor/model/Editor'; import { filterObjectForSnapshot, setupTestEditor } from '../../../../common'; -import { getSymbolMain, getSymbolTop } from '../../../../../src/dom_components/model/SymbolUtils'; +import { getSymbolMain } from '../../../../../src/dom_components/model/SymbolUtils'; import { ProjectData } from '../../../../../src/storage_manager'; describe('Collection component', () => { @@ -39,7 +39,7 @@ describe('Collection component', () => { em.destroy(); }); - test('Should be undroppable', () => { + test('Collection component should be undroppable', () => { const cmp = wrapper.components({ type: CollectionComponentType, collectionDefinition: { @@ -58,6 +58,27 @@ describe('Collection component', () => { expect(cmp.get('droppable')).toBe(false); }); + test('Collection items should be undraggable', () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + }, + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + cmp.components().forEach((child) => { + expect(child.get('draggable')).toBe(false); + }); + }); + test('Collection items should be symbols', () => { const cmp = wrapper.components({ type: CollectionComponentType, From 0167fce5437b36d2db88716f0ae80c37f008fd7a Mon Sep 17 00:00:00 2001 From: mohamed yahia Date: Tue, 14 Jan 2025 12:44:31 +0200 Subject: [PATCH 56/87] Change collection component definition options to camel case --- README.md | 2 +- .../CollectionComponent.ts | 33 ++-- .../CollectionVariable.ts | 24 +-- .../model/collection_component/types.ts | 34 ++-- .../CollectionComponent.ts | 106 ++++++------- .../__snapshots__/CollectionComponent.ts.snap | 148 +++++++++--------- .../dom_components/model/ComponentTypes.ts | 4 +- 7 files changed, 175 insertions(+), 176 deletions(-) diff --git a/README.md b/README.md index 96a9dd409e..e24cf16a1d 120000 --- a/README.md +++ b/README.md @@ -1 +1 @@ -./packages/core/README.md \ No newline at end of file +./packages/core/README.md diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index cb62fea0d6..b8ab18557b 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -1,7 +1,7 @@ import DataVariable, { DataVariableType } from './../DataVariable'; import { isArray } from 'underscore'; -import Component, { keySymbol, keySymbolOvrd, keySymbols } from '../../../dom_components/model/Component'; -import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; +import Component from '../../../dom_components/model/Component'; +import { ComponentOptions } from '../../../dom_components/model/types'; import { toLowerCase } from '../../../utils/mixins'; import DataSource from '../DataSource'; import { ObjectAny } from '../../../common'; @@ -10,7 +10,6 @@ import { keyCollectionsStateMap } from '../../../dom_components/model/Component' import { CollectionComponentDefinition, CollectionDefinition, CollectionState, CollectionsStateMap } from './types'; import { keyCollectionDefinition, keyInnerCollectionState, CollectionComponentType } from './constants'; import DynamicVariableListenerManager from '../DataVariableListenerManager'; -import Components from '../../../dom_components/model/Components'; export default class CollectionComponent extends Component { constructor(props: CollectionComponentDefinition, opt: ComponentOptions) { @@ -103,7 +102,7 @@ function getCollectionItems( parentCollectionStateMap: CollectionsStateMap, opt: ComponentOptions, ) { - const { collection_name, block, config } = collectionDefinition; + const { collectionName, block, config } = collectionDefinition; if (!block) { em.logError('The "block" property is required in the collection definition.'); return []; @@ -117,30 +116,30 @@ function getCollectionItems( const components: Component[] = []; let items: any[] = getDataSourceItems(config.dataSource, em); - const start_index = Math.max(0, config.start_index || 0); - const end_index = Math.min(items.length - 1, config.end_index !== undefined ? config.end_index : Number.MAX_VALUE); + const startIndex = Math.max(0, config.startIndex || 0); + const endIndex = Math.min(items.length - 1, config.endIndex !== undefined ? config.endIndex : Number.MAX_VALUE); - const total_items = end_index - start_index + 1; + const totalItems = endIndex - startIndex + 1; let blockSymbolMain: Component; - for (let index = start_index; index <= end_index; index++) { + for (let index = startIndex; index <= endIndex; index++) { const item = items[index]; const collectionState: CollectionState = { - collection_name, - current_index: index, - current_item: item, - start_index: start_index, - end_index: end_index, - total_items: total_items, - remaining_items: total_items - (index + 1), + collectionName, + currentIndex: index, + currentItem: item, + startIndex: startIndex, + endIndex: endIndex, + totalItems: totalItems, + remainingItems: totalItems - (index + 1), }; const collectionsStateMap: CollectionsStateMap = { ...parentCollectionStateMap, - ...(collection_name && { [collection_name]: collectionState }), + ...(collectionName && { [collectionName]: collectionState }), [keyInnerCollectionState]: collectionState, }; - if (index === start_index) { + if (index === startIndex) { // @ts-ignore const type = em.Components.getType(block?.type || 'default'); const model = type.model; diff --git a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts index ccb15a183f..97e9b60401 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts @@ -63,36 +63,36 @@ function resolveCollectionVariable( collectionsStateMap: CollectionsStateMap, em: EditorModel, ) { - const { collection_name = keyInnerCollectionState, variable_type, path } = collectionVariableDefinition; - const collectionItem = collectionsStateMap[collection_name]; + const { collectionName = keyInnerCollectionState, variableType, path } = collectionVariableDefinition; + const collectionItem = collectionsStateMap[collectionName]; if (!collectionItem) { - em.logError(`Collection not found: ${collection_name}`); + em.logError(`Collection not found: ${collectionName}`); return ''; } - if (!variable_type) { - em.logError(`Missing collection variable type for collection: ${collection_name}`); + if (!variableType) { + em.logError(`Missing collection variable type for collection: ${collectionName}`); return ''; } - if (variable_type === 'current_item') { - return resolveCurrentItem(collectionItem, path, collection_name, em); + if (variableType === 'currentItem') { + return resolveCurrentItem(collectionItem, path, collectionName, em); } - return collectionItem[variable_type]; + return collectionItem[variableType]; } function resolveCurrentItem( collectionItem: CollectionState, path: string | undefined, - collection_name: string, + collectionName: string, em: EditorModel, ) { - const currentItem = collectionItem.current_item; + const currentItem = collectionItem.currentItem; if (!currentItem) { - em.logError(`Current item is missing for collection: ${collection_name}`); + em.logError(`Current item is missing for collection: ${collectionName}`); return ''; } @@ -105,7 +105,7 @@ function resolveCurrentItem( } if (path && !currentItem[path]) { - em.logError(`Path not found in current item: ${path} for collection: ${collection_name}`); + em.logError(`Path not found in current item: ${path} for collection: ${collectionName}`); return ''; } diff --git a/packages/core/src/data_sources/model/collection_component/types.ts b/packages/core/src/data_sources/model/collection_component/types.ts index e7cc146769..17237f1e77 100644 --- a/packages/core/src/data_sources/model/collection_component/types.ts +++ b/packages/core/src/data_sources/model/collection_component/types.ts @@ -6,29 +6,29 @@ import { DataVariableDefinition } from '../DataVariable'; type CollectionDataSource = any[] | DataVariableDefinition | CollectionVariableDefinition; type CollectionConfig = { - start_index?: number; - end_index?: number; + startIndex?: number; + endIndex?: number; dataSource: CollectionDataSource; }; export enum CollectionStateVariableType { - current_index = 'current_index', - start_index = 'start_index', - current_item = 'current_item', - end_index = 'end_index', - collection_name = 'collection_name', - total_items = 'total_items', - remaining_items = 'remaining_items', + currentIndex = 'currentIndex', + startIndex = 'startIndex', + currentItem = 'currentItem', + endIndex = 'endIndex', + collectionName = 'collectionName', + totalItems = 'totalItems', + remainingItems = 'remainingItems', } export type CollectionState = { - [CollectionStateVariableType.current_index]: number; - [CollectionStateVariableType.start_index]: number; - [CollectionStateVariableType.current_item]: any; - [CollectionStateVariableType.end_index]: number; - [CollectionStateVariableType.collection_name]?: string; - [CollectionStateVariableType.total_items]: number; - [CollectionStateVariableType.remaining_items]: number; + [CollectionStateVariableType.currentIndex]: number; + [CollectionStateVariableType.startIndex]: number; + [CollectionStateVariableType.currentItem]: any; + [CollectionStateVariableType.endIndex]: number; + [CollectionStateVariableType.collectionName]?: string; + [CollectionStateVariableType.totalItems]: number; + [CollectionStateVariableType.remainingItems]: number; }; export type CollectionsStateMap = { @@ -41,7 +41,7 @@ export type CollectionComponentDefinition = { export type CollectionDefinition = { type: typeof CollectionComponentType; - collection_name?: string; + collectionName?: string; config: CollectionConfig; block: ComponentDefinition; }; diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index 81f657ca7b..38e4682668 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -130,19 +130,19 @@ describe('Collection component', () => { type: 'default', content: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, ], content: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, custom_property: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, @@ -205,7 +205,7 @@ describe('Collection component', () => { firstChild.set('content', { // @ts-ignore type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'age', }); expect(firstChild.get('content')).toBe('12'); @@ -223,7 +223,7 @@ describe('Collection component', () => { firstGrandchild.set('content', { // @ts-ignore type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'age', }); expect(firstGrandchild.get('content')).toBe('new_value_12'); @@ -286,7 +286,7 @@ describe('Collection component', () => { attributes: { content: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, @@ -295,7 +295,7 @@ describe('Collection component', () => { attributes: { content: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, @@ -356,7 +356,7 @@ describe('Collection component', () => { content: { // @ts-ignore type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'age', }, }); @@ -376,7 +376,7 @@ describe('Collection component', () => { content: { // @ts-ignore type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'age', }, }); @@ -435,7 +435,7 @@ describe('Collection component', () => { name: 'attribute_trait', value: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, @@ -444,7 +444,7 @@ describe('Collection component', () => { changeProp: true, value: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, @@ -486,18 +486,18 @@ describe('Collection component', () => { type: 'default', content: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, custom_prop: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_index, + variableType: CollectionStateVariableType.currentIndex, path: 'user', }, attributes: { content: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, @@ -506,7 +506,7 @@ describe('Collection component', () => { name: 'attribute_trait', value: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, @@ -515,7 +515,7 @@ describe('Collection component', () => { changeProp: true, value: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, @@ -525,14 +525,14 @@ describe('Collection component', () => { const collectionComponentDefinition = { type: CollectionComponentType, collectionDefinition: { - collection_name: 'my_collection', + collectionName: 'my_collection', block: { ...cmpDefinition, components: [cmpDefinition, cmpDefinition], }, config: { - start_index: 0, - end_index: 1, + startIndex: 0, + endIndex: 1, dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -552,7 +552,7 @@ describe('Collection component', () => { type: 'default', content: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_index, + variableType: CollectionStateVariableType.currentIndex, path: 'user', }, }; @@ -573,7 +573,7 @@ describe('Collection component', () => { type: 'default', content: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_index, + variableType: CollectionStateVariableType.currentIndex, path: 'user', }, }; @@ -597,12 +597,12 @@ describe('Collection component', () => { attribute_trait: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, content: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, }, components: [ @@ -611,28 +611,28 @@ describe('Collection component', () => { attribute_trait: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, content: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, }, content: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, custom_prop: { path: 'user', type: CollectionVariableType, - variable_type: 'current_index', + variableType: 'currentIndex', }, property_trait: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, type: 'default', }, @@ -641,28 +641,28 @@ describe('Collection component', () => { attribute_trait: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, content: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, }, content: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, custom_prop: { path: 'user', type: CollectionVariableType, - variable_type: 'current_index', + variableType: 'currentIndex', }, property_trait: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, type: 'default', }, @@ -670,28 +670,28 @@ describe('Collection component', () => { content: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, custom_prop: { path: 'user', type: CollectionVariableType, - variable_type: 'current_index', + variableType: 'currentIndex', }, property_trait: { path: 'user', type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, }, type: 'default', }, - collection_name: 'my_collection', + collectionName: 'my_collection', config: { dataSource: { path: 'my_data_source_id', type: DataVariableType, }, - end_index: 1, - start_index: 0, + endIndex: 1, + startIndex: 0, }, }, type: 'collection-component', @@ -766,13 +766,13 @@ describe('Collection component', () => { type: 'default', content: { type: CollectionVariableType, - variable_type: CollectionStateVariableType.current_item, + variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, config: { - start_index: 1, - end_index: 2, + startIndex: 1, + endIndex: 2, dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -792,15 +792,15 @@ describe('Collection component', () => { describe('Diffirent Collection variable types', () => { const stateVariableTests = [ - { variableType: CollectionStateVariableType.current_index, expectedValues: [0, 1, 2] }, - { variableType: CollectionStateVariableType.start_index, expectedValues: [0, 0, 0] }, - { variableType: CollectionStateVariableType.end_index, expectedValues: [2, 2, 2] }, + { variableType: CollectionStateVariableType.currentIndex, expectedValues: [0, 1, 2] }, + { variableType: CollectionStateVariableType.startIndex, expectedValues: [0, 0, 0] }, + { variableType: CollectionStateVariableType.endIndex, expectedValues: [2, 2, 2] }, { - variableType: CollectionStateVariableType.collection_name, + variableType: CollectionStateVariableType.collectionName, expectedValues: ['my_collection', 'my_collection', 'my_collection'], }, - { variableType: CollectionStateVariableType.total_items, expectedValues: [3, 3, 3] }, - { variableType: CollectionStateVariableType.remaining_items, expectedValues: [2, 1, 0] }, + { variableType: CollectionStateVariableType.totalItems, expectedValues: [3, 3, 3] }, + { variableType: CollectionStateVariableType.remainingItems, expectedValues: [2, 1, 0] }, ]; stateVariableTests.forEach(({ variableType, expectedValues }) => { @@ -808,17 +808,17 @@ describe('Collection component', () => { const cmp = wrapper.components({ type: CollectionComponentType, collectionDefinition: { - collection_name: 'my_collection', + collectionName: 'my_collection', block: { type: 'default', content: { type: CollectionVariableType, - variable_type: variableType, + variableType: variableType, }, attributes: { custom_attribute: { type: CollectionVariableType, - variable_type: variableType, + variableType: variableType, }, }, traits: [ @@ -826,7 +826,7 @@ describe('Collection component', () => { name: 'attribute_trait', value: { type: CollectionVariableType, - variable_type: variableType, + variableType: variableType, }, }, { @@ -834,7 +834,7 @@ describe('Collection component', () => { changeProp: true, value: { type: CollectionVariableType, - variable_type: variableType, + variableType: variableType, }, }, ], diff --git a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap index 46f76cdf52..d2d5a8a53d 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap +++ b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap @@ -8,12 +8,12 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "components": [ @@ -22,12 +22,12 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "components": [ @@ -35,7 +35,7 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "type": "default", }, @@ -43,17 +43,17 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, @@ -62,28 +62,28 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, @@ -91,28 +91,28 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, - "collection_name": "my_collection", + "collectionName": "my_collection", "config": { "dataSource": { "path": "my_data_source_id", "type": "data-variable", }, - "end_index": 1, - "start_index": 0, + "endIndex": 1, + "startIndex": 0, }, }, "type": "collection-component", @@ -127,12 +127,12 @@ exports[`Collection component Serialization Saving: Collection with no grandchil "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "components": [ @@ -141,28 +141,28 @@ exports[`Collection component Serialization Saving: Collection with no grandchil "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, @@ -171,28 +171,28 @@ exports[`Collection component Serialization Saving: Collection with no grandchil "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, @@ -200,28 +200,28 @@ exports[`Collection component Serialization Saving: Collection with no grandchil "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, - "collection_name": "my_collection", + "collectionName": "my_collection", "config": { "dataSource": { "path": "my_data_source_id", "type": "data-variable", }, - "end_index": 1, - "start_index": 0, + "endIndex": 1, + "startIndex": 0, }, }, "type": "collection-component", @@ -236,12 +236,12 @@ exports[`Collection component Serialization Serializion with Collection Variable "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "components": [ @@ -250,12 +250,12 @@ exports[`Collection component Serialization Serializion with Collection Variable "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "components": [ @@ -263,7 +263,7 @@ exports[`Collection component Serialization Serializion with Collection Variable "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "type": "default", }, @@ -271,17 +271,17 @@ exports[`Collection component Serialization Serializion with Collection Variable "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, @@ -290,28 +290,28 @@ exports[`Collection component Serialization Serializion with Collection Variable "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, @@ -319,28 +319,28 @@ exports[`Collection component Serialization Serializion with Collection Variable "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, - "collection_name": "my_collection", + "collectionName": "my_collection", "config": { "dataSource": { "path": "my_data_source_id", "type": "data-variable", }, - "end_index": 1, - "start_index": 0, + "endIndex": 1, + "startIndex": 0, }, }, "type": "collection-component", @@ -355,12 +355,12 @@ exports[`Collection component Serialization Serializion with Collection Variable "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "components": [ @@ -369,28 +369,28 @@ exports[`Collection component Serialization Serializion with Collection Variable "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, @@ -399,28 +399,28 @@ exports[`Collection component Serialization Serializion with Collection Variable "attribute_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, }, "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, @@ -428,28 +428,28 @@ exports[`Collection component Serialization Serializion with Collection Variable "content": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_index", + "variableType": "currentIndex", }, "property_trait": { "path": "user", "type": "parent-collection-variable", - "variable_type": "current_item", + "variableType": "currentItem", }, "type": "default", }, - "collection_name": "my_collection", + "collectionName": "my_collection", "config": { "dataSource": { "path": "my_data_source_id", "type": "data-variable", }, - "end_index": 1, - "start_index": 0, + "endIndex": 1, + "startIndex": 0, }, }, "type": "collection-component", diff --git a/packages/core/test/specs/dom_components/model/ComponentTypes.ts b/packages/core/test/specs/dom_components/model/ComponentTypes.ts index 2cc912b97c..f074d4b3b8 100644 --- a/packages/core/test/specs/dom_components/model/ComponentTypes.ts +++ b/packages/core/test/specs/dom_components/model/ComponentTypes.ts @@ -101,7 +101,7 @@ describe('Component Types', () => { export type CollectionVariableDefinition = { type: typeof CollectionVariableType; - variable_type: CollectionStateVariableType; - collection_name?: string; + variableType: CollectionStateVariableType; + collectionName?: string; path?: string; }; From a9fec3d2f778a4c8be21e852e3478c9cfb275c44 Mon Sep 17 00:00:00 2001 From: mohamed yahia Date: Tue, 14 Jan 2025 18:02:08 +0200 Subject: [PATCH 57/87] Refactor propagation of collection map state --- .../CollectionComponent.ts | 8 +++ .../CollectionVariable.ts | 2 + .../src/dom_components/model/Component.ts | 60 ++++++++++++++----- .../model/ComponentDynamicValueWatcher.ts | 2 +- .../src/dom_components/model/Components.ts | 32 ++-------- .../model/DynamicValueWatcher.ts | 29 +++++---- .../core/src/dom_components/model/types.ts | 33 ++++++++++ 7 files changed, 112 insertions(+), 54 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index b8ab18557b..f12374076e 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/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) { diff --git a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts b/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts index 97e9b60401..9e241fc394 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts +++ b/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) { diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 4920f8ffb1..d7b64fbc95 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/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 {} -export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {} -export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions {} +export interface IComponent extends ExtractMethods { } +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} [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} [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} [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 { attributes: {}, traits: ['id', 'title'], propagate: '', + deepPropagate: '', dmode: '', toolbar: null, delegate: null, @@ -225,12 +229,12 @@ export default class Component extends StyleableModel { 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 { * @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 { * @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 { } 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 { 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 { 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 { 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 = {}; + 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 { 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 { * // -> 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 { .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 { 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); }); } diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index c9d04821e8..d5c8836de7 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/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); diff --git a/packages/core/src/dom_components/model/Components.ts b/packages/core/src/dom_components/model/Components.ts index 36568a8a4c..90e13c6f79 100644 --- a/packages/core/src/dom_components/model/Components.ts +++ b/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; diff --git a/packages/core/src/dom_components/model/DynamicValueWatcher.ts b/packages/core/src/dom_components/model/DynamicValueWatcher.ts index 27229b7c96..d0a9bfd618 100644 --- a/packages/core/src/dom_components/model/DynamicValueWatcher.ts +++ b/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; } diff --git a/packages/core/src/dom_components/model/types.ts b/packages/core/src/dom_components/model/types.ts index 25f5ddc158..a669c9b83a 100644 --- a/packages/core/src/dom_components/model/types.ts +++ b/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} [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' }, ... ]`. From 6710b8f5b0f1c4a64dac9dd516fa1b9104ca1a99 Mon Sep 17 00:00:00 2001 From: mohamed yahia Date: Tue, 14 Jan 2025 18:11:33 +0200 Subject: [PATCH 58/87] Refactor collection type --- packages/core/src/dom_components/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/dom_components/index.ts b/packages/core/src/dom_components/index.ts index 8c1235d61f..8ab4afaf7c 100644 --- a/packages/core/src/dom_components/index.ts +++ b/packages/core/src/dom_components/index.ts @@ -130,6 +130,7 @@ import ComponentConditionalVariable from '../data_sources/model/conditional_vari import ConditionalComponentView from '../data_sources/view/ComponentDynamicView'; import CollectionComponent from '../data_sources/model/collection_component/CollectionComponent'; import CollectionComponentView from '../data_sources/model/collection_component/CollectionComponentView'; +import { CollectionComponentType } from "../data_sources/model/collection_component/constants"; export type ComponentEvent = | 'component:create' @@ -196,7 +197,7 @@ export interface CanMoveResult { export default class ComponentManager extends ItemManagerModule { componentTypes: ComponentStackItem[] = [ { - id: 'collection-component', + id: CollectionComponentType, model: CollectionComponent, view: CollectionComponentView, }, From 550499efacf306febea037f6a338fe7bd35625b5 Mon Sep 17 00:00:00 2001 From: mohamed yahia Date: Tue, 14 Jan 2025 18:22:42 +0200 Subject: [PATCH 59/87] Delete null assertion --- packages/core/src/dom_components/model/SymbolUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/dom_components/model/SymbolUtils.ts b/packages/core/src/dom_components/model/SymbolUtils.ts index 60708c56c7..e57abd5f26 100644 --- a/packages/core/src/dom_components/model/SymbolUtils.ts +++ b/packages/core/src/dom_components/model/SymbolUtils.ts @@ -131,8 +131,8 @@ export const logSymbol = (symb: Component, type: string, toUp: Component[], opts }; export const updateSymbolProps = (symbol: Component, opts: SymbolToUpOptions = {}): void => { - const changed = symbol.componentDVListener.getPropsDefsOrValues({ ...symbol.changedAttributes() } || {}); - const attrs = symbol.componentDVListener.getAttributesDefsOrValues({ ...changed.attributes } || {}); + const changed = symbol.componentDVListener.getPropsDefsOrValues({ ...symbol.changedAttributes() }); + const attrs = symbol.componentDVListener.getAttributesDefsOrValues({ ...changed.attributes }); cleanChangedProperties(changed, attrs); From d4fe2c3e5173e53bcc56dad1085697d99557fdb8 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 15 Jan 2025 13:21:22 +0200 Subject: [PATCH 60/87] Replace types with interfaces --- .../model/collection_component/types.ts | 23 ++++++++++--------- packages/core/src/dom_components/index.ts | 2 +- .../src/dom_components/model/Component.ts | 14 +++++------ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/types.ts b/packages/core/src/data_sources/model/collection_component/types.ts index 17237f1e77..f051590dae 100644 --- a/packages/core/src/data_sources/model/collection_component/types.ts +++ b/packages/core/src/data_sources/model/collection_component/types.ts @@ -4,12 +4,13 @@ import { ComponentDefinition } from '../../../dom_components/model/types'; import { CollectionVariableDefinition } from '../../../../test/specs/dom_components/model/ComponentTypes'; import { DataVariableDefinition } from '../DataVariable'; -type CollectionDataSource = any[] | DataVariableDefinition | CollectionVariableDefinition; -type CollectionConfig = { +export type CollectionDataSource = any[] | DataVariableDefinition | CollectionVariableDefinition; + +export interface CollectionConfig { startIndex?: number; endIndex?: number; dataSource: CollectionDataSource; -}; +} export enum CollectionStateVariableType { currentIndex = 'currentIndex', @@ -21,7 +22,7 @@ export enum CollectionStateVariableType { remainingItems = 'remainingItems', } -export type CollectionState = { +export interface CollectionState { [CollectionStateVariableType.currentIndex]: number; [CollectionStateVariableType.startIndex]: number; [CollectionStateVariableType.currentItem]: any; @@ -29,19 +30,19 @@ export type CollectionState = { [CollectionStateVariableType.collectionName]?: string; [CollectionStateVariableType.totalItems]: number; [CollectionStateVariableType.remainingItems]: number; -}; +} -export type CollectionsStateMap = { +export interface CollectionsStateMap { [key: string]: CollectionState; -}; +} -export type CollectionComponentDefinition = { +export interface CollectionComponentDefinition extends ComponentDefinition { [keyCollectionDefinition]: CollectionDefinition; -} & ComponentDefinition; +} -export type CollectionDefinition = { +export interface CollectionDefinition { type: typeof CollectionComponentType; collectionName?: string; config: CollectionConfig; block: ComponentDefinition; -}; +} diff --git a/packages/core/src/dom_components/index.ts b/packages/core/src/dom_components/index.ts index 8ab4afaf7c..7daac11c5c 100644 --- a/packages/core/src/dom_components/index.ts +++ b/packages/core/src/dom_components/index.ts @@ -130,7 +130,7 @@ import ComponentConditionalVariable from '../data_sources/model/conditional_vari import ConditionalComponentView from '../data_sources/view/ComponentDynamicView'; import CollectionComponent from '../data_sources/model/collection_component/CollectionComponent'; import CollectionComponentView from '../data_sources/model/collection_component/CollectionComponentView'; -import { CollectionComponentType } from "../data_sources/model/collection_component/constants"; +import { CollectionComponentType } from '../data_sources/model/collection_component/constants'; export type ComponentEvent = | 'component:create' diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index d7b64fbc95..83e18af7a4 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -56,9 +56,9 @@ import { import { ComponentDynamicValueWatcher } from './ComponentDynamicValueWatcher'; import { DynamicWatchersOptions } from './DynamicValueWatcher'; -export interface IComponent extends ExtractMethods { } -export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions { } -export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions { } +export interface IComponent extends ExtractMethods {} +export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {} +export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions {} const escapeRegExp = (str: string) => { return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); @@ -229,12 +229,12 @@ export default class Component extends StyleableModel { 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) @@ -242,12 +242,12 @@ export default class Component extends StyleableModel { * @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; From b9ebb756e50f0dd75a64bf12a79e9cf7b29eb6f3 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 15 Jan 2025 13:29:16 +0200 Subject: [PATCH 61/87] Refactor keyIsCollectionItem --- .../model/collection_component/CollectionComponent.ts | 11 ++++++++--- .../model/collection_component/constants.ts | 1 + packages/core/src/dom_components/model/Component.ts | 10 ++++------ .../model/ComponentDynamicValueWatcher.ts | 4 ++-- packages/core/src/dom_components/model/types.ts | 3 --- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts index f12374076e..a32dacea7c 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/src/data_sources/model/collection_component/CollectionComponent.ts @@ -8,7 +8,12 @@ import { ObjectAny } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; import { keyCollectionsStateMap } from '../../../dom_components/model/Component'; import { CollectionComponentDefinition, CollectionDefinition, CollectionState, CollectionsStateMap } from './types'; -import { keyCollectionDefinition, keyInnerCollectionState, CollectionComponentType } from './constants'; +import { + keyCollectionDefinition, + keyInnerCollectionState, + CollectionComponentType, + keyIsCollectionItem, +} from './constants'; import DynamicVariableListenerManager from '../DataVariableListenerManager'; export default class CollectionComponent extends Component { @@ -148,7 +153,7 @@ function getCollectionItems( { ...block, [keyCollectionsStateMap]: collectionsStateMap, - isCollectionItem: true, + [keyIsCollectionItem]: true, draggable: false, deepPropagate: [setCollectionStateMap(collectionsStateMap)], }, @@ -167,7 +172,7 @@ function getCollectionItems( function setCollectionStateMap(collectionsStateMap: CollectionsStateMap) { return (cmp: Component) => { - cmp.set('isCollectionItem', true); + cmp.set(keyIsCollectionItem, true); cmp.set(keyCollectionsStateMap, collectionsStateMap); }; } diff --git a/packages/core/src/data_sources/model/collection_component/constants.ts b/packages/core/src/data_sources/model/collection_component/constants.ts index adf3b232c3..c4db44134d 100644 --- a/packages/core/src/data_sources/model/collection_component/constants.ts +++ b/packages/core/src/data_sources/model/collection_component/constants.ts @@ -1,4 +1,5 @@ export const CollectionComponentType = 'collection-component'; export const keyCollectionDefinition = 'collectionDefinition'; export const keyInnerCollectionState = 'innerCollectionState'; +export const keyIsCollectionItem = '__is_collection_item'; export const CollectionVariableType = 'parent-collection-variable'; diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 83e18af7a4..1512deb9b8 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -55,6 +55,7 @@ import { } from './SymbolUtils'; import { ComponentDynamicValueWatcher } from './ComponentDynamicValueWatcher'; import { DynamicWatchersOptions } from './DynamicValueWatcher'; +import { keyIsCollectionItem } from '../../data_sources/model/collection_component/constants'; export interface IComponent extends ExtractMethods {} export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {} @@ -73,7 +74,6 @@ export const keySymbolOvrd = '__symbol_ovrd'; export const keyUpdate = ComponentsEvents.update; export const keyUpdateInside = ComponentsEvents.updateInside; export const keyCollectionsStateMap = '__collections_state_map'; -export const keyIsCollectionItem = '__is_collection_item'; /** * The Component object represents a single node of our template structure, so when you update its properties the changes are @@ -265,7 +265,6 @@ export default class Component extends StyleableModel { * @ts-ignore */ collection!: Components; componentDVListener: ComponentDynamicValueWatcher; - initialParent?: Component; accumulatedPropagatedProps: DeepPropagationArray = []; constructor(props: ComponentProperties = {}, opt: ComponentOptions) { @@ -356,7 +355,7 @@ export default class Component extends StyleableModel { return collectionStateMapProp; } - const parent = this.parent() || this.opt.parent; + const parent = this.parent(); return parent?.getCollectionStateMap() || {}; } @@ -1144,7 +1143,6 @@ export default class Component extends StyleableModel { * // -> 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; } @@ -1632,12 +1630,12 @@ export default class Component extends StyleableModel { delete obj.open; // used in Layers delete obj._undoexc; delete obj.delegate; - if (this.get('isCollectionItem')) { + if (this.get(keyIsCollectionItem)) { delete obj[keySymbol]; delete obj[keySymbolOvrd]; delete obj[keySymbols]; delete obj[keyCollectionsStateMap]; - delete obj['isCollectionItem']; + delete obj[keyIsCollectionItem]; delete obj.attributes.id; obj['components'] = this.components() .toArray() diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index d5c8836de7..0eb08228bd 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -1,5 +1,5 @@ import { ObjectAny } from '../../common'; -import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; +import { CollectionVariableType, keyIsCollectionItem } from '../../data_sources/model/collection_component/constants'; import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import EditorModel from '../../editor/model/Editor'; import Component, { keyCollectionsStateMap } from './Component'; @@ -69,7 +69,7 @@ export class ComponentDynamicValueWatcher { } private updateSymbolOverride() { - if (!this.component || !this.component.get('isCollectionItem')) return; + if (!this.component || !this.component.get(keyIsCollectionItem)) return; const keys = this.propertyWatcher.getDynamicValuesOfType(CollectionVariableType); const attributesKeys = this.attributeWatcher.getDynamicValuesOfType(CollectionVariableType); diff --git a/packages/core/src/dom_components/model/types.ts b/packages/core/src/dom_components/model/types.ts index a669c9b83a..c36911b921 100644 --- a/packages/core/src/dom_components/model/types.ts +++ b/packages/core/src/dom_components/model/types.ts @@ -355,7 +355,4 @@ export interface ComponentOptions { frame?: Frame; temporary?: boolean; avoidChildren?: boolean; - collectionsStateMap?: CollectionsStateMap; - isCollectionItem?: boolean; - parent?: Component; } From e6f4a6ef3f37e9230ac0ac8bbb561a2b22973957 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 15 Jan 2025 13:33:22 +0200 Subject: [PATCH 62/87] Add missing opts in setId method --- packages/core/src/dom_components/model/Component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 1512deb9b8..a273b39d3c 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -1708,7 +1708,7 @@ export default class Component extends StyleableModel { * @return {this} */ setId(id: string, opts?: SetOptions & { idUpdate?: boolean }) { - this.addAttributes({ id }); + this.addAttributes({ id }, opts ); return this; } From 535eba0dc4e1fe7bda42b04b90d1cc30f14bc2d5 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 15 Jan 2025 13:34:41 +0200 Subject: [PATCH 63/87] Remove console.log --- packages/core/src/utils/sorter/CanvasComponentNode.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/utils/sorter/CanvasComponentNode.ts b/packages/core/src/utils/sorter/CanvasComponentNode.ts index 9563386e86..cbe23f08ac 100644 --- a/packages/core/src/utils/sorter/CanvasComponentNode.ts +++ b/packages/core/src/utils/sorter/CanvasComponentNode.ts @@ -13,8 +13,6 @@ export default class CanvasComponentNode extends BaseComponentNode { * @returns {boolean} - True if the move is allowed, false otherwise. */ canMove(source: BaseComponentNode, index: number): boolean { - console.log('🚀 ~ CanvasComponentNode ~ canMove ~ this.model:', this.model); - console.log('🚀 ~ CanvasComponentNode ~ canMove ~ source.model:', source.model); return this.model.em.Components.canMove(this.model, source.model, this.getRealIndex(index)).result; } /** From a6bb3e9c0272d89b22f1cc0e8aaab3cd5620e844 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 15 Jan 2025 13:44:09 +0200 Subject: [PATCH 64/87] Replace `content` property for collection component testing --- .../CollectionComponent.ts | 232 +++++++++--------- .../__snapshots__/CollectionComponent.ts.snap | 124 +++++----- 2 files changed, 178 insertions(+), 178 deletions(-) diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index 38e4682668..417dc871d8 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -128,14 +128,14 @@ describe('Collection component', () => { components: [ { type: 'default', - content: { + name: { type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'user', }, }, ], - content: { + name: { type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'user', @@ -163,53 +163,53 @@ describe('Collection component', () => { }); test('Evaluating to static value', () => { - expect(firstChild.get('content')).toBe('user1'); + expect(firstChild.get('name')).toBe('user1'); expect(firstChild.get('custom_property')).toBe('user1'); - expect(firstGrandchild.get('content')).toBe('user1'); + expect(firstGrandchild.get('name')).toBe('user1'); - expect(secondChild.get('content')).toBe('user2'); + expect(secondChild.get('name')).toBe('user2'); expect(secondChild.get('custom_property')).toBe('user2'); - expect(secondGrandchild.get('content')).toBe('user2'); + expect(secondGrandchild.get('name')).toBe('user2'); }); test('Watching Records', async () => { firstRecord.set('user', 'new_user1_value'); - expect(firstChild.get('content')).toBe('new_user1_value'); + expect(firstChild.get('name')).toBe('new_user1_value'); expect(firstChild.get('custom_property')).toBe('new_user1_value'); - expect(firstGrandchild.get('content')).toBe('new_user1_value'); + expect(firstGrandchild.get('name')).toBe('new_user1_value'); - expect(secondChild.get('content')).toBe('user2'); + expect(secondChild.get('name')).toBe('user2'); expect(secondChild.get('custom_property')).toBe('user2'); - expect(secondGrandchild.get('content')).toBe('user2'); + expect(secondGrandchild.get('name')).toBe('user2'); }); test('Updating the value to a static value', async () => { - firstChild.set('content', 'new_content_value'); - expect(firstChild.get('content')).toBe('new_content_value'); - expect(secondChild.get('content')).toBe('new_content_value'); + firstChild.set('name', 'new_content_value'); + expect(firstChild.get('name')).toBe('new_content_value'); + expect(secondChild.get('name')).toBe('new_content_value'); firstRecord.set('user', 'wrong_value'); - expect(firstChild.get('content')).toBe('new_content_value'); - expect(secondChild.get('content')).toBe('new_content_value'); + expect(firstChild.get('name')).toBe('new_content_value'); + expect(secondChild.get('name')).toBe('new_content_value'); - firstGrandchild.set('content', 'new_content_value'); - expect(firstGrandchild.get('content')).toBe('new_content_value'); - expect(secondGrandchild.get('content')).toBe('new_content_value'); + firstGrandchild.set('name', 'new_content_value'); + expect(firstGrandchild.get('name')).toBe('new_content_value'); + expect(secondGrandchild.get('name')).toBe('new_content_value'); firstRecord.set('user', 'wrong_value'); - expect(firstGrandchild.get('content')).toBe('new_content_value'); - expect(secondGrandchild.get('content')).toBe('new_content_value'); + expect(firstGrandchild.get('name')).toBe('new_content_value'); + expect(secondGrandchild.get('name')).toBe('new_content_value'); }); test('Updating the value to a diffirent collection variable', async () => { - firstChild.set('content', { + firstChild.set('name', { // @ts-ignore type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'age', }); - expect(firstChild.get('content')).toBe('12'); - expect(secondChild.get('content')).toBe('14'); + expect(firstChild.get('name')).toBe('12'); + expect(secondChild.get('name')).toBe('14'); firstRecord.set('age', 'new_value_12'); secondRecord.set('age', 'new_value_14'); @@ -217,52 +217,52 @@ describe('Collection component', () => { firstRecord.set('user', 'wrong_value'); secondRecord.set('user', 'wrong_value'); - expect(firstChild.get('content')).toBe('new_value_12'); - expect(secondChild.get('content')).toBe('new_value_14'); + expect(firstChild.get('name')).toBe('new_value_12'); + expect(secondChild.get('name')).toBe('new_value_14'); - firstGrandchild.set('content', { + firstGrandchild.set('name', { // @ts-ignore type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'age', }); - expect(firstGrandchild.get('content')).toBe('new_value_12'); - expect(secondGrandchild.get('content')).toBe('new_value_14'); + expect(firstGrandchild.get('name')).toBe('new_value_12'); + expect(secondGrandchild.get('name')).toBe('new_value_14'); firstRecord.set('age', 'most_new_value_12'); secondRecord.set('age', 'most_new_value_14'); - expect(firstGrandchild.get('content')).toBe('most_new_value_12'); - expect(secondGrandchild.get('content')).toBe('most_new_value_14'); + expect(firstGrandchild.get('name')).toBe('most_new_value_12'); + expect(secondGrandchild.get('name')).toBe('most_new_value_14'); }); test('Updating the value to a diffirent dynamic variable', async () => { - firstChild.set('content', { + firstChild.set('name', { // @ts-ignore type: DataVariableType, path: 'my_data_source_id.user2.user', }); - expect(firstChild.get('content')).toBe('user2'); - expect(secondChild.get('content')).toBe('user2'); - expect(thirdChild.get('content')).toBe('user2'); + expect(firstChild.get('name')).toBe('user2'); + expect(secondChild.get('name')).toBe('user2'); + expect(thirdChild.get('name')).toBe('user2'); secondRecord.set('user', 'new_value'); - expect(firstChild.get('content')).toBe('new_value'); - expect(secondChild.get('content')).toBe('new_value'); - expect(thirdChild.get('content')).toBe('new_value'); + expect(firstChild.get('name')).toBe('new_value'); + expect(secondChild.get('name')).toBe('new_value'); + expect(thirdChild.get('name')).toBe('new_value'); - firstGrandchild.set('content', { + firstGrandchild.set('name', { // @ts-ignore type: DataVariableType, path: 'my_data_source_id.user2.user', }); - expect(firstGrandchild.get('content')).toBe('new_value'); - expect(secondGrandchild.get('content')).toBe('new_value'); + expect(firstGrandchild.get('name')).toBe('new_value'); + expect(secondGrandchild.get('name')).toBe('new_value'); secondRecord.set('user', 'most_new_value'); - expect(firstGrandchild.get('content')).toBe('most_new_value'); - expect(secondGrandchild.get('content')).toBe('most_new_value'); + expect(firstGrandchild.get('name')).toBe('most_new_value'); + expect(secondGrandchild.get('name')).toBe('most_new_value'); }); }); @@ -284,7 +284,7 @@ describe('Collection component', () => { { type: 'default', attributes: { - content: { + name: { type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'user', @@ -293,7 +293,7 @@ describe('Collection component', () => { }, ], attributes: { - content: { + name: { type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'user', @@ -317,51 +317,51 @@ describe('Collection component', () => { }); test('Evaluating to static value', () => { - expect(firstChild.getAttributes()['content']).toBe('user1'); - expect(firstGrandchild.getAttributes()['content']).toBe('user1'); + expect(firstChild.getAttributes()['name']).toBe('user1'); + expect(firstGrandchild.getAttributes()['name']).toBe('user1'); - expect(secondChild.getAttributes()['content']).toBe('user2'); - expect(secondGrandchild.getAttributes()['content']).toBe('user2'); + expect(secondChild.getAttributes()['name']).toBe('user2'); + expect(secondGrandchild.getAttributes()['name']).toBe('user2'); }); test('Watching Records', async () => { firstRecord.set('user', 'new_user1_value'); - expect(firstChild.getAttributes()['content']).toBe('new_user1_value'); - expect(firstGrandchild.getAttributes()['content']).toBe('new_user1_value'); + expect(firstChild.getAttributes()['name']).toBe('new_user1_value'); + expect(firstGrandchild.getAttributes()['name']).toBe('new_user1_value'); - expect(secondChild.getAttributes()['content']).toBe('user2'); - expect(secondGrandchild.getAttributes()['content']).toBe('user2'); + expect(secondChild.getAttributes()['name']).toBe('user2'); + expect(secondGrandchild.getAttributes()['name']).toBe('user2'); }); test('Updating the value to a static value', async () => { - firstChild.setAttributes({ content: 'new_content_value' }); - expect(firstChild.getAttributes()['content']).toBe('new_content_value'); - expect(secondChild.getAttributes()['content']).toBe('new_content_value'); + firstChild.setAttributes({ name: 'new_content_value' }); + expect(firstChild.getAttributes()['name']).toBe('new_content_value'); + expect(secondChild.getAttributes()['name']).toBe('new_content_value'); firstRecord.set('user', 'wrong_value'); - expect(firstChild.getAttributes()['content']).toBe('new_content_value'); - expect(secondChild.getAttributes()['content']).toBe('new_content_value'); + expect(firstChild.getAttributes()['name']).toBe('new_content_value'); + expect(secondChild.getAttributes()['name']).toBe('new_content_value'); - firstGrandchild.setAttributes({ content: 'new_content_value' }); - expect(firstGrandchild.getAttributes()['content']).toBe('new_content_value'); - expect(secondGrandchild.getAttributes()['content']).toBe('new_content_value'); + firstGrandchild.setAttributes({ name: 'new_content_value' }); + expect(firstGrandchild.getAttributes()['name']).toBe('new_content_value'); + expect(secondGrandchild.getAttributes()['name']).toBe('new_content_value'); firstRecord.set('user', 'wrong_value'); - expect(firstGrandchild.getAttributes()['content']).toBe('new_content_value'); - expect(secondGrandchild.getAttributes()['content']).toBe('new_content_value'); + expect(firstGrandchild.getAttributes()['name']).toBe('new_content_value'); + expect(secondGrandchild.getAttributes()['name']).toBe('new_content_value'); }); test('Updating the value to a diffirent collection variable', async () => { firstChild.setAttributes({ - content: { + name: { // @ts-ignore type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'age', }, }); - expect(firstChild.getAttributes()['content']).toBe('12'); - expect(secondChild.getAttributes()['content']).toBe('14'); + expect(firstChild.getAttributes()['name']).toBe('12'); + expect(secondChild.getAttributes()['name']).toBe('14'); firstRecord.set('age', 'new_value_12'); secondRecord.set('age', 'new_value_14'); @@ -369,58 +369,58 @@ describe('Collection component', () => { firstRecord.set('user', 'wrong_value'); secondRecord.set('user', 'wrong_value'); - expect(firstChild.getAttributes()['content']).toBe('new_value_12'); - expect(secondChild.getAttributes()['content']).toBe('new_value_14'); + expect(firstChild.getAttributes()['name']).toBe('new_value_12'); + expect(secondChild.getAttributes()['name']).toBe('new_value_14'); firstGrandchild.setAttributes({ - content: { + name: { // @ts-ignore type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'age', }, }); - expect(firstGrandchild.getAttributes()['content']).toBe('new_value_12'); - expect(secondGrandchild.getAttributes()['content']).toBe('new_value_14'); + expect(firstGrandchild.getAttributes()['name']).toBe('new_value_12'); + expect(secondGrandchild.getAttributes()['name']).toBe('new_value_14'); firstRecord.set('age', 'most_new_value_12'); secondRecord.set('age', 'most_new_value_14'); - expect(firstGrandchild.getAttributes()['content']).toBe('most_new_value_12'); - expect(secondGrandchild.getAttributes()['content']).toBe('most_new_value_14'); + expect(firstGrandchild.getAttributes()['name']).toBe('most_new_value_12'); + expect(secondGrandchild.getAttributes()['name']).toBe('most_new_value_14'); }); test('Updating the value to a diffirent dynamic variable', async () => { firstChild.setAttributes({ - content: { + name: { // @ts-ignore type: DataVariableType, path: 'my_data_source_id.user2.user', }, }); - expect(firstChild.getAttributes()['content']).toBe('user2'); - expect(secondChild.getAttributes()['content']).toBe('user2'); - expect(thirdChild.getAttributes()['content']).toBe('user2'); + expect(firstChild.getAttributes()['name']).toBe('user2'); + expect(secondChild.getAttributes()['name']).toBe('user2'); + expect(thirdChild.getAttributes()['name']).toBe('user2'); secondRecord.set('user', 'new_value'); - expect(firstChild.getAttributes()['content']).toBe('new_value'); - expect(secondChild.getAttributes()['content']).toBe('new_value'); - expect(thirdChild.getAttributes()['content']).toBe('new_value'); + expect(firstChild.getAttributes()['name']).toBe('new_value'); + expect(secondChild.getAttributes()['name']).toBe('new_value'); + expect(thirdChild.getAttributes()['name']).toBe('new_value'); firstGrandchild.setAttributes({ - content: { + name: { // @ts-ignore type: DataVariableType, path: 'my_data_source_id.user2.user', }, }); - expect(firstGrandchild.getAttributes()['content']).toBe('new_value'); - expect(secondGrandchild.getAttributes()['content']).toBe('new_value'); + expect(firstGrandchild.getAttributes()['name']).toBe('new_value'); + expect(secondGrandchild.getAttributes()['name']).toBe('new_value'); secondRecord.set('user', 'most_new_value'); - expect(firstGrandchild.getAttributes()['content']).toBe('most_new_value'); - expect(secondGrandchild.getAttributes()['content']).toBe('most_new_value'); + expect(firstGrandchild.getAttributes()['name']).toBe('most_new_value'); + expect(secondGrandchild.getAttributes()['name']).toBe('most_new_value'); }); }); @@ -484,7 +484,7 @@ describe('Collection component', () => { beforeEach(() => { const cmpDefinition = { type: 'default', - content: { + name: { type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'user', @@ -495,7 +495,7 @@ describe('Collection component', () => { path: 'user', }, attributes: { - content: { + name: { type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'user', @@ -550,7 +550,7 @@ describe('Collection component', () => { const firstChild = cmp.components().at(0); const newChildDefinition = { type: 'default', - content: { + name: { type: CollectionVariableType, variableType: CollectionStateVariableType.currentIndex, path: 'user', @@ -571,7 +571,7 @@ describe('Collection component', () => { const firstChild = cmp.components().at(0); const newChildDefinition = { type: 'default', - content: { + name: { type: CollectionVariableType, variableType: CollectionStateVariableType.currentIndex, path: 'user', @@ -599,7 +599,7 @@ describe('Collection component', () => { type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, }, - content: { + name: { path: 'user', type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, @@ -613,13 +613,13 @@ describe('Collection component', () => { type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, }, - content: { + name: { path: 'user', type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, }, }, - content: { + name: { path: 'user', type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, @@ -643,13 +643,13 @@ describe('Collection component', () => { type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, }, - content: { + name: { path: 'user', type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, }, }, - content: { + name: { path: 'user', type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, @@ -667,7 +667,7 @@ describe('Collection component', () => { type: 'default', }, ], - content: { + name: { path: 'user', type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, @@ -734,26 +734,26 @@ describe('Collection component', () => { const secondChild = component.components().at(1); const secondGrandchild = secondChild.components().at(0); - expect(firstChild.get('content')).toBe('user1'); - expect(firstChild.getAttributes()['content']).toBe('user1'); - expect(firstGrandchild.get('content')).toBe('user1'); - expect(firstGrandchild.getAttributes()['content']).toBe('user1'); + expect(firstChild.get('name')).toBe('user1'); + expect(firstChild.getAttributes()['name']).toBe('user1'); + expect(firstGrandchild.get('name')).toBe('user1'); + expect(firstGrandchild.getAttributes()['name']).toBe('user1'); - expect(secondChild.get('content')).toBe('user2'); - expect(secondChild.getAttributes()['content']).toBe('user2'); - expect(secondGrandchild.get('content')).toBe('user2'); - expect(secondGrandchild.getAttributes()['content']).toBe('user2'); + expect(secondChild.get('name')).toBe('user2'); + expect(secondChild.getAttributes()['name']).toBe('user2'); + expect(secondGrandchild.get('name')).toBe('user2'); + expect(secondGrandchild.getAttributes()['name']).toBe('user2'); firstRecord.set('user', 'new_user1_value'); - expect(firstChild.get('content')).toBe('new_user1_value'); - expect(firstChild.getAttributes()['content']).toBe('new_user1_value'); - expect(firstGrandchild.get('content')).toBe('new_user1_value'); - expect(firstGrandchild.getAttributes()['content']).toBe('new_user1_value'); - - expect(secondChild.get('content')).toBe('user2'); - expect(secondChild.getAttributes()['content']).toBe('user2'); - expect(secondGrandchild.get('content')).toBe('user2'); - expect(secondGrandchild.getAttributes()['content']).toBe('user2'); + expect(firstChild.get('name')).toBe('new_user1_value'); + expect(firstChild.getAttributes()['name']).toBe('new_user1_value'); + expect(firstGrandchild.get('name')).toBe('new_user1_value'); + expect(firstGrandchild.getAttributes()['name']).toBe('new_user1_value'); + + expect(secondChild.get('name')).toBe('user2'); + expect(secondChild.getAttributes()['name']).toBe('user2'); + expect(secondGrandchild.get('name')).toBe('user2'); + expect(secondGrandchild.getAttributes()['name']).toBe('user2'); }); }); @@ -764,7 +764,7 @@ describe('Collection component', () => { collectionDefinition: { block: { type: 'default', - content: { + name: { type: CollectionVariableType, variableType: CollectionStateVariableType.currentItem, path: 'user', @@ -785,8 +785,8 @@ describe('Collection component', () => { const firstChild = cmp.components().at(0); const secondChild = cmp.components().at(1); - expect(firstChild.get('content')).toBe('user2'); - expect(secondChild.get('content')).toBe('user3'); + expect(firstChild.get('name')).toBe('user2'); + expect(secondChild.get('name')).toBe('user3'); }); }); @@ -811,7 +811,7 @@ describe('Collection component', () => { collectionName: 'my_collection', block: { type: 'default', - content: { + name: { type: CollectionVariableType, variableType: variableType, }, @@ -852,7 +852,7 @@ describe('Collection component', () => { expect(children).toHaveLength(3); children.each((child, index) => { - expect(child.get('content')).toBe(expectedValues[index]); + expect(child.get('name')).toBe(expectedValues[index]); expect(child.get('property_trait')).toBe(expectedValues[index]); expect(child.getAttributes()['custom_attribute']).toBe(expectedValues[index]); expect(child.getAttributes()['attribute_trait']).toBe(expectedValues[index]); diff --git a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap index d2d5a8a53d..39905c5565 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap +++ b/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap @@ -10,7 +10,7 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -24,7 +24,7 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -32,7 +32,7 @@ exports[`Collection component Serialization Saving: Collection with grandchildre }, "components": [ { - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", @@ -40,15 +40,15 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "type": "default", }, ], - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -64,21 +64,21 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -88,15 +88,15 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "type": "default", }, ], - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -129,7 +129,7 @@ exports[`Collection component Serialization Saving: Collection with no grandchil "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -143,21 +143,21 @@ exports[`Collection component Serialization Saving: Collection with no grandchil "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -173,21 +173,21 @@ exports[`Collection component Serialization Saving: Collection with no grandchil "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -197,15 +197,15 @@ exports[`Collection component Serialization Saving: Collection with no grandchil "type": "default", }, ], - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -238,7 +238,7 @@ exports[`Collection component Serialization Serializion with Collection Variable "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -252,7 +252,7 @@ exports[`Collection component Serialization Serializion with Collection Variable "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -260,7 +260,7 @@ exports[`Collection component Serialization Serializion with Collection Variable }, "components": [ { - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", @@ -268,15 +268,15 @@ exports[`Collection component Serialization Serializion with Collection Variable "type": "default", }, ], - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -292,21 +292,21 @@ exports[`Collection component Serialization Serializion with Collection Variable "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -316,15 +316,15 @@ exports[`Collection component Serialization Serializion with Collection Variable "type": "default", }, ], - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -357,7 +357,7 @@ exports[`Collection component Serialization Serializion with Collection Variable "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -371,21 +371,21 @@ exports[`Collection component Serialization Serializion with Collection Variable "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -401,21 +401,21 @@ exports[`Collection component Serialization Serializion with Collection Variable "type": "parent-collection-variable", "variableType": "currentItem", }, - "content": { + "name": { "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", @@ -425,15 +425,15 @@ exports[`Collection component Serialization Serializion with Collection Variable "type": "default", }, ], - "content": { + "custom_prop": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentItem", + "variableType": "currentIndex", }, - "custom_prop": { + "name": { "path": "user", "type": "parent-collection-variable", - "variableType": "currentIndex", + "variableType": "currentItem", }, "property_trait": { "path": "user", From 5851eec63042725fe537d86ba250182d7994447d Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 15 Jan 2025 16:22:28 +0200 Subject: [PATCH 65/87] Fix collection component serialization tests --- .../core/src/dom_components/model/Component.ts | 7 ++----- .../model/ComponentDynamicValueWatcher.ts | 8 ++++---- .../core/src/dom_components/model/Components.ts | 1 - .../dom_components/model/DynamicValueWatcher.ts | 15 ++++++++++++--- .../collection_component/CollectionComponent.ts | 8 ++++---- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index a273b39d3c..ad127eaaeb 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -1637,11 +1637,8 @@ export default class Component extends StyleableModel { delete obj[keyCollectionsStateMap]; delete obj[keyIsCollectionItem]; delete obj.attributes.id; - obj['components'] = this.components() - .toArray() - .map((cmp) => cmp.toJSON()); + delete obj.deepPropagate; } - delete obj.deepPropagate; if (!opts.fromUndo) { const symbol = obj[keySymbol]; @@ -1708,7 +1705,7 @@ export default class Component extends StyleableModel { * @return {this} */ setId(id: string, opts?: SetOptions & { idUpdate?: boolean }) { - this.addAttributes({ id }, opts ); + this.addAttributes({ id }, opts); return this; } diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 0eb08228bd..384e095533 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -1,4 +1,4 @@ -import { ObjectAny } from '../../common'; +import { Model, ObjectAny } from '../../common'; import { CollectionVariableType, keyIsCollectionItem } from '../../data_sources/model/collection_component/constants'; import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; import EditorModel from '../../editor/model/Editor'; @@ -7,7 +7,7 @@ import { DynamicWatchersOptions } from './DynamicValueWatcher'; import { DynamicValueWatcher } from './DynamicValueWatcher'; import { getSymbolsToUpdate } from './SymbolUtils'; -export class ComponentDynamicValueWatcher { +export class ComponentDynamicValueWatcher extends Model { private propertyWatcher: DynamicValueWatcher; private attributeWatcher: DynamicValueWatcher; @@ -18,6 +18,7 @@ export class ComponentDynamicValueWatcher { collectionsStateMap?: CollectionsStateMap; }, ) { + super(component, options); this.propertyWatcher = new DynamicValueWatcher(component, this.createPropertyUpdater(), options); this.attributeWatcher = new DynamicValueWatcher(component, this.createAttributeUpdater(), options); } @@ -102,7 +103,6 @@ export class ComponentDynamicValueWatcher { } destroy() { - this.propertyWatcher.removeListeners(); - this.attributeWatcher.removeListeners(); + return this.propertyWatcher.destroy() && this.attributeWatcher.destroy(); } } diff --git a/packages/core/src/dom_components/model/Components.ts b/packages/core/src/dom_components/model/Components.ts index 90e13c6f79..1a708ee251 100644 --- a/packages/core/src/dom_components/model/Components.ts +++ b/packages/core/src/dom_components/model/Components.ts @@ -380,7 +380,6 @@ Component> { } onAdd(model: Component, c?: any, opts: { temporary?: boolean } = {}) { - model.initialParent = this.parent; model.propagateDeeplyFromParent(); const { domc, em } = this; const style = model.getStyle(); diff --git a/packages/core/src/dom_components/model/DynamicValueWatcher.ts b/packages/core/src/dom_components/model/DynamicValueWatcher.ts index d0a9bfd618..3667ccc1e4 100644 --- a/packages/core/src/dom_components/model/DynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/DynamicValueWatcher.ts @@ -1,29 +1,33 @@ import { DynamicValueDefinition } from './../../data_sources/types'; import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; -import { ObjectAny } from '../../common'; +import { Model, ObjectAny } from '../../common'; import DynamicVariableListenerManager from '../../data_sources/model/DataVariableListenerManager'; import { evaluateDynamicValueDefinition, isDynamicValueDefinition } from '../../data_sources/model/utils'; import EditorModel from '../../editor/model/Editor'; import Component from './Component'; import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; +import { ModelDestroyOptions } from 'backbone'; export interface DynamicWatchersOptions { skipWatcherUpdates?: boolean; fromDataSource?: boolean; } -export class DynamicValueWatcher { +type UpdateFn = (component: Component | undefined, key: string, value: any) => void; + +export class DynamicValueWatcher extends Model<{ component: Component | undefined; updateFn: UpdateFn }> { 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 updateFn: UpdateFn, options: { em: EditorModel; collectionsStateMap?: CollectionsStateMap; }, ) { + super({ component, updateFn }, options); this.em = options.em; this.collectionsStateMap = options.collectionsStateMap; } @@ -167,4 +171,9 @@ export class DynamicValueWatcher { return keys; } + + destroy(options?: ModelDestroyOptions | undefined): false | JQueryXHR { + this.removeListeners(); + return super.destroy(); + } } diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts index 417dc871d8..36756565a9 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts @@ -545,7 +545,7 @@ describe('Collection component', () => { }); test('Serializion with Collection Variables to JSON', () => { - expect(filterObjectForSnapshot(cmp.toJSON())).toMatchSnapshot(`Collection with no grandchildren`); + expect(cmp.toJSON()).toMatchSnapshot(`Collection with no grandchildren`); const firstChild = cmp.components().at(0); const newChildDefinition = { @@ -557,7 +557,7 @@ describe('Collection component', () => { }, }; firstChild.components().at(0).components(newChildDefinition); - expect(filterObjectForSnapshot(cmp.toJSON())).toMatchSnapshot(`Collection with grandchildren`); + expect(cmp.toJSON()).toMatchSnapshot(`Collection with grandchildren`); }); test('Saving', () => { @@ -566,7 +566,7 @@ describe('Collection component', () => { const frame = page.frames[0]; const component = frame.component.components[0]; - expect(filterObjectForSnapshot(component)).toMatchSnapshot(`Collection with no grandchildren`); + expect(component).toMatchSnapshot(`Collection with no grandchildren`); const firstChild = cmp.components().at(0); const newChildDefinition = { @@ -578,7 +578,7 @@ describe('Collection component', () => { }, }; firstChild.components().at(0).components(newChildDefinition); - expect(filterObjectForSnapshot(cmp.toJSON())).toMatchSnapshot(`Collection with grandchildren`); + expect(cmp.toJSON()).toMatchSnapshot(`Collection with grandchildren`); }); test('Loading', () => { From 68707db63461fcfc63a37129266046389c62381e Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 15 Jan 2025 16:34:03 +0200 Subject: [PATCH 66/87] Rename collection to DataCollection --- .../model/DataVariableListenerManager.ts | 4 +- .../CollectionComponentView.ts | 4 - .../model/collection_component/types.ts | 48 ------------ .../CollectionVariable.ts | 20 ++--- .../ComponentDataCollection.ts} | 33 ++++---- .../ComponentDataCollectionView.ts | 4 + .../constants.ts | 0 .../model/data_collection/types.ts | 53 +++++++++++++ packages/core/src/data_sources/model/utils.ts | 12 +-- packages/core/src/data_sources/types.ts | 6 +- packages/core/src/dom_components/index.ts | 10 +-- .../src/dom_components/model/Component.ts | 8 +- .../model/ComponentDynamicValueWatcher.ts | 8 +- .../model/DynamicValueWatcher.ts | 10 +-- .../src/dom_components/model/SymbolUtils.ts | 2 +- .../core/src/dom_components/model/types.ts | 2 +- .../ComponentDataCollection.ts} | 78 +++++++++---------- .../ComponentDataCollection.ts.snap} | 0 .../dom_components/model/ComponentTypes.ts | 9 --- 19 files changed, 156 insertions(+), 155 deletions(-) delete mode 100644 packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts delete mode 100644 packages/core/src/data_sources/model/collection_component/types.ts rename packages/core/src/data_sources/model/{collection_component => data_collection}/CollectionVariable.ts (81%) rename packages/core/src/data_sources/model/{collection_component/CollectionComponent.ts => data_collection/ComponentDataCollection.ts} (86%) create mode 100644 packages/core/src/data_sources/model/data_collection/ComponentDataCollectionView.ts rename packages/core/src/data_sources/model/{collection_component => data_collection}/constants.ts (100%) create mode 100644 packages/core/src/data_sources/model/data_collection/types.ts rename packages/core/test/specs/data_sources/model/{collection_component/CollectionComponent.ts => data_collection/ComponentDataCollection.ts} (90%) rename packages/core/test/specs/data_sources/model/{collection_component/__snapshots__/CollectionComponent.ts.snap => data_collection/__snapshots__/ComponentDataCollection.ts.snap} (100%) diff --git a/packages/core/src/data_sources/model/DataVariableListenerManager.ts b/packages/core/src/data_sources/model/DataVariableListenerManager.ts index a43ef0b631..e090e962b5 100644 --- a/packages/core/src/data_sources/model/DataVariableListenerManager.ts +++ b/packages/core/src/data_sources/model/DataVariableListenerManager.ts @@ -6,8 +6,8 @@ import DataVariable, { DataVariableType } from './DataVariable'; import { DynamicValue } from '../types'; import { DataCondition, ConditionalVariableType } from './conditional_variables/DataCondition'; import ComponentDataVariable from './ComponentDataVariable'; -import { CollectionVariableType } from './collection_component/constants'; -import CollectionVariable from './collection_component/CollectionVariable'; +import { CollectionVariableType } from './data_collection/constants'; +import CollectionVariable from './data_collection/CollectionVariable'; export interface DynamicVariableListenerManagerOptions { em: EditorModel; diff --git a/packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts b/packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts deleted file mode 100644 index 19a8fdce69..0000000000 --- a/packages/core/src/data_sources/model/collection_component/CollectionComponentView.ts +++ /dev/null @@ -1,4 +0,0 @@ -import ComponentView from '../../../dom_components/view/ComponentView'; -import CollectionComponent from './CollectionComponent'; - -export default class CollectionComponentView extends ComponentView {} diff --git a/packages/core/src/data_sources/model/collection_component/types.ts b/packages/core/src/data_sources/model/collection_component/types.ts deleted file mode 100644 index f051590dae..0000000000 --- a/packages/core/src/data_sources/model/collection_component/types.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { CollectionComponentType, keyCollectionDefinition } from './constants'; - -import { ComponentDefinition } from '../../../dom_components/model/types'; -import { CollectionVariableDefinition } from '../../../../test/specs/dom_components/model/ComponentTypes'; -import { DataVariableDefinition } from '../DataVariable'; - -export type CollectionDataSource = any[] | DataVariableDefinition | CollectionVariableDefinition; - -export interface CollectionConfig { - startIndex?: number; - endIndex?: number; - dataSource: CollectionDataSource; -} - -export enum CollectionStateVariableType { - currentIndex = 'currentIndex', - startIndex = 'startIndex', - currentItem = 'currentItem', - endIndex = 'endIndex', - collectionName = 'collectionName', - totalItems = 'totalItems', - remainingItems = 'remainingItems', -} - -export interface CollectionState { - [CollectionStateVariableType.currentIndex]: number; - [CollectionStateVariableType.startIndex]: number; - [CollectionStateVariableType.currentItem]: any; - [CollectionStateVariableType.endIndex]: number; - [CollectionStateVariableType.collectionName]?: string; - [CollectionStateVariableType.totalItems]: number; - [CollectionStateVariableType.remainingItems]: number; -} - -export interface CollectionsStateMap { - [key: string]: CollectionState; -} - -export interface CollectionComponentDefinition extends ComponentDefinition { - [keyCollectionDefinition]: CollectionDefinition; -} - -export interface CollectionDefinition { - type: typeof CollectionComponentType; - collectionName?: string; - config: CollectionConfig; - block: ComponentDefinition; -} diff --git a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/CollectionVariable.ts similarity index 81% rename from packages/core/src/data_sources/model/collection_component/CollectionVariable.ts rename to packages/core/src/data_sources/model/data_collection/CollectionVariable.ts index 9e241fc394..38dc6d429a 100644 --- a/packages/core/src/data_sources/model/collection_component/CollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/CollectionVariable.ts @@ -1,20 +1,20 @@ -import { CollectionVariableDefinition } from '../../../../test/specs/dom_components/model/ComponentTypes'; +import { DataCollectionVariableDefinition } from './types'; import { Model } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; import DataVariable, { DataVariableType } from '../DataVariable'; import { keyInnerCollectionState } from './constants'; -import { CollectionState, CollectionsStateMap } from './types'; +import { DataCollectionState, DataCollectionStateMap } from './types'; -export default class CollectionVariable extends Model { +export default class CollectionVariable extends Model { em: EditorModel; - collectionsStateMap: CollectionsStateMap; + collectionsStateMap: DataCollectionStateMap; dataVariable?: DataVariable; constructor( - attrs: CollectionVariableDefinition, + attrs: DataCollectionVariableDefinition, options: { em: EditorModel; - collectionsStateMap: CollectionsStateMap; + collectionsStateMap: DataCollectionStateMap; }, ) { super(attrs, options); @@ -39,7 +39,7 @@ export default class CollectionVariable extends Model { cmp.set(keyIsCollectionItem, true); cmp.set(keyCollectionsStateMap, collectionsStateMap); diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionView.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionView.ts new file mode 100644 index 0000000000..9b3f88a84c --- /dev/null +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionView.ts @@ -0,0 +1,4 @@ +import ComponentView from '../../../dom_components/view/ComponentView'; +import ComponentDataCollection from './ComponentDataCollection'; + +export default class ComponentDataCollectionView extends ComponentView {} diff --git a/packages/core/src/data_sources/model/collection_component/constants.ts b/packages/core/src/data_sources/model/data_collection/constants.ts similarity index 100% rename from packages/core/src/data_sources/model/collection_component/constants.ts rename to packages/core/src/data_sources/model/data_collection/constants.ts diff --git a/packages/core/src/data_sources/model/data_collection/types.ts b/packages/core/src/data_sources/model/data_collection/types.ts new file mode 100644 index 0000000000..50d1186187 --- /dev/null +++ b/packages/core/src/data_sources/model/data_collection/types.ts @@ -0,0 +1,53 @@ +import { CollectionComponentType, CollectionVariableType, keyCollectionDefinition } from './constants'; +import { ComponentDefinition } from '../../../dom_components/model/types'; +import { DataVariableDefinition } from '../DataVariable'; + +export type DataCollectionDataSource = any[] | DataVariableDefinition | DataCollectionVariableDefinition; + +export interface DataCollectionConfig { + startIndex?: number; + endIndex?: number; + dataSource: DataCollectionDataSource; +} + +export enum DataCollectionStateVariableType { + currentIndex = 'currentIndex', + startIndex = 'startIndex', + currentItem = 'currentItem', + endIndex = 'endIndex', + collectionName = 'collectionName', + totalItems = 'totalItems', + remainingItems = 'remainingItems', +} + +export interface DataCollectionState { + [DataCollectionStateVariableType.currentIndex]: number; + [DataCollectionStateVariableType.startIndex]: number; + [DataCollectionStateVariableType.currentItem]: any; + [DataCollectionStateVariableType.endIndex]: number; + [DataCollectionStateVariableType.collectionName]?: string; + [DataCollectionStateVariableType.totalItems]: number; + [DataCollectionStateVariableType.remainingItems]: number; +} + +export interface DataCollectionStateMap { + [key: string]: DataCollectionState; +} + +export interface ComponentDataCollectionDefinition extends ComponentDefinition { + [keyCollectionDefinition]: DataCollectionDefinition; +} + +export interface DataCollectionDefinition { + type: typeof CollectionComponentType; + collectionName?: string; + config: DataCollectionConfig; + block: ComponentDefinition; +} + +export type DataCollectionVariableDefinition = { + type: typeof CollectionVariableType; + variableType: DataCollectionStateVariableType; + collectionName?: string; + path?: string; +}; diff --git a/packages/core/src/data_sources/model/utils.ts b/packages/core/src/data_sources/model/utils.ts index 71322c1609..0f1d216fa9 100644 --- a/packages/core/src/data_sources/model/utils.ts +++ b/packages/core/src/data_sources/model/utils.ts @@ -1,9 +1,9 @@ import EditorModel from '../../editor/model/Editor'; import { DynamicValue, DynamicValueDefinition } from '../types'; -import { CollectionsStateMap } from './collection_component/types'; -import CollectionVariable from './collection_component/CollectionVariable'; -import { CollectionVariableDefinition } from '../../../test/specs/dom_components/model/ComponentTypes'; -import { CollectionVariableType } from './collection_component/constants'; +import { DataCollectionStateMap } from './data_collection/types'; +import CollectionVariable from './data_collection/CollectionVariable'; +import { DataCollectionVariableDefinition } from './data_collection/types'; +import { CollectionVariableType } from './data_collection/constants'; import { ConditionalVariableType, DataCondition } from './conditional_variables/DataCondition'; import DataVariable, { DataVariableType } from './DataVariable'; @@ -34,7 +34,7 @@ export function getDynamicValueInstance( valueDefinition: DynamicValueDefinition, options: { em: EditorModel; - collectionsStateMap?: CollectionsStateMap; + collectionsStateMap?: DataCollectionStateMap; }, ): DynamicValue { const { em } = options; @@ -66,7 +66,7 @@ export function evaluateDynamicValueDefinition( valueDefinition: DynamicValueDefinition, options: { em: EditorModel; - collectionsStateMap?: CollectionsStateMap; + collectionsStateMap?: DataCollectionStateMap; }, ) { const dynamicVariable = getDynamicValueInstance(valueDefinition, options); diff --git a/packages/core/src/data_sources/types.ts b/packages/core/src/data_sources/types.ts index f40bf729a6..33626e8c3e 100644 --- a/packages/core/src/data_sources/types.ts +++ b/packages/core/src/data_sources/types.ts @@ -1,6 +1,6 @@ import { ObjectAny } from '../common'; -import CollectionVariable from './model/collection_component/CollectionVariable'; -import { CollectionVariableDefinition } from '../../test/specs/dom_components/model/ComponentTypes'; +import CollectionVariable from './model/data_collection/CollectionVariable'; +import { DataCollectionVariableDefinition } from './model/data_collection/types'; import ComponentDataVariable from './model/ComponentDataVariable'; import DataRecord from './model/DataRecord'; import DataRecords from './model/DataRecords'; @@ -11,7 +11,7 @@ export type DynamicValue = DataVariable | ComponentDataVariable | DataCondition export type DynamicValueDefinition = | DataVariableDefinition | ConditionalVariableDefinition - | CollectionVariableDefinition; + | DataCollectionVariableDefinition; export interface DataRecordProps extends ObjectAny { /** * Record id. diff --git a/packages/core/src/dom_components/index.ts b/packages/core/src/dom_components/index.ts index 7daac11c5c..0f4cfeaeed 100644 --- a/packages/core/src/dom_components/index.ts +++ b/packages/core/src/dom_components/index.ts @@ -128,9 +128,9 @@ import { DataVariableType } from '../data_sources/model/DataVariable'; import { ConditionalVariableType } from '../data_sources/model/conditional_variables/DataCondition'; import ComponentConditionalVariable from '../data_sources/model/conditional_variables/ConditionalComponent'; import ConditionalComponentView from '../data_sources/view/ComponentDynamicView'; -import CollectionComponent from '../data_sources/model/collection_component/CollectionComponent'; -import CollectionComponentView from '../data_sources/model/collection_component/CollectionComponentView'; -import { CollectionComponentType } from '../data_sources/model/collection_component/constants'; +import ComponentDataCollection from '../data_sources/model/data_collection/ComponentDataCollection'; +import ComponentDataCollectionView from '../data_sources/model/data_collection/ComponentDataCollectionView'; +import { CollectionComponentType } from '../data_sources/model/data_collection/constants'; export type ComponentEvent = | 'component:create' @@ -198,8 +198,8 @@ export default class ComponentManager extends ItemManagerModule {} export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {} @@ -349,7 +349,7 @@ export default class Component extends StyleableModel { } } - getCollectionStateMap(): CollectionsStateMap { + getCollectionStateMap(): DataCollectionStateMap { const collectionStateMapProp = this.get(keyCollectionsStateMap); if (collectionStateMapProp) { return collectionStateMapProp; @@ -2012,7 +2012,7 @@ export default class Component extends StyleableModel { selector && selector.set({ name: id, label: id }); } - private handleCollectionsMapStateChange(m: any, v: CollectionsStateMap, opts = {}) { + private handleCollectionsMapStateChange(m: any, v: DataCollectionStateMap, opts = {}) { this.componentDVListener.updateCollectionStateMap(v); this.components()?.forEach((child) => { child.set?.(keyCollectionsStateMap, v); diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 384e095533..02464764e9 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -1,6 +1,6 @@ import { Model, ObjectAny } from '../../common'; -import { CollectionVariableType, keyIsCollectionItem } from '../../data_sources/model/collection_component/constants'; -import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; +import { CollectionVariableType, keyIsCollectionItem } from '../../data_sources/model/data_collection/constants'; +import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types'; import EditorModel from '../../editor/model/Editor'; import Component, { keyCollectionsStateMap } from './Component'; import { DynamicWatchersOptions } from './DynamicValueWatcher'; @@ -15,7 +15,7 @@ export class ComponentDynamicValueWatcher extends Model { private component: Component | undefined, options: { em: EditorModel; - collectionsStateMap?: CollectionsStateMap; + collectionsStateMap?: DataCollectionStateMap; }, ) { super(component, options); @@ -44,7 +44,7 @@ export class ComponentDynamicValueWatcher extends Model { this.updateSymbolOverride(); } - updateCollectionStateMap(collectionsStateMap: CollectionsStateMap) { + updateCollectionStateMap(collectionsStateMap: DataCollectionStateMap) { this.propertyWatcher.updateCollectionStateMap(collectionsStateMap); this.attributeWatcher.updateCollectionStateMap(collectionsStateMap); } diff --git a/packages/core/src/dom_components/model/DynamicValueWatcher.ts b/packages/core/src/dom_components/model/DynamicValueWatcher.ts index 3667ccc1e4..a14737b3b2 100644 --- a/packages/core/src/dom_components/model/DynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/DynamicValueWatcher.ts @@ -1,11 +1,11 @@ import { DynamicValueDefinition } from './../../data_sources/types'; -import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; +import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types'; import { Model, ObjectAny } from '../../common'; import DynamicVariableListenerManager from '../../data_sources/model/DataVariableListenerManager'; import { evaluateDynamicValueDefinition, isDynamicValueDefinition } from '../../data_sources/model/utils'; import EditorModel from '../../editor/model/Editor'; import Component from './Component'; -import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; +import { CollectionVariableType } from '../../data_sources/model/data_collection/constants'; import { ModelDestroyOptions } from 'backbone'; export interface DynamicWatchersOptions { @@ -18,13 +18,13 @@ type UpdateFn = (component: Component | undefined, key: string, value: any) => v export class DynamicValueWatcher extends Model<{ component: Component | undefined; updateFn: UpdateFn }> { private dynamicVariableListeners: { [key: string]: DynamicVariableListenerManager } = {}; private em: EditorModel; - private collectionsStateMap?: CollectionsStateMap; + private collectionsStateMap?: DataCollectionStateMap; constructor( private component: Component | undefined, private updateFn: UpdateFn, options: { em: EditorModel; - collectionsStateMap?: CollectionsStateMap; + collectionsStateMap?: DataCollectionStateMap; }, ) { super({ component, updateFn }, options); @@ -36,7 +36,7 @@ export class DynamicValueWatcher extends Model<{ component: Component | undefine this.component = component; } - updateCollectionStateMap(collectionsStateMap: CollectionsStateMap) { + updateCollectionStateMap(collectionsStateMap: DataCollectionStateMap) { this.collectionsStateMap = collectionsStateMap; const collectionVariablesKeys = this.getDynamicValuesOfType(CollectionVariableType); diff --git a/packages/core/src/dom_components/model/SymbolUtils.ts b/packages/core/src/dom_components/model/SymbolUtils.ts index e57abd5f26..94e4affd6e 100644 --- a/packages/core/src/dom_components/model/SymbolUtils.ts +++ b/packages/core/src/dom_components/model/SymbolUtils.ts @@ -3,7 +3,7 @@ import Component, { keySymbol, keySymbolOvrd, keySymbols } from './Component'; import { SymbolToUpOptions } from './types'; import { isEmptyObj } from '../../utils/mixins'; import Components from './Components'; -import { CollectionVariableType } from '../../data_sources/model/collection_component/constants'; +import { CollectionVariableType } from '../../data_sources/model/data_collection/constants'; export const isSymbolMain = (cmp: Component) => isArray(cmp.get(keySymbols)); diff --git a/packages/core/src/dom_components/model/types.ts b/packages/core/src/dom_components/model/types.ts index c36911b921..0c9cfa2372 100644 --- a/packages/core/src/dom_components/model/types.ts +++ b/packages/core/src/dom_components/model/types.ts @@ -12,7 +12,7 @@ import Component from './Component'; import Components from './Components'; import { ToolbarButtonProps } from './ToolbarButton'; import { ParseNodeOptions } from '../../parser/config/config'; -import { CollectionsStateMap } from '../../data_sources/model/collection_component/types'; +import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types'; export type DragMode = 'translate' | 'absolute' | ''; diff --git a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts similarity index 90% rename from packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts rename to packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts index 36756565a9..7a6a4a72e0 100644 --- a/packages/core/test/specs/data_sources/model/collection_component/CollectionComponent.ts +++ b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts @@ -3,8 +3,8 @@ import { DataVariableType } from '../../../../../src/data_sources/model/DataVari import { CollectionComponentType, CollectionVariableType, -} from '../../../../../src/data_sources/model/collection_component/constants'; -import { CollectionStateVariableType } from '../../../../../src/data_sources/model/collection_component/types'; +} from '../../../../../src/data_sources/model/data_collection/constants'; +import { DataCollectionStateVariableType } from '../../../../../src/data_sources/model/data_collection/types'; import EditorModel from '../../../../../src/editor/model/Editor'; import { filterObjectForSnapshot, setupTestEditor } from '../../../../common'; import { getSymbolMain } from '../../../../../src/dom_components/model/SymbolUtils'; @@ -130,19 +130,19 @@ describe('Collection component', () => { type: 'default', name: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, }, ], name: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, custom_property: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, }, @@ -205,7 +205,7 @@ describe('Collection component', () => { firstChild.set('name', { // @ts-ignore type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'age', }); expect(firstChild.get('name')).toBe('12'); @@ -223,7 +223,7 @@ describe('Collection component', () => { firstGrandchild.set('name', { // @ts-ignore type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'age', }); expect(firstGrandchild.get('name')).toBe('new_value_12'); @@ -286,7 +286,7 @@ describe('Collection component', () => { attributes: { name: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, }, @@ -295,7 +295,7 @@ describe('Collection component', () => { attributes: { name: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, }, @@ -356,7 +356,7 @@ describe('Collection component', () => { name: { // @ts-ignore type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'age', }, }); @@ -376,7 +376,7 @@ describe('Collection component', () => { name: { // @ts-ignore type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'age', }, }); @@ -435,7 +435,7 @@ describe('Collection component', () => { name: 'attribute_trait', value: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, }, @@ -444,7 +444,7 @@ describe('Collection component', () => { changeProp: true, value: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, }, @@ -486,18 +486,18 @@ describe('Collection component', () => { type: 'default', name: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, custom_prop: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentIndex, + variableType: DataCollectionStateVariableType.currentIndex, path: 'user', }, attributes: { name: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, }, @@ -506,7 +506,7 @@ describe('Collection component', () => { name: 'attribute_trait', value: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, }, @@ -515,7 +515,7 @@ describe('Collection component', () => { changeProp: true, value: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, }, @@ -552,7 +552,7 @@ describe('Collection component', () => { type: 'default', name: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentIndex, + variableType: DataCollectionStateVariableType.currentIndex, path: 'user', }, }; @@ -573,7 +573,7 @@ describe('Collection component', () => { type: 'default', name: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentIndex, + variableType: DataCollectionStateVariableType.currentIndex, path: 'user', }, }; @@ -597,12 +597,12 @@ describe('Collection component', () => { attribute_trait: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, name: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, }, components: [ @@ -611,18 +611,18 @@ describe('Collection component', () => { attribute_trait: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, name: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, }, name: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, custom_prop: { path: 'user', @@ -632,7 +632,7 @@ describe('Collection component', () => { property_trait: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, type: 'default', }, @@ -641,18 +641,18 @@ describe('Collection component', () => { attribute_trait: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, name: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, }, name: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, custom_prop: { path: 'user', @@ -662,7 +662,7 @@ describe('Collection component', () => { property_trait: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, type: 'default', }, @@ -670,7 +670,7 @@ describe('Collection component', () => { name: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, custom_prop: { path: 'user', @@ -680,7 +680,7 @@ describe('Collection component', () => { property_trait: { path: 'user', type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, }, type: 'default', }, @@ -766,7 +766,7 @@ describe('Collection component', () => { type: 'default', name: { type: CollectionVariableType, - variableType: CollectionStateVariableType.currentItem, + variableType: DataCollectionStateVariableType.currentItem, path: 'user', }, }, @@ -792,15 +792,15 @@ describe('Collection component', () => { describe('Diffirent Collection variable types', () => { const stateVariableTests = [ - { variableType: CollectionStateVariableType.currentIndex, expectedValues: [0, 1, 2] }, - { variableType: CollectionStateVariableType.startIndex, expectedValues: [0, 0, 0] }, - { variableType: CollectionStateVariableType.endIndex, expectedValues: [2, 2, 2] }, + { variableType: DataCollectionStateVariableType.currentIndex, expectedValues: [0, 1, 2] }, + { variableType: DataCollectionStateVariableType.startIndex, expectedValues: [0, 0, 0] }, + { variableType: DataCollectionStateVariableType.endIndex, expectedValues: [2, 2, 2] }, { - variableType: CollectionStateVariableType.collectionName, + variableType: DataCollectionStateVariableType.collectionName, expectedValues: ['my_collection', 'my_collection', 'my_collection'], }, - { variableType: CollectionStateVariableType.totalItems, expectedValues: [3, 3, 3] }, - { variableType: CollectionStateVariableType.remainingItems, expectedValues: [2, 1, 0] }, + { variableType: DataCollectionStateVariableType.totalItems, expectedValues: [3, 3, 3] }, + { variableType: DataCollectionStateVariableType.remainingItems, expectedValues: [2, 1, 0] }, ]; stateVariableTests.forEach(({ variableType, expectedValues }) => { diff --git a/packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap similarity index 100% rename from packages/core/test/specs/data_sources/model/collection_component/__snapshots__/CollectionComponent.ts.snap rename to packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap diff --git a/packages/core/test/specs/dom_components/model/ComponentTypes.ts b/packages/core/test/specs/dom_components/model/ComponentTypes.ts index f074d4b3b8..63db8a0347 100644 --- a/packages/core/test/specs/dom_components/model/ComponentTypes.ts +++ b/packages/core/test/specs/dom_components/model/ComponentTypes.ts @@ -1,5 +1,3 @@ -import { CollectionVariableType } from '../../../../src/data_sources/model/collection_component/constants'; -import { CollectionStateVariableType } from '../../../../src/data_sources/model/collection_component/types'; import Editor from '../../../../src/editor'; describe('Component Types', () => { @@ -98,10 +96,3 @@ describe('Component Types', () => { expect(cmp.components().at(0).is('svg-in')).toBe(true); }); }); - -export type CollectionVariableDefinition = { - type: typeof CollectionVariableType; - variableType: CollectionStateVariableType; - collectionName?: string; - path?: string; -}; From 19007fabf0a0e7ed4e09c076b259cce3a8eb266c Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 16 Jan 2025 12:39:07 +0200 Subject: [PATCH 67/87] Add collection variable component --- .../model/DataVariableListenerManager.ts | 12 ++-- .../ComponentDataCollection.ts | 1 + .../ComponentDataCollectionVariable.ts | 53 ++++++++++++++ .../ComponentDataCollectionVariableView.ts | 24 +++++++ ...nVariable.ts => DataCollectionVariable.ts} | 42 +++++++++-- packages/core/src/data_sources/model/utils.ts | 5 +- packages/core/src/data_sources/types.ts | 4 +- packages/core/src/dom_components/index.ts | 9 ++- .../ComponentDataCollection.ts | 2 +- .../ComponentDataCollectionVariable.ts | 69 +++++++++++++++++++ 10 files changed, 203 insertions(+), 18 deletions(-) create mode 100644 packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts create mode 100644 packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariableView.ts rename packages/core/src/data_sources/model/data_collection/{CollectionVariable.ts => DataCollectionVariable.ts} (69%) create mode 100644 packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts diff --git a/packages/core/src/data_sources/model/DataVariableListenerManager.ts b/packages/core/src/data_sources/model/DataVariableListenerManager.ts index e090e962b5..62e165a5fd 100644 --- a/packages/core/src/data_sources/model/DataVariableListenerManager.ts +++ b/packages/core/src/data_sources/model/DataVariableListenerManager.ts @@ -7,7 +7,7 @@ import { DynamicValue } from '../types'; import { DataCondition, ConditionalVariableType } from './conditional_variables/DataCondition'; import ComponentDataVariable from './ComponentDataVariable'; import { CollectionVariableType } from './data_collection/constants'; -import CollectionVariable from './data_collection/CollectionVariable'; +import DataCollectionVariable from './data_collection/DataCollectionVariable'; export interface DynamicVariableListenerManagerOptions { em: EditorModel; @@ -44,11 +44,7 @@ export default class DynamicVariableListenerManager { let dataListeners: DataVariableListener[] = []; switch (type) { case CollectionVariableType: - const collectionVariable = dynamicVariable as CollectionVariable; - if (collectionVariable.hasDynamicValue()) { - dataListeners = this.listenToDataVariable(collectionVariable.dataVariable!, em); - } - + dataListeners = this.listenToDataCollectionVariable(dynamicVariable as DataCollectionVariable); break; case DataVariableType: dataListeners = this.listenToDataVariable(dynamicVariable as DataVariable | ComponentDataVariable, em); @@ -86,6 +82,10 @@ export default class DynamicVariableListenerManager { return dataListeners; } + private listenToDataCollectionVariable(dataVariable: DataCollectionVariable) { + return [{ obj: dataVariable, event: 'change:value' }]; + } + private removeListeners() { this.dataListeners.forEach((ls) => this.model.stopListening(ls.obj, ls.event, this.onChange)); this.dataListeners = []; diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts index a1d8d18936..0d6a925c9a 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts @@ -95,6 +95,7 @@ export default class ComponentDataCollection extends Component { }, { em }, ); + new DynamicVariableListenerManager({ em: em, dataVariable, diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts new file mode 100644 index 0000000000..484aa9fd30 --- /dev/null +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts @@ -0,0 +1,53 @@ +import Component, { keyCollectionsStateMap, keySymbolOvrd } from '../../../dom_components/model/Component'; +import { ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; +import { toLowerCase } from '../../../utils/mixins'; +import DataCollectionVariable from './DataCollectionVariable'; +import { CollectionVariableType, keyInnerCollectionState } from './constants'; +import { DataCollectionStateMap, DataCollectionVariableDefinition } from './types'; + +export default class ComponentDataCollectionVariable extends Component { + datacollectionVariable: DataCollectionVariable; + + get defaults() { + return { + // @ts-ignore + ...super.defaults, + type: CollectionVariableType, + variableType: '', + path: '', + collectionName: keyInnerCollectionState, + }; + } + + constructor(props: DataCollectionVariableDefinition & ComponentProperties, opt: ComponentOptions) { + super(props, opt); + const em = opt.em; + const { type, variableType, path, collectionName } = props; + + this.datacollectionVariable = new DataCollectionVariable( + { type, variableType, path, collectionName }, + { + em, + collectionsStateMap: this.get(keyCollectionsStateMap), + }, + ); + + this.listenTo(this, `change:${keyCollectionsStateMap}`, this.handleCollectionsMapStateUpdate); + } + + private handleCollectionsMapStateUpdate(m: any, v: DataCollectionStateMap, opts = {}) { + this.datacollectionVariable.updateCollectionsStateMap(v); + } + + getDataValue() { + return this.datacollectionVariable.getDataValue(); + } + + getInnerHTML() { + return this.getDataValue(); + } + + static isComponent(el: HTMLElement) { + return toLowerCase(el.tagName) === CollectionVariableType; + } +} diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariableView.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariableView.ts new file mode 100644 index 0000000000..fa4bab47d6 --- /dev/null +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariableView.ts @@ -0,0 +1,24 @@ +import ComponentView from '../../../dom_components/view/ComponentView'; +import DynamicVariableListenerManager from '../DataVariableListenerManager'; +import ComponentDataCollectionVariable from './ComponentDataCollectionVariable'; + +export default class ComponentDataCollectionVariableView extends ComponentView { + collectionVariableListener?: DynamicVariableListenerManager; + + initialize(opt = {}) { + super.initialize(opt); + this.postRender(); + this.collectionVariableListener = new DynamicVariableListenerManager({ + em: this.em!, + dataVariable: this.model.datacollectionVariable, + updateValueFromDataVariable: this.postRender, + }); + } + + postRender() { + const { model, el } = this; + el.innerHTML = model.datacollectionVariable.getDataValue(); + + super.postRender(); + } +} diff --git a/packages/core/src/data_sources/model/data_collection/CollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts similarity index 69% rename from packages/core/src/data_sources/model/data_collection/CollectionVariable.ts rename to packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts index 38dc6d429a..7e8073ecd2 100644 --- a/packages/core/src/data_sources/model/data_collection/CollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts @@ -2,16 +2,31 @@ import { DataCollectionVariableDefinition } from './types'; import { Model } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; import DataVariable, { DataVariableType } from '../DataVariable'; -import { keyInnerCollectionState } from './constants'; +import { CollectionVariableType, keyInnerCollectionState } from './constants'; import { DataCollectionState, DataCollectionStateMap } from './types'; +import DynamicVariableListenerManager from '../DataVariableListenerManager'; +type ResolvedDataCollectionVariable = DataCollectionVariableDefinition & { + value?: any; +}; -export default class CollectionVariable extends Model { +export default class DataCollectionVariable extends Model { em: EditorModel; collectionsStateMap: DataCollectionStateMap; dataVariable?: DataVariable; + dynamicValueListener?: DynamicVariableListenerManager; + + defaults(): Partial { + return { + type: CollectionVariableType, + variableType: undefined, + path: '', + collectionName: keyInnerCollectionState, + value: undefined, + }; + } constructor( - attrs: DataCollectionVariableDefinition, + attrs: ResolvedDataCollectionVariable, options: { em: EditorModel; collectionsStateMap: DataCollectionStateMap; @@ -20,7 +35,6 @@ export default class CollectionVariable extends Model { componentTypes: ComponentStackItem[] = [ + { + id: CollectionVariableType, + model: ComponentDataCollectionVariable, + view: ComponentDataCollectionVariableView, + }, { id: CollectionComponentType, model: ComponentDataCollection, diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts index 7a6a4a72e0..cf944bef3e 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts @@ -6,7 +6,7 @@ import { } from '../../../../../src/data_sources/model/data_collection/constants'; import { DataCollectionStateVariableType } from '../../../../../src/data_sources/model/data_collection/types'; import EditorModel from '../../../../../src/editor/model/Editor'; -import { filterObjectForSnapshot, setupTestEditor } from '../../../../common'; +import { setupTestEditor } from '../../../../common'; import { getSymbolMain } from '../../../../../src/dom_components/model/SymbolUtils'; import { ProjectData } from '../../../../../src/storage_manager'; diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts new file mode 100644 index 0000000000..7c3bc161ca --- /dev/null +++ b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts @@ -0,0 +1,69 @@ +import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src'; +import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; +import { + CollectionComponentType, + CollectionVariableType, +} from '../../../../../src/data_sources/model/data_collection/constants'; +import { DataCollectionStateVariableType } from '../../../../../src/data_sources/model/data_collection/types'; +import EditorModel from '../../../../../src/editor/model/Editor'; +import { setupTestEditor } from '../../../../common'; + +describe('Collection component', () => { + let em: EditorModel; + let editor: Editor; + let dsm: DataSourceManager; + let dataSource: DataSource; + let wrapper: Component; + let firstRecord: DataRecord; + let secondRecord: DataRecord; + + beforeEach(() => { + ({ em, editor, dsm } = setupTestEditor()); + wrapper = em.getWrapper()!; + dataSource = dsm.add({ + id: 'my_data_source_id', + records: [ + { id: 'user1', user: 'user1', age: '12' }, + { id: 'user2', user: 'user2', age: '14' }, + { id: 'user3', user: 'user3', age: '16' }, + ], + }); + + firstRecord = dataSource.getRecord('user1')!; + secondRecord = dataSource.getRecord('user2')!; + }); + + afterEach(() => { + em.destroy(); + }); + + test('Collection variable components', async () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDefinition: { + block: { + type: 'default', + components: [ + { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + path: 'user', + }, + ], + }, + config: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + const firstGrandchild = cmp.components().at(0).components().at(0); + expect(firstGrandchild.getInnerHTML()).toContain('user1'); + + const secondGrandchild = cmp.components().at(1).components().at(0); + expect(secondGrandchild.getInnerHTML()).toContain('user2'); + }); +}); From fedde43a3b6eac5f71ebaa6b00a3f90b52dcdea4 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 16 Jan 2025 12:52:53 +0200 Subject: [PATCH 68/87] Exclude "components" property from being dynamic --- .../core/src/dom_components/model/Component.ts | 14 +++++++------- .../model/ComponentDynamicValueWatcher.ts | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 08421aff2b..f111e26f13 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -57,9 +57,9 @@ import { ComponentDynamicValueWatcher } from './ComponentDynamicValueWatcher'; import { DynamicWatchersOptions } from './DynamicValueWatcher'; import { keyIsCollectionItem } from '../../data_sources/model/data_collection/constants'; -export interface IComponent extends ExtractMethods {} -export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {} -export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions {} +export interface IComponent extends ExtractMethods { } +export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions { } +export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions { } const escapeRegExp = (str: string) => { return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); @@ -229,12 +229,12 @@ export default class Component extends StyleableModel { 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) @@ -242,12 +242,12 @@ export default class Component extends StyleableModel { * @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; diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 02464764e9..bbee5e8383 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -50,14 +50,23 @@ export class ComponentDynamicValueWatcher extends Model { } addProps(props: ObjectAny, options: DynamicWatchersOptions = {}) { - const evaluatedProps = this.propertyWatcher.addDynamicValues(props, options); + const excludedFromEvaluation = ['components']; + + const evaluatedProps = Object.fromEntries( + Object.entries(props).map(([key, value]) => + excludedFromEvaluation.includes(key) + ? [key, value] // Return excluded keys as they are + : [key, this.propertyWatcher.addDynamicValues({ [key]: value }, options)[key]] + ) + ); + if (props.attributes) { const evaluatedAttributes = this.attributeWatcher.setDynamicValues(props.attributes, options); evaluatedProps['attributes'] = evaluatedAttributes; } - const skipOverridUpdates = options.skipWatcherUpdates || options.fromDataSource; - if (!skipOverridUpdates) { + const skipOverrideUpdates = options.skipWatcherUpdates || options.fromDataSource; + if (!skipOverrideUpdates) { this.updateSymbolOverride(); } From 0aeef2d0337f1d4bcb2eacb527c670f4125b4b72 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 16 Jan 2025 12:59:58 +0200 Subject: [PATCH 69/87] Improve DataCollectionVariable serialization --- .../ComponentDataCollectionVariable.ts | 6 +++--- .../data_collection/DataCollectionVariable.ts | 13 +++++++++++-- .../core/src/dom_components/model/Component.ts | 14 +++++++------- .../model/ComponentDynamicValueWatcher.ts | 4 ++-- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts index 484aa9fd30..15a55a191f 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts @@ -13,9 +13,9 @@ export default class ComponentDataCollectionVariable extends Component { // @ts-ignore ...super.defaults, type: CollectionVariableType, - variableType: '', - path: '', - collectionName: keyInnerCollectionState, + collectionName: undefined, + variableType: undefined, + path: undefined, }; } diff --git a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts index 7e8073ecd2..c2d8a8b611 100644 --- a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts @@ -18,9 +18,9 @@ export default class DataCollectionVariable extends Model { return { type: CollectionVariableType, + collectionName: undefined, variableType: undefined, - path: '', - collectionName: keyInnerCollectionState, + path: undefined, value: undefined, }; } @@ -86,8 +86,17 @@ export default class DataCollectionVariable extends Model { } -export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions { } -export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions { } +export interface IComponent extends ExtractMethods {} +export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {} +export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions {} const escapeRegExp = (str: string) => { return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'); @@ -229,12 +229,12 @@ export default class Component extends StyleableModel { 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) @@ -242,12 +242,12 @@ export default class Component extends StyleableModel { * @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; diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index bbee5e8383..1eefa87e25 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -56,8 +56,8 @@ export class ComponentDynamicValueWatcher extends Model { Object.entries(props).map(([key, value]) => excludedFromEvaluation.includes(key) ? [key, value] // Return excluded keys as they are - : [key, this.propertyWatcher.addDynamicValues({ [key]: value }, options)[key]] - ) + : [key, this.propertyWatcher.addDynamicValues({ [key]: value }, options)[key]], + ), ); if (props.attributes) { From ea01b761a110499386340481612dba7179af2c15 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 16 Jan 2025 13:07:30 +0200 Subject: [PATCH 70/87] Fix logic for updating data collection variable --- .../model/data_collection/DataCollectionVariable.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts index c2d8a8b611..223264584c 100644 --- a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts @@ -70,7 +70,9 @@ export default class DataCollectionVariable extends Model { + this.set('value', this.dataVariable?.getDataValue()); + }, }); } From c3cd2a0411ba38123416094ed24819929ee79c07 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 16 Jan 2025 13:16:17 +0200 Subject: [PATCH 71/87] Fix tests --- packages/core/src/dom_components/model/Component.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 08421aff2b..33a10cfa41 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -306,8 +306,11 @@ export default class Component extends StyleableModel { this.opt = { ...opt }; this.em = em!; this.config = opt.config || {}; - this.addAttributes({ + const dynamicAttributes = this.componentDVListener.getDynamicAttributesDefs(); + this.setAttributes({ ...(result(this, 'defaults').attributes || {}), + ...(this.get('attributes') || {}), + ...dynamicAttributes, }); this.ccid = Component.createId(this, opt); this.preInit(); From 2ad06d1eb3c70741b25338063466ec7c091404fe Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 16 Jan 2025 13:28:19 +0200 Subject: [PATCH 72/87] Change collection definition properties --- .../ComponentDataCollection.ts | 54 +++++++----- .../ComponentDataCollectionVariable.ts | 6 +- .../data_collection/DataCollectionVariable.ts | 20 ++--- .../model/data_collection/constants.ts | 2 +- .../model/data_collection/types.ts | 12 +-- .../ComponentDataCollection.ts | 68 +++++++------- .../ComponentDataCollectionVariable.ts | 6 +- .../ComponentDataCollection.ts.snap | 88 +++++++++---------- 8 files changed, 133 insertions(+), 123 deletions(-) diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts index 0d6a925c9a..eaea887cb5 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts @@ -35,8 +35,8 @@ export default class ComponentDataCollection extends Component { opt, ); - const collectionDefinition = props[keyCollectionDefinition]; - if (!collectionDefinition) { + const collectionDef = props[keyCollectionDefinition]; + if (!collectionDef) { em.logError('missing collection definition'); return cmp; @@ -44,10 +44,10 @@ export default class ComponentDataCollection extends Component { const parentCollectionStateMap = (props[keyCollectionsStateMap] || {}) as DataCollectionStateMap; - const components: Component[] = getCollectionItems(em, collectionDefinition, parentCollectionStateMap, opt); + const components: Component[] = getCollectionItems(em, collectionDef, parentCollectionStateMap, opt); if (this.hasDynamicDataSource()) { - this.watchDataSource(em, collectionDefinition, parentCollectionStateMap, opt); + this.watchDataSource(em, collectionDef, parentCollectionStateMap, opt); } cmp.components(components); @@ -59,7 +59,7 @@ export default class ComponentDataCollection extends Component { } hasDynamicDataSource() { - const dataSource = this.get(keyCollectionDefinition).config.dataSource; + const dataSource = this.get(keyCollectionDefinition).collectionConfig.dataSource; return typeof dataSource === 'object' && dataSource.type === DataVariableType; } @@ -67,7 +67,7 @@ export default class ComponentDataCollection extends Component { const json = super.toJSON(opts) as ComponentDataCollectionDefinition; const firstChild = this.getBlockDefinition(); - json[keyCollectionDefinition].block = firstChild; + json[keyCollectionDefinition].componentDef = firstChild; delete json.components; delete json.droppable; @@ -83,11 +83,11 @@ export default class ComponentDataCollection extends Component { private watchDataSource( em: EditorModel, - collectionDefinition: DataCollectionDefinition, + collectionDef: DataCollectionDefinition, parentCollectionStateMap: DataCollectionStateMap, opt: ComponentOptions, ) { - const path = this.get(keyCollectionDefinition).config.dataSource?.path; + const path = this.get(keyCollectionDefinition).collectionConfig.dataSource?.path; const dataVariable = new DataVariable( { type: DataVariableType, @@ -100,7 +100,7 @@ export default class ComponentDataCollection extends Component { em: em, dataVariable, updateValueFromDataVariable: () => { - const collectionItems = getCollectionItems(em, collectionDefinition, parentCollectionStateMap, opt); + const collectionItems = getCollectionItems(em, collectionDef, parentCollectionStateMap, opt); this.components(collectionItems); }, }); @@ -109,33 +109,43 @@ export default class ComponentDataCollection extends Component { function getCollectionItems( em: EditorModel, - collectionDefinition: DataCollectionDefinition, + collectionDef: DataCollectionDefinition, parentCollectionStateMap: DataCollectionStateMap, opt: ComponentOptions, ) { - const { collectionName, block, config } = collectionDefinition; - if (!block) { - em.logError('The "block" property is required in the collection definition.'); + const { componentDef, collectionConfig } = collectionDef; + if (!collectionConfig) { + em.logError('The "collectionConfig" property is required in the collection definition.'); return []; } - if (!config?.dataSource) { - em.logError('The "config.dataSource" property is required in the collection definition.'); + if (!componentDef) { + em.logError('The "componentDef" property is required in the collection definition.'); return []; } + if (!collectionConfig?.dataSource) { + em.logError('The "collectionConfig.dataSource" property is required in the collection definition.'); + return []; + } + + const collectionId = collectionConfig.collectionId; + const components: Component[] = []; - let items: any[] = getDataSourceItems(config.dataSource, em); - const startIndex = Math.max(0, config.startIndex || 0); - const endIndex = Math.min(items.length - 1, config.endIndex !== undefined ? config.endIndex : Number.MAX_VALUE); + let items: any[] = getDataSourceItems(collectionConfig.dataSource, em); + const startIndex = Math.max(0, collectionConfig.startIndex || 0); + const endIndex = Math.min( + items.length - 1, + collectionConfig.endIndex !== undefined ? collectionConfig.endIndex : Number.MAX_VALUE, + ); const totalItems = endIndex - startIndex + 1; let blockSymbolMain: Component; for (let index = startIndex; index <= endIndex; index++) { const item = items[index]; const collectionState: DataCollectionState = { - collectionName, + collectionId, currentIndex: index, currentItem: item, startIndex: startIndex, @@ -146,18 +156,18 @@ function getCollectionItems( const collectionsStateMap: DataCollectionStateMap = { ...parentCollectionStateMap, - ...(collectionName && { [collectionName]: collectionState }), + ...(collectionId && { [collectionId]: collectionState }), [keyInnerCollectionState]: collectionState, }; if (index === startIndex) { // @ts-ignore - const type = em.Components.getType(block?.type || 'default'); + const type = em.Components.getType(componentDef?.type || 'default'); const model = type.model; blockSymbolMain = new model( { - ...block, + ...componentDef, [keyCollectionsStateMap]: collectionsStateMap, [keyIsCollectionItem]: true, draggable: false, diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts index 15a55a191f..14e2e6a43f 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts @@ -13,7 +13,7 @@ export default class ComponentDataCollectionVariable extends Component { // @ts-ignore ...super.defaults, type: CollectionVariableType, - collectionName: undefined, + collectionId: undefined, variableType: undefined, path: undefined, }; @@ -22,10 +22,10 @@ export default class ComponentDataCollectionVariable extends Component { constructor(props: DataCollectionVariableDefinition & ComponentProperties, opt: ComponentOptions) { super(props, opt); const em = opt.em; - const { type, variableType, path, collectionName } = props; + const { type, variableType, path, collectionId } = props; this.datacollectionVariable = new DataCollectionVariable( - { type, variableType, path, collectionName }, + { type, variableType, path, collectionId }, { em, collectionsStateMap: this.get(keyCollectionsStateMap), diff --git a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts index 223264584c..6a9a64d8f0 100644 --- a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts @@ -18,7 +18,7 @@ export default class DataCollectionVariable extends Model { return { type: CollectionVariableType, - collectionName: undefined, + collectionId: undefined, variableType: undefined, path: undefined, value: undefined, @@ -95,7 +95,7 @@ export default class DataCollectionVariable extends Model { test('Collection component should be undroppable', () => { const cmp = wrapper.components({ type: CollectionComponentType, - collectionDefinition: { - block: { + collectionDef: { + componentDef: { type: 'default', }, - config: { + collectionConfig: { dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -61,11 +61,11 @@ describe('Collection component', () => { test('Collection items should be undraggable', () => { const cmp = wrapper.components({ type: CollectionComponentType, - collectionDefinition: { - block: { + collectionDef: { + componentDef: { type: 'default', }, - config: { + collectionConfig: { dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -82,8 +82,8 @@ describe('Collection component', () => { test('Collection items should be symbols', () => { const cmp = wrapper.components({ type: CollectionComponentType, - collectionDefinition: { - block: { + collectionDef: { + componentDef: { type: 'default', components: [ { @@ -91,7 +91,7 @@ describe('Collection component', () => { }, ], }, - config: { + collectionConfig: { dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -122,8 +122,8 @@ describe('Collection component', () => { beforeEach(() => { cmp = wrapper.components({ type: CollectionComponentType, - collectionDefinition: { - block: { + collectionDef: { + componentDef: { type: 'default', components: [ { @@ -146,7 +146,7 @@ describe('Collection component', () => { path: 'user', }, }, - config: { + collectionConfig: { dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -277,8 +277,8 @@ describe('Collection component', () => { beforeEach(() => { cmp = wrapper.components({ type: CollectionComponentType, - collectionDefinition: { - block: { + collectionDef: { + componentDef: { type: 'default', components: [ { @@ -300,7 +300,7 @@ describe('Collection component', () => { }, }, }, - config: { + collectionConfig: { dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -427,8 +427,8 @@ describe('Collection component', () => { test('Traits', () => { const cmp = wrapper.components({ type: CollectionComponentType, - collectionDefinition: { - block: { + collectionDef: { + componentDef: { type: 'default', traits: [ { @@ -450,7 +450,7 @@ describe('Collection component', () => { }, ], }, - config: { + collectionConfig: { dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -524,13 +524,13 @@ describe('Collection component', () => { const collectionComponentDefinition = { type: CollectionComponentType, - collectionDefinition: { - collectionName: 'my_collection', - block: { + collectionDef: { + componentDef: { ...cmpDefinition, components: [cmpDefinition, cmpDefinition], }, - config: { + collectionConfig: { + collectionId: 'my_collection', startIndex: 0, endIndex: 1, dataSource: { @@ -591,8 +591,8 @@ describe('Collection component', () => { component: { components: [ { - collectionDefinition: { - block: { + collectionDef: { + componentDef: { attributes: { attribute_trait: { path: 'user', @@ -684,8 +684,8 @@ describe('Collection component', () => { }, type: 'default', }, - collectionName: 'my_collection', - config: { + collectionConfig: { + collectionId: 'my_collection', dataSource: { path: 'my_data_source_id', type: DataVariableType, @@ -761,8 +761,8 @@ describe('Collection component', () => { test('Collection with start and end indexes', () => { const cmp = wrapper.components({ type: CollectionComponentType, - collectionDefinition: { - block: { + collectionDef: { + componentDef: { type: 'default', name: { type: CollectionVariableType, @@ -770,7 +770,7 @@ describe('Collection component', () => { path: 'user', }, }, - config: { + collectionConfig: { startIndex: 1, endIndex: 2, dataSource: { @@ -796,7 +796,7 @@ describe('Collection component', () => { { variableType: DataCollectionStateVariableType.startIndex, expectedValues: [0, 0, 0] }, { variableType: DataCollectionStateVariableType.endIndex, expectedValues: [2, 2, 2] }, { - variableType: DataCollectionStateVariableType.collectionName, + variableType: DataCollectionStateVariableType.collectionId, expectedValues: ['my_collection', 'my_collection', 'my_collection'], }, { variableType: DataCollectionStateVariableType.totalItems, expectedValues: [3, 3, 3] }, @@ -807,9 +807,8 @@ describe('Collection component', () => { test(`Variable type: ${variableType}`, () => { const cmp = wrapper.components({ type: CollectionComponentType, - collectionDefinition: { - collectionName: 'my_collection', - block: { + collectionDef: { + componentDef: { type: 'default', name: { type: CollectionVariableType, @@ -839,7 +838,8 @@ describe('Collection component', () => { }, ], }, - config: { + collectionConfig: { + collectionId: 'my_collection', dataSource: { type: DataVariableType, path: 'my_data_source_id', diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts index 7c3bc161ca..e032416cd0 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts +++ b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts @@ -40,8 +40,8 @@ describe('Collection component', () => { test('Collection variable components', async () => { const cmp = wrapper.components({ type: CollectionComponentType, - collectionDefinition: { - block: { + collectionDef: { + componentDef: { type: 'default', components: [ { @@ -51,7 +51,7 @@ describe('Collection component', () => { }, ], }, - config: { + collectionConfig: { dataSource: { type: DataVariableType, path: 'my_data_source_id', diff --git a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap index 39905c5565..7fc6d5d71f 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap +++ b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap @@ -2,8 +2,17 @@ exports[`Collection component Serialization Saving: Collection with grandchildren 1`] = ` { - "collectionDefinition": { - "block": { + "collectionDef": { + "collectionConfig": { + "collectionId": "my_collection", + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "endIndex": 1, + "startIndex": 0, + }, + "componentDef": { "attributes": { "attribute_trait": { "path": "user", @@ -105,15 +114,6 @@ exports[`Collection component Serialization Saving: Collection with grandchildre }, "type": "default", }, - "collectionName": "my_collection", - "config": { - "dataSource": { - "path": "my_data_source_id", - "type": "data-variable", - }, - "endIndex": 1, - "startIndex": 0, - }, }, "type": "collection-component", } @@ -121,8 +121,17 @@ exports[`Collection component Serialization Saving: Collection with grandchildre exports[`Collection component Serialization Saving: Collection with no grandchildren 1`] = ` { - "collectionDefinition": { - "block": { + "collectionDef": { + "collectionConfig": { + "collectionId": "my_collection", + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "endIndex": 1, + "startIndex": 0, + }, + "componentDef": { "attributes": { "attribute_trait": { "path": "user", @@ -214,15 +223,6 @@ exports[`Collection component Serialization Saving: Collection with no grandchil }, "type": "default", }, - "collectionName": "my_collection", - "config": { - "dataSource": { - "path": "my_data_source_id", - "type": "data-variable", - }, - "endIndex": 1, - "startIndex": 0, - }, }, "type": "collection-component", } @@ -230,8 +230,17 @@ exports[`Collection component Serialization Saving: Collection with no grandchil exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with grandchildren 1`] = ` { - "collectionDefinition": { - "block": { + "collectionDef": { + "collectionConfig": { + "collectionId": "my_collection", + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "endIndex": 1, + "startIndex": 0, + }, + "componentDef": { "attributes": { "attribute_trait": { "path": "user", @@ -333,15 +342,6 @@ exports[`Collection component Serialization Serializion with Collection Variable }, "type": "default", }, - "collectionName": "my_collection", - "config": { - "dataSource": { - "path": "my_data_source_id", - "type": "data-variable", - }, - "endIndex": 1, - "startIndex": 0, - }, }, "type": "collection-component", } @@ -349,8 +349,17 @@ exports[`Collection component Serialization Serializion with Collection Variable exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with no grandchildren 1`] = ` { - "collectionDefinition": { - "block": { + "collectionDef": { + "collectionConfig": { + "collectionId": "my_collection", + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "endIndex": 1, + "startIndex": 0, + }, + "componentDef": { "attributes": { "attribute_trait": { "path": "user", @@ -442,15 +451,6 @@ exports[`Collection component Serialization Serializion with Collection Variable }, "type": "default", }, - "collectionName": "my_collection", - "config": { - "dataSource": { - "path": "my_data_source_id", - "type": "data-variable", - }, - "endIndex": 1, - "startIndex": 0, - }, }, "type": "collection-component", } From 4297f123dd4c3e8da1f2c42a5f9dee2cdca5453e Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 16 Jan 2025 13:51:51 +0200 Subject: [PATCH 73/87] Add more tests for Collection variable components --- .../ComponentDataCollectionVariableView.ts | 2 + .../ComponentDataCollectionVariable.ts | 187 +++++++++++++++++- .../ComponentDataCollectionVariable.ts.snap | 135 +++++++++++++ 3 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariableView.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariableView.ts index fa4bab47d6..618cdb283d 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariableView.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariableView.ts @@ -8,6 +8,7 @@ export default class ComponentDataCollectionVariableView extends ComponentView { +describe('Collection variable components', () => { let em: EditorModel; let editor: Editor; let dsm: DataSourceManager; @@ -37,7 +38,7 @@ describe('Collection component', () => { em.destroy(); }); - test('Collection variable components', async () => { + test('Gets the correct static value', async () => { const cmp = wrapper.components({ type: CollectionComponentType, collectionDef: { @@ -66,4 +67,186 @@ describe('Collection component', () => { const secondGrandchild = cmp.components().at(1).components().at(0); expect(secondGrandchild.getInnerHTML()).toContain('user2'); }); + + test('Watches collection variable changes', async () => { + const cmp = wrapper.components({ + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: 'default', + components: { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + path: 'user', + }, + }, + collectionConfig: { + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + firstRecord.set('user', 'new_correct_value'); + + const firstGrandchild = cmp.components().at(0).components().at(0); + expect(firstGrandchild.getInnerHTML()).toContain('new_correct_value'); + + const secondGrandchild = cmp.components().at(1).components().at(0); + expect(secondGrandchild.getInnerHTML()).toContain('user2'); + }); + + describe('Serialization', () => { + let cmp: Component; + + beforeEach(() => { + const variableCmpDef = { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + path: 'user', + } + + const collectionComponentDefinition = { + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: 'default', + components: [ + { + type: 'default', + }, + variableCmpDef, + ], + }, + collectionConfig: { + collectionId: 'my_collection', + startIndex: 0, + endIndex: 2, + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + }; + + cmp = wrapper.components(collectionComponentDefinition)[0]; + }); + + test('Serializion to JSON', () => { + expect(cmp.toJSON()).toMatchSnapshot(`Collection with collection variable component ( no grandchildren )`); + + const firstChild = cmp.components().at(0); + const newChildDefinition = { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentIndex, + path: 'user', + }; + firstChild.components().at(0).components(newChildDefinition); + expect(cmp.toJSON()).toMatchSnapshot(`Collection with collection variable component ( with grandchildren )`); + }); + + test('Saving', () => { + const projectData = editor.getProjectData(); + const page = projectData.pages[0]; + const frame = page.frames[0]; + const component = frame.component.components[0]; + + expect(component).toMatchSnapshot(`Collection with collection variable component ( no grandchildren )`); + + const firstChild = cmp.components().at(0); + const newChildDefinition = { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentIndex, + path: 'user', + }; + + firstChild.components().at(0).components(newChildDefinition); + expect(cmp.toJSON()).toMatchSnapshot(`Collection with collection variable component ( with grandchildren )`); + }); + + test('Loading', () => { + const componentProjectData: ProjectData = { + assets: [], + pages: [ + { + frames: [ + { + component: { + components: [ + { + collectionDef: { + componentDef: { + type: 'default', + components: [ + { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + path: 'user', + }, + ], + }, + collectionConfig: { + collectionId: 'my_collection', + dataSource: { + path: 'my_data_source_id', + type: DataVariableType, + }, + endIndex: 1, + startIndex: 0, + }, + }, + type: 'collection-component', + }, + ], + docEl: { + tagName: 'html', + }, + head: { + type: 'head', + }, + stylable: [ + 'background', + 'background-color', + 'background-image', + 'background-repeat', + 'background-attachment', + 'background-position', + 'background-size', + ], + type: 'wrapper', + }, + id: 'frameid', + }, + ], + id: 'pageid', + type: 'main', + }, + ], + styles: [], + symbols: [], + dataSources: [dataSource], + }; + editor.loadProjectData(componentProjectData); + + const components = editor.getComponents(); + const component = components.models[0]; + const firstChild = component.components().at(0); + const firstGrandchild = firstChild.components().at(0); + const secondChild = component.components().at(1); + const secondGrandchild = secondChild.components().at(0); + + expect(firstGrandchild.getInnerHTML()).toBe('user1'); + expect(secondGrandchild.getInnerHTML()).toBe('user2'); + + firstRecord.set('user', 'new_user1_value'); + expect(firstGrandchild.getInnerHTML()).toBe('new_user1_value'); + expect(secondGrandchild.getInnerHTML()).toBe('user2'); + + secondRecord.set('user', 'new_user2_value'); + expect(firstGrandchild.getInnerHTML()).toBe('new_user1_value'); + expect(secondGrandchild.getInnerHTML()).toBe('new_user2_value'); + }); + }); }); diff --git a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap new file mode 100644 index 0000000000..ba96ea6e86 --- /dev/null +++ b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap @@ -0,0 +1,135 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collection variable components Serialization Saving: Collection with collection variable component ( no grandchildren ) 1`] = ` +{ + "collectionDef": { + "collectionConfig": { + "collectionId": "my_collection", + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "endIndex": 2, + "startIndex": 0, + }, + "componentDef": { + "components": [ + { + "type": "default", + }, + { + "path": "user", + "type": "parent-collection-variable", + "variableType": "currentItem", + }, + ], + "type": "default", + }, + }, + "type": "collection-component", +} +`; + +exports[`Collection variable components Serialization Saving: Collection with collection variable component ( with grandchildren ) 1`] = ` +{ + "collectionDef": { + "collectionConfig": { + "collectionId": "my_collection", + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "endIndex": 2, + "startIndex": 0, + }, + "componentDef": { + "components": [ + { + "components": [ + { + "path": "user", + "type": "parent-collection-variable", + "variableType": "currentIndex", + }, + ], + "type": "default", + }, + { + "path": "user", + "type": "parent-collection-variable", + "variableType": "currentItem", + }, + ], + "type": "default", + }, + }, + "type": "collection-component", +} +`; + +exports[`Collection variable components Serialization Serializion to JSON: Collection with collection variable component ( no grandchildren ) 1`] = ` +{ + "collectionDef": { + "collectionConfig": { + "collectionId": "my_collection", + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "endIndex": 2, + "startIndex": 0, + }, + "componentDef": { + "components": [ + { + "type": "default", + }, + { + "path": "user", + "type": "parent-collection-variable", + "variableType": "currentItem", + }, + ], + "type": "default", + }, + }, + "type": "collection-component", +} +`; + +exports[`Collection variable components Serialization Serializion to JSON: Collection with collection variable component ( with grandchildren ) 1`] = ` +{ + "collectionDef": { + "collectionConfig": { + "collectionId": "my_collection", + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + "endIndex": 2, + "startIndex": 0, + }, + "componentDef": { + "components": [ + { + "components": [ + { + "path": "user", + "type": "parent-collection-variable", + "variableType": "currentIndex", + }, + ], + "type": "default", + }, + { + "path": "user", + "type": "parent-collection-variable", + "variableType": "currentItem", + }, + ], + "type": "default", + }, + }, + "type": "collection-component", +} +`; From cf774bb536fdd41bac5421b840506d79d62219fd Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 16 Jan 2025 14:20:29 +0200 Subject: [PATCH 74/87] Format --- .../model/data_collection/ComponentDataCollectionVariable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts index 0807c94bd1..8ee78f8b7d 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts +++ b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts @@ -105,7 +105,7 @@ describe('Collection variable components', () => { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, path: 'user', - } + }; const collectionComponentDefinition = { type: CollectionComponentType, From 0a15b69f1faa06e9075d07f371d021a76a3b7707 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Thu, 16 Jan 2025 14:24:32 +0200 Subject: [PATCH 75/87] Fix lint --- .../model/data_collection/ComponentDataCollection.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts index eaea887cb5..08b54e7a7f 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts @@ -199,11 +199,12 @@ function getDataSourceItems(dataSource: any, em: EditorModel) { case isArray(dataSource): items = dataSource; break; - case typeof dataSource === 'object' && dataSource instanceof DataSource: + case typeof dataSource === 'object' && dataSource instanceof DataSource: { const id = dataSource.get('id')!; items = listDataSourceVariables(id, em); break; - case typeof dataSource === 'object' && dataSource.type === DataVariableType: + } + case typeof dataSource === 'object' && dataSource.type === DataVariableType: { const isDataSourceId = dataSource.path.split('.').length === 1; if (isDataSourceId) { const id = dataSource.path; @@ -213,6 +214,7 @@ function getDataSourceItems(dataSource: any, em: EditorModel) { items = em.DataSources.getValue(dataSource.path, []); } break; + } default: } return items; From 9847e0c4982ba1438c011566ab981014d8fcb9d8 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 20 Jan 2025 01:43:28 +0200 Subject: [PATCH 76/87] Refactor collectionStateMap propagation logic --- .../ComponentDataCollection.ts | 47 ++++++++++++++++++- .../data_collection/DataCollectionVariable.ts | 1 - .../src/dom_components/model/Component.ts | 2 +- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts index 08b54e7a7f..daba285ef7 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts @@ -171,7 +171,6 @@ function getCollectionItems( [keyCollectionsStateMap]: collectionsStateMap, [keyIsCollectionItem]: true, draggable: false, - deepPropagate: [setCollectionStateMap(collectionsStateMap)], }, opt, ); @@ -179,6 +178,7 @@ function getCollectionItems( } blockSymbolMain!.set(keyCollectionsStateMap, collectionsStateMap); const instance = blockSymbolMain!.clone({ symbol: true }); + setCollectionStateMapAndPropagate(collectionsStateMap, collectionId)(instance); components.push(instance); } @@ -186,10 +186,53 @@ function getCollectionItems( return components; } +function setCollectionStateMapAndPropagate( + collectionsStateMap: DataCollectionStateMap, + collectionId: string | undefined, +) { + return (model: Component) => { + setCollectionStateMap(collectionsStateMap)(model); + + // Listener function for the 'add' event + const addListener = (component: Component) => { + setCollectionStateMapAndPropagate(collectionsStateMap, collectionId)(component); + }; + + // Generate a unique listener key + const listenerKey = `_hasAddListener${collectionId ? `_${collectionId}` : ''}`; + + // Add the 'add' listener if not already in the listeners array + if (!model.collectionStateListeners.includes(listenerKey)) { + model.listenTo(model.components(), 'add', addListener); + model.collectionStateListeners.push(listenerKey); + + // Add a 'remove' listener to clean up + model.listenTo(model.components(), 'remove', () => { + model.stopListening(model.components(), 'add', addListener); // Remove the 'add' listener + const index = model.collectionStateListeners.indexOf(listenerKey); + if (index > -1) { + model.collectionStateListeners.splice(index, 1); // Remove the listener key + } + }); + } + + // Recursively apply to all child components + model + .components() + ?.toArray() + .forEach((component: Component) => { + setCollectionStateMapAndPropagate(collectionsStateMap, collectionId)(component); + }); + }; +} + function setCollectionStateMap(collectionsStateMap: DataCollectionStateMap) { return (cmp: Component) => { cmp.set(keyIsCollectionItem, true); - cmp.set(keyCollectionsStateMap, collectionsStateMap); + cmp.set(keyCollectionsStateMap, { + ...cmp.get(keyCollectionsStateMap), + ...collectionsStateMap, + }); }; } diff --git a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts index 6a9a64d8f0..6ee2422f04 100644 --- a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts @@ -112,7 +112,6 @@ function resolveCollectionVariable( const collectionItem = collectionsStateMap[collectionId]; if (!collectionItem) { - em.logError(`Collection not found: ${collectionId}`); return ''; } diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 33a10cfa41..599eb59c68 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -266,6 +266,7 @@ export default class Component extends StyleableModel { collection!: Components; componentDVListener: ComponentDynamicValueWatcher; accumulatedPropagatedProps: DeepPropagationArray = []; + collectionStateListeners: string[] = []; constructor(props: ComponentProperties = {}, opt: ComponentOptions) { const componentDVListener = new ComponentDynamicValueWatcher(undefined, { @@ -316,7 +317,6 @@ export default class Component extends StyleableModel { this.preInit(); this.initClasses(); this.listenTo(this, `change:${keyCollectionsStateMap}`, this.handleCollectionsMapStateChange); - this.propagateDeeplyFromParent(); this.initComponents(); this.initTraits(); this.initToolbar(); From d293d5931044a8cc5175b6aabb3a1b3b7b8b6f6e Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 20 Jan 2025 02:15:41 +0200 Subject: [PATCH 77/87] Make collectionId a required field --- .../ComponentDataCollection.ts | 63 ++++++++++++------- .../ComponentDataCollectionVariable.ts | 2 +- .../data_collection/DataCollectionVariable.ts | 4 +- .../model/data_collection/constants.ts | 1 - .../model/data_collection/types.ts | 6 +- .../ComponentDataCollection.ts | 42 +++++++++++++ .../ComponentDataCollectionVariable.ts | 8 +++ .../ComponentDataCollectionVariable.ts.snap | 6 ++ 8 files changed, 104 insertions(+), 28 deletions(-) diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts index daba285ef7..0dff5b698d 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts @@ -1,7 +1,7 @@ import DataVariable, { DataVariableType } from '../DataVariable'; import { isArray } from 'underscore'; import Component from '../../../dom_components/model/Component'; -import { ComponentOptions } from '../../../dom_components/model/types'; +import { ComponentDefinition, ComponentOptions } from '../../../dom_components/model/types'; import { toLowerCase } from '../../../utils/mixins'; import DataSource from '../DataSource'; import { ObjectAny } from '../../../common'; @@ -9,16 +9,12 @@ import EditorModel from '../../../editor/model/Editor'; import { keyCollectionsStateMap } from '../../../dom_components/model/Component'; import { ComponentDataCollectionDefinition, + DataCollectionConfig, DataCollectionDefinition, DataCollectionState, DataCollectionStateMap, } from './types'; -import { - keyCollectionDefinition, - keyInnerCollectionState, - CollectionComponentType, - keyIsCollectionItem, -} from './constants'; +import { keyCollectionDefinition, CollectionComponentType, keyIsCollectionItem } from './constants'; import DynamicVariableListenerManager from '../DataVariableListenerManager'; export default class ComponentDataCollection extends Component { @@ -114,18 +110,8 @@ function getCollectionItems( opt: ComponentOptions, ) { const { componentDef, collectionConfig } = collectionDef; - if (!collectionConfig) { - em.logError('The "collectionConfig" property is required in the collection definition.'); - return []; - } - - if (!componentDef) { - em.logError('The "componentDef" property is required in the collection definition.'); - return []; - } - - if (!collectionConfig?.dataSource) { - em.logError('The "collectionConfig.dataSource" property is required in the collection definition.'); + const result = validateCollectionConfig(collectionConfig, componentDef, em); + if (!result) { return []; } @@ -154,10 +140,16 @@ function getCollectionItems( remainingItems: totalItems - (index + 1), }; + if (parentCollectionStateMap[collectionId]) { + em.logError( + `The collection ID "${collectionId}" already exists in the parent collection state. Overriding it is not allowed.`, + ); + return []; + } + const collectionsStateMap: DataCollectionStateMap = { ...parentCollectionStateMap, - ...(collectionId && { [collectionId]: collectionState }), - [keyInnerCollectionState]: collectionState, + [collectionId]: collectionState, }; if (index === startIndex) { @@ -226,6 +218,35 @@ function setCollectionStateMapAndPropagate( }; } +function logErrorIfMissing(property: any, propertyPath: string, em: EditorModel) { + if (!property) { + em.logError(`The "${propertyPath}" property is required in the collection definition.`); + return false; + } + return true; +} + +function validateCollectionConfig( + collectionConfig: DataCollectionConfig, + componentDef: ComponentDefinition, + em: EditorModel, +) { + const validations = [ + { property: collectionConfig, propertyPath: 'collectionConfig' }, + { property: componentDef, propertyPath: 'componentDef' }, + { property: collectionConfig?.collectionId, propertyPath: 'collectionConfig.collectionId' }, + { property: collectionConfig?.dataSource, propertyPath: 'collectionConfig.dataSource' }, + ]; + + for (const { property, propertyPath } of validations) { + if (!logErrorIfMissing(property, propertyPath, em)) { + return []; + } + } + + return true; +} + function setCollectionStateMap(collectionsStateMap: DataCollectionStateMap) { return (cmp: Component) => { cmp.set(keyIsCollectionItem, true); diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts index 14e2e6a43f..78fba3d5af 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts @@ -2,7 +2,7 @@ import Component, { keyCollectionsStateMap, keySymbolOvrd } from '../../../dom_c import { ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; import { toLowerCase } from '../../../utils/mixins'; import DataCollectionVariable from './DataCollectionVariable'; -import { CollectionVariableType, keyInnerCollectionState } from './constants'; +import { CollectionVariableType } from './constants'; import { DataCollectionStateMap, DataCollectionVariableDefinition } from './types'; export default class ComponentDataCollectionVariable extends Component { diff --git a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts index 6ee2422f04..4132c61c5c 100644 --- a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts @@ -2,7 +2,7 @@ import { DataCollectionVariableDefinition } from './types'; import { Model } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; import DataVariable, { DataVariableType } from '../DataVariable'; -import { CollectionVariableType, keyInnerCollectionState } from './constants'; +import { CollectionVariableType } from './constants'; import { DataCollectionState, DataCollectionStateMap } from './types'; import DynamicVariableListenerManager from '../DataVariableListenerManager'; type ResolvedDataCollectionVariable = DataCollectionVariableDefinition & { @@ -106,7 +106,7 @@ function resolveCollectionVariable( collectionsStateMap: DataCollectionStateMap, em: EditorModel, ) { - const { collectionId = keyInnerCollectionState, variableType, path } = collectionVariableDefinition; + const { collectionId, variableType, path } = collectionVariableDefinition; if (!collectionsStateMap) return; const collectionItem = collectionsStateMap[collectionId]; diff --git a/packages/core/src/data_sources/model/data_collection/constants.ts b/packages/core/src/data_sources/model/data_collection/constants.ts index 590635f5d5..e3193802ad 100644 --- a/packages/core/src/data_sources/model/data_collection/constants.ts +++ b/packages/core/src/data_sources/model/data_collection/constants.ts @@ -1,5 +1,4 @@ export const CollectionComponentType = 'collection-component'; export const keyCollectionDefinition = 'collectionDef'; -export const keyInnerCollectionState = 'innerCollectionState'; export const keyIsCollectionItem = '__is_collection_item'; export const CollectionVariableType = 'parent-collection-variable'; diff --git a/packages/core/src/data_sources/model/data_collection/types.ts b/packages/core/src/data_sources/model/data_collection/types.ts index 6246aeddde..a6793f59cc 100644 --- a/packages/core/src/data_sources/model/data_collection/types.ts +++ b/packages/core/src/data_sources/model/data_collection/types.ts @@ -5,7 +5,7 @@ import { DataVariableDefinition } from '../DataVariable'; export type DataCollectionDataSource = any[] | DataVariableDefinition | DataCollectionVariableDefinition; export interface DataCollectionConfig { - collectionId?: string; + collectionId: string; startIndex?: number; endIndex?: number; dataSource: DataCollectionDataSource; @@ -26,7 +26,7 @@ export interface DataCollectionState { [DataCollectionStateVariableType.startIndex]: number; [DataCollectionStateVariableType.currentItem]: any; [DataCollectionStateVariableType.endIndex]: number; - [DataCollectionStateVariableType.collectionId]?: string; + [DataCollectionStateVariableType.collectionId]: string; [DataCollectionStateVariableType.totalItems]: number; [DataCollectionStateVariableType.remainingItems]: number; } @@ -48,6 +48,6 @@ export interface DataCollectionDefinition { export type DataCollectionVariableDefinition = { type: typeof CollectionVariableType; variableType: DataCollectionStateVariableType; - collectionId?: string; + collectionId: string; path?: string; }; diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts index 3506399d45..8a6c05fdde 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts @@ -47,6 +47,7 @@ describe('Collection component', () => { type: 'default', }, collectionConfig: { + collectionId: 'my_collection', dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -66,6 +67,7 @@ describe('Collection component', () => { type: 'default', }, collectionConfig: { + collectionId: 'my_collection', dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -92,6 +94,7 @@ describe('Collection component', () => { ], }, collectionConfig: { + collectionId: 'my_collection', dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -131,6 +134,7 @@ describe('Collection component', () => { name: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, @@ -138,15 +142,18 @@ describe('Collection component', () => { name: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, custom_property: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, collectionConfig: { + collectionId: 'my_collection', dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -206,6 +213,7 @@ describe('Collection component', () => { // @ts-ignore type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'age', }); expect(firstChild.get('name')).toBe('12'); @@ -287,6 +295,7 @@ describe('Collection component', () => { name: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, @@ -296,11 +305,13 @@ describe('Collection component', () => { name: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, }, collectionConfig: { + collectionId: 'my_collection', dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -377,6 +388,7 @@ describe('Collection component', () => { // @ts-ignore type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'age', }, }); @@ -436,6 +448,7 @@ describe('Collection component', () => { value: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, @@ -445,12 +458,14 @@ describe('Collection component', () => { value: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, ], }, collectionConfig: { + collectionId: 'my_collection', dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -487,17 +502,20 @@ describe('Collection component', () => { name: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, custom_prop: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentIndex, + collectionId: 'my_collection', path: 'user', }, attributes: { name: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, @@ -507,6 +525,7 @@ describe('Collection component', () => { value: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, @@ -516,6 +535,7 @@ describe('Collection component', () => { value: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, @@ -553,6 +573,7 @@ describe('Collection component', () => { name: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentIndex, + collectionId: 'my_collection', path: 'user', }, }; @@ -574,6 +595,7 @@ describe('Collection component', () => { name: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentIndex, + collectionId: 'my_collection', path: 'user', }, }; @@ -602,6 +624,7 @@ describe('Collection component', () => { name: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, }, @@ -611,27 +634,32 @@ describe('Collection component', () => { attribute_trait: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, name: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, }, name: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, custom_prop: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: 'currentIndex', }, property_trait: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, type: 'default', @@ -641,27 +669,32 @@ describe('Collection component', () => { attribute_trait: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, name: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, }, name: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, custom_prop: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: 'currentIndex', }, property_trait: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, type: 'default', @@ -670,16 +703,19 @@ describe('Collection component', () => { name: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, custom_prop: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: 'currentIndex', }, property_trait: { path: 'user', type: CollectionVariableType, + collectionId: 'my_collection', variableType: DataCollectionStateVariableType.currentItem, }, type: 'default', @@ -767,12 +803,14 @@ describe('Collection component', () => { name: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, collectionConfig: { startIndex: 1, endIndex: 2, + collectionId: 'my_collection', dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -813,11 +851,13 @@ describe('Collection component', () => { name: { type: CollectionVariableType, variableType: variableType, + collectionId: 'my_collection', }, attributes: { custom_attribute: { type: CollectionVariableType, variableType: variableType, + collectionId: 'my_collection', }, }, traits: [ @@ -826,6 +866,7 @@ describe('Collection component', () => { value: { type: CollectionVariableType, variableType: variableType, + collectionId: 'my_collection', }, }, { @@ -834,6 +875,7 @@ describe('Collection component', () => { value: { type: CollectionVariableType, variableType: variableType, + collectionId: 'my_collection', }, }, ], diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts index 8ee78f8b7d..3fbd07b299 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts +++ b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts @@ -48,11 +48,13 @@ describe('Collection variable components', () => { { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, ], }, collectionConfig: { + collectionId: 'my_collection', dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -77,10 +79,12 @@ describe('Collection variable components', () => { components: { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, }, collectionConfig: { + collectionId: 'my_collection', dataSource: { type: DataVariableType, path: 'my_data_source_id', @@ -104,6 +108,7 @@ describe('Collection variable components', () => { const variableCmpDef = { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }; @@ -141,6 +146,7 @@ describe('Collection variable components', () => { const newChildDefinition = { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentIndex, + collectionId: 'my_collection', path: 'user', }; firstChild.components().at(0).components(newChildDefinition); @@ -159,6 +165,7 @@ describe('Collection variable components', () => { const newChildDefinition = { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentIndex, + collectionId: 'my_collection', path: 'user', }; @@ -183,6 +190,7 @@ describe('Collection variable components', () => { { type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'user', }, ], diff --git a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap index ba96ea6e86..e22d545fc7 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap +++ b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap @@ -18,6 +18,7 @@ exports[`Collection variable components Serialization Saving: Collection with co "type": "default", }, { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -47,6 +48,7 @@ exports[`Collection variable components Serialization Saving: Collection with co { "components": [ { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", @@ -55,6 +57,7 @@ exports[`Collection variable components Serialization Saving: Collection with co "type": "default", }, { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -85,6 +88,7 @@ exports[`Collection variable components Serialization Serializion to JSON: Colle "type": "default", }, { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -114,6 +118,7 @@ exports[`Collection variable components Serialization Serializion to JSON: Colle { "components": [ { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", @@ -122,6 +127,7 @@ exports[`Collection variable components Serialization Serializion to JSON: Colle "type": "default", }, { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", From 3d3e9e1a825f76319d8498585933f948c7b4ed49 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 20 Jan 2025 02:32:26 +0200 Subject: [PATCH 78/87] Tests for nested collection components --- .../nestedComponentDataCollections.ts.snap | 36 ++ .../nestedComponentDataCollections.ts | 314 ++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap create mode 100644 packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts diff --git a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap new file mode 100644 index 0000000000..36dae289fb --- /dev/null +++ b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collection component Nested collections are correctly serialized 1`] = ` +{ + "collectionDef": { + "collectionConfig": { + "collectionId": "parent_collection", + "dataSource": { + "path": "my_data_source_id", + "type": "data-variable", + }, + }, + "componentDef": { + "collectionDef": { + "collectionConfig": { + "collectionId": "nested_collection", + "dataSource": { + "path": "nested_data_source_id", + "type": "data-variable", + }, + }, + "componentDef": { + "name": { + "path": "user", + "type": "parent-collection-variable", + "variableType": "currentItem", + }, + "type": "default", + }, + }, + "type": "collection-component", + }, + }, + "type": "collection-component", +} +`; diff --git a/packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts b/packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts new file mode 100644 index 0000000000..d77ff13ce5 --- /dev/null +++ b/packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts @@ -0,0 +1,314 @@ +import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src'; +import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; +import { + CollectionComponentType, + CollectionVariableType, +} from '../../../../../src/data_sources/model/data_collection/constants'; +import { DataCollectionStateVariableType } from '../../../../../src/data_sources/model/data_collection/types'; +import EditorModel from '../../../../../src/editor/model/Editor'; +import { setupTestEditor } from '../../../../common'; + +describe('Collection component', () => { + let em: EditorModel; + let editor: Editor; + let dsm: DataSourceManager; + let dataSource: DataSource; + let nestedDataSource: DataSource; + let wrapper: Component; + let firstRecord: DataRecord; + let secondRecord: DataRecord; + let firstNestedRecord: DataRecord; + let secondNestedRecord: DataRecord; + + beforeEach(() => { + ({ em, editor, dsm } = setupTestEditor()); + wrapper = em.getWrapper()!; + dataSource = dsm.add({ + id: 'my_data_source_id', + records: [ + { id: 'user1', user: 'user1', age: '12' }, + { id: 'user2', user: 'user2', age: '14' }, + ], + }); + + nestedDataSource = dsm.add({ + id: 'nested_data_source_id', + records: [ + { id: 'user1', user: 'nested_user1', age: '12' }, + { id: 'user2', user: 'nested_user2', age: '14' }, + { id: 'user3', user: 'nested_user3', age: '16' }, + ], + }); + + firstRecord = dataSource.getRecord('user1')!; + secondRecord = dataSource.getRecord('user2')!; + firstNestedRecord = nestedDataSource.getRecord('user1')!; + secondNestedRecord = nestedDataSource.getRecord('user2')!; + }); + + afterEach(() => { + em.destroy(); + }); + + test('Nested collections bind to correct data sources', () => { + const parentCollection = wrapper.components({ + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: 'default', + name: { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'nested_collection', + path: 'user', + }, + }, + collectionConfig: { + collectionId: 'nested_collection', + dataSource: { + type: DataVariableType, + path: 'nested_data_source_id', + }, + }, + }, + }, + collectionConfig: { + collectionId: 'parent_collection', + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + const nestedCollection = parentCollection.components().at(0); + const nestedFirstChild = nestedCollection.components().at(0); + const nestedSecondChild = nestedCollection.components().at(1); + + expect(nestedFirstChild.get('name')).toBe('nested_user1'); + expect(nestedSecondChild.get('name')).toBe('nested_user2'); + }); + + test('Updates in parent collection propagate to nested collections', () => { + const parentCollection = wrapper.components({ + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: 'default', + name: { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'nested_collection', + path: 'user', + }, + }, + collectionConfig: { + collectionId: 'nested_collection', + dataSource: { + type: DataVariableType, + path: 'nested_data_source_id', + }, + }, + }, + }, + collectionConfig: { + collectionId: 'parent_collection', + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + const nestedCollection = parentCollection.components().at(0); + const nestedFirstChild = nestedCollection.components().at(0); + const nestedSecondChild = nestedCollection.components().at(1); + + firstNestedRecord.set('user', 'updated_user1'); + expect(nestedFirstChild.get('name')).toBe('updated_user1'); + expect(nestedSecondChild.get('name')).toBe('nested_user2'); + }); + + test('Nested collections are correctly serialized', () => { + const parentCollection = wrapper.components({ + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: 'default', + name: { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + path: 'user', + }, + }, + collectionConfig: { + collectionId: 'nested_collection', + dataSource: { + type: DataVariableType, + path: 'nested_data_source_id', + }, + }, + }, + }, + collectionConfig: { + collectionId: 'parent_collection', + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + const serialized = parentCollection.toJSON(); + expect(serialized).toMatchSnapshot(); + }); + + test('Nested collections respect startIndex and endIndex', () => { + const parentCollection = wrapper.components({ + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: 'default', + name: { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'nested_collection', + path: 'user', + }, + }, + collectionConfig: { + collectionId: 'nested_collection', + startIndex: 0, + endIndex: 1, + dataSource: { + type: DataVariableType, + path: 'nested_data_source_id', + }, + }, + }, + }, + collectionConfig: { + collectionId: 'parent_collection', + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + const nestedCollection = parentCollection.components().at(0); + expect(nestedCollection.components().length).toBe(2); + }); + + test('Nested collection gets and watches value from the parent collection', () => { + const parentCollection = wrapper.components({ + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: 'default', + name: { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'parent_collection', + path: 'user', + }, + }, + collectionConfig: { + collectionId: 'nested_collection', + dataSource: { + type: DataVariableType, + path: 'nested_data_source_id', + }, + }, + }, + }, + collectionConfig: { + collectionId: 'parent_collection', + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + const nestedCollection = parentCollection.components().at(0); + const firstNestedChild = nestedCollection.components().at(0); + + // Verify initial value + expect(firstNestedChild.get('name')).toBe('user1'); + + // Update value in parent collection and verify nested collection updates + firstRecord.set('user', 'updated_user1'); + expect(firstNestedChild.get('name')).toBe('updated_user1'); + }); + + test('Nested collection switches to using its own collection variable', () => { + const parentCollection = wrapper.components({ + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: 'default', + name: { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + path: 'user', + collectionId: 'parent_collection', + }, + }, + collectionConfig: { + collectionId: 'nested_collection', + dataSource: { + type: DataVariableType, + path: 'nested_data_source_id', + }, + }, + }, + }, + collectionConfig: { + collectionId: 'parent_collection', + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + const nestedCollection = parentCollection.components().at(0); + + const firstChild = nestedCollection.components().at(0); + // Replace the collection variable with one from the inner collection + firstChild.set('name', { + // @ts-ignore + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + path: 'user', + collectionId: 'nested_collection', + }); + + expect(firstChild.get('name')).toBe('nested_user1'); + }); +}); From d5c64ff26161e8ca1ee7498d02842cae5967ecff Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 20 Jan 2025 02:35:51 +0200 Subject: [PATCH 79/87] Cleanup --- .../src/dom_components/model/Component.ts | 38 ------------------- .../core/src/dom_components/model/types.ts | 33 ---------------- 2 files changed, 71 deletions(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 599eb59c68..571f1ef081 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -26,7 +26,6 @@ import { ComponentDefinitionDefined, ComponentOptions, ComponentProperties, - DeepPropagationArray, DragMode, ResetComponentsOptions, SymbolToUpOptions, @@ -129,8 +128,6 @@ export const keyCollectionsStateMap = '__collections_state_map'; * @property {Array} [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} [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} [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`). @@ -177,7 +174,6 @@ export default class Component extends StyleableModel { attributes: {}, traits: ['id', 'title'], propagate: '', - deepPropagate: '', dmode: '', toolbar: null, delegate: null, @@ -265,7 +261,6 @@ export default class Component extends StyleableModel { * @ts-ignore */ collection!: Components; componentDVListener: ComponentDynamicValueWatcher; - accumulatedPropagatedProps: DeepPropagationArray = []; collectionStateListeners: string[] = []; constructor(props: ComponentProperties = {}, opt: ComponentOptions) { @@ -352,16 +347,6 @@ export default class Component extends StyleableModel { } } - getCollectionStateMap(): DataCollectionStateMap { - const collectionStateMapProp = this.get(keyCollectionsStateMap); - if (collectionStateMapProp) { - return collectionStateMapProp; - } - - const parent = this.parent(); - return parent?.getCollectionStateMap() || {}; - } - set( keyOrAttributes: A | Partial, valueOrOptions?: ComponentProperties[A] | ComponentSetOptions, @@ -387,28 +372,6 @@ export default class Component extends StyleableModel { 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 = {}; - 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; @@ -1640,7 +1603,6 @@ export default class Component extends StyleableModel { delete obj[keyCollectionsStateMap]; delete obj[keyIsCollectionItem]; delete obj.attributes.id; - delete obj.deepPropagate; } if (!opts.fromUndo) { diff --git a/packages/core/src/dom_components/model/types.ts b/packages/core/src/dom_components/model/types.ts index 0c9cfa2372..509d0dd158 100644 --- a/packages/core/src/dom_components/model/types.ts +++ b/packages/core/src/dom_components/model/types.ts @@ -83,8 +83,6 @@ 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. @@ -233,37 +231,6 @@ export interface ComponentProperties { */ propagate?: (keyof ComponentProperties)[]; - /** - * @property {Array} [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' }, ... ]`. From 3c6e72f8ed9c49fa723d8d76be92d97569ce5c8b Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Mon, 20 Jan 2025 02:36:39 +0200 Subject: [PATCH 80/87] Cleanup --- packages/core/src/dom_components/model/Components.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/dom_components/model/Components.ts b/packages/core/src/dom_components/model/Components.ts index 1a708ee251..892b1d6c27 100644 --- a/packages/core/src/dom_components/model/Components.ts +++ b/packages/core/src/dom_components/model/Components.ts @@ -380,7 +380,6 @@ Component> { } onAdd(model: Component, c?: any, opts: { temporary?: boolean } = {}) { - model.propagateDeeplyFromParent(); const { domc, em } = this; const style = model.getStyle(); const avoidInline = em && em.getConfig().avoidInlineStyle; From 374dbc4ffa0cd5c46a7d81028449e214125d89f8 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 21 Jan 2025 01:12:42 +0200 Subject: [PATCH 81/87] Fix collection component symbols --- .../ComponentDataCollection.ts | 40 +++++++++--- .../src/dom_components/model/Component.ts | 10 +-- .../core/src/dom_components/model/types.ts | 1 + .../ComponentDataCollection.ts | 2 + .../ComponentDataCollection.ts.snap | 62 +++++++++++++++++++ 5 files changed, 97 insertions(+), 18 deletions(-) diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts index 0dff5b698d..3d2ba07e55 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts @@ -19,6 +19,12 @@ import DynamicVariableListenerManager from '../DataVariableListenerManager'; export default class ComponentDataCollection extends Component { constructor(props: ComponentDataCollectionDefinition, opt: ComponentOptions) { + const collectionDef = props[keyCollectionDefinition]; + if (opt.forCloning) { + // @ts-ignore + return super(props, opt); + } + const em = opt.em; // @ts-ignore const cmp: ComponentDataCollection = super( @@ -31,7 +37,6 @@ export default class ComponentDataCollection extends Component { opt, ); - const collectionDef = props[keyCollectionDefinition]; if (!collectionDef) { em.logError('missing collection definition'); @@ -41,11 +46,11 @@ export default class ComponentDataCollection extends Component { const parentCollectionStateMap = (props[keyCollectionsStateMap] || {}) as DataCollectionStateMap; const components: Component[] = getCollectionItems(em, collectionDef, parentCollectionStateMap, opt); + cmp.components(components); if (this.hasDynamicDataSource()) { this.watchDataSource(em, collectionDef, parentCollectionStateMap, opt); } - cmp.components(components); return cmp; } @@ -60,7 +65,7 @@ export default class ComponentDataCollection extends Component { } toJSON(opts?: ObjectAny) { - const json = super.toJSON(opts) as ComponentDataCollectionDefinition; + const json = super.toJSON.call(this, opts) as ComponentDataCollectionDefinition; const firstChild = this.getBlockDefinition(); json[keyCollectionDefinition].componentDef = firstChild; @@ -166,9 +171,8 @@ function getCollectionItems( }, opt, ); - blockSymbolMain!.setSymbolOverride([keyCollectionsStateMap]); + setCollectionStateMapAndPropagate(collectionsStateMap, collectionId)(blockSymbolMain); } - blockSymbolMain!.set(keyCollectionsStateMap, collectionsStateMap); const instance = blockSymbolMain!.clone({ symbol: true }); setCollectionStateMapAndPropagate(collectionsStateMap, collectionId)(instance); @@ -183,6 +187,7 @@ function setCollectionStateMapAndPropagate( collectionId: string | undefined, ) { return (model: Component) => { + // Set the collectionStateMap on the current model setCollectionStateMap(collectionsStateMap)(model); // Listener function for the 'add' event @@ -199,13 +204,16 @@ function setCollectionStateMapAndPropagate( model.collectionStateListeners.push(listenerKey); // Add a 'remove' listener to clean up - model.listenTo(model.components(), 'remove', () => { + const removeListener = () => { model.stopListening(model.components(), 'add', addListener); // Remove the 'add' listener + model.off(`change:${keyCollectionsStateMap}`, handleCollectionStateMapChange); // Remove the change listener const index = model.collectionStateListeners.indexOf(listenerKey); if (index > -1) { model.collectionStateListeners.splice(index, 1); // Remove the listener key } - }); + }; + + model.listenTo(model.components(), 'remove', removeListener); } // Recursively apply to all child components @@ -215,9 +223,21 @@ function setCollectionStateMapAndPropagate( .forEach((component: Component) => { setCollectionStateMapAndPropagate(collectionsStateMap, collectionId)(component); }); + + // Listen for changes in the collectionStateMap and propagate to children + model.on(`change:${keyCollectionsStateMap}`, handleCollectionStateMapChange); }; } +function handleCollectionStateMapChange(this: Component) { + const updatedCollectionsStateMap = this.get(keyCollectionsStateMap); + this.components() + ?.toArray() + .forEach((component: Component) => { + setCollectionStateMap(updatedCollectionsStateMap)(component); + }); +} + function logErrorIfMissing(property: any, propertyPath: string, em: EditorModel) { if (!property) { em.logError(`The "${propertyPath}" property is required in the collection definition.`); @@ -250,10 +270,12 @@ function validateCollectionConfig( function setCollectionStateMap(collectionsStateMap: DataCollectionStateMap) { return (cmp: Component) => { cmp.set(keyIsCollectionItem, true); - cmp.set(keyCollectionsStateMap, { + const updatedCollectionStateMap = { ...cmp.get(keyCollectionsStateMap), ...collectionsStateMap, - }); + }; + cmp.set(keyCollectionsStateMap, updatedCollectionStateMap); + cmp.componentDVListener.updateCollectionStateMap(updatedCollectionStateMap); }; } diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 571f1ef081..78871aad26 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -311,7 +311,6 @@ export default class Component extends StyleableModel { this.ccid = Component.createId(this, opt); this.preInit(); this.initClasses(); - this.listenTo(this, `change:${keyCollectionsStateMap}`, this.handleCollectionsMapStateChange); this.initComponents(); this.initTraits(); this.initToolbar(); @@ -1358,6 +1357,7 @@ export default class Component extends StyleableModel { attr.status = ''; // @ts-ignore opts.collection = null; + opts.forCloning = true; // @ts-ignore const cloned = new this.constructor(attr, opts); @@ -1588,7 +1588,6 @@ export default class Component extends StyleableModel { let obj = Model.prototype.toJSON.call(this, opts); obj = { ...obj, ...this.componentDVListener.getDynamicPropsDefs() }; obj.attributes = this.componentDVListener.getAttributesDefsOrValues(this.getAttributes()); - delete obj.componentDVListener; delete obj.attributes.class; delete obj.toolbar; delete obj.traits; @@ -1977,13 +1976,6 @@ export default class Component extends StyleableModel { selector && selector.set({ name: id, label: id }); } - private handleCollectionsMapStateChange(m: any, v: DataCollectionStateMap, opts = {}) { - this.componentDVListener.updateCollectionStateMap(v); - this.components()?.forEach((child) => { - child.set?.(keyCollectionsStateMap, v); - }); - } - static typeExtends = new Set(); static getDefaults() { diff --git a/packages/core/src/dom_components/model/types.ts b/packages/core/src/dom_components/model/types.ts index 509d0dd158..60d34a4c6e 100644 --- a/packages/core/src/dom_components/model/types.ts +++ b/packages/core/src/dom_components/model/types.ts @@ -322,4 +322,5 @@ export interface ComponentOptions { frame?: Frame; temporary?: boolean; avoidChildren?: boolean; + forCloning?: boolean; } diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts index 8a6c05fdde..fba2c46d96 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts @@ -232,6 +232,7 @@ describe('Collection component', () => { // @ts-ignore type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'age', }); expect(firstGrandchild.get('name')).toBe('new_value_12'); @@ -368,6 +369,7 @@ describe('Collection component', () => { // @ts-ignore type: CollectionVariableType, variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'my_collection', path: 'age', }, }); diff --git a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap index 7fc6d5d71f..2e851b7152 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap +++ b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap @@ -15,11 +15,13 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "componentDef": { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -29,11 +31,13 @@ exports[`Collection component Serialization Saving: Collection with grandchildre { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -42,6 +46,7 @@ exports[`Collection component Serialization Saving: Collection with grandchildre "components": [ { "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", @@ -50,16 +55,19 @@ exports[`Collection component Serialization Saving: Collection with grandchildre }, ], "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -69,27 +77,32 @@ exports[`Collection component Serialization Saving: Collection with grandchildre { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -98,16 +111,19 @@ exports[`Collection component Serialization Saving: Collection with grandchildre }, ], "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -134,11 +150,13 @@ exports[`Collection component Serialization Saving: Collection with no grandchil "componentDef": { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -148,27 +166,32 @@ exports[`Collection component Serialization Saving: Collection with no grandchil { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -178,27 +201,32 @@ exports[`Collection component Serialization Saving: Collection with no grandchil { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -207,16 +235,19 @@ exports[`Collection component Serialization Saving: Collection with no grandchil }, ], "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -243,11 +274,13 @@ exports[`Collection component Serialization Serializion with Collection Variable "componentDef": { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -257,11 +290,13 @@ exports[`Collection component Serialization Serializion with Collection Variable { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -270,6 +305,7 @@ exports[`Collection component Serialization Serializion with Collection Variable "components": [ { "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", @@ -278,16 +314,19 @@ exports[`Collection component Serialization Serializion with Collection Variable }, ], "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -297,27 +336,32 @@ exports[`Collection component Serialization Serializion with Collection Variable { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -326,16 +370,19 @@ exports[`Collection component Serialization Serializion with Collection Variable }, ], "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -362,11 +409,13 @@ exports[`Collection component Serialization Serializion with Collection Variable "componentDef": { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -376,27 +425,32 @@ exports[`Collection component Serialization Serializion with Collection Variable { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -406,27 +460,32 @@ exports[`Collection component Serialization Serializion with Collection Variable { "attributes": { "attribute_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, }, "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", @@ -435,16 +494,19 @@ exports[`Collection component Serialization Serializion with Collection Variable }, ], "custom_prop": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentIndex", }, "name": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", }, "property_trait": { + "collectionId": "my_collection", "path": "user", "type": "parent-collection-variable", "variableType": "currentItem", From 1f00f16655e6eca1cfcc67e659e52a67c703f99c Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 21 Jan 2025 03:27:43 +0200 Subject: [PATCH 82/87] Add tests for adding and removing records --- .../ComponentDataCollection.ts | 47 +++++++ .../nestedComponentDataCollections.ts | 126 +++++++++++++++++- 2 files changed, 168 insertions(+), 5 deletions(-) diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts index fba2c46d96..bed87062b5 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts @@ -190,6 +190,53 @@ describe('Collection component', () => { expect(secondGrandchild.get('name')).toBe('user2'); }); + test('Removing a record updates the collection component correctly', () => { + // Remove a record from the data source + dataSource.removeRecord('user1'); + + // Verify that the first child component is no longer present + expect(cmp.components().length).toBe(2); // Only 2 records remain + + // Verify that the remaining components are updated correctly + const updatedFirstChild = cmp.components().at(0); + const updatedSecondChild = cmp.components().at(1); + + expect(updatedFirstChild.get('name')).toBe("user2"); + expect(updatedSecondChild.get('name')).toBe("user3"); + + // Verify that the grandchild components are also updated + const updatedFirstGrandchild = updatedFirstChild.components().at(0); + const updatedSecondGrandchild = updatedSecondChild.components().at(0); + + expect(updatedFirstGrandchild.get('name')).toBe("user2"); + expect(updatedSecondGrandchild.get('name')).toBe("user3"); + }); + + test('Adding a record updates the collection component correctly', () => { + // Add a new record to the data source + dataSource.addRecord({ id: 'user4', user: 'user4', age: '20' }); + + // Verify that the collection component now has 4 children + expect(cmp.components().length).toBe(4); + + // Verify that the new child component is added correctly + const newChild = cmp.components().at(3); + expect(newChild.get('name')).toBe('user4'); // user4's age + + // Verify that the grandchild component is also added correctly + const newGrandchild = newChild.components().at(0); + expect(newGrandchild.get('name')).toBe('user4'); // user4's age + + // Verify that existing components are unaffected + const firstChild = cmp.components().at(0); + const secondChild = cmp.components().at(1); + const thirdChild = cmp.components().at(2); + + expect(firstChild.get('name')).toBe('user1'); + expect(secondChild.get('name')).toBe('user2'); + expect(thirdChild.get('name')).toBe('user3'); + }); + test('Updating the value to a static value', async () => { firstChild.set('name', 'new_content_value'); expect(firstChild.get('name')).toBe('new_content_value'); diff --git a/packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts b/packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts index d77ff13ce5..d5536355a6 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts +++ b/packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts @@ -34,16 +34,16 @@ describe('Collection component', () => { nestedDataSource = dsm.add({ id: 'nested_data_source_id', records: [ - { id: 'user1', user: 'nested_user1', age: '12' }, - { id: 'user2', user: 'nested_user2', age: '14' }, - { id: 'user3', user: 'nested_user3', age: '16' }, + { id: 'nested_user1', user: 'nested_user1', age: '12' }, + { id: 'nested_user2', user: 'nested_user2', age: '14' }, + { id: 'nested_user3', user: 'nested_user3', age: '16' }, ], }); firstRecord = dataSource.getRecord('user1')!; secondRecord = dataSource.getRecord('user2')!; - firstNestedRecord = nestedDataSource.getRecord('user1')!; - secondNestedRecord = nestedDataSource.getRecord('user2')!; + firstNestedRecord = nestedDataSource.getRecord('nested_user1')!; + secondNestedRecord = nestedDataSource.getRecord('nested_user2')!; }); afterEach(() => { @@ -311,4 +311,120 @@ describe('Collection component', () => { expect(firstChild.get('name')).toBe('nested_user1'); }); + + describe('Nested Collection Component with Parent and Nested Data Sources', () => { + let parentCollection: Component; + let nestedCollection: Component; + + beforeEach(() => { + // Initialize the parent and nested collections + parentCollection = wrapper.components({ + type: CollectionComponentType, + collectionDef: { + componentDef: { + type: CollectionComponentType, + name: { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'parent_collection', + path: 'user', + }, + collectionDef: { + componentDef: { + type: 'default', + name: { + type: CollectionVariableType, + variableType: DataCollectionStateVariableType.currentItem, + collectionId: 'nested_collection', + path: 'user', + }, + }, + collectionConfig: { + collectionId: 'nested_collection', + dataSource: { + type: DataVariableType, + path: 'nested_data_source_id', + }, + }, + }, + }, + collectionConfig: { + collectionId: 'parent_collection', + dataSource: { + type: DataVariableType, + path: 'my_data_source_id', + }, + }, + }, + })[0]; + + nestedCollection = parentCollection.components().at(0); + }); + + test('Removing a record from the parent data source updates the parent collection correctly', () => { + // Verify initial state + expect(parentCollection.components().length).toBe(2); // 2 parent records initially + + // Remove a record from the parent data source + dataSource.removeRecord('user1'); + + // Verify that the parent collection updates correctly + expect(parentCollection.components().length).toBe(1); // Only 1 parent record remains + expect(parentCollection.components().at(0).get('name')).toBe('user2'); // Verify updated name + + // Verify that the nested collection is unaffected + expect(nestedCollection.components().length).toBe(3); // Nested records remain the same + expect(nestedCollection.components().at(0).get('name')).toBe('nested_user1'); // Verify nested name + }); + + test('Adding a record to the parent data source updates the parent collection correctly', () => { + // Verify initial state + expect(parentCollection.components().length).toBe(2); // 2 parent records initially + + // Add a new record to the parent data source + dataSource.addRecord({ id: 'user3', user: 'user3', age: '16' }); + + // Verify that the parent collection updates correctly + expect(parentCollection.components().length).toBe(3); // 3 parent records now + expect(parentCollection.components().at(2).get('name')).toBe('user3'); // Verify new name + + // Verify that the nested collection is unaffected + expect(nestedCollection.components().length).toBe(3); // Nested records remain the same + expect(nestedCollection.components().at(0).get('name')).toBe('nested_user1'); // Verify nested name + expect(parentCollection.components().at(2).components().at(0).get('name')).toBe('nested_user1'); // Verify nested name + }); + + test('Removing a record from the nested data source updates the nested collection correctly', () => { + // Verify initial state + expect(nestedCollection.components().length).toBe(3); // 3 nested records initially + + // Remove a record from the nested data source + nestedDataSource.removeRecord('nested_user1'); + + // Verify that the nested collection updates correctly + expect(nestedCollection.components().length).toBe(2); // Only 2 nested records remain + expect(nestedCollection.components().at(0).get('name')).toBe('nested_user2'); // Verify updated name + expect(nestedCollection.components().at(1).get('name')).toBe('nested_user3'); // Verify updated name + }); + + test('Adding a record to the nested data source updates the nested collection correctly', () => { + // Verify initial state + expect(nestedCollection.components().length).toBe(3); // 3 nested records initially + expect(nestedCollection.components().at(0).get('name')).toBe('nested_user1'); // Verify initial name + expect(nestedCollection.components().at(1).get('name')).toBe('nested_user2'); // Verify initial name + expect(nestedCollection.components().at(2).get('name')).toBe('nested_user3'); // Verify initial name + + // Add a new record to the nested data source + nestedDataSource.addRecord({ id: 'user4', user: 'nested_user4', age: '18' }); + + // Verify that the nested collection updates correctly + expect(nestedCollection.components().length).toBe(4); // 4 nested records now + expect(nestedCollection.components().at(3).get('name')).toBe('nested_user4'); // Verify new name + + // Verify existing records are unaffected + expect(nestedCollection.components().at(0).get('name')).toBe('nested_user1'); // Verify existing name + expect(nestedCollection.components().at(1).get('name')).toBe('nested_user2'); // Verify existing name + expect(nestedCollection.components().at(2).get('name')).toBe('nested_user3'); // Verify existing name + }); + }); }); From 39d6de8413601249e6070a4bd0041896fe1d2cc8 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 21 Jan 2025 04:26:46 +0200 Subject: [PATCH 83/87] Format --- .../model/data_collection/ComponentDataCollection.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts index bed87062b5..2d5633f9ab 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts @@ -201,15 +201,15 @@ describe('Collection component', () => { const updatedFirstChild = cmp.components().at(0); const updatedSecondChild = cmp.components().at(1); - expect(updatedFirstChild.get('name')).toBe("user2"); - expect(updatedSecondChild.get('name')).toBe("user3"); + expect(updatedFirstChild.get('name')).toBe('user2'); + expect(updatedSecondChild.get('name')).toBe('user3'); // Verify that the grandchild components are also updated const updatedFirstGrandchild = updatedFirstChild.components().at(0); const updatedSecondGrandchild = updatedSecondChild.components().at(0); - expect(updatedFirstGrandchild.get('name')).toBe("user2"); - expect(updatedSecondGrandchild.get('name')).toBe("user3"); + expect(updatedFirstGrandchild.get('name')).toBe('user2'); + expect(updatedSecondGrandchild.get('name')).toBe('user3'); }); test('Adding a record updates the collection component correctly', () => { From 979d0351c95966b9d1e5952a85825c96a913090a Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 21 Jan 2025 04:27:38 +0200 Subject: [PATCH 84/87] Fix updating datasource --- .../model/data_collection/ComponentDataCollection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts index 3d2ba07e55..f278321129 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts @@ -102,7 +102,7 @@ export default class ComponentDataCollection extends Component { dataVariable, updateValueFromDataVariable: () => { const collectionItems = getCollectionItems(em, collectionDef, parentCollectionStateMap, opt); - this.components(collectionItems); + this.components().reset(collectionItems); }, }); } From e9b46e738cd159cd21fa575418fce9921b2f7b3c Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 21 Jan 2025 04:38:04 +0200 Subject: [PATCH 85/87] Fix nested symbols not working on collection component --- packages/core/src/dom_components/model/Component.ts | 1 + packages/core/src/dom_components/model/SymbolUtils.ts | 10 +--------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 78871aad26..ceff75e9c0 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -1588,6 +1588,7 @@ export default class Component extends StyleableModel { let obj = Model.prototype.toJSON.call(this, opts); obj = { ...obj, ...this.componentDVListener.getDynamicPropsDefs() }; obj.attributes = this.componentDVListener.getAttributesDefsOrValues(this.getAttributes()); + delete obj.componentDVListener; delete obj.attributes.class; delete obj.toolbar; delete obj.traits; diff --git a/packages/core/src/dom_components/model/SymbolUtils.ts b/packages/core/src/dom_components/model/SymbolUtils.ts index 94e4affd6e..137126f37a 100644 --- a/packages/core/src/dom_components/model/SymbolUtils.ts +++ b/packages/core/src/dom_components/model/SymbolUtils.ts @@ -208,20 +208,12 @@ export const updateSymbolComps = (symbol: Component, m: Component, c: Components changed: 'components:reset', }); const cmps = coll.models; - const newSymbols = new Set(); logSymbol(symbol, 'reset', toUp, { components: cmps }); toUp.forEach((rel) => { const relCmps = rel.components(); const toReset = cmps.map((cmp, i) => { - // This particular case here is to handle reset from `resetFromString` - // where we can receive an array of regulat components or already - // existing symbols (updated already before reset) - if (!isSymbol(cmp) || newSymbols.has(cmp)) { - newSymbols.add(cmp); - return cmp.clone({ symbol: true }); - } - return relCmps.at(i); + return cmp.clone({ symbol: isSymbol(cmp) }); }); relCmps.reset(toReset, { fromInstance: symbol, ...c } as any); }); From c3172a6f53dbe47c3a6cd1219568cc153d167c81 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 21 Jan 2025 12:05:34 +0200 Subject: [PATCH 86/87] Fix syncing collection items for nested collections --- .../core/src/dom_components/model/SymbolUtils.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/core/src/dom_components/model/SymbolUtils.ts b/packages/core/src/dom_components/model/SymbolUtils.ts index 137126f37a..e187fe61e3 100644 --- a/packages/core/src/dom_components/model/SymbolUtils.ts +++ b/packages/core/src/dom_components/model/SymbolUtils.ts @@ -3,7 +3,7 @@ import Component, { keySymbol, keySymbolOvrd, keySymbols } from './Component'; import { SymbolToUpOptions } from './types'; import { isEmptyObj } from '../../utils/mixins'; import Components from './Components'; -import { CollectionVariableType } from '../../data_sources/model/data_collection/constants'; +import { CollectionVariableType, keyCollectionDefinition } from '../../data_sources/model/data_collection/constants'; export const isSymbolMain = (cmp: Component) => isArray(cmp.get(keySymbols)); @@ -208,13 +208,25 @@ export const updateSymbolComps = (symbol: Component, m: Component, c: Components changed: 'components:reset', }); const cmps = coll.models; + const newSymbols = new Set(); logSymbol(symbol, 'reset', toUp, { components: cmps }); toUp.forEach((rel) => { const relCmps = rel.components(); const toReset = cmps.map((cmp, i) => { - return cmp.clone({ symbol: isSymbol(cmp) }); + if (symbol.get(keyCollectionDefinition)) { + return cmp.clone({ symbol: isSymbol(cmp) }); + } + // This particular case here is to handle reset from `resetFromString` + // where we can receive an array of regulat components or already + // existing symbols (updated already before reset) + if (!isSymbol(cmp) || newSymbols.has(cmp)) { + newSymbols.add(cmp); + return cmp.clone({ symbol: true }); + } + return relCmps.at(i); }); + relCmps.reset(toReset, { fromInstance: symbol, ...c } as any); }); // Add From dc13e1140e81d56095cb3a9560f79fecf73078ce Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Tue, 21 Jan 2025 12:38:57 +0200 Subject: [PATCH 87/87] Refactor --- .../ComponentDataCollection.ts | 33 ++++++++++++------- .../ComponentDataCollectionVariable.ts | 10 +++--- .../data_collection/DataCollectionVariable.ts | 4 +-- .../model/data_collection/constants.ts | 3 +- packages/core/src/data_sources/model/utils.ts | 1 - .../src/dom_components/model/Component.ts | 14 ++++---- .../model/ComponentDynamicValueWatcher.ts | 3 +- .../src/utils/sorter/CanvasComponentNode.ts | 9 ----- 8 files changed, 39 insertions(+), 38 deletions(-) diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts index f278321129..f9de10b678 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts @@ -6,7 +6,6 @@ import { toLowerCase } from '../../../utils/mixins'; import DataSource from '../DataSource'; import { ObjectAny } from '../../../common'; import EditorModel from '../../../editor/model/Editor'; -import { keyCollectionsStateMap } from '../../../dom_components/model/Component'; import { ComponentDataCollectionDefinition, DataCollectionConfig, @@ -14,28 +13,31 @@ import { DataCollectionState, DataCollectionStateMap, } from './types'; -import { keyCollectionDefinition, CollectionComponentType, keyIsCollectionItem } from './constants'; +import { + keyCollectionDefinition, + keyCollectionsStateMap, + CollectionComponentType, + keyIsCollectionItem, +} from './constants'; import DynamicVariableListenerManager from '../DataVariableListenerManager'; export default class ComponentDataCollection extends Component { constructor(props: ComponentDataCollectionDefinition, opt: ComponentOptions) { const collectionDef = props[keyCollectionDefinition]; if (opt.forCloning) { - // @ts-ignore - return super(props, opt); + // If we are cloning, leave setting the collection items to the main symbol collection + return super(props as any, opt) as unknown as ComponentDataCollection; } const em = opt.em; - // @ts-ignore const cmp: ComponentDataCollection = super( - // @ts-ignore { ...props, components: undefined, droppable: false, - }, + } as any, opt, - ); + ) as unknown as ComponentDataCollection; if (!collectionDef) { em.logError('missing collection definition'); @@ -158,15 +160,22 @@ function getCollectionItems( }; if (index === startIndex) { - // @ts-ignore - const type = em.Components.getType(componentDef?.type || 'default'); + const componentType = (componentDef?.type as string) || 'default'; + let type = em.Components.getType(componentType); + // Handle the case where the type is not found + if (!type) { + em.logWarning(`Component type "${componentType}" not found. Using default type.`); + const defaultType = em.Components.getType('default'); + if (!defaultType) { + throw new Error('Default component type not found. Cannot proceed.'); + } + type = defaultType; + } const model = type.model; blockSymbolMain = new model( { ...componentDef, - [keyCollectionsStateMap]: collectionsStateMap, - [keyIsCollectionItem]: true, draggable: false, }, opt, diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts index 78fba3d5af..928eb6bbaf 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts @@ -1,17 +1,19 @@ -import Component, { keyCollectionsStateMap, keySymbolOvrd } from '../../../dom_components/model/Component'; +import Component from '../../../dom_components/model/Component'; import { ComponentOptions, ComponentProperties } from '../../../dom_components/model/types'; import { toLowerCase } from '../../../utils/mixins'; import DataCollectionVariable from './DataCollectionVariable'; -import { CollectionVariableType } from './constants'; +import { CollectionVariableType, keyCollectionsStateMap } from './constants'; import { DataCollectionStateMap, DataCollectionVariableDefinition } from './types'; export default class ComponentDataCollectionVariable extends Component { datacollectionVariable: DataCollectionVariable; get defaults() { + // @ts-expect-error + const componentDefaults = super.defaults; + return { - // @ts-ignore - ...super.defaults, + ...componentDefaults, type: CollectionVariableType, collectionId: undefined, variableType: undefined, diff --git a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts index 4132c61c5c..f77f289156 100644 --- a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts +++ b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts @@ -11,7 +11,7 @@ type ResolvedDataCollectionVariable = DataCollectionVariableDefinition & { export default class DataCollectionVariable extends Model { em: EditorModel; - collectionsStateMap: DataCollectionStateMap; + collectionsStateMap?: DataCollectionStateMap; dataVariable?: DataVariable; dynamicValueListener?: DynamicVariableListenerManager; @@ -29,7 +29,7 @@ export default class DataCollectionVariable extends Model {} export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {} @@ -72,7 +72,6 @@ export const keySymbol = '__symbol'; export const keySymbolOvrd = '__symbol_ovrd'; export const keyUpdate = ComponentsEvents.update; export const keyUpdateInside = ComponentsEvents.updateInside; -export const keyCollectionsStateMap = '__collections_state_map'; /** * The Component object represents a single node of our template structure, so when you update its properties the changes are @@ -270,9 +269,8 @@ export default class Component extends StyleableModel { }); super(props, { ...opt, - // @ts-ignore componentDVListener, - }); + } as any); componentDVListener.bindComponent(this); this.componentDVListener = componentDVListener; @@ -299,7 +297,7 @@ export default class Component extends StyleableModel { } opt.em = em; - this.opt = { ...opt }; + this.opt = opt; this.em = em!; this.config = opt.config || {}; const dynamicAttributes = this.componentDVListener.getDynamicAttributesDefs(); @@ -352,7 +350,9 @@ export default class Component extends StyleableModel { optionsOrUndefined?: ComponentSetOptions, ): this { let attributes: Partial; - let options: ComponentSetOptions = { skipWatcherUpdates: false, fromDataSource: false }; + let options: ComponentSetOptions & { + componentDVListener?: ComponentDynamicValueWatcher; + } = { skipWatcherUpdates: false, fromDataSource: false }; if (typeof keyOrAttributes === 'object') { attributes = keyOrAttributes; options = valueOrOptions || (options as ComponentSetOptions); @@ -364,7 +364,6 @@ export default class Component extends StyleableModel { options = optionsOrUndefined || options; } - // @ts-ignore this.componentDVListener = this.componentDVListener || options.componentDVListener; const evaluatedProps = this.componentDVListener.addProps(attributes, options); @@ -1061,7 +1060,6 @@ export default class Component extends StyleableModel { return coll as any; } else { coll.reset(undefined, opts); - // @ts-ignore return components ? this.append(components, opts) : ([] as any); } } diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index 1eefa87e25..075d1df447 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts @@ -2,7 +2,8 @@ import { Model, ObjectAny } from '../../common'; import { CollectionVariableType, keyIsCollectionItem } from '../../data_sources/model/data_collection/constants'; import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types'; import EditorModel from '../../editor/model/Editor'; -import Component, { keyCollectionsStateMap } from './Component'; +import Component from './Component'; +import { keyCollectionsStateMap } from '../../data_sources/model/data_collection/constants'; import { DynamicWatchersOptions } from './DynamicValueWatcher'; import { DynamicValueWatcher } from './DynamicValueWatcher'; import { getSymbolsToUpdate } from './SymbolUtils'; diff --git a/packages/core/src/utils/sorter/CanvasComponentNode.ts b/packages/core/src/utils/sorter/CanvasComponentNode.ts index cbe23f08ac..2495e75f24 100644 --- a/packages/core/src/utils/sorter/CanvasComponentNode.ts +++ b/packages/core/src/utils/sorter/CanvasComponentNode.ts @@ -6,15 +6,6 @@ export default class CanvasComponentNode extends BaseComponentNode { minUndroppableDimension: 1, // In px maxUndroppableDimension: 15, // In px }; - /** - * Check if a source node can be moved to a specified index within this component. - * @param {BaseComponentNode} source - The source node to move. - * @param {number} index - The display index to move the source to. - * @returns {boolean} - True if the move is allowed, false otherwise. - */ - canMove(source: BaseComponentNode, index: number): boolean { - return this.model.em.Components.canMove(this.model, source.model, this.getRealIndex(index)).result; - } /** * Get the associated view of this component. * @returns The view associated with the component, or undefined if none.