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'
|
||||
},
|
||||
plugins: [
|
||||
'only-warn',
|
||||
'react',
|
||||
'@typescript-eslint'
|
||||
],
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
[](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.
|
||||
|
||||
# Getting Started
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
"eslint-config-standard-with-typescript": "^22.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.2.4",
|
||||
"eslint-plugin-only-warn": "^1.0.3",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"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-plugin-import: ^2.26.0
|
||||
eslint-plugin-n: ^15.2.4
|
||||
eslint-plugin-only-warn: ^1.0.3
|
||||
eslint-plugin-promise: ^6.0.0
|
||||
eslint-plugin-react: ^7.30.1
|
||||
framer-motion: ^6.5.1
|
||||
|
@ -58,6 +59,7 @@ devDependencies:
|
|||
eslint-config-standard-with-typescript: 22.0.0_mfupvx5msz6are6ggwiepter3m
|
||||
eslint-plugin-import: 2.26.0_wuikv5nqgdfyng42xxm7lklfmi
|
||||
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-react: 7.30.1_eslint@8.21.0
|
||||
jsdom: 20.0.0
|
||||
|
@ -1724,6 +1726,11 @@ packages:
|
|||
semver: 7.3.7
|
||||
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:
|
||||
resolution: {integrity: sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==}
|
||||
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>({
|
||||
configuration: DEFAULT_CONFIG,
|
||||
history: [{
|
||||
LastAction: '',
|
||||
MainContainer: defaultMainContainer,
|
||||
SelectedContainer: defaultMainContainer,
|
||||
SelectedContainerId: defaultMainContainer.properties.id,
|
||||
|
@ -40,6 +41,7 @@ export const App: React.FunctionComponent<IAppProps> = (props) => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!isLoaded) {
|
||||
fetch(state)
|
||||
.then(
|
||||
async(response) => await response.json(),
|
||||
|
@ -48,6 +50,7 @@ export const App: React.FunctionComponent<IAppProps> = (props) => {
|
|||
.then((data: IEditorState) => {
|
||||
LoadState(data, setEditorState, setLoaded);
|
||||
}, (error) => { throw new Error(error); });
|
||||
}
|
||||
});
|
||||
|
||||
if (isLoaded) {
|
||||
|
|
|
@ -22,6 +22,7 @@ export function NewEditor(
|
|||
y: 0,
|
||||
width: configuration.MainContainer.Width,
|
||||
height: configuration.MainContainer.Height,
|
||||
isRigidBody: false,
|
||||
fillOpacity: 0,
|
||||
stroke: 'black'
|
||||
}
|
||||
|
@ -34,6 +35,7 @@ export function NewEditor(
|
|||
history:
|
||||
[
|
||||
{
|
||||
LastAction: '',
|
||||
MainContainer,
|
||||
SelectedContainer: MainContainer,
|
||||
SelectedContainerId: MainContainer.properties.id,
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { HistoryState } from "../../Interfaces/HistoryState";
|
||||
import { HistoryState } from '../../Interfaces/HistoryState';
|
||||
import { Configuration } from '../../Interfaces/Configuration';
|
||||
import { ContainerModel, IContainerModel } from '../../Interfaces/ContainerModel';
|
||||
import { findContainerById } from '../../utils/itertools';
|
||||
import { getCurrentHistory } from './Editor';
|
||||
import { SizePointer } from '../../Interfaces/SizePointer';
|
||||
|
||||
/**
|
||||
* Select a container
|
||||
|
@ -19,22 +20,19 @@ export function SelectContainer(
|
|||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||
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 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!');
|
||||
}
|
||||
|
||||
setHistory(history.concat([{
|
||||
LastAction: `Select container ${selectedContainer.properties.id}`,
|
||||
MainContainer: mainContainerClone,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters),
|
||||
SelectedContainer,
|
||||
SelectedContainerId: SelectedContainer.properties.id
|
||||
SelectedContainer: selectedContainer,
|
||||
SelectedContainerId: selectedContainer.properties.id,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
}]));
|
||||
setHistoryCurrentStep(history.length);
|
||||
}
|
||||
|
@ -49,10 +47,6 @@ export function DeleteContainer(
|
|||
const history = getCurrentHistory(fullHistory, 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 container = findContainerById(mainContainerClone, containerId);
|
||||
|
||||
|
@ -62,7 +56,7 @@ export function DeleteContainer(
|
|||
|
||||
if (container === mainContainerClone) {
|
||||
// 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) {
|
||||
|
@ -77,9 +71,10 @@ export function DeleteContainer(
|
|||
}
|
||||
|
||||
setHistory(history.concat([{
|
||||
LastAction: `Delete container ${containerId}`,
|
||||
MainContainer: mainContainerClone,
|
||||
SelectedContainer: null,
|
||||
SelectedContainerId: '',
|
||||
MainContainer: mainContainerClone,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
}]));
|
||||
setHistoryCurrentStep(history.length);
|
||||
|
@ -168,7 +163,7 @@ export function AddContainer(
|
|||
}
|
||||
|
||||
let x = 0;
|
||||
if (index !== 0) {
|
||||
if (index > 0) {
|
||||
const lastChild: IContainerModel | undefined = parentClone.children.at(index - 1);
|
||||
if (lastChild !== undefined) {
|
||||
x = lastChild.properties.x + Number(lastChild.properties.width);
|
||||
|
@ -185,6 +180,7 @@ export function AddContainer(
|
|||
y: 0,
|
||||
width: properties?.Width,
|
||||
height: parentClone.properties.height,
|
||||
isRigidBody: false,
|
||||
...properties.Style
|
||||
},
|
||||
[],
|
||||
|
@ -202,10 +198,11 @@ export function AddContainer(
|
|||
|
||||
// Update the state
|
||||
setHistory(history.concat([{
|
||||
LastAction: 'Add container',
|
||||
MainContainer: clone,
|
||||
TypeCounters: newCounters,
|
||||
SelectedContainer: parentClone,
|
||||
SelectedContainerId: parentClone.properties.id
|
||||
SelectedContainerId: parentClone.properties.id,
|
||||
TypeCounters: newCounters
|
||||
}]));
|
||||
setHistoryCurrentStep(history.length);
|
||||
}
|
||||
|
@ -218,7 +215,7 @@ export function AddContainer(
|
|||
*/
|
||||
export function OnPropertyChange(
|
||||
key: string,
|
||||
value: string | number,
|
||||
value: string | number | boolean,
|
||||
fullHistory: HistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistory: Dispatch<SetStateAction<HistoryState[]>>,
|
||||
|
@ -232,18 +229,14 @@ export function OnPropertyChange(
|
|||
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) {
|
||||
const selectedContainerClone: IContainerModel = structuredClone(current.SelectedContainer);
|
||||
(selectedContainerClone.properties as any)[key] = value;
|
||||
setHistory(history.concat([{
|
||||
LastAction: 'Change property of main',
|
||||
MainContainer: selectedContainerClone,
|
||||
SelectedContainer: selectedContainerClone,
|
||||
SelectedContainerId: selectedContainerClone.properties.id,
|
||||
MainContainer: selectedContainerClone,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
}]));
|
||||
setHistoryCurrentStep(history.length);
|
||||
|
@ -259,11 +252,223 @@ export function OnPropertyChange(
|
|||
|
||||
(container.properties as any)[key] = value;
|
||||
|
||||
if (container.properties.isRigidBody) {
|
||||
RecalculatePhysics(container);
|
||||
}
|
||||
|
||||
setHistory(history.concat([{
|
||||
LastAction: `Change property of container ${container.properties.id}`,
|
||||
MainContainer: mainContainerClone,
|
||||
SelectedContainer: container,
|
||||
SelectedContainerId: container.properties.id,
|
||||
MainContainer: mainContainerClone,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
||||
}]));
|
||||
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];
|
||||
|
||||
const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||
const [history, setHistory] = React.useState<HistoryState[]>([...props.history]);
|
||||
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(0);
|
||||
const [history, setHistory] = React.useState<HistoryState[]>(structuredClone(props.history));
|
||||
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener('keyup', (event) => onKeyDown(
|
||||
const onKeyUp = (event: KeyboardEvent): void => onKeyDown(
|
||||
event,
|
||||
history,
|
||||
historyCurrentStep,
|
||||
setHistoryCurrentStep
|
||||
));
|
||||
);
|
||||
|
||||
window.addEventListener('keyup', onKeyUp);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keyup', (event) => onKeyDown(
|
||||
event,
|
||||
history,
|
||||
historyCurrentStep,
|
||||
setHistoryCurrentStep
|
||||
));
|
||||
window.removeEventListener('keyup', onKeyUp);
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { HistoryState } from "../../Interfaces/HistoryState";
|
||||
import { HistoryState } from '../../Interfaces/HistoryState';
|
||||
|
||||
export function onKeyDown(
|
||||
event: KeyboardEvent,
|
||||
|
|
|
@ -16,14 +16,15 @@ describe.concurrent('Elements sidebar', () => {
|
|||
x: 0,
|
||||
y: 0,
|
||||
width: 2000,
|
||||
height: 100
|
||||
height: 100,
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
}}
|
||||
isOpen={true}
|
||||
isHistoryOpen={false}
|
||||
SelectedContainer={null}
|
||||
onPropertyChange={() => {}}
|
||||
OnPropertyChange={() => {}}
|
||||
SelectContainer={() => {}}
|
||||
DeleteContainer={() => {}}
|
||||
AddContainer={() => {}}
|
||||
|
@ -44,7 +45,8 @@ describe.concurrent('Elements sidebar', () => {
|
|||
x: 0,
|
||||
y: 0,
|
||||
width: 2000,
|
||||
height: 100
|
||||
height: 100,
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
};
|
||||
|
@ -54,7 +56,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
isOpen={true}
|
||||
isHistoryOpen={false}
|
||||
SelectedContainer={MainContainer}
|
||||
onPropertyChange={() => {}}
|
||||
OnPropertyChange={() => {}}
|
||||
SelectContainer={() => {}}
|
||||
DeleteContainer={() => {}}
|
||||
AddContainer={() => {}}
|
||||
|
@ -98,7 +100,8 @@ describe.concurrent('Elements sidebar', () => {
|
|||
x: 0,
|
||||
y: 0,
|
||||
width: 2000,
|
||||
height: 100
|
||||
height: 100,
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
};
|
||||
|
@ -113,7 +116,8 @@ describe.concurrent('Elements sidebar', () => {
|
|||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0
|
||||
height: 0,
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
}
|
||||
|
@ -129,7 +133,8 @@ describe.concurrent('Elements sidebar', () => {
|
|||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0
|
||||
height: 0,
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
}
|
||||
|
@ -140,7 +145,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
isOpen={true}
|
||||
isHistoryOpen={false}
|
||||
SelectedContainer={MainContainer}
|
||||
onPropertyChange={() => {}}
|
||||
OnPropertyChange={() => {}}
|
||||
SelectContainer={() => {}}
|
||||
DeleteContainer={() => {}}
|
||||
AddContainer={() => {}}
|
||||
|
@ -164,7 +169,8 @@ describe.concurrent('Elements sidebar', () => {
|
|||
x: 0,
|
||||
y: 0,
|
||||
width: 2000,
|
||||
height: 100
|
||||
height: 100,
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
};
|
||||
|
@ -178,7 +184,8 @@ describe.concurrent('Elements sidebar', () => {
|
|||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0
|
||||
height: 0,
|
||||
isRigidBody: false
|
||||
},
|
||||
userData: {}
|
||||
};
|
||||
|
@ -194,7 +201,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
isOpen={true}
|
||||
isHistoryOpen={false}
|
||||
SelectedContainer={SelectedContainer}
|
||||
onPropertyChange={() => {}}
|
||||
OnPropertyChange={() => {}}
|
||||
SelectContainer={selectContainer}
|
||||
DeleteContainer={() => {}}
|
||||
AddContainer={() => {}}
|
||||
|
@ -217,7 +224,7 @@ describe.concurrent('Elements sidebar', () => {
|
|||
isOpen={true}
|
||||
isHistoryOpen={false}
|
||||
SelectedContainer={SelectedContainer}
|
||||
onPropertyChange={() => {}}
|
||||
OnPropertyChange={() => {}}
|
||||
SelectContainer={selectContainer}
|
||||
DeleteContainer={() => {}}
|
||||
AddContainer={() => {}}
|
||||
|
|
|
@ -13,7 +13,7 @@ interface IElementsSidebarProps {
|
|||
isOpen: boolean
|
||||
isHistoryOpen: boolean
|
||||
SelectedContainer: IContainerModel | null
|
||||
onPropertyChange: (key: string, value: string) => void
|
||||
OnPropertyChange: (key: string, value: string | number | boolean) => void
|
||||
SelectContainer: (container: IContainerModel) => void
|
||||
DeleteContainer: (containerid: string) => void
|
||||
AddContainer: (index: number, type: string, parent: string) => void
|
||||
|
@ -71,40 +71,39 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
|||
|
||||
// Event listeners
|
||||
React.useEffect(() => {
|
||||
elementRef.current?.addEventListener(
|
||||
'contextmenu',
|
||||
(event) => handleRightClick(
|
||||
const onContextMenu = (event: MouseEvent): void => handleRightClick(
|
||||
event,
|
||||
setIsContextMenuOpen,
|
||||
setOnClickContainerId,
|
||||
setContextMenuPosition
|
||||
));
|
||||
);
|
||||
|
||||
const onLeftClick = (): void => handleLeftClick(
|
||||
isContextMenuOpen,
|
||||
setIsContextMenuOpen,
|
||||
setOnClickContainerId
|
||||
);
|
||||
|
||||
elementRef.current?.addEventListener(
|
||||
'contextmenu',
|
||||
onContextMenu
|
||||
);
|
||||
|
||||
window.addEventListener(
|
||||
'click',
|
||||
(event) => handleLeftClick(
|
||||
isContextMenuOpen,
|
||||
setIsContextMenuOpen,
|
||||
setOnClickContainerId
|
||||
));
|
||||
onLeftClick
|
||||
);
|
||||
|
||||
return () => {
|
||||
elementRef.current?.addEventListener(
|
||||
elementRef.current?.removeEventListener(
|
||||
'contextmenu',
|
||||
(event) => handleRightClick(
|
||||
event,
|
||||
setIsContextMenuOpen,
|
||||
setOnClickContainerId,
|
||||
setContextMenuPosition
|
||||
));
|
||||
onContextMenu
|
||||
);
|
||||
|
||||
window.removeEventListener(
|
||||
'click',
|
||||
(event) => handleLeftClick(
|
||||
isContextMenuOpen,
|
||||
setIsContextMenuOpen,
|
||||
setOnClickContainerId
|
||||
));
|
||||
onLeftClick
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@ -141,9 +140,12 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
|||
y={contextMenuPosition.y}
|
||||
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>
|
||||
<Properties properties={props.SelectedContainer?.properties} onChange={props.onPropertyChange}></Properties>
|
||||
<Properties properties={props.SelectedContainer?.properties} onChange={props.OnPropertyChange}></Properties>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,7 +21,8 @@ describe.concurrent('Properties', () => {
|
|||
id: 'stuff',
|
||||
parentId: 'parentId',
|
||||
x: 1,
|
||||
y: 1
|
||||
y: 1,
|
||||
isRigidBody: false
|
||||
};
|
||||
|
||||
const handleChange = vi.fn((key, value) => {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import ContainerProperties from '../../Interfaces/Properties';
|
||||
import { INPUT_TYPES } from './PropertiesInputTypes';
|
||||
|
||||
interface IPropertiesProps {
|
||||
properties?: ContainerProperties
|
||||
onChange: (key: string, value: string) => void
|
||||
onChange: (key: string, value: string | number | boolean) => void
|
||||
}
|
||||
|
||||
export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps) => {
|
||||
|
@ -26,11 +27,24 @@ export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps)
|
|||
const handleProperties = (
|
||||
[key, value]: [string, string | number],
|
||||
groupInput: React.ReactNode[],
|
||||
onChange: (key: string, value: string) => void
|
||||
onChange: (key: string, value: string | number | boolean) => void
|
||||
): void => {
|
||||
const id = `property-${key}`;
|
||||
const type = 'text';
|
||||
const isDisabled = key === 'id' || key === 'parentId'; // hardcoded
|
||||
let type = 'text';
|
||||
let checked;
|
||||
|
||||
/// hardcoded stuff for ergonomy ///
|
||||
if (typeof value === 'boolean') {
|
||||
checked = value;
|
||||
}
|
||||
|
||||
if (key in INPUT_TYPES) {
|
||||
type = INPUT_TYPES[key];
|
||||
}
|
||||
|
||||
const isDisabled = ['id', 'parentId'].includes(key);
|
||||
///
|
||||
|
||||
groupInput.push(
|
||||
<div key={id} className='mt-4'>
|
||||
<label className='text-sm font-medium text-gray-800' htmlFor={id}>{key}</label>
|
||||
|
@ -43,7 +57,14 @@ const handleProperties = (
|
|||
type={type}
|
||||
id={id}
|
||||
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}
|
||||
/>
|
||||
</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(() => {
|
||||
window.addEventListener('resize', () => resizeViewBox(setViewer));
|
||||
const onResize = (): void => resizeViewBox(setViewer);
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
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 { AvailableContainer } from '../../Interfaces/AvailableContainer';
|
||||
import { ContainerModel } from '../../Interfaces/ContainerModel';
|
||||
import { HistoryState } from "../../Interfaces/HistoryState";
|
||||
import { HistoryState } from '../../Interfaces/HistoryState';
|
||||
import { PhotographIcon, UploadIcon } from '@heroicons/react/outline';
|
||||
import { FloatingButton } from '../FloatingButton/FloatingButton';
|
||||
import { Bar } from '../Bar/Bar';
|
||||
|
@ -16,7 +16,7 @@ interface IUIProps {
|
|||
AvailableContainers: AvailableContainer[]
|
||||
SelectContainer: (container: ContainerModel) => void
|
||||
DeleteContainer: (containerId: string) => void
|
||||
OnPropertyChange: (key: string, value: string) => void
|
||||
OnPropertyChange: (key: string, value: string | number | boolean) => void
|
||||
AddContainerToSelectedContainer: (type: string) => void
|
||||
AddContainer: (index: number, type: string, parentId: string) => void
|
||||
SaveEditorAsJSON: () => void
|
||||
|
@ -58,7 +58,7 @@ export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
|||
SelectedContainer={props.current.SelectedContainer}
|
||||
isOpen={isElementsSidebarOpen}
|
||||
isHistoryOpen={isHistoryOpen}
|
||||
onPropertyChange={props.OnPropertyChange}
|
||||
OnPropertyChange={props.OnPropertyChange}
|
||||
SelectContainer={props.SelectContainer}
|
||||
DeleteContainer={props.DeleteContainer}
|
||||
AddContainer={props.AddContainer}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { IContainerModel } from './ContainerModel';
|
||||
|
||||
export interface HistoryState {
|
||||
LastAction: string
|
||||
MainContainer: IContainerModel
|
||||
SelectedContainer: IContainerModel | null
|
||||
SelectedContainerId: string
|
||||
|
|
|
@ -5,4 +5,5 @@ export default interface Properties extends React.CSSProperties {
|
|||
parentId: string | null
|
||||
x: 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',
|
||||
x: 0,
|
||||
y: 0,
|
||||
isRigidBody: false,
|
||||
width: DEFAULT_CONFIG.MainContainer.Width,
|
||||
height: DEFAULT_CONFIG.MainContainer.Height,
|
||||
fillOpacity: 0,
|
||||
|
|
|
@ -8,6 +8,11 @@ import { IEditorState } from '../Components/Editor/Editor';
|
|||
*/
|
||||
export function Revive(editorState: IEditorState): void {
|
||||
const history = editorState.history;
|
||||
|
||||
// restore last step
|
||||
editorState.historyCurrentStep = history.length - 1;
|
||||
|
||||
// restore the parents and the selected container
|
||||
for (const state of history) {
|
||||
if (state.MainContainer === null || state.MainContainer === undefined) {
|
||||
continue;
|
||||
|
@ -43,6 +48,10 @@ export const getCircularReplacer = (): (key: any, value: object | null) => objec
|
|||
return;
|
||||
}
|
||||
|
||||
if (key === 'SelectedContainer') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue