From b8bd179167bd5780033941e0d91dfb71b890dcf9 Mon Sep 17 00:00:00 2001 From: Siklos Date: Mon, 15 Aug 2022 15:12:35 -0400 Subject: [PATCH 01/14] Added Dimension of all children under the container (#30) Reviewed-on: https://git.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react/pulls/30 --- src/Components/SVG/Elements/Container.tsx | 52 +++++++++++++++++++++-- src/utils/default.ts | 1 + 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index 6417570..e743899 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { XPositionReference } from '../../../Enums/XPositionReference'; import { IContainerModel } from '../../../Interfaces/IContainerModel'; +import { DIMENSION_MARGIN } from '../../../utils/default'; import { getDepth } from '../../../utils/itertools'; import { Dimension } from './Dimension'; @@ -8,8 +9,6 @@ interface IContainerProps { model: IContainerModel } -const GAP = 50; - /** * Render the container * @returns Render the container @@ -45,13 +44,35 @@ export const Container: React.FC = (props: IContainerProps) => delete style.width; // Dimension props + const depth = getDepth(props.model); + const dimensionMargin = DIMENSION_MARGIN * (depth + 1); const id = `dim-${props.model.properties.id}`; const xStart: number = 0; const xEnd = Number(props.model.properties.width); - const y = -(GAP * (getDepth(props.model) + 1)); + const y = -dimensionMargin; const strokeWidth = 1; const text = (props.model.properties.width ?? 0).toString(); + let dimensionChildren: JSX.Element | null = null; + if (props.model.children.length > 0) { + const { + childrenId, + xChildrenStart, + xChildrenEnd, + yChildren, + textChildren + } = GetChildrenDimensionProps(props, dimensionMargin); + dimensionChildren = ; + } + return ( = (props: IContainerProps) => strokeWidth={strokeWidth} text={text} /> + { dimensionChildren } = (props: IContainerProps) => ); }; +function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: number): +{ childrenId: string, xChildrenStart: number, xChildrenEnd: number, yChildren: number, textChildren: string } { + const childrenId = `dim-children-${props.model.properties.id}`; + + const lastChild = props.model.children[props.model.children.length - 1]; + let xChildrenStart = lastChild.properties.x; + let xChildrenEnd = lastChild.properties.x + Number(lastChild.properties.width); + for (let i = props.model.children.length - 2; i >= 0; i--) { + const child = props.model.children[i]; + const left = child.properties.x; + if (left < xChildrenStart) { + xChildrenStart = left; + } + const right = child.properties.x + Number(child.properties.width); + if (right > xChildrenEnd) { + xChildrenEnd = right; + } + } + + const yChildren = Number(props.model.properties.height) + dimensionMargin; + const textChildren = (xChildrenEnd - xChildrenStart).toString(); + return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren }; +} + function transformPosition(x: number, y: number, width: number, xPositionReference = XPositionReference.Left): [number, number] { let transformedX = x; if (xPositionReference === XPositionReference.Center) { diff --git a/src/utils/default.ts b/src/utils/default.ts index 027c540..44f737f 100644 --- a/src/utils/default.ts +++ b/src/utils/default.ts @@ -38,6 +38,7 @@ export const DEFAULT_MAINCONTAINER_PROPS: IProperties = { stroke: 'black' }; +export const DIMENSION_MARGIN = 50; export const NOTCHES_LENGTH = 4; export const MAX_HISTORY = 200; From 522cd722c01af8183f66a2b215efe0ab5bec20b8 Mon Sep 17 00:00:00 2001 From: Siklos Date: Mon, 15 Aug 2022 22:07:08 +0200 Subject: [PATCH 02/14] Added AddMethod and Default x,y,width,height on Add --- src/Components/Editor/ContainerOperations.ts | 44 ++++++++++++------- .../Properties/PropertiesInputTypes.tsx | 3 +- src/Enums/AddMethod.ts | 10 +++++ src/Interfaces/IAvailableContainer.ts | 8 +++- test-server/http.js | 20 +++++++-- 5 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 src/Enums/AddMethod.ts diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index f1736fc..e2df160 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -1,10 +1,12 @@ -import { Dispatch, SetStateAction } from 'react'; +import React, { Dispatch, SetStateAction } from 'react'; import { IHistoryState } from '../../Interfaces/IHistoryState'; import { IConfiguration } from '../../Interfaces/IConfiguration'; import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel'; import { findContainerById } from '../../utils/itertools'; import { getCurrentHistory } from './Editor'; import IProperties from '../../Interfaces/IProperties'; +import { AddMethod } from '../../Enums/AddMethod'; +import { IAvailableContainer } from '../../Interfaces/IAvailableContainer'; /** * Select a container @@ -169,10 +171,10 @@ export function AddContainer( } // Get the preset properties from the API - const properties = configuration.AvailableContainers + const containerConfig = configuration.AvailableContainers .find(option => option.Type === type); - if (properties === undefined) { + if (containerConfig === undefined) { throw new Error(`[AddContainer] Object type not found. Found: ${type}`); } @@ -198,27 +200,25 @@ export function AddContainer( throw new Error('[AddContainer] Container model was not found among children of the main container!'); } - let x = 0; - if (index > 0) { - const lastChild: IContainerModel | undefined = parentClone.children.at(index - 1); - if (lastChild !== undefined) { - x = lastChild.properties.x + Number(lastChild.properties.width); - } - } + let x = containerConfig.DefaultX ?? 0; + const y = containerConfig.DefaultY ?? 0; + const width = containerConfig.Width ?? parentClone.properties.width; + const height = containerConfig.Height ?? parentClone.properties.height; + + x = ApplyAddMethod(index, containerConfig, parentClone, x); const defaultProperties: IProperties = { id: `${type}-${count}`, parentId: parentClone.properties.id, x, - y: 0, - width: properties.Width, - height: parentClone.properties.height, + y, + width, + height, isRigidBody: false, isAnchor: false, - XPositionReference: properties.XPositionReference, - ...properties.Style + XPositionReference: containerConfig.XPositionReference, + ...containerConfig.Style }; - // Create the container const newContainer = new ContainerModel( parentClone, @@ -247,3 +247,15 @@ export function AddContainer( setHistory(history); setHistoryCurrentStep(history.length - 1); } + +function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, parentClone: IContainerModel, x: number): number { + if (index > 0 && ( + containerConfig.AddMethod === undefined || + containerConfig.AddMethod === AddMethod.Append)) { + const lastChild: IContainerModel | undefined = parentClone.children.at(index - 1); + if (lastChild !== undefined) { + x += lastChild.properties.x + Number(lastChild.properties.width); + } + } + return x; +} diff --git a/src/Components/Properties/PropertiesInputTypes.tsx b/src/Components/Properties/PropertiesInputTypes.tsx index d91ddbc..b8f29d3 100644 --- a/src/Components/Properties/PropertiesInputTypes.tsx +++ b/src/Components/Properties/PropertiesInputTypes.tsx @@ -4,5 +4,6 @@ export const INPUT_TYPES: Record = { width: 'number', height: 'number', isRigidBody: 'checkbox', - isAnchor: 'checkbox' + isAnchor: 'checkbox', + XPositionReference: 'number' }; diff --git a/src/Enums/AddMethod.ts b/src/Enums/AddMethod.ts new file mode 100644 index 0000000..c0d1e37 --- /dev/null +++ b/src/Enums/AddMethod.ts @@ -0,0 +1,10 @@ +/** + * Add method when creating a container + * - Append will append to the last children in list + * - Insert will always place it at the begining + * (default: Append) + */ +export enum AddMethod { + Append, + Insert +} diff --git a/src/Interfaces/IAvailableContainer.ts b/src/Interfaces/IAvailableContainer.ts index c7ad3c1..2f4a011 100644 --- a/src/Interfaces/IAvailableContainer.ts +++ b/src/Interfaces/IAvailableContainer.ts @@ -1,11 +1,15 @@ import React from 'react'; +import { AddMethod } from '../Enums/AddMethod'; import { XPositionReference } from '../Enums/XPositionReference'; /** Model of available container used in application configuration */ export interface IAvailableContainer { Type: string - Width: number - Height: number + Width?: number + Height?: number + DefaultX?: number + DefaultY?: number + AddMethod?: AddMethod XPositionReference?: XPositionReference Style: React.CSSProperties } diff --git a/test-server/http.js b/test-server/http.js index 69088c9..3c54590 100644 --- a/test-server/http.js +++ b/test-server/http.js @@ -62,20 +62,34 @@ const GetSVGLayoutConfiguration = () => { }, { Type: 'Trou', - Width: 300, + DefaultX: 10, + DefaultY: 10, + Width: 480, + Height: 180, Style: { fillOpacity: 0, borderWidth: 2, stroke: 'green' } }, + { + Type: 'Remplissage', + Style: { + fillOpacity: 1, + borderWidth: 2, + stroke: '#bfdbfe', + fill: '#bfdbfe' + } + }, { Type: 'Montant', - Width: 100, + Width: 10, + XPositionReference: 1, Style: { fillOpacity: 0, borderWidth: 2, - stroke: 'blue', + stroke: '#713f12', + fill: '#713f12' } } ], From 4fbc67835c2571412642149c7de6699fda370002 Mon Sep 17 00:00:00 2001 From: Siklos Date: Mon, 15 Aug 2022 22:26:59 +0200 Subject: [PATCH 03/14] XPositionReference: Fix Selector and Append --- src/Components/Editor/ContainerOperations.ts | 24 +++++++++++++++++--- src/Components/SVG/Elements/Container.tsx | 2 +- src/Components/SVG/Elements/Selector.tsx | 11 +++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index e2df160..a6defd6 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -7,6 +7,7 @@ import { getCurrentHistory } from './Editor'; import IProperties from '../../Interfaces/IProperties'; import { AddMethod } from '../../Enums/AddMethod'; import { IAvailableContainer } from '../../Interfaces/IAvailableContainer'; +import { transformPosition } from '../SVG/Elements/Container'; /** * Select a container @@ -248,13 +249,30 @@ export function AddContainer( setHistoryCurrentStep(history.length - 1); } -function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, parentClone: IContainerModel, x: number): number { +/** + * Returns a new offset by applying an Add method (append, insert etc.) + * See AddMethod + * @param index Index of the container + * @param containerConfig Configuration of a container + * @param parent Parent container + * @param x Additionnal offset + * @returns New offset + */ +function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, parent: IContainerModel, x: number): number { if (index > 0 && ( containerConfig.AddMethod === undefined || containerConfig.AddMethod === AddMethod.Append)) { - const lastChild: IContainerModel | undefined = parentClone.children.at(index - 1); + const lastChild: IContainerModel | undefined = parent.children.at(index - 1); + if (lastChild !== undefined) { - x += lastChild.properties.x + Number(lastChild.properties.width); + const [transformedX] = transformPosition( + Number(lastChild.properties.x), + Number(lastChild.properties.y), + Number(lastChild.properties.width), + lastChild.properties.XPositionReference + ); + + x += transformedX + Number(lastChild.properties.width); } } return x; diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index e743899..36e5f19 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -130,7 +130,7 @@ function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: numb return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren }; } -function transformPosition(x: number, y: number, width: number, xPositionReference = XPositionReference.Left): [number, number] { +export function transformPosition(x: number, y: number, width: number, xPositionReference = XPositionReference.Left): [number, number] { let transformedX = x; if (xPositionReference === XPositionReference.Center) { transformedX -= width / 2; diff --git a/src/Components/SVG/Elements/Selector.tsx b/src/Components/SVG/Elements/Selector.tsx index e70ca79..5f0c2ea 100644 --- a/src/Components/SVG/Elements/Selector.tsx +++ b/src/Components/SVG/Elements/Selector.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { getAbsolutePosition } from '../../../utils/itertools'; +import { transformPosition } from './Container'; interface ISelectorProps { selected: IContainerModel | null @@ -15,6 +16,12 @@ export const Selector: React.FC = (props) => { } const [x, y] = getAbsolutePosition(props.selected); + const [transformedX, transformedY] = transformPosition( + x, + y, + Number(props.selected.properties.width), + props.selected.properties.XPositionReference + ); const [width, height] = [ props.selected.properties.width, props.selected.properties.height @@ -31,8 +38,8 @@ export const Selector: React.FC = (props) => { return ( Date: Mon, 15 Aug 2022 22:52:09 +0200 Subject: [PATCH 04/14] Fix width and height not being numbers --- src/Interfaces/IProperties.ts | 4 +++- test-server/http.js | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Interfaces/IProperties.ts b/src/Interfaces/IProperties.ts index ef2db7e..ad23442 100644 --- a/src/Interfaces/IProperties.ts +++ b/src/Interfaces/IProperties.ts @@ -10,11 +10,13 @@ import { XPositionReference } from '../Enums/XPositionReference'; * @property isRigidBody if true apply rigid body behaviors * @property isAnchor if true apply anchor behaviors */ -export default interface IProperties extends React.CSSProperties { +export default interface IProperties extends Omit { id: string parentId: string | null x: number y: number + width: number + height: number isRigidBody: boolean isAnchor: boolean XPositionReference?: XPositionReference diff --git a/test-server/http.js b/test-server/http.js index 3c54590..e6b7051 100644 --- a/test-server/http.js +++ b/test-server/http.js @@ -55,8 +55,10 @@ const GetSVGLayoutConfiguration = () => { Type: 'Chassis', Width: 500, Style: { - fillOpacity: 0, + fillOpacity: 1, borderWidth: 2, + stroke: 'red', + fill: '#78350F', stroke: 'red' } }, @@ -67,9 +69,10 @@ const GetSVGLayoutConfiguration = () => { Width: 480, Height: 180, Style: { - fillOpacity: 0, + fillOpacity: 1, borderWidth: 2, - stroke: 'green' + stroke: 'green', + fill: 'white' } }, { @@ -89,7 +92,7 @@ const GetSVGLayoutConfiguration = () => { fillOpacity: 0, borderWidth: 2, stroke: '#713f12', - fill: '#713f12' + fill: '#713f12', } } ], From 3c164581cedf55fa1cae638d9752dc3eaee5af3a Mon Sep 17 00:00:00 2001 From: Siklos Date: Mon, 15 Aug 2022 22:57:08 +0200 Subject: [PATCH 05/14] Remove Number() calls --- .../Editor/Behaviors/AnchorBehaviors.ts | 4 ++-- .../Editor/Behaviors/RigidBodyBehaviors.ts | 24 +++++++++---------- src/Components/Editor/ContainerOperations.ts | 8 +++---- src/Components/Editor/Editor.tsx | 4 ++-- src/Components/SVG/Elements/Container.tsx | 18 +++++++------- src/Components/SVG/Elements/Selector.tsx | 2 +- src/utils/itertools.ts | 8 +++---- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Components/Editor/Behaviors/AnchorBehaviors.ts b/src/Components/Editor/Behaviors/AnchorBehaviors.ts index d9731e8..22ce2f4 100644 --- a/src/Components/Editor/Behaviors/AnchorBehaviors.ts +++ b/src/Components/Editor/Behaviors/AnchorBehaviors.ts @@ -49,7 +49,7 @@ function getOverlappingContainers( containers: IContainerModel[] ): IContainerModel[] { const min1 = container.properties.x; - const max1 = container.properties.x + Number(container.properties.width); + const max1 = container.properties.x + container.properties.width; const overlappingContainers: IContainerModel[] = []; for (const other of containers) { if (other === container) { @@ -57,7 +57,7 @@ function getOverlappingContainers( } const min2 = other.properties.x; - const max2 = other.properties.x + Number(other.properties.width); + const max2 = other.properties.x + other.properties.width; const isOverlapping = Math.min(max1, max2) - Math.max(min1, min2) > 0; if (!isOverlapping) { diff --git a/src/Components/Editor/Behaviors/RigidBodyBehaviors.ts b/src/Components/Editor/Behaviors/RigidBodyBehaviors.ts index eabec85..e0ffb2f 100644 --- a/src/Components/Editor/Behaviors/RigidBodyBehaviors.ts +++ b/src/Components/Editor/Behaviors/RigidBodyBehaviors.ts @@ -42,8 +42,8 @@ function constraintBodyInsideParent( } const parentProperties = container.parent.properties; - const parentWidth = Number(parentProperties.width); - const parentHeight = Number(parentProperties.height); + const parentWidth = parentProperties.width; + const parentHeight = parentProperties.height; return constraintBodyInsideSpace(container, 0, 0, parentWidth, parentHeight); } @@ -66,10 +66,10 @@ function constraintBodyInsideSpace( height: number ): IContainerModel { const containerProperties = container.properties; - const containerX = Number(containerProperties.x); - const containerY = Number(containerProperties.y); - const containerWidth = Number(containerProperties.width); - const containerHeight = Number(containerProperties.height); + const containerX = containerProperties.x; + const containerY = containerProperties.y; + const containerWidth = containerProperties.width; + const containerHeight = containerProperties.height; // Check size bigger than parent const isBodyLargerThanParent = containerWidth > width; @@ -121,8 +121,8 @@ export function constraintBodyInsideUnallocatedWidth( // Get the available spaces of the parent const availableWidths = getAvailableWidths(container.parent, container); - const containerX = Number(container.properties.x); - const containerWidth = Number(container.properties.width); + const containerX = container.properties.x; + const containerWidth = container.properties.width; // Check if there is still some space if (availableWidths.length === 0) { @@ -177,7 +177,7 @@ export function constraintBodyInsideUnallocatedWidth( availableWidthFound.x, 0, availableWidthFound.width, - Number(container.parent.properties.height) + container.parent.properties.height ); } @@ -197,7 +197,7 @@ function getAvailableWidths( // Initialize the first size pointer // which takes full width of the available space const x = 0; - const width = Number(container.properties.width); + const width = container.properties.width; let unallocatedSpaces: ISizePointer[] = [{ x, width }]; // We will only uses containers that also are rigid or are anchors @@ -211,7 +211,7 @@ function getAvailableWidths( continue; } const childX = child.properties.x; - const childWidth = Number(child.properties.width); + const childWidth = child.properties.width; // get the space of the child that is inside the parent let newUnallocatedSpace: ISizePointer[] = []; @@ -309,4 +309,4 @@ function getAvailableWidthsTwoLines( const isFitting = ( container: IContainerModel, sizePointer: ISizePointer -): boolean => Number(container.properties.width) <= sizePointer.width; +): boolean => container.properties.width <= sizePointer.width; diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index a6defd6..43f7d31 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -266,13 +266,13 @@ function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, par if (lastChild !== undefined) { const [transformedX] = transformPosition( - Number(lastChild.properties.x), - Number(lastChild.properties.y), - Number(lastChild.properties.width), + lastChild.properties.x, + lastChild.properties.y, + lastChild.properties.width, lastChild.properties.XPositionReference ); - x += transformedX + Number(lastChild.properties.width); + x += transformedX + lastChild.properties.width; } } return x; diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index 3a728e3..fcd62f9 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -133,8 +133,8 @@ const Editor: React.FunctionComponent = (props) => { LoadState={(move) => setHistoryCurrentStep(move)} /> { current.MainContainer } diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index 36e5f19..56d4797 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -15,13 +15,13 @@ interface IContainerProps { */ export const Container: React.FC = (props: IContainerProps) => { const containersElements = props.model.children.map(child => ); - const xText = Number(props.model.properties.width) / 2; - const yText = Number(props.model.properties.height) / 2; + const xText = props.model.properties.width / 2; + const yText = props.model.properties.height / 2; const [transformedX, transformedY] = transformPosition( - Number(props.model.properties.x), - Number(props.model.properties.y), - Number(props.model.properties.width), + props.model.properties.x, + props.model.properties.y, + props.model.properties.width, props.model.properties.XPositionReference ); const transform = `translate(${transformedX}, ${transformedY})`; @@ -48,7 +48,7 @@ export const Container: React.FC = (props: IContainerProps) => const dimensionMargin = DIMENSION_MARGIN * (depth + 1); const id = `dim-${props.model.properties.id}`; const xStart: number = 0; - const xEnd = Number(props.model.properties.width); + const xEnd = props.model.properties.width; const y = -dimensionMargin; const strokeWidth = 1; const text = (props.model.properties.width ?? 0).toString(); @@ -112,20 +112,20 @@ function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: numb const lastChild = props.model.children[props.model.children.length - 1]; let xChildrenStart = lastChild.properties.x; - let xChildrenEnd = lastChild.properties.x + Number(lastChild.properties.width); + let xChildrenEnd = lastChild.properties.x + lastChild.properties.width; for (let i = props.model.children.length - 2; i >= 0; i--) { const child = props.model.children[i]; const left = child.properties.x; if (left < xChildrenStart) { xChildrenStart = left; } - const right = child.properties.x + Number(child.properties.width); + const right = child.properties.x + child.properties.width; if (right > xChildrenEnd) { xChildrenEnd = right; } } - const yChildren = Number(props.model.properties.height) + dimensionMargin; + const yChildren = props.model.properties.height + dimensionMargin; const textChildren = (xChildrenEnd - xChildrenStart).toString(); return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren }; } diff --git a/src/Components/SVG/Elements/Selector.tsx b/src/Components/SVG/Elements/Selector.tsx index 5f0c2ea..101f19a 100644 --- a/src/Components/SVG/Elements/Selector.tsx +++ b/src/Components/SVG/Elements/Selector.tsx @@ -19,7 +19,7 @@ export const Selector: React.FC = (props) => { const [transformedX, transformedY] = transformPosition( x, y, - Number(props.selected.properties.width), + props.selected.properties.width, props.selected.properties.XPositionReference ); const [width, height] = [ diff --git a/src/utils/itertools.ts b/src/utils/itertools.ts index d50a089..221d6c0 100644 --- a/src/utils/itertools.ts +++ b/src/utils/itertools.ts @@ -43,12 +43,12 @@ export function getDepth(parent: IContainerModel): number { * @returns The absolute position of the container */ export function getAbsolutePosition(container: IContainerModel): [number, number] { - let x = Number(container.properties.x); - let y = Number(container.properties.y); + let x = container.properties.x; + let y = container.properties.y; let current = container.parent; while (current != null) { - x += Number(current.properties.x); - y += Number(current.properties.y); + x += current.properties.x; + y += current.properties.y; current = current.parent; } return [x, y]; From dcfb93aa12717e4d369d3a550390eb44d44c82a0 Mon Sep 17 00:00:00 2001 From: Siklos Date: Mon, 15 Aug 2022 23:04:45 +0200 Subject: [PATCH 06/14] Fix bugs created by using Number() --- src/Components/App/MenuActions.ts | 4 ++-- src/Components/Editor/ContainerOperations.ts | 5 +++-- src/Components/Properties/Properties.test.tsx | 5 ++++- src/utils/default.ts | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Components/App/MenuActions.ts b/src/Components/App/MenuActions.ts index c144acc..8dec2a1 100644 --- a/src/Components/App/MenuActions.ts +++ b/src/Components/App/MenuActions.ts @@ -20,8 +20,8 @@ export function NewEditor( parentId: 'null', x: 0, y: 0, - width: configuration.MainContainer.Width, - height: configuration.MainContainer.Height, + width: Number(configuration.MainContainer.Width), + height: Number(configuration.MainContainer.Height), isRigidBody: false, isAnchor: false, fillOpacity: 0, diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index 43f7d31..0ab670d 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -209,6 +209,7 @@ export function AddContainer( x = ApplyAddMethod(index, containerConfig, parentClone, x); const defaultProperties: IProperties = { + ...containerConfig.Style, id: `${type}-${count}`, parentId: parentClone.properties.id, x, @@ -217,9 +218,9 @@ export function AddContainer( height, isRigidBody: false, isAnchor: false, - XPositionReference: containerConfig.XPositionReference, - ...containerConfig.Style + XPositionReference: containerConfig.XPositionReference }; + // Create the container const newContainer = new ContainerModel( parentClone, diff --git a/src/Components/Properties/Properties.test.tsx b/src/Components/Properties/Properties.test.tsx index 9fbef37..8b8db31 100644 --- a/src/Components/Properties/Properties.test.tsx +++ b/src/Components/Properties/Properties.test.tsx @@ -1,6 +1,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import * as React from 'react'; import { expect, describe, it, vi } from 'vitest'; +import IProperties from '../../Interfaces/IProperties'; import { Properties } from './Properties'; describe.concurrent('Properties', () => { @@ -18,11 +19,13 @@ describe.concurrent('Properties', () => { }); it('Some properties, change values with dynamic input', () => { - const prop = { + const prop: IProperties = { id: 'stuff', parentId: 'parentId', x: 1, y: 1, + width: 1, + height: 1, isRigidBody: false, isAnchor: false }; diff --git a/src/utils/default.ts b/src/utils/default.ts index 44f737f..b16e528 100644 --- a/src/utils/default.ts +++ b/src/utils/default.ts @@ -30,8 +30,8 @@ export const DEFAULT_MAINCONTAINER_PROPS: IProperties = { parentId: 'null', x: 0, y: 0, - width: DEFAULT_CONFIG.MainContainer.Width, - height: DEFAULT_CONFIG.MainContainer.Height, + width: Number(DEFAULT_CONFIG.MainContainer.Width), + height: Number(DEFAULT_CONFIG.MainContainer.Height), isRigidBody: false, isAnchor: false, fillOpacity: 0, From 3d7baafc1741bf205052bacc6691c0f6b3ac56cd Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 16 Aug 2022 09:57:59 +0200 Subject: [PATCH 07/14] Clean elementsSidebar and fix eslint --- .eslintrc.cjs | 3 ++- src/Components/ElementsSidebar/ElementsSidebar.tsx | 2 -- .../ElementsSidebar/MouseEventHandlers.ts | 14 +++----------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 13ac012..9c5ea7c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -25,7 +25,8 @@ module.exports = { rules: { 'space-before-function-paren': ['error', 'never'], '@typescript-eslint/space-before-function-paren': ['error', 'never'], - indent: ['warn', 2, { SwitchCase: 1 }], + indent: 'off', + '@typescript-eslint/indent': ['warn', 2, {SwitchCase: 1}], semi: 'off', '@typescript-eslint/semi': ['warn', 'always'], 'no-unused-vars': 'off', diff --git a/src/Components/ElementsSidebar/ElementsSidebar.tsx b/src/Components/ElementsSidebar/ElementsSidebar.tsx index 30f1617..ab3efdd 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.tsx @@ -110,8 +110,6 @@ export const ElementsSidebar: React.FC = (props: IElement ); }; - const ROW_HEIGHT = 35; - const NUMBERS_OF_ROWS = 10; return (
diff --git a/src/Components/ElementsSidebar/MouseEventHandlers.ts b/src/Components/ElementsSidebar/MouseEventHandlers.ts index 1a814d2..5081033 100644 --- a/src/Components/ElementsSidebar/MouseEventHandlers.ts +++ b/src/Components/ElementsSidebar/MouseEventHandlers.ts @@ -36,9 +36,8 @@ export function handleLeftClick( } export function removeBorderClasses(target: HTMLButtonElement): void { - target.classList.remove('border-t-8'); - target.classList.remove('border-8'); - target.classList.remove('border-b-8'); + const bordersClasses = ['border-t-8', 'border-8', 'border-b-8']; + target.classList.remove(...bordersClasses); } export function handleDragLeave(event: React.DragEvent): void { @@ -54,26 +53,19 @@ export function handleDragOver( const target: HTMLButtonElement = event.target as HTMLButtonElement; const rect = target.getBoundingClientRect(); const y = event.clientY - rect.top; // y position within the element. + removeBorderClasses(target); if (target.id === mainContainer.properties.id) { target.classList.add('border-8'); - target.classList.remove('border-t-8'); - target.classList.remove('border-b-8'); return; } if (y < 12) { target.classList.add('border-t-8'); - target.classList.remove('border-b-8'); - target.classList.remove('border-8'); } else if (y < 24) { target.classList.add('border-8'); - target.classList.remove('border-t-8'); - target.classList.remove('border-b-8'); } else { target.classList.add('border-b-8'); - target.classList.remove('border-8'); - target.classList.remove('border-t-8'); } } From 5f8e011bc6562985624057d5343ca1bfaf6a6a51 Mon Sep 17 00:00:00 2001 From: Siklos Date: Tue, 16 Aug 2022 08:57:54 -0400 Subject: [PATCH 08/14] Unrefactor Properties form to allow more freedom on the input types and form (#32) - The css style is now in IProperties.Style again. - Forms are divided in DynamicForm and StaticForm - Faster because less logic - Add RadioGroupButton - Add InputGroup - Fix Children Dimensions not using x for their origin Co-authored-by: Eric NGUYEN Reviewed-on: https://git.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react/pulls/32 --- src/Components/App/MenuActions.ts | 8 +- src/Components/Editor/ContainerOperations.ts | 7 +- src/Components/Editor/Editor.tsx | 7 +- src/Components/Editor/PropertiesOperations.ts | 45 ++++-- .../ElementsSidebar/ElementsSidebar.test.tsx | 12 +- .../ElementsSidebar/ElementsSidebar.tsx | 5 +- src/Components/InputGroup/InputGroup.tsx | 48 ++++++ src/Components/Properties/DynamicForm.tsx | 146 ++++++++++++++++++ src/Components/Properties/Form.tsx | 24 +++ src/Components/Properties/Properties.test.tsx | 12 +- src/Components/Properties/Properties.tsx | 102 ++---------- src/Components/Properties/StaticForm.tsx | 139 +++++++++++++++++ .../RadioGroupButtons/RadioGroupButtons.tsx | 66 ++++++++ src/Components/SVG/Elements/Container.tsx | 14 +- src/Components/UI/UI.tsx | 5 +- src/Interfaces/IInputGroup.ts | 6 + src/Interfaces/IProperties.ts | 5 +- src/index.scss | 4 + src/utils/default.ts | 8 +- 19 files changed, 529 insertions(+), 134 deletions(-) create mode 100644 src/Components/InputGroup/InputGroup.tsx create mode 100644 src/Components/Properties/DynamicForm.tsx create mode 100644 src/Components/Properties/Form.tsx create mode 100644 src/Components/Properties/StaticForm.tsx create mode 100644 src/Components/RadioGroupButtons/RadioGroupButtons.tsx create mode 100644 src/Interfaces/IInputGroup.ts diff --git a/src/Components/App/MenuActions.ts b/src/Components/App/MenuActions.ts index 8dec2a1..1e37328 100644 --- a/src/Components/App/MenuActions.ts +++ b/src/Components/App/MenuActions.ts @@ -4,6 +4,7 @@ import { ContainerModel } from '../../Interfaces/IContainerModel'; import { fetchConfiguration } from '../API/api'; import { IEditorState } from '../../Interfaces/IEditorState'; import { LoadState } from './Load'; +import { XPositionReference } from '../../Enums/XPositionReference'; export function NewEditor( setEditorState: Dispatch>, @@ -24,8 +25,11 @@ export function NewEditor( height: Number(configuration.MainContainer.Height), isRigidBody: false, isAnchor: false, - fillOpacity: 0, - stroke: 'black' + XPositionReference: XPositionReference.Left, + style: { + fillOpacity: 0, + stroke: 'black' + } } ); diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index 0ab670d..95a0889 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -1,4 +1,4 @@ -import React, { Dispatch, SetStateAction } from 'react'; +import { Dispatch, SetStateAction } from 'react'; import { IHistoryState } from '../../Interfaces/IHistoryState'; import { IConfiguration } from '../../Interfaces/IConfiguration'; import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel'; @@ -8,6 +8,7 @@ import IProperties from '../../Interfaces/IProperties'; import { AddMethod } from '../../Enums/AddMethod'; import { IAvailableContainer } from '../../Interfaces/IAvailableContainer'; import { transformPosition } from '../SVG/Elements/Container'; +import { XPositionReference } from '../../Enums/XPositionReference'; /** * Select a container @@ -209,7 +210,6 @@ export function AddContainer( x = ApplyAddMethod(index, containerConfig, parentClone, x); const defaultProperties: IProperties = { - ...containerConfig.Style, id: `${type}-${count}`, parentId: parentClone.properties.id, x, @@ -218,7 +218,8 @@ export function AddContainer( height, isRigidBody: false, isAnchor: false, - XPositionReference: containerConfig.XPositionReference + XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left, + style: containerConfig.Style }; // Create the container diff --git a/src/Components/Editor/Editor.tsx b/src/Components/Editor/Editor.tsx index fcd62f9..75bf90c 100644 --- a/src/Components/Editor/Editor.tsx +++ b/src/Components/Editor/Editor.tsx @@ -91,16 +91,15 @@ const Editor: React.FunctionComponent = (props) => { setHistory, setHistoryCurrentStep )} - OnPropertyChange={(key, value) => OnPropertyChange( - key, value, + OnPropertyChange={(key, value, isStyle) => OnPropertyChange( + key, value, isStyle, history, historyCurrentStep, setHistory, setHistoryCurrentStep )} - OnPropertiesSubmit={(event, properties) => OnPropertiesSubmit( + OnPropertiesSubmit={(event) => OnPropertiesSubmit( event, - properties, history, historyCurrentStep, setHistory, diff --git a/src/Components/Editor/PropertiesOperations.ts b/src/Components/Editor/PropertiesOperations.ts index 4bd4c2d..9f35e63 100644 --- a/src/Components/Editor/PropertiesOperations.ts +++ b/src/Components/Editor/PropertiesOperations.ts @@ -1,7 +1,6 @@ import { Dispatch, SetStateAction } from 'react'; import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerModel'; import { IHistoryState } from '../../Interfaces/IHistoryState'; -import IProperties from '../../Interfaces/IProperties'; import { findContainerById } from '../../utils/itertools'; import { getCurrentHistory } from './Editor'; import { RecalculatePhysics } from './Behaviors/RigidBodyBehaviors'; @@ -17,6 +16,7 @@ import { ImposePosition } from './Behaviors/AnchorBehaviors'; export function OnPropertyChange( key: string, value: string | number | boolean, + isStyle: boolean = false, fullHistory: IHistoryState[], historyCurrentStep: number, setHistory: Dispatch>, @@ -37,10 +37,14 @@ export function OnPropertyChange( throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); } - if (INPUT_TYPES[key] === 'number') { - (container.properties as any)[key] = Number(value); + if (isStyle) { + (container.properties.style as any)[key] = value; } else { - (container.properties as any)[key] = value; + if (INPUT_TYPES[key] === 'number') { + (container.properties as any)[key] = Number(value); + } else { + (container.properties as any)[key] = value; + } } if (container.properties.isAnchor) { @@ -70,7 +74,6 @@ export function OnPropertyChange( */ export function OnPropertiesSubmit( event: React.SyntheticEvent, - properties: IProperties, fullHistory: IHistoryState[], historyCurrentStep: number, setHistory: Dispatch>, @@ -92,18 +95,42 @@ export function OnPropertiesSubmit( throw new Error('[OnPropertyChange] Container model was not found among children of the main container!'); } - for (const property in properties) { - const input = (event.target as HTMLFormElement).querySelector(`#${property}`); + // Assign container properties + for (const property in container.properties) { + const input: HTMLInputElement | HTMLDivElement | null = (event.target as HTMLFormElement).querySelector(`#${property}`); + + if (input === null) { + continue; + } + if (input instanceof HTMLInputElement) { (container.properties as any)[property] = input.value; if (INPUT_TYPES[property] === 'number') { (container.properties as any)[property] = Number(input.value); - } else { - (container.properties as any)[property] = input.value; + } + } else if (input instanceof HTMLDivElement) { + const radiobutton: HTMLInputElement | null = input.querySelector(`input[name="${property}"]:checked`); + + if (radiobutton === null) { + continue; + } + + (container.properties as any)[property] = radiobutton.value; + if (INPUT_TYPES[property] === 'number') { + (container.properties as any)[property] = Number(radiobutton.value); } } } + // Assign cssproperties + for (const styleProperty in container.properties.style) { + const input: HTMLInputElement | null = (event.target as HTMLFormElement).querySelector(`#${styleProperty}`); + if (input === null) { + continue; + } + (container.properties.style as any)[styleProperty] = input.value; + } + if (container.properties.isRigidBody) { RecalculatePhysics(container); } diff --git a/src/Components/ElementsSidebar/ElementsSidebar.test.tsx b/src/Components/ElementsSidebar/ElementsSidebar.test.tsx index 8d34480..f207a46 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.test.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.test.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { fireEvent, render, screen } from '../../utils/test-utils'; import { ElementsSidebar } from './ElementsSidebar'; import { IContainerModel } from '../../Interfaces/IContainerModel'; +import { XPositionReference } from '../../Enums/XPositionReference'; describe.concurrent('Elements sidebar', () => { it('With a MainContainer', () => { @@ -17,6 +18,7 @@ describe.concurrent('Elements sidebar', () => { y: 0, width: 2000, height: 100, + XPositionReference: XPositionReference.Left, isRigidBody: false, isAnchor: false }, @@ -49,7 +51,8 @@ describe.concurrent('Elements sidebar', () => { width: 2000, height: 100, isRigidBody: false, - isAnchor: false + isAnchor: false, + XPositionReference: XPositionReference.Left }, userData: {} }; @@ -105,6 +108,7 @@ describe.concurrent('Elements sidebar', () => { y: 0, width: 2000, height: 100, + XPositionReference: XPositionReference.Left, isRigidBody: false, isAnchor: false }, @@ -123,7 +127,8 @@ describe.concurrent('Elements sidebar', () => { width: 0, height: 0, isRigidBody: false, - isAnchor: false + isAnchor: false, + XPositionReference: XPositionReference.Left }, userData: {} } @@ -140,6 +145,7 @@ describe.concurrent('Elements sidebar', () => { y: 0, width: 0, height: 0, + XPositionReference: XPositionReference.Left, isRigidBody: false, isAnchor: false }, @@ -178,6 +184,7 @@ describe.concurrent('Elements sidebar', () => { y: 0, width: 2000, height: 100, + XPositionReference: XPositionReference.Left, isRigidBody: false, isAnchor: false }, @@ -194,6 +201,7 @@ describe.concurrent('Elements sidebar', () => { y: 0, width: 0, height: 0, + XPositionReference: XPositionReference.Left, isRigidBody: false, isAnchor: false }, diff --git a/src/Components/ElementsSidebar/ElementsSidebar.tsx b/src/Components/ElementsSidebar/ElementsSidebar.tsx index ab3efdd..98eb071 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { FixedSizeList as List } from 'react-window'; import { Properties } from '../Properties/Properties'; -import ContainerProperties from '../../Interfaces/IProperties'; import { IContainerModel } from '../../Interfaces/IContainerModel'; import { getDepth, MakeIterator } from '../../utils/itertools'; import { Menu } from '../Menu/Menu'; @@ -14,8 +13,8 @@ interface IElementsSidebarProps { isOpen: boolean isHistoryOpen: boolean SelectedContainer: IContainerModel | null - OnPropertyChange: (key: string, value: string | number | boolean) => void - OnPropertiesSubmit: (event: React.FormEvent, properties: ContainerProperties) => void + OnPropertyChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void + OnPropertiesSubmit: (event: React.FormEvent) => void SelectContainer: (container: IContainerModel) => void DeleteContainer: (containerid: string) => void AddContainer: (index: number, type: string, parent: string) => void diff --git a/src/Components/InputGroup/InputGroup.tsx b/src/Components/InputGroup/InputGroup.tsx new file mode 100644 index 0000000..bc794d3 --- /dev/null +++ b/src/Components/InputGroup/InputGroup.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; + +interface IInputGroupProps { + labelKey?: string + labelText: string + inputKey: string + labelClassName: string + inputClassName: string + type: string + value?: string + checked?: boolean + defaultValue?: string + defaultChecked?: boolean + isDisabled?: boolean + onChange?: (event: React.ChangeEvent) => void +} + +const className = ` + w-full + text-xs font-medium transition-all text-gray-800 mt-1 px-3 py-2 + bg-white border-2 border-white rounded-lg placeholder-gray-800 + focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 + disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none`; + +export const InputGroup: React.FunctionComponent = (props) => { + return <> + + + ; +}; diff --git a/src/Components/Properties/DynamicForm.tsx b/src/Components/Properties/DynamicForm.tsx new file mode 100644 index 0000000..0be155d --- /dev/null +++ b/src/Components/Properties/DynamicForm.tsx @@ -0,0 +1,146 @@ +import { MenuAlt2Icon, MenuAlt3Icon, MenuIcon } from '@heroicons/react/outline'; +import * as React from 'react'; +import { XPositionReference } from '../../Enums/XPositionReference'; +import IProperties from '../../Interfaces/IProperties'; +import { InputGroup } from '../InputGroup/InputGroup'; +import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons'; + +interface IDynamicFormProps { + properties: IProperties + onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void +} + +const getCSSInputs = ( + properties: IProperties, + onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void +): JSX.Element[] => { + const groupInput: JSX.Element[] = []; + for (const key in properties.style) { + groupInput.push( onChange(key, event.target.value, true)} + />); + } + return groupInput; +}; + +const DynamicForm: React.FunctionComponent = (props) => { + return ( +
+ + + props.onChange('x', event.target.value)} + /> + props.onChange('y', event.target.value)} + /> + props.onChange('width', event.target.value)} + /> + props.onChange('height', event.target.value)} + /> + props.onChange('isRigidBody', event.target.checked)} + /> + props.onChange('isAnchor', event.target.checked)} + /> + + +
+ ), + value: XPositionReference.Left.toString() + }, + { + text: ( +
+ +
+ ), + value: XPositionReference.Center.toString() + }, + { + text: ( +
+ +
+ ), + value: XPositionReference.Right.toString() + } + ]} + onChange={(event) => props.onChange('XPositionReference', event.target.value)} + /> + { getCSSInputs(props.properties, props.onChange) } +
+ ); +}; + +export default DynamicForm; diff --git a/src/Components/Properties/Form.tsx b/src/Components/Properties/Form.tsx new file mode 100644 index 0000000..bc0f508 --- /dev/null +++ b/src/Components/Properties/Form.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import IProperties from '../../Interfaces/IProperties'; +import DynamicForm from './DynamicForm'; +import StaticForm from './StaticForm'; + +interface IFormProps { + properties: IProperties + isDynamicInput: boolean + onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void + onSubmit: (event: React.FormEvent) => void +} + +export const Form: React.FunctionComponent = (props) => { + if (props.isDynamicInput) { + return ; + } + return ; +}; diff --git a/src/Components/Properties/Properties.test.tsx b/src/Components/Properties/Properties.test.tsx index 8b8db31..61441f8 100644 --- a/src/Components/Properties/Properties.test.tsx +++ b/src/Components/Properties/Properties.test.tsx @@ -1,6 +1,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import * as React from 'react'; import { expect, describe, it, vi } from 'vitest'; +import { XPositionReference } from '../../Enums/XPositionReference'; import IProperties from '../../Interfaces/IProperties'; import { Properties } from './Properties'; @@ -26,6 +27,7 @@ describe.concurrent('Properties', () => { y: 1, width: 1, height: 1, + XPositionReference: XPositionReference.Left, isRigidBody: false, isAnchor: false }; @@ -62,10 +64,10 @@ describe.concurrent('Properties', () => { fireEvent.change(propertyParentId as Element, { target: { value: 'parentedId' } }); fireEvent.change(propertyX as Element, { target: { value: '2' } }); fireEvent.change(propertyY as Element, { target: { value: '2' } }); - expect(handleChange).toBeCalledTimes(4); + expect(handleChange).toBeCalledTimes(2); - expect(prop.id).toBe('stuffed'); - expect(prop.parentId).toBe('parentedId'); + expect(prop.id).toBe('stuff'); + expect(prop.parentId).toBe('parentId'); expect(prop.x).toBe('2'); expect(prop.y).toBe('2'); rerender( { propertyX = container.querySelector('#x'); propertyY = container.querySelector('#y'); expect(propertyId).toBeDefined(); - expect((propertyId as HTMLInputElement).value).toBe('stuffed'); + expect((propertyId as HTMLInputElement).value).toBe('stuff'); expect(propertyParentId).toBeDefined(); - expect((propertyParentId as HTMLInputElement).value).toBe('parentedId'); + expect((propertyParentId as HTMLInputElement).value).toBe('parentId'); expect(propertyX).toBeDefined(); expect((propertyX as HTMLInputElement).value).toBe('2'); expect(propertyY).toBeDefined(); diff --git a/src/Components/Properties/Properties.tsx b/src/Components/Properties/Properties.tsx index 516f23d..cb38a60 100644 --- a/src/Components/Properties/Properties.tsx +++ b/src/Components/Properties/Properties.tsx @@ -1,12 +1,12 @@ import React, { useState } from 'react'; -import ContainerProperties from '../../Interfaces/IProperties'; +import IProperties from '../../Interfaces/IProperties'; import { ToggleButton } from '../ToggleButton/ToggleButton'; -import { INPUT_TYPES } from './PropertiesInputTypes'; +import { Form } from './Form'; interface IPropertiesProps { - properties?: ContainerProperties - onChange: (key: string, value: string | number | boolean) => void - onSubmit: (event: React.FormEvent, properties: ContainerProperties) => void + properties?: IProperties + onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void + onSubmit: (event: React.FormEvent) => void } export const Properties: React.FC = (props: IPropertiesProps) => { @@ -16,26 +16,6 @@ export const Properties: React.FC = (props: IPropertiesProps) return
; } - const groupInput: React.ReactNode[] = []; - Object - .entries(props.properties) - .forEach((pair) => handleProperties(pair, groupInput, isDynamicInput, props.onChange)); - - const form = isDynamicInput - ?
- { groupInput } -
- :
props.onSubmit(event, props.properties as ContainerProperties)} - > - -
- { groupInput } -
-
- ; - return (
= (props: IPropertiesProps) checked={isDynamicInput} onChange={() => setIsDynamicInput(!isDynamicInput)} /> - { form } +
); }; - -const handleProperties = ( - [key, value]: [string, string | number], - groupInput: React.ReactNode[], - isDynamicInput: boolean, - onChange: (key: string, value: string | number | boolean) => void -): void => { - const id = `property-${key}`; - let type = 'text'; - let checked; - - /// hardcoded stuff for ergonomy /// - if (typeof value === 'boolean') { - checked = value; - } - - if (key in INPUT_TYPES) { - type = INPUT_TYPES[key]; - } - - const className = ` - w-full - text-xs font-medium transition-all text-gray-800 mt-1 px-3 py-2 - bg-white border-2 border-white rounded-lg placeholder-gray-800 - focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 - disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none`; - const isDisabled = ['id', 'parentId'].includes(key); - const input = isDynamicInput - ? { - if (type === 'checkbox') { - onChange(key, event.target.checked); - return; - } - onChange(key, event.target.value); - }} - disabled={isDisabled} - /> - : ; - - groupInput.push( - - ); - groupInput.push(input); -}; diff --git a/src/Components/Properties/StaticForm.tsx b/src/Components/Properties/StaticForm.tsx new file mode 100644 index 0000000..f16d196 --- /dev/null +++ b/src/Components/Properties/StaticForm.tsx @@ -0,0 +1,139 @@ +import { MenuAlt2Icon, MenuIcon, MenuAlt3Icon } from '@heroicons/react/outline'; +import * as React from 'react'; +import { XPositionReference } from '../../Enums/XPositionReference'; +import IProperties from '../../Interfaces/IProperties'; +import { InputGroup } from '../InputGroup/InputGroup'; +import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons'; + +interface IStaticFormProps { + properties: IProperties + onSubmit: (event: React.FormEvent) => void +} + +const getCSSInputs = (properties: IProperties): JSX.Element[] => { + const groupInput: JSX.Element[] = []; + for (const key in properties.style) { + groupInput.push(); + } + return groupInput; +}; + +const StaticForm: React.FunctionComponent = (props) => { + return ( props.onSubmit(event)} + > + +
+ + + + + + + + + + +
+ ), + value: XPositionReference.Left.toString() + }, + { + text: ( +
+ +
+ ), + value: XPositionReference.Center.toString() + }, + { + text: ( +
+ +
+ ), + value: XPositionReference.Right.toString() + } + ]} + /> + { getCSSInputs(props.properties) } +
+ ); +}; + +export default StaticForm; diff --git a/src/Components/RadioGroupButtons/RadioGroupButtons.tsx b/src/Components/RadioGroupButtons/RadioGroupButtons.tsx new file mode 100644 index 0000000..8fb620b --- /dev/null +++ b/src/Components/RadioGroupButtons/RadioGroupButtons.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import { IInputGroup } from '../../Interfaces/IInputGroup'; + +interface IRadioGroupButtonsProps { + name: string + value?: string + defaultValue?: string + inputClassName: string + labelText: string + inputGroups: IInputGroup[] + onChange?: (event: React.ChangeEvent) => void +} + +export const RadioGroupButtons: React.FunctionComponent = (props) => { + let inputGroups; + if (props.value !== undefined) { + // dynamic + inputGroups = props.inputGroups.map((inputGroup) => ( +
+ + +
+ + )); + } else { + // static + inputGroups = props.inputGroups.map((inputGroup) => ( +
+ + +
+ )); + } + + return ( + <> + +
+ { inputGroups } +
+ + ); +}; diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index 56d4797..80ae56c 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -36,12 +36,8 @@ export const Container: React.FC = (props: IContainerProps) => // Rect style const style = Object.assign( JSON.parse(JSON.stringify(defaultStyle)), - props.model.properties + props.model.properties.style ); - style.x = 0; - style.y = 0; - delete style.height; - delete style.width; // Dimension props const depth = getDepth(props.model); @@ -54,7 +50,7 @@ export const Container: React.FC = (props: IContainerProps) => const text = (props.model.properties.width ?? 0).toString(); let dimensionChildren: JSX.Element | null = null; - if (props.model.children.length > 0) { + if (props.model.children.length > 1) { const { childrenId, xChildrenStart, @@ -112,14 +108,16 @@ function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: numb const lastChild = props.model.children[props.model.children.length - 1]; let xChildrenStart = lastChild.properties.x; - let xChildrenEnd = lastChild.properties.x + lastChild.properties.width; + let xChildrenEnd = lastChild.properties.x; + + // Find the min and max for (let i = props.model.children.length - 2; i >= 0; i--) { const child = props.model.children[i]; const left = child.properties.x; if (left < xChildrenStart) { xChildrenStart = left; } - const right = child.properties.x + child.properties.width; + const right = child.properties.x; if (right > xChildrenEnd) { xChildrenEnd = right; } diff --git a/src/Components/UI/UI.tsx b/src/Components/UI/UI.tsx index 4fa36e6..c57daee 100644 --- a/src/Components/UI/UI.tsx +++ b/src/Components/UI/UI.tsx @@ -8,7 +8,6 @@ import { IHistoryState } from '../../Interfaces/IHistoryState'; import { PhotographIcon, UploadIcon } from '@heroicons/react/outline'; import { FloatingButton } from '../FloatingButton/FloatingButton'; import { Bar } from '../Bar/Bar'; -import IProperties from '../../Interfaces/IProperties'; interface IUIProps { current: IHistoryState @@ -17,8 +16,8 @@ interface IUIProps { AvailableContainers: IAvailableContainer[] SelectContainer: (container: ContainerModel) => void DeleteContainer: (containerId: string) => void - OnPropertyChange: (key: string, value: string | number | boolean) => void - OnPropertiesSubmit: (event: React.FormEvent, properties: IProperties) => void + OnPropertyChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void + OnPropertiesSubmit: (event: React.FormEvent) => void AddContainerToSelectedContainer: (type: string) => void AddContainer: (index: number, type: string, parentId: string) => void SaveEditorAsJSON: () => void diff --git a/src/Interfaces/IInputGroup.ts b/src/Interfaces/IInputGroup.ts new file mode 100644 index 0000000..dfc9942 --- /dev/null +++ b/src/Interfaces/IInputGroup.ts @@ -0,0 +1,6 @@ +import React from 'react'; + +export interface IInputGroup { + text: React.ReactNode + value: string +} diff --git a/src/Interfaces/IProperties.ts b/src/Interfaces/IProperties.ts index ad23442..2513d74 100644 --- a/src/Interfaces/IProperties.ts +++ b/src/Interfaces/IProperties.ts @@ -10,7 +10,7 @@ import { XPositionReference } from '../Enums/XPositionReference'; * @property isRigidBody if true apply rigid body behaviors * @property isAnchor if true apply anchor behaviors */ -export default interface IProperties extends Omit { +export default interface IProperties { id: string parentId: string | null x: number @@ -19,5 +19,6 @@ export default interface IProperties extends Omit Date: Tue, 16 Aug 2022 15:17:34 +0200 Subject: [PATCH 09/14] Update documentation --- docs/ComponentStructure.drawio | 4 ++-- docs/Dependencies.md | 1 + docs/Project_Structure.md | 24 +++++++++++++----------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/ComponentStructure.drawio b/docs/ComponentStructure.drawio index 2fdcefc..e4ee4af 100644 --- a/docs/ComponentStructure.drawio +++ b/docs/ComponentStructure.drawio @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59a34bc3428280ecd661efa7b4756bf9f4930f36c077984a40e7fdc6983aeeff -size 2063 +oid sha256:b8156818d0348ba0edefc52d6ed8fc84b65305c7eca275a37d3ff1733391bb1d +size 19858 diff --git a/docs/Dependencies.md b/docs/Dependencies.md index 626458e..34212ee 100644 --- a/docs/Dependencies.md +++ b/docs/Dependencies.md @@ -13,6 +13,7 @@ It depends on Vite in order to build the project. Others dependencies: - [react-dom](https://reactjs.org/docs/react-dom.html): library used to inject the app to `#root` html element. +- [react-window](https://www.npmjs.com/package/react-windows): component that offers component dynamic loading over scroll (very useful++) - [react-svg-pan-zoom](https://www.npmjs.com/package/react-svg-pan-zoom): component that offers pan + zoom to a svg element diff --git a/docs/Project_Structure.md b/docs/Project_Structure.md index 890daf8..3daa579 100644 --- a/docs/Project_Structure.md +++ b/docs/Project_Structure.md @@ -4,22 +4,24 @@ The project is structured this way ``` . -├── docs Documentation folder -├── public Public folder in which the index.html +├── docs/ Documentation folder +├── public/ Public folder in which the index.html │ import its resources -├── src Source folder for the react app -│ ├── assets Assets folder in which the react app +├── src/ Source folder for the react app +│ ├── assets/ Assets folder in which the react app │ │ import its resources -│ ├── Components Components folder -│ ├── Enums Enums folder -│ ├── Interfaces Interface (+ types folder) -│ ├── test Setup folder for the tests -│ ├── tests Other tests + resources -│ ├── utils Utilities folder +│ ├── Components/ Components folder +│ ├── Enums/ Enums folder +│ ├── Events/ API Events folder +│ ├── Interfaces/ Interface (+ types folder) +│ ├── test/ Setup folder for the tests +│ ├── tests/ Other tests + resources +│ ├── utils/ Utilities folder +│ ├── workers/ Webworkers folder │ ├── index.scss Tailwind CSS extends │ ├── main.tsx Entrypoint for App injection │ └── vite-env.d.ts Types for .env files -├── test-server Tests servers to test the API +├── test-server/ Tests servers to test the API │ ├── http.js Test server for bun.sh │ └── node-http.js Test server for Node.js ├── azure-pipelines.yml Azure Pipelines YAML config file From 1622816035c98e434373c64268c8e21b2c4cfa02 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 16 Aug 2022 16:34:32 +0200 Subject: [PATCH 10/14] Final fix for XPositionReference : We are now lying to the user in the form. The transformation is applied to the value that is shown but the transformation is restored for the computing afterward --- src/Components/Editor/ContainerOperations.ts | 11 ++---- src/Components/Editor/PropertiesOperations.ts | 36 +++++++++++++++---- src/Components/Properties/DynamicForm.tsx | 13 +++---- src/Components/Properties/StaticForm.tsx | 3 +- src/Components/SVG/Elements/Container.tsx | 30 +++++++++------- src/Components/SVG/Elements/Selector.tsx | 11 ++---- 6 files changed, 59 insertions(+), 45 deletions(-) diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index 95a0889..28af9e7 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -7,7 +7,7 @@ import { getCurrentHistory } from './Editor'; import IProperties from '../../Interfaces/IProperties'; import { AddMethod } from '../../Enums/AddMethod'; import { IAvailableContainer } from '../../Interfaces/IAvailableContainer'; -import { transformPosition } from '../SVG/Elements/Container'; +import { transformX } from '../SVG/Elements/Container'; import { XPositionReference } from '../../Enums/XPositionReference'; /** @@ -267,14 +267,7 @@ function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, par const lastChild: IContainerModel | undefined = parent.children.at(index - 1); if (lastChild !== undefined) { - const [transformedX] = transformPosition( - lastChild.properties.x, - lastChild.properties.y, - lastChild.properties.width, - lastChild.properties.XPositionReference - ); - - x += transformedX + lastChild.properties.width; + x += (lastChild.properties.x + lastChild.properties.width); } } return x; diff --git a/src/Components/Editor/PropertiesOperations.ts b/src/Components/Editor/PropertiesOperations.ts index 9f35e63..59098a6 100644 --- a/src/Components/Editor/PropertiesOperations.ts +++ b/src/Components/Editor/PropertiesOperations.ts @@ -6,6 +6,7 @@ import { getCurrentHistory } from './Editor'; import { RecalculatePhysics } from './Behaviors/RigidBodyBehaviors'; import { INPUT_TYPES } from '../Properties/PropertiesInputTypes'; import { ImposePosition } from './Behaviors/AnchorBehaviors'; +import { restoreX } from '../SVG/Elements/Container'; /** * Handled the property change event in the properties form @@ -40,11 +41,7 @@ export function OnPropertyChange( if (isStyle) { (container.properties.style as any)[key] = value; } else { - if (INPUT_TYPES[key] === 'number') { - (container.properties as any)[key] = Number(value); - } else { - (container.properties as any)[key] = value; - } + (container.properties as any)[key] = value; } if (container.properties.isAnchor) { @@ -104,10 +101,33 @@ export function OnPropertiesSubmit( } if (input instanceof HTMLInputElement) { - (container.properties as any)[property] = input.value; if (INPUT_TYPES[property] === 'number') { + if (property === 'x') { + // Hardcoded fix for XPositionReference + + const inputWidth: HTMLInputElement | null = (event.target as HTMLFormElement).querySelector('#width'); + const inputRadio: HTMLDivElement | null = (event.target as HTMLFormElement).querySelector('#XPositionReference'); + + if (inputWidth === null || inputRadio === null) { + throw new Error('[OnPropertiesSubmit] Missing inputs for width or XPositionReference'); + } + + const radiobutton: HTMLInputElement | null = inputRadio.querySelector('input[name="XPositionReference"]:checked'); + + if (radiobutton === null) { + throw new Error('[OnPropertiesSubmit] Missing inputs for XPositionReference'); + } + + const x = restoreX(Number(input.value), Number(inputWidth.value), Number(radiobutton.value)); + (container.properties as any)[property] = x; + continue; + } + (container.properties as any)[property] = Number(input.value); + continue; } + + (container.properties as any)[property] = input.value; } else if (input instanceof HTMLDivElement) { const radiobutton: HTMLInputElement | null = input.querySelector(`input[name="${property}"]:checked`); @@ -115,10 +135,12 @@ export function OnPropertiesSubmit( continue; } - (container.properties as any)[property] = radiobutton.value; if (INPUT_TYPES[property] === 'number') { (container.properties as any)[property] = Number(radiobutton.value); + continue; } + + (container.properties as any)[property] = radiobutton.value; } } diff --git a/src/Components/Properties/DynamicForm.tsx b/src/Components/Properties/DynamicForm.tsx index 0be155d..b5665b6 100644 --- a/src/Components/Properties/DynamicForm.tsx +++ b/src/Components/Properties/DynamicForm.tsx @@ -4,6 +4,7 @@ import { XPositionReference } from '../../Enums/XPositionReference'; import IProperties from '../../Interfaces/IProperties'; import { InputGroup } from '../InputGroup/InputGroup'; import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons'; +import { restoreX, transformX } from '../SVG/Elements/Container'; interface IDynamicFormProps { properties: IProperties @@ -57,8 +58,8 @@ const DynamicForm: React.FunctionComponent = (props) => { labelClassName='' inputClassName='' type='number' - value={props.properties.x.toString()} - onChange={(event) => props.onChange('x', event.target.value)} + value={transformX(props.properties.x, props.properties.width, props.properties.XPositionReference).toString()} + onChange={(event) => props.onChange('x', restoreX(Number(event.target.value), props.properties.width, props.properties.XPositionReference))} /> = (props) => { inputClassName='' type='number' value={props.properties.y.toString()} - onChange={(event) => props.onChange('y', event.target.value)} + onChange={(event) => props.onChange('y', Number(event.target.value))} /> = (props) => { inputClassName='' type='number' value={props.properties.width.toString()} - onChange={(event) => props.onChange('width', event.target.value)} + onChange={(event) => props.onChange('width', Number(event.target.value))} /> = (props) => { inputClassName='' type='number' value={props.properties.height.toString()} - onChange={(event) => props.onChange('height', event.target.value)} + onChange={(event) => props.onChange('height', Number(event.target.value))} /> = (props) => { value: XPositionReference.Right.toString() } ]} - onChange={(event) => props.onChange('XPositionReference', event.target.value)} + onChange={(event) => props.onChange('XPositionReference', Number(event.target.value))} /> { getCSSInputs(props.properties, props.onChange) } diff --git a/src/Components/Properties/StaticForm.tsx b/src/Components/Properties/StaticForm.tsx index f16d196..9f6055b 100644 --- a/src/Components/Properties/StaticForm.tsx +++ b/src/Components/Properties/StaticForm.tsx @@ -4,6 +4,7 @@ import { XPositionReference } from '../../Enums/XPositionReference'; import IProperties from '../../Interfaces/IProperties'; import { InputGroup } from '../InputGroup/InputGroup'; import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons'; +import { transformX } from '../SVG/Elements/Container'; interface IStaticFormProps { properties: IProperties @@ -57,7 +58,7 @@ const StaticForm: React.FunctionComponent = (props) => { labelClassName='' inputClassName='' type='number' - defaultValue={props.properties.x.toString()} + defaultValue={transformX(props.properties.x, props.properties.width, props.properties.XPositionReference).toString()} /> = (props: IContainerProps) => const xText = props.model.properties.width / 2; const yText = props.model.properties.height / 2; - const [transformedX, transformedY] = transformPosition( - props.model.properties.x, - props.model.properties.y, - props.model.properties.width, - props.model.properties.XPositionReference - ); - const transform = `translate(${transformedX}, ${transformedY})`; + const transform = `translate(${props.model.properties.x}, ${props.model.properties.y})`; // g style const defaultStyle: React.CSSProperties = { @@ -107,17 +101,17 @@ function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: numb const childrenId = `dim-children-${props.model.properties.id}`; const lastChild = props.model.children[props.model.children.length - 1]; - let xChildrenStart = lastChild.properties.x; - let xChildrenEnd = lastChild.properties.x; + let xChildrenStart = transformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.XPositionReference); + let xChildrenEnd = transformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.XPositionReference); // Find the min and max for (let i = props.model.children.length - 2; i >= 0; i--) { const child = props.model.children[i]; - const left = child.properties.x; + const left = transformX(child.properties.x, child.properties.width, child.properties.XPositionReference); if (left < xChildrenStart) { xChildrenStart = left; } - const right = child.properties.x; + const right = transformX(child.properties.x, child.properties.width, child.properties.XPositionReference); if (right > xChildrenEnd) { xChildrenEnd = right; } @@ -128,12 +122,22 @@ function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: numb return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren }; } -export function transformPosition(x: number, y: number, width: number, xPositionReference = XPositionReference.Left): [number, number] { +export function transformX(x: number, width: number, xPositionReference = XPositionReference.Left): number { + let transformedX = x; + if (xPositionReference === XPositionReference.Center) { + transformedX += width / 2; + } else if (xPositionReference === XPositionReference.Right) { + transformedX += width; + } + return transformedX; +} + +export function restoreX(x: number, width: number, xPositionReference = XPositionReference.Left): number { let transformedX = x; if (xPositionReference === XPositionReference.Center) { transformedX -= width / 2; } else if (xPositionReference === XPositionReference.Right) { transformedX -= width; } - return [transformedX, y]; + return transformedX; } diff --git a/src/Components/SVG/Elements/Selector.tsx b/src/Components/SVG/Elements/Selector.tsx index 101f19a..e70ca79 100644 --- a/src/Components/SVG/Elements/Selector.tsx +++ b/src/Components/SVG/Elements/Selector.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { getAbsolutePosition } from '../../../utils/itertools'; -import { transformPosition } from './Container'; interface ISelectorProps { selected: IContainerModel | null @@ -16,12 +15,6 @@ export const Selector: React.FC = (props) => { } const [x, y] = getAbsolutePosition(props.selected); - const [transformedX, transformedY] = transformPosition( - x, - y, - props.selected.properties.width, - props.selected.properties.XPositionReference - ); const [width, height] = [ props.selected.properties.width, props.selected.properties.height @@ -38,8 +31,8 @@ export const Selector: React.FC = (props) => { return ( Date: Tue, 16 Aug 2022 16:34:44 +0200 Subject: [PATCH 11/14] History: Fix regression with delete --- src/Components/Editor/ContainerOperations.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index 28af9e7..3d9e91e 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -7,7 +7,6 @@ import { getCurrentHistory } from './Editor'; import IProperties from '../../Interfaces/IProperties'; import { AddMethod } from '../../Enums/AddMethod'; import { IAvailableContainer } from '../../Interfaces/IAvailableContainer'; -import { transformX } from '../SVG/Elements/Container'; import { XPositionReference } from '../../Enums/XPositionReference'; /** @@ -58,7 +57,7 @@ export function DeleteContainer( setHistoryCurrentStep: Dispatch> ): void { const history = getCurrentHistory(fullHistory, historyCurrentStep); - const current = history[historyCurrentStep]; + const current = history[history.length - 1]; const mainContainerClone: IContainerModel = structuredClone(current.MainContainer); const container = findContainerById(mainContainerClone, containerId); From e3190dfe0a129a21b3757ba8edc4e5fc93581ea7 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 16 Aug 2022 16:48:43 +0200 Subject: [PATCH 12/14] Refactor PropertiesOperations for more readability --- src/Components/Editor/PropertiesOperations.ts | 120 +++++++++++------- 1 file changed, 74 insertions(+), 46 deletions(-) diff --git a/src/Components/Editor/PropertiesOperations.ts b/src/Components/Editor/PropertiesOperations.ts index 59098a6..0e3802c 100644 --- a/src/Components/Editor/PropertiesOperations.ts +++ b/src/Components/Editor/PropertiesOperations.ts @@ -3,7 +3,7 @@ import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerMode import { IHistoryState } from '../../Interfaces/IHistoryState'; import { findContainerById } from '../../utils/itertools'; import { getCurrentHistory } from './Editor'; -import { RecalculatePhysics } from './Behaviors/RigidBodyBehaviors'; +import { constraintBodyInsideUnallocatedWidth, RecalculatePhysics } from './Behaviors/RigidBodyBehaviors'; import { INPUT_TYPES } from '../Properties/PropertiesInputTypes'; import { ImposePosition } from './Behaviors/AnchorBehaviors'; import { restoreX } from '../SVG/Elements/Container'; @@ -93,64 +93,28 @@ export function OnPropertiesSubmit( } // Assign container properties + const form: HTMLFormElement = event.target as HTMLFormElement; for (const property in container.properties) { - const input: HTMLInputElement | HTMLDivElement | null = (event.target as HTMLFormElement).querySelector(`#${property}`); + const input: HTMLInputElement | HTMLDivElement | null = form.querySelector(`#${property}`); if (input === null) { continue; } if (input instanceof HTMLInputElement) { - if (INPUT_TYPES[property] === 'number') { - if (property === 'x') { - // Hardcoded fix for XPositionReference + submitHTMLInput(input, container, property, form); + continue; + } - const inputWidth: HTMLInputElement | null = (event.target as HTMLFormElement).querySelector('#width'); - const inputRadio: HTMLDivElement | null = (event.target as HTMLFormElement).querySelector('#XPositionReference'); - - if (inputWidth === null || inputRadio === null) { - throw new Error('[OnPropertiesSubmit] Missing inputs for width or XPositionReference'); - } - - const radiobutton: HTMLInputElement | null = inputRadio.querySelector('input[name="XPositionReference"]:checked'); - - if (radiobutton === null) { - throw new Error('[OnPropertiesSubmit] Missing inputs for XPositionReference'); - } - - const x = restoreX(Number(input.value), Number(inputWidth.value), Number(radiobutton.value)); - (container.properties as any)[property] = x; - continue; - } - - (container.properties as any)[property] = Number(input.value); - continue; - } - - (container.properties as any)[property] = input.value; - } else if (input instanceof HTMLDivElement) { - const radiobutton: HTMLInputElement | null = input.querySelector(`input[name="${property}"]:checked`); - - if (radiobutton === null) { - continue; - } - - if (INPUT_TYPES[property] === 'number') { - (container.properties as any)[property] = Number(radiobutton.value); - continue; - } - - (container.properties as any)[property] = radiobutton.value; + if (input instanceof HTMLDivElement) { + submitRadioButtons(input, container, property); + continue; } } // Assign cssproperties for (const styleProperty in container.properties.style) { - const input: HTMLInputElement | null = (event.target as HTMLFormElement).querySelector(`#${styleProperty}`); - if (input === null) { - continue; - } - (container.properties.style as any)[styleProperty] = input.value; + submitCSSForm(form, styleProperty, container); } if (container.properties.isRigidBody) { @@ -167,3 +131,67 @@ export function OnPropertiesSubmit( setHistory(history); setHistoryCurrentStep(history.length - 1); } + +const submitHTMLInput = ( + input: HTMLInputElement, + container: IContainerModel, + property: string, + form: HTMLFormElement +): void => { + if (INPUT_TYPES[property] !== 'number') { + (container.properties as any)[property] = input.value; + } + + if (property === 'x') { + // Hardcoded fix for XPositionReference + const x = RestoreX(form, input); + (container.properties as any)[property] = x; + return; + } + + (container.properties as any)[property] = Number(input.value); +}; + +const submitCSSForm = (form: HTMLFormElement, styleProperty: string, container: ContainerModel): void => { + const input: HTMLInputElement | null = form.querySelector(`#${styleProperty}`); + if (input === null) { + return; + } + (container.properties.style as any)[styleProperty] = input.value; +}; + +const RestoreX = ( + form: HTMLFormElement, + input: HTMLInputElement +): number => { + const inputWidth: HTMLInputElement | null = form.querySelector('#width'); + const inputRadio: HTMLDivElement | null = form.querySelector('#XPositionReference'); + if (inputWidth === null || inputRadio === null) { + throw new Error('[OnPropertiesSubmit] Missing inputs for width or XPositionReference'); + } + + const radiobutton: HTMLInputElement | null = inputRadio.querySelector('input[name="XPositionReference"]:checked'); + if (radiobutton === null) { + throw new Error('[OnPropertiesSubmit] Missing inputs for XPositionReference'); + } + + return restoreX(Number(input.value), Number(inputWidth.value), Number(radiobutton.value)); +}; + +const submitRadioButtons = ( + div: HTMLDivElement, + container: IContainerModel, + property: string +): void => { + const radiobutton: HTMLInputElement | null = div.querySelector(`input[name="${property}"]:checked`); + if (radiobutton === null) { + return; + } + + if (INPUT_TYPES[property] === 'number') { + (container.properties as any)[property] = Number(radiobutton.value); + return; + } + + (container.properties as any)[property] = radiobutton.value; +}; From a36d5d79d498b4d70d77245c5b3f94305b44e319 Mon Sep 17 00:00:00 2001 From: Eric NGUYEN Date: Tue, 16 Aug 2022 16:54:12 +0200 Subject: [PATCH 13/14] Fix test: when using dynamic input, the value is converted correctly as number before being send to the change event --- src/Components/Properties/Properties.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Properties/Properties.test.tsx b/src/Components/Properties/Properties.test.tsx index 61441f8..dc9473e 100644 --- a/src/Components/Properties/Properties.test.tsx +++ b/src/Components/Properties/Properties.test.tsx @@ -68,8 +68,8 @@ describe.concurrent('Properties', () => { expect(prop.id).toBe('stuff'); expect(prop.parentId).toBe('parentId'); - expect(prop.x).toBe('2'); - expect(prop.y).toBe('2'); + expect(prop.x).toBe(2); + expect(prop.y).toBe(2); rerender( Date: Tue, 16 Aug 2022 17:02:40 +0200 Subject: [PATCH 14/14] drone-ci: Remove pnpm --- .drone.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.drone.yml b/.drone.yml index e3b1f91..b0d7165 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,11 +6,10 @@ steps: - name: test image: node:16 commands: - - curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7 - node ./test-server/node-http.js & - - pnpm install - - pnpm run test:nowatch - - pnpm run build + - npm ci + - npm run test:nowatch + - npm run build --- kind: pipeline @@ -20,8 +19,7 @@ steps: - name: test image: node commands: - - curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7 - node ./test-server/node-http.js & - - pnpm install - - pnpm run test:nowatch - - pnpm run build \ No newline at end of file + - npm ci + - npm run test:nowatch + - npm run build \ No newline at end of file