Merged PR 212: Optimize FindChildrenById from O(n) to O(1)
Optimize FindChildrenById from O(n) to O(1): - Deprecate FindContainerByIdDFS - Container: Replace Children to string[] - Add HashMap to IHistoryState that contains all containers To access a container by id now cost O(1) without any additional cost + Implement CICD for SVGLibs
This commit is contained in:
parent
466ef2b08b
commit
c256a76e01
45 changed files with 775 additions and 450 deletions
|
@ -69,12 +69,11 @@ export function AddContainers(
|
|||
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);
|
||||
const containers = structuredClone(current.containers);
|
||||
|
||||
// Find the parent in the clone
|
||||
const parentClone: IContainerModel | undefined = FindContainerById(
|
||||
clone, parentId
|
||||
containers, parentId
|
||||
);
|
||||
|
||||
if (parentClone === null || parentClone === undefined) {
|
||||
|
@ -90,14 +89,15 @@ export function AddContainers(
|
|||
// 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);
|
||||
AddNewContainerToParent(availableContainer, configuration, containers, parentClone, index, typeIndex, newCounters, current.symbols, containerIds);
|
||||
});
|
||||
|
||||
// Update the state
|
||||
history.push({
|
||||
lastAction: `Add [${containerIds.join(', ')}] in ${parentClone.properties.id}`,
|
||||
mainContainer: clone,
|
||||
mainContainer: current.mainContainer,
|
||||
selectedContainerId: parentClone.properties.id,
|
||||
containers,
|
||||
typeCounters: newCounters,
|
||||
symbols: structuredClone(current.symbols),
|
||||
selectedSymbolId: current.selectedSymbolId
|
||||
|
@ -109,6 +109,7 @@ export function AddContainers(
|
|||
function AddNewContainerToParent(
|
||||
availableContainer: IAvailableContainer,
|
||||
configuration: IConfiguration,
|
||||
containers: Map<string, IContainerModel>,
|
||||
parentClone: IContainerModel,
|
||||
index: number,
|
||||
typeIndex: number,
|
||||
|
@ -143,7 +144,7 @@ function AddNewContainerToParent(
|
|||
({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
|
||||
|
||||
// Apply an add method (append or insert/replace)
|
||||
({ x, y } = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x, y));
|
||||
({ x, y } = ApplyAddMethod(containers, index + typeIndex, containerConfig, parentClone, x, y));
|
||||
|
||||
// Set the counter of the object type in order to assign an unique id
|
||||
UpdateCounters(newCounters, type);
|
||||
|
@ -170,22 +171,25 @@ function AddNewContainerToParent(
|
|||
}
|
||||
);
|
||||
|
||||
// Register the container in the hashmap
|
||||
containers.set(newContainer.properties.id, newContainer);
|
||||
|
||||
// Add it to the parent
|
||||
if (index === parentClone.children.length) {
|
||||
parentClone.children.push(newContainer);
|
||||
parentClone.children.push(newContainer.properties.id);
|
||||
} else {
|
||||
parentClone.children.splice(index, 0, newContainer);
|
||||
parentClone.children.splice(index, 0, newContainer.properties.id);
|
||||
}
|
||||
|
||||
// Sort the parent children by x
|
||||
SortChildren(parentClone);
|
||||
SortChildren(containers, parentClone);
|
||||
|
||||
/// Handle behaviors here ///
|
||||
// Apply the behaviors (flex, rigid, anchor)
|
||||
ApplyBehaviors(newContainer, symbols);
|
||||
ApplyBehaviors(containers, newContainer, symbols);
|
||||
|
||||
// Then, apply the behaviors on its siblings (mostly for flex)
|
||||
ApplyBehaviorsOnSiblingsChildren(newContainer, symbols);
|
||||
ApplyBehaviorsOnSiblingsChildren(containers, newContainer, symbols);
|
||||
|
||||
// Initialize default children of the container
|
||||
if (initChilds) {
|
||||
|
@ -194,6 +198,7 @@ function AddNewContainerToParent(
|
|||
newContainer,
|
||||
configuration,
|
||||
containerConfig,
|
||||
containers,
|
||||
newCounters,
|
||||
symbols
|
||||
);
|
||||
|
@ -201,6 +206,7 @@ function AddNewContainerToParent(
|
|||
InitializeChildrenWithPattern(
|
||||
newContainer,
|
||||
configuration,
|
||||
containers,
|
||||
containerConfig,
|
||||
newCounters,
|
||||
symbols
|
||||
|
@ -258,6 +264,7 @@ function InitializeDefaultChild(
|
|||
newContainer: ContainerModel,
|
||||
configuration: IConfiguration,
|
||||
containerConfig: IAvailableContainer,
|
||||
containers: Map<string, IContainerModel>,
|
||||
newCounters: Record<string, number>,
|
||||
symbols: Map<string, ISymbolModel>
|
||||
): void {
|
||||
|
@ -276,6 +283,7 @@ function InitializeDefaultChild(
|
|||
AddNewContainerToParent(
|
||||
currentConfig,
|
||||
configuration,
|
||||
containers,
|
||||
parent,
|
||||
0, 0,
|
||||
newCounters,
|
||||
|
@ -286,6 +294,7 @@ function InitializeDefaultChild(
|
|||
function InitializeChildrenWithPattern(
|
||||
newContainer: ContainerModel,
|
||||
configuration: IConfiguration,
|
||||
containers: Map<string, IContainerModel>,
|
||||
containerConfig: IAvailableContainer,
|
||||
newCounters: Record<string, number>,
|
||||
symbols: Map<string, ISymbolModel>
|
||||
|
@ -307,6 +316,7 @@ function InitializeChildrenWithPattern(
|
|||
AddNewContainerToParent(
|
||||
container,
|
||||
configuration,
|
||||
containers,
|
||||
newContainer,
|
||||
0, 0,
|
||||
newCounters,
|
||||
|
@ -334,6 +344,7 @@ function InitializeChildrenWithPattern(
|
|||
AddNewContainerToParent(
|
||||
containerConfig,
|
||||
configuration,
|
||||
containers,
|
||||
node.parent,
|
||||
0, 0,
|
||||
newCounters,
|
||||
|
@ -350,9 +361,10 @@ function InitializeChildrenWithPattern(
|
|||
console.warn(`[InitializeChildrenFromPattern] IAvailableContainer from pattern was not found in the configuration: ${pattern.wrapper}.
|
||||
Process will ignore the container.`);
|
||||
} else {
|
||||
parent = AddNewContainerToParent(
|
||||
const newChildContainer = AddNewContainerToParent(
|
||||
container,
|
||||
configuration,
|
||||
containers,
|
||||
parent,
|
||||
0, 0,
|
||||
newCounters,
|
||||
|
@ -360,6 +372,9 @@ function InitializeChildrenWithPattern(
|
|||
undefined,
|
||||
false
|
||||
);
|
||||
|
||||
// iterate
|
||||
parent = newChildContainer;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,6 +413,7 @@ interface Node {
|
|||
* @returns New offset
|
||||
*/
|
||||
function ApplyAddMethod(
|
||||
containers: Map<string, IContainerModel>,
|
||||
index: number,
|
||||
containerConfig: IAvailableContainer,
|
||||
parent: IContainerModel,
|
||||
|
@ -410,8 +426,8 @@ function ApplyAddMethod(
|
|||
containerConfig.AddMethod === AddMethod.Append
|
||||
)) {
|
||||
// Append method (default)
|
||||
const lastChild: IContainerModel | undefined = parent.children
|
||||
.at(index - 1);
|
||||
const lastChildId: string = parent.children[index - 1];
|
||||
const lastChild = FindContainerById(containers, lastChildId);
|
||||
|
||||
if (lastChild !== undefined) {
|
||||
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
|
||||
|
|
|
@ -24,7 +24,8 @@ export function SelectContainer(
|
|||
|
||||
history.push({
|
||||
lastAction: `Select ${containerId}`,
|
||||
mainContainer: structuredClone(current.mainContainer),
|
||||
mainContainer: current.mainContainer,
|
||||
containers: structuredClone(current.containers),
|
||||
selectedContainerId: containerId,
|
||||
typeCounters: Object.assign({}, current.typeCounters),
|
||||
symbols: structuredClone(current.symbols),
|
||||
|
@ -48,8 +49,9 @@ export function DeleteContainer(
|
|||
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||
const current = history[history.length - 1];
|
||||
|
||||
const mainContainerClone: IContainerModel = structuredClone(current.mainContainer);
|
||||
const container = FindContainerById(mainContainerClone, containerId);
|
||||
const containers = structuredClone(current.containers);
|
||||
const mainContainerClone: IContainerModel | undefined = FindContainerById(containers, current.mainContainer);
|
||||
const container = FindContainerById(containers, containerId);
|
||||
|
||||
if (container === undefined) {
|
||||
throw new Error(`[DeleteContainer] Tried to delete a container that is not present in the main container: ${containerId}`);
|
||||
|
@ -71,21 +73,22 @@ export function DeleteContainer(
|
|||
}
|
||||
|
||||
const newSymbols = structuredClone(current.symbols);
|
||||
UnlinkContainerFromSymbols(newSymbols, container);
|
||||
UnlinkContainerFromSymbols(containers, newSymbols, container);
|
||||
|
||||
const index = container.parent.children.indexOf(container);
|
||||
if (index > -1) {
|
||||
const index = container.parent.children.indexOf(container.properties.id);
|
||||
const success = containers.delete(container.properties.id);
|
||||
if (index > -1 && success) {
|
||||
container.parent.children.splice(index, 1);
|
||||
} else {
|
||||
throw new Error('[DeleteContainer] Could not find container among parent\'s children');
|
||||
}
|
||||
|
||||
ApplyBehaviorsOnSiblings(container, current.symbols);
|
||||
ApplyBehaviorsOnSiblings(containers, container, current.symbols);
|
||||
|
||||
// Select the previous container
|
||||
// or select the one above
|
||||
const selectedContainerId = GetSelectedContainerOnDelete(
|
||||
mainContainerClone,
|
||||
containers,
|
||||
current.selectedContainerId,
|
||||
container.parent,
|
||||
index
|
||||
|
@ -93,7 +96,8 @@ export function DeleteContainer(
|
|||
|
||||
history.push({
|
||||
lastAction: `Delete ${containerId}`,
|
||||
mainContainer: mainContainerClone,
|
||||
mainContainer: current.mainContainer,
|
||||
containers,
|
||||
selectedContainerId,
|
||||
typeCounters: Object.assign({}, current.typeCounters),
|
||||
symbols: newSymbols,
|
||||
|
@ -115,16 +119,15 @@ export function DeleteContainer(
|
|||
* @returns {IContainerModel} Next selected container
|
||||
*/
|
||||
function GetSelectedContainerOnDelete(
|
||||
mainContainerClone: IContainerModel,
|
||||
containers: Map<string, IContainerModel>,
|
||||
selectedContainerId: string,
|
||||
parent: IContainerModel,
|
||||
index: number
|
||||
): string {
|
||||
const newSelectedContainer = FindContainerById(mainContainerClone, selectedContainerId) ??
|
||||
const newSelectedContainerId = FindContainerById(containers, selectedContainerId)?.properties.id ??
|
||||
parent.children.at(index) ??
|
||||
parent.children.at(index - 1) ??
|
||||
parent;
|
||||
const newSelectedContainerId = newSelectedContainer.properties.id;
|
||||
parent.properties.id;
|
||||
return newSelectedContainerId;
|
||||
}
|
||||
|
||||
|
@ -134,8 +137,12 @@ function GetSelectedContainerOnDelete(
|
|||
* @param symbols Symbols to update
|
||||
* @param container Container to unlink
|
||||
*/
|
||||
function UnlinkContainerFromSymbols(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
|
||||
const it = MakeDFSIterator(container);
|
||||
function UnlinkContainerFromSymbols(
|
||||
containers: Map<string, IContainerModel>,
|
||||
symbols: Map<string, ISymbolModel>,
|
||||
container: IContainerModel
|
||||
): void {
|
||||
const it = MakeDFSIterator(container, containers);
|
||||
for (const child of it) {
|
||||
const symbol = symbols.get(child.properties.linkedSymbolId);
|
||||
if (symbol === undefined) {
|
||||
|
@ -167,18 +174,19 @@ export function OnPropertyChange(
|
|||
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
||||
}
|
||||
|
||||
const mainContainerClone: IContainerModel = structuredClone(current.mainContainer);
|
||||
const container: ContainerModel | undefined = FindContainerById(mainContainerClone, selected.properties.id);
|
||||
const containers = structuredClone(current.containers);
|
||||
const container: ContainerModel | undefined = FindContainerById(containers, selected.properties.id);
|
||||
|
||||
if (container === null || container === undefined) {
|
||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
||||
}
|
||||
|
||||
SetContainer(container, key, value, type, current.symbols);
|
||||
SetContainer(containers, container, key, value, type, current.symbols);
|
||||
|
||||
history.push({
|
||||
lastAction: `Change ${key} of ${container.properties.id}`,
|
||||
mainContainer: mainContainerClone,
|
||||
mainContainer: current.mainContainer,
|
||||
containers,
|
||||
selectedContainerId: container.properties.id,
|
||||
typeCounters: Object.assign({}, current.typeCounters),
|
||||
symbols: structuredClone(current.symbols),
|
||||
|
@ -189,20 +197,30 @@ export function OnPropertyChange(
|
|||
|
||||
/**
|
||||
* Sort the parent children by x
|
||||
* @param parentClone The clone used for the sort
|
||||
* @param parent The clone used for the sort
|
||||
* @returns void
|
||||
*/
|
||||
export function SortChildren(parentClone: IContainerModel | null | undefined): void {
|
||||
if (parentClone === null || parentClone === undefined) {
|
||||
export function SortChildren(
|
||||
containers: Map<string, IContainerModel>,
|
||||
parent: IContainerModel | null | undefined
|
||||
): void {
|
||||
if (parent === null || parent === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isHorizontal = parentClone.properties.orientation === Orientation.Horizontal;
|
||||
const children = parentClone.children;
|
||||
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
|
||||
const children = parent.children;
|
||||
|
||||
if (!isHorizontal) {
|
||||
parentClone.children.sort(
|
||||
(a, b) => {
|
||||
parent.children.sort(
|
||||
(aId, bId) => {
|
||||
const a = FindContainerById(containers, aId);
|
||||
const b = FindContainerById(containers, bId);
|
||||
|
||||
if (a === undefined || b === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const yA = TransformY(a.properties.y, a.properties.height, a.properties.positionReference);
|
||||
const yB = TransformY(b.properties.y, b.properties.height, b.properties.positionReference);
|
||||
if (yA < yB) {
|
||||
|
@ -212,16 +230,23 @@ export function SortChildren(parentClone: IContainerModel | null | undefined): v
|
|||
return 1;
|
||||
}
|
||||
// xA = xB
|
||||
const indexA = children.indexOf(a);
|
||||
const indexB = children.indexOf(b);
|
||||
const indexA = children.indexOf(aId);
|
||||
const indexB = children.indexOf(bId);
|
||||
return indexA - indexB;
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
parentClone.children.sort(
|
||||
(a, b) => {
|
||||
parent.children.sort(
|
||||
(aId, bId) => {
|
||||
const a = FindContainerById(containers, aId);
|
||||
const b = FindContainerById(containers, bId);
|
||||
|
||||
if (a === undefined || b === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const xA = TransformX(a.properties.x, a.properties.width, a.properties.positionReference);
|
||||
const xB = TransformX(b.properties.x, b.properties.width, b.properties.positionReference);
|
||||
if (xA < xB) {
|
||||
|
@ -231,8 +256,8 @@ export function SortChildren(parentClone: IContainerModel | null | undefined): v
|
|||
return 1;
|
||||
}
|
||||
// xA = xB
|
||||
const indexA = children.indexOf(a);
|
||||
const indexB = children.indexOf(b);
|
||||
const indexA = children.indexOf(aId);
|
||||
const indexB = children.indexOf(bId);
|
||||
return indexA - indexB;
|
||||
}
|
||||
);
|
||||
|
@ -247,6 +272,7 @@ export function SortChildren(parentClone: IContainerModel | null | undefined): v
|
|||
* @param symbols Current list of symbols
|
||||
*/
|
||||
function SetContainer(
|
||||
containers: Map<string, IContainerModel>,
|
||||
container: ContainerModel,
|
||||
key: string, value: string | number | boolean | number[],
|
||||
type: PropertyType,
|
||||
|
@ -267,13 +293,13 @@ function SetContainer(
|
|||
);
|
||||
|
||||
// sort the children list by their position
|
||||
SortChildren(container.parent);
|
||||
SortChildren(containers, container.parent);
|
||||
|
||||
// Apply special behaviors: rigid, flex, symbol, anchor
|
||||
ApplyBehaviors(container, symbols);
|
||||
ApplyBehaviors(containers, container, symbols);
|
||||
|
||||
// Apply special behaviors on siblings
|
||||
ApplyBehaviorsOnSiblingsChildren(container, symbols);
|
||||
ApplyBehaviorsOnSiblingsChildren(containers, container, symbols);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,7 +21,7 @@ export function GetAction(
|
|||
): (target: HTMLElement) => void {
|
||||
return (target: HTMLElement) => {
|
||||
const id = target.id;
|
||||
const container = FindContainerById(currentState.mainContainer, id);
|
||||
const container = FindContainerById(currentState.containers, id);
|
||||
|
||||
if (container === undefined) {
|
||||
Swal.fire({
|
||||
|
@ -33,7 +33,7 @@ export function GetAction(
|
|||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
const { prev, next } = GetPreviousAndNextSiblings(container);
|
||||
const { prev, next } = GetPreviousAndNextSiblings(currentState.containers, container);
|
||||
|
||||
const request: ISetContainerListRequest = {
|
||||
Container: container,
|
||||
|
@ -59,18 +59,18 @@ export function GetAction(
|
|||
};
|
||||
}
|
||||
|
||||
function GetPreviousAndNextSiblings(container: IContainerModel): { prev: IContainerModel | undefined, next: IContainerModel | undefined } {
|
||||
function GetPreviousAndNextSiblings(containers: Map<string, IContainerModel>, container: IContainerModel): { prev: IContainerModel | undefined, next: IContainerModel | undefined } {
|
||||
let prev;
|
||||
let next;
|
||||
if (container.parent !== undefined &&
|
||||
container.parent !== null &&
|
||||
container.parent.children.length > 1) {
|
||||
const index = container.parent.children.indexOf(container);
|
||||
const index = container.parent.children.indexOf(container.properties.id);
|
||||
if (index > 0) {
|
||||
prev = container.parent.children[index - 1];
|
||||
prev = FindContainerById(containers, container.parent.children[index - 1]);
|
||||
}
|
||||
if (index < container.parent.children.length - 1) {
|
||||
next = container.parent.children[index + 1];
|
||||
next = FindContainerById(containers, container.parent.children[index + 1]);
|
||||
}
|
||||
}
|
||||
return { prev, next };
|
||||
|
@ -144,7 +144,7 @@ function HandleReplace(
|
|||
throw new Error('[ReplaceContainer] Cannot replace a container that does not exists');
|
||||
}
|
||||
|
||||
const index = selectedContainer.parent.children.indexOf(selectedContainer);
|
||||
const index = selectedContainer.parent.children.indexOf(selectedContainer.properties.id);
|
||||
|
||||
const newHistoryAfterDelete = DeleteContainer(
|
||||
selectedContainer.properties.id,
|
||||
|
|
|
@ -36,6 +36,7 @@ export function AddSymbol(
|
|||
history.push({
|
||||
lastAction: `Add ${name}`,
|
||||
mainContainer: structuredClone(current.mainContainer),
|
||||
containers: structuredClone(current.containers),
|
||||
selectedContainerId: current.selectedContainerId,
|
||||
typeCounters: newCounters,
|
||||
symbols: newSymbols,
|
||||
|
@ -55,6 +56,7 @@ export function SelectSymbol(
|
|||
history.push({
|
||||
lastAction: `Select ${symbolId}`,
|
||||
mainContainer: structuredClone(current.mainContainer),
|
||||
containers: structuredClone(current.containers),
|
||||
selectedContainerId: current.selectedContainerId,
|
||||
typeCounters: structuredClone(current.typeCounters),
|
||||
symbols: structuredClone(current.symbols),
|
||||
|
@ -78,15 +80,15 @@ export function DeleteSymbol(
|
|||
throw new Error(`[DeleteSymbol] Could not find symbol in the current state!: ${symbolId}`);
|
||||
}
|
||||
|
||||
const newMainContainer = structuredClone(current.mainContainer);
|
||||
|
||||
UnlinkSymbolFromContainers(symbol, newMainContainer);
|
||||
const containers = structuredClone(current.containers);
|
||||
UnlinkSymbolFromContainers(containers, symbol);
|
||||
|
||||
newSymbols.delete(symbolId);
|
||||
|
||||
history.push({
|
||||
lastAction: `Select ${symbolId}`,
|
||||
mainContainer: newMainContainer,
|
||||
mainContainer: current.mainContainer,
|
||||
containers,
|
||||
selectedContainerId: current.selectedContainerId,
|
||||
typeCounters: structuredClone(current.typeCounters),
|
||||
symbols: newSymbols,
|
||||
|
@ -100,9 +102,9 @@ export function DeleteSymbol(
|
|||
* @param symbol Symbol to remove
|
||||
* @param root Container and its children to remove a symbol from
|
||||
*/
|
||||
function UnlinkSymbolFromContainers(symbol: ISymbolModel, root: IContainerModel): void {
|
||||
function UnlinkSymbolFromContainers(containers: Map<string, IContainerModel>, symbol: ISymbolModel): void {
|
||||
symbol.linkedContainers.forEach((containerId) => {
|
||||
const container = FindContainerById(root, containerId);
|
||||
const container = FindContainerById(containers, containerId);
|
||||
|
||||
if (container === undefined) {
|
||||
return;
|
||||
|
@ -140,22 +142,23 @@ export function OnPropertyChange(
|
|||
|
||||
(symbol as any)[key] = value;
|
||||
|
||||
const newMainContainer = structuredClone(current.mainContainer);
|
||||
const containers = structuredClone(current.containers);
|
||||
symbol.linkedContainers.forEach((containerId) => {
|
||||
const container = FindContainerById(newMainContainer, containerId);
|
||||
const container = FindContainerById(containers, containerId);
|
||||
|
||||
if (container === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyBehaviors(container, newSymbols);
|
||||
ApplyBehaviors(containers, container, newSymbols);
|
||||
|
||||
ApplyBehaviorsOnSiblingsChildren(container, newSymbols);
|
||||
ApplyBehaviorsOnSiblingsChildren(containers, container, newSymbols);
|
||||
});
|
||||
|
||||
history.push({
|
||||
lastAction: `Change ${key} of ${symbol.id}`,
|
||||
mainContainer: newMainContainer,
|
||||
mainContainer: current.mainContainer,
|
||||
containers,
|
||||
selectedContainerId: current.selectedContainerId,
|
||||
typeCounters: Object.assign({}, current.typeCounters),
|
||||
symbols: newSymbols,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue