diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts index 7950f31..d589d96 100644 --- a/src/Components/Editor/Actions/ContainerOperations.ts +++ b/src/Components/Editor/Actions/ContainerOperations.ts @@ -11,6 +11,7 @@ import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; import Swal from 'sweetalert2'; import { ApplyMargin, TransformX } from '../../../utils/svg'; import { PropertyType } from '../../../Enums/PropertyType'; +import { ContainerOrPattern, GetPattern, IPattern } from '../../../Interfaces/IPattern'; /** * Select a container @@ -220,82 +221,7 @@ export function AddContainers( // Iterate over the containers availableContainers.forEach((availableContainer, typeIndex) => { // Get the preset properties from the API - const type = availableContainer.Type; - - const defaultConfig = configuration.AvailableContainers - .find(option => option.Type === type); - - if (defaultConfig === undefined) { - throw new Error(`[AddContainer] Object type not found among default config. Found: ${type}`); - } - - const containerConfig = Object.assign(defaultConfig, availableContainer); - - // Default margin - const left: number = containerConfig.Margin?.left ?? 0; - const bottom: number = containerConfig.Margin?.bottom ?? 0; - const top: number = containerConfig.Margin?.top ?? 0; - const right: number = containerConfig.Margin?.right ?? 0; - - // Default coordinates - let x = containerConfig.X ?? 0; - let y = containerConfig.Y ?? 0; - let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width; - let height = containerConfig.Height ?? parentClone.properties.height; - - ({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right)); - - // Apply an add method (append or insert/replace) - x = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x); - - // Set the counter of the object type in order to assign an unique id - UpdateCounters(newCounters, type); - const count = newCounters[type]; - - const defaultProperties = GetDefaultContainerProps( - type, - count, - parentClone, - x, - y, - width, - height, - containerConfig - ); - - // Create the container - const newContainer = new ContainerModel( - parentClone, - defaultProperties, - [], - { - type - } - ); - - // Add it to the parent - if (index === parentClone.children.length) { - parentClone.children.push(newContainer); - } else { - parentClone.children.splice(index, 0, newContainer); - } - - // Sort the parent children by x - UpdateParentChildrenList(parentClone); - - /// Handle behaviors here /// - - // Apply the behaviors (flex, rigid, anchor) - ApplyBehaviors(newContainer, current.symbols); - - // Then, apply the behaviors on its siblings (mostly for flex) - ApplyBehaviorsOnSiblingsChildren(newContainer, current.symbols); - - // Initialize default children of the container - InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters); - - // Add to the list of container id for logging purpose - containerIds.push(newContainer.properties.id); + AddNewContainerToParent(availableContainer, configuration, parentClone, index, typeIndex, newCounters, current.symbols, containerIds); }); // Update the state @@ -311,6 +237,113 @@ export function AddContainers( return history; } +function AddNewContainerToParent( + availableContainer: IAvailableContainer, + configuration: IConfiguration, + parentClone: IContainerModel, + index: number, + typeIndex: number, + newCounters: Record, + symbols: Map, + containerIds: string[], + initChilds: boolean = true +): ContainerModel { + const type = availableContainer.Type; + + const defaultConfig = configuration.AvailableContainers + .find(option => option.Type === type); + + if (defaultConfig === undefined) { + throw new Error(`[AddContainer] Object type not found among default config. Found: ${type}`); + } + + const containerConfig = Object.assign(defaultConfig, availableContainer); + + // Default margin + const left: number = containerConfig.Margin?.left ?? 0; + const bottom: number = containerConfig.Margin?.bottom ?? 0; + const top: number = containerConfig.Margin?.top ?? 0; + const right: number = containerConfig.Margin?.right ?? 0; + + // Default coordinates + let x = containerConfig.X ?? 0; + let y = containerConfig.Y ?? 0; + let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width; + let height = containerConfig.Height ?? parentClone.properties.height; + + ({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right)); + + // Apply an add method (append or insert/replace) + x = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x); + + // Set the counter of the object type in order to assign an unique id + UpdateCounters(newCounters, type); + const count = newCounters[type]; + + const defaultProperties = GetDefaultContainerProps( + type, + count, + parentClone, + x, + y, + width, + height, + containerConfig + ); + + // Create the container + const newContainer = new ContainerModel( + parentClone, + defaultProperties, + [], + { + type + } + ); + + // Add it to the parent + if (index === parentClone.children.length) { + parentClone.children.push(newContainer); + } else { + parentClone.children.splice(index, 0, newContainer); + } + + // Sort the parent children by x + UpdateParentChildrenList(parentClone); + + /// Handle behaviors here /// + // Apply the behaviors (flex, rigid, anchor) + ApplyBehaviors(newContainer, symbols); + + // Then, apply the behaviors on its siblings (mostly for flex) + ApplyBehaviorsOnSiblingsChildren(newContainer, symbols); + + // Initialize default children of the container + if (initChilds) { + if (containerConfig.DefaultChildType !== undefined) { + InitializeDefaultChild( + newContainer, + configuration, + containerConfig, + newCounters + ); + } else { + InitializeChildrenWithPattern( + newContainer, + configuration, + containerConfig, + newCounters, + symbols + ); + } + } + + // Add to the list of container id for logging purpose + containerIds.push(newContainer.properties.id); + + return newContainer; +} + /** * Create and add a new container at `index` in children of parent of `parentId` * @param index Index where to insert to the new container @@ -380,9 +413,9 @@ function UpdateParentChildrenList(parentClone: IContainerModel | null | undefine * @returns */ function InitializeDefaultChild( + newContainer: ContainerModel, configuration: IConfiguration, containerConfig: IAvailableContainer, - newContainer: ContainerModel, newCounters: Record ): void { if (containerConfig.DefaultChildType === undefined) { @@ -404,6 +437,7 @@ function InitializeDefaultChild( seen.add(currentConfig.Type); + /// TODO: REFACTOR this with AddContainerToParent /// const left: number = currentConfig.Margin?.left ?? 0; const bottom: number = currentConfig.Margin?.bottom ?? 0; const top: number = currentConfig.Margin?.top ?? 0; @@ -440,6 +474,7 @@ function InitializeDefaultChild( // And push it the the parent children parent.children.push(newChildContainer); + /// /// // iterate depth++; @@ -449,6 +484,116 @@ function InitializeDefaultChild( } } +function InitializeChildrenWithPattern( + newContainer: ContainerModel, + configuration: IConfiguration, + containerConfig: IAvailableContainer, + newCounters: Record, + symbols: Map +): void { + const patternId = containerConfig.Pattern; + + if (patternId === undefined || patternId === null) { + return; + } + + const configs: Map = new Map(configuration.AvailableContainers.map(config => [config.Type, config])); + const patterns: Map = new Map(configuration.Patterns.map(pattern => [pattern.id, pattern])); + const containerOrPattern = GetPattern(patternId, configs, patterns); + const pattern = containerOrPattern as IPattern; + + // Will store the ids of the iterated IAvailableContainer types + const debugContainerArr: string[] = []; + + if (pattern.children === undefined) { + const container = containerOrPattern as IAvailableContainer; + // Add Container + AddNewContainerToParent( + container, + configuration, + newContainer, + 0, 0, + newCounters, + symbols, + debugContainerArr + ); + } + + // BFS over patterns + const rootNode: Node = { + containerOrPattern: pattern, + parent: newContainer + }; + + const queue: Node[] = [rootNode]; + while (queue.length > 0) { + let levelSize = queue.length; + while (levelSize-- !== 0) { + const node = queue.shift() as Node; + + const pattern = node.containerOrPattern as IPattern; + + if (pattern.children === undefined) { + // Add Container + const containerConfig = node.containerOrPattern as IAvailableContainer; + AddNewContainerToParent( + containerConfig, + configuration, + node.parent, + 0, 0, + newCounters, + symbols, + debugContainerArr + ); + continue; + } + + let parent = node.parent; + if (pattern.wrapper !== undefined) { + // Add Container + const container = configs.get(pattern.wrapper); + if (container === undefined) { + console.warn(`[InitializeChildrenFromPattern] IAvailableContainer from pattern was not found in the configuration: ${pattern.wrapper}. + Process will ignore the container.`); + } else { + parent = AddNewContainerToParent( + container, + configuration, + parent, + 0, 0, + newCounters, + symbols, + debugContainerArr, + false + ); + } + } + + for (let i = pattern.children.length - 1; i >= 0; i--) { + const childId: string = pattern.children[i]; + + const child = GetPattern(childId, configs, patterns); + + if (child === undefined) { + continue; + } + + const nextNode: Node = { + containerOrPattern: child, + parent + }; + + queue.push(nextNode); + } + } + } +} + +interface Node { + containerOrPattern: ContainerOrPattern + parent: IContainerModel +} + /** * Returns a new offset by applying an Add method (append, insert etc.) * See AddMethod diff --git a/src/Interfaces/IAvailableContainer.ts b/src/Interfaces/IAvailableContainer.ts index 03406f7..0f6e36b 100644 --- a/src/Interfaces/IAvailableContainer.ts +++ b/src/Interfaces/IAvailableContainer.ts @@ -67,6 +67,8 @@ export interface IAvailableContainer { /** * (optional) + * Disabled when Pattern is used. + * * Replace a by a customized "SVG". It is not really an svg but it at least allows * to draw some patterns that can be bind to the properties of the container * Use {prop} to bind a property. Use {{ styleProp }} to use an object. @@ -81,6 +83,13 @@ export interface IAvailableContainer { */ DefaultChildType?: string + /** + * Allow to use a Pattern to create the list of children + * Cannot be used with DefaultChildType, + * DefaultChildType will be disabled for this container and the children + */ + Pattern?: string + /** if true, show the dimension of the container */ ShowSelfDimensions?: boolean diff --git a/src/Interfaces/IConfiguration.ts b/src/Interfaces/IConfiguration.ts index 7f1ffc0..d3b98bc 100644 --- a/src/Interfaces/IConfiguration.ts +++ b/src/Interfaces/IConfiguration.ts @@ -1,10 +1,12 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { IAvailableContainer } from './IAvailableContainer'; import { IAvailableSymbol } from './IAvailableSymbol'; +import { IPattern } from './IPattern'; /** Model of configuration for the application to configure it */ export interface IConfiguration { AvailableContainers: IAvailableContainer[] // TODO: Use a Map AvailableSymbols: IAvailableSymbol[] // TODO: Use a Map + Patterns: IPattern[] MainContainer: IAvailableContainer } diff --git a/src/Interfaces/IPattern.ts b/src/Interfaces/IPattern.ts new file mode 100644 index 0000000..27b0ee1 --- /dev/null +++ b/src/Interfaces/IPattern.ts @@ -0,0 +1,57 @@ +import { IAvailableContainer } from './IAvailableContainer'; + +export interface IPattern { + /** + * Unique id for the pattern + */ + id: string + + /** + * Text to display in the sidebar + */ + text: string + + /** + * IAvailableContainer id used to wrap the children. + */ + wrapper: string + + /** + * List of ids of Pattern or IAvailableContainer + * If a IAvailableContainer and a Pattern have the same id, + * IAvailableContainer will be prioritized + */ + children: string[] +} + +export type ContainerOrPattern = IAvailableContainer | IPattern; + +export function GetPattern( + id: string, + configs: Map, + patterns: Map +): ContainerOrPattern | undefined { + let containerOrPattern: ContainerOrPattern | undefined = configs.get(id); + containerOrPattern = containerOrPattern ?? patterns.get(id); + return containerOrPattern; +} + +export function IsPattern( + id: string, + configs: Map, + patterns: Map +): boolean { + let containerOrPattern: ContainerOrPattern | undefined = configs.get(id); + + if (containerOrPattern !== undefined) { + return false; + } + + containerOrPattern = patterns.get(id); + + if (containerOrPattern === undefined) { + return false; + } + + return true; +}