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 #33

Merged
Siklos merged 4 commits from dev.fixtransform into dev 2022-08-16 10:56:58 -04:00
7 changed files with 108 additions and 67 deletions

View file

@ -7,7 +7,6 @@ import { getCurrentHistory } from './Editor';
import IProperties from '../../Interfaces/IProperties'; import IProperties from '../../Interfaces/IProperties';
import { AddMethod } from '../../Enums/AddMethod'; import { AddMethod } from '../../Enums/AddMethod';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer'; import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { transformPosition } from '../SVG/Elements/Container';
import { XPositionReference } from '../../Enums/XPositionReference'; import { XPositionReference } from '../../Enums/XPositionReference';
/** /**
@ -58,7 +57,7 @@ export function DeleteContainer(
setHistoryCurrentStep: Dispatch<SetStateAction<number>> setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): void { ): void {
const history = getCurrentHistory(fullHistory, historyCurrentStep); const history = getCurrentHistory(fullHistory, historyCurrentStep);
const current = history[historyCurrentStep]; const current = history[history.length - 1];
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer); const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
const container = findContainerById(mainContainerClone, containerId); const container = findContainerById(mainContainerClone, containerId);
@ -267,14 +266,7 @@ function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, par
const lastChild: IContainerModel | undefined = parent.children.at(index - 1); const lastChild: IContainerModel | undefined = parent.children.at(index - 1);
if (lastChild !== undefined) { if (lastChild !== undefined) {
const [transformedX] = transformPosition( x += (lastChild.properties.x + lastChild.properties.width);
lastChild.properties.x,
lastChild.properties.y,
lastChild.properties.width,
lastChild.properties.XPositionReference
);
x += transformedX + lastChild.properties.width;
} }
} }
return x; return x;

View file

@ -3,9 +3,10 @@ import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerMode
import { IHistoryState } from '../../Interfaces/IHistoryState'; import { IHistoryState } from '../../Interfaces/IHistoryState';
import { findContainerById } from '../../utils/itertools'; import { findContainerById } from '../../utils/itertools';
import { getCurrentHistory } from './Editor'; import { getCurrentHistory } from './Editor';
import { RecalculatePhysics } from './Behaviors/RigidBodyBehaviors'; import { constraintBodyInsideUnallocatedWidth, RecalculatePhysics } from './Behaviors/RigidBodyBehaviors';
import { INPUT_TYPES } from '../Properties/PropertiesInputTypes'; import { INPUT_TYPES } from '../Properties/PropertiesInputTypes';
import { ImposePosition } from './Behaviors/AnchorBehaviors'; import { ImposePosition } from './Behaviors/AnchorBehaviors';
import { restoreX } from '../SVG/Elements/Container';
/** /**
* Handled the property change event in the properties form * Handled the property change event in the properties form
@ -40,11 +41,7 @@ export function OnPropertyChange(
if (isStyle) { if (isStyle) {
(container.properties.style as any)[key] = value; (container.properties.style as any)[key] = value;
} else { } else {
if (INPUT_TYPES[key] === 'number') { (container.properties as any)[key] = value;
(container.properties as any)[key] = Number(value);
} else {
(container.properties as any)[key] = value;
}
} }
if (container.properties.isAnchor) { if (container.properties.isAnchor) {
@ -96,39 +93,28 @@ export function OnPropertiesSubmit(
} }
// Assign container properties // Assign container properties
const form: HTMLFormElement = event.target as HTMLFormElement;
for (const property in container.properties) { 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) { if (input === null) {
continue; continue;
} }
if (input instanceof HTMLInputElement) { if (input instanceof HTMLInputElement) {
(container.properties as any)[property] = input.value; submitHTMLInput(input, container, property, form);
if (INPUT_TYPES[property] === 'number') { continue;
(container.properties as any)[property] = Number(input.value); }
}
} else if (input instanceof HTMLDivElement) {
const radiobutton: HTMLInputElement | null = input.querySelector(`input[name="${property}"]:checked`);
if (radiobutton === null) { if (input instanceof HTMLDivElement) {
continue; submitRadioButtons(input, container, property);
} continue;
(container.properties as any)[property] = radiobutton.value;
if (INPUT_TYPES[property] === 'number') {
(container.properties as any)[property] = Number(radiobutton.value);
}
} }
} }
// Assign cssproperties // Assign cssproperties
for (const styleProperty in container.properties.style) { for (const styleProperty in container.properties.style) {
const input: HTMLInputElement | null = (event.target as HTMLFormElement).querySelector(`#${styleProperty}`); submitCSSForm(form, styleProperty, container);
if (input === null) {
continue;
}
(container.properties.style as any)[styleProperty] = input.value;
} }
if (container.properties.isRigidBody) { if (container.properties.isRigidBody) {
@ -145,3 +131,67 @@ export function OnPropertiesSubmit(
setHistory(history); setHistory(history);
setHistoryCurrentStep(history.length - 1); 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;
};

View file

@ -4,6 +4,7 @@ import { XPositionReference } from '../../Enums/XPositionReference';
import IProperties from '../../Interfaces/IProperties'; import IProperties from '../../Interfaces/IProperties';
import { InputGroup } from '../InputGroup/InputGroup'; import { InputGroup } from '../InputGroup/InputGroup';
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons'; import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
import { restoreX, transformX } from '../SVG/Elements/Container';
interface IDynamicFormProps { interface IDynamicFormProps {
properties: IProperties properties: IProperties
@ -57,8 +58,8 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='number' type='number'
value={props.properties.x.toString()} value={transformX(props.properties.x, props.properties.width, props.properties.XPositionReference).toString()}
onChange={(event) => props.onChange('x', event.target.value)} onChange={(event) => props.onChange('x', restoreX(Number(event.target.value), props.properties.width, props.properties.XPositionReference))}
/> />
<InputGroup <InputGroup
labelText='y' labelText='y'
@ -67,7 +68,7 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
inputClassName='' inputClassName=''
type='number' type='number'
value={props.properties.y.toString()} value={props.properties.y.toString()}
onChange={(event) => props.onChange('y', event.target.value)} onChange={(event) => props.onChange('y', Number(event.target.value))}
/> />
<InputGroup <InputGroup
labelText='Width' labelText='Width'
@ -76,7 +77,7 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
inputClassName='' inputClassName=''
type='number' type='number'
value={props.properties.width.toString()} value={props.properties.width.toString()}
onChange={(event) => props.onChange('width', event.target.value)} onChange={(event) => props.onChange('width', Number(event.target.value))}
/> />
<InputGroup <InputGroup
labelText='Height' labelText='Height'
@ -85,7 +86,7 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
inputClassName='' inputClassName=''
type='number' type='number'
value={props.properties.height.toString()} value={props.properties.height.toString()}
onChange={(event) => props.onChange('height', event.target.value)} onChange={(event) => props.onChange('height', Number(event.target.value))}
/> />
<InputGroup <InputGroup
labelText='Rigid' labelText='Rigid'
@ -136,7 +137,7 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
value: XPositionReference.Right.toString() 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) } { getCSSInputs(props.properties, props.onChange) }
</div> </div>

View file

@ -68,8 +68,8 @@ describe.concurrent('Properties', () => {
expect(prop.id).toBe('stuff'); expect(prop.id).toBe('stuff');
expect(prop.parentId).toBe('parentId'); expect(prop.parentId).toBe('parentId');
expect(prop.x).toBe('2'); expect(prop.x).toBe(2);
expect(prop.y).toBe('2'); expect(prop.y).toBe(2);
rerender(<Properties rerender(<Properties
properties={Object.assign({}, prop)} properties={Object.assign({}, prop)}
onChange={handleChange} onChange={handleChange}

View file

@ -4,6 +4,7 @@ import { XPositionReference } from '../../Enums/XPositionReference';
import IProperties from '../../Interfaces/IProperties'; import IProperties from '../../Interfaces/IProperties';
import { InputGroup } from '../InputGroup/InputGroup'; import { InputGroup } from '../InputGroup/InputGroup';
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons'; import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
import { transformX } from '../SVG/Elements/Container';
interface IStaticFormProps { interface IStaticFormProps {
properties: IProperties properties: IProperties
@ -57,7 +58,7 @@ const StaticForm: React.FunctionComponent<IStaticFormProps> = (props) => {
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
type='number' type='number'
defaultValue={props.properties.x.toString()} defaultValue={transformX(props.properties.x, props.properties.width, props.properties.XPositionReference).toString()}
/> />
<InputGroup <InputGroup
labelText='y' labelText='y'

View file

@ -18,13 +18,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
const xText = props.model.properties.width / 2; const xText = props.model.properties.width / 2;
const yText = props.model.properties.height / 2; const yText = props.model.properties.height / 2;
const [transformedX, transformedY] = transformPosition( const transform = `translate(${props.model.properties.x}, ${props.model.properties.y})`;
props.model.properties.x,
props.model.properties.y,
props.model.properties.width,
props.model.properties.XPositionReference
);
const transform = `translate(${transformedX}, ${transformedY})`;
// g style // g style
const defaultStyle: React.CSSProperties = { const defaultStyle: React.CSSProperties = {
@ -107,17 +101,17 @@ function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: numb
const childrenId = `dim-children-${props.model.properties.id}`; const childrenId = `dim-children-${props.model.properties.id}`;
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 = transformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.XPositionReference);
let xChildrenEnd = lastChild.properties.x; let xChildrenEnd = transformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.XPositionReference);
// Find the min and max // Find the min and max
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 = transformX(child.properties.x, child.properties.width, child.properties.XPositionReference);
if (left < xChildrenStart) { if (left < xChildrenStart) {
xChildrenStart = left; xChildrenStart = left;
} }
const right = child.properties.x; const right = transformX(child.properties.x, child.properties.width, child.properties.XPositionReference);
if (right > xChildrenEnd) { if (right > xChildrenEnd) {
xChildrenEnd = right; xChildrenEnd = right;
} }
@ -128,12 +122,22 @@ function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: numb
return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren }; 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; let transformedX = x;
if (xPositionReference === XPositionReference.Center) { if (xPositionReference === XPositionReference.Center) {
transformedX -= width / 2; transformedX -= width / 2;
} else if (xPositionReference === XPositionReference.Right) { } else if (xPositionReference === XPositionReference.Right) {
transformedX -= width; transformedX -= width;
} }
return [transformedX, y]; return transformedX;
} }

View file

@ -1,7 +1,6 @@
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
@ -16,12 +15,6 @@ 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
@ -38,8 +31,8 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
return ( return (
<rect <rect
x={transformedX} x={x}
y={transformedY} y={y}
width={width} width={width}
height={height} height={height}
style={style} style={style}