Merged PR 17: Implement rigid body Fix multiple bugs
Implement rigid body Fix saveload bug: having null elements Fix events being duplicated and not being removed
This commit is contained in:
parent
d2e1d9f0a4
commit
616fe3e9ac
22 changed files with 804 additions and 95 deletions
|
@ -17,6 +17,7 @@ module.exports = {
|
||||||
project: './tsconfig.json'
|
project: './tsconfig.json'
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
'only-warn',
|
||||||
'react',
|
'react',
|
||||||
'@typescript-eslint'
|
'@typescript-eslint'
|
||||||
],
|
],
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
[](https://drone.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react)
|
[](https://drone.siklos-chaneru.duckdns.org/Siklos/svg-layout-designer-react)
|
||||||
|
|
||||||
|
[](https://dev.azure.com/enguyen0660/SVGLayoutDesignerReact/_build/latest?definitionId=4&branchName=dev)
|
||||||
|
|
||||||
An svg layout designer.
|
An svg layout designer.
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
"eslint-config-standard-with-typescript": "^22.0.0",
|
"eslint-config-standard-with-typescript": "^22.0.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-n": "^15.2.4",
|
"eslint-plugin-n": "^15.2.4",
|
||||||
|
"eslint-plugin-only-warn": "^1.0.3",
|
||||||
"eslint-plugin-promise": "^6.0.0",
|
"eslint-plugin-promise": "^6.0.0",
|
||||||
"eslint-plugin-react": "^7.30.1",
|
"eslint-plugin-react": "^7.30.1",
|
||||||
"jsdom": "^20.0.0",
|
"jsdom": "^20.0.0",
|
||||||
|
|
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
|
@ -19,6 +19,7 @@ specifiers:
|
||||||
eslint-config-standard-with-typescript: ^22.0.0
|
eslint-config-standard-with-typescript: ^22.0.0
|
||||||
eslint-plugin-import: ^2.26.0
|
eslint-plugin-import: ^2.26.0
|
||||||
eslint-plugin-n: ^15.2.4
|
eslint-plugin-n: ^15.2.4
|
||||||
|
eslint-plugin-only-warn: ^1.0.3
|
||||||
eslint-plugin-promise: ^6.0.0
|
eslint-plugin-promise: ^6.0.0
|
||||||
eslint-plugin-react: ^7.30.1
|
eslint-plugin-react: ^7.30.1
|
||||||
framer-motion: ^6.5.1
|
framer-motion: ^6.5.1
|
||||||
|
@ -58,6 +59,7 @@ devDependencies:
|
||||||
eslint-config-standard-with-typescript: 22.0.0_mfupvx5msz6are6ggwiepter3m
|
eslint-config-standard-with-typescript: 22.0.0_mfupvx5msz6are6ggwiepter3m
|
||||||
eslint-plugin-import: 2.26.0_wuikv5nqgdfyng42xxm7lklfmi
|
eslint-plugin-import: 2.26.0_wuikv5nqgdfyng42xxm7lklfmi
|
||||||
eslint-plugin-n: 15.2.4_eslint@8.21.0
|
eslint-plugin-n: 15.2.4_eslint@8.21.0
|
||||||
|
eslint-plugin-only-warn: 1.0.3
|
||||||
eslint-plugin-promise: 6.0.0_eslint@8.21.0
|
eslint-plugin-promise: 6.0.0_eslint@8.21.0
|
||||||
eslint-plugin-react: 7.30.1_eslint@8.21.0
|
eslint-plugin-react: 7.30.1_eslint@8.21.0
|
||||||
jsdom: 20.0.0
|
jsdom: 20.0.0
|
||||||
|
@ -1724,6 +1726,11 @@ packages:
|
||||||
semver: 7.3.7
|
semver: 7.3.7
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/eslint-plugin-only-warn/1.0.3:
|
||||||
|
resolution: {integrity: sha512-XQOX/TfLoLw6h8ky51d29uUjXRTQHqBGXPylDEmy5fe/w7LIOnp8MA24b1OSMEn9BQoKow1q3g1kLe5/9uBTvw==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/eslint-plugin-promise/6.0.0_eslint@8.21.0:
|
/eslint-plugin-promise/6.0.0_eslint@8.21.0:
|
||||||
resolution: {integrity: sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==}
|
resolution: {integrity: sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
|
|
@ -23,6 +23,7 @@ export const App: React.FunctionComponent<IAppProps> = (props) => {
|
||||||
const [editorState, setEditorState] = useState<IEditorState>({
|
const [editorState, setEditorState] = useState<IEditorState>({
|
||||||
configuration: DEFAULT_CONFIG,
|
configuration: DEFAULT_CONFIG,
|
||||||
history: [{
|
history: [{
|
||||||
|
LastAction: '',
|
||||||
MainContainer: defaultMainContainer,
|
MainContainer: defaultMainContainer,
|
||||||
SelectedContainer: defaultMainContainer,
|
SelectedContainer: defaultMainContainer,
|
||||||
SelectedContainerId: defaultMainContainer.properties.id,
|
SelectedContainerId: defaultMainContainer.properties.id,
|
||||||
|
@ -40,6 +41,7 @@ export const App: React.FunctionComponent<IAppProps> = (props) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isLoaded) {
|
||||||
fetch(state)
|
fetch(state)
|
||||||
.then(
|
.then(
|
||||||
async(response) => await response.json(),
|
async(response) => await response.json(),
|
||||||
|
@ -48,6 +50,7 @@ export const App: React.FunctionComponent<IAppProps> = (props) => {
|
||||||
.then((data: IEditorState) => {
|
.then((data: IEditorState) => {
|
||||||
LoadState(data, setEditorState, setLoaded);
|
LoadState(data, setEditorState, setLoaded);
|
||||||
}, (error) => { throw new Error(error); });
|
}, (error) => { throw new Error(error); });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isLoaded) {
|
if (isLoaded) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ export function NewEditor(
|
||||||
y: 0,
|
y: 0,
|
||||||
width: configuration.MainContainer.Width,
|
width: configuration.MainContainer.Width,
|
||||||
height: configuration.MainContainer.Height,
|
height: configuration.MainContainer.Height,
|
||||||
|
isRigidBody: false,
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
stroke: 'black'
|
stroke: 'black'
|
||||||
}
|
}
|
||||||
|
@ -34,6 +35,7 @@ export function NewEditor(
|
||||||
history:
|
history:
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
LastAction: '',
|
||||||
MainContainer,
|
MainContainer,
|
||||||
SelectedContainer: MainContainer,
|
SelectedContainer: MainContainer,
|
||||||
SelectedContainerId: MainContainer.properties.id,
|
SelectedContainerId: MainContainer.properties.id,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
import { HistoryState } from "../../Interfaces/HistoryState";
|
import { HistoryState } from '../../Interfaces/HistoryState';
|
||||||
import { Configuration } from '../../Interfaces/Configuration';
|
import { Configuration } from '../../Interfaces/Configuration';
|
||||||
import { ContainerModel, IContainerModel } from '../../Interfaces/ContainerModel';
|
import { ContainerModel, IContainerModel } from '../../Interfaces/ContainerModel';
|
||||||
import { findContainerById } from '../../utils/itertools';
|
import { findContainerById } from '../../utils/itertools';
|
||||||
import { getCurrentHistory } from './Editor';
|
import { getCurrentHistory } from './Editor';
|
||||||
|
import { SizePointer } from '../../Interfaces/SizePointer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a container
|
* Select a container
|
||||||
|
@ -19,22 +20,19 @@ export function SelectContainer(
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
if (current.MainContainer === null) {
|
|
||||||
throw new Error('[SelectContainer] Tried to select a container while there is no main container!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainContainerClone = structuredClone(current.MainContainer);
|
const mainContainerClone = structuredClone(current.MainContainer);
|
||||||
const SelectedContainer = findContainerById(mainContainerClone, container.properties.id);
|
const selectedContainer = findContainerById(mainContainerClone, container.properties.id);
|
||||||
|
|
||||||
if (SelectedContainer === undefined) {
|
if (selectedContainer === undefined) {
|
||||||
throw new Error('[SelectContainer] Cannot find container among children of main container!');
|
throw new Error('[SelectContainer] Cannot find container among children of main container!');
|
||||||
}
|
}
|
||||||
|
|
||||||
setHistory(history.concat([{
|
setHistory(history.concat([{
|
||||||
|
LastAction: `Select container ${selectedContainer.properties.id}`,
|
||||||
MainContainer: mainContainerClone,
|
MainContainer: mainContainerClone,
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters),
|
SelectedContainer: selectedContainer,
|
||||||
SelectedContainer,
|
SelectedContainerId: selectedContainer.properties.id,
|
||||||
SelectedContainerId: SelectedContainer.properties.id
|
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||||
}]));
|
}]));
|
||||||
setHistoryCurrentStep(history.length);
|
setHistoryCurrentStep(history.length);
|
||||||
}
|
}
|
||||||
|
@ -49,10 +47,6 @@ export function DeleteContainer(
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[historyCurrentStep];
|
const current = history[historyCurrentStep];
|
||||||
|
|
||||||
if (current.MainContainer === null) {
|
|
||||||
throw new Error('[DeleteContainer] Error: Tried to delete a container without a main container');
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
||||||
const container = findContainerById(mainContainerClone, containerId);
|
const container = findContainerById(mainContainerClone, containerId);
|
||||||
|
|
||||||
|
@ -62,7 +56,7 @@ export function DeleteContainer(
|
||||||
|
|
||||||
if (container === mainContainerClone) {
|
if (container === mainContainerClone) {
|
||||||
// TODO: Implement alert
|
// TODO: Implement alert
|
||||||
throw new Error('[DeleteContainer] Tried to delete the main container! Deleting the main container is not allowed !');
|
throw new Error('[DeleteContainer] Tried to delete the main container! Deleting the main container is not allowed!');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container === null || container === undefined) {
|
if (container === null || container === undefined) {
|
||||||
|
@ -77,9 +71,10 @@ export function DeleteContainer(
|
||||||
}
|
}
|
||||||
|
|
||||||
setHistory(history.concat([{
|
setHistory(history.concat([{
|
||||||
|
LastAction: `Delete container ${containerId}`,
|
||||||
|
MainContainer: mainContainerClone,
|
||||||
SelectedContainer: null,
|
SelectedContainer: null,
|
||||||
SelectedContainerId: '',
|
SelectedContainerId: '',
|
||||||
MainContainer: mainContainerClone,
|
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||||
}]));
|
}]));
|
||||||
setHistoryCurrentStep(history.length);
|
setHistoryCurrentStep(history.length);
|
||||||
|
@ -168,7 +163,7 @@ export function AddContainer(
|
||||||
}
|
}
|
||||||
|
|
||||||
let x = 0;
|
let x = 0;
|
||||||
if (index !== 0) {
|
if (index > 0) {
|
||||||
const lastChild: IContainerModel | undefined = parentClone.children.at(index - 1);
|
const lastChild: IContainerModel | undefined = parentClone.children.at(index - 1);
|
||||||
if (lastChild !== undefined) {
|
if (lastChild !== undefined) {
|
||||||
x = lastChild.properties.x + Number(lastChild.properties.width);
|
x = lastChild.properties.x + Number(lastChild.properties.width);
|
||||||
|
@ -185,6 +180,7 @@ export function AddContainer(
|
||||||
y: 0,
|
y: 0,
|
||||||
width: properties?.Width,
|
width: properties?.Width,
|
||||||
height: parentClone.properties.height,
|
height: parentClone.properties.height,
|
||||||
|
isRigidBody: false,
|
||||||
...properties.Style
|
...properties.Style
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
|
@ -202,10 +198,11 @@ export function AddContainer(
|
||||||
|
|
||||||
// Update the state
|
// Update the state
|
||||||
setHistory(history.concat([{
|
setHistory(history.concat([{
|
||||||
|
LastAction: 'Add container',
|
||||||
MainContainer: clone,
|
MainContainer: clone,
|
||||||
TypeCounters: newCounters,
|
|
||||||
SelectedContainer: parentClone,
|
SelectedContainer: parentClone,
|
||||||
SelectedContainerId: parentClone.properties.id
|
SelectedContainerId: parentClone.properties.id,
|
||||||
|
TypeCounters: newCounters
|
||||||
}]));
|
}]));
|
||||||
setHistoryCurrentStep(history.length);
|
setHistoryCurrentStep(history.length);
|
||||||
}
|
}
|
||||||
|
@ -218,7 +215,7 @@ export function AddContainer(
|
||||||
*/
|
*/
|
||||||
export function OnPropertyChange(
|
export function OnPropertyChange(
|
||||||
key: string,
|
key: string,
|
||||||
value: string | number,
|
value: string | number | boolean,
|
||||||
fullHistory: HistoryState[],
|
fullHistory: HistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
setHistory: Dispatch<SetStateAction<HistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<HistoryState[]>>,
|
||||||
|
@ -232,18 +229,14 @@ export function OnPropertyChange(
|
||||||
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current.MainContainer === null ||
|
|
||||||
current.MainContainer === undefined) {
|
|
||||||
throw new Error('[OnPropertyChange] Property was changed before the main container was added');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent === null) {
|
if (parent === null) {
|
||||||
const selectedContainerClone: IContainerModel = structuredClone(current.SelectedContainer);
|
const selectedContainerClone: IContainerModel = structuredClone(current.SelectedContainer);
|
||||||
(selectedContainerClone.properties as any)[key] = value;
|
(selectedContainerClone.properties as any)[key] = value;
|
||||||
setHistory(history.concat([{
|
setHistory(history.concat([{
|
||||||
|
LastAction: 'Change property of main',
|
||||||
|
MainContainer: selectedContainerClone,
|
||||||
SelectedContainer: selectedContainerClone,
|
SelectedContainer: selectedContainerClone,
|
||||||
SelectedContainerId: selectedContainerClone.properties.id,
|
SelectedContainerId: selectedContainerClone.properties.id,
|
||||||
MainContainer: selectedContainerClone,
|
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||||
}]));
|
}]));
|
||||||
setHistoryCurrentStep(history.length);
|
setHistoryCurrentStep(history.length);
|
||||||
|
@ -259,11 +252,223 @@ export function OnPropertyChange(
|
||||||
|
|
||||||
(container.properties as any)[key] = value;
|
(container.properties as any)[key] = value;
|
||||||
|
|
||||||
|
if (container.properties.isRigidBody) {
|
||||||
|
RecalculatePhysics(container);
|
||||||
|
}
|
||||||
|
|
||||||
setHistory(history.concat([{
|
setHistory(history.concat([{
|
||||||
|
LastAction: `Change property of container ${container.properties.id}`,
|
||||||
|
MainContainer: mainContainerClone,
|
||||||
SelectedContainer: container,
|
SelectedContainer: container,
|
||||||
SelectedContainerId: container.properties.id,
|
SelectedContainerId: container.properties.id,
|
||||||
MainContainer: mainContainerClone,
|
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||||
}]));
|
}]));
|
||||||
setHistoryCurrentStep(history.length);
|
setHistoryCurrentStep(history.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO put this in a different file
|
||||||
|
|
||||||
|
export function RecalculatePhysics(container: IContainerModel): IContainerModel {
|
||||||
|
container = constraintBodyInsideParent(container);
|
||||||
|
container = constraintBodyInsideUnallocatedWidth(container);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limit a rect inside a parent rect by applying the following rules :
|
||||||
|
* it cannot be bigger than the parent
|
||||||
|
* it cannot go out of bound
|
||||||
|
* @param container
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function constraintBodyInsideParent(container: IContainerModel): IContainerModel {
|
||||||
|
if (container.parent === null || container.parent === undefined) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentProperties = container.parent.properties;
|
||||||
|
const parentWidth = Number(parentProperties.width);
|
||||||
|
const parentHeight = Number(parentProperties.height);
|
||||||
|
|
||||||
|
return constraintBodyInsideSpace(container, 0, 0, parentWidth, parentHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
function constraintBodyInsideSpace(
|
||||||
|
container: IContainerModel,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Check size bigger than parent
|
||||||
|
const isBodyLargerThanParent = containerWidth > width;
|
||||||
|
const isBodyTallerThanParentHeight = containerHeight > height;
|
||||||
|
if (isBodyLargerThanParent || isBodyTallerThanParentHeight) {
|
||||||
|
if (isBodyLargerThanParent) {
|
||||||
|
containerProperties.x = x;
|
||||||
|
containerProperties.width = width;
|
||||||
|
}
|
||||||
|
if (isBodyTallerThanParentHeight) {
|
||||||
|
containerProperties.y = y;
|
||||||
|
containerProperties.height = height;
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check horizontal out of bound
|
||||||
|
if (containerX < x) {
|
||||||
|
containerProperties.x = x;
|
||||||
|
}
|
||||||
|
if (containerX + containerWidth > width) {
|
||||||
|
containerProperties.x = x + width - containerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check vertical out of bound
|
||||||
|
if (containerY < y) {
|
||||||
|
containerProperties.y = y;
|
||||||
|
}
|
||||||
|
if (containerY + containerHeight > height) {
|
||||||
|
containerProperties.y = y + height - containerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unallocated widths inside a container
|
||||||
|
* An allocated width is defined by its the widths of the children that are rigid bodies.
|
||||||
|
* An example of this allocation system is the disk space
|
||||||
|
* (except the fact that disk space is divided by block).
|
||||||
|
* @param container
|
||||||
|
* @returns {SizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
|
||||||
|
*/
|
||||||
|
function getAvailableWidths(container: IContainerModel, exception: IContainerModel): SizePointer[] {
|
||||||
|
const x = 0;
|
||||||
|
const width = Number(container.properties.width);
|
||||||
|
let unallocatedSpaces: SizePointer[] = [{ x, width }];
|
||||||
|
|
||||||
|
const rigidBodies = container.children.filter(child => child.properties.isRigidBody);
|
||||||
|
for (const child of rigidBodies) {
|
||||||
|
if (child === exception) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the space of the child that is inside the parent
|
||||||
|
let newUnallocatedSpace: SizePointer[] = [];
|
||||||
|
for (const unallocatedSpace of unallocatedSpaces) {
|
||||||
|
const newUnallocatedWidths = getAvailableWidthsTwoLines(
|
||||||
|
unallocatedSpace.x,
|
||||||
|
unallocatedSpace.x + unallocatedSpace.width,
|
||||||
|
child.properties.x,
|
||||||
|
child.properties.x + Number(child.properties.width));
|
||||||
|
newUnallocatedSpace = newUnallocatedSpace.concat(newUnallocatedWidths);
|
||||||
|
}
|
||||||
|
unallocatedSpaces = newUnallocatedSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return unallocatedSpaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the unallocated widths between two lines in 1D
|
||||||
|
* @param min1 left of the first line
|
||||||
|
* @param max1 rigth of the first line
|
||||||
|
* @param min2 left of the second line
|
||||||
|
* @param max2 right of the second line
|
||||||
|
* @returns Available widths
|
||||||
|
*/
|
||||||
|
function getAvailableWidthsTwoLines(min1: number, max1: number, min2: number, max2: number): SizePointer[] {
|
||||||
|
if (min2 < min1 && max2 > max1) {
|
||||||
|
// object 2 is overlapping full width
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min1 >= min2) {
|
||||||
|
// object 2 is partially overlapping on the left
|
||||||
|
return [{
|
||||||
|
x: max2,
|
||||||
|
width: max1 - max2
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max2 >= max1) {
|
||||||
|
// object 2 is partially overlapping on the right
|
||||||
|
return [{
|
||||||
|
x: min2,
|
||||||
|
width: max2 - min1
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// object 2 is overlapping in the middle
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
x: min1,
|
||||||
|
width: min2 - min1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: min2,
|
||||||
|
width: max1 - max2
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param container
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function constraintBodyInsideUnallocatedWidth(container: IContainerModel): IContainerModel {
|
||||||
|
if (container.parent === null) {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableWidths = getAvailableWidths(container.parent, container);
|
||||||
|
const containerX = Number(container.properties.x);
|
||||||
|
|
||||||
|
// Sort the available width
|
||||||
|
availableWidths
|
||||||
|
.sort((width1, width2) => Math.abs(width1.x - containerX) - Math.abs(width2.x - containerX));
|
||||||
|
|
||||||
|
if (availableWidths.length === 0) {
|
||||||
|
throw new Error('No available space found on the parent container. Try to free the parent a little before placing it inside.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableWidthFound = availableWidths.find(
|
||||||
|
width => isFitting(container, width)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (availableWidthFound === undefined) {
|
||||||
|
// There is two way to reach this part of the code
|
||||||
|
// 1) toggle the isRigidBody such as width > availableWidth.width
|
||||||
|
// 2) resize a container such as width > availableWidth.width
|
||||||
|
// We want the container to fit automatically inside the available space
|
||||||
|
// even if it means to resize the container
|
||||||
|
// The end goal is that the code never show the error message no matter what action is done
|
||||||
|
// TODO: Actually give an option to not fit and show the error message shown below
|
||||||
|
const availableWidth = availableWidths[0];
|
||||||
|
container.properties.x = availableWidth.x;
|
||||||
|
container.properties.width = availableWidth.width;
|
||||||
|
// throw new Error('[constraintBodyInsideUnallocatedWidth] BIGERR: No available space found on the parent container, even though there is some.');
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
return constraintBodyInsideSpace(
|
||||||
|
container,
|
||||||
|
availableWidthFound.x,
|
||||||
|
0,
|
||||||
|
availableWidthFound.width,
|
||||||
|
Number(container.parent.properties.height)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFitting(container: IContainerModel, sizePointer: SizePointer): boolean {
|
||||||
|
const containerWidth = Number(container.properties.width);
|
||||||
|
|
||||||
|
return containerWidth <= sizePointer.width;
|
||||||
|
}
|
||||||
|
|
|
@ -24,24 +24,21 @@ export const getCurrentHistory = (history: HistoryState[], historyCurrentStep: n
|
||||||
export const getCurrentHistoryState = (history: HistoryState[], historyCurrentStep: number): HistoryState => history[historyCurrentStep];
|
export const getCurrentHistoryState = (history: HistoryState[], historyCurrentStep: number): HistoryState => history[historyCurrentStep];
|
||||||
|
|
||||||
const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
const [history, setHistory] = React.useState<HistoryState[]>([...props.history]);
|
const [history, setHistory] = React.useState<HistoryState[]>(structuredClone(props.history));
|
||||||
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(0);
|
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.addEventListener('keyup', (event) => onKeyDown(
|
const onKeyUp = (event: KeyboardEvent): void => onKeyDown(
|
||||||
event,
|
event,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
));
|
);
|
||||||
|
|
||||||
|
window.addEventListener('keyup', onKeyUp);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keyup', (event) => onKeyDown(
|
window.removeEventListener('keyup', onKeyUp);
|
||||||
event,
|
|
||||||
history,
|
|
||||||
historyCurrentStep,
|
|
||||||
setHistoryCurrentStep
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
import { HistoryState } from "../../Interfaces/HistoryState";
|
import { HistoryState } from '../../Interfaces/HistoryState';
|
||||||
|
|
||||||
export function onKeyDown(
|
export function onKeyDown(
|
||||||
event: KeyboardEvent,
|
event: KeyboardEvent,
|
||||||
|
|
|
@ -16,14 +16,15 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100
|
height: 100,
|
||||||
|
isRigidBody: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
}}
|
}}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={null}
|
SelectedContainer={null}
|
||||||
onPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
SelectContainer={() => {}}
|
SelectContainer={() => {}}
|
||||||
DeleteContainer={() => {}}
|
DeleteContainer={() => {}}
|
||||||
AddContainer={() => {}}
|
AddContainer={() => {}}
|
||||||
|
@ -44,7 +45,8 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100
|
height: 100,
|
||||||
|
isRigidBody: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
};
|
};
|
||||||
|
@ -54,7 +56,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={MainContainer}
|
SelectedContainer={MainContainer}
|
||||||
onPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
SelectContainer={() => {}}
|
SelectContainer={() => {}}
|
||||||
DeleteContainer={() => {}}
|
DeleteContainer={() => {}}
|
||||||
AddContainer={() => {}}
|
AddContainer={() => {}}
|
||||||
|
@ -98,7 +100,8 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100
|
height: 100,
|
||||||
|
isRigidBody: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
};
|
};
|
||||||
|
@ -113,7 +116,8 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0
|
height: 0,
|
||||||
|
isRigidBody: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
}
|
}
|
||||||
|
@ -129,7 +133,8 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0
|
height: 0,
|
||||||
|
isRigidBody: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +145,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={MainContainer}
|
SelectedContainer={MainContainer}
|
||||||
onPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
SelectContainer={() => {}}
|
SelectContainer={() => {}}
|
||||||
DeleteContainer={() => {}}
|
DeleteContainer={() => {}}
|
||||||
AddContainer={() => {}}
|
AddContainer={() => {}}
|
||||||
|
@ -164,7 +169,8 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100
|
height: 100,
|
||||||
|
isRigidBody: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
};
|
};
|
||||||
|
@ -178,7 +184,8 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0
|
height: 0,
|
||||||
|
isRigidBody: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
};
|
};
|
||||||
|
@ -194,7 +201,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={SelectedContainer}
|
SelectedContainer={SelectedContainer}
|
||||||
onPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
SelectContainer={selectContainer}
|
SelectContainer={selectContainer}
|
||||||
DeleteContainer={() => {}}
|
DeleteContainer={() => {}}
|
||||||
AddContainer={() => {}}
|
AddContainer={() => {}}
|
||||||
|
@ -217,7 +224,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={SelectedContainer}
|
SelectedContainer={SelectedContainer}
|
||||||
onPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
SelectContainer={selectContainer}
|
SelectContainer={selectContainer}
|
||||||
DeleteContainer={() => {}}
|
DeleteContainer={() => {}}
|
||||||
AddContainer={() => {}}
|
AddContainer={() => {}}
|
||||||
|
|
|
@ -13,7 +13,7 @@ interface IElementsSidebarProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
isHistoryOpen: boolean
|
isHistoryOpen: boolean
|
||||||
SelectedContainer: IContainerModel | null
|
SelectedContainer: IContainerModel | null
|
||||||
onPropertyChange: (key: string, value: string) => void
|
OnPropertyChange: (key: string, value: string | number | boolean) => void
|
||||||
SelectContainer: (container: IContainerModel) => void
|
SelectContainer: (container: IContainerModel) => void
|
||||||
DeleteContainer: (containerid: string) => void
|
DeleteContainer: (containerid: string) => void
|
||||||
AddContainer: (index: number, type: string, parent: string) => void
|
AddContainer: (index: number, type: string, parent: string) => void
|
||||||
|
@ -71,40 +71,39 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
||||||
|
|
||||||
// Event listeners
|
// Event listeners
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
elementRef.current?.addEventListener(
|
const onContextMenu = (event: MouseEvent): void => handleRightClick(
|
||||||
'contextmenu',
|
|
||||||
(event) => handleRightClick(
|
|
||||||
event,
|
event,
|
||||||
setIsContextMenuOpen,
|
setIsContextMenuOpen,
|
||||||
setOnClickContainerId,
|
setOnClickContainerId,
|
||||||
setContextMenuPosition
|
setContextMenuPosition
|
||||||
));
|
);
|
||||||
|
|
||||||
|
const onLeftClick = (): void => handleLeftClick(
|
||||||
|
isContextMenuOpen,
|
||||||
|
setIsContextMenuOpen,
|
||||||
|
setOnClickContainerId
|
||||||
|
);
|
||||||
|
|
||||||
|
elementRef.current?.addEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
onContextMenu
|
||||||
|
);
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
'click',
|
'click',
|
||||||
(event) => handleLeftClick(
|
onLeftClick
|
||||||
isContextMenuOpen,
|
);
|
||||||
setIsContextMenuOpen,
|
|
||||||
setOnClickContainerId
|
|
||||||
));
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
elementRef.current?.addEventListener(
|
elementRef.current?.removeEventListener(
|
||||||
'contextmenu',
|
'contextmenu',
|
||||||
(event) => handleRightClick(
|
onContextMenu
|
||||||
event,
|
);
|
||||||
setIsContextMenuOpen,
|
|
||||||
setOnClickContainerId,
|
|
||||||
setContextMenuPosition
|
|
||||||
));
|
|
||||||
|
|
||||||
window.removeEventListener(
|
window.removeEventListener(
|
||||||
'click',
|
'click',
|
||||||
(event) => handleLeftClick(
|
onLeftClick
|
||||||
isContextMenuOpen,
|
);
|
||||||
setIsContextMenuOpen,
|
|
||||||
setOnClickContainerId
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -141,9 +140,12 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
||||||
y={contextMenuPosition.y}
|
y={contextMenuPosition.y}
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
>
|
>
|
||||||
<MenuItem className='contextmenu-item' text='Delete' onClick={() => props.DeleteContainer(onClickContainerId)} />
|
<MenuItem className='contextmenu-item' text='Delete' onClick={() => {
|
||||||
|
setIsContextMenuOpen(false);
|
||||||
|
props.DeleteContainer(onClickContainerId);
|
||||||
|
}} />
|
||||||
</Menu>
|
</Menu>
|
||||||
<Properties properties={props.SelectedContainer?.properties} onChange={props.onPropertyChange}></Properties>
|
<Properties properties={props.SelectedContainer?.properties} onChange={props.OnPropertyChange}></Properties>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,8 @@ describe.concurrent('Properties', () => {
|
||||||
id: 'stuff',
|
id: 'stuff',
|
||||||
parentId: 'parentId',
|
parentId: 'parentId',
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1
|
y: 1,
|
||||||
|
isRigidBody: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = vi.fn((key, value) => {
|
const handleChange = vi.fn((key, value) => {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import ContainerProperties from '../../Interfaces/Properties';
|
import ContainerProperties from '../../Interfaces/Properties';
|
||||||
|
import { INPUT_TYPES } from './PropertiesInputTypes';
|
||||||
|
|
||||||
interface IPropertiesProps {
|
interface IPropertiesProps {
|
||||||
properties?: ContainerProperties
|
properties?: ContainerProperties
|
||||||
onChange: (key: string, value: string) => void
|
onChange: (key: string, value: string | number | boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps) => {
|
export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps) => {
|
||||||
|
@ -26,11 +27,24 @@ export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps)
|
||||||
const handleProperties = (
|
const handleProperties = (
|
||||||
[key, value]: [string, string | number],
|
[key, value]: [string, string | number],
|
||||||
groupInput: React.ReactNode[],
|
groupInput: React.ReactNode[],
|
||||||
onChange: (key: string, value: string) => void
|
onChange: (key: string, value: string | number | boolean) => void
|
||||||
): void => {
|
): void => {
|
||||||
const id = `property-${key}`;
|
const id = `property-${key}`;
|
||||||
const type = 'text';
|
let type = 'text';
|
||||||
const isDisabled = key === 'id' || key === 'parentId'; // hardcoded
|
let checked;
|
||||||
|
|
||||||
|
/// hardcoded stuff for ergonomy ///
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
checked = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key in INPUT_TYPES) {
|
||||||
|
type = INPUT_TYPES[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDisabled = ['id', 'parentId'].includes(key);
|
||||||
|
///
|
||||||
|
|
||||||
groupInput.push(
|
groupInput.push(
|
||||||
<div key={id} className='mt-4'>
|
<div key={id} className='mt-4'>
|
||||||
<label className='text-sm font-medium text-gray-800' htmlFor={id}>{key}</label>
|
<label className='text-sm font-medium text-gray-800' htmlFor={id}>{key}</label>
|
||||||
|
@ -43,7 +57,14 @@ const handleProperties = (
|
||||||
type={type}
|
type={type}
|
||||||
id={id}
|
id={id}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(event) => onChange(key, event.target.value)}
|
checked={checked}
|
||||||
|
onChange={(event) => {
|
||||||
|
if (type === 'checkbox') {
|
||||||
|
onChange(key, event.target.checked);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChange(key, event.target.value);
|
||||||
|
}}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
7
src/Components/Properties/PropertiesInputTypes.tsx
Normal file
7
src/Components/Properties/PropertiesInputTypes.tsx
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export const INPUT_TYPES: Record<string, string> = {
|
||||||
|
x: 'number',
|
||||||
|
y: 'number',
|
||||||
|
width: 'number',
|
||||||
|
height: 'number',
|
||||||
|
isRigidBody: 'checkbox'
|
||||||
|
};
|
|
@ -35,10 +35,11 @@ export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.addEventListener('resize', () => resizeViewBox(setViewer));
|
const onResize = (): void => resizeViewBox(setViewer);
|
||||||
|
window.addEventListener('resize', onResize);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.addEventListener('resize', () => resizeViewBox(setViewer));
|
window.removeEventListener('resize', onResize);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Sidebar } from '../Sidebar/Sidebar';
|
||||||
import { History } from '../History/History';
|
import { History } from '../History/History';
|
||||||
import { AvailableContainer } from '../../Interfaces/AvailableContainer';
|
import { AvailableContainer } from '../../Interfaces/AvailableContainer';
|
||||||
import { ContainerModel } from '../../Interfaces/ContainerModel';
|
import { ContainerModel } from '../../Interfaces/ContainerModel';
|
||||||
import { HistoryState } from "../../Interfaces/HistoryState";
|
import { HistoryState } from '../../Interfaces/HistoryState';
|
||||||
import { PhotographIcon, UploadIcon } from '@heroicons/react/outline';
|
import { PhotographIcon, UploadIcon } from '@heroicons/react/outline';
|
||||||
import { FloatingButton } from '../FloatingButton/FloatingButton';
|
import { FloatingButton } from '../FloatingButton/FloatingButton';
|
||||||
import { Bar } from '../Bar/Bar';
|
import { Bar } from '../Bar/Bar';
|
||||||
|
@ -16,7 +16,7 @@ interface IUIProps {
|
||||||
AvailableContainers: AvailableContainer[]
|
AvailableContainers: AvailableContainer[]
|
||||||
SelectContainer: (container: ContainerModel) => void
|
SelectContainer: (container: ContainerModel) => void
|
||||||
DeleteContainer: (containerId: string) => void
|
DeleteContainer: (containerId: string) => void
|
||||||
OnPropertyChange: (key: string, value: string) => void
|
OnPropertyChange: (key: string, value: string | number | boolean) => void
|
||||||
AddContainerToSelectedContainer: (type: string) => void
|
AddContainerToSelectedContainer: (type: string) => void
|
||||||
AddContainer: (index: number, type: string, parentId: string) => void
|
AddContainer: (index: number, type: string, parentId: string) => void
|
||||||
SaveEditorAsJSON: () => void
|
SaveEditorAsJSON: () => void
|
||||||
|
@ -58,7 +58,7 @@ export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
||||||
SelectedContainer={props.current.SelectedContainer}
|
SelectedContainer={props.current.SelectedContainer}
|
||||||
isOpen={isElementsSidebarOpen}
|
isOpen={isElementsSidebarOpen}
|
||||||
isHistoryOpen={isHistoryOpen}
|
isHistoryOpen={isHistoryOpen}
|
||||||
onPropertyChange={props.OnPropertyChange}
|
OnPropertyChange={props.OnPropertyChange}
|
||||||
SelectContainer={props.SelectContainer}
|
SelectContainer={props.SelectContainer}
|
||||||
DeleteContainer={props.DeleteContainer}
|
DeleteContainer={props.DeleteContainer}
|
||||||
AddContainer={props.AddContainer}
|
AddContainer={props.AddContainer}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { IContainerModel } from './ContainerModel';
|
import { IContainerModel } from './ContainerModel';
|
||||||
|
|
||||||
export interface HistoryState {
|
export interface HistoryState {
|
||||||
|
LastAction: string
|
||||||
MainContainer: IContainerModel
|
MainContainer: IContainerModel
|
||||||
SelectedContainer: IContainerModel | null
|
SelectedContainer: IContainerModel | null
|
||||||
SelectedContainerId: string
|
SelectedContainerId: string
|
||||||
|
|
|
@ -5,4 +5,5 @@ export default interface Properties extends React.CSSProperties {
|
||||||
parentId: string | null
|
parentId: string | null
|
||||||
x: number
|
x: number
|
||||||
y: number
|
y: number
|
||||||
|
isRigidBody: boolean
|
||||||
}
|
}
|
||||||
|
|
4
src/Interfaces/SizePointer.ts
Normal file
4
src/Interfaces/SizePointer.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface SizePointer {
|
||||||
|
x: number
|
||||||
|
width: number
|
||||||
|
}
|
436
src/tests/resources/rigidbodytest.json
Normal file
436
src/tests/resources/rigidbodytest.json
Normal file
|
@ -0,0 +1,436 @@
|
||||||
|
{
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "main",
|
||||||
|
"TypeCounters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LastAction": "Add container",
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "Container-0",
|
||||||
|
"parentId": "main",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 75,
|
||||||
|
"height": 100,
|
||||||
|
"isRigidBody": false,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
},
|
||||||
|
"userData": {
|
||||||
|
"type": "Container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "main",
|
||||||
|
"TypeCounters": {
|
||||||
|
"Container": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LastAction": "Select container Container-0",
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "Container-0",
|
||||||
|
"parentId": "main",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 75,
|
||||||
|
"height": 100,
|
||||||
|
"isRigidBody": false,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
},
|
||||||
|
"userData": {
|
||||||
|
"type": "Container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "Container-0",
|
||||||
|
"TypeCounters": {
|
||||||
|
"Container": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LastAction": "Change property of container Container-0",
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "Container-0",
|
||||||
|
"parentId": "main",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": "7",
|
||||||
|
"height": 100,
|
||||||
|
"isRigidBody": false,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
},
|
||||||
|
"userData": {
|
||||||
|
"type": "Container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "Container-0",
|
||||||
|
"TypeCounters": {
|
||||||
|
"Container": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LastAction": "Change property of container Container-0",
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "Container-0",
|
||||||
|
"parentId": "main",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": "",
|
||||||
|
"height": 100,
|
||||||
|
"isRigidBody": false,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
},
|
||||||
|
"userData": {
|
||||||
|
"type": "Container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "Container-0",
|
||||||
|
"TypeCounters": {
|
||||||
|
"Container": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LastAction": "Change property of container Container-0",
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "Container-0",
|
||||||
|
"parentId": "main",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": "2",
|
||||||
|
"height": 100,
|
||||||
|
"isRigidBody": false,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
},
|
||||||
|
"userData": {
|
||||||
|
"type": "Container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "Container-0",
|
||||||
|
"TypeCounters": {
|
||||||
|
"Container": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LastAction": "Change property of container Container-0",
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "Container-0",
|
||||||
|
"parentId": "main",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": "20",
|
||||||
|
"height": 100,
|
||||||
|
"isRigidBody": false,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
},
|
||||||
|
"userData": {
|
||||||
|
"type": "Container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "Container-0",
|
||||||
|
"TypeCounters": {
|
||||||
|
"Container": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LastAction": "Change property of container Container-0",
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "Container-0",
|
||||||
|
"parentId": "main",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": "200",
|
||||||
|
"height": 100,
|
||||||
|
"isRigidBody": false,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
},
|
||||||
|
"userData": {
|
||||||
|
"type": "Container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "Container-0",
|
||||||
|
"TypeCounters": {
|
||||||
|
"Container": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LastAction": "Change property of container Container-0",
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "Container-0",
|
||||||
|
"parentId": "main",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": "2000",
|
||||||
|
"height": 100,
|
||||||
|
"isRigidBody": false,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
},
|
||||||
|
"userData": {
|
||||||
|
"type": "Container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "Container-0",
|
||||||
|
"TypeCounters": {
|
||||||
|
"Container": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LastAction": "Change property of container Container-0",
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "Container-0",
|
||||||
|
"parentId": "main",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": "20000",
|
||||||
|
"height": 100,
|
||||||
|
"isRigidBody": false,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
},
|
||||||
|
"userData": {
|
||||||
|
"type": "Container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "Container-0",
|
||||||
|
"TypeCounters": {
|
||||||
|
"Container": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LastAction": "Change property of container Container-0",
|
||||||
|
"MainContainer": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [],
|
||||||
|
"properties": {
|
||||||
|
"id": "Container-0",
|
||||||
|
"parentId": "main",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"isRigidBody": true,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
},
|
||||||
|
"userData": {
|
||||||
|
"type": "Container"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": "main",
|
||||||
|
"parentId": "null",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 100,
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
},
|
||||||
|
"userData": {}
|
||||||
|
},
|
||||||
|
"SelectedContainerId": "Container-0",
|
||||||
|
"TypeCounters": {
|
||||||
|
"Container": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"historyCurrentStep": 10,
|
||||||
|
"configuration": {
|
||||||
|
"AvailableContainers": [
|
||||||
|
{
|
||||||
|
"Type": "Container",
|
||||||
|
"Width": 75,
|
||||||
|
"Height": 100,
|
||||||
|
"Style": {
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "green"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AvailableSymbols": [],
|
||||||
|
"MainContainer": {
|
||||||
|
"Type": "Container",
|
||||||
|
"Width": 2000,
|
||||||
|
"Height": 100,
|
||||||
|
"Style": {
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"stroke": "black"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ export const DEFAULT_MAINCONTAINER_PROPS: Properties = {
|
||||||
parentId: 'null',
|
parentId: 'null',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
isRigidBody: false,
|
||||||
width: DEFAULT_CONFIG.MainContainer.Width,
|
width: DEFAULT_CONFIG.MainContainer.Width,
|
||||||
height: DEFAULT_CONFIG.MainContainer.Height,
|
height: DEFAULT_CONFIG.MainContainer.Height,
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
|
|
|
@ -8,6 +8,11 @@ import { IEditorState } from '../Components/Editor/Editor';
|
||||||
*/
|
*/
|
||||||
export function Revive(editorState: IEditorState): void {
|
export function Revive(editorState: IEditorState): void {
|
||||||
const history = editorState.history;
|
const history = editorState.history;
|
||||||
|
|
||||||
|
// restore last step
|
||||||
|
editorState.historyCurrentStep = history.length - 1;
|
||||||
|
|
||||||
|
// restore the parents and the selected container
|
||||||
for (const state of history) {
|
for (const state of history) {
|
||||||
if (state.MainContainer === null || state.MainContainer === undefined) {
|
if (state.MainContainer === null || state.MainContainer === undefined) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -43,6 +48,10 @@ export const getCircularReplacer = (): (key: any, value: object | null) => objec
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key === 'SelectedContainer') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof value === 'object' && value !== null) {
|
if (typeof value === 'object' && value !== null) {
|
||||||
if (seen.has(value)) {
|
if (seen.has(value)) {
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue