diff --git a/src/Components/Editor/Actions/AddContainer.ts b/src/Components/Editor/Actions/AddContainer.ts new file mode 100644 index 0000000..056a7c5 --- /dev/null +++ b/src/Components/Editor/Actions/AddContainer.ts @@ -0,0 +1,433 @@ +/** + * This file is dedicated to the AddContainer + */ + +import { AddMethod } from '../../../Enums/AddMethod'; +import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer'; +import { IConfiguration } from '../../../Interfaces/IConfiguration'; +import { IContainerModel, ContainerModel } from '../../../Interfaces/IContainerModel'; +import { IHistoryState } from '../../../Interfaces/IHistoryState'; +import { IPattern, GetPattern, ContainerOrPattern } from '../../../Interfaces/IPattern'; +import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; +import { GetDefaultContainerProps, DEFAULTCHILDTYPE_MAX_DEPTH, DEFAULTCHILDTYPE_ALLOW_CYCLIC } from '../../../utils/default'; +import { FindContainerById } from '../../../utils/itertools'; +import { ApplyMargin } from '../../../utils/svg'; +import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors'; +import { GetCurrentHistory, UpdateCounters } from '../Editor'; +import { SortChildren } from './ContainerOperations'; + +/** + * Add a new container to a selected container + * @param type The type of container + * @param configuration Configuration of the App + * @param fullHistory History of the editor + * @param historyCurrentStep Current step + * @returns void + */ +export function AddContainerToSelectedContainer( + type: string, + selected: IContainerModel | undefined, + configuration: IConfiguration, + fullHistory: IHistoryState[], + historyCurrentStep: number +): IHistoryState[] | null { + if (selected === null || + selected === undefined) { + return null; + } + + const parent = selected; + return AddContainer( + parent.children.length, + type, + parent.properties.id, + configuration, + fullHistory, + historyCurrentStep + ); +} + +/** + * Create and add a new container at `index` in children of parent of `parentId` + * @param index Index where to insert to the new container + * @param type Type of container + * @param parentId Parent in which to insert the new container + * @param configuration Configuration of the app + * @param fullHistory History of the editor + * @param historyCurrentStep Current step + * @returns void + */ +export function AddContainers( + index: number, + availableContainers: IAvailableContainer[], + parentId: string, + configuration: IConfiguration, + fullHistory: IHistoryState[], + historyCurrentStep: number +): IHistoryState[] { + const history = GetCurrentHistory(fullHistory, historyCurrentStep); + const current = history[history.length - 1]; + + // Deep clone the main container for the history + const clone: IContainerModel = structuredClone(current.mainContainer); + + // Find the parent in the clone + const parentClone: IContainerModel | undefined = FindContainerById( + clone, parentId + ); + + if (parentClone === null || parentClone === undefined) { + throw new Error('[AddContainer] Container model was not found among children of the main container!'); + } + + // Deep clone the counters + const newCounters = Object.assign({}, current.typeCounters); + + // containerIds is used for logging purpose (see setHistory below) + const containerIds: string[] = []; + + // Iterate over the containers + availableContainers.forEach((availableContainer, typeIndex) => { + // Get the preset properties from the API + AddNewContainerToParent(availableContainer, configuration, parentClone, index, typeIndex, newCounters, current.symbols, containerIds); + }); + + // Update the state + history.push({ + lastAction: `Add [${containerIds.join(', ')}] in ${parentClone.properties.id}`, + mainContainer: clone, + selectedContainerId: parentClone.properties.id, + typeCounters: newCounters, + symbols: structuredClone(current.symbols), + selectedSymbolId: current.selectedSymbolId + }); + + 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 + SortChildren(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, + symbols + ); + } 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 + * @param type Type of container + * @param parentId Parent in which to insert the new container + * @param configuration Configuration of the app + * @param fullHistory History of the editor + * @param historyCurrentStep Current step + * @param setHistory State setter of History + * @param setHistoryCurrentStep State setter of the current step + * @returns new history + */ +export function AddContainer( + index: number, + type: string, + parentId: string, + configuration: IConfiguration, + fullHistory: IHistoryState[], + historyCurrentStep: number +): IHistoryState[] { + // just call AddContainers with an array on a single element + return AddContainers( + index, + // eslint-disable-next-line @typescript-eslint/naming-convention + [{ Type: type }], + parentId, + configuration, + fullHistory, + historyCurrentStep + ); +} + +/** + * Initialize the children of the container + * @param configuration Current configuration of the app + * @param containerConfig The new container config used to read the default child type + * @param newContainer The new container to initialize its default children + * @param newCounters Type counter used for unique ids + * @returns + */ +function InitializeDefaultChild( + newContainer: ContainerModel, + configuration: IConfiguration, + containerConfig: IAvailableContainer, + newCounters: Record, + symbols: Map +): void { + if (containerConfig.DefaultChildType === undefined) { + return; + } + + let currentConfig = configuration.AvailableContainers + .find(option => option.Type === containerConfig.DefaultChildType); + let parent = newContainer; + let depth = 0; + const seen = new Set([containerConfig.Type]); + + while (currentConfig !== undefined && + depth <= DEFAULTCHILDTYPE_MAX_DEPTH + ) { + if (!DEFAULTCHILDTYPE_ALLOW_CYCLIC && seen.has(currentConfig.Type)) { + return; + } + + seen.add(currentConfig.Type); + + const newChildContainer = AddNewContainerToParent( + currentConfig, + configuration, + parent, + 0, 0, + newCounters, + symbols + ); + + // iterate + depth++; + parent = newChildContainer; + currentConfig = configuration.AvailableContainers + .find(option => option.Type === (currentConfig as IAvailableContainer).DefaultChildType); + } +} + +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; + + if (pattern.children === undefined) { + const container = containerOrPattern as IAvailableContainer; + // Add Container + AddNewContainerToParent( + container, + configuration, + newContainer, + 0, 0, + newCounters, + symbols + ); + } + + // 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 + ); + 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, + undefined, + 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 + * @param index Index of the container + * @param containerConfig Configuration of a container + * @param parent Parent container + * @param x Additionnal offset + * @returns New offset + */ +function ApplyAddMethod( + index: number, + containerConfig: IAvailableContainer, + parent: IContainerModel, + x: number +): number { + if (index > 0 && ( + containerConfig.AddMethod === undefined || + containerConfig.AddMethod === null || + containerConfig.AddMethod === AddMethod.Append + )) { + // Append method (default) + const lastChild: IContainerModel | undefined = parent.children + .at(index - 1); + + if (lastChild !== undefined) { + x += (lastChild.properties.x + lastChild.properties.width); + } + } + return x; +} diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts index d589d96..7457a1f 100644 --- a/src/Components/Editor/Actions/ContainerOperations.ts +++ b/src/Components/Editor/Actions/ContainerOperations.ts @@ -1,17 +1,12 @@ import { IHistoryState } from '../../../Interfaces/IHistoryState'; -import { IConfiguration } from '../../../Interfaces/IConfiguration'; import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel'; import { FindContainerById, MakeIterator } from '../../../utils/itertools'; -import { GetCurrentHistory, UpdateCounters } from '../Editor'; -import { AddMethod } from '../../../Enums/AddMethod'; -import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer'; -import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../../utils/default'; +import { GetCurrentHistory } from '../Editor'; import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors'; 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'; +import { TransformX } from '../../../utils/svg'; /** * Select a container @@ -148,483 +143,6 @@ function UnlinkContainerFromSymbols(symbols: Map, containe } } -/** - * Add a new container to a selected container - * @param type The type of container - * @param configuration Configuration of the App - * @param fullHistory History of the editor - * @param historyCurrentStep Current step - * @returns void - */ -export function AddContainerToSelectedContainer( - type: string, - selected: IContainerModel | undefined, - configuration: IConfiguration, - fullHistory: IHistoryState[], - historyCurrentStep: number -): IHistoryState[] | null { - if (selected === null || - selected === undefined) { - return null; - } - - const parent = selected; - return AddContainer( - parent.children.length, - type, - parent.properties.id, - configuration, - fullHistory, - historyCurrentStep - ); -} - -/** - * Create and add a new container at `index` in children of parent of `parentId` - * @param index Index where to insert to the new container - * @param type Type of container - * @param parentId Parent in which to insert the new container - * @param configuration Configuration of the app - * @param fullHistory History of the editor - * @param historyCurrentStep Current step - * @returns void - */ -export function AddContainers( - index: number, - availableContainers: IAvailableContainer[], - parentId: string, - configuration: IConfiguration, - fullHistory: IHistoryState[], - historyCurrentStep: number -): IHistoryState[] { - const history = GetCurrentHistory(fullHistory, historyCurrentStep); - const current = history[history.length - 1]; - - // Deep clone the main container for the history - const clone: IContainerModel = structuredClone(current.mainContainer); - - // Find the parent in the clone - const parentClone: IContainerModel | undefined = FindContainerById( - clone, parentId - ); - - if (parentClone === null || parentClone === undefined) { - throw new Error('[AddContainer] Container model was not found among children of the main container!'); - } - - // Deep clone the counters - const newCounters = Object.assign({}, current.typeCounters); - - // containerIds is used for logging purpose (see setHistory below) - const containerIds: string[] = []; - - // Iterate over the containers - availableContainers.forEach((availableContainer, typeIndex) => { - // Get the preset properties from the API - AddNewContainerToParent(availableContainer, configuration, parentClone, index, typeIndex, newCounters, current.symbols, containerIds); - }); - - // Update the state - history.push({ - lastAction: `Add [${containerIds.join(', ')}] in ${parentClone.properties.id}`, - mainContainer: clone, - selectedContainerId: parentClone.properties.id, - typeCounters: newCounters, - symbols: structuredClone(current.symbols), - selectedSymbolId: current.selectedSymbolId - }); - - 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 - * @param type Type of container - * @param parentId Parent in which to insert the new container - * @param configuration Configuration of the app - * @param fullHistory History of the editor - * @param historyCurrentStep Current step - * @param setHistory State setter of History - * @param setHistoryCurrentStep State setter of the current step - * @returns new history - */ -export function AddContainer( - index: number, - type: string, - parentId: string, - configuration: IConfiguration, - fullHistory: IHistoryState[], - historyCurrentStep: number -): IHistoryState[] { - // just call AddContainers with an array on a single element - return AddContainers( - index, - // eslint-disable-next-line @typescript-eslint/naming-convention - [{ Type: type }], - parentId, - configuration, - fullHistory, - historyCurrentStep - ); -} - -/** - * Sort the parent children by x - * @param parentClone The clone used for the sort - * @returns void - */ -function UpdateParentChildrenList(parentClone: IContainerModel | null | undefined): void { - if (parentClone === null || parentClone === undefined) { - return; - } - const children = parentClone.children; - parentClone.children.sort( - (a, b) => { - const xA = TransformX(a.properties.x, a.properties.width, a.properties.xPositionReference); - const xB = TransformX(b.properties.x, b.properties.width, b.properties.xPositionReference); - if (xA < xB) { - return -1; - } - if (xB < xA) { - return 1; - } - // xA = xB - const indexA = children.indexOf(a); - const indexB = children.indexOf(b); - return indexA - indexB; - } - ); -} - -/** - * Initialize the children of the container - * @param configuration Current configuration of the app - * @param containerConfig The new container config used to read the default child type - * @param newContainer The new container to initialize its default children - * @param newCounters Type counter used for unique ids - * @returns - */ -function InitializeDefaultChild( - newContainer: ContainerModel, - configuration: IConfiguration, - containerConfig: IAvailableContainer, - newCounters: Record -): void { - if (containerConfig.DefaultChildType === undefined) { - return; - } - - let currentConfig = configuration.AvailableContainers - .find(option => option.Type === containerConfig.DefaultChildType); - let parent = newContainer; - let depth = 0; - const seen = new Set([containerConfig.Type]); - - while (currentConfig !== undefined && - depth <= DEFAULTCHILDTYPE_MAX_DEPTH - ) { - if (!DEFAULTCHILDTYPE_ALLOW_CYCLIC && seen.has(currentConfig.Type)) { - return; - } - - 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; - const right: number = currentConfig.Margin?.right ?? 0; - let x = currentConfig.X ?? 0; - let y = currentConfig.Y ?? 0; - let width = currentConfig.Width ?? currentConfig.MaxWidth ?? currentConfig.MinWidth ?? parent.properties.width; - let height = currentConfig.Height ?? parent.properties.height; - - ({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right)); - - UpdateCounters(newCounters, currentConfig.Type); - const count = newCounters[currentConfig.Type]; - const defaultChildProperties = GetDefaultContainerProps( - currentConfig.Type, - count, - parent, - x, - y, - width, - height, - currentConfig - ); - - // Create the container - const newChildContainer = new ContainerModel( - parent, - defaultChildProperties, - [], - { - type: currentConfig.Type - } - ); - - // And push it the the parent children - parent.children.push(newChildContainer); - /// /// - - // iterate - depth++; - parent = newChildContainer; - currentConfig = configuration.AvailableContainers - .find(option => option.Type === (currentConfig as IAvailableContainer).DefaultChildType); - } -} - -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 - * @param index Index of the container - * @param containerConfig Configuration of a container - * @param parent Parent container - * @param x Additionnal offset - * @returns New offset - */ -function ApplyAddMethod( - index: number, - containerConfig: IAvailableContainer, - parent: IContainerModel, - x: number -): number { - if (index > 0 && ( - containerConfig.AddMethod === undefined || - containerConfig.AddMethod === null || - containerConfig.AddMethod === AddMethod.Append - )) { - // Append method (default) - const lastChild: IContainerModel | undefined = parent.children - .at(index - 1); - - if (lastChild !== undefined) { - x += (lastChild.properties.x + lastChild.properties.width); - } - } - return x; -} - /** * Handled the property change event in the properties form * @param key Property name @@ -667,6 +185,34 @@ export function OnPropertyChange( return history; } +/** + * Sort the parent children by x + * @param parentClone The clone used for the sort + * @returns void + */ +export function SortChildren(parentClone: IContainerModel | null | undefined): void { + if (parentClone === null || parentClone === undefined) { + return; + } + const children = parentClone.children; + parentClone.children.sort( + (a, b) => { + const xA = TransformX(a.properties.x, a.properties.width, a.properties.xPositionReference); + const xB = TransformX(b.properties.x, b.properties.width, b.properties.xPositionReference); + if (xA < xB) { + return -1; + } + if (xB < xA) { + return 1; + } + // xA = xB + const indexA = children.indexOf(a); + const indexB = children.indexOf(b); + return indexA - indexB; + } + ); +} + /** * Set the container with properties and behaviors (mutate) * @param container Container to update @@ -701,7 +247,7 @@ function SetContainer( ApplyBehaviorsOnSiblingsChildren(container, symbols); // sort the children list by their position - UpdateParentChildrenList(container.parent); + SortChildren(container.parent); } /** diff --git a/src/Components/Editor/Actions/ContextMenuActions.ts b/src/Components/Editor/Actions/ContextMenuActions.ts index 85ae2b3..ae7ad51 100644 --- a/src/Components/Editor/Actions/ContextMenuActions.ts +++ b/src/Components/Editor/Actions/ContextMenuActions.ts @@ -8,7 +8,8 @@ import { ISetContainerListRequest } from '../../../Interfaces/ISetContainerListR import { ISetContainerListResponse } from '../../../Interfaces/ISetContainerListResponse'; import { FindContainerById } from '../../../utils/itertools'; import { SetContainerList } from '../../API/api'; -import { AddContainers, DeleteContainer } from './ContainerOperations'; +import { AddContainers } from './AddContainer'; +import { DeleteContainer } from './ContainerOperations'; export function GetAction( action: IAction, diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 91c919c..6411fa3 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -4,7 +4,7 @@ import { IConfiguration } from '../../Interfaces/IConfiguration'; import { SVG } from '../SVG/SVG'; import { IHistoryState } from '../../Interfaces/IHistoryState'; import { UI } from '../UI/UI'; -import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, OnPropertyChange, AddContainer } from './Actions/ContainerOperations'; +import { SelectContainer, DeleteContainer, OnPropertyChange } from './Actions/ContainerOperations'; import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save'; import { OnKey } from './Actions/Shortcuts'; import { events as EVENTS } from '../../Events/EditorEvents'; @@ -14,6 +14,7 @@ import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, Se import { FindContainerById } from '../../utils/itertools'; import { IMenuAction, Menu } from '../Menu/Menu'; import { GetAction } from './Actions/ContextMenuActions'; +import { AddContainerToSelectedContainer, AddContainer } from './Actions/AddContainer'; interface IEditorProps { root: Element | Document diff --git a/src/utils/default.ts b/src/utils/default.ts index 04fc6c1..7eb4a3b 100644 --- a/src/utils/default.ts +++ b/src/utils/default.ts @@ -94,6 +94,7 @@ export const DEFAULT_CONFIG: IConfiguration = { } ], AvailableSymbols: [], + Patterns: [], MainContainer: { Type: 'Container', Width: 800,