diff --git a/src/Components/Editor/ContainerOperations.ts b/src/Components/Editor/ContainerOperations.ts index ec5a28a..cbc0a9c 100644 --- a/src/Components/Editor/ContainerOperations.ts +++ b/src/Components/Editor/ContainerOperations.ts @@ -55,7 +55,9 @@ export function DeleteContainer( throw new Error(`[DeleteContainer] Tried to delete a container that is not present in the main container: ${containerId}`); } - if (container === mainContainerClone) { + if (container === mainContainerClone || + container.parent === undefined || + container.parent === null) { // TODO: Implement alert throw new Error('[DeleteContainer] Tried to delete the main container! Deleting the main container is not allowed!'); } @@ -64,18 +66,25 @@ export function DeleteContainer( throw new Error('[DeleteContainer] Container model was not found among children of the main container!'); } - if (container.parent != null) { - const index = container.parent.children.indexOf(container); - if (index > -1) { - container.parent.children.splice(index, 1); - } + const index = container.parent.children.indexOf(container); + if (index > -1) { + container.parent.children.splice(index, 1); + } else { + throw new Error('[DeleteContainer] Could not find container among parent\'s children'); } + // Select the previous container + // or select the one above + const SelectedContainer = findContainerById(mainContainerClone, current.SelectedContainerId) ?? + container.parent.children.at(index - 1) ?? + container.parent; + const SelectedContainerId = SelectedContainer.properties.id; + setHistory(history.concat([{ LastAction: `Delete container ${containerId}`, MainContainer: mainContainerClone, - SelectedContainer: null, - SelectedContainerId: '', + SelectedContainer, + SelectedContainerId, TypeCounters: Object.assign({}, current.TypeCounters) }])); setHistoryCurrentStep(history.length); @@ -182,6 +191,7 @@ export function AddContainer( width: properties?.Width, height: parentClone.properties.height, isRigidBody: false, + XPositionReference: properties.XPositionReference, ...properties.Style }, [], diff --git a/src/Components/Editor/Save.ts b/src/Components/Editor/Save.ts index 91cee2f..3aa201a 100644 --- a/src/Components/Editor/Save.ts +++ b/src/Components/Editor/Save.ts @@ -1,4 +1,4 @@ -import { HistoryState } from "../../Interfaces/HistoryState"; +import { HistoryState } from '../../Interfaces/HistoryState'; import { Configuration } from '../../Interfaces/Configuration'; import { getCircularReplacer } from '../../utils/saveload'; import { ID } from '../SVG/SVG'; diff --git a/src/Components/ElementsSidebar/ElementsSidebar.tsx b/src/Components/ElementsSidebar/ElementsSidebar.tsx index c0a89c6..e9d5e59 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.tsx @@ -9,7 +9,6 @@ import { MenuItem } from '../Menu/MenuItem'; import { handleDragLeave, handleDragOver, handleLeftClick, handleOnDrop, handleRightClick } from './MouseEventHandlers'; import { Point } from '../../Interfaces/Point'; - interface IElementsSidebarProps { MainContainer: IContainerModel isOpen: boolean @@ -108,7 +107,7 @@ export const ElementsSidebar: React.FC = (props: IElement onLeftClick ); }; - }, []); + }); // Render let isOpenClasses = '-right-64'; diff --git a/src/Components/Properties/Properties.tsx b/src/Components/Properties/Properties.tsx index 3720188..49f5028 100644 --- a/src/Components/Properties/Properties.tsx +++ b/src/Components/Properties/Properties.tsx @@ -22,15 +22,17 @@ export const Properties: React.FC = (props: IPropertiesProps) .forEach((pair) => handleProperties(pair, groupInput, isDynamicInput, props.onChange)); const form = isDynamicInput - ?
+ ?
{ groupInput }
:
props.onSubmit(event, props.properties as ContainerProperties)} > - - { groupInput } + +
+ { groupInput } +
; @@ -67,16 +69,19 @@ const handleProperties = ( 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 ? { @@ -89,22 +94,23 @@ const handleProperties = ( disabled={isDisabled} /> : ; groupInput.push( -
- - {input} -
+ ); + groupInput.push(input); }; diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index 02dea6f..25a45c9 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { XPositionReference } from '../../../Enums/XPositionReference'; import { IContainerModel } from '../../../Interfaces/ContainerModel'; import { getDepth } from '../../../utils/itertools'; import { Dimension } from './Dimension'; @@ -17,7 +18,14 @@ 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 transform = `translate(${Number(props.model.properties.x)}, ${Number(props.model.properties.y)})`; + + const [transformedX, transformedY] = transformPosition( + Number(props.model.properties.x), + Number(props.model.properties.y), + Number(props.model.properties.width), + props.model.properties.XPositionReference + ); + const transform = `translate(${transformedX}, ${transformedY})`; // g style const defaultStyle: React.CSSProperties = { @@ -54,7 +62,8 @@ export const Container: React.FC = (props: IContainerProps) => id={id} xStart={xStart} xEnd={xEnd} - y={y} + yStart={y} + yEnd={y} strokeWidth={strokeWidth} text={text} /> @@ -74,3 +83,13 @@ export const Container: React.FC = (props: IContainerProps) => ); }; + +function transformPosition(x: number, y: number, width: number, xPositionReference = XPositionReference.Left): [number, number] { + let transformedX = x; + if (xPositionReference === XPositionReference.Center) { + transformedX -= width / 2; + } else if (xPositionReference === XPositionReference.Right) { + transformedX -= width; + } + return [transformedX, y]; +} diff --git a/src/Components/SVG/Elements/Dimension.tsx b/src/Components/SVG/Elements/Dimension.tsx index ec51873..08f0a22 100644 --- a/src/Components/SVG/Elements/Dimension.tsx +++ b/src/Components/SVG/Elements/Dimension.tsx @@ -3,45 +3,80 @@ import * as React from 'react'; interface IDimensionProps { id: string xStart: number + yStart: number xEnd: number - y: number + yEnd: number text: string strokeWidth: number } +/** + * 2D Parametric function. Returns a new coordinate from the origin coordinate + * See for more details https://en.wikipedia.org/wiki/Parametric_equation. + * TL;DR a parametric function is a function with a parameter + * @param x0 Origin coordinate + * @param t The parameter + * @param vx Transform vector + * @returns Returns a new coordinate from the origin coordinate + */ +const applyParametric = (x0: number, t: number, vx: number): number => x0 + t * vx; + export const Dimension: React.FC = (props: IDimensionProps) => { const style: React.CSSProperties = { stroke: 'black' }; + + /// We need to find the points of the notches + // Get the vector of the line + const [deltaX, deltaY] = [(props.xEnd - props.xStart), (props.yEnd - props.yStart)]; + + // Get the unit vector + const norm = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + const [unitX, unitY] = [deltaX / norm, deltaY / norm]; + + // Get the perpandicular vector + const [perpVecX, perpVecY] = [unitY, -unitX]; + + // Use the parametric function to get the coordinates (x = x0 + t * v.x) + const startTopX = applyParametric(props.xStart, 4, perpVecX); + const startTopY = applyParametric(props.yStart, 4, perpVecY); + const startBottomX = applyParametric(props.xStart, -4, perpVecX); + const startBottomY = applyParametric(props.yStart, -4, perpVecY); + + const endTopX = applyParametric(props.xEnd, 4, perpVecX); + const endTopY = applyParametric(props.yEnd, 4, perpVecY); + const endBottomX = applyParametric(props.xEnd, -4, perpVecX); + const endBottomY = applyParametric(props.yEnd, -4, perpVecY); + return ( {props.text} diff --git a/src/Components/SVG/Elements/DimensionLayer.tsx b/src/Components/SVG/Elements/DimensionLayer.tsx deleted file mode 100644 index 85f1a43..0000000 --- a/src/Components/SVG/Elements/DimensionLayer.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from 'react'; -import { ContainerModel } from '../../../Interfaces/ContainerModel'; -import { getDepth, MakeIterator } from '../../../utils/itertools'; -import { Dimension } from './Dimension'; - -interface IDimensionLayerProps { - isHidden: boolean - roots: ContainerModel | ContainerModel[] | null -} - -const GAP: number = 50; - -const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => { - const it = MakeIterator(root); - const dimensions: React.ReactNode[] = []; - for (const container of it) { - // WARN: this might be dangerous later when using other units/rules - const width = Number(container.properties.width); - - const id = `dim-${container.properties.id}`; - const xStart: number = container.properties.x; - const xEnd = xStart + width; - const y = -(GAP * (getDepth(container) + 1)); - const strokeWidth = 1; - const text = width.toString(); - dimensions.push( - - ); - } - return dimensions; -}; - -/** - * A layer containing all dimension - * - * @deprecated In order to avoid adding complexity - * with computing the position in a group hierarchy, - * use Dimension directly inside the Container, - * Currently it is glitched as - * it does not take parents into account, - * and will not work correctly - * @param props - * @returns - */ -export const DimensionLayer: React.FC = (props: IDimensionLayerProps) => { - let dimensions: React.ReactNode[] = []; - if (Array.isArray(props.roots)) { - props.roots.forEach(child => { - dimensions.concat(getDimensionsNodes(child)); - }); - } else if (props.roots !== null) { - dimensions = getDimensionsNodes(props.roots); - } - return ( - - { dimensions } - - ); -}; diff --git a/src/Interfaces/AvailableContainer.ts b/src/Interfaces/AvailableContainer.ts index d7bb22f..b328b0a 100644 --- a/src/Interfaces/AvailableContainer.ts +++ b/src/Interfaces/AvailableContainer.ts @@ -1,9 +1,11 @@ import React from 'react'; +import { XPositionReference } from '../Enums/XPositionReference'; /** Model of available container used in application configuration */ export interface AvailableContainer { Type: string Width: number Height: number + XPositionReference?: XPositionReference Style: React.CSSProperties } diff --git a/src/Interfaces/Properties.ts b/src/Interfaces/Properties.ts index ea5f54e..6f2f32e 100644 --- a/src/Interfaces/Properties.ts +++ b/src/Interfaces/Properties.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import { XPositionReference } from '../Enums/XPositionReference'; export default interface Properties extends React.CSSProperties { id: string @@ -6,4 +7,5 @@ export default interface Properties extends React.CSSProperties { x: number y: number isRigidBody: boolean + XPositionReference?: XPositionReference } diff --git a/src/utils/default.ts b/src/utils/default.ts index a117ef0..51c1d7a 100644 --- a/src/utils/default.ts +++ b/src/utils/default.ts @@ -36,3 +36,5 @@ export const DEFAULT_MAINCONTAINER_PROPS: Properties = { fillOpacity: 0, stroke: 'black' }; + +export const NOTCHES_LENGTH = 4; diff --git a/test-server/http.js b/test-server/http.js index c4f3238..69088c9 100644 --- a/test-server/http.js +++ b/test-server/http.js @@ -76,9 +76,6 @@ const GetSVGLayoutConfiguration = () => { fillOpacity: 0, borderWidth: 2, stroke: 'blue', - transform: 'translateX(-50%)', - transformOrigin: 'center', - transformBox: 'fill-box' } } ],