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 }));