Implement more properties for IAvailableContainer #31

Merged
Siklos merged 5 commits from dev.moreproperties into dev 2022-08-15 17:04:45 -04:00
15 changed files with 137 additions and 62 deletions

View file

@ -20,8 +20,8 @@ export function NewEditor(
parentId: 'null', parentId: 'null',
x: 0, x: 0,
y: 0, y: 0,
width: configuration.MainContainer.Width, width: Number(configuration.MainContainer.Width),
height: configuration.MainContainer.Height, height: Number(configuration.MainContainer.Height),
isRigidBody: false, isRigidBody: false,
isAnchor: false, isAnchor: false,
fillOpacity: 0, fillOpacity: 0,

View file

@ -49,7 +49,7 @@ function getOverlappingContainers(
containers: IContainerModel[] containers: IContainerModel[]
): IContainerModel[] { ): IContainerModel[] {
const min1 = container.properties.x; 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[] = []; const overlappingContainers: IContainerModel[] = [];
for (const other of containers) { for (const other of containers) {
if (other === container) { if (other === container) {
@ -57,7 +57,7 @@ function getOverlappingContainers(
} }
const min2 = other.properties.x; 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; const isOverlapping = Math.min(max1, max2) - Math.max(min1, min2) > 0;
if (!isOverlapping) { if (!isOverlapping) {

View file

@ -42,8 +42,8 @@ function constraintBodyInsideParent(
} }
const parentProperties = container.parent.properties; const parentProperties = container.parent.properties;
const parentWidth = Number(parentProperties.width); const parentWidth = parentProperties.width;
const parentHeight = Number(parentProperties.height); const parentHeight = parentProperties.height;
return constraintBodyInsideSpace(container, 0, 0, parentWidth, parentHeight); return constraintBodyInsideSpace(container, 0, 0, parentWidth, parentHeight);
} }
@ -66,10 +66,10 @@ function constraintBodyInsideSpace(
height: number height: number
): IContainerModel { ): IContainerModel {
const containerProperties = container.properties; const containerProperties = container.properties;
const containerX = Number(containerProperties.x); const containerX = containerProperties.x;
const containerY = Number(containerProperties.y); const containerY = containerProperties.y;
const containerWidth = Number(containerProperties.width); const containerWidth = containerProperties.width;
const containerHeight = Number(containerProperties.height); const containerHeight = containerProperties.height;
// Check size bigger than parent // Check size bigger than parent
const isBodyLargerThanParent = containerWidth > width; const isBodyLargerThanParent = containerWidth > width;
@ -121,8 +121,8 @@ export function constraintBodyInsideUnallocatedWidth(
// Get the available spaces of the parent // Get the available spaces of the parent
const availableWidths = getAvailableWidths(container.parent, container); const availableWidths = getAvailableWidths(container.parent, container);
const containerX = Number(container.properties.x); const containerX = container.properties.x;
const containerWidth = Number(container.properties.width); const containerWidth = container.properties.width;
// Check if there is still some space // Check if there is still some space
if (availableWidths.length === 0) { if (availableWidths.length === 0) {
@ -177,7 +177,7 @@ export function constraintBodyInsideUnallocatedWidth(
availableWidthFound.x, availableWidthFound.x,
0, 0,
availableWidthFound.width, availableWidthFound.width,
Number(container.parent.properties.height) container.parent.properties.height
); );
} }
@ -197,7 +197,7 @@ function getAvailableWidths(
// Initialize the first size pointer // Initialize the first size pointer
// which takes full width of the available space // which takes full width of the available space
const x = 0; const x = 0;
const width = Number(container.properties.width); const width = container.properties.width;
let unallocatedSpaces: ISizePointer[] = [{ x, width }]; let unallocatedSpaces: ISizePointer[] = [{ x, width }];
// We will only uses containers that also are rigid or are anchors // We will only uses containers that also are rigid or are anchors
@ -211,7 +211,7 @@ function getAvailableWidths(
continue; continue;
} }
const childX = child.properties.x; 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 // get the space of the child that is inside the parent
let newUnallocatedSpace: ISizePointer[] = []; let newUnallocatedSpace: ISizePointer[] = [];
@ -309,4 +309,4 @@ function getAvailableWidthsTwoLines(
const isFitting = ( const isFitting = (
container: IContainerModel, container: IContainerModel,
sizePointer: ISizePointer sizePointer: ISizePointer
): boolean => Number(container.properties.width) <= sizePointer.width; ): boolean => container.properties.width <= sizePointer.width;

View file

@ -1,10 +1,13 @@
import { Dispatch, SetStateAction } from 'react'; import React, { Dispatch, SetStateAction } from 'react';
import { IHistoryState } from '../../Interfaces/IHistoryState'; import { IHistoryState } from '../../Interfaces/IHistoryState';
import { IConfiguration } from '../../Interfaces/IConfiguration'; import { IConfiguration } from '../../Interfaces/IConfiguration';
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel'; import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
import { findContainerById } from '../../utils/itertools'; import { findContainerById } from '../../utils/itertools';
import { getCurrentHistory } from './Editor'; import { getCurrentHistory } from './Editor';
import IProperties from '../../Interfaces/IProperties'; import IProperties from '../../Interfaces/IProperties';
import { AddMethod } from '../../Enums/AddMethod';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { transformPosition } from '../SVG/Elements/Container';
/** /**
* Select a container * Select a container
@ -169,10 +172,10 @@ export function AddContainer(
} }
// Get the preset properties from the API // Get the preset properties from the API
const properties = configuration.AvailableContainers const containerConfig = configuration.AvailableContainers
.find(option => option.Type === type); .find(option => option.Type === type);
if (properties === undefined) { if (containerConfig === undefined) {
throw new Error(`[AddContainer] Object type not found. Found: ${type}`); throw new Error(`[AddContainer] Object type not found. Found: ${type}`);
} }
@ -198,25 +201,24 @@ export function AddContainer(
throw new Error('[AddContainer] Container model was not found among children of the main container!'); throw new Error('[AddContainer] Container model was not found among children of the main container!');
} }
let x = 0; let x = containerConfig.DefaultX ?? 0;
if (index > 0) { const y = containerConfig.DefaultY ?? 0;
const lastChild: IContainerModel | undefined = parentClone.children.at(index - 1); const width = containerConfig.Width ?? parentClone.properties.width;
if (lastChild !== undefined) { const height = containerConfig.Height ?? parentClone.properties.height;
x = lastChild.properties.x + Number(lastChild.properties.width);
} x = ApplyAddMethod(index, containerConfig, parentClone, x);
}
const defaultProperties: IProperties = { const defaultProperties: IProperties = {
...containerConfig.Style,
id: `${type}-${count}`, id: `${type}-${count}`,
parentId: parentClone.properties.id, parentId: parentClone.properties.id,
x, x,
y: 0, y,
width: properties.Width, width,
height: parentClone.properties.height, height,
isRigidBody: false, isRigidBody: false,
isAnchor: false, isAnchor: false,
XPositionReference: properties.XPositionReference, XPositionReference: containerConfig.XPositionReference
...properties.Style
}; };
// Create the container // Create the container
@ -247,3 +249,32 @@ export function AddContainer(
setHistory(history); setHistory(history);
setHistoryCurrentStep(history.length - 1); setHistoryCurrentStep(history.length - 1);
} }
/**
* 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 = 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;
}
}
return x;
}

View file

@ -133,8 +133,8 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
LoadState={(move) => setHistoryCurrentStep(move)} LoadState={(move) => setHistoryCurrentStep(move)}
/> />
<SVG <SVG
width={Number(current.MainContainer?.properties.width)} width={current.MainContainer?.properties.width}
height={Number(current.MainContainer?.properties.height)} height={current.MainContainer?.properties.height}
selected={current.SelectedContainer} selected={current.SelectedContainer}
> >
{ current.MainContainer } { current.MainContainer }

View file

@ -1,6 +1,7 @@
import { fireEvent, render, screen } from '@testing-library/react'; import { fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react'; import * as React from 'react';
import { expect, describe, it, vi } from 'vitest'; import { expect, describe, it, vi } from 'vitest';
import IProperties from '../../Interfaces/IProperties';
import { Properties } from './Properties'; import { Properties } from './Properties';
describe.concurrent('Properties', () => { describe.concurrent('Properties', () => {
@ -18,11 +19,13 @@ describe.concurrent('Properties', () => {
}); });
it('Some properties, change values with dynamic input', () => { it('Some properties, change values with dynamic input', () => {
const prop = { const prop: IProperties = {
id: 'stuff', id: 'stuff',
parentId: 'parentId', parentId: 'parentId',
x: 1, x: 1,
y: 1, y: 1,
width: 1,
height: 1,
isRigidBody: false, isRigidBody: false,
isAnchor: false isAnchor: false
}; };

View file

@ -4,5 +4,6 @@ export const INPUT_TYPES: Record<string, string> = {
width: 'number', width: 'number',
height: 'number', height: 'number',
isRigidBody: 'checkbox', isRigidBody: 'checkbox',
isAnchor: 'checkbox' isAnchor: 'checkbox',
XPositionReference: 'number'
}; };

View file

@ -15,13 +15,13 @@ interface IContainerProps {
*/ */
export const Container: React.FC<IContainerProps> = (props: IContainerProps) => { export const Container: React.FC<IContainerProps> = (props: IContainerProps) => {
const containersElements = props.model.children.map(child => <Container key={`container-${child.properties.id}`} model={child} />); const containersElements = props.model.children.map(child => <Container key={`container-${child.properties.id}`} model={child} />);
const xText = Number(props.model.properties.width) / 2; const xText = props.model.properties.width / 2;
const yText = Number(props.model.properties.height) / 2; const yText = props.model.properties.height / 2;
const [transformedX, transformedY] = transformPosition( const [transformedX, transformedY] = transformPosition(
Number(props.model.properties.x), props.model.properties.x,
Number(props.model.properties.y), props.model.properties.y,
Number(props.model.properties.width), props.model.properties.width,
props.model.properties.XPositionReference props.model.properties.XPositionReference
); );
const transform = `translate(${transformedX}, ${transformedY})`; const transform = `translate(${transformedX}, ${transformedY})`;
@ -48,7 +48,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
const dimensionMargin = DIMENSION_MARGIN * (depth + 1); const dimensionMargin = DIMENSION_MARGIN * (depth + 1);
const id = `dim-${props.model.properties.id}`; const id = `dim-${props.model.properties.id}`;
const xStart: number = 0; const xStart: number = 0;
const xEnd = Number(props.model.properties.width); const xEnd = props.model.properties.width;
const y = -dimensionMargin; const y = -dimensionMargin;
const strokeWidth = 1; const strokeWidth = 1;
const text = (props.model.properties.width ?? 0).toString(); const text = (props.model.properties.width ?? 0).toString();
@ -112,25 +112,25 @@ function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: numb
const lastChild = props.model.children[props.model.children.length - 1]; const lastChild = props.model.children[props.model.children.length - 1];
let xChildrenStart = lastChild.properties.x; 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--) { for (let i = props.model.children.length - 2; i >= 0; i--) {
const child = props.model.children[i]; const child = props.model.children[i];
const left = child.properties.x; const left = child.properties.x;
if (left < xChildrenStart) { if (left < xChildrenStart) {
xChildrenStart = left; xChildrenStart = left;
} }
const right = child.properties.x + Number(child.properties.width); const right = child.properties.x + child.properties.width;
if (right > xChildrenEnd) { if (right > xChildrenEnd) {
xChildrenEnd = right; xChildrenEnd = right;
} }
} }
const yChildren = Number(props.model.properties.height) + dimensionMargin; const yChildren = props.model.properties.height + dimensionMargin;
const textChildren = (xChildrenEnd - xChildrenStart).toString(); const textChildren = (xChildrenEnd - xChildrenStart).toString();
return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren }; 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; let transformedX = x;
if (xPositionReference === XPositionReference.Center) { if (xPositionReference === XPositionReference.Center) {
transformedX -= width / 2; transformedX -= width / 2;

View file

@ -1,6 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { getAbsolutePosition } from '../../../utils/itertools'; import { getAbsolutePosition } from '../../../utils/itertools';
import { transformPosition } from './Container';
interface ISelectorProps { interface ISelectorProps {
selected: IContainerModel | null selected: IContainerModel | null
@ -15,6 +16,12 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
} }
const [x, y] = getAbsolutePosition(props.selected); const [x, y] = getAbsolutePosition(props.selected);
const [transformedX, transformedY] = transformPosition(
x,
y,
props.selected.properties.width,
props.selected.properties.XPositionReference
);
const [width, height] = [ const [width, height] = [
props.selected.properties.width, props.selected.properties.width,
props.selected.properties.height props.selected.properties.height
@ -31,8 +38,8 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
return ( return (
<rect <rect
x={x} x={transformedX}
y={y} y={transformedY}
width={width} width={width}
height={height} height={height}
style={style} style={style}

10
src/Enums/AddMethod.ts Normal file
View file

@ -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
}

View file

@ -1,11 +1,15 @@
import React from 'react'; import React from 'react';
import { AddMethod } from '../Enums/AddMethod';
import { XPositionReference } from '../Enums/XPositionReference'; import { XPositionReference } from '../Enums/XPositionReference';
/** Model of available container used in application configuration */ /** Model of available container used in application configuration */
export interface IAvailableContainer { export interface IAvailableContainer {
Type: string Type: string
Width: number Width?: number
Height: number Height?: number
DefaultX?: number
DefaultY?: number
AddMethod?: AddMethod
XPositionReference?: XPositionReference XPositionReference?: XPositionReference
Style: React.CSSProperties Style: React.CSSProperties
} }

View file

@ -10,11 +10,13 @@ import { XPositionReference } from '../Enums/XPositionReference';
* @property isRigidBody if true apply rigid body behaviors * @property isRigidBody if true apply rigid body behaviors
* @property isAnchor if true apply anchor behaviors * @property isAnchor if true apply anchor behaviors
*/ */
export default interface IProperties extends React.CSSProperties { export default interface IProperties extends Omit<React.CSSProperties, 'width' | 'height'> {
id: string id: string
parentId: string | null parentId: string | null
x: number x: number
y: number y: number
width: number
height: number
isRigidBody: boolean isRigidBody: boolean
isAnchor: boolean isAnchor: boolean
XPositionReference?: XPositionReference XPositionReference?: XPositionReference

View file

@ -30,8 +30,8 @@ export const DEFAULT_MAINCONTAINER_PROPS: IProperties = {
parentId: 'null', parentId: 'null',
x: 0, x: 0,
y: 0, y: 0,
width: DEFAULT_CONFIG.MainContainer.Width, width: Number(DEFAULT_CONFIG.MainContainer.Width),
height: DEFAULT_CONFIG.MainContainer.Height, height: Number(DEFAULT_CONFIG.MainContainer.Height),
isRigidBody: false, isRigidBody: false,
isAnchor: false, isAnchor: false,
fillOpacity: 0, fillOpacity: 0,

View file

@ -43,12 +43,12 @@ export function getDepth(parent: IContainerModel): number {
* @returns The absolute position of the container * @returns The absolute position of the container
*/ */
export function getAbsolutePosition(container: IContainerModel): [number, number] { export function getAbsolutePosition(container: IContainerModel): [number, number] {
let x = Number(container.properties.x); let x = container.properties.x;
let y = Number(container.properties.y); let y = container.properties.y;
let current = container.parent; let current = container.parent;
while (current != null) { while (current != null) {
x += Number(current.properties.x); x += current.properties.x;
y += Number(current.properties.y); y += current.properties.y;
current = current.parent; current = current.parent;
} }
return [x, y]; return [x, y];

View file

@ -55,27 +55,44 @@ const GetSVGLayoutConfiguration = () => {
Type: 'Chassis', Type: 'Chassis',
Width: 500, Width: 500,
Style: { Style: {
fillOpacity: 0, fillOpacity: 1,
borderWidth: 2, borderWidth: 2,
stroke: 'red',
fill: '#78350F',
stroke: 'red' stroke: 'red'
} }
}, },
{ {
Type: 'Trou', Type: 'Trou',
Width: 300, DefaultX: 10,
DefaultY: 10,
Width: 480,
Height: 180,
Style: { Style: {
fillOpacity: 0, fillOpacity: 1,
borderWidth: 2, borderWidth: 2,
stroke: 'green' stroke: 'green',
fill: 'white'
}
},
{
Type: 'Remplissage',
Style: {
fillOpacity: 1,
borderWidth: 2,
stroke: '#bfdbfe',
fill: '#bfdbfe'
} }
}, },
{ {
Type: 'Montant', Type: 'Montant',
Width: 100, Width: 10,
XPositionReference: 1,
Style: { Style: {
fillOpacity: 0, fillOpacity: 0,
borderWidth: 2, borderWidth: 2,
stroke: 'blue', stroke: '#713f12',
fill: '#713f12',
} }
} }
], ],