From b4eba6bb9b8ed0fe03571abbaebd41c27d2a5ad7 Mon Sep 17 00:00:00 2001 From: Eric Nguyen Date: Mon, 17 Oct 2022 16:01:06 +0000 Subject: [PATCH 01/11] Merged PR 216: Deprecate parent from IContainerModel for FindContainerById --- public/workers/worker.js | 4 --- src/Components/API/api.test.tsx | 2 -- src/Components/App/App.tsx | 1 - src/Components/Canvas/Selector.ts | 3 +- src/Components/Editor/Actions/AddContainer.ts | 1 - .../Editor/Actions/ContainerOperations.ts | 22 ++++++------- .../Editor/Actions/ContextMenuActions.ts | 31 ++++++++++------- src/Components/Editor/Behaviors/Behaviors.ts | 27 ++++++++------- .../Editor/Behaviors/RigidBodyBehaviors.ts | 15 +++++---- .../Editor/Behaviors/SymbolBehaviors.ts | 10 ++++-- src/Components/ElementsList/ElementsList.tsx | 13 ++++---- .../SVG/Elements/DepthDimensionLayer.tsx | 2 +- .../SVG/Elements/Selector/Selector.tsx | 3 +- src/Components/SVG/SVG.tsx | 2 +- src/Components/Viewer/Viewer.tsx | 1 + src/Interfaces/IContainerModel.ts | 8 ----- src/utils/default.ts | 1 - src/utils/itertools.ts | 33 ++++++++++++------- src/utils/saveload.ts | 27 --------------- 19 files changed, 97 insertions(+), 109 deletions(-) diff --git a/public/workers/worker.js b/public/workers/worker.js index a933fc2..915c5bf 100644 --- a/public/workers/worker.js +++ b/public/workers/worker.js @@ -5,10 +5,6 @@ onmessage = (e) => { const getCircularReplacer = () => { return (key, value) => { - if (key === 'parent') { - return; - } - if (key === 'containers') { return [...value.entries()] .map(([Key, Value]) => ({ Key, Value })); diff --git a/src/Components/API/api.test.tsx b/src/Components/API/api.test.tsx index cf8ae3f..d33b2a5 100644 --- a/src/Components/API/api.test.tsx +++ b/src/Components/API/api.test.tsx @@ -98,7 +98,6 @@ describe.concurrent('Models test suite', () => { }); const mainContainer = new ContainerModel( - null, DEFAULT_MAINCONTAINER_PROPS ); @@ -267,7 +266,6 @@ describe.concurrent('Models test suite', () => { it('ContainerModel', async() => { const model: IContainerModel = { children: [], - parent: null, properties: containerProperties, userData: {} }; diff --git a/src/Components/App/App.tsx b/src/Components/App/App.tsx index ccf556d..8e493b4 100644 --- a/src/Components/App/App.tsx +++ b/src/Components/App/App.tsx @@ -78,7 +78,6 @@ export function App(props: IAppProps): JSX.Element { const appRef = useRef(null); const defaultMainContainer = new ContainerModel( - null, DEFAULT_MAINCONTAINER_PROPS ); const containers = new Map(); diff --git a/src/Components/Canvas/Selector.ts b/src/Components/Canvas/Selector.ts index 51ede29..a91e924 100644 --- a/src/Components/Canvas/Selector.ts +++ b/src/Components/Canvas/Selector.ts @@ -4,6 +4,7 @@ import { GetAbsolutePosition } from '../../utils/itertools'; import { RemoveMargin } from '../../utils/svg'; interface ISelectorProps { + containers: Map selected?: IContainerModel scale?: number } @@ -14,7 +15,7 @@ export function RenderSelector(ctx: CanvasRenderingContext2D, frameCount: number } const scale = (props.scale ?? 1); - let [x, y] = GetAbsolutePosition(props.selected); + let [x, y] = GetAbsolutePosition(props.containers, props.selected); let [width, height] = [ props.selected.properties.width, props.selected.properties.height diff --git a/src/Components/Editor/Actions/AddContainer.ts b/src/Components/Editor/Actions/AddContainer.ts index fb75995..0bfd0e9 100644 --- a/src/Components/Editor/Actions/AddContainer.ts +++ b/src/Components/Editor/Actions/AddContainer.ts @@ -163,7 +163,6 @@ function AddNewContainerToParent( // Create the container const newContainer = new ContainerModel( - parentClone, defaultProperties, [], { diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts index d2902c6..00b5ba6 100644 --- a/src/Components/Editor/Actions/ContainerOperations.ts +++ b/src/Components/Editor/Actions/ContainerOperations.ts @@ -57,9 +57,10 @@ export function DeleteContainer( throw new Error(`[DeleteContainer] Tried to delete a container that is not present in the main container: ${containerId}`); } + const parent = FindContainerById(containers, container.properties.parentId); if (container === mainContainerClone || - container.parent === undefined || - container.parent === null) { + parent === undefined || + parent === null) { Swal.fire({ title: 'Oops...', text: 'Deleting the main container is not allowed!', @@ -75,10 +76,10 @@ export function DeleteContainer( const newSymbols = structuredClone(current.symbols); UnlinkContainerFromSymbols(containers, newSymbols, container); - const index = container.parent.children.indexOf(container.properties.id); + const index = parent.children.indexOf(container.properties.id); const success = containers.delete(container.properties.id); if (index > -1 && success) { - container.parent.children.splice(index, 1); + parent.children.splice(index, 1); } else { throw new Error('[DeleteContainer] Could not find container among parent\'s children'); } @@ -90,7 +91,7 @@ export function DeleteContainer( const selectedContainerId = GetSelectedContainerOnDelete( containers, current.selectedContainerId, - container.parent, + parent, index ); @@ -202,12 +203,8 @@ export function OnPropertyChange( */ export function SortChildren( containers: Map, - parent: IContainerModel | null | undefined + parent: IContainerModel ): void { - if (parent === null || parent === undefined) { - return; - } - const isHorizontal = parent.properties.orientation === Orientation.Horizontal; const children = parent.children; @@ -293,7 +290,10 @@ function SetContainer( ); // sort the children list by their position - SortChildren(containers, container.parent); + const parent = FindContainerById(containers, container.properties.parentId); + if (parent !== null && parent !== undefined) { + SortChildren(containers, parent); + } // Apply special behaviors: rigid, flex, symbol, anchor ApplyBehaviors(containers, container, symbols); diff --git a/src/Components/Editor/Actions/ContextMenuActions.ts b/src/Components/Editor/Actions/ContextMenuActions.ts index e5f8ea0..21c8ac0 100644 --- a/src/Components/Editor/Actions/ContextMenuActions.ts +++ b/src/Components/Editor/Actions/ContextMenuActions.ts @@ -8,6 +8,7 @@ import { ISetContainerListRequest } from '../../../Interfaces/ISetContainerListR import { ISetContainerListResponse } from '../../../Interfaces/ISetContainerListResponse'; import { FindContainerById } from '../../../utils/itertools'; import { SetContainerList } from '../../API/api'; +import { GetCurrentHistoryState } from '../Editor'; import { AddContainers } from './AddContainer'; import { DeleteContainer } from './ContainerOperations'; @@ -62,15 +63,16 @@ export function GetAction( function GetPreviousAndNextSiblings(containers: Map, 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.properties.id); + const parent = FindContainerById(containers, container.properties.parentId); + if (parent !== undefined && + parent !== null && + parent.children.length > 1) { + const index = parent.children.indexOf(container.properties.id); if (index > 0) { - prev = FindContainerById(containers, container.parent.children[index - 1]); + prev = FindContainerById(containers, parent.children[index - 1]); } - if (index < container.parent.children.length - 1) { - next = FindContainerById(containers, container.parent.children[index + 1]); + if (index < parent.children.length - 1) { + next = FindContainerById(containers, parent.children[index + 1]); } } return { prev, next }; @@ -86,6 +88,9 @@ function HandleSetContainerList( setNewHistory: (newHistory: IHistoryState[]) => void ): void { const addingBehavior = response.AddingBehavior ?? action.AddingBehavior; + const current = GetCurrentHistoryState(history, historyCurrentStep); + const containers = current.containers; + const parent = FindContainerById(containers, selectedContainer.properties.parentId); switch (addingBehavior) { case AddMethod.Append: setNewHistory( @@ -103,6 +108,7 @@ function HandleSetContainerList( case AddMethod.Replace: setNewHistory( HandleReplace( + containers, selectedContainer, response, configuration, @@ -112,7 +118,7 @@ function HandleSetContainerList( ); break; case AddMethod.ReplaceParent: - if (selectedContainer.parent === undefined || selectedContainer.parent === null) { + if (parent === undefined || parent === null) { Swal.fire({ title: 'Error', text: 'The selected container has not parent to replace', @@ -122,7 +128,8 @@ function HandleSetContainerList( } setNewHistory( HandleReplace( - selectedContainer.parent, + containers, + parent, response, configuration, history, @@ -134,17 +141,19 @@ function HandleSetContainerList( } function HandleReplace( + containers: Map, selectedContainer: IContainerModel, response: ISetContainerListResponse, configuration: IConfiguration, history: IHistoryState[], historyCurrentStep: number ): IHistoryState[] { - if (selectedContainer.parent === undefined || selectedContainer.parent === null) { + const parent = FindContainerById(containers, selectedContainer.properties.parentId); + if (parent === undefined || parent === null) { throw new Error('[ReplaceContainer] Cannot replace a container that does not exists'); } - const index = selectedContainer.parent.children.indexOf(selectedContainer.properties.id); + const index = parent.children.indexOf(selectedContainer.properties.id); const newHistoryAfterDelete = DeleteContainer( selectedContainer.properties.id, diff --git a/src/Components/Editor/Behaviors/Behaviors.ts b/src/Components/Editor/Behaviors/Behaviors.ts index e4f777b..178ec3d 100644 --- a/src/Components/Editor/Behaviors/Behaviors.ts +++ b/src/Components/Editor/Behaviors/Behaviors.ts @@ -18,12 +18,11 @@ export function ApplyBehaviors(containers: Map, contain try { const symbol = symbols.get(container.properties.linkedSymbolId); if (container.properties.linkedSymbolId !== '' && symbol !== undefined) { - ApplySymbol(container, symbol); + ApplySymbol(containers, container, symbol); } - if (container.parent !== undefined && container.parent !== null) { - const parent = container.parent; - + const parent = FindContainerById(containers, container.properties.parentId); + if (parent !== undefined && parent !== null) { if (container.properties.isAnchor) { ApplyAnchor(containers, container, parent); } @@ -69,11 +68,12 @@ export function ApplyBehaviorsOnSiblingsChildren( containers: Map, newContainer: IContainerModel, symbols: Map): void { - if (newContainer.parent === null || newContainer.parent === undefined) { + const parent = FindContainerById(containers, newContainer.properties.parentId); + if (parent === null || parent === undefined) { return; } - newContainer.parent.children + parent.children .forEach((containerId: string) => { const container = FindContainerById(containers, containerId); @@ -81,8 +81,9 @@ export function ApplyBehaviorsOnSiblingsChildren( return; } - if (container.parent !== null) { - UpdateWarning(containers, container, container.parent); + const containerParent = FindContainerById(containers, container.properties.parentId); + if (containerParent !== null && containerParent !== undefined) { + UpdateWarning(containers, container, containerParent); } if (container === newContainer) { @@ -102,11 +103,12 @@ export function ApplyBehaviorsOnSiblingsChildren( * @returns */ export function ApplyBehaviorsOnSiblings(containers: Map, newContainer: IContainerModel, symbols: Map): void { - if (newContainer.parent === null || newContainer.parent === undefined) { + const parent = FindContainerById(containers, newContainer.properties.parentId); + if (parent === null || parent === undefined) { return; } - newContainer.parent.children + parent.children .forEach((containerId: string) => { const container = FindContainerById(containers, containerId); @@ -116,8 +118,9 @@ export function ApplyBehaviorsOnSiblings(containers: Map, container: IContainerModel ): IContainerModel { - if (container.parent === null || container.parent === undefined) { + const parent = FindContainerById(containers, container.properties.parentId); + if (parent === null || parent === undefined) { return container; } // Get the available spaces of the parent const isHorizontal = - container.parent.properties.orientation === Orientation.Horizontal; - const children: IContainerModel[] = [...MakeChildrenIterator(containers, container.parent.children)]; + parent.properties.orientation === Orientation.Horizontal; + const children: IContainerModel[] = [...MakeChildrenIterator(containers, parent.children)]; const availableWidths = GetAvailableWidths( 0, - container.parent.properties.width, + parent.properties.width, children, container, isHorizontal @@ -172,7 +173,7 @@ export function ConstraintBodyInsideUnallocatedWidth( container, 0, availableWidthFound.x, - container.parent.properties.width, + parent.properties.width, availableWidthFound.width ); @@ -203,7 +204,7 @@ export function ConstraintBodyInsideUnallocatedWidth( availableWidthFound.x, 0, availableWidthFound.width, - container.parent.properties.height + parent.properties.height ); return container; diff --git a/src/Components/Editor/Behaviors/SymbolBehaviors.ts b/src/Components/Editor/Behaviors/SymbolBehaviors.ts index 7d6d4af..5efde19 100644 --- a/src/Components/Editor/Behaviors/SymbolBehaviors.ts +++ b/src/Components/Editor/Behaviors/SymbolBehaviors.ts @@ -1,12 +1,16 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; -import { ApplyParentTransform } from '../../../utils/itertools'; +import { ApplyParentTransform, FindContainerById } from '../../../utils/itertools'; import { RestoreX, TransformX } from '../../../utils/svg'; -export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel { +export function ApplySymbol(containers: Map, container: IContainerModel, symbol: ISymbolModel): IContainerModel { container.properties.x = TransformX(symbol.x, symbol.width, symbol.config.PositionReference); container.properties.x = RestoreX(container.properties.x, container.properties.width, container.properties.positionReference); - const [x] = ApplyParentTransform(container.parent, container.properties.x, 0); + const parent = FindContainerById(containers, container.properties.parentId); + let x = 0; + if (parent !== undefined && parent !== null) { + ([x] = ApplyParentTransform(containers, parent, container.properties.x, 0)); + } container.properties.x = x; return container; } diff --git a/src/Components/ElementsList/ElementsList.tsx b/src/Components/ElementsList/ElementsList.tsx index a38400e..04265cd 100644 --- a/src/Components/ElementsList/ElementsList.tsx +++ b/src/Components/ElementsList/ElementsList.tsx @@ -87,8 +87,9 @@ function HandleOnDrop( return; } - if (targetContainer.parent === null || - targetContainer.parent === undefined) { + const parent = FindContainerById(containers, targetContainer.properties.parentId); + if (parent === null || + parent === undefined) { throw new Error('[handleDrop] Tried to drop into a child container without a parent!'); } @@ -97,11 +98,11 @@ function HandleOnDrop( // locate the hitboxes if (y < 12) { - const index = targetContainer.parent.children.indexOf(targetContainer.properties.id); + const index = parent.children.indexOf(targetContainer.properties.id); addContainer( index, type, - targetContainer.parent.properties.id + parent.properties.id ); } else if (y < 24) { addContainer( @@ -109,11 +110,11 @@ function HandleOnDrop( type, targetContainer.properties.id); } else { - const index = targetContainer.parent.children.indexOf(targetContainer.properties.id); + const index = parent.children.indexOf(targetContainer.properties.id); addContainer( index + 1, type, - targetContainer.parent.properties.id + parent.properties.id ); } } diff --git a/src/Components/SVG/Elements/DepthDimensionLayer.tsx b/src/Components/SVG/Elements/DepthDimensionLayer.tsx index 0c3a36e..15bd78e 100644 --- a/src/Components/SVG/Elements/DepthDimensionLayer.tsx +++ b/src/Components/SVG/Elements/DepthDimensionLayer.tsx @@ -31,7 +31,7 @@ function GetDimensionsNodes( max = -Infinity; } - const absoluteX = GetAbsolutePosition(container)[0]; + const absoluteX = GetAbsolutePosition(containers, container)[0]; const x = TransformX(absoluteX, container.properties.width, container.properties.positionReference); lastY = container.properties.y + container.properties.height; if (x < min) { diff --git a/src/Components/SVG/Elements/Selector/Selector.tsx b/src/Components/SVG/Elements/Selector/Selector.tsx index e43e7be..c7baf92 100644 --- a/src/Components/SVG/Elements/Selector/Selector.tsx +++ b/src/Components/SVG/Elements/Selector/Selector.tsx @@ -6,6 +6,7 @@ import { GetAbsolutePosition } from '../../../../utils/itertools'; import { RemoveMargin } from '../../../../utils/svg'; interface ISelectorProps { + containers: Map selected?: IContainerModel scale?: number } @@ -19,7 +20,7 @@ export function Selector(props: ISelectorProps): JSX.Element { } const scale = (props.scale ?? 1); - let [x, y] = GetAbsolutePosition(props.selected); + let [x, y] = GetAbsolutePosition(props.containers, props.selected); let [width, height] = [ props.selected.properties.width, props.selected.properties.height diff --git a/src/Components/SVG/SVG.tsx b/src/Components/SVG/SVG.tsx index 92bb7fa..4b5572b 100644 --- a/src/Components/SVG/SVG.tsx +++ b/src/Components/SVG/SVG.tsx @@ -98,7 +98,7 @@ export function SVG(props: ISVGProps): JSX.Element { : null} - {/* leave this at the end so it can be removed during the svg export */} + {/* leave this at the end so it can be removed during the svg export */} diff --git a/src/Components/Viewer/Viewer.tsx b/src/Components/Viewer/Viewer.tsx index c37d3c6..ba0560b 100644 --- a/src/Components/Viewer/Viewer.tsx +++ b/src/Components/Viewer/Viewer.tsx @@ -147,6 +147,7 @@ export function Viewer({ // Draw selector RenderSelector(ctx, frameCount, { + containers: current.containers, scale, selected: selectedContainer }); diff --git a/src/Interfaces/IContainerModel.ts b/src/Interfaces/IContainerModel.ts index 8e38efa..42d966b 100644 --- a/src/Interfaces/IContainerModel.ts +++ b/src/Interfaces/IContainerModel.ts @@ -2,9 +2,6 @@ import { IContainerProperties } from './IContainerProperties'; export interface IContainerModel { children: string[] - // TODO: Remove parent now that accessing the parent by id is faster. - // TODO: Use GetContainerById(container.properties.parentId) as the better alternative. - parent: IContainerModel | null properties: IContainerProperties userData: Record } @@ -15,18 +12,13 @@ export interface IContainerModel { */ export class ContainerModel implements IContainerModel { public children: string[]; - // TODO: Remove parent now that accessing the parent by id is faster. - // TODO: Use GetContainerById(container.properties.parentId) as the better alternative. - public parent: IContainerModel | null; public properties: IContainerProperties; public userData: Record; constructor( - parent: IContainerModel | null, properties: IContainerProperties, children: string[] = [], userData = {}) { - this.parent = parent; this.properties = properties; this.children = children; this.userData = userData; diff --git a/src/utils/default.ts b/src/utils/default.ts index 253610b..0eba5db 100644 --- a/src/utils/default.ts +++ b/src/utils/default.ts @@ -121,7 +121,6 @@ export function GetDefaultEditorState(configuration: IConfiguration): IEditorSta ); } const mainContainer = new ContainerModel( - null, mainContainerConfig ); const containers = new Map(); diff --git a/src/utils/itertools.ts b/src/utils/itertools.ts index 0db5812..cdd4164 100644 --- a/src/utils/itertools.ts +++ b/src/utils/itertools.ts @@ -123,16 +123,16 @@ export function * MakeRecursionDFSIterator( /** * Returns the depth of the container * @returns The depth of the container - * @deprecated Please avoid using this function inside an iterationl, + * @deprecated Please avoid using this function inside an iteration, * use recursive DFS or iterative BFS to get the depth */ -export function GetDepth(parent: IContainerModel): number { +export function GetDepth(containers: Map, parent: IContainerModel): number { let depth = 0; let current: IContainerModel | null = parent; while (current != null) { depth++; - current = current.parent; + current = FindContainerById(containers, current.properties.parentId) ?? null; } return depth; @@ -142,22 +142,31 @@ export function GetDepth(parent: IContainerModel): number { * Returns the absolute position by iterating to the parent * @returns The absolute position of the container */ -export function GetAbsolutePosition(container: IContainerModel): [number, number] { +export function GetAbsolutePosition(containers: Map, container: IContainerModel): [number, number] { const x = container.properties.x; const y = container.properties.y; - return CancelParentTransform(container.parent, x, y); + const parent = FindContainerById(containers, container.properties.parentId) ?? null; + return CancelParentTransform(containers, parent, x, y); } -export function GetContainerLinkedList(container: IContainerModel, stop?: IContainerModel): IContainerModel[] { - const it = MakeContainerLinkedListIterator(container, stop); +export function GetContainerLinkedList( + containers: Map, + container: IContainerModel, + stop?: IContainerModel +): IContainerModel[] { + const it = MakeContainerLinkedListIterator(containers, container, stop); return [...it]; } -export function * MakeContainerLinkedListIterator(container: IContainerModel, stop?: IContainerModel): Generator { +export function * MakeContainerLinkedListIterator( + containers: Map, + container: IContainerModel, + stop?: IContainerModel +): Generator { let current: IContainerModel | null = container; while (current !== stop && current != null) { yield current; - current = current.parent; + current = FindContainerById(containers, current.properties.parentId) ?? null; } } @@ -169,6 +178,7 @@ export function * MakeContainerLinkedListIterator(container: IContainerModel, st * @returns x and y such that the transformations of the parent are cancelled */ export function CancelParentTransform( + containers: Map, parent: IContainerModel | null, x: number, y: number, @@ -178,7 +188,7 @@ export function CancelParentTransform( return [x, y]; } - const it = MakeContainerLinkedListIterator(parent, stop); + const it = MakeContainerLinkedListIterator(containers, parent, stop); for (const current of it) { x += current.properties.x; y += current.properties.y; @@ -195,6 +205,7 @@ export function CancelParentTransform( * @returns x and y such that the transformations of the parent are applied */ export function ApplyParentTransform( + containers: Map, parent: IContainerModel | null, x: number, y: number, @@ -204,7 +215,7 @@ export function ApplyParentTransform( return [x, y]; } - const it = MakeContainerLinkedListIterator(parent, stop); + const it = MakeContainerLinkedListIterator(containers, parent, stop); for (const current of it) { x -= current.properties.x; y -= current.properties.y; diff --git a/src/utils/saveload.ts b/src/utils/saveload.ts index 5033fab..ad9a4f3 100644 --- a/src/utils/saveload.ts +++ b/src/utils/saveload.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { FindContainerById, MakeDFSIterator } from './itertools'; import { IEditorState } from '../Interfaces/IEditorState'; import { IHistoryState } from '../Interfaces/IHistoryState'; import { IContainerModel } from '../Interfaces/IContainerModel'; @@ -37,36 +36,10 @@ export function ReviveState(state: IHistoryState): void { const containers: Array<{ Key: string, Value: IContainerModel }> = (state.containers) as any; state.containers = new Map(containers.map(({ Key, Value }: {Key: string, Value: IContainerModel}) => [Key, Value])); - - const root = FindContainerById(state.containers, state.mainContainer); - - if (root === undefined) { - return; - } - - // TODO: remove parent and remove this bloc of code - // TODO: See IContainerModel.ts for more detail - const it = MakeDFSIterator(root, state.containers); - for (const container of it) { - const parentId = container.properties.parentId; - if (parentId === null) { - container.parent = null; - continue; - } - const parent = FindContainerById(state.containers, parentId); - if (parent === undefined) { - continue; - } - container.parent = parent; - } } export function GetCircularReplacer(): (key: any, value: object | Map | null) => object | null | undefined { return (key: any, value: object | null) => { - if (key === 'parent') { - return; - } - if (key === 'containers') { return [...(value as Map).entries()] .map(([Key, Value]: [string, any]) => ({ Key, Value })); From 28b096562662b94e52a480a81c1a23a7afefae53 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Mon, 17 Oct 2022 19:02:36 +0200 Subject: [PATCH 02/11] Refactor UI's props --- src/Components/Editor/Editor.tsx | 13 ++--- src/Components/UI/UI.tsx | 85 +++++++++++++++----------------- 2 files changed, 44 insertions(+), 54 deletions(-) diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 507fa58..5309f8f 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -279,14 +279,11 @@ export function Editor(props: IEditorProps): JSX.Element { return (
setNewHistory( SelectContainer( container, diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index c9afe1e..2c4683a 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -1,16 +1,11 @@ import * as React from 'react'; import { ElementsList } from '../ElementsList/ElementsList'; import { History } from '../History/History'; -import { IAvailableContainer } from '../../Interfaces/IAvailableContainer'; -import { IContainerModel } from '../../Interfaces/IContainerModel'; -import { IHistoryState } from '../../Interfaces/IHistoryState'; import { Bar } from '../Bar/Bar'; -import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol'; import { Symbols } from '../Symbols/Symbols'; import { SymbolsSidebar } from '../SymbolsList/SymbolsList'; import { PropertyType } from '../../Enums/PropertyType'; import { Messages } from '../Messages/Messages'; -import { ICategory } from '../../Interfaces/ICategory'; import { Sidebar } from '../Sidebar/Sidebar'; import { Components } from '../Components/Components'; import { Viewer } from '../Viewer/Viewer'; @@ -19,17 +14,11 @@ import { IMessage } from '../../Interfaces/IMessage'; import { DISABLE_API } from '../../utils/default'; import { UseWorker, UseAsync } from './UseWorker'; import { FindContainerById } from '../../utils/itertools'; -import { IAPIConfiguration } from '../../Interfaces/IAPIConfiguration'; +import { IEditorState } from '../../Interfaces/IEditorState'; +import { GetCurrentHistoryState } from '../Editor/Editor'; export interface IUIProps { - selectedContainer: IContainerModel | undefined - current: IHistoryState - history: IHistoryState[] - historyCurrentStep: number - availableContainers: IAvailableContainer[] - availableSymbols: IAvailableSymbol[] - categories: ICategory[] - apiConfiguration: IAPIConfiguration | undefined + editorState: IEditorState selectContainer: (containerId: string) => void deleteContainer: (containerId: string) => void onPropertyChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void @@ -67,21 +56,23 @@ function UseSetOrToggleSidebar( }; } -export function UI(props: IUIProps): JSX.Element { +export function UI({ editorState, ...methods }: IUIProps): JSX.Element { const [selectedSidebar, setSelectedSidebar] = React.useState(SidebarType.Components); const [messages, setMessages] = React.useState([]); + const current = GetCurrentHistoryState(editorState.history, editorState.historyCurrentStep); + const configuration = editorState.configuration; // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (window.Worker && !DISABLE_API) { UseWorker( - props.current, - props.apiConfiguration?.apiGetFeedbackUrl, + current, + configuration.APIConfiguration?.apiGetFeedbackUrl, setMessages ); } else if (!DISABLE_API) { UseAsync( - props.current, - props.apiConfiguration?.apiGetFeedbackUrl, + current, + configuration.APIConfiguration?.apiGetFeedbackUrl, setMessages ); } @@ -94,61 +85,63 @@ export function UI(props: IUIProps): JSX.Element { let leftChildren: JSX.Element = (<>); let rightChildren: JSX.Element = (<>); - const mainContainer = FindContainerById(props.current.containers, props.current.mainContainer) + const mainContainer = FindContainerById(current.containers, current.mainContainer); if (mainContainer === undefined) { throw new Error('Tried to initialized UI but there is no main container!'); } + const selectedContainer = FindContainerById(current.containers, current.selectedContainerId); + switch (selectedSidebar) { case SidebarType.Components: leftSidebarTitle = 'Components'; leftChildren = ; rightSidebarTitle = 'Elements'; rightChildren = ; break; case SidebarType.Symbols: leftSidebarTitle = 'Symbols'; leftChildren = ; rightSidebarTitle = 'Symbols'; rightChildren = ; break; case SidebarType.History: leftSidebarTitle = 'Timeline'; leftChildren = ; break; case SidebarType.Messages: leftSidebarTitle = 'Messages'; leftChildren = setMessages([])} />; @@ -157,8 +150,8 @@ export function UI(props: IUIProps): JSX.Element { case SidebarType.Settings: leftSidebarTitle = 'Settings'; leftChildren = ; break; } @@ -166,7 +159,7 @@ export function UI(props: IUIProps): JSX.Element { const isLeftSidebarOpen = selectedSidebar !== SidebarType.None; const isRightSidebarOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.Symbols; - let isLeftSidebarOpenClasses = new Set([ + const isLeftSidebarOpenClasses = new Set([ 'left-sidebar', 'left-16', '-bottom-full', @@ -222,9 +215,9 @@ export function UI(props: IUIProps): JSX.Element { Date: Mon, 17 Oct 2022 19:17:46 +0200 Subject: [PATCH 03/11] Refactor App and Editor events --- src/Components/App/App.tsx | 34 +--- .../Editor/Actions/ContextMenuActions.ts | 112 +++++++++++- src/Components/Editor/Editor.tsx | 172 +----------------- src/Events/AppEvents.ts | 33 ++++ src/Events/EditorEvents.ts | 61 ++++++- 5 files changed, 209 insertions(+), 203 deletions(-) diff --git a/src/Components/App/App.tsx b/src/Components/App/App.tsx index 8e493b4..88eb9ee 100644 --- a/src/Components/App/App.tsx +++ b/src/Components/App/App.tsx @@ -1,5 +1,5 @@ import React, { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; -import { events as EVENTS } from '../../Events/AppEvents'; +import { events as EVENTS, UseCustomEvents } from '../../Events/AppEvents'; import { MainMenu } from '../MainMenu/MainMenu'; import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel'; import { Editor } from '../Editor/Editor'; @@ -41,38 +41,6 @@ function UseHTTPGETStatePreloading( }); }; -function UseCustomEvents( - root: Element | Document, - appRef: React.RefObject, - setEditor: (newState: IEditorState) => void, - setLoaded: (loaded: boolean) => void -): void { - useEffect(() => { - const funcs = new Map void>(); - for (const event of EVENTS) { - function Func(eventInitDict?: CustomEventInit): void { - return event.func( - root, - setEditor, - setLoaded, - eventInitDict - ); - } - appRef.current?.addEventListener(event.name, Func); - funcs.set(event.name, Func); - } - return () => { - for (const event of EVENTS) { - const func = funcs.get(event.name); - if (func === undefined) { - continue; - } - appRef.current?.removeEventListener(event.name, func); - } - }; - }); -} - export function App(props: IAppProps): JSX.Element { const [isLoaded, setLoaded] = useState(false); const appRef = useRef(null); diff --git a/src/Components/Editor/Actions/ContextMenuActions.ts b/src/Components/Editor/Actions/ContextMenuActions.ts index 21c8ac0..7135bf2 100644 --- a/src/Components/Editor/Actions/ContextMenuActions.ts +++ b/src/Components/Editor/Actions/ContextMenuActions.ts @@ -1,4 +1,5 @@ import Swal from 'sweetalert2'; +import { Dispatch, SetStateAction } from 'react'; import { AddMethod } from '../../../Enums/AddMethod'; import { IAction } from '../../../Interfaces/IAction'; import { IConfiguration } from '../../../Interfaces/IConfiguration'; @@ -6,13 +7,122 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { IHistoryState } from '../../../Interfaces/IHistoryState'; import { ISetContainerListRequest } from '../../../Interfaces/ISetContainerListRequest'; import { ISetContainerListResponse } from '../../../Interfaces/ISetContainerListResponse'; +import { DISABLE_API } from '../../../utils/default'; import { FindContainerById } from '../../../utils/itertools'; import { SetContainerList } from '../../API/api'; +import { IMenuAction } from '../../Menu/Menu'; import { GetCurrentHistoryState } from '../Editor'; import { AddContainers } from './AddContainer'; import { DeleteContainer } from './ContainerOperations'; +import { DeleteSymbol } from './SymbolOperations'; -export function GetAction( +export function InitActions( + menuActions: Map, + configuration: IConfiguration, + history: IHistoryState[], + historyCurrentStep: number, + setNewHistory: (newHistory: IHistoryState[]) => void, + setHistoryCurrentStep: Dispatch> +): void { + menuActions.set( + '', + [ + { + text: 'Undo', + title: 'Undo last action', + shortcut: 'Ctrl+Z', + action: () => { + if (historyCurrentStep <= 0) { + return; + } + setHistoryCurrentStep(historyCurrentStep - 1); + } + }, + { + text: 'Redo', + title: 'Redo last action', + shortcut: 'Ctrl+Y', + action: () => { + if (historyCurrentStep >= history.length - 1) { + return; + } + setHistoryCurrentStep(historyCurrentStep + 1); + } + } + ] + ); + + menuActions.set( + 'elements-sidebar-row', + [{ + text: 'Delete', + title: 'Delete the container', + shortcut: 'Suppr', + action: (target: HTMLElement) => { + const id = target.id; + const newHistory = DeleteContainer( + id, + history, + historyCurrentStep + ); + setNewHistory(newHistory); + } + }] + ); + + menuActions.set( + 'symbols-sidebar-row', + [{ + text: 'Delete', + title: 'Delete the container', + shortcut: 'Suppr', + action: (target: HTMLElement) => { + const id = target.id; + const newHistory = DeleteSymbol( + id, + history, + historyCurrentStep + ); + setNewHistory(newHistory); + } + }] + ); + + // API Actions + if (DISABLE_API) { + return; + } + + for (const availableContainer of configuration.AvailableContainers) { + if (availableContainer.Actions === undefined || availableContainer.Actions === null) { + continue; + } + + for (const action of availableContainer.Actions) { + if (menuActions.get(availableContainer.Type) === undefined) { + menuActions.set(availableContainer.Type, []); + } + + const currentState = GetCurrentHistoryState(history, historyCurrentStep); + const newAction: IMenuAction = { + text: action.Label, + title: action.Description, + action: GetAction( + action, + currentState, + configuration, + history, + historyCurrentStep, + setNewHistory + ) + }; + + menuActions.get(availableContainer.Type)?.push(newAction); + } + } +} + +function GetAction( action: IAction, currentState: IHistoryState, configuration: IConfiguration, diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 5309f8f..7707608 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -6,13 +6,12 @@ import { UI } from '../UI/UI'; 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'; -import { IEditorState } from '../../Interfaces/IEditorState'; -import { DISABLE_API, MAX_HISTORY } from '../../utils/default'; +import { UseCustomEvents, UseEditorListener } from '../../Events/EditorEvents'; +import { MAX_HISTORY } from '../../utils/default'; import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations'; import { FindContainerById } from '../../utils/itertools'; -import { IMenuAction, Menu } from '../Menu/Menu'; -import { GetAction } from './Actions/ContextMenuActions'; +import { Menu } from '../Menu/Menu'; +import { InitActions } from './Actions/ContextMenuActions'; import { AddContainerToSelectedContainer, AddContainer } from './Actions/AddContainer'; interface IEditorProps { @@ -22,112 +21,6 @@ interface IEditorProps { historyCurrentStep: number } -function InitActions( - menuActions: Map, - configuration: IConfiguration, - history: IHistoryState[], - historyCurrentStep: number, - setNewHistory: (newHistory: IHistoryState[]) => void, - setHistoryCurrentStep: Dispatch> -): void { - menuActions.set( - '', - [ - { - text: 'Undo', - title: 'Undo last action', - shortcut: 'Ctrl+Z', - action: () => { - if (historyCurrentStep <= 0) { - return; - } - setHistoryCurrentStep(historyCurrentStep - 1); - } - }, - { - text: 'Redo', - title: 'Redo last action', - shortcut: 'Ctrl+Y', - action: () => { - if (historyCurrentStep >= history.length - 1) { - return; - } - setHistoryCurrentStep(historyCurrentStep + 1); - } - } - ] - ); - - menuActions.set( - 'elements-sidebar-row', - [{ - text: 'Delete', - title: 'Delete the container', - shortcut: 'Suppr', - action: (target: HTMLElement) => { - const id = target.id; - const newHistory = DeleteContainer( - id, - history, - historyCurrentStep - ); - setNewHistory(newHistory); - } - }] - ); - - menuActions.set( - 'symbols-sidebar-row', - [{ - text: 'Delete', - title: 'Delete the container', - shortcut: 'Suppr', - action: (target: HTMLElement) => { - const id = target.id; - const newHistory = DeleteSymbol( - id, - history, - historyCurrentStep - ); - setNewHistory(newHistory); - } - }] - ); - - // API Actions - if (DISABLE_API) { - return; - } - - for (const availableContainer of configuration.AvailableContainers) { - if (availableContainer.Actions === undefined || availableContainer.Actions === null) { - continue; - } - - for (const action of availableContainer.Actions) { - if (menuActions.get(availableContainer.Type) === undefined) { - menuActions.set(availableContainer.Type, []); - } - - const currentState = GetCurrentHistoryState(history, historyCurrentStep); - const newAction: IMenuAction = { - text: action.Label, - title: action.Description, - action: GetAction( - action, - currentState, - configuration, - history, - historyCurrentStep, - setNewHistory - ) - }; - - menuActions.get(availableContainer.Type)?.push(newAction); - } - } -} - function UseShortcuts( history: IHistoryState[], historyCurrentStep: number, @@ -152,63 +45,6 @@ function UseShortcuts( }); } -function UseCustomEvents( - root: Element | Document, - history: IHistoryState[], - historyCurrentStep: number, - configuration: IConfiguration, - editorRef: React.RefObject, - setNewHistory: (newHistory: IHistoryState[], historyCurrentStep?: number) => void -): void { - useEffect(() => { - const editorState: IEditorState = { - history, - historyCurrentStep, - configuration - }; - - const funcs = new Map void>(); - for (const event of EVENTS) { - function Func(eventInitDict?: CustomEventInit): void { - return event.func( - root, - editorState, - setNewHistory, - eventInitDict - ); - } - editorRef.current?.addEventListener(event.name, Func); - funcs.set(event.name, Func); - } - return () => { - for (const event of EVENTS) { - const func = funcs.get(event.name); - if (func === undefined) { - continue; - } - editorRef.current?.removeEventListener(event.name, func); - } - }; - }); -} - -function UseEditorListener( - root: Element | Document, - history: IHistoryState[], - historyCurrentStep: number, - configuration: IConfiguration -): void { - useEffect(() => { - const editorState: IEditorState = { - history, - historyCurrentStep, - configuration - }; - const event = new CustomEvent('editorListener', { detail: editorState }); - root.dispatchEvent(event); - }); -} - /** * Return a macro function to use both setHistory * and setHistoryCurrentStep at the same time diff --git a/src/Events/AppEvents.ts b/src/Events/AppEvents.ts index 3aabbee..6ba07ad 100644 --- a/src/Events/AppEvents.ts +++ b/src/Events/AppEvents.ts @@ -1,3 +1,4 @@ +import { useEffect } from 'react'; import { IConfiguration } from '../Interfaces/IConfiguration'; import { IEditorState } from '../Interfaces/IEditorState'; import { IHistoryState } from '../Interfaces/IHistoryState'; @@ -22,6 +23,38 @@ export const events: IAppEvent[] = [ { name: 'getDefaultEditorState', func: GetDefaultEditorState } ]; +export function UseCustomEvents( + root: Element | Document, + appRef: React.RefObject, + setEditor: (newState: IEditorState) => void, + setLoaded: (loaded: boolean) => void +): void { + useEffect(() => { + const funcs = new Map void>(); + for (const event of events) { + function Func(eventInitDict?: CustomEventInit): void { + return event.func( + root, + setEditor, + setLoaded, + eventInitDict + ); + } + appRef.current?.addEventListener(event.name, Func); + funcs.set(event.name, Func); + } + return () => { + for (const event of events) { + const func = funcs.get(event.name); + if (func === undefined) { + continue; + } + appRef.current?.removeEventListener(event.name, func); + } + }; + }); +} + function SetEditor( root: Element | Document, setEditor: (newState: IEditorState) => void, diff --git a/src/Events/EditorEvents.ts b/src/Events/EditorEvents.ts index c572bda..5cb644a 100644 --- a/src/Events/EditorEvents.ts +++ b/src/Events/EditorEvents.ts @@ -1,11 +1,13 @@ +import { useEffect } from 'react'; import { AddContainer as AddContainerAction, AddContainerToSelectedContainer as AddContainerToSelectedContainerAction } from '../Components/Editor/Actions/AddContainer'; import { DeleteContainer as DeleteContainerAction, SelectContainer as SelectContainerAction } from '../Components/Editor/Actions/ContainerOperations'; import { AddSymbol as AddSymbolAction, DeleteSymbol as DeleteSymbolAction, SelectSymbol as SelectSymbolAction } from '../Components/Editor/Actions/SymbolOperations'; import { GetCurrentHistory } from '../Components/Editor/Editor'; +import { IConfiguration } from '../Interfaces/IConfiguration'; import { IEditorState } from '../Interfaces/IEditorState'; import { IHistoryState } from '../Interfaces/IHistoryState'; import { FindContainerById } from '../utils/itertools'; -import { GetCircularReplacer, ReviveHistory as ReviveHistoryAction } from '../utils/saveload'; +import { GetCircularReplacer } from '../utils/saveload'; export interface IEditorEvent { name: string @@ -34,6 +36,63 @@ export const events: IEditorEvent[] = [ { name: 'deleteSymbol', func: DeleteSymbol } ]; +export function UseCustomEvents( + root: Element | Document, + history: IHistoryState[], + historyCurrentStep: number, + configuration: IConfiguration, + editorRef: React.RefObject, + setNewHistory: (newHistory: IHistoryState[], historyCurrentStep?: number) => void +): void { + useEffect(() => { + const editorState: IEditorState = { + history, + historyCurrentStep, + configuration + }; + + const funcs = new Map void>(); + for (const event of events) { + function Func(eventInitDict?: CustomEventInit): void { + return event.func( + root, + editorState, + setNewHistory, + eventInitDict + ); + } + editorRef.current?.addEventListener(event.name, Func); + funcs.set(event.name, Func); + } + return () => { + for (const event of events) { + const func = funcs.get(event.name); + if (func === undefined) { + continue; + } + editorRef.current?.removeEventListener(event.name, func); + } + }; + }); +} + +export function UseEditorListener( + root: Element | Document, + history: IHistoryState[], + historyCurrentStep: number, + configuration: IConfiguration +): void { + useEffect(() => { + const editorState: IEditorState = { + history, + historyCurrentStep, + configuration + }; + const event = new CustomEvent('editorListener', { detail: editorState }); + root.dispatchEvent(event); + }); +} + function GetEditorState(root: Element | Document, editorState: IEditorState): void { const customEvent = new CustomEvent('getEditorState', { detail: structuredClone(editorState) }); From b1b30e8486e85bce57abe6210b23c392ff299bd5 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Mon, 17 Oct 2022 19:32:27 +0200 Subject: [PATCH 04/11] Refactor AddContainerToSelectedContainer --- src/Components/App/App.tsx | 2 +- src/Components/Editor/Actions/AddContainer.ts | 14 ++++--------- src/Components/Editor/Editor.tsx | 11 +++++----- src/Events/EditorEvents.ts | 20 ++++++++----------- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/src/Components/App/App.tsx b/src/Components/App/App.tsx index 88eb9ee..ef86b3c 100644 --- a/src/Components/App/App.tsx +++ b/src/Components/App/App.tsx @@ -1,5 +1,5 @@ import React, { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; -import { events as EVENTS, UseCustomEvents } from '../../Events/AppEvents'; +import { UseCustomEvents } from '../../Events/AppEvents'; import { MainMenu } from '../MainMenu/MainMenu'; import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel'; import { Editor } from '../Editor/Editor'; diff --git a/src/Components/Editor/Actions/AddContainer.ts b/src/Components/Editor/Actions/AddContainer.ts index 0bfd0e9..b50babb 100644 --- a/src/Components/Editor/Actions/AddContainer.ts +++ b/src/Components/Editor/Actions/AddContainer.ts @@ -27,21 +27,15 @@ import { SortChildren } from './ContainerOperations'; */ export function AddContainerToSelectedContainer( type: string, - selected: IContainerModel | undefined, + selected: IContainerModel, configuration: IConfiguration, fullHistory: IHistoryState[], historyCurrentStep: number -): IHistoryState[] | null { - if (selected === null || - selected === undefined) { - return null; - } - - const parent = selected; +): IHistoryState[] { return AddContainer( - parent.children.length, + selected.children.length, type, - parent.properties.id, + selected.properties.id, configuration, fullHistory, historyCurrentStep diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 7707608..372ab06 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -140,16 +140,17 @@ export function Editor(props: IEditorProps): JSX.Element { historyCurrentStep ))} addContainer={(type) => { - const newHistory = AddContainerToSelectedContainer( + if (selected === null || selected === undefined) { + return; + } + + setNewHistory(AddContainerToSelectedContainer( type, selected, configuration, history, historyCurrentStep - ); - if (newHistory !== null) { - setNewHistory(newHistory); - } + )); }} addContainerAt={(index, type, parent) => setNewHistory( AddContainer( diff --git a/src/Events/EditorEvents.ts b/src/Events/EditorEvents.ts index 5cb644a..c67fc4c 100644 --- a/src/Events/EditorEvents.ts +++ b/src/Events/EditorEvents.ts @@ -240,20 +240,16 @@ function AppendContainerToSelectedContainer(root: Element | Document, const selected = FindContainerById(currentState.containers, currentState.selectedContainerId); - const newHistory = AddContainerToSelectedContainerAction( - type, - selected, - editorState.configuration, - history, - editorState.historyCurrentStep - ); - - if (newHistory === null) { - return; + if (selected !== null && selected !== undefined) { + setNewHistory(AddContainerToSelectedContainerAction( + type, + selected, + editorState.configuration, + history, + editorState.historyCurrentStep + )); } - setNewHistory(newHistory); - const customEvent = new CustomEvent( 'appendContainerToSelectedContainer', { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) }); From 639480678de3125e08732fddd3ee273d6c7c90d1 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 18 Oct 2022 14:56:49 +0200 Subject: [PATCH 05/11] Fix hr in Context menu having the same key --- src/Components/Menu/Menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Menu/Menu.tsx b/src/Components/Menu/Menu.tsx index 841041e..a0fbc03 100644 --- a/src/Components/Menu/Menu.tsx +++ b/src/Components/Menu/Menu.tsx @@ -138,7 +138,7 @@ function AddClassSpecificActions( shortcut={action.shortcut} onClick={() => action.action(target)} />); }); - children.push(
); + children.push(
); }; return count; } From d778e85b99b33bb8559bf0ebc82c098dde5325cb Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 18 Oct 2022 15:00:19 +0200 Subject: [PATCH 06/11] #7344 Fix AddContainer pattern wrong order --- src/Components/Editor/Actions/AddContainer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Components/Editor/Actions/AddContainer.ts b/src/Components/Editor/Actions/AddContainer.ts index b50babb..5258bf6 100644 --- a/src/Components/Editor/Actions/AddContainer.ts +++ b/src/Components/Editor/Actions/AddContainer.ts @@ -326,6 +326,7 @@ function InitializeChildrenWithPattern( const queue: Node[] = [rootNode]; while (queue.length > 0) { let levelSize = queue.length; + const maxLevelSize = levelSize - 1; while (levelSize-- !== 0) { const node = queue.shift() as Node; @@ -334,12 +335,13 @@ function InitializeChildrenWithPattern( if (pattern.children === undefined) { // Add Container const containerConfig = node.containerOrPattern as IAvailableContainer; + const index = maxLevelSize - levelSize; AddNewContainerToParent( containerConfig, configuration, containers, node.parent, - 0, 0, + index, 0, newCounters, symbols ); From 7790ff6dfa9e93346a2919458585a22200e7ca22 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 18 Oct 2022 15:21:41 +0200 Subject: [PATCH 07/11] Fix setting margins causing invalid values --- src/Components/Editor/Actions/ContainerOperations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts index 00b5ba6..3e8525e 100644 --- a/src/Components/Editor/Actions/ContainerOperations.ts +++ b/src/Components/Editor/Actions/ContainerOperations.ts @@ -327,7 +327,7 @@ function AssignProperty(container: ContainerModel, key: string, value: string | function SetMargin(): void { // We need to detect change in order to apply transformation to the width and height // Knowing the current margin is not enough as we dont keep the original width and height - const oldMarginValue: number = (container.properties.margin as any)[key]; + const oldMarginValue: number = (container.properties.margin as any)[key] ?? 0; const diff = Number(value) - oldMarginValue; switch (key) { case 'left': From 5acc57cf7fe15ebdf80ad642c34b11f481da2689 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 18 Oct 2022 15:21:59 +0200 Subject: [PATCH 08/11] Fix potential bug with non valid margin values --- src/utils/svg.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/svg.ts b/src/utils/svg.ts index 263aca2..92e7c47 100644 --- a/src/utils/svg.ts +++ b/src/utils/svg.ts @@ -113,6 +113,8 @@ export function RemoveMargin( top?: number, right?: number ): { x: number, y: number, width: number, height: number } { + left = left ?? 0; + right = right ?? 0; bottom = bottom ?? 0; top = top ?? 0; x = RemoveXMargin(x, left); From 5bc47d02cb42dd90cfbc7d2fe604bb754406f9a1 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 18 Oct 2022 16:16:30 +0200 Subject: [PATCH 09/11] Refactor InitializeChildrenWithPattern --- src/Components/Editor/Actions/AddContainer.ts | 196 +++++++++++++----- 1 file changed, 143 insertions(+), 53 deletions(-) diff --git a/src/Components/Editor/Actions/AddContainer.ts b/src/Components/Editor/Actions/AddContainer.ts index 5258bf6..227b73a 100644 --- a/src/Components/Editor/Actions/AddContainer.ts +++ b/src/Components/Editor/Actions/AddContainer.ts @@ -10,7 +10,7 @@ import { IHistoryState } from '../../../Interfaces/IHistoryState'; import { IPattern, GetPattern, ContainerOrPattern } from '../../../Interfaces/IPattern'; import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; import { Orientation } from '../../../Enums/Orientation'; -import { GetDefaultContainerProps, DEFAULTCHILDTYPE_MAX_DEPTH, DEFAULTCHILDTYPE_ALLOW_CYCLIC } from '../../../utils/default'; +import { GetDefaultContainerProps } from '../../../utils/default'; import { FindContainerById } from '../../../utils/itertools'; import { ApplyMargin } from '../../../utils/svg'; import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors'; @@ -301,9 +301,10 @@ function InitializeChildrenWithPattern( 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; + const pattern = containerOrPattern as IPattern | IAvailableContainer; - if (pattern.children === undefined) { + if (!('children' in pattern)) { + // containerOrPattern is not a pattern but a container const container = containerOrPattern as IAvailableContainer; // Add Container AddNewContainerToParent( @@ -315,11 +316,38 @@ function InitializeChildrenWithPattern( newCounters, symbols ); + return; } // BFS over patterns + BuildPatterns(pattern, newContainer, configuration, containers, newCounters, symbols, configs, patterns); +} + +/** + * Apply the BFS algorithm to build containers from given patterns + * from the top to the bottom + * + * @param pattern + * @param newContainer + * @param configuration + * @param containers + * @param newCounters + * @param symbols + * @param configs + * @param patterns + */ +function BuildPatterns( + rootPattern: IPattern, + newContainer: ContainerModel, + configuration: IConfiguration, + containers: Map, + newCounters: Record, + symbols: Map, + configs: Map, + patterns: Map +): void { const rootNode: Node = { - containerOrPattern: pattern, + containerOrPattern: rootPattern, parent: newContainer }; @@ -330,69 +358,131 @@ function InitializeChildrenWithPattern( while (levelSize-- !== 0) { const node = queue.shift() as Node; - const pattern = node.containerOrPattern as IPattern; + const newParent = AddContainerInLevel(node, maxLevelSize, levelSize, configuration, containers, newCounters, symbols, configs); - if (pattern.children === undefined) { - // Add Container - const containerConfig = node.containerOrPattern as IAvailableContainer; - const index = maxLevelSize - levelSize; - AddNewContainerToParent( - containerConfig, - configuration, - containers, - node.parent, - index, 0, - newCounters, - symbols - ); + if (newParent === undefined) { + // node.pattern is not a IPattern, there is no children to iterate 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 { - const newChildContainer = AddNewContainerToParent( - container, - configuration, - containers, - parent, - 0, 0, - newCounters, - symbols, - undefined, - false - ); + for (let i = newParent.pattern.children.length - 1; i >= 0; i--) { + const nextNode = GetNextNode(newParent.parent, newParent.pattern, i, configs, patterns); - // iterate - parent = newChildContainer; - } - } - - 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) { + if (nextNode === undefined) { continue; } - const nextNode: Node = { - containerOrPattern: child, - parent - }; - queue.push(nextNode); } } } } +/** + * Add a new container in the parent if node.pattern is a Pattern. + * Then, return the next parent to iterate with a pattern/container. + * Otherwise, if node.pattern is a IAvailableContainer, + * create the container from node.pattern and return undefined. + * + * @param node + * @param maxLevelSize + * @param levelSize + * @param configuration + * @param containers + * @param newCounters + * @param symbols + * @param configs + * @returns + */ +function AddContainerInLevel( + node: Node, + maxLevelSize: number, + levelSize: number, + configuration: IConfiguration, + containers: Map, + newCounters: Record, + symbols: Map, + configs: Map +): { parent: IContainerModel, pattern: IPattern } | undefined { + if (!('children' in node.containerOrPattern)) { + // Add Container from pattern + const containerConfig: IAvailableContainer = node.containerOrPattern; + const index = maxLevelSize - levelSize; + AddNewContainerToParent( + containerConfig, + configuration, + containers, + node.parent, + index, 0, + newCounters, + symbols + ); + return; + } + + const pattern: IPattern = node.containerOrPattern; + const parent = node.parent; + + if (pattern.wrapper === undefined) { + return { parent, pattern }; + } + + // Add Container from wrapper + // and set the new parent as the child of this parent + 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.`); + return { parent, pattern }; + } + + const newChildContainer = AddNewContainerToParent( + container, + configuration, + containers, + parent, + 0, 0, + newCounters, + symbols, + undefined, + false + ); + + // change the parent to be the child of the wrapper + return { parent: newChildContainer, pattern }; +} + +/** + * Return the next node from the given pattern from the configs + * + * @param parent + * @param pattern + * @param i + * @param configs + * @param patterns + * @returns {Node} The next node + */ +function GetNextNode( + parent: IContainerModel, + pattern: IPattern, + i: number, + configs: Map, + patterns: Map +): Node | undefined { + const childId: string = pattern.children[i]; + + const child = GetPattern(childId, configs, patterns); + + if (child === undefined) { + return undefined; + } + + return { + containerOrPattern: child, + parent + }; +} + interface Node { containerOrPattern: ContainerOrPattern parent: IContainerModel From b44c6fb4777fb13d5bb76f22f0766ae6957fdd5f Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 18 Oct 2022 16:22:44 +0200 Subject: [PATCH 10/11] Refactor and fix potential bug in InitializeChildrenWithPattern when pattern is not found in the config --- src/Components/Editor/Actions/AddContainer.ts | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/Components/Editor/Actions/AddContainer.ts b/src/Components/Editor/Actions/AddContainer.ts index 227b73a..06100ea 100644 --- a/src/Components/Editor/Actions/AddContainer.ts +++ b/src/Components/Editor/Actions/AddContainer.ts @@ -301,26 +301,14 @@ function InitializeChildrenWithPattern( 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 | IAvailableContainer; - if (!('children' in pattern)) { - // containerOrPattern is not a pattern but a container - const container = containerOrPattern as IAvailableContainer; - // Add Container - AddNewContainerToParent( - container, - configuration, - containers, - newContainer, - 0, 0, - newCounters, - symbols - ); + if (containerOrPattern === undefined) { + console.warn(`[InitializeChildrenWithPattern] PatternId ${patternId} was neither found as Pattern nor as IAvailableContainer`); return; } // BFS over patterns - BuildPatterns(pattern, newContainer, configuration, containers, newCounters, symbols, configs, patterns); + BuildPatterns(containerOrPattern, newContainer, configuration, containers, newCounters, symbols, configs, patterns); } /** @@ -337,7 +325,7 @@ function InitializeChildrenWithPattern( * @param patterns */ function BuildPatterns( - rootPattern: IPattern, + rootPattern: ContainerOrPattern, newContainer: ContainerModel, configuration: IConfiguration, containers: Map, From afffaa05958c470b74784ac617459d6ee703ab3b Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Wed, 19 Oct 2022 10:09:09 +0200 Subject: [PATCH 11/11] Fix Symbols serialization like the containers --- src/utils/saveload.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/saveload.ts b/src/utils/saveload.ts index ad9a4f3..43a6ec0 100644 --- a/src/utils/saveload.ts +++ b/src/utils/saveload.ts @@ -2,6 +2,7 @@ import { IEditorState } from '../Interfaces/IEditorState'; import { IHistoryState } from '../Interfaces/IHistoryState'; import { IContainerModel } from '../Interfaces/IContainerModel'; +import { ISymbolModel } from '../Interfaces/ISymbolModel'; /** * Revive the Editor state @@ -29,13 +30,14 @@ export function ReviveState(state: IHistoryState): void { return; } - state.symbols = new Map(state.symbols); + const symbols: Array<{ Key: string, Value: ISymbolModel }> = (state.symbols) as any; + state.symbols = new Map(symbols.map(({ Key, Value }) => [Key, Value])); for (const symbol of state.symbols.values()) { symbol.linkedContainers = new Set(symbol.linkedContainers); } const containers: Array<{ Key: string, Value: IContainerModel }> = (state.containers) as any; - state.containers = new Map(containers.map(({ Key, Value }: {Key: string, Value: IContainerModel}) => [Key, Value])); + state.containers = new Map(containers.map(({ Key, Value }) => [Key, Value])); } export function GetCircularReplacer(): (key: any, value: object | Map | null) => object | null | undefined {