Move all AddContainer functions to a different file and fix default.ts
This commit is contained in:
parent
fd06723fb9
commit
498ca9dc75
5 changed files with 469 additions and 487 deletions
433
src/Components/Editor/Actions/AddContainer.ts
Normal file
433
src/Components/Editor/Actions/AddContainer.ts
Normal file
|
@ -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<string, number>,
|
||||||
|
symbols: Map<string, ISymbolModel>,
|
||||||
|
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<string, number>,
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
|
): 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<string>([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<string, number>,
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
|
): void {
|
||||||
|
const patternId = containerConfig.Pattern;
|
||||||
|
|
||||||
|
if (patternId === undefined || patternId === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const configs: Map<string, IAvailableContainer> = new Map(configuration.AvailableContainers.map(config => [config.Type, config]));
|
||||||
|
const patterns: Map<string, IPattern> = 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;
|
||||||
|
}
|
|
@ -1,17 +1,12 @@
|
||||||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||||
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
|
||||||
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { FindContainerById, MakeIterator } from '../../../utils/itertools';
|
import { FindContainerById, MakeIterator } from '../../../utils/itertools';
|
||||||
import { GetCurrentHistory, UpdateCounters } from '../Editor';
|
import { GetCurrentHistory } from '../Editor';
|
||||||
import { AddMethod } from '../../../Enums/AddMethod';
|
|
||||||
import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer';
|
|
||||||
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../../utils/default';
|
|
||||||
import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
|
import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
|
||||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
import { ApplyMargin, TransformX } from '../../../utils/svg';
|
|
||||||
import { PropertyType } from '../../../Enums/PropertyType';
|
import { PropertyType } from '../../../Enums/PropertyType';
|
||||||
import { ContainerOrPattern, GetPattern, IPattern } from '../../../Interfaces/IPattern';
|
import { TransformX } from '../../../utils/svg';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a container
|
* Select a container
|
||||||
|
@ -148,483 +143,6 @@ function UnlinkContainerFromSymbols(symbols: Map<string, ISymbolModel>, 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<string, number>,
|
|
||||||
symbols: Map<string, ISymbolModel>,
|
|
||||||
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<string, number>
|
|
||||||
): 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<string>([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<string, number>,
|
|
||||||
symbols: Map<string, ISymbolModel>
|
|
||||||
): void {
|
|
||||||
const patternId = containerConfig.Pattern;
|
|
||||||
|
|
||||||
if (patternId === undefined || patternId === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configs: Map<string, IAvailableContainer> = new Map(configuration.AvailableContainers.map(config => [config.Type, config]));
|
|
||||||
const patterns: Map<string, IPattern> = 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
|
* Handled the property change event in the properties form
|
||||||
* @param key Property name
|
* @param key Property name
|
||||||
|
@ -667,6 +185,34 @@ export function OnPropertyChange(
|
||||||
return history;
|
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)
|
* Set the container with properties and behaviors (mutate)
|
||||||
* @param container Container to update
|
* @param container Container to update
|
||||||
|
@ -701,7 +247,7 @@ function SetContainer(
|
||||||
ApplyBehaviorsOnSiblingsChildren(container, symbols);
|
ApplyBehaviorsOnSiblingsChildren(container, symbols);
|
||||||
|
|
||||||
// sort the children list by their position
|
// sort the children list by their position
|
||||||
UpdateParentChildrenList(container.parent);
|
SortChildren(container.parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,7 +8,8 @@ import { ISetContainerListRequest } from '../../../Interfaces/ISetContainerListR
|
||||||
import { ISetContainerListResponse } from '../../../Interfaces/ISetContainerListResponse';
|
import { ISetContainerListResponse } from '../../../Interfaces/ISetContainerListResponse';
|
||||||
import { FindContainerById } from '../../../utils/itertools';
|
import { FindContainerById } from '../../../utils/itertools';
|
||||||
import { SetContainerList } from '../../API/api';
|
import { SetContainerList } from '../../API/api';
|
||||||
import { AddContainers, DeleteContainer } from './ContainerOperations';
|
import { AddContainers } from './AddContainer';
|
||||||
|
import { DeleteContainer } from './ContainerOperations';
|
||||||
|
|
||||||
export function GetAction(
|
export function GetAction(
|
||||||
action: IAction,
|
action: IAction,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||||
import { SVG } from '../SVG/SVG';
|
import { SVG } from '../SVG/SVG';
|
||||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
import { UI } from '../UI/UI';
|
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 { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
|
||||||
import { OnKey } from './Actions/Shortcuts';
|
import { OnKey } from './Actions/Shortcuts';
|
||||||
import { events as EVENTS } from '../../Events/EditorEvents';
|
import { events as EVENTS } from '../../Events/EditorEvents';
|
||||||
|
@ -14,6 +14,7 @@ import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, Se
|
||||||
import { FindContainerById } from '../../utils/itertools';
|
import { FindContainerById } from '../../utils/itertools';
|
||||||
import { IMenuAction, Menu } from '../Menu/Menu';
|
import { IMenuAction, Menu } from '../Menu/Menu';
|
||||||
import { GetAction } from './Actions/ContextMenuActions';
|
import { GetAction } from './Actions/ContextMenuActions';
|
||||||
|
import { AddContainerToSelectedContainer, AddContainer } from './Actions/AddContainer';
|
||||||
|
|
||||||
interface IEditorProps {
|
interface IEditorProps {
|
||||||
root: Element | Document
|
root: Element | Document
|
||||||
|
|
|
@ -94,6 +94,7 @@ export const DEFAULT_CONFIG: IConfiguration = {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
AvailableSymbols: [],
|
AvailableSymbols: [],
|
||||||
|
Patterns: [],
|
||||||
MainContainer: {
|
MainContainer: {
|
||||||
Type: 'Container',
|
Type: 'Container',
|
||||||
Width: 800,
|
Width: 800,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue