Implement Pattern

This commit is contained in:
Eric NGUYEN 2022-09-14 16:55:28 +02:00
parent c0a7a26874
commit f6e4238b3d
4 changed files with 290 additions and 77 deletions

View file

@ -11,6 +11,7 @@ import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import Swal from 'sweetalert2'; import Swal from 'sweetalert2';
import { ApplyMargin, TransformX } from '../../../utils/svg'; import { ApplyMargin, TransformX } from '../../../utils/svg';
import { PropertyType } from '../../../Enums/PropertyType'; import { PropertyType } from '../../../Enums/PropertyType';
import { ContainerOrPattern, GetPattern, IPattern } from '../../../Interfaces/IPattern';
/** /**
* Select a container * Select a container
@ -220,82 +221,7 @@ export function AddContainers(
// Iterate over the containers // Iterate over the containers
availableContainers.forEach((availableContainer, typeIndex) => { availableContainers.forEach((availableContainer, typeIndex) => {
// Get the preset properties from the API // Get the preset properties from the API
const type = availableContainer.Type; AddNewContainerToParent(availableContainer, configuration, parentClone, index, typeIndex, newCounters, current.symbols, containerIds);
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);
}); });
// Update the state // Update the state
@ -311,6 +237,113 @@ export function AddContainers(
return history; 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` * 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 index Index where to insert to the new container
@ -380,9 +413,9 @@ function UpdateParentChildrenList(parentClone: IContainerModel | null | undefine
* @returns * @returns
*/ */
function InitializeDefaultChild( function InitializeDefaultChild(
newContainer: ContainerModel,
configuration: IConfiguration, configuration: IConfiguration,
containerConfig: IAvailableContainer, containerConfig: IAvailableContainer,
newContainer: ContainerModel,
newCounters: Record<string, number> newCounters: Record<string, number>
): void { ): void {
if (containerConfig.DefaultChildType === undefined) { if (containerConfig.DefaultChildType === undefined) {
@ -404,6 +437,7 @@ function InitializeDefaultChild(
seen.add(currentConfig.Type); seen.add(currentConfig.Type);
/// TODO: REFACTOR this with AddContainerToParent ///
const left: number = currentConfig.Margin?.left ?? 0; const left: number = currentConfig.Margin?.left ?? 0;
const bottom: number = currentConfig.Margin?.bottom ?? 0; const bottom: number = currentConfig.Margin?.bottom ?? 0;
const top: number = currentConfig.Margin?.top ?? 0; const top: number = currentConfig.Margin?.top ?? 0;
@ -440,6 +474,7 @@ function InitializeDefaultChild(
// And push it the the parent children // And push it the the parent children
parent.children.push(newChildContainer); parent.children.push(newChildContainer);
/// ///
// iterate // iterate
depth++; depth++;
@ -449,6 +484,116 @@ function InitializeDefaultChild(
} }
} }
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.) * Returns a new offset by applying an Add method (append, insert etc.)
* See AddMethod * See AddMethod

View file

@ -67,6 +67,8 @@ export interface IAvailableContainer {
/** /**
* (optional) * (optional)
* Disabled when Pattern is used.
*
* Replace a <rect> by a customized "SVG". It is not really an svg but it at least allows * Replace a <rect> 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 * 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. * Use {prop} to bind a property. Use {{ styleProp }} to use an object.
@ -81,6 +83,13 @@ export interface IAvailableContainer {
*/ */
DefaultChildType?: string 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 */ /** if true, show the dimension of the container */
ShowSelfDimensions?: boolean ShowSelfDimensions?: boolean

View file

@ -1,10 +1,12 @@
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
import { IAvailableContainer } from './IAvailableContainer'; import { IAvailableContainer } from './IAvailableContainer';
import { IAvailableSymbol } from './IAvailableSymbol'; import { IAvailableSymbol } from './IAvailableSymbol';
import { IPattern } from './IPattern';
/** Model of configuration for the application to configure it */ /** Model of configuration for the application to configure it */
export interface IConfiguration { export interface IConfiguration {
AvailableContainers: IAvailableContainer[] // TODO: Use a Map<string, IAvailableContainer> AvailableContainers: IAvailableContainer[] // TODO: Use a Map<string, IAvailableContainer>
AvailableSymbols: IAvailableSymbol[] // TODO: Use a Map<string, IAvailableContainer> AvailableSymbols: IAvailableSymbol[] // TODO: Use a Map<string, IAvailableContainer>
Patterns: IPattern[]
MainContainer: IAvailableContainer MainContainer: IAvailableContainer
} }

View file

@ -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<string, IAvailableContainer>,
patterns: Map<string, IPattern>
): ContainerOrPattern | undefined {
let containerOrPattern: ContainerOrPattern | undefined = configs.get(id);
containerOrPattern = containerOrPattern ?? patterns.get(id);
return containerOrPattern;
}
export function IsPattern(
id: string,
configs: Map<string, IAvailableContainer>,
patterns: Map<string, IPattern>
): boolean {
let containerOrPattern: ContainerOrPattern | undefined = configs.get(id);
if (containerOrPattern !== undefined) {
return false;
}
containerOrPattern = patterns.get(id);
if (containerOrPattern === undefined) {
return false;
}
return true;
}