diff --git a/.gitattributes b/.gitattributes index 7118888..54d6a95 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ *.pdf filter=lfs diff=lfs merge=lfs -text *.dwg filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text +*.pptx filter=lfs diff=lfs merge=lfs -text diff --git a/csharp/SVGLDLibs/SVGLDLibs/Models/ContainerProperties.cs b/csharp/SVGLDLibs/SVGLDLibs/Models/ContainerProperties.cs index e973393..27bd9ab 100644 --- a/csharp/SVGLDLibs/SVGLDLibs/Models/ContainerProperties.cs +++ b/csharp/SVGLDLibs/SVGLDLibs/Models/ContainerProperties.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization; +using System.Collections.Generic; namespace SVGLDLibs.Models { @@ -149,6 +150,6 @@ namespace SVGLDLibs.Models * User data that can be used for data storage or custom SVG */ [DataMember(EmitDefaultValue = false)] - public object userData; + public Dictionary userData; } } \ No newline at end of file diff --git a/csharp/SVGLDLibs/SVGLDLibs/Models/PointModel.cs b/csharp/SVGLDLibs/SVGLDLibs/Models/PointModel.cs index 26b63a8..4291204 100644 --- a/csharp/SVGLDLibs/SVGLDLibs/Models/PointModel.cs +++ b/csharp/SVGLDLibs/SVGLDLibs/Models/PointModel.cs @@ -6,8 +6,8 @@ namespace SVGLDLibs.Models public class PointModel { [DataMember(EmitDefaultValue = false)] - public string X { get; set; } + public double x { get; set; } [DataMember(EmitDefaultValue = false)] - public string Y { get; set; } + public double y { get; set; } } } \ No newline at end of file diff --git a/csharp/SVGLDLibs/SVGLDLibs/Models/SymbolModel.cs b/csharp/SVGLDLibs/SVGLDLibs/Models/SymbolModel.cs index 7cbce20..97e7615 100644 --- a/csharp/SVGLDLibs/SVGLDLibs/Models/SymbolModel.cs +++ b/csharp/SVGLDLibs/SVGLDLibs/Models/SymbolModel.cs @@ -1,17 +1,24 @@ using System.Runtime.Serialization; +using System.Collections.Generic; namespace SVGLDLibs.Models { [DataContract] - public class SymbolModel : AvailableSymbolModel + public class SymbolModel { [DataMember(EmitDefaultValue = false)] - public string Id { get; set; } + public string id { get; set; } [DataMember(EmitDefaultValue = false)] - public PointModel Point { get; set; } + public string type { get; set; } [DataMember(EmitDefaultValue = false)] - public bool IsLinkedToContainer { get; set; } + public AvailableSymbolModel config { get; set; } [DataMember(EmitDefaultValue = false)] - public string LinkedContainerId { get; set; } + public double x { get; set; } + [DataMember(EmitDefaultValue = false)] + public double width { get; set; } + [DataMember(EmitDefaultValue = false)] + public double height { get; set; } + [DataMember(EmitDefaultValue = false)] + public List linkedContainers { get; set; } } } \ No newline at end of file diff --git a/csharp/SVGLDLibs/SVGLDWebAPI/Controllers/SVGLDController.cs b/csharp/SVGLDLibs/SVGLDWebAPI/Controllers/SVGLDController.cs index a1c936f..e6031aa 100644 --- a/csharp/SVGLDLibs/SVGLDWebAPI/Controllers/SVGLDController.cs +++ b/csharp/SVGLDLibs/SVGLDWebAPI/Controllers/SVGLDController.cs @@ -45,7 +45,7 @@ public class SVGLDController : ControllerBase } [HttpPost(Name = nameof(SVGLDLibs.Models.Configuration))] - public bool ConfigurationResponseModel(Configuration model) + public bool Configuration(Configuration model) { return true; } diff --git a/docs/#Project/Pages/SVGLD_Cotes.pdf b/docs/#Project/Pages/SVGLD_Cotes.pdf new file mode 100644 index 0000000..938f9d6 --- /dev/null +++ b/docs/#Project/Pages/SVGLD_Cotes.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9200f873dc30971dd9f6f88fc4a5c2b0b716d31ba80470d930fef17863866c8b +size 309145 diff --git a/docs/#Project/Pages/SVGLD_Cotes.pptx b/docs/#Project/Pages/SVGLD_Cotes.pptx new file mode 100644 index 0000000..14fbfc5 --- /dev/null +++ b/docs/#Project/Pages/SVGLD_Cotes.pptx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c800e17f3aad51dca4638b4a5474a70fbf7644d32bb7b1d37e88518ad5dc3a2b +size 539089 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..ca8ff72 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 ); @@ -191,12 +190,7 @@ describe.concurrent('Models test suite', () => { } ], CustomSVG: 'string', - Style: {}, - UserData: { - additionalProp1: 'string', - additionalProp2: 'string', - additionalProp3: 'string' - } + Style: {} }; it('AvailableContainerModel', async() => { @@ -239,7 +233,7 @@ describe.concurrent('Models test suite', () => { wrapper: 'string' }; - it('ConfigurationResponseModel', async() => { + it('Configuration', async() => { const model: IConfiguration = { AvailableContainers: [ availableContainerModel @@ -251,10 +245,15 @@ describe.concurrent('Models test suite', () => { category ], MainContainer: availableContainerModel, - Patterns: [pattern] + Patterns: [pattern], + APIConfiguration: { + apiFetchUrl: '', + apiGetFeedbackUrl: '', + apiSetContainerListUrl: '' + } }; - const res = await Post2API('ConfigurationResponseModel', JSON.stringify(model)); + const res = await Post2API('Configuration', JSON.stringify(model)); expect(res).toBe(true); }); @@ -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..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 } from '../../Events/AppEvents'; +import { UseCustomEvents } from '../../Events/AppEvents'; import { MainMenu } from '../MainMenu/MainMenu'; import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel'; import { Editor } from '../Editor/Editor'; @@ -41,44 +41,11 @@ 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); 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..89778ab 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'; @@ -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 @@ -163,7 +157,6 @@ function AddNewContainerToParent( // Create the container const newContainer = new ContainerModel( - parentClone, defaultProperties, [], { @@ -308,96 +301,176 @@ 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; - if (pattern.children === undefined) { - 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(containerOrPattern, 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: ContainerOrPattern, + newContainer: ContainerModel, + configuration: IConfiguration, + containers: Map, + newCounters: Record, + symbols: Map, + configs: Map, + patterns: Map +): void { const rootNode: Node = { - containerOrPattern: pattern, + containerOrPattern: rootPattern, parent: newContainer }; 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; - 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; - AddNewContainerToParent( - containerConfig, - configuration, - containers, - node.parent, - 0, 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 = 0; i <= newParent.pattern.children.length - 1; 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 diff --git a/src/Components/Editor/Actions/ContainerOperations.ts b/src/Components/Editor/Actions/ContainerOperations.ts index d2902c6..f264f5a 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; @@ -292,14 +289,17 @@ function SetContainer( symbols ); - // sort the children list by their position - SortChildren(containers, container.parent); - // Apply special behaviors: rigid, flex, symbol, anchor ApplyBehaviors(containers, container, symbols); // Apply special behaviors on siblings ApplyBehaviorsOnSiblingsChildren(containers, container, symbols); + + // sort the children list by their position + const parent = FindContainerById(containers, container.properties.parentId); + if (parent !== null && parent !== undefined) { + SortChildren(containers, parent); + } } /** @@ -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': diff --git a/src/Components/Editor/Actions/ContextMenuActions.ts b/src/Components/Editor/Actions/ContextMenuActions.ts index e5f8ea0..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,12 +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, @@ -62,15 +173,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 +198,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 +218,7 @@ function HandleSetContainerList( case AddMethod.Replace: setNewHistory( HandleReplace( + containers, selectedContainer, response, configuration, @@ -112,7 +228,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 +238,8 @@ function HandleSetContainerList( } setNewHistory( HandleReplace( - selectedContainer.parent, + containers, + parent, response, configuration, history, @@ -134,17 +251,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/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 507fa58..372ab06 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 @@ -279,14 +115,11 @@ export function Editor(props: IEditorProps): JSX.Element { return (
setNewHistory( SelectContainer( container, @@ -307,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/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/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; } diff --git a/src/Components/SVG/Elements/DepthDimensionLayer.tsx b/src/Components/SVG/Elements/DepthDimensionLayer.tsx index 0c3a36e..ec93d9c 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) { @@ -74,7 +74,7 @@ function AddNewDimension(currentDepth: number, min: number, max: number, lastY: const id = `dim-depth-${currentDepth}`; const xStart = min; const xEnd = max; - const y = (lastY + (DIMENSION_MARGIN * (currentDepth + 1))) / scale; + const y = lastY + (DIMENSION_MARGIN * (currentDepth + 1)) / scale; const strokeWidth = 1; const width = xEnd - xStart; const text = width 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/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 { , + 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..c67fc4c 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) }); @@ -181,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]) }); diff --git a/src/Interfaces/IAvailableContainer.ts b/src/Interfaces/IAvailableContainer.ts index c973d41..a4067e3 100644 --- a/src/Interfaces/IAvailableContainer.ts +++ b/src/Interfaces/IAvailableContainer.ts @@ -6,6 +6,7 @@ import { IAction } from './IAction'; import { IMargin } from './IMargin'; import { Orientation } from '../Enums/Orientation'; import { Position } from '../Enums/Position'; +import { IKeyValue } from './IKeyValue'; /** Model of available container used in application configuration */ export interface IAvailableContainer { @@ -161,5 +162,5 @@ export interface IAvailableContainer { * (optional) * User data that can be used for data storage or custom SVG */ - UserData?: object + UserData?: IKeyValue[] } 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/Interfaces/IContainerProperties.ts b/src/Interfaces/IContainerProperties.ts index 11ae73f..35b1db7 100644 --- a/src/Interfaces/IContainerProperties.ts +++ b/src/Interfaces/IContainerProperties.ts @@ -3,6 +3,7 @@ import { PositionReference } from '../Enums/PositionReference'; import { IMargin } from './IMargin'; import { Orientation } from '../Enums/Orientation'; import { Position } from '../Enums/Position'; +import { IKeyValue } from './IKeyValue'; /** * Properties of a container @@ -122,5 +123,5 @@ export interface IContainerProperties { * (optional) * User data that can be used for data storage or custom SVG */ - userData?: object + userData?: IKeyValue[] } diff --git a/src/Interfaces/IKeyValue.ts b/src/Interfaces/IKeyValue.ts new file mode 100644 index 0000000..06a7b0c --- /dev/null +++ b/src/Interfaces/IKeyValue.ts @@ -0,0 +1,5 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +export interface IKeyValue { + Key: string + Value: string +} diff --git a/src/utils/default.ts b/src/utils/default.ts index 253610b..97870a4 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(); @@ -235,8 +234,6 @@ export function GetDefaultContainerProps(type: string, height: number, containerConfig: IAvailableContainer): IContainerProperties { const orientation = containerConfig.Orientation ?? Orientation.Horizontal; - const defaultIsFlex = (orientation === Orientation.Vertical && containerConfig.Height === undefined) || - (orientation === Orientation.Horizontal && containerConfig.Width === undefined); return ({ id: `${type}-${typeCount}`, type, @@ -250,7 +247,7 @@ export function GetDefaultContainerProps(type: string, width, height, isAnchor: containerConfig.IsAnchor ?? false, - isFlex: containerConfig.IsFlex ?? defaultIsFlex, + isFlex: containerConfig.IsFlex ?? false, positionReference: containerConfig.PositionReference ?? PositionReference.TopLeft, minWidth: containerConfig.MinWidth ?? 1, maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER, 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..43a6ec0 100644 --- a/src/utils/saveload.ts +++ b/src/utils/saveload.ts @@ -1,8 +1,8 @@ /* 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'; +import { ISymbolModel } from '../Interfaces/ISymbolModel'; /** * Revive the Editor state @@ -30,43 +30,18 @@ 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])); - - 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; - } + state.containers = new Map(containers.map(({ Key, Value }) => [Key, Value])); } 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 })); 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);