Merged PR 212: Optimize FindChildrenById from O(n) to O(1)

Optimize FindChildrenById from O(n) to O(1):
- Deprecate FindContainerByIdDFS
- Container: Replace Children to string[]
- Add HashMap to IHistoryState that contains all containers

To access a container by id now cost O(1) without any additional cost

+ Implement CICD for SVGLibs
This commit is contained in:
Eric Nguyen 2022-10-12 09:39:54 +00:00
parent 466ef2b08b
commit c256a76e01
45 changed files with 775 additions and 450 deletions

View file

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { describe, it, expect } from 'vitest';
import { API_FETCH_URL } from '../../../public/svgld-settings';
import { AddMethod } from '../../Enums/AddMethod';
import { Orientation } from '../../Enums/Orientation';
import { Position } from '../../Enums/Position';
@ -10,7 +11,6 @@ import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
import { ICategory } from '../../Interfaces/ICategory';
import { IConfiguration } from '../../Interfaces/IConfiguration';
import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerModel';
import { IEditorState } from '../../Interfaces/IEditorState';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { IPattern } from '../../Interfaces/IPattern';
import { DEFAULT_MAINCONTAINER_PROPS, GetDefaultContainerProps } from '../../utils/default';
@ -21,9 +21,9 @@ const CHARP_WEB_API_RESOURCE_URL = 'SVGLD';
const CSHARP_WEB_API_URL = CSHARP_WEB_API_BASE_URL + CHARP_WEB_API_RESOURCE_URL + '/';
// TODO: Migrate this test to SVGLDWebAPI rather than using test-server/
describe.concurrent('Test server test', () => {
describe.concurrent('Test server test', async() => {
it('Load environment', () => {
const url = import.meta.env.VITE_API_FETCH_URL;
const url = API_FETCH_URL;
expect(url).toBe('http://localhost:5000');
});
@ -103,9 +103,11 @@ describe.concurrent('Models test suite', () => {
DEFAULT_MAINCONTAINER_PROPS
);
const containers = new Map<string, IContainerModel>();
const historyState: IHistoryState = {
lastAction: 'string',
mainContainer,
mainContainer: mainContainer.properties.id,
containers,
selectedContainerId: '3',
typeCounters: {
main: 1

View file

@ -1,3 +1,4 @@
import { API_FETCH_URL, API_SET_CONTAINER_LIST_URL } from '../../../public/svgld-settings';
import { IConfiguration } from '../../Interfaces/IConfiguration';
import { ISetContainerListRequest } from '../../Interfaces/ISetContainerListRequest';
import { ISetContainerListResponse } from '../../Interfaces/ISetContainerListResponse';
@ -8,7 +9,7 @@ import { GetCircularReplacerKeepDataStructure } from '../../utils/saveload';
* @returns {Configation} The model of the configuration for the application
*/
export async function FetchConfiguration(): Promise<IConfiguration> {
const url = import.meta.env.VITE_API_FETCH_URL;
const url = API_FETCH_URL;
// The test library cannot use the Fetch API
// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
@ -34,7 +35,7 @@ export async function FetchConfiguration(): Promise<IConfiguration> {
}
export async function SetContainerList(request: ISetContainerListRequest): Promise<ISetContainerListResponse> {
const url = import.meta.env.VITE_API_SET_CONTAINER_LIST_URL;
const url = API_SET_CONTAINER_LIST_URL;
const dataParsed = JSON.stringify(request, GetCircularReplacerKeepDataStructure());
// The test library cannot use the Fetch API
// @ts-expect-error

View file

@ -1,7 +1,7 @@
import React, { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
import { events as EVENTS } from '../../Events/AppEvents';
import { MainMenu } from '../MainMenu/MainMenu';
import { ContainerModel } from '../../Interfaces/IContainerModel';
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
import { Editor } from '../Editor/Editor';
import { IEditorState } from '../../Interfaces/IEditorState';
import { LoadState } from './Actions/Load';
@ -81,12 +81,15 @@ export function App(props: IAppProps): JSX.Element {
null,
DEFAULT_MAINCONTAINER_PROPS
);
const containers = new Map<string, IContainerModel>();
containers.set(defaultMainContainer.properties.id, defaultMainContainer);
const [editorState, setEditorState] = useState<IEditorState>({
configuration: DEFAULT_CONFIG,
history: [{
lastAction: '',
mainContainer: defaultMainContainer,
mainContainer: defaultMainContainer.properties.id,
containers,
selectedContainerId: defaultMainContainer.properties.id,
typeCounters: {},
symbols: new Map(),

View file

@ -2,7 +2,7 @@ import { Orientation } from '../../Enums/Orientation';
import { Position } from '../../Enums/Position';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { SHOW_SELF_DIMENSIONS, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS } from '../../utils/default';
import { MakeRecursionDFSIterator, Pairwise } from '../../utils/itertools';
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../utils/itertools';
import { TransformX, TransformY } from '../../utils/svg';
import { RenderDimension } from './Dimension';
@ -10,6 +10,7 @@ const MODULE_STROKE_WIDTH = 1;
export function AddDimensions(
ctx: CanvasRenderingContext2D,
containers: Map<string, IContainerModel>,
container: IContainerModel,
dimMapped: number[],
currentTransform: [number, number],
@ -24,7 +25,8 @@ export function AddDimensions(
container.properties.showSelfDimensions,
AddHorizontalSelfDimension,
AddVerticalSelfDimension,
[container,
[
container,
currentTransform,
scale]
);
@ -37,7 +39,9 @@ export function AddDimensions(
container.properties.showDimensionWithMarks,
AddHorizontalBorrowerDimension,
AddVerticalBorrowerDimension,
[container,
[
containers,
container,
depth,
currentTransform,
scale]
@ -51,7 +55,9 @@ export function AddDimensions(
container.properties.showChildrenDimensions,
AddHorizontalChildrenDimension,
AddVerticalChildrenDimension,
[container,
[
containers,
container,
currentTransform,
scale]
);
@ -94,19 +100,30 @@ function ActionByPosition(
function AddHorizontalChildrenDimension(
ctx: CanvasRenderingContext2D,
yDim: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number],
scale: number
): void {
const childrenId = `dim-y${yDim.toFixed(0)}-children-${container.properties.id}`;
const lastChild = container.children[container.children.length - 1];
const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId);
if (lastChild === undefined) {
return;
}
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
// Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) {
const child = container.children[i];
const childId = container.children[i];
const child = FindContainerById(containers, childId);
if (child === undefined) {
continue;
}
const left = TransformX(child.properties.x, child.properties.width, child.properties.positionReference);
if (left < xChildrenStart) {
xChildrenStart = left;
@ -142,19 +159,32 @@ function AddHorizontalChildrenDimension(
function AddVerticalChildrenDimension(
ctx: CanvasRenderingContext2D,
xDim: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number],
scale: number
): void {
const childrenId = `dim-x${xDim.toFixed(0)}-children-${container.properties.id}`;
const lastChild = container.children[container.children.length - 1];
const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId);
if (lastChild === undefined) {
return;
}
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
let yChildrenEnd = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
// Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) {
const child = container.children[i];
const childId = container.children[i];
const child = FindContainerById(containers, childId);
if (child === undefined) {
continue;
}
const top = TransformY(child.properties.y, child.properties.height, child.properties.positionReference);
if (top < yChildrenStart) {
yChildrenStart = top;
@ -191,12 +221,13 @@ function AddVerticalChildrenDimension(
function AddHorizontalBorrowerDimension(
ctx: CanvasRenderingContext2D,
yDim: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
depth: number,
currentTransform: [number, number],
scale: number
): void {
const it = MakeRecursionDFSIterator(container, depth, currentTransform);
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const marks = []; // list of vertical lines for the dimension
for (const {
container: childContainer, currentTransform: childCurrentTransform
@ -243,12 +274,13 @@ function AddHorizontalBorrowerDimension(
function AddVerticalBorrowerDimension(
ctx: CanvasRenderingContext2D,
xDim: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
depth: number,
currentTransform: [number, number],
scale: number
): void {
const it = MakeRecursionDFSIterator(container, depth, currentTransform);
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const marks = []; // list of vertical lines for the dimension
for (const {
container: childContainer, currentTransform: childCurrentTransform

View file

@ -69,12 +69,11 @@ export function AddContainers(
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
// Deep clone the main container for the history
const clone: IContainerModel = structuredClone(current.mainContainer);
const containers = structuredClone(current.containers);
// Find the parent in the clone
const parentClone: IContainerModel | undefined = FindContainerById(
clone, parentId
containers, parentId
);
if (parentClone === null || parentClone === undefined) {
@ -90,14 +89,15 @@ export function AddContainers(
// Iterate over the containers
availableContainers.forEach((availableContainer, typeIndex) => {
// Get the preset properties from the API
AddNewContainerToParent(availableContainer, configuration, parentClone, index, typeIndex, newCounters, current.symbols, containerIds);
AddNewContainerToParent(availableContainer, configuration, containers, parentClone, index, typeIndex, newCounters, current.symbols, containerIds);
});
// Update the state
history.push({
lastAction: `Add [${containerIds.join(', ')}] in ${parentClone.properties.id}`,
mainContainer: clone,
mainContainer: current.mainContainer,
selectedContainerId: parentClone.properties.id,
containers,
typeCounters: newCounters,
symbols: structuredClone(current.symbols),
selectedSymbolId: current.selectedSymbolId
@ -109,6 +109,7 @@ export function AddContainers(
function AddNewContainerToParent(
availableContainer: IAvailableContainer,
configuration: IConfiguration,
containers: Map<string, IContainerModel>,
parentClone: IContainerModel,
index: number,
typeIndex: number,
@ -143,7 +144,7 @@ function AddNewContainerToParent(
({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
// Apply an add method (append or insert/replace)
({ x, y } = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x, y));
({ x, y } = ApplyAddMethod(containers, index + typeIndex, containerConfig, parentClone, x, y));
// Set the counter of the object type in order to assign an unique id
UpdateCounters(newCounters, type);
@ -170,22 +171,25 @@ function AddNewContainerToParent(
}
);
// Register the container in the hashmap
containers.set(newContainer.properties.id, newContainer);
// Add it to the parent
if (index === parentClone.children.length) {
parentClone.children.push(newContainer);
parentClone.children.push(newContainer.properties.id);
} else {
parentClone.children.splice(index, 0, newContainer);
parentClone.children.splice(index, 0, newContainer.properties.id);
}
// Sort the parent children by x
SortChildren(parentClone);
SortChildren(containers, parentClone);
/// Handle behaviors here ///
// Apply the behaviors (flex, rigid, anchor)
ApplyBehaviors(newContainer, symbols);
ApplyBehaviors(containers, newContainer, symbols);
// Then, apply the behaviors on its siblings (mostly for flex)
ApplyBehaviorsOnSiblingsChildren(newContainer, symbols);
ApplyBehaviorsOnSiblingsChildren(containers, newContainer, symbols);
// Initialize default children of the container
if (initChilds) {
@ -194,6 +198,7 @@ function AddNewContainerToParent(
newContainer,
configuration,
containerConfig,
containers,
newCounters,
symbols
);
@ -201,6 +206,7 @@ function AddNewContainerToParent(
InitializeChildrenWithPattern(
newContainer,
configuration,
containers,
containerConfig,
newCounters,
symbols
@ -258,6 +264,7 @@ function InitializeDefaultChild(
newContainer: ContainerModel,
configuration: IConfiguration,
containerConfig: IAvailableContainer,
containers: Map<string, IContainerModel>,
newCounters: Record<string, number>,
symbols: Map<string, ISymbolModel>
): void {
@ -276,6 +283,7 @@ function InitializeDefaultChild(
AddNewContainerToParent(
currentConfig,
configuration,
containers,
parent,
0, 0,
newCounters,
@ -286,6 +294,7 @@ function InitializeDefaultChild(
function InitializeChildrenWithPattern(
newContainer: ContainerModel,
configuration: IConfiguration,
containers: Map<string, IContainerModel>,
containerConfig: IAvailableContainer,
newCounters: Record<string, number>,
symbols: Map<string, ISymbolModel>
@ -307,6 +316,7 @@ function InitializeChildrenWithPattern(
AddNewContainerToParent(
container,
configuration,
containers,
newContainer,
0, 0,
newCounters,
@ -334,6 +344,7 @@ function InitializeChildrenWithPattern(
AddNewContainerToParent(
containerConfig,
configuration,
containers,
node.parent,
0, 0,
newCounters,
@ -350,9 +361,10 @@ function InitializeChildrenWithPattern(
console.warn(`[InitializeChildrenFromPattern] IAvailableContainer from pattern was not found in the configuration: ${pattern.wrapper}.
Process will ignore the container.`);
} else {
parent = AddNewContainerToParent(
const newChildContainer = AddNewContainerToParent(
container,
configuration,
containers,
parent,
0, 0,
newCounters,
@ -360,6 +372,9 @@ function InitializeChildrenWithPattern(
undefined,
false
);
// iterate
parent = newChildContainer;
}
}
@ -398,6 +413,7 @@ interface Node {
* @returns New offset
*/
function ApplyAddMethod(
containers: Map<string, IContainerModel>,
index: number,
containerConfig: IAvailableContainer,
parent: IContainerModel,
@ -410,8 +426,8 @@ function ApplyAddMethod(
containerConfig.AddMethod === AddMethod.Append
)) {
// Append method (default)
const lastChild: IContainerModel | undefined = parent.children
.at(index - 1);
const lastChildId: string = parent.children[index - 1];
const lastChild = FindContainerById(containers, lastChildId);
if (lastChild !== undefined) {
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;

View file

@ -24,7 +24,8 @@ export function SelectContainer(
history.push({
lastAction: `Select ${containerId}`,
mainContainer: structuredClone(current.mainContainer),
mainContainer: current.mainContainer,
containers: structuredClone(current.containers),
selectedContainerId: containerId,
typeCounters: Object.assign({}, current.typeCounters),
symbols: structuredClone(current.symbols),
@ -48,8 +49,9 @@ export function DeleteContainer(
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
const mainContainerClone: IContainerModel = structuredClone(current.mainContainer);
const container = FindContainerById(mainContainerClone, containerId);
const containers = structuredClone(current.containers);
const mainContainerClone: IContainerModel | undefined = FindContainerById(containers, current.mainContainer);
const container = FindContainerById(containers, containerId);
if (container === undefined) {
throw new Error(`[DeleteContainer] Tried to delete a container that is not present in the main container: ${containerId}`);
@ -71,21 +73,22 @@ export function DeleteContainer(
}
const newSymbols = structuredClone(current.symbols);
UnlinkContainerFromSymbols(newSymbols, container);
UnlinkContainerFromSymbols(containers, newSymbols, container);
const index = container.parent.children.indexOf(container);
if (index > -1) {
const index = container.parent.children.indexOf(container.properties.id);
const success = containers.delete(container.properties.id);
if (index > -1 && success) {
container.parent.children.splice(index, 1);
} else {
throw new Error('[DeleteContainer] Could not find container among parent\'s children');
}
ApplyBehaviorsOnSiblings(container, current.symbols);
ApplyBehaviorsOnSiblings(containers, container, current.symbols);
// Select the previous container
// or select the one above
const selectedContainerId = GetSelectedContainerOnDelete(
mainContainerClone,
containers,
current.selectedContainerId,
container.parent,
index
@ -93,7 +96,8 @@ export function DeleteContainer(
history.push({
lastAction: `Delete ${containerId}`,
mainContainer: mainContainerClone,
mainContainer: current.mainContainer,
containers,
selectedContainerId,
typeCounters: Object.assign({}, current.typeCounters),
symbols: newSymbols,
@ -115,16 +119,15 @@ export function DeleteContainer(
* @returns {IContainerModel} Next selected container
*/
function GetSelectedContainerOnDelete(
mainContainerClone: IContainerModel,
containers: Map<string, IContainerModel>,
selectedContainerId: string,
parent: IContainerModel,
index: number
): string {
const newSelectedContainer = FindContainerById(mainContainerClone, selectedContainerId) ??
const newSelectedContainerId = FindContainerById(containers, selectedContainerId)?.properties.id ??
parent.children.at(index) ??
parent.children.at(index - 1) ??
parent;
const newSelectedContainerId = newSelectedContainer.properties.id;
parent.properties.id;
return newSelectedContainerId;
}
@ -134,8 +137,12 @@ function GetSelectedContainerOnDelete(
* @param symbols Symbols to update
* @param container Container to unlink
*/
function UnlinkContainerFromSymbols(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
const it = MakeDFSIterator(container);
function UnlinkContainerFromSymbols(
containers: Map<string, IContainerModel>,
symbols: Map<string, ISymbolModel>,
container: IContainerModel
): void {
const it = MakeDFSIterator(container, containers);
for (const child of it) {
const symbol = symbols.get(child.properties.linkedSymbolId);
if (symbol === undefined) {
@ -167,18 +174,19 @@ export function OnPropertyChange(
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
}
const mainContainerClone: IContainerModel = structuredClone(current.mainContainer);
const container: ContainerModel | undefined = FindContainerById(mainContainerClone, selected.properties.id);
const containers = structuredClone(current.containers);
const container: ContainerModel | undefined = FindContainerById(containers, selected.properties.id);
if (container === null || container === undefined) {
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
}
SetContainer(container, key, value, type, current.symbols);
SetContainer(containers, container, key, value, type, current.symbols);
history.push({
lastAction: `Change ${key} of ${container.properties.id}`,
mainContainer: mainContainerClone,
mainContainer: current.mainContainer,
containers,
selectedContainerId: container.properties.id,
typeCounters: Object.assign({}, current.typeCounters),
symbols: structuredClone(current.symbols),
@ -189,20 +197,30 @@ export function OnPropertyChange(
/**
* Sort the parent children by x
* @param parentClone The clone used for the sort
* @param parent The clone used for the sort
* @returns void
*/
export function SortChildren(parentClone: IContainerModel | null | undefined): void {
if (parentClone === null || parentClone === undefined) {
export function SortChildren(
containers: Map<string, IContainerModel>,
parent: IContainerModel | null | undefined
): void {
if (parent === null || parent === undefined) {
return;
}
const isHorizontal = parentClone.properties.orientation === Orientation.Horizontal;
const children = parentClone.children;
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
const children = parent.children;
if (!isHorizontal) {
parentClone.children.sort(
(a, b) => {
parent.children.sort(
(aId, bId) => {
const a = FindContainerById(containers, aId);
const b = FindContainerById(containers, bId);
if (a === undefined || b === undefined) {
return 0;
}
const yA = TransformY(a.properties.y, a.properties.height, a.properties.positionReference);
const yB = TransformY(b.properties.y, b.properties.height, b.properties.positionReference);
if (yA < yB) {
@ -212,16 +230,23 @@ export function SortChildren(parentClone: IContainerModel | null | undefined): v
return 1;
}
// xA = xB
const indexA = children.indexOf(a);
const indexB = children.indexOf(b);
const indexA = children.indexOf(aId);
const indexB = children.indexOf(bId);
return indexA - indexB;
}
);
return;
}
parentClone.children.sort(
(a, b) => {
parent.children.sort(
(aId, bId) => {
const a = FindContainerById(containers, aId);
const b = FindContainerById(containers, bId);
if (a === undefined || b === undefined) {
return 0;
}
const xA = TransformX(a.properties.x, a.properties.width, a.properties.positionReference);
const xB = TransformX(b.properties.x, b.properties.width, b.properties.positionReference);
if (xA < xB) {
@ -231,8 +256,8 @@ export function SortChildren(parentClone: IContainerModel | null | undefined): v
return 1;
}
// xA = xB
const indexA = children.indexOf(a);
const indexB = children.indexOf(b);
const indexA = children.indexOf(aId);
const indexB = children.indexOf(bId);
return indexA - indexB;
}
);
@ -247,6 +272,7 @@ export function SortChildren(parentClone: IContainerModel | null | undefined): v
* @param symbols Current list of symbols
*/
function SetContainer(
containers: Map<string, IContainerModel>,
container: ContainerModel,
key: string, value: string | number | boolean | number[],
type: PropertyType,
@ -267,13 +293,13 @@ function SetContainer(
);
// sort the children list by their position
SortChildren(container.parent);
SortChildren(containers, container.parent);
// Apply special behaviors: rigid, flex, symbol, anchor
ApplyBehaviors(container, symbols);
ApplyBehaviors(containers, container, symbols);
// Apply special behaviors on siblings
ApplyBehaviorsOnSiblingsChildren(container, symbols);
ApplyBehaviorsOnSiblingsChildren(containers, container, symbols);
}
/**

View file

@ -21,7 +21,7 @@ export function GetAction(
): (target: HTMLElement) => void {
return (target: HTMLElement) => {
const id = target.id;
const container = FindContainerById(currentState.mainContainer, id);
const container = FindContainerById(currentState.containers, id);
if (container === undefined) {
Swal.fire({
@ -33,7 +33,7 @@ export function GetAction(
}
/* eslint-disable @typescript-eslint/naming-convention */
const { prev, next } = GetPreviousAndNextSiblings(container);
const { prev, next } = GetPreviousAndNextSiblings(currentState.containers, container);
const request: ISetContainerListRequest = {
Container: container,
@ -59,18 +59,18 @@ export function GetAction(
};
}
function GetPreviousAndNextSiblings(container: IContainerModel): { prev: IContainerModel | undefined, next: IContainerModel | undefined } {
function GetPreviousAndNextSiblings(containers: Map<string, IContainerModel>, container: IContainerModel): { prev: IContainerModel | undefined, next: IContainerModel | undefined } {
let prev;
let next;
if (container.parent !== undefined &&
container.parent !== null &&
container.parent.children.length > 1) {
const index = container.parent.children.indexOf(container);
const index = container.parent.children.indexOf(container.properties.id);
if (index > 0) {
prev = container.parent.children[index - 1];
prev = FindContainerById(containers, container.parent.children[index - 1]);
}
if (index < container.parent.children.length - 1) {
next = container.parent.children[index + 1];
next = FindContainerById(containers, container.parent.children[index + 1]);
}
}
return { prev, next };
@ -144,7 +144,7 @@ function HandleReplace(
throw new Error('[ReplaceContainer] Cannot replace a container that does not exists');
}
const index = selectedContainer.parent.children.indexOf(selectedContainer);
const index = selectedContainer.parent.children.indexOf(selectedContainer.properties.id);
const newHistoryAfterDelete = DeleteContainer(
selectedContainer.properties.id,

View file

@ -36,6 +36,7 @@ export function AddSymbol(
history.push({
lastAction: `Add ${name}`,
mainContainer: structuredClone(current.mainContainer),
containers: structuredClone(current.containers),
selectedContainerId: current.selectedContainerId,
typeCounters: newCounters,
symbols: newSymbols,
@ -55,6 +56,7 @@ export function SelectSymbol(
history.push({
lastAction: `Select ${symbolId}`,
mainContainer: structuredClone(current.mainContainer),
containers: structuredClone(current.containers),
selectedContainerId: current.selectedContainerId,
typeCounters: structuredClone(current.typeCounters),
symbols: structuredClone(current.symbols),
@ -78,15 +80,15 @@ export function DeleteSymbol(
throw new Error(`[DeleteSymbol] Could not find symbol in the current state!: ${symbolId}`);
}
const newMainContainer = structuredClone(current.mainContainer);
UnlinkSymbolFromContainers(symbol, newMainContainer);
const containers = structuredClone(current.containers);
UnlinkSymbolFromContainers(containers, symbol);
newSymbols.delete(symbolId);
history.push({
lastAction: `Select ${symbolId}`,
mainContainer: newMainContainer,
mainContainer: current.mainContainer,
containers,
selectedContainerId: current.selectedContainerId,
typeCounters: structuredClone(current.typeCounters),
symbols: newSymbols,
@ -100,9 +102,9 @@ export function DeleteSymbol(
* @param symbol Symbol to remove
* @param root Container and its children to remove a symbol from
*/
function UnlinkSymbolFromContainers(symbol: ISymbolModel, root: IContainerModel): void {
function UnlinkSymbolFromContainers(containers: Map<string, IContainerModel>, symbol: ISymbolModel): void {
symbol.linkedContainers.forEach((containerId) => {
const container = FindContainerById(root, containerId);
const container = FindContainerById(containers, containerId);
if (container === undefined) {
return;
@ -140,22 +142,23 @@ export function OnPropertyChange(
(symbol as any)[key] = value;
const newMainContainer = structuredClone(current.mainContainer);
const containers = structuredClone(current.containers);
symbol.linkedContainers.forEach((containerId) => {
const container = FindContainerById(newMainContainer, containerId);
const container = FindContainerById(containers, containerId);
if (container === undefined) {
return;
}
ApplyBehaviors(container, newSymbols);
ApplyBehaviors(containers, container, newSymbols);
ApplyBehaviorsOnSiblingsChildren(container, newSymbols);
ApplyBehaviorsOnSiblingsChildren(containers, container, newSymbols);
});
history.push({
lastAction: `Change ${key} of ${symbol.id}`,
mainContainer: newMainContainer,
mainContainer: current.mainContainer,
containers,
selectedContainerId: current.selectedContainerId,
typeCounters: Object.assign({}, current.typeCounters),
symbols: newSymbols,

View file

@ -16,16 +16,28 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Enums/Orientation';
import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
import { FindContainerById } from '../../../utils/itertools';
/**
* Impose the container position to its siblings
* Apply the following modification to the overlapping rigid body container :
* @param container Container to impose its position
*/
export function ApplyAnchor(container: IContainerModel, parent: IContainerModel): IContainerModel {
const rigidBodies = parent.children.filter(
child => !child.properties.isAnchor
);
export function ApplyAnchor(containers: Map<string, IContainerModel>, container: IContainerModel, parent: IContainerModel): IContainerModel {
const rigidBodies: IContainerModel[] = [];
parent.children.forEach(
childId => {
const child = FindContainerById(containers, childId);
if (child === undefined) {
return;
}
if (child.properties.isAnchor) {
return;
}
rigidBodies.push(child);
});
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
const overlappingContainers = isHorizontal
@ -33,7 +45,7 @@ export function ApplyAnchor(container: IContainerModel, parent: IContainerModel)
: GetVerticallyOverlappingContainers(container, rigidBodies);
for (const overlappingContainer of overlappingContainers) {
ConstraintBodyInsideUnallocatedWidth(overlappingContainer);
ConstraintBodyInsideUnallocatedWidth(containers, overlappingContainer);
}
return container;
}

View file

@ -1,6 +1,7 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { APPLY_BEHAVIORS_ON_CHILDREN, ENABLE_RIGID, ENABLE_SWAP } from '../../../utils/default';
import { FindContainerById, MakeChildrenIterator } from '../../../utils/itertools';
import { ApplyAnchor, GetOverlappingContainers } from './AnchorBehaviors';
import { Flex } from './FlexBehaviors';
import { ApplyRigidBody } from './RigidBodyBehaviors';
@ -13,7 +14,7 @@ import { ApplySymbol } from './SymbolBehaviors';
* @param container Container to recalculate its positions
* @returns Updated container
*/
export function ApplyBehaviors(container: IContainerModel, symbols: Map<string, ISymbolModel>): IContainerModel {
export function ApplyBehaviors(containers: Map<string, IContainerModel>, container: IContainerModel, symbols: Map<string, ISymbolModel>): IContainerModel {
try {
const symbol = symbols.get(container.properties.linkedSymbolId);
if (container.properties.linkedSymbolId !== '' && symbol !== undefined) {
@ -24,24 +25,24 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
const parent = container.parent;
if (container.properties.isAnchor) {
ApplyAnchor(container, parent);
ApplyAnchor(containers, container, parent);
}
if (ENABLE_SWAP) {
ApplySwap(container, parent);
ApplySwap(containers, container, parent);
}
Flex(container, parent);
Flex(containers, container, parent);
if (ENABLE_RIGID) {
ApplyRigidBody(container, parent);
ApplyRigidBody(containers, container, parent);
}
}
if (APPLY_BEHAVIORS_ON_CHILDREN) {
// Apply DFS by recursion
for (const child of container.children) {
ApplyBehaviors(child, symbols);
for (const child of MakeChildrenIterator(containers, container.children)) {
ApplyBehaviors(containers, child, symbols);
}
}
} catch (e) {
@ -64,23 +65,32 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
* @param symbols
* @returns
*/
export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel, symbols: Map<string, ISymbolModel>): void {
export function ApplyBehaviorsOnSiblingsChildren(
containers: Map<string, IContainerModel>,
newContainer: IContainerModel,
symbols: Map<string, ISymbolModel>): void {
if (newContainer.parent === null || newContainer.parent === undefined) {
return;
}
newContainer.parent.children
.forEach((container: IContainerModel) => {
if (container.parent != null) {
UpdateWarning(container, container.parent);
.forEach((containerId: string) => {
const container = FindContainerById(containers, containerId);
if (container === undefined) {
return;
}
if (container.parent !== null) {
UpdateWarning(containers, container, container.parent);
}
if (container === newContainer) {
return;
}
for (const child of container.children) {
ApplyBehaviors(child, symbols);
for (const child of MakeChildrenIterator(containers, container.children)) {
ApplyBehaviors(containers, child, symbols);
}
});
}
@ -91,30 +101,47 @@ export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel,
* @param symbols
* @returns
*/
export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols: Map<string, ISymbolModel>): void {
export function ApplyBehaviorsOnSiblings(containers: Map<string, IContainerModel>, newContainer: IContainerModel, symbols: Map<string, ISymbolModel>): void {
if (newContainer.parent === null || newContainer.parent === undefined) {
return;
}
newContainer.parent.children
.forEach((container: IContainerModel) => {
ApplyBehaviors(container, symbols);
.forEach((containerId: string) => {
const container = FindContainerById(containers, containerId);
if (container === undefined) {
return;
}
ApplyBehaviors(containers, container, symbols);
if (container.parent != null) {
UpdateWarning(container, container.parent);
UpdateWarning(containers, container, container.parent);
}
if (container === newContainer) {
return;
}
for (const child of container.children) {
ApplyBehaviors(child, symbols);
for (const child of MakeChildrenIterator(containers, container.children)) {
ApplyBehaviors(containers, child, symbols);
}
});
}
function UpdateWarning(container: IContainerModel, parent: IContainerModel): void {
const overlappingContainers = GetOverlappingContainers(container, parent.children);
function UpdateWarning(containers: Map<string, IContainerModel>, container: IContainerModel, parent: IContainerModel): void {
const targetContainers: IContainerModel[] = [];
parent.children.forEach((child) => {
const targetContainer = FindContainerById(containers, child);
if (targetContainer === undefined) {
return;
}
targetContainers.push(targetContainer);
});
const overlappingContainers = GetOverlappingContainers(container, targetContainers);
if (overlappingContainers.length > 0) {
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
} else {

View file

@ -2,6 +2,7 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Enums/Orientation';
import { Simplex } from '../../../utils/simplex';
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
import { MakeChildrenIterator } from '../../../utils/itertools';
interface IFlexibleGroup {
group: IContainerModel[]
@ -13,13 +14,13 @@ interface IFlexibleGroup {
* Flex the container and its siblings (mutate)
* @returns Flexed container
*/
export function Flex(container: IContainerModel, parent: IContainerModel): void {
export function Flex(containers: Map<string, IContainerModel>, container: IContainerModel, parent: IContainerModel): void {
const isVertical = parent.properties.orientation === Orientation.Vertical;
if (isVertical) {
const wantedWidth = Math.min(container.properties.maxWidth, parent.properties.width);
container.properties.width = ApplyWidthMargin(wantedWidth, container.properties.margin.left, container.properties.margin.right);
const flexibleGroups = GetVerticalFlexibleGroups(parent);
const flexibleGroups = GetVerticalFlexibleGroups(containers, parent);
for (const flexibleGroup of flexibleGroups) {
FlexGroupVertically(flexibleGroup);
}
@ -28,7 +29,7 @@ export function Flex(container: IContainerModel, parent: IContainerModel): void
const wantedHeight = Math.min(container.properties.maxHeight, parent.properties.height);
container.properties.height = ApplyWidthMargin(wantedHeight, container.properties.margin.top, container.properties.margin.bottom);
const flexibleGroups = GetHorizontalFlexibleGroups(parent);
const flexibleGroups = GetHorizontalFlexibleGroups(containers, parent);
for (const flexibleGroup of flexibleGroups) {
FlexGroupHorizontally(flexibleGroup);
}
@ -39,12 +40,12 @@ export function Flex(container: IContainerModel, parent: IContainerModel): void
* @param parent Parent in which the flexible children will be set in groups
* @returns a list of groups of flexible containers
*/
export function GetHorizontalFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
export function GetHorizontalFlexibleGroups(containers: Map<string, IContainerModel>, parent: IContainerModel): IFlexibleGroup[] {
const flexibleGroups: IFlexibleGroup[] = [];
let group: IContainerModel[] = [];
let offset = 0;
let size = 0;
for (const child of parent.children) {
for (const child of MakeChildrenIterator(containers, parent.children)) {
if (child.properties.isAnchor) {
size = child.properties.x - offset;
const flexibleGroup: IFlexibleGroup = {
@ -77,12 +78,15 @@ export function GetHorizontalFlexibleGroups(parent: IContainerModel): IFlexibleG
* @param parent Parent in which the flexible children will be set in groups
* @returns a list of groups of flexible containers
*/
export function GetVerticalFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
export function GetVerticalFlexibleGroups(
containers: Map<string, IContainerModel>,
parent: IContainerModel
): IFlexibleGroup[] {
const flexibleGroups: IFlexibleGroup[] = [];
let group: IContainerModel[] = [];
let offset = 0;
let size = 0;
for (const child of parent.children) {
for (const child of MakeChildrenIterator(containers, parent.children)) {
if (child.properties.isAnchor) {
size = child.properties.y - offset;
const flexibleGroup: IFlexibleGroup = {

View file

@ -1,6 +1,6 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Enums/Orientation';
import { ReversePairwise } from '../../../utils/itertools';
import { MakeChildrenIterator, ReversePairwise } from '../../../utils/itertools';
import { Flex } from './FlexBehaviors';
/**
@ -8,12 +8,17 @@ import { Flex } from './FlexBehaviors';
* @param container
* @returns
*/
export function ApplyPush(container: IContainerModel, parent: IContainerModel): IContainerModel {
export function ApplyPush(
containers: Map<string, IContainerModel>,
container: IContainerModel,
parent: IContainerModel
): IContainerModel {
if (parent.children.length <= 1) {
return container;
}
const children = parent.children;
const children: IContainerModel[] = [...MakeChildrenIterator(containers, parent.children)];
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
if (isHorizontal) {
@ -22,7 +27,7 @@ export function ApplyPush(container: IContainerModel, parent: IContainerModel):
PushContainersVertically(container, children);
}
Flex(container, parent);
Flex(containers, container, parent);
return container;
}

View file

@ -10,6 +10,7 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISizePointer } from '../../../Interfaces/ISizePointer';
import { Orientation } from '../../../Enums/Orientation';
import { ENABLE_HARD_RIGID } from '../../../utils/default';
import { MakeChildrenIterator } from '../../../utils/itertools';
/**
* "Transform the container into a rigid body"
@ -21,13 +22,14 @@ import { ENABLE_HARD_RIGID } from '../../../utils/default';
* @returns A rigid body container
*/
export function ApplyRigidBody(
containers: Map<string, IContainerModel>,
container: IContainerModel,
parent: IContainerModel
): IContainerModel {
container = ConstraintBodyInsideParent(container, parent);
if (ENABLE_HARD_RIGID) {
container = ConstraintBodyInsideUnallocatedWidth(container);
container = ConstraintBodyInsideUnallocatedWidth(containers, container);
}
return container;
@ -117,6 +119,7 @@ function ConstraintBodyInsideSpace(
* @returns Updated container
*/
export function ConstraintBodyInsideUnallocatedWidth(
containers: Map<string, IContainerModel>,
container: IContainerModel
): IContainerModel {
if (container.parent === null || container.parent === undefined) {
@ -126,10 +129,11 @@ export function ConstraintBodyInsideUnallocatedWidth(
// Get the available spaces of the parent
const isHorizontal =
container.parent.properties.orientation === Orientation.Horizontal;
const children: IContainerModel[] = [...MakeChildrenIterator(containers, container.parent.children)];
const availableWidths = GetAvailableWidths(
0,
container.parent.properties.width,
container.parent.children,
children,
container,
isHorizontal
);

View file

@ -5,9 +5,13 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Enums/Orientation';
import { GetHorizontallyOverlappingContainers, GetVerticallyOverlappingContainers } from './AnchorBehaviors';
import { MakeChildrenIterator } from '../../../utils/itertools';
export function ApplySwap(container: IContainerModel, parent: IContainerModel): void {
const children = parent.children;
export function ApplySwap(
containers: Map<string, IContainerModel>,
container: IContainerModel,
parent: IContainerModel): void {
const children = [...MakeChildrenIterator(containers, parent.children)];
const isVertical = parent.properties.orientation === Orientation.Vertical;
if (isVertical) {

View file

@ -274,7 +274,7 @@ export function Editor(props: IEditorProps): JSX.Element {
// Render
const configuration = props.configuration;
const current = GetCurrentHistoryState(history, historyCurrentStep);
const selected = FindContainerById(current.mainContainer, current.selectedContainerId);
const selected = FindContainerById(current.containers, current.selectedContainerId);
return (
<div ref={editorRef} className="Editor font-sans h-full">

View file

@ -9,6 +9,7 @@ import { PropertyType } from '../../Enums/PropertyType';
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
interface IElementsListProps {
containers: Map<string, IContainerModel>
mainContainer: IContainerModel
symbols: Map<string, ISymbolModel>
selectedContainer: IContainerModel | undefined
@ -59,6 +60,7 @@ function HandleDragOver(
function HandleOnDrop(
event: React.DragEvent,
containers: Map<string, IContainerModel>,
mainContainer: IContainerModel,
addContainer: (index: number, type: string, parent: string) => void
): void {
@ -68,7 +70,7 @@ function HandleOnDrop(
RemoveBorderClasses(target);
const targetContainer: IContainerModel | undefined = FindContainerById(
mainContainer,
containers,
target.id
);
@ -95,7 +97,7 @@ function HandleOnDrop(
// locate the hitboxes
if (y < 12) {
const index = targetContainer.parent.children.indexOf(targetContainer);
const index = targetContainer.parent.children.indexOf(targetContainer.properties.id);
addContainer(
index,
type,
@ -107,7 +109,7 @@ function HandleOnDrop(
type,
targetContainer.properties.id);
} else {
const index = targetContainer.parent.children.indexOf(targetContainer);
const index = targetContainer.parent.children.indexOf(targetContainer.properties.id);
addContainer(
index + 1,
type,
@ -119,10 +121,10 @@ function HandleOnDrop(
export function ElementsList(props: IElementsListProps): JSX.Element {
// States
const divRef = React.useRef<HTMLDivElement>(null);
const [width, height] = useSize(divRef);
const [, height] = useSize(divRef);
// Render
const it = MakeRecursionDFSIterator(props.mainContainer, 0, [0, 0], true);
const it = MakeRecursionDFSIterator(props.mainContainer, props.containers, 0, [0, 0], true);
const containers = [...it];
function Row({
index, style
@ -154,7 +156,7 @@ export function ElementsList(props: IElementsListProps): JSX.Element {
style={style}
title={container.properties.warning}
onClick={() => props.selectContainer(container.properties.id)}
onDrop={(event) => HandleOnDrop(event, props.mainContainer, props.addContainer)}
onDrop={(event) => HandleOnDrop(event, props.containers, props.mainContainer, props.addContainer)}
onDragOver={(event) => HandleDragOver(event, props.mainContainer)}
onDragLeave={(event) => HandleDragLeave(event)}
>

View file

@ -1,183 +0,0 @@
import * as React from 'react';
import { FixedSizeList as List } from 'react-window';
import { Properties } from '../ContainerProperties/ContainerProperties';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { PropertyType } from '../../Enums/PropertyType';
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
interface IElementsProps {
mainContainer: IContainerModel
symbols: Map<string, ISymbolModel>
selectedContainer: IContainerModel | undefined
onPropertyChange: (
key: string,
value: string | number | boolean | number[],
type?: PropertyType
) => void
selectContainer: (containerId: string) => void
addContainer: (index: number, type: string, parent: string) => void
}
function RemoveBorderClasses(target: HTMLButtonElement, exception: string = ''): void {
const bordersClasses = ['border-t-8', 'border-8', 'border-b-8'].filter(className => className !== exception);
target.classList.remove(...bordersClasses);
}
function HandleDragLeave(event: React.DragEvent): void {
const target: HTMLButtonElement = event.target as HTMLButtonElement;
RemoveBorderClasses(target);
}
function HandleDragOver(
event: React.DragEvent,
mainContainer: IContainerModel
): void {
event.preventDefault();
const target: HTMLButtonElement = event.target as HTMLButtonElement;
const rect = target.getBoundingClientRect();
const y = event.clientY - rect.top; // y position within the element.
if (target.id === mainContainer.properties.id) {
target.classList.add('border-8');
return;
}
if (y < 12) {
RemoveBorderClasses(target, 'border-t-8');
target.classList.add('border-t-8');
} else if (y < 24) {
RemoveBorderClasses(target, 'border-8');
target.classList.add('border-8');
} else {
RemoveBorderClasses(target, 'border-b-8');
target.classList.add('border-b-8');
}
}
function HandleOnDrop(
event: React.DragEvent,
mainContainer: IContainerModel,
addContainer: (index: number, type: string, parent: string) => void
): void {
event.preventDefault();
const type = event.dataTransfer.getData('type');
const target: HTMLButtonElement = event.target as HTMLButtonElement;
RemoveBorderClasses(target);
const targetContainer: IContainerModel | undefined = FindContainerById(
mainContainer,
target.id
);
if (targetContainer === undefined) {
throw new Error('[handleOnDrop] Tried to drop onto a unknown container!');
}
if (targetContainer === mainContainer) {
// if the container is the root, only add type as child
addContainer(
targetContainer.children.length,
type,
targetContainer.properties.id);
return;
}
if (targetContainer.parent === null ||
targetContainer.parent === undefined) {
throw new Error('[handleDrop] Tried to drop into a child container without a parent!');
}
const rect = target.getBoundingClientRect();
const y = event.clientY - rect.top; // y position within the element.
// locate the hitboxes
if (y < 12) {
const index = targetContainer.parent.children.indexOf(targetContainer);
addContainer(
index,
type,
targetContainer.parent.properties.id
);
} else if (y < 24) {
addContainer(
targetContainer.children.length,
type,
targetContainer.properties.id);
} else {
const index = targetContainer.parent.children.indexOf(targetContainer);
addContainer(
index + 1,
type,
targetContainer.parent.properties.id
);
}
}
export function Elements(props: IElementsProps): JSX.Element {
// Render
const it = MakeRecursionDFSIterator(props.mainContainer, 0, [0, 0], true);
const containers = [...it];
function Row({
index, style
}: {
index: number
style: React.CSSProperties
}): JSX.Element {
const { container, depth } = containers[index];
const key = container.properties.id.toString();
const tabs = '|\t'.repeat(depth);
const text = container.properties.displayedText === key
? `${key}`
: `${container.properties.displayedText}`;
const isSelected = props.selectedContainer !== undefined &&
props.selectedContainer !== null &&
props.selectedContainer.properties.id === container.properties.id;
const selectedClass: string = isSelected
? 'border-l-4 bg-blue-500 shadow-lg shadow-blue-500/60 hover:bg-blue-600 hover:shadow-blue-500 text-slate-50'
: 'bg-slate-300/60 hover:bg-slate-400 hover:shadow-slate-400';
return (
<button type="button"
className={`transition-all w-full border-blue-500 hover:shadow-lg elements-sidebar-row whitespace-pre
text-left text-sm font-medium inline-flex ${container.properties.type} ${selectedClass}`}
id={key}
key={key}
style={style}
title={container.properties.warning}
onClick={() => props.selectContainer(container.properties.id)}
onDrop={(event) => HandleOnDrop(event, props.mainContainer, props.addContainer)}
onDragOver={(event) => HandleDragOver(event, props.mainContainer)}
onDragLeave={(event) => HandleDragLeave(event)}
>
{tabs}
{text}
{container.properties.warning.length > 0 &&
<ExclamationTriangleIcon className='w-8'/>
}
</button>
);
}
return (
<>
<List
className="List divide-y divide-black overflow-y-auto"
itemCount={containers.length}
itemSize={35}
height={192}
width={'100%'}
>
{Row}
</List>
<Properties
properties={props.selectedContainer?.properties}
symbols={props.symbols}
onChange={props.onPropertyChange}
/>
</>
);
}

View file

@ -1,6 +1,6 @@
import { TrashIcon } from '@heroicons/react/24/outline';
import * as React from 'react';
import { FixedSizeList as List } from 'react-window';
import { API_GET_FEEDBACK_URL } from '../../../public/svgld-settings';
import { MessageType } from '../../Enums/MessageType';
import { IGetFeedbackRequest } from '../../Interfaces/IGetFeedbackRequest';
import { IGetFeedbackResponse } from '../../Interfaces/IGetFeedbackResponse';
@ -25,7 +25,7 @@ function UseWorker(
// use webworker for the stringify to avoid freezing
myWorker.postMessage({
state,
url: import.meta.env.VITE_API_GET_FEEDBACK_URL
url: API_GET_FEEDBACK_URL
});
return () => {
@ -49,7 +49,7 @@ function UseAsync(
ApplicationState: state
};
const dataParsed = JSON.stringify(request, GetCircularReplacerKeepDataStructure());
fetch(import.meta.env.VITE_API_GET_FEEDBACK_URL, {
fetch(API_GET_FEEDBACK_URL, {
method: 'POST',
headers: new Headers({
// eslint-disable-next-line @typescript-eslint/naming-convention

View file

@ -4,8 +4,10 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { IContainerProperties } from '../../../Interfaces/IContainerProperties';
import { Camelize } from '../../../utils/stringtools';
import { SHOW_TEXT } from '../../../utils/default';
import { FindContainerById } from '../../../utils/itertools';
interface IContainerProps {
containers: Map<string, IContainerModel>
model: IContainerModel
depth: number
scale: number
@ -18,13 +20,22 @@ interface IContainerProps {
*/
export function Container(props: IContainerProps): JSX.Element {
const containersElements = props.model.children.map(
child => <Container
key={`container-${child.properties.id}`}
model={child}
depth={props.depth + 1}
scale={props.scale}
selectContainer={props.selectContainer}
/>);
childId => {
const child = FindContainerById(props.containers, childId);
if (child === undefined) {
return <></>;
}
return <Container
key={`container-${child.properties.id}`}
containers={props.containers}
model={child}
depth={props.depth + 1}
scale={props.scale}
selectContainer={props.selectContainer}
/>;
});
const width: number = props.model.properties.width;
const height: number = props.model.properties.height;

View file

@ -1,17 +1,22 @@
import * as React from 'react';
import { ContainerModel } from '../../../Interfaces/IContainerModel';
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN } from '../../../utils/default';
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
import { TransformX } from '../../../utils/svg';
import { Dimension } from './Dimension';
interface IDimensionLayerProps {
containers: Map<string, IContainerModel>
roots: ContainerModel | ContainerModel[] | null
scale?: number
}
function GetDimensionsNodes(root: ContainerModel, scale: number): React.ReactNode[] {
const it = MakeBFSIterator(root);
function GetDimensionsNodes(
containers: Map<string, IContainerModel>,
root: ContainerModel,
scale: number
): React.ReactNode[] {
const it = MakeBFSIterator(root, containers);
const dimensions: React.ReactNode[] = [];
let currentDepth = 0;
let min = Infinity;
@ -53,10 +58,10 @@ export function DepthDimensionLayer(props: IDimensionLayerProps): JSX.Element {
const scale = props.scale ?? 1;
if (Array.isArray(props.roots)) {
props.roots.forEach(child => {
dimensions.concat(GetDimensionsNodes(child, scale));
dimensions.concat(GetDimensionsNodes(props.containers, child, scale));
});
} else if (props.roots !== null) {
dimensions = GetDimensionsNodes(props.roots, scale);
dimensions = GetDimensionsNodes(props.containers, props.roots, scale);
}
return (
<g>

View file

@ -3,11 +3,12 @@ import { Orientation } from '../../../Enums/Orientation';
import { Position } from '../../../Enums/Position';
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../../utils/default';
import { MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
import { TransformX, TransformY } from '../../../utils/svg';
import { Dimension } from './Dimension';
interface IDimensionLayerProps {
containers: Map<string, IContainerModel>
root: ContainerModel
scale: number
}
@ -51,8 +52,8 @@ function ActionByPosition(
* @param param0 Object with the root container and the scale of the svg
* @returns A list of dimensions
*/
function Dimensions({ root, scale }: IDimensionLayerProps): React.ReactNode[] {
const it = MakeRecursionDFSIterator(root, 0, [0, 0]);
function Dimensions({ containers, root, scale }: IDimensionLayerProps): React.ReactNode[] {
const it = MakeRecursionDFSIterator(root, containers, 0, [0, 0]);
const dimensions: React.ReactNode[] = [];
const topDim = root.properties.y;
const leftDim = root.properties.x;
@ -75,7 +76,8 @@ function Dimensions({ root, scale }: IDimensionLayerProps): React.ReactNode[] {
container.properties.showSelfDimensions,
AddHorizontalSelfDimension,
AddVerticalSelfDimension,
[container,
[
container,
currentTransform,
dimensions,
scale]
@ -88,7 +90,9 @@ function Dimensions({ root, scale }: IDimensionLayerProps): React.ReactNode[] {
container.properties.showDimensionWithMarks,
AddHorizontalBorrowerDimension,
AddVerticalBorrowerDimension,
[container,
[
containers,
container,
depth,
currentTransform,
dimensions,
@ -102,7 +106,9 @@ function Dimensions({ root, scale }: IDimensionLayerProps): React.ReactNode[] {
container.properties.showChildrenDimensions,
AddHorizontalChildrenDimension,
AddVerticalChildrenDimension,
[container,
[
containers,
container,
currentTransform,
dimensions,
scale]
@ -130,6 +136,7 @@ export function DimensionLayer(props: IDimensionLayerProps): JSX.Element {
function AddHorizontalChildrenDimension(
yDim: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
@ -137,13 +144,25 @@ function AddHorizontalChildrenDimension(
): void {
const childrenId = `dim-y${yDim.toFixed(0)}-children-${container.properties.id}`;
const lastChild = container.children[container.children.length - 1];
const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId);
if (lastChild === undefined) {
return;
}
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
// Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) {
const child = container.children[i];
const childId = container.children[i];
const child = FindContainerById(containers, childId);
if (child === undefined) {
continue;
}
const left = TransformX(child.properties.x, child.properties.width, child.properties.positionReference);
if (left < xChildrenStart) {
xChildrenStart = left;
@ -178,6 +197,7 @@ function AddHorizontalChildrenDimension(
function AddVerticalChildrenDimension(
xDim: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
@ -185,13 +205,25 @@ function AddVerticalChildrenDimension(
): void {
const childrenId = `dim-x${xDim.toFixed(0)}-children-${container.properties.id}`;
const lastChild = container.children[container.children.length - 1];
const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId);
if (lastChild === undefined) {
return;
}
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
let yChildrenEnd = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
// Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) {
const child = container.children[i];
const childId = container.children[i];
const child = FindContainerById(containers, childId);
if (child === undefined) {
continue;
}
const top = TransformY(child.properties.y, child.properties.height, child.properties.positionReference);
if (top < yChildrenStart) {
yChildrenStart = top;
@ -227,13 +259,14 @@ function AddVerticalChildrenDimension(
function AddHorizontalBorrowerDimension(
yDim: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
depth: number,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number
): void {
const it = MakeRecursionDFSIterator(container, depth, currentTransform);
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const marks = []; // list of vertical lines for the dimension
for (const {
container: childContainer, currentTransform: childCurrentTransform
@ -279,13 +312,14 @@ function AddHorizontalBorrowerDimension(
function AddVerticalBorrowerDimension(
xDim: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
depth: number,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number
): void {
const it = MakeRecursionDFSIterator(container, depth, currentTransform);
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const marks = []; // list of vertical lines for the dimension
for (const {
container: childContainer, currentTransform: childCurrentTransform

View file

@ -1,7 +1,7 @@
import * as React from 'react';
import { ReactSVGPanZoom, Tool, TOOL_PAN, Value } from 'react-svg-pan-zoom';
import { Container } from './Elements/Container';
import { ContainerModel } from '../../Interfaces/IContainerModel';
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
import { Selector } from './Elements/Selector/Selector';
import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
import { MAX_FRAMERATE, SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
@ -15,17 +15,13 @@ interface ISVGProps {
viewerHeight: number
width: number
height: number
containers: Map<string, IContainerModel>
children: ContainerModel
selected?: ContainerModel
symbols: Map<string, ISymbolModel>
selectContainer: (containerId: string) => void
}
interface Viewer {
viewerWidth: number
viewerHeight: number
}
export const ID = 'svg';
export function SVG(props: ISVGProps): JSX.Element {
@ -55,6 +51,7 @@ export function SVG(props: ISVGProps): JSX.Element {
let children: React.ReactNode | React.ReactNode[] = [];
children = <Container
key={`container-${props.children.properties.id}`}
containers={props.containers}
model={props.children}
depth={0}
scale={scale}
@ -97,9 +94,9 @@ export function SVG(props: ISVGProps): JSX.Element {
<svg {...properties}>
{children}
{SHOW_DIMENSIONS_PER_DEPTH
? <DepthDimensionLayer scale={scale} roots={props.children} />
? <DepthDimensionLayer containers={props.containers} scale={scale} roots={props.children} />
: null}
<DimensionLayer scale={scale} root={props.children} />
<DimensionLayer containers={props.containers} scale={scale} root={props.children} />
<SymbolLayer scale={scale} symbols={props.symbols} />
<Selector scale={scale} selected={props.selected} /> {/* leave this at the end so it can be removed during the svg export */}
</svg>

View file

@ -18,6 +18,7 @@ import { Settings } from '../Settings/Settings';
import { IMessage } from '../../Interfaces/IMessage';
import { DISABLE_API } from '../../utils/default';
import { UseWorker, UseAsync } from './UseWorker';
import { FindContainerById } from '../../utils/itertools';
export interface IUIProps {
selectedContainer: IContainerModel | undefined
@ -89,6 +90,12 @@ export function UI(props: IUIProps): JSX.Element {
let leftChildren: JSX.Element = (<></>);
let rightChildren: JSX.Element = (<></>);
const mainContainer = FindContainerById(props.current.containers, props.current.mainContainer)
if (mainContainer === undefined) {
throw new Error('Tried to initialized UI but there is no main container!');
}
switch (selectedSidebar) {
case SidebarType.Components:
leftSidebarTitle = 'Components';
@ -100,7 +107,8 @@ export function UI(props: IUIProps): JSX.Element {
/>;
rightSidebarTitle = 'Elements';
rightChildren = <ElementsList
mainContainer={props.current.mainContainer}
containers={props.current.containers}
mainContainer={mainContainer}
symbols={props.current.symbols}
selectedContainer={props.selectedContainer}
onPropertyChange={props.onPropertyChange}

View file

@ -4,6 +4,7 @@ import { IGetFeedbackRequest } from '../../Interfaces/IGetFeedbackRequest';
import { IGetFeedbackResponse } from '../../Interfaces/IGetFeedbackResponse';
import { IMessage } from '../../Interfaces/IMessage';
import { GetCircularReplacerKeepDataStructure } from '../../utils/saveload';
import { API_GET_FEEDBACK_URL } from '../../../public/svgld-settings';
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const myWorker = window.Worker && new Worker('workers/message_worker.js');
@ -15,7 +16,7 @@ export function UseWorker(
// use webworker for the stringify to avoid freezing
myWorker.postMessage({
state,
url: import.meta.env.VITE_API_GET_FEEDBACK_URL
url: API_GET_FEEDBACK_URL
});
return () => {
@ -37,7 +38,7 @@ export function UseAsync(
ApplicationState: state
};
const dataParsed = JSON.stringify(request, GetCircularReplacerKeepDataStructure());
fetch(import.meta.env.VITE_API_GET_FEEDBACK_URL, {
fetch(API_GET_FEEDBACK_URL, {
method: 'POST',
headers: new Headers({
// eslint-disable-next-line @typescript-eslint/naming-convention

View file

@ -3,7 +3,7 @@ import { IContainerModel } from '../../Interfaces/IContainerModel';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { IPoint } from '../../Interfaces/IPoint';
import { DIMENSION_MARGIN, USE_EXPERIMENTAL_CANVAS_API } from '../../utils/default';
import { MakeRecursionDFSIterator } from '../../utils/itertools';
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
import { BAR_WIDTH } from '../Bar/Bar';
import { Canvas } from '../Canvas/Canvas';
import { AddDimensions } from '../Canvas/DimensionLayer';
@ -96,22 +96,32 @@ export function Viewer({
viewerHeight: window.innerHeight
});
const mainContainer = FindContainerById(current.containers, current.mainContainer);
if (mainContainer === undefined) {
return <></>;
}
UseSVGAutoResizerOnWindowResize(isLeftSidebarOpen, isRightSidebarOpen, setViewer);
UseSVGAutoResizerOnSidebar(isLeftSidebarOpen, isRightSidebarOpen, setViewer);
if (USE_EXPERIMENTAL_CANVAS_API) {
function Draw(ctx: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint): void {
const topDim = current.mainContainer.properties.y;
const leftDim = current.mainContainer.properties.x;
const rightDim = current.mainContainer.properties.x + current.mainContainer.properties.width;
const bottomDim = current.mainContainer.properties.y + current.mainContainer.properties.height;
if (mainContainer === undefined) {
return;
}
const topDim = mainContainer.properties.y;
const leftDim = mainContainer.properties.x;
const rightDim = mainContainer.properties.x + mainContainer.properties.width;
const bottomDim = mainContainer.properties.y + mainContainer.properties.height;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.setTransform(scale, 0, 0, scale, translatePos.x, translatePos.y);
ctx.fillStyle = '#000000';
const it = MakeRecursionDFSIterator(current.mainContainer, 0, [0, 0]);
const it = MakeRecursionDFSIterator(mainContainer, current.containers, 0, [0, 0]);
for (const { container, depth, currentTransform } of it) {
const [x, y] = [
container.properties.x + currentTransform[0],
@ -130,6 +140,7 @@ export function Viewer({
rightDim,
depth,
scale,
current.containers,
container,
currentTransform
);
@ -160,13 +171,14 @@ export function Viewer({
className={marginClasses}
viewerWidth={viewer.viewerWidth}
viewerHeight={viewer.viewerHeight}
width={current.mainContainer?.properties.width}
height={current.mainContainer?.properties.height}
width={mainContainer.properties.width}
height={mainContainer.properties.height}
containers={current.containers}
selected={selectedContainer}
symbols={current.symbols}
selectContainer={selectContainer}
>
{current.mainContainer}
{mainContainer}
</SVG>
);
}
@ -188,6 +200,7 @@ function RenderDimensions(
rightDim: number,
depth: number,
scale: number,
containers: Map<string, IContainerModel>,
container: IContainerModel,
currentTransform: [number, number]
): void {
@ -197,7 +210,7 @@ function RenderDimensions(
const containerBottomDim = bottomDim + (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerRightDim = rightDim + (DIMENSION_MARGIN * (depth + 1)) / scale;
const dimMapped = [containerLeftDim, containerBottomDim, containerTopDim, containerRightDim];
AddDimensions(ctx, container, dimMapped, currentTransform, scale, depth);
AddDimensions(ctx, containers, container, dimMapped, currentTransform, scale, depth);
ctx.restore();
}