Merged PR 170: Add new eslint rules

- naming-convention
- prefer-arrow-callback
- func-style
- import/no-default-export
This commit is contained in:
Eric Nguyen 2022-08-26 16:13:21 +00:00
parent 3f58c5ba5e
commit ad126c6c28
65 changed files with 781 additions and 784 deletions

View file

@ -1,12 +1,10 @@
// TODO: https://eslint.org/docs/latest/rules/func-names
// TODO: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md
module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'only-warn',
'plugin:react/recommended',
'standard-with-typescript'
],
@ -20,22 +18,67 @@ module.exports = {
project: './tsconfig.json'
},
plugins: [
'only-warn',
'react',
'react-hooks',
'@typescript-eslint'
],
rules: {
'prefer-arrow-callback': 'error',
'func-style': ['error', 'declaration'],
'space-before-function-paren': ['error', 'never'],
'@typescript-eslint/space-before-function-paren': ['error', 'never'],
// Import/export
'import/no-default-export': 'error',
// Typescript overload
indent: 'off',
'@typescript-eslint/indent': ['warn', 2, {SwitchCase: 1}],
semi: 'off',
'@typescript-eslint/semi': ['warn', 'always'],
"camelcase": "off",
'no-unused-vars': 'off',
// Typescript
'@typescript-eslint/space-before-function-paren': ['error', 'never'],
'@typescript-eslint/indent': ['warn', 2, {SwitchCase: 1}],
'@typescript-eslint/semi': ['warn', 'always'],
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/ban-types': ['error'],
'@typescript-eslint/no-floating-promises': 'off', // disabled cuz troublesome for SweetAlert since they never reject
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "default",
"format": ["camelCase"]
},
{
'selector': 'function',
'format': ['PascalCase']
},
{
"selector": "variable",
"format": ["camelCase", "UPPER_CASE"]
},
{
"selector": "parameter",
"format": ["camelCase"],
"leadingUnderscore": "allow"
},
{
'selector': ['enumMember', 'enum'],
'format': ['PascalCase']
},
{
"selector": "memberLike",
"modifiers": ["private"],
"format": ["camelCase"],
"leadingUnderscore": "require"
},
{
"selector": ['typeLike'],
"format": ["PascalCase"],
}
],
// React
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies
}

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { fetchConfiguration } from './api';
import { FetchConfiguration } from './api';
describe.concurrent('API test', () => {
it('Load environment', () => {
@ -8,7 +8,7 @@ describe.concurrent('API test', () => {
});
it('Fetch configuration', async() => {
const configuration = await fetchConfiguration();
const configuration = await FetchConfiguration();
expect(configuration.MainContainer).toBeDefined();
expect(configuration.MainContainer.Height).toBeGreaterThan(0);
expect(configuration.MainContainer.Width).toBeGreaterThan(0);

View file

@ -4,7 +4,7 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
* Fetch the configuration from the API
* @returns {Configation} The model of the configuration for the application
*/
export async function fetchConfiguration(): Promise<IConfiguration> {
export async function FetchConfiguration(): Promise<IConfiguration> {
const url = `${import.meta.env.VITE_API_URL}`;
// The test library cannot use the Fetch API
// @ts-expect-error

View file

@ -1,6 +1,6 @@
import { Dispatch, SetStateAction } from 'react';
import { IConfiguration } from '../../../Interfaces/IConfiguration';
import { fetchConfiguration } from '../../API/api';
import { FetchConfiguration } from '../../API/api';
import { IEditorState } from '../../../Interfaces/IEditorState';
import { LoadState } from './Load';
import { GetDefaultEditorState } from '../../../utils/default';
@ -10,7 +10,7 @@ export function NewEditor(
setLoaded: Dispatch<SetStateAction<boolean>>
): void {
// Fetch the configuration from the API
fetchConfiguration()
FetchConfiguration()
.then((configuration: IConfiguration) => {
// Set the editor from the given properties of the API
const editorState: IEditorState = GetDefaultEditorState(configuration);

View file

@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react';
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
import './App.scss';
import { MainMenu } from '../MainMenu/MainMenu';
import { ContainerModel } from '../../Interfaces/IContainerModel';
import Editor from '../Editor/Editor';
import { Editor } from '../Editor/Editor';
import { IEditorState } from '../../Interfaces/IEditorState';
import { LoadState } from './Actions/Load';
import { LoadEditor, NewEditor } from './Actions/MenuActions';
@ -14,28 +14,11 @@ interface IAppProps {
root: Element | Document
}
export const App: React.FunctionComponent<IAppProps> = (props) => {
const [isLoaded, setLoaded] = useState<boolean>(false);
const defaultMainContainer = new ContainerModel(
null,
DEFAULT_MAINCONTAINER_PROPS
);
const [editorState, setEditorState] = useState<IEditorState>({
configuration: DEFAULT_CONFIG,
history: [{
LastAction: '',
MainContainer: defaultMainContainer,
SelectedContainerId: defaultMainContainer.properties.id,
TypeCounters: {},
Symbols: new Map(),
SelectedSymbolId: ''
}],
historyCurrentStep: 0
});
// TODO: move this into a variable
function UseHTTPGETStatePreloading(
isLoaded: boolean,
setEditorState: Dispatch<SetStateAction<IEditorState>>,
setLoaded: Dispatch<SetStateAction<boolean>>
): void {
useEffect(() => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
@ -56,6 +39,30 @@ export const App: React.FunctionComponent<IAppProps> = (props) => {
}, (error) => { throw new Error(error); });
}
});
};
export function App(props: IAppProps): JSX.Element {
const [isLoaded, setLoaded] = useState<boolean>(false);
const defaultMainContainer = new ContainerModel(
null,
DEFAULT_MAINCONTAINER_PROPS
);
const [editorState, setEditorState] = useState<IEditorState>({
configuration: DEFAULT_CONFIG,
history: [{
lastAction: '',
mainContainer: defaultMainContainer,
selectedContainerId: defaultMainContainer.properties.id,
typeCounters: {},
symbols: new Map(),
selectedSymbolId: ''
}],
historyCurrentStep: 0
});
UseHTTPGETStatePreloading(isLoaded, setEditorState, setLoaded);
if (isLoaded) {
return (

View file

@ -1,4 +1,4 @@
import { ClockIcon, CubeIcon, LinkIcon, MapIcon } from '@heroicons/react/outline';
import { ClockIcon, CubeIcon, LinkIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { BarIcon } from './BarIcon';
@ -7,34 +7,34 @@ interface IBarProps {
isSymbolsOpen: boolean
isElementsSidebarOpen: boolean
isHistoryOpen: boolean
ToggleSidebar: () => void
ToggleSymbols: () => void
ToggleTimeline: () => void
toggleSidebar: () => void
toggleSymbols: () => void
toggleTimeline: () => void
}
export const BAR_WIDTH = 64; // 4rem
export const Bar: React.FC<IBarProps> = (props) => {
export function Bar(props: IBarProps): JSX.Element {
return (
<div className='fixed z-20 flex flex-col top-0 left-0 h-full w-16 bg-slate-100'>
<BarIcon
isActive={props.isSidebarOpen}
title='Components'
onClick={() => props.ToggleSidebar()}>
onClick={() => props.toggleSidebar()}>
<CubeIcon className='heroicon' />
</BarIcon>
<BarIcon
isActive={props.isSymbolsOpen}
title='Symbols'
onClick={() => props.ToggleSymbols()}>
onClick={() => props.toggleSymbols()}>
<LinkIcon className='heroicon' />
</BarIcon>
<BarIcon
isActive={props.isHistoryOpen}
title='Timeline'
onClick={() => props.ToggleTimeline()}>
onClick={() => props.toggleTimeline()}>
<ClockIcon className='heroicon' />
</BarIcon>
</div>
);
};
}

View file

@ -7,7 +7,7 @@ interface IBarIconProps {
onClick: () => void
}
export const BarIcon: React.FC<IBarIconProps> = (props) => {
export function BarIcon(props: IBarIconProps): JSX.Element {
const isActiveClasses = props.isActive ? 'border-l-4 border-blue-500 bg-slate-200' : '';
return (
<button type="button"
@ -19,4 +19,4 @@ export const BarIcon: React.FC<IBarIconProps> = (props) => {
{props.children}
</button>
);
};
}

View file

@ -2,9 +2,9 @@ import { MenuAlt2Icon, MenuAlt3Icon, MenuIcon } from '@heroicons/react/outline';
import * as React from 'react';
import { PropertyType } from '../../Enums/PropertyType';
import { XPositionReference } from '../../Enums/XPositionReference';
import IContainerProperties from '../../Interfaces/IContainerProperties';
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { ApplyWidthMargin, ApplyXMargin, RemoveWidthMargin, RemoveXMargin, restoreX, transformX } from '../../utils/svg';
import { ApplyWidthMargin, ApplyXMargin, RemoveWidthMargin, RemoveXMargin, RestoreX, TransformX } from '../../utils/svg';
import { InputGroup } from '../InputGroup/InputGroup';
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
import { Select } from '../Select/Select';
@ -15,10 +15,8 @@ interface IContainerFormProps {
onChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
}
const getCSSInputs = (
properties: IContainerProperties,
onChange: (key: string, value: string | number | boolean, type: PropertyType) => void
): JSX.Element[] => {
function GetCSSInputs(properties: IContainerProperties,
onChange: (key: string, value: string | number | boolean, type: PropertyType) => void): JSX.Element[] {
const groupInput: JSX.Element[] = [];
for (const key in properties.style) {
groupInput.push(<InputGroup
@ -29,13 +27,12 @@ const getCSSInputs = (
inputClassName=''
type='string'
value={(properties.style as any)[key]}
onChange={(event) => onChange(key, event.target.value, PropertyType.STYLE)}
/>);
onChange={(event) => onChange(key, event.target.value, PropertyType.Style)} />);
}
return groupInput;
};
}
const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
export function ContainerForm(props: IContainerFormProps): JSX.Element {
return (
<div className='grid grid-cols-2 gap-y-4'>
<InputGroup
@ -45,8 +42,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
inputClassName=''
type='string'
value={props.properties.id.toString()}
isDisabled={true}
/>
isDisabled={true} />
<InputGroup
labelText='Parent name'
inputKey='parentId'
@ -54,8 +50,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
inputClassName=''
type='string'
value={props.properties.parentId}
isDisabled={true}
/>
isDisabled={true} />
<InputGroup
labelText='Type'
inputKey='type'
@ -63,8 +58,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
inputClassName=''
type='string'
value={props.properties.type}
isDisabled={true}
/>
isDisabled={true} />
<InputGroup
labelText='Displayed text'
inputKey='displayedText'
@ -72,8 +66,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
inputClassName=''
type='string'
value={props.properties.displayedText?.toString()}
onChange={(event) => props.onChange('displayedText', event.target.value)}
/>
onChange={(event) => props.onChange('displayedText', event.target.value)} />
<InputGroup
labelText='x'
inputKey='x'
@ -81,19 +74,18 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
inputClassName=''
type='number'
isDisabled={props.properties.linkedSymbolId !== ''}
value={transformX(RemoveXMargin(props.properties.x, props.properties.margin.left), props.properties.width, props.properties.XPositionReference).toString()}
value={TransformX(RemoveXMargin(props.properties.x, props.properties.margin.left), props.properties.width, props.properties.xPositionReference).toString()}
onChange={(event) => props.onChange(
'x',
ApplyXMargin(
restoreX(
RestoreX(
Number(event.target.value),
props.properties.width,
props.properties.XPositionReference
props.properties.xPositionReference
),
props.properties.margin.left
)
)}
/>
)} />
<InputGroup
labelText='y'
inputKey='y'
@ -101,8 +93,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
inputClassName=''
type='number'
value={(props.properties.y - (props.properties.margin?.top ?? 0)).toString()}
onChange={(event) => props.onChange('y', Number(event.target.value) + (props.properties.margin?.top ?? 0))}
/>
onChange={(event) => props.onChange('y', Number(event.target.value) + (props.properties.margin?.top ?? 0))} />
<InputGroup
labelText='Minimum width'
inputKey='minWidth'
@ -111,8 +102,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
type='number'
min={1}
value={props.properties.minWidth.toString()}
onChange={(event) => props.onChange('minWidth', Number(event.target.value))}
/>
onChange={(event) => props.onChange('minWidth', Number(event.target.value))} />
<InputGroup
labelText='Maximum width'
inputKey='maxWidth'
@ -121,8 +111,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
type='number'
min={1}
value={props.properties.maxWidth.toString()}
onChange={(event) => props.onChange('maxWidth', Number(event.target.value))}
/>
onChange={(event) => props.onChange('maxWidth', Number(event.target.value))} />
<InputGroup
labelText='Width'
inputKey='width'
@ -132,8 +121,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
min={props.properties.minWidth}
value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()}
onChange={(event) => props.onChange('width', ApplyWidthMargin(Number(event.target.value), props.properties.margin.left, props.properties.margin.right))}
isDisabled={props.properties.isFlex}
/>
isDisabled={props.properties.isFlex} />
<InputGroup
labelText='Height'
inputKey='height'
@ -142,8 +130,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
type='number'
min={0}
value={(props.properties.height + (props.properties.margin?.top ?? 0) + (props.properties.margin?.bottom ?? 0)).toString()}
onChange={(event) => props.onChange('height', Number(event.target.value) - (props.properties.margin?.top ?? 0) - (props.properties.margin?.bottom ?? 0))}
/>
onChange={(event) => props.onChange('height', Number(event.target.value) - (props.properties.margin?.top ?? 0) - (props.properties.margin?.bottom ?? 0))} />
<InputGroup
labelText='Margin left'
inputKey='left'
@ -152,8 +139,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
type='number'
min={0}
value={(props.properties.margin.left ?? 0).toString()}
onChange={(event) => props.onChange('left', Number(event.target.value), PropertyType.MARGIN)}
/>
onChange={(event) => props.onChange('left', Number(event.target.value), PropertyType.Margin)} />
<InputGroup
labelText='Margin bottom'
inputKey='bottom'
@ -162,8 +148,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
type='number'
min={0}
value={(props.properties.margin.bottom ?? 0).toString()}
onChange={(event) => props.onChange('bottom', Number(event.target.value), PropertyType.MARGIN)}
/>
onChange={(event) => props.onChange('bottom', Number(event.target.value), PropertyType.Margin)} />
<InputGroup
labelText='Margin top'
inputKey='top'
@ -172,8 +157,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
type='number'
min={0}
value={(props.properties.margin.top ?? 0).toString()}
onChange={(event) => props.onChange('top', Number(event.target.value), PropertyType.MARGIN)}
/>
onChange={(event) => props.onChange('top', Number(event.target.value), PropertyType.Margin)} />
<InputGroup
labelText='Margin right'
inputKey='right'
@ -182,8 +166,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
type='number'
min={0}
value={(props.properties.margin.right ?? 0).toString()}
onChange={(event) => props.onChange('right', Number(event.target.value), PropertyType.MARGIN)}
/>
onChange={(event) => props.onChange('right', Number(event.target.value), PropertyType.Margin)} />
<InputGroup
labelText='Flex'
inputKey='isFlex'
@ -191,8 +174,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
inputClassName=''
type='checkbox'
checked={props.properties.isFlex}
onChange={(event) => props.onChange('isFlex', event.target.checked)}
/>
onChange={(event) => props.onChange('isFlex', event.target.checked)} />
<InputGroup
labelText='Anchor'
inputKey='isAnchor'
@ -200,11 +182,10 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
inputClassName=''
type='checkbox'
checked={props.properties.isAnchor}
onChange={(event) => props.onChange('isAnchor', event.target.checked)}
/>
onChange={(event) => props.onChange('isAnchor', event.target.checked)} />
<RadioGroupButtons
name='XPositionReference'
value={props.properties.XPositionReference.toString()}
value={props.properties.xPositionReference.toString()}
inputClassName='hidden'
labelText='Horizontal alignment'
inputGroups={[
@ -233,8 +214,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
value: XPositionReference.Right.toString()
}
]}
onChange={(event) => props.onChange('XPositionReference', Number(event.target.value))}
/>
onChange={(event) => props.onChange('xPositionReference', Number(event.target.value))} />
<Select
inputKey='linkedSymbolId'
labelText='Align with symbol'
@ -245,11 +225,8 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
value: symbol.id
}))}
value={props.properties.linkedSymbolId ?? ''}
onChange={(event) => props.onChange('linkedSymbolId', event.target.value)}
/>
{ getCSSInputs(props.properties, props.onChange) }
onChange={(event) => props.onChange('linkedSymbolId', event.target.value)} />
{GetCSSInputs(props.properties, props.onChange)}
</div>
);
};
export default ContainerForm;
}

View file

@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react';
import { expect, describe, it, vi } from 'vitest';
import { XPositionReference } from '../../Enums/XPositionReference';
import IContainerProperties from '../../Interfaces/IContainerProperties';
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { Properties } from './ContainerProperties';
describe.concurrent('Properties', () => {
@ -33,7 +33,7 @@ describe.concurrent('Properties', () => {
minWidth: 1,
maxWidth: Infinity,
margin: {},
XPositionReference: XPositionReference.Left,
xPositionReference: XPositionReference.Left,
isFlex: false,
isAnchor: false
};

View file

@ -1,8 +1,8 @@
import React from 'react';
import { PropertyType } from '../../Enums/PropertyType';
import IContainerProperties from '../../Interfaces/IContainerProperties';
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import ContainerForm from './ContainerForm';
import { ContainerForm } from './ContainerForm';
interface IPropertiesProps {
properties?: IContainerProperties
@ -10,7 +10,7 @@ interface IPropertiesProps {
onChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
}
export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps) => {
export function Properties(props: IPropertiesProps): JSX.Element {
if (props.properties === undefined) {
return <div></div>;
}
@ -20,8 +20,7 @@ export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps)
<ContainerForm
properties={props.properties}
symbols={props.symbols}
onChange={props.onChange}
/>
onChange={props.onChange} />
</div>
);
};
}

View file

@ -2,16 +2,15 @@ import { Dispatch, SetStateAction } from 'react';
import { IHistoryState } from '../../../Interfaces/IHistoryState';
import { IConfiguration } from '../../../Interfaces/IConfiguration';
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
import { findContainerById, MakeIterator } from '../../../utils/itertools';
import { getCurrentHistory, UpdateCounters } from '../Editor';
import { FindContainerById, MakeIterator } from '../../../utils/itertools';
import { GetCurrentHistory, UpdateCounters } from '../Editor';
import { AddMethod } from '../../../Enums/AddMethod';
import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer';
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../../utils/default';
import { ApplyBehaviors } from '../Behaviors/Behaviors';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import Swal from 'sweetalert2';
import { ApplyMargin, transformX } from '../../../utils/svg';
import { Flex } from '../Behaviors/FlexBehaviors';
import { ApplyMargin, TransformX } from '../../../utils/svg';
import { PropertyType } from '../../../Enums/PropertyType';
/**
@ -25,16 +24,16 @@ export function SelectContainer(
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): void {
const history = getCurrentHistory(fullHistory, historyCurrentStep);
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
history.push({
LastAction: `Select ${containerId}`,
MainContainer: structuredClone(current.MainContainer),
SelectedContainerId: containerId,
TypeCounters: Object.assign({}, current.TypeCounters),
Symbols: structuredClone(current.Symbols),
SelectedSymbolId: current.SelectedSymbolId
lastAction: `Select ${containerId}`,
mainContainer: structuredClone(current.mainContainer),
selectedContainerId: containerId,
typeCounters: Object.assign({}, current.typeCounters),
symbols: structuredClone(current.symbols),
selectedSymbolId: current.selectedSymbolId
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
@ -55,11 +54,11 @@ export function DeleteContainer(
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): void {
const history = getCurrentHistory(fullHistory, historyCurrentStep);
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
const container = findContainerById(mainContainerClone, containerId);
const mainContainerClone: IContainerModel = structuredClone(current.mainContainer);
const container = FindContainerById(mainContainerClone, containerId);
if (container === undefined) {
throw new Error(`[DeleteContainer] Tried to delete a container that is not present in the main container: ${containerId}`);
@ -80,7 +79,7 @@ export function DeleteContainer(
throw new Error('[DeleteContainer] Container model was not found among children of the main container!');
}
const newSymbols = structuredClone(current.Symbols);
const newSymbols = structuredClone(current.symbols);
UnlinkSymbol(newSymbols, container);
const index = container.parent.children.indexOf(container);
@ -90,35 +89,40 @@ export function DeleteContainer(
throw new Error('[DeleteContainer] Could not find container among parent\'s children');
}
ApplyBehaviorsOnSiblings(container, current.Symbols);
ApplyBehaviorsOnSiblings(container, current.symbols);
// Select the previous container
// or select the one above
const SelectedContainerId = GetSelectedContainerOnDelete(
const selectedContainerId = GetSelectedContainerOnDelete(
mainContainerClone,
current.SelectedContainerId,
current.selectedContainerId,
container.parent,
index
);
history.push({
LastAction: `Delete ${containerId}`,
MainContainer: mainContainerClone,
SelectedContainerId,
TypeCounters: Object.assign({}, current.TypeCounters),
Symbols: newSymbols,
SelectedSymbolId: current.SelectedSymbolId
lastAction: `Delete ${containerId}`,
mainContainer: mainContainerClone,
selectedContainerId,
typeCounters: Object.assign({}, current.typeCounters),
symbols: newSymbols,
selectedSymbolId: current.selectedSymbolId
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
}
function GetSelectedContainerOnDelete(mainContainerClone: IContainerModel, selectedContainerId: string, parent: IContainerModel, index: number): string {
const SelectedContainer = findContainerById(mainContainerClone, selectedContainerId) ??
function GetSelectedContainerOnDelete(
mainContainerClone: IContainerModel,
selectedContainerId: string,
parent: IContainerModel,
index: number
): string {
const newSelectedContainer = FindContainerById(mainContainerClone, selectedContainerId) ??
parent.children.at(index - 1) ??
parent;
const SelectedContainerId = SelectedContainer.properties.id;
return SelectedContainerId;
const newSelectedContainerId = newSelectedContainer.properties.id;
return newSelectedContainerId;
}
function UnlinkSymbol(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
@ -191,7 +195,7 @@ export function AddContainer(
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): void {
const history = getCurrentHistory(fullHistory, historyCurrentStep);
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
// Get the preset properties from the API
@ -203,15 +207,15 @@ export function AddContainer(
}
// Set the counter of the object type in order to assign an unique id
const newCounters = Object.assign({}, current.TypeCounters);
const newCounters = Object.assign({}, current.typeCounters);
UpdateCounters(newCounters, type);
const count = newCounters[type];
// Create maincontainer model
const clone: IContainerModel = structuredClone(current.MainContainer);
const clone: IContainerModel = structuredClone(current.mainContainer);
// Find the parent
const parentClone: IContainerModel | undefined = findContainerById(
const parentClone: IContainerModel | undefined = FindContainerById(
clone, parentId
);
@ -258,18 +262,18 @@ export function AddContainer(
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
ApplyBehaviors(newContainer, current.Symbols);
ApplyBehaviors(newContainer, current.symbols);
ApplyBehaviorsOnSiblings(newContainer, current.Symbols);
ApplyBehaviorsOnSiblings(newContainer, current.symbols);
// Update the state
history.push({
LastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
MainContainer: clone,
SelectedContainerId: parentClone.properties.id,
TypeCounters: newCounters,
Symbols: structuredClone(current.Symbols),
SelectedSymbolId: current.SelectedSymbolId
lastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
mainContainer: clone,
selectedContainerId: parentClone.properties.id,
typeCounters: newCounters,
symbols: structuredClone(current.symbols),
selectedSymbolId: current.selectedSymbolId
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
@ -280,8 +284,8 @@ function UpdateParentChildrenList(parentClone: IContainerModel | null | undefine
return;
}
parentClone.children.sort(
(a, b) => transformX(a.properties.x, a.properties.width, a.properties.XPositionReference) -
transformX(b.properties.x, b.properties.width, b.properties.XPositionReference)
(a, b) => TransformX(a.properties.x, a.properties.width, a.properties.xPositionReference) -
TransformX(b.properties.x, b.properties.width, b.properties.xPositionReference)
);
}
@ -387,14 +391,14 @@ function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, par
export function OnPropertyChange(
key: string,
value: string | number | boolean,
type: PropertyType = PropertyType.SIMPLE,
type: PropertyType = PropertyType.Simple,
selected: IContainerModel | undefined,
fullHistory: IHistoryState[],
historyCurrentStep: number,
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): void {
const history = getCurrentHistory(fullHistory, historyCurrentStep);
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
if (selected === null ||
@ -402,22 +406,22 @@ 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 mainContainerClone: IContainerModel = structuredClone(current.mainContainer);
const container: ContainerModel | undefined = FindContainerById(mainContainerClone, 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(container, key, value, type, current.symbols);
history.push({
LastAction: `Change ${key} of ${container.properties.id}`,
MainContainer: mainContainerClone,
SelectedContainerId: container.properties.id,
TypeCounters: Object.assign({}, current.TypeCounters),
Symbols: structuredClone(current.Symbols),
SelectedSymbolId: current.SelectedSymbolId
lastAction: `Change ${key} of ${container.properties.id}`,
mainContainer: mainContainerClone,
selectedContainerId: container.properties.id,
typeCounters: Object.assign({}, current.typeCounters),
symbols: structuredClone(current.symbols),
selectedSymbolId: current.selectedSymbolId
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
@ -463,10 +467,10 @@ function SetContainer(
function AssignProperty(container: ContainerModel, key: string, value: string | number | boolean, type: PropertyType): void {
switch (type) {
case PropertyType.STYLE:
case PropertyType.Style:
(container.properties.style as any)[key] = value;
break;
case PropertyType.MARGIN:
case PropertyType.Margin:
SetMargin();
break;
default:
@ -514,10 +518,11 @@ function LinkSymbol(
newSymbol.linkedContainers.add(containerId);
}
function ApplyBehaviorsOnSiblings(newContainer: ContainerModel, Symbols: Map<string, ISymbolModel>): void {
function ApplyBehaviorsOnSiblings(newContainer: ContainerModel, symbols: Map<string, ISymbolModel>): void {
if (newContainer.parent === null || newContainer.parent === undefined) {
return;
}
newContainer.parent.children.filter(container => newContainer !== container).forEach(container => ApplyBehaviors(container, Symbols));
newContainer.parent.children.filter(container => newContainer !== container).forEach(container => ApplyBehaviors(container, symbols));
}

View file

@ -1,6 +1,6 @@
import { IHistoryState } from '../../../Interfaces/IHistoryState';
import { IConfiguration } from '../../../Interfaces/IConfiguration';
import { getCircularReplacer } from '../../../utils/saveload';
import { GetCircularReplacer } from '../../../utils/saveload';
import { ID } from '../../SVG/SVG';
import { IEditorState } from '../../../Interfaces/IEditorState';
import { SHOW_SELECTOR_TEXT } from '../../../utils/default';
@ -26,15 +26,15 @@ export function SaveEditorAsJSON(
myWorker.onmessage = (event) => {
const data = event.data;
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
createDownloadNode(exportName, dataStr);
CreateDownloadNode(exportName, dataStr);
myWorker.terminate();
};
return;
}
const data = JSON.stringify(editorState, getCircularReplacer(), spaces);
const data = JSON.stringify(editorState, GetCircularReplacer(), spaces);
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
createDownloadNode(exportName, dataStr);
CreateDownloadNode(exportName, dataStr);
}
export function SaveEditorAsSVG(): void {
@ -64,10 +64,10 @@ export function SaveEditorAsSVG(): void {
let source = serializer.serializeToString(svg);
// add name spaces.
if (source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/) == null) {
if (source.match(/^<svg[^>]+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/) == null) {
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
if (source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/) == null) {
if (source.match(/^<svg[^>]+"http:\/\/www\.w3\.org\/1999\/xlink"/) == null) {
source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
}
@ -76,10 +76,10 @@ export function SaveEditorAsSVG(): void {
// convert svg source to URI data scheme.
const url = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source);
createDownloadNode('state.svg', url);
CreateDownloadNode('state.svg', url);
}
function createDownloadNode(filename: string, datastring: string): void {
function CreateDownloadNode(filename: string, datastring: string): void {
const downloadAnchorNode = document.createElement('a');
downloadAnchorNode.href = datastring;
downloadAnchorNode.download = filename;

View file

@ -2,7 +2,7 @@ import { Dispatch, SetStateAction } from 'react';
import { IHistoryState } from '../../../Interfaces/IHistoryState';
import { ENABLE_SHORTCUTS } from '../../../utils/default';
export function onKeyDown(
export function OnKey(
event: KeyboardEvent,
history: IHistoryState[],
historyCurrentStep: number,

View file

@ -4,10 +4,10 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { IHistoryState } from '../../../Interfaces/IHistoryState';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { GetDefaultSymbolModel } from '../../../utils/default';
import { findContainerById } from '../../../utils/itertools';
import { restoreX } from '../../../utils/svg';
import { FindContainerById } from '../../../utils/itertools';
import { RestoreX } from '../../../utils/svg';
import { ApplyBehaviors } from '../Behaviors/Behaviors';
import { getCurrentHistory, UpdateCounters } from '../Editor';
import { GetCurrentHistory, UpdateCounters } from '../Editor';
export function AddSymbol(
name: string,
@ -17,7 +17,7 @@ export function AddSymbol(
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): void {
const history = getCurrentHistory(fullHistory, historyCurrentStep);
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
const symbolConfig = configuration.AvailableSymbols
@ -27,22 +27,22 @@ export function AddSymbol(
throw new Error('[AddSymbol] Symbol could not be found in the config');
}
const type = `symbol-${name}`;
const newCounters = structuredClone(current.TypeCounters);
const newCounters = structuredClone(current.typeCounters);
UpdateCounters(newCounters, type);
const newSymbols = structuredClone(current.Symbols);
const newSymbols = structuredClone(current.symbols);
const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, newCounters, type, symbolConfig);
newSymbol.x = restoreX(newSymbol.x, newSymbol.width, newSymbol.config.XPositionReference);
newSymbol.x = RestoreX(newSymbol.x, newSymbol.width, newSymbol.config.XPositionReference);
newSymbols.set(newSymbol.id, newSymbol);
history.push({
LastAction: `Add ${name}`,
MainContainer: structuredClone(current.MainContainer),
SelectedContainerId: current.SelectedContainerId,
TypeCounters: newCounters,
Symbols: newSymbols,
SelectedSymbolId: newSymbol.id
lastAction: `Add ${name}`,
mainContainer: structuredClone(current.mainContainer),
selectedContainerId: current.selectedContainerId,
typeCounters: newCounters,
symbols: newSymbols,
selectedSymbolId: newSymbol.id
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
@ -55,16 +55,16 @@ export function SelectSymbol(
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): void {
const history = getCurrentHistory(fullHistory, historyCurrentStep);
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
history.push({
LastAction: `Select ${symbolId}`,
MainContainer: structuredClone(current.MainContainer),
SelectedContainerId: current.SelectedContainerId,
TypeCounters: structuredClone(current.TypeCounters),
Symbols: structuredClone(current.Symbols),
SelectedSymbolId: symbolId
lastAction: `Select ${symbolId}`,
mainContainer: structuredClone(current.mainContainer),
selectedContainerId: current.selectedContainerId,
typeCounters: structuredClone(current.typeCounters),
symbols: structuredClone(current.symbols),
selectedSymbolId: symbolId
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
@ -77,29 +77,29 @@ export function DeleteSymbol(
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): void {
const history = getCurrentHistory(fullHistory, historyCurrentStep);
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
const newSymbols = structuredClone(current.Symbols);
const newSymbols = structuredClone(current.symbols);
const symbol = newSymbols.get(symbolId);
if (symbol === undefined) {
throw new Error(`[DeleteSymbol] Could not find symbol in the current state!: ${symbolId}`);
}
const newMainContainer = structuredClone(current.MainContainer);
const newMainContainer = structuredClone(current.mainContainer);
UnlinkContainers(symbol, newMainContainer);
newSymbols.delete(symbolId);
history.push({
LastAction: `Select ${symbolId}`,
MainContainer: newMainContainer,
SelectedContainerId: current.SelectedContainerId,
TypeCounters: structuredClone(current.TypeCounters),
Symbols: newSymbols,
SelectedSymbolId: symbolId
lastAction: `Select ${symbolId}`,
mainContainer: newMainContainer,
selectedContainerId: current.selectedContainerId,
typeCounters: structuredClone(current.typeCounters),
symbols: newSymbols,
selectedSymbolId: symbolId
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);
@ -107,7 +107,7 @@ export function DeleteSymbol(
function UnlinkContainers(symbol: ISymbolModel, newMainContainer: IContainerModel): void {
symbol.linkedContainers.forEach((containerId) => {
const container = findContainerById(newMainContainer, containerId);
const container = FindContainerById(newMainContainer, containerId);
if (container === undefined) {
return;
@ -131,15 +131,15 @@ export function OnPropertyChange(
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): void {
const history = getCurrentHistory(fullHistory, historyCurrentStep);
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
if (current.SelectedSymbolId === '') {
if (current.selectedSymbolId === '') {
throw new Error('[OnSymbolPropertyChange] Property was changed before selecting a symbol');
}
const newSymbols: Map<string, ISymbolModel> = structuredClone(current.Symbols);
const symbol = newSymbols.get(current.SelectedSymbolId);
const newSymbols: Map<string, ISymbolModel> = structuredClone(current.symbols);
const symbol = newSymbols.get(current.selectedSymbolId);
if (symbol === null || symbol === undefined) {
throw new Error('[OnSymbolPropertyChange] Symbol model was not found in state!');
@ -147,9 +147,9 @@ export function OnPropertyChange(
(symbol as any)[key] = value;
const newMainContainer = structuredClone(current.MainContainer);
const newMainContainer = structuredClone(current.mainContainer);
symbol.linkedContainers.forEach((containerId) => {
const container = findContainerById(newMainContainer, containerId);
const container = FindContainerById(newMainContainer, containerId);
if (container === undefined) {
return;
@ -159,12 +159,12 @@ export function OnPropertyChange(
});
history.push({
LastAction: `Change ${key} of ${symbol.id}`,
MainContainer: newMainContainer,
SelectedContainerId: current.SelectedContainerId,
TypeCounters: Object.assign({}, current.TypeCounters),
Symbols: newSymbols,
SelectedSymbolId: symbol.id
lastAction: `Change ${key} of ${symbol.id}`,
mainContainer: newMainContainer,
selectedContainerId: current.selectedContainerId,
typeCounters: Object.assign({}, current.typeCounters),
symbols: newSymbols,
selectedSymbolId: symbol.id
});
setHistory(history);
setHistoryCurrentStep(history.length - 1);

View file

@ -14,7 +14,7 @@
*/
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { constraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
/**
* Impose the container position to its siblings
@ -31,9 +31,9 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
child => !child.properties.isAnchor
);
const overlappingContainers = getOverlappingContainers(container, rigidBodies);
const overlappingContainers = GetOverlappingContainers(container, rigidBodies);
for (const overlappingContainer of overlappingContainers) {
constraintBodyInsideUnallocatedWidth(overlappingContainer);
ConstraintBodyInsideUnallocatedWidth(overlappingContainer);
}
return container;
}
@ -44,7 +44,7 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
* @param containers A list of containers
* @returns A list of overlapping containers
*/
function getOverlappingContainers(
function GetOverlappingContainers(
container: IContainerModel,
containers: IContainerModel[]
): IContainerModel[] {

View file

@ -1,4 +1,3 @@
import Swal from 'sweetalert2';
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Simplex } from '../../../utils/simplex';
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';

View file

@ -1,5 +1,5 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { reversePairwise } from '../../../utils/itertools';
import { ReversePairwise } from '../../../utils/itertools';
import { Flex } from './FlexBehaviors';
/**
@ -32,7 +32,7 @@ export function PushContainers(container: IContainerModel): IContainerModel {
// FIXME: A fix was applied using toFixed(2).
// FIXME: A coverture check must be done to ensure that all scenarios are covered
const it = reversePairwise<IContainerModel>(container.parent.children.filter(child => child !== container));
const it = ReversePairwise<IContainerModel>(container.parent.children.filter(child => child !== container));
for (const { cur, next } of it) {
const hasSpaceBetween = next.properties.x + next.properties.width < cur.properties.x;

View file

@ -22,8 +22,8 @@ import { ISizePointer } from '../../../Interfaces/ISizePointer';
export function ApplyRigidBody(
container: IContainerModel
): IContainerModel {
container = constraintBodyInsideParent(container);
container = constraintBodyInsideUnallocatedWidth(container);
container = ConstraintBodyInsideParent(container);
container = ConstraintBodyInsideUnallocatedWidth(container);
return container;
}
@ -35,7 +35,7 @@ export function ApplyRigidBody(
* @param container
* @returns Updated container
*/
function constraintBodyInsideParent(
function ConstraintBodyInsideParent(
container: IContainerModel
): IContainerModel {
if (container.parent === null || container.parent === undefined) {
@ -46,7 +46,7 @@ function constraintBodyInsideParent(
const parentWidth = parentProperties.width;
const parentHeight = parentProperties.height;
return constraintBodyInsideSpace(container, 0, 0, parentWidth, parentHeight);
return ConstraintBodyInsideSpace(container, 0, 0, parentWidth, parentHeight);
}
/**
@ -59,7 +59,7 @@ function constraintBodyInsideParent(
* @param height height of the rectangle
* @returns Updated container
*/
function constraintBodyInsideSpace(
function ConstraintBodyInsideSpace(
container: IContainerModel,
x: number,
y: number,
@ -113,7 +113,7 @@ function constraintBodyInsideSpace(
* @param container
* @returns Updated container
*/
export function constraintBodyInsideUnallocatedWidth(
export function ConstraintBodyInsideUnallocatedWidth(
container: IContainerModel
): IContainerModel {
if (container.parent === null || container.parent === undefined) {
@ -121,7 +121,7 @@ export function constraintBodyInsideUnallocatedWidth(
}
// Get the available spaces of the parent
const availableWidths = getAvailableWidths(container.parent, container);
const availableWidths = GetAvailableWidths(container.parent, container);
const containerX = container.properties.x;
const containerWidth = container.properties.width;
@ -158,7 +158,7 @@ export function constraintBodyInsideUnallocatedWidth(
// Check if the container actually fit inside
// It will usually fit if it was alrady fitting
const availableWidthFound = availableWidths.find((width) =>
isFitting(container.properties.width, width)
IsFitting(container.properties.width, width)
);
if (availableWidthFound === undefined) {
@ -170,7 +170,7 @@ export function constraintBodyInsideUnallocatedWidth(
// We want the container to fit automatically inside the available space
// even if it means to resize the container
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
return isFitting(container.properties.minWidth, width);
return IsFitting(container.properties.minWidth, width);
});
if (availableWidth === undefined) {
@ -191,7 +191,7 @@ export function constraintBodyInsideUnallocatedWidth(
return container;
}
return constraintBodyInsideSpace(
return ConstraintBodyInsideSpace(
container,
availableWidthFound.x,
0,
@ -206,10 +206,10 @@ export function constraintBodyInsideUnallocatedWidth(
* @param sizePointer Size space to check
* @returns
*/
const isFitting = (
containerWidth: number,
sizePointer: ISizePointer
): boolean => containerWidth <= sizePointer.width;
function IsFitting(containerWidth: number,
sizePointer: ISizePointer): boolean {
return containerWidth <= sizePointer.width;
}
/**
* Get the unallocated widths inside a container
@ -220,7 +220,7 @@ const isFitting = (
* @param exception Container to exclude of the widths (since a container will be moved, it might need to be excluded)
* @returns {ISizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
*/
function getAvailableWidths(
function GetAvailableWidths(
container: IContainerModel,
exception: IContainerModel
): ISizePointer[] {
@ -251,7 +251,7 @@ function getAvailableWidths(
// In order to find unallocated space,
// We need to calculate the overlap between the two containers
// We only works with widths meaning in 1D (with lines)
const newUnallocatedWidths = getAvailableWidthsTwoLines(
const newUnallocatedWidths = GetAvailableWidthsTwoLines(
unallocatedSpace,
childX,
childWidth
@ -274,7 +274,7 @@ function getAvailableWidths(
* @param rectWidth width of the second line
* @returns Available widths
*/
function getAvailableWidthsTwoLines(
function GetAvailableWidthsTwoLines(
unallocatedSpace: ISizePointer,
rectX: number,
rectWidth: number
@ -295,18 +295,18 @@ function getAvailableWidthsTwoLines(
const isOverlappingOnTheLeft = unallocatedSpace.x >= rectX;
if (isOverlappingOnTheLeft) {
return getAvailableWidthsLeft(unallocatedSpaceRight, rectRight);
return GetAvailableWidthsLeft(unallocatedSpaceRight, rectRight);
}
const isOverlappingOnTheRight = rectRight >= unallocatedSpaceRight;
if (isOverlappingOnTheRight) {
return getAvailableWidthsRight(unallocatedSpace.x, rectX);
return GetAvailableWidthsRight(unallocatedSpace.x, rectX);
}
return getAvailableWidthsMiddle(unallocatedSpace.x, unallocatedSpaceRight, rectX, rectRight);
return GetAvailableWidthsMiddle(unallocatedSpace.x, unallocatedSpaceRight, rectX, rectRight);
}
function getAvailableWidthsLeft(unallocatedSpaceRight: number, rectRight: number): ISizePointer[] {
function GetAvailableWidthsLeft(unallocatedSpaceRight: number, rectRight: number): ISizePointer[] {
if (unallocatedSpaceRight - rectRight <= 0) {
return [];
}
@ -319,7 +319,7 @@ function getAvailableWidthsLeft(unallocatedSpaceRight: number, rectRight: number
];
}
function getAvailableWidthsRight(unallocatedSpaceX: number, rectX: number): ISizePointer[] {
function GetAvailableWidthsRight(unallocatedSpaceX: number, rectX: number): ISizePointer[] {
if (rectX - unallocatedSpaceX <= 0) {
return [];
}
@ -332,7 +332,7 @@ function getAvailableWidthsRight(unallocatedSpaceX: number, rectX: number): ISiz
];
}
function getAvailableWidthsMiddle(
function GetAvailableWidthsMiddle(
unallocatedSpaceX: number,
unallocatedSpaceRight: number,
rectX: number,

View file

@ -1,12 +1,12 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { applyParentTransform } from '../../../utils/itertools';
import { restoreX, transformX } from '../../../utils/svg';
import { ApplyParentTransform } from '../../../utils/itertools';
import { RestoreX, TransformX } from '../../../utils/svg';
export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel {
container.properties.x = transformX(symbol.x, symbol.width, symbol.config.XPositionReference);
container.properties.x = restoreX(container.properties.x, container.properties.width, container.properties.XPositionReference);
const [x] = applyParentTransform(container.parent, container.properties.x, 0);
container.properties.x = TransformX(symbol.x, symbol.width, symbol.config.XPositionReference);
container.properties.x = RestoreX(container.properties.x, container.properties.width, container.properties.xPositionReference);
const [x] = ApplyParentTransform(container.parent, container.properties.x, 0);
container.properties.x = x;
return container;
}

View file

@ -6,12 +6,12 @@ import { IHistoryState } from '../../Interfaces/IHistoryState';
import { UI } from '../UI/UI';
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, OnPropertyChange } from './Actions/ContainerOperations';
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
import { onKeyDown } from './Actions/Shortcuts';
import { OnKey } from './Actions/Shortcuts';
import EditorEvents from '../../Events/EditorEvents';
import { IEditorState } from '../../Interfaces/IEditorState';
import { MAX_HISTORY } from '../../utils/default';
import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations';
import { findContainerById } from '../../utils/itertools';
import { FindContainerById } from '../../utils/itertools';
interface IEditorProps {
root: Element | Document
@ -29,35 +29,40 @@ export function UpdateCounters(counters: Record<string, number>, type: string):
}
}
export const getCurrentHistory = (history: IHistoryState[], historyCurrentStep: number): IHistoryState[] =>
history.slice(
Math.max(0, history.length - MAX_HISTORY), // change this to 0 for unlimited (not recommanded because of overflow)
export function GetCurrentHistory(history: IHistoryState[], historyCurrentStep: number): IHistoryState[] {
return history.slice(
Math.max(0, history.length - MAX_HISTORY),
historyCurrentStep + 1
);
}
export const getCurrentHistoryState = (history: IHistoryState[], historyCurrentStep: number): IHistoryState => history[historyCurrentStep];
export function GetCurrentHistoryState(history: IHistoryState[], historyCurrentStep: number): IHistoryState {
return history[historyCurrentStep];
}
function useShortcuts(
function UseShortcuts(
history: IHistoryState[],
historyCurrentStep: number,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
): void {
useEffect(() => {
const onKeyUp = (event: KeyboardEvent): void => onKeyDown(
function OnKeyUp(event: KeyboardEvent): void {
return OnKey(
event,
history,
historyCurrentStep,
setHistoryCurrentStep
);
}
window.addEventListener('keyup', onKeyUp);
window.addEventListener('keyup', OnKeyUp);
return () => {
window.removeEventListener('keyup', onKeyUp);
window.removeEventListener('keyup', OnKeyUp);
};
});
}
function useWindowEvents(
function UseWindowEvents(
root: Element | Document,
history: IHistoryState[],
historyCurrentStep: number,
@ -76,15 +81,17 @@ function useWindowEvents(
const funcs = new Map<string, () => void>();
for (const event of events) {
const func = (eventInitDict?: CustomEventInit): void => event.func(
function Func(eventInitDict?: CustomEventInit): void {
return event.func(
root,
editorState,
setHistory,
setHistoryCurrentStep,
eventInitDict
);
editorRef.current?.addEventListener(event.name, func);
funcs.set(event.name, func);
}
editorRef.current?.addEventListener(event.name, Func);
funcs.set(event.name, Func);
}
return () => {
for (const event of events) {
@ -98,13 +105,13 @@ function useWindowEvents(
});
}
const Editor: React.FunctionComponent<IEditorProps> = (props) => {
export function Editor(props: IEditorProps): JSX.Element {
const [history, setHistory] = React.useState<IHistoryState[]>(structuredClone(props.history));
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
const editorRef = useRef<HTMLDivElement>(null);
useShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
useWindowEvents(
UseShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
UseWindowEvents(
props.root,
history,
historyCurrentStep,
@ -115,32 +122,32 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
);
const configuration = props.configuration;
const current = getCurrentHistoryState(history, historyCurrentStep);
const selected = findContainerById(current.MainContainer, current.SelectedContainerId);
const current = GetCurrentHistoryState(history, historyCurrentStep);
const selected = FindContainerById(current.mainContainer, current.selectedContainerId);
return (
<div ref={editorRef} className="Editor font-sans h-full">
<UI
SelectedContainer={selected}
selectedContainer={selected}
current={current}
history={history}
historyCurrentStep={historyCurrentStep}
AvailableContainers={configuration.AvailableContainers}
AvailableSymbols={configuration.AvailableSymbols}
SelectContainer={(container) => SelectContainer(
availableContainers={configuration.AvailableContainers}
availableSymbols={configuration.AvailableSymbols}
selectContainer={(container) => SelectContainer(
container,
history,
historyCurrentStep,
setHistory,
setHistoryCurrentStep
)}
DeleteContainer={(containerId: string) => DeleteContainer(
deleteContainer={(containerId: string) => DeleteContainer(
containerId,
history,
historyCurrentStep,
setHistory,
setHistoryCurrentStep
)}
OnPropertyChange={(key, value, type) => OnPropertyChange(
onPropertyChange={(key, value, type) => OnPropertyChange(
key, value, type,
selected,
history,
@ -148,7 +155,7 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
setHistory,
setHistoryCurrentStep
)}
AddContainer={(type) => AddContainerToSelectedContainer(
addContainer={(type) => AddContainerToSelectedContainer(
type,
selected,
configuration,
@ -157,7 +164,7 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
setHistory,
setHistoryCurrentStep
)}
AddSymbol={(type) => AddSymbol(
addSymbol={(type) => AddSymbol(
type,
configuration,
history,
@ -165,45 +172,42 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
setHistory,
setHistoryCurrentStep
)}
OnSymbolPropertyChange={(key, value) => OnSymbolPropertyChange(
onSymbolPropertyChange={(key, value) => OnSymbolPropertyChange(
key, value,
history,
historyCurrentStep,
setHistory,
setHistoryCurrentStep
)}
SelectSymbol={(symbolId) => SelectSymbol(
selectSymbol={(symbolId) => SelectSymbol(
symbolId,
history,
historyCurrentStep,
setHistory,
setHistoryCurrentStep
)}
DeleteSymbol={(symbolId) => DeleteSymbol(
deleteSymbol={(symbolId) => DeleteSymbol(
symbolId,
history,
historyCurrentStep,
setHistory,
setHistoryCurrentStep
)}
SaveEditorAsJSON={() => SaveEditorAsJSON(
saveEditorAsJSON={() => SaveEditorAsJSON(
history,
historyCurrentStep,
configuration
)}
SaveEditorAsSVG={() => SaveEditorAsSVG()}
LoadState={(move) => setHistoryCurrentStep(move)}
/>
saveEditorAsSVG={() => SaveEditorAsSVG()}
loadState={(move) => setHistoryCurrentStep(move)} />
<SVG
width={current.MainContainer?.properties.width}
height={current.MainContainer?.properties.height}
width={current.mainContainer?.properties.width}
height={current.mainContainer?.properties.height}
selected={selected}
symbols={current.Symbols}
symbols={current.symbols}
>
{ current.MainContainer }
{current.mainContainer}
</SVG>
</div>
);
};
export default Editor;
}

View file

@ -4,13 +4,13 @@ import { fireEvent, render, screen } from '../../utils/test-utils';
import { ElementsSidebar } from './ElementsSidebar';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { XPositionReference } from '../../Enums/XPositionReference';
import { findContainerById } from '../../utils/itertools';
import { FindContainerById } from '../../utils/itertools';
describe.concurrent('Elements sidebar', () => {
it('With a MainContainer', () => {
render(<ElementsSidebar
symbols={new Map()}
MainContainer={{
mainContainer={{
children: [],
parent: null,
properties: {
@ -27,17 +27,17 @@ describe.concurrent('Elements sidebar', () => {
type: 'type',
maxWidth: Infinity,
isFlex: false,
XPositionReference: XPositionReference.Left,
xPositionReference: XPositionReference.Left,
isAnchor: false
},
userData: {}
}}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={undefined}
OnPropertyChange={() => {}}
SelectContainer={() => {}}
DeleteContainer={() => {}}
selectedContainer={undefined}
onPropertyChange={() => {}}
selectContainer={() => {}}
deleteContainer={() => {}}
/>);
expect(screen.getByText(/Elements/i));
@ -46,7 +46,7 @@ describe.concurrent('Elements sidebar', () => {
});
it('With a selected MainContainer', () => {
const MainContainer: IContainerModel = {
const mainContainer: IContainerModel = {
children: [],
parent: null,
properties: {
@ -64,20 +64,20 @@ describe.concurrent('Elements sidebar', () => {
maxWidth: Infinity,
type: 'type',
isAnchor: false,
XPositionReference: XPositionReference.Left
xPositionReference: XPositionReference.Left
},
userData: {}
};
const { container } = render(<ElementsSidebar
symbols={new Map()}
MainContainer={MainContainer}
mainContainer={mainContainer}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={MainContainer}
OnPropertyChange={() => {}}
SelectContainer={() => {}}
DeleteContainer={() => {}}
selectedContainer={mainContainer}
onPropertyChange={() => {}}
selectContainer={() => {}}
deleteContainer={() => {}}
/>);
expect(screen.getByText(/Elements/i));
@ -94,22 +94,22 @@ describe.concurrent('Elements sidebar', () => {
const propertyY = container.querySelector('#y');
const propertyWidth = container.querySelector('#width');
const propertyHeight = container.querySelector('#height');
expect((propertyId as HTMLInputElement).value).toBe(MainContainer.properties.id.toString());
expect((propertyId as HTMLInputElement).value).toBe(mainContainer.properties.id.toString());
expect(propertyParentId).toBeDefined();
expect((propertyParentId as HTMLInputElement).value).toBe('');
expect(propertyX).toBeDefined();
expect((propertyX as HTMLInputElement).value).toBe(MainContainer.properties.x.toString());
expect((propertyX as HTMLInputElement).value).toBe(mainContainer.properties.x.toString());
expect(propertyY).toBeDefined();
expect((propertyY as HTMLInputElement).value).toBe(MainContainer.properties.y.toString());
expect((propertyY as HTMLInputElement).value).toBe(mainContainer.properties.y.toString());
expect(propertyWidth).toBeDefined();
expect((propertyWidth as HTMLInputElement).value).toBe(MainContainer.properties.width.toString());
expect((propertyWidth as HTMLInputElement).value).toBe(mainContainer.properties.width.toString());
expect(propertyHeight).toBeDefined();
expect((propertyHeight as HTMLInputElement).value).toBe(MainContainer.properties.height.toString());
expect((propertyHeight as HTMLInputElement).value).toBe(mainContainer.properties.height.toString());
});
it('With multiple containers', () => {
const children: IContainerModel[] = [];
const MainContainer: IContainerModel = {
const mainContainer: IContainerModel = {
children,
parent: null,
properties: {
@ -122,7 +122,7 @@ describe.concurrent('Elements sidebar', () => {
minWidth: 1,
width: 2000,
height: 100,
XPositionReference: XPositionReference.Left,
xPositionReference: XPositionReference.Left,
margin: {},
isFlex: false,
maxWidth: Infinity,
@ -135,7 +135,7 @@ describe.concurrent('Elements sidebar', () => {
children.push(
{
children: [],
parent: MainContainer,
parent: mainContainer,
properties: {
id: 'child-1',
parentId: 'main',
@ -151,7 +151,7 @@ describe.concurrent('Elements sidebar', () => {
maxWidth: Infinity,
type: 'type',
isAnchor: false,
XPositionReference: XPositionReference.Left
xPositionReference: XPositionReference.Left
},
userData: {}
}
@ -160,7 +160,7 @@ describe.concurrent('Elements sidebar', () => {
children.push(
{
children: [],
parent: MainContainer,
parent: mainContainer,
properties: {
id: 'child-2',
parentId: 'main',
@ -172,7 +172,7 @@ describe.concurrent('Elements sidebar', () => {
minWidth: 1,
width: 0,
height: 0,
XPositionReference: XPositionReference.Left,
xPositionReference: XPositionReference.Left,
isFlex: false,
maxWidth: Infinity,
type: 'type',
@ -184,13 +184,13 @@ describe.concurrent('Elements sidebar', () => {
render(<ElementsSidebar
symbols={new Map()}
MainContainer={MainContainer}
mainContainer={mainContainer}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={MainContainer}
OnPropertyChange={() => {}}
SelectContainer={() => {}}
DeleteContainer={() => {}}
selectedContainer={mainContainer}
onPropertyChange={() => {}}
selectContainer={() => {}}
deleteContainer={() => {}}
/>);
expect(screen.getByText(/Elements/i));
@ -202,7 +202,7 @@ describe.concurrent('Elements sidebar', () => {
it('With multiple containers, change selection', () => {
const children: IContainerModel[] = [];
const MainContainer: IContainerModel = {
const mainContainer: IContainerModel = {
children,
parent: null,
properties: {
@ -215,7 +215,7 @@ describe.concurrent('Elements sidebar', () => {
minWidth: 1,
width: 2000,
height: 100,
XPositionReference: XPositionReference.Left,
xPositionReference: XPositionReference.Left,
margin: {},
isFlex: false,
maxWidth: Infinity,
@ -227,7 +227,7 @@ describe.concurrent('Elements sidebar', () => {
const child1Model: IContainerModel = {
children: [],
parent: MainContainer,
parent: mainContainer,
properties: {
id: 'child-1',
parentId: 'main',
@ -238,7 +238,7 @@ describe.concurrent('Elements sidebar', () => {
minWidth: 1,
width: 0,
height: 0,
XPositionReference: XPositionReference.Left,
xPositionReference: XPositionReference.Left,
margin: {},
isFlex: false,
maxWidth: Infinity,
@ -249,20 +249,20 @@ describe.concurrent('Elements sidebar', () => {
};
children.push(child1Model);
let SelectedContainer: IContainerModel | undefined = MainContainer;
let selectedContainer: IContainerModel | undefined = mainContainer;
const selectContainer = vi.fn((containerId: string) => {
SelectedContainer = findContainerById(MainContainer, containerId);
selectedContainer = FindContainerById(mainContainer, containerId);
});
const { container, rerender } = render(<ElementsSidebar
symbols={new Map()}
MainContainer={MainContainer}
mainContainer={mainContainer}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={SelectedContainer}
OnPropertyChange={() => {}}
SelectContainer={selectContainer}
DeleteContainer={() => {}}
selectedContainer={selectedContainer}
onPropertyChange={() => {}}
selectContainer={selectContainer}
deleteContainer={() => {}}
/>);
expect(screen.getByText(/Elements/i));
@ -272,20 +272,20 @@ describe.concurrent('Elements sidebar', () => {
expect(child1);
const propertyId = container.querySelector('#id');
const propertyParentId = container.querySelector('#parentId');
expect((propertyId as HTMLInputElement).value).toBe(MainContainer.properties.id.toString());
expect((propertyId as HTMLInputElement).value).toBe(mainContainer.properties.id.toString());
expect((propertyParentId as HTMLInputElement).value).toBe('');
fireEvent.click(child1);
rerender(<ElementsSidebar
symbols={new Map()}
MainContainer={MainContainer}
mainContainer={mainContainer}
isOpen={true}
isHistoryOpen={false}
SelectedContainer={SelectedContainer}
OnPropertyChange={() => {}}
SelectContainer={selectContainer}
DeleteContainer={() => {}}
selectedContainer={selectedContainer}
onPropertyChange={() => {}}
selectContainer={selectContainer}
deleteContainer={() => {}}
/>);
expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy();

View file

@ -2,37 +2,33 @@ import * as React from 'react';
import { FixedSizeList as List } from 'react-window';
import { Properties } from '../ContainerProperties/ContainerProperties';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { getDepth, MakeIterator } from '../../utils/itertools';
import { GetDepth, MakeIterator } from '../../utils/itertools';
import { Menu } from '../Menu/Menu';
import { MenuItem } from '../Menu/MenuItem';
import { useMouseEvents } from './MouseEventHandlers';
import { UseMouseEvents } from './MouseEventHandlers';
import { IPoint } from '../../Interfaces/IPoint';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { PropertyType } from '../../Enums/PropertyType';
interface IElementsSidebarProps {
MainContainer: IContainerModel
mainContainer: IContainerModel
symbols: Map<string, ISymbolModel>
isOpen: boolean
isHistoryOpen: boolean
SelectedContainer: IContainerModel | undefined
OnPropertyChange: (
selectedContainer: IContainerModel | undefined
onPropertyChange: (
key: string,
value: string | number | boolean,
type?: PropertyType
) => void
SelectContainer: (containerId: string) => void
DeleteContainer: (containerid: string) => void
selectContainer: (containerId: string) => void
deleteContainer: (containerid: string) => void
}
export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
props: IElementsSidebarProps
): JSX.Element => {
export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
// States
const [isContextMenuOpen, setIsContextMenuOpen] =
React.useState<boolean>(false);
const [onClickContainerId, setOnClickContainerId] =
React.useState<string>('');
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
const [onClickContainerId, setOnClickContainerId] = React.useState<string>('');
const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
x: 0,
y: 0
@ -41,7 +37,7 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
const elementRef = React.useRef<HTMLDivElement>(null);
// Event listeners
useMouseEvents(
UseMouseEvents(
isContextMenuOpen,
elementRef,
setIsContextMenuOpen,
@ -55,28 +51,23 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
isOpenClasses = props.isHistoryOpen ? 'right-64' : 'right-0';
}
const it = MakeIterator(props.MainContainer);
const it = MakeIterator(props.mainContainer);
const containers = [...it];
const Row = ({
index,
style
function Row({
index, style
}: {
index: number
style: React.CSSProperties
}): JSX.Element => {
}): JSX.Element {
const container = containers[index];
const depth: number = getDepth(container);
const depth: number = GetDepth(container);
const key = container.properties.id.toString();
const text =
container.properties.displayedText === key
const text = container.properties.displayedText === key
? `${'|\t'.repeat(depth)} ${key}`
: `${'|\t'.repeat(depth)} ${
container.properties.displayedText
} (${key})`;
const selectedClass: string =
props.SelectedContainer !== undefined &&
props.SelectedContainer !== null &&
props.SelectedContainer.properties.id === container.properties.id
: `${'|\t'.repeat(depth)} ${container.properties.displayedText} (${key})`;
const selectedClass: string = props.selectedContainer !== undefined &&
props.selectedContainer !== null &&
props.selectedContainer.properties.id === container.properties.id
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
: 'bg-slate-300/60 hover:bg-slate-300';
@ -87,12 +78,12 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
id={key}
key={key}
style={style}
onClick={() => props.SelectContainer(container.properties.id)}
onClick={() => props.selectContainer(container.properties.id)}
>
{text}
</button>
);
};
}
return (
<div
@ -121,15 +112,13 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
text="Delete"
onClick={() => {
setIsContextMenuOpen(false);
props.DeleteContainer(onClickContainerId);
}}
/>
props.deleteContainer(onClickContainerId);
} } />
</Menu>
<Properties
properties={props.SelectedContainer?.properties}
properties={props.selectedContainer?.properties}
symbols={props.symbols}
onChange={props.OnPropertyChange}
/>
onChange={props.onPropertyChange} />
</div>
);
};
}

View file

@ -1,7 +1,7 @@
import React, { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
import { IPoint } from '../../Interfaces/IPoint';
export function useMouseEvents(
export function UseMouseEvents(
isContextMenuOpen: boolean,
elementRef: RefObject<HTMLDivElement>,
setIsContextMenuOpen: Dispatch<SetStateAction<boolean>>,
@ -9,44 +9,48 @@ export function useMouseEvents(
setContextMenuPosition: Dispatch<SetStateAction<IPoint>>
): void {
useEffect(() => {
const onContextMenu = (event: MouseEvent): void => handleRightClick(
function OnContextMenu(event: MouseEvent): void {
return HandleRightClick(
event,
setIsContextMenuOpen,
setOnClickContainerId,
setContextMenuPosition
);
}
const onLeftClick = (): void => handleLeftClick(
function OnLeftClick(): void {
return HandleLeftClick(
isContextMenuOpen,
setIsContextMenuOpen,
setOnClickContainerId
);
}
elementRef.current?.addEventListener(
'contextmenu',
onContextMenu
OnContextMenu
);
window.addEventListener(
'click',
onLeftClick
OnLeftClick
);
return () => {
elementRef.current?.removeEventListener(
'contextmenu',
onContextMenu
OnContextMenu
);
window.removeEventListener(
'click',
onLeftClick
OnLeftClick
);
};
});
}
export function handleRightClick(
export function HandleRightClick(
event: MouseEvent,
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>,
@ -66,7 +70,7 @@ export function handleRightClick(
setContextMenuPosition(contextMenuPosition);
}
export function handleLeftClick(
export function HandleLeftClick(
isContextMenuOpen: boolean,
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>
@ -78,4 +82,3 @@ export function handleLeftClick(
setIsContextMenuOpen(false);
setOnClickContainerId('');
}

View file

@ -6,14 +6,12 @@ interface IFloatingButtonProps {
className: string
}
const toggleState = (
isHidden: boolean,
setHidden: React.Dispatch<React.SetStateAction<boolean>>
): void => {
function ToggleState(isHidden: boolean,
setHidden: React.Dispatch<React.SetStateAction<boolean>>): void {
setHidden(!isHidden);
};
}
export const FloatingButton: React.FC<IFloatingButtonProps> = (props: IFloatingButtonProps) => {
export function FloatingButton(props: IFloatingButtonProps): JSX.Element {
const [isHidden, setHidden] = React.useState(true);
const buttonListClasses = isHidden ? 'invisible opacity-0' : 'visible opacity-100';
const icon = isHidden
@ -28,9 +26,9 @@ export const FloatingButton: React.FC<IFloatingButtonProps> = (props: IFloatingB
<button type="button"
className={'transition-all w-14 h-14 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
title='Open menu'
onClick={() => toggleState(isHidden, setHidden)}
onClick={() => ToggleState(isHidden, setHidden)}
>
{icon}
</button>
</div>);
};
}

View file

@ -9,12 +9,12 @@ interface IHistoryProps {
jumpTo: (move: number) => void
}
export const History: React.FC<IHistoryProps> = (props: IHistoryProps) => {
export function History(props: IHistoryProps): JSX.Element {
const isOpenClasses = props.isOpen ? 'right-0' : '-right-64';
const Row = ({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element => {
function Row({ index, style }: { index: number, style: React.CSSProperties }): JSX.Element {
const reversedIndex = (props.history.length - 1) - index;
const step = props.history[reversedIndex];
const desc = step.LastAction;
const desc = step.lastAction;
const selectedClass = reversedIndex === props.historyCurrentStep
? 'bg-blue-500 hover:bg-blue-600'
@ -25,16 +25,14 @@ export const History: React.FC<IHistoryProps> = (props: IHistoryProps) => {
key={reversedIndex}
style={style}
onClick={() => props.jumpTo(reversedIndex)}
title={step.LastAction}
className={
`w-full elements-sidebar-row whitespace-pre overflow-hidden
text-left text-sm font-medium transition-all ${selectedClass}`
}
title={step.lastAction}
className={`w-full elements-sidebar-row whitespace-pre overflow-hidden
text-left text-sm font-medium transition-all ${selectedClass}`}
>
{desc}
</button>
);
};
}
return (
<div className={`fixed flex flex-col bg-slate-300 text-white transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
@ -52,4 +50,4 @@ export const History: React.FC<IHistoryProps> = (props: IHistoryProps) => {
</List>
</div>
);
};
}

View file

@ -23,7 +23,7 @@ const className = `
focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500
disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none`;
export const InputGroup: React.FunctionComponent<IInputGroupProps> = (props) => {
export function InputGroup(props: IInputGroupProps): JSX.Element {
return <>
<label
key={props.labelKey}
@ -44,7 +44,6 @@ export const InputGroup: React.FunctionComponent<IInputGroupProps> = (props) =>
defaultChecked={props.defaultChecked}
onChange={props.onChange}
min={props.min}
disabled={props.isDisabled}
/>
disabled={props.isDisabled} />
</>;
};
}

View file

@ -6,14 +6,14 @@ interface IMainMenuProps {
}
enum WindowState {
MAIN,
LOAD,
Main,
Load,
}
export const MainMenu: React.FC<IMainMenuProps> = (props) => {
const [windowState, setWindowState] = React.useState(WindowState.MAIN);
export function MainMenu(props: IMainMenuProps): JSX.Element {
const [windowState, setWindowState] = React.useState(WindowState.Main);
switch (windowState) {
case WindowState.LOAD:
case WindowState.Load:
return (
<div className='flex flex-col drop-shadow-lg bg-blue-50 p-12 rounded-lg absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
<form className="flex items-center space-x-6">
@ -37,7 +37,7 @@ export const MainMenu: React.FC<IMainMenuProps> = (props) => {
</label>
</form>
<button type="button"
onClick={() => setWindowState(WindowState.MAIN)}
onClick={() => setWindowState(WindowState.Main)}
className='normal-btn block
mt-8 '
>
@ -50,7 +50,7 @@ export const MainMenu: React.FC<IMainMenuProps> = (props) => {
return (
<div className='absolute bg-blue-50 p-12 rounded-lg drop-shadow-lg grid grid-cols-1 md:grid-cols-2 gap-8 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
<button type="button" className='mainmenu-btn' onClick={props.newEditor}>Start from scratch</button>
<button type="button" className='mainmenu-btn' onClick={() => setWindowState(WindowState.LOAD)}>Load a configuration file</button>
<button type="button" className='mainmenu-btn' onClick={() => setWindowState(WindowState.Load)}>Load a configuration file</button>
</div>
);
}

View file

@ -8,7 +8,7 @@ interface IMenuProps {
children: React.ReactNode[] | React.ReactNode
}
export const Menu: React.FC<IMenuProps> = (props) => {
export function Menu(props: IMenuProps): JSX.Element {
const visible = props.isOpen ? 'visible opacity-1' : 'invisible opacity-0';
return (
<div
@ -20,4 +20,4 @@ export const Menu: React.FC<IMenuProps> = (props) => {
{props.children}
</div>
);
};
}

View file

@ -6,11 +6,11 @@ interface IMenuItemProps {
onClick: () => void
}
export const MenuItem: React.FC<IMenuItemProps> = (props) => {
export function MenuItem(props: IMenuItemProps): JSX.Element {
return (
<button type="button"
className={props.className}
onClick={() => props.onClick()}>{props.text}
</button>
);
};
}

View file

@ -11,7 +11,7 @@ interface IRadioGroupButtonsProps {
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
}
export const RadioGroupButtons: React.FunctionComponent<IRadioGroupButtonsProps> = (props) => {
export function RadioGroupButtons(props: IRadioGroupButtonsProps): JSX.Element {
let inputGroups;
if (props.value !== undefined) {
// dynamic
@ -24,8 +24,7 @@ export const RadioGroupButtons: React.FunctionComponent<IRadioGroupButtonsProps>
className={`peer m-2 ${props.inputClassName}`}
value={inputGroup.value}
checked={props.value === inputGroup.value}
onChange={props.onChange}
/>
onChange={props.onChange} />
<label htmlFor={inputGroup.value} className='text-gray-400 peer-checked:text-blue-500'>
{inputGroup.text}
</label>
@ -42,8 +41,7 @@ export const RadioGroupButtons: React.FunctionComponent<IRadioGroupButtonsProps>
name={props.name}
className={`peer m-2 ${props.inputClassName}`}
value={inputGroup.value}
defaultChecked={props.defaultValue === inputGroup.value}
/>
defaultChecked={props.defaultValue === inputGroup.value} />
<label htmlFor={inputGroup.value} className='text-gray-400 peer-checked:text-blue-500'>
{inputGroup.text}
</label>
@ -63,4 +61,4 @@ export const RadioGroupButtons: React.FunctionComponent<IRadioGroupButtonsProps>
</div>
</>
);
};
}

View file

@ -2,11 +2,11 @@ import * as React from 'react';
import { Interweave, Node } from 'interweave';
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN, SHOW_CHILDREN_DIMENSIONS, SHOW_PARENT_DIMENSION, SHOW_TEXT } from '../../../utils/default';
import { getDepth } from '../../../utils/itertools';
import { GetDepth } from '../../../utils/itertools';
import { Dimension } from './Dimension';
import IContainerProperties from '../../../Interfaces/IContainerProperties';
import { transformX } from '../../../utils/svg';
import { camelize } from '../../../utils/stringtools';
import { IContainerProperties } from '../../../Interfaces/IContainerProperties';
import { TransformX } from '../../../utils/svg';
import { Camelize } from '../../../utils/stringtools';
interface IContainerProps {
model: IContainerModel
@ -16,7 +16,7 @@ interface IContainerProps {
* Render the container
* @returns Render the container
*/
export const Container: React.FC<IContainerProps> = (props: IContainerProps) => {
export function Container(props: IContainerProps): JSX.Element {
const containersElements = props.model.children.map(child => <Container key={`container-${child.properties.id}`} model={child} />);
const width: number = props.model.properties.width;
@ -52,7 +52,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
>
</rect>);
// Dimension props
const depth = getDepth(props.model);
const depth = GetDepth(props.model);
const dimensionMargin = DIMENSION_MARGIN * depth;
const id = `dim-${props.model.properties.id}`;
const xStart: number = 0;
@ -64,11 +64,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
let dimensionChildren: JSX.Element | null = null;
if (props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
const {
childrenId,
xChildrenStart,
xChildrenEnd,
yChildren,
textChildren
childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren
} = GetChildrenDimensionProps(props, dimensionMargin);
dimensionChildren = <Dimension
id={childrenId}
@ -77,8 +73,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
yStart={yChildren}
yEnd={yChildren}
strokeWidth={strokeWidth}
text={textChildren}
/>;
text={textChildren} />;
}
return (
@ -95,10 +90,8 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
yStart={yDim}
yEnd={yDim}
strokeWidth={strokeWidth}
text={text}
/>
: null
}
text={text} />
: null}
{dimensionChildren}
{svg}
{SHOW_TEXT
@ -112,23 +105,23 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
{containersElements}
</g>
);
};
}
function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: number): { childrenId: string, xChildrenStart: number, xChildrenEnd: number, yChildren: number, textChildren: string } {
const childrenId = `dim-children-${props.model.properties.id}`;
const lastChild = props.model.children[props.model.children.length - 1];
let xChildrenStart = transformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.XPositionReference);
let xChildrenEnd = transformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.XPositionReference);
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.xPositionReference);
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.xPositionReference);
// Find the min and max
for (let i = props.model.children.length - 2; i >= 0; i--) {
const child = props.model.children[i];
const left = transformX(child.properties.x, child.properties.width, child.properties.XPositionReference);
const left = TransformX(child.properties.x, child.properties.width, child.properties.xPositionReference);
if (left < xChildrenStart) {
xChildrenStart = left;
}
const right = transformX(child.properties.x, child.properties.width, child.properties.XPositionReference);
const right = TransformX(child.properties.x, child.properties.width, child.properties.xPositionReference);
if (right > xChildrenEnd) {
xChildrenEnd = right;
}
@ -145,11 +138,11 @@ function CreateReactCustomSVG(customSVG: string, props: IContainerProperties): R
disableLineBreaks={true}
content={customSVG}
allowElements={true}
transform={(node, children) => transform(node, children, props)}
transform={(node, children) => Transform(node, children, props)}
/>;
}
function transform(node: HTMLElement, children: Node[], props: IContainerProperties): React.ReactNode {
function Transform(node: HTMLElement, children: Node[], props: IContainerProperties): React.ReactNode {
const supportedTags = ['line', 'path', 'rect'];
if (supportedTags.includes(node.tagName.toLowerCase())) {
const attributes: { [att: string]: string | object | null } = {};
@ -170,7 +163,7 @@ function transform(node: HTMLElement, children: Node[], props: IContainerPropert
const prop = Object.entries(props.userData).find(([key]) => `{${key}}` === userDataKey);
if (prop !== undefined) {
attributes[camelize(attName)] = prop[1];
attributes[Camelize(attName)] = prop[1];
return;
}
}
@ -179,16 +172,16 @@ function transform(node: HTMLElement, children: Node[], props: IContainerPropert
// support for object
const stringObject = attributeValue.slice(1, -1);
const object: JSON = JSON.parse(stringObject);
attributes[camelize(attName)] = object;
attributes[Camelize(attName)] = object;
return;
}
const prop = Object.entries(props).find(([key]) => `{${key}}` === attributeValue);
if (prop !== undefined) {
attributes[camelize(attName)] = prop[1];
attributes[Camelize(attName)] = prop[1];
return;
}
attributes[camelize(attName)] = attributeValue;
attributes[Camelize(attName)] = attributeValue;
});
return React.createElement(node.tagName.toLowerCase(), attributes, children);
}

View file

@ -1,15 +1,15 @@
import * as React from 'react';
import { ContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN } from '../../../utils/default';
import { getAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
import { transformX } from '../../../utils/svg';
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
import { TransformX } from '../../../utils/svg';
import { Dimension } from './Dimension';
interface IDimensionLayerProps {
roots: ContainerModel | ContainerModel[] | null
}
const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
const it = MakeBFSIterator(root);
const dimensions: React.ReactNode[] = [];
let currentDepth = 0;
@ -25,8 +25,8 @@ const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
max = -Infinity;
}
const absoluteX = getAbsolutePosition(container)[0];
const x = transformX(absoluteX, container.properties.width, container.properties.XPositionReference);
const absoluteX = GetAbsolutePosition(container)[0];
const x = TransformX(absoluteX, container.properties.width, container.properties.xPositionReference);
lastY = container.properties.y + container.properties.height;
if (x < min) {
min = x;
@ -40,28 +40,28 @@ const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
AddNewDimension(currentDepth, min, max, lastY, dimensions);
return dimensions;
};
}
/**
* A layer containing all dimension
* @param props
* @returns
*/
export const DepthDimensionLayer: React.FC<IDimensionLayerProps> = (props: IDimensionLayerProps) => {
export function DepthDimensionLayer(props: IDimensionLayerProps): JSX.Element {
let dimensions: React.ReactNode[] = [];
if (Array.isArray(props.roots)) {
props.roots.forEach(child => {
dimensions.concat(getDimensionsNodes(child));
dimensions.concat(GetDimensionsNodes(child));
});
} else if (props.roots !== null) {
dimensions = getDimensionsNodes(props.roots);
dimensions = GetDimensionsNodes(props.roots);
}
return (
<g>
{dimensions}
</g>
);
};
}
function AddNewDimension(currentDepth: number, min: number, max: number, lastY: number, dimensions: React.ReactNode[]): void {
const id = `dim-depth-${currentDepth}`;

View file

@ -20,9 +20,11 @@ interface IDimensionProps {
* @param vx Transform vector
* @returns Returns a new coordinate from the origin coordinate
*/
const applyParametric = (x0: number, t: number, vx: number): number => x0 + t * vx;
function ApplyParametric(x0: number, t: number, vx: number): number {
return x0 + t * vx;
}
export const Dimension: React.FC<IDimensionProps> = (props: IDimensionProps) => {
export function Dimension(props: IDimensionProps): JSX.Element {
const style: React.CSSProperties = {
stroke: 'black'
};
@ -39,15 +41,15 @@ export const Dimension: React.FC<IDimensionProps> = (props: IDimensionProps) =>
const [perpVecX, perpVecY] = [unitY, -unitX];
// Use the parametric function to get the coordinates (x = x0 + t * v.x)
const startTopX = applyParametric(props.xStart, NOTCHES_LENGTH, perpVecX);
const startTopY = applyParametric(props.yStart, NOTCHES_LENGTH, perpVecY);
const startBottomX = applyParametric(props.xStart, -NOTCHES_LENGTH, perpVecX);
const startBottomY = applyParametric(props.yStart, -NOTCHES_LENGTH, perpVecY);
const startTopX = ApplyParametric(props.xStart, NOTCHES_LENGTH, perpVecX);
const startTopY = ApplyParametric(props.yStart, NOTCHES_LENGTH, perpVecY);
const startBottomX = ApplyParametric(props.xStart, -NOTCHES_LENGTH, perpVecX);
const startBottomY = ApplyParametric(props.yStart, -NOTCHES_LENGTH, perpVecY);
const endTopX = applyParametric(props.xEnd, NOTCHES_LENGTH, perpVecX);
const endTopY = applyParametric(props.yEnd, NOTCHES_LENGTH, perpVecY);
const endBottomX = applyParametric(props.xEnd, -NOTCHES_LENGTH, perpVecX);
const endBottomY = applyParametric(props.yEnd, -NOTCHES_LENGTH, perpVecY);
const endTopX = ApplyParametric(props.xEnd, NOTCHES_LENGTH, perpVecX);
const endTopY = ApplyParametric(props.yEnd, NOTCHES_LENGTH, perpVecY);
const endBottomX = ApplyParametric(props.xEnd, -NOTCHES_LENGTH, perpVecX);
const endBottomY = ApplyParametric(props.yEnd, -NOTCHES_LENGTH, perpVecY);
return (
<g key={props.id}>
@ -57,24 +59,21 @@ export const Dimension: React.FC<IDimensionProps> = (props: IDimensionProps) =>
x2={startBottomX}
y2={startBottomY}
strokeWidth={props.strokeWidth}
style={style}
/>
style={style} />
<line
x1={props.xStart}
y1={props.yStart}
x2={props.xEnd}
y2={props.yEnd}
strokeWidth={props.strokeWidth}
style={style}
/>
style={style} />
<line
x1={endTopX}
y1={endTopY}
x2={endBottomX}
y2={endBottomY}
strokeWidth={props.strokeWidth}
style={style}
/>
style={style} />
<text
x={(props.xStart + props.xEnd) / 2}
y={props.yStart}
@ -83,4 +82,4 @@ export const Dimension: React.FC<IDimensionProps> = (props: IDimensionProps) =>
</text>
</g>
);
};
}

View file

@ -1,20 +1,20 @@
import * as React from 'react';
import { ContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN } from '../../../utils/default';
import { getAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
import { Dimension } from './Dimension';
interface IDimensionLayerProps {
roots: ContainerModel | ContainerModel[] | null
}
const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
const it = MakeBFSIterator(root);
const dimensions: React.ReactNode[] = [];
for (const { container, depth } of it) {
const width = container.properties.width;
const id = `dim-${container.properties.id}`;
const xStart = getAbsolutePosition(container)[0];
const xStart = GetAbsolutePosition(container)[0];
const xEnd = xStart + width;
const y = (container.properties.y + container.properties.height) + (DIMENSION_MARGIN * (depth + 1));
const strokeWidth = 1;
@ -28,30 +28,29 @@ const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
xEnd={xEnd}
yEnd={y}
strokeWidth={strokeWidth}
text={text}
/>
text={text} />
);
}
return dimensions;
};
}
/**
* A layer containing all dimension
* @param props
* @returns
*/
export const DimensionLayer: React.FC<IDimensionLayerProps> = (props: IDimensionLayerProps) => {
export function DimensionLayer(props: IDimensionLayerProps): JSX.Element {
let dimensions: React.ReactNode[] = [];
if (Array.isArray(props.roots)) {
props.roots.forEach(child => {
dimensions.concat(getDimensionsNodes(child));
dimensions.concat(GetDimensionsNodes(child));
});
} else if (props.roots !== null) {
dimensions = getDimensionsNodes(props.roots);
dimensions = GetDimensionsNodes(props.roots);
}
return (
<g>
{dimensions}
</g>
);
};
}

View file

@ -2,14 +2,14 @@ import './Selector.scss';
import * as React from 'react';
import { IContainerModel } from '../../../../Interfaces/IContainerModel';
import { SHOW_SELECTOR_TEXT } from '../../../../utils/default';
import { getAbsolutePosition } from '../../../../utils/itertools';
import { GetAbsolutePosition } from '../../../../utils/itertools';
import { RemoveMargin } from '../../../../utils/svg';
interface ISelectorProps {
selected?: IContainerModel
}
export const Selector: React.FC<ISelectorProps> = (props) => {
export function Selector(props: ISelectorProps): JSX.Element {
if (props.selected === undefined || props.selected === null) {
return (
<rect visibility={'hidden'}>
@ -17,7 +17,7 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
);
}
let [x, y] = getAbsolutePosition(props.selected);
let [x, y] = GetAbsolutePosition(props.selected);
let [width, height] = [
props.selected.properties.width,
props.selected.properties.height
@ -34,7 +34,7 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
const yText = y + height / 2;
const style: React.CSSProperties = {
stroke: '#3B82F6', // tw blue-500
stroke: '#3B82F6',
strokeWidth: 4,
fillOpacity: 0,
transitionProperty: 'all',
@ -63,4 +63,4 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
: null}
</>
);
};
}

View file

@ -7,7 +7,7 @@ interface ISymbolProps {
model: ISymbolModel
}
export const Symbol: React.FC<ISymbolProps> = (props) => {
export function Symbol(props: ISymbolProps): JSX.Element {
const href = props.model.config.Image.Base64Image ?? props.model.config.Image.Url;
const hasSVG = props.model.config.Image.Svg !== undefined &&
props.model.config.Image.Svg !== null;
@ -21,8 +21,7 @@ export const Symbol: React.FC<ISymbolProps> = (props) => {
noWrap={true}
disableLineBreaks={true}
content={props.model.config.Image.Svg}
allowElements={true}
/>
allowElements={true} />
</g>
);
}
@ -33,7 +32,6 @@ export const Symbol: React.FC<ISymbolProps> = (props) => {
x={props.model.x}
y={-DIMENSION_MARGIN}
height={props.model.height}
width={props.model.width}
/>
width={props.model.width} />
);
};
}

View file

@ -6,7 +6,7 @@ interface ISymbolLayerProps {
symbols: Map<string, ISymbolModel>
}
export const SymbolLayer: React.FC<ISymbolLayerProps> = (props) => {
export function SymbolLayer(props: ISymbolLayerProps): JSX.Element {
const symbols: JSX.Element[] = [];
props.symbols.forEach((symbol) => {
symbols.push(
@ -15,9 +15,7 @@ export const SymbolLayer: React.FC<ISymbolLayerProps> = (props) => {
});
return (
<g>
{
symbols
}
{symbols}
</g>
);
};
}

View file

@ -25,7 +25,7 @@ interface Viewer {
export const ID = 'svg';
function resizeViewBox(
function ResizeViewBox(
setViewer: React.Dispatch<React.SetStateAction<Viewer>>
): void {
setViewer({
@ -34,26 +34,28 @@ function resizeViewBox(
});
}
function useSVGAutoResizer(
function UseSVGAutoResizer(
setViewer: React.Dispatch<React.SetStateAction<Viewer>>
): void {
React.useEffect(() => {
const onResize = (): void => resizeViewBox(setViewer);
window.addEventListener('resize', onResize);
function OnResize(): void {
return ResizeViewBox(setViewer);
}
window.addEventListener('resize', OnResize);
return () => {
window.removeEventListener('resize', onResize);
window.removeEventListener('resize', OnResize);
};
});
}
export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
export function SVG(props: ISVGProps): JSX.Element {
const [viewer, setViewer] = React.useState<Viewer>({
viewerWidth: window.innerWidth - BAR_WIDTH,
viewerHeight: window.innerHeight
});
useSVGAutoResizer(setViewer);
UseSVGAutoResizer(setViewer);
const xmlns = '<http://www.w3.org/2000/svg>';
const properties = {
@ -85,15 +87,13 @@ export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
>
<svg {...properties}>
{children}
{
SHOW_DIMENSIONS_PER_DEPTH
{SHOW_DIMENSIONS_PER_DEPTH
? <DepthDimensionLayer roots={props.children} />
: null
}
: null}
<SymbolLayer symbols={props.symbols} />
<Selector selected={props.selected} /> {/* leave this at the end so it can be removed during the svg export */}
</svg>
</UncontrolledReactSVGPanZoom>
</div>
);
};
}

View file

@ -19,7 +19,7 @@ const className = `
focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500
disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none`;
export const Select: React.FC<ISelectProps> = (props) => {
export function Select(props: ISelectProps): JSX.Element {
const options = [(
<option key='symbol-none' value=''>None</option>
)];
@ -52,4 +52,4 @@ export const Select: React.FC<ISelectProps> = (props) => {
</select>
</>
);
};
}

View file

@ -31,15 +31,17 @@ describe.concurrent('Sidebar', () => {
});
it('With stuff', () => {
const Type = 'stuff';
const type = 'stuff';
const handleButtonClick = vi.fn();
render(<Sidebar
componentOptions={[
{
Type,
/* eslint-disable @typescript-eslint/naming-convention */
Type: type,
Width: 30,
Height: 30,
Style: {}
/* eslint-enable */
}
]}
isOpen={true}

View file

@ -1,6 +1,6 @@
import * as React from 'react';
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
import { truncateString } from '../../utils/stringtools';
import { TruncateString } from '../../utils/stringtools';
interface ISidebarProps {
componentOptions: IAvailableContainer[]
@ -8,11 +8,11 @@ interface ISidebarProps {
buttonOnClick: (type: string) => void
}
function handleDragStart(event: React.DragEvent<HTMLButtonElement>): void {
function HandleDragStart(event: React.DragEvent<HTMLButtonElement>): void {
event.dataTransfer.setData('type', (event.target as HTMLButtonElement).id);
}
export const Sidebar: React.FC<ISidebarProps> = (props: ISidebarProps) => {
export function Sidebar(props: ISidebarProps): JSX.Element {
const listElements = props.componentOptions.map(componentOption =>
<button type="button"
className='justify-center transition-all sidebar-component'
@ -21,9 +21,9 @@ export const Sidebar: React.FC<ISidebarProps> = (props: ISidebarProps) => {
title={componentOption.Type}
onClick={() => props.buttonOnClick(componentOption.Type)}
draggable={true}
onDragStart={(event) => handleDragStart(event)}
onDragStart={(event) => HandleDragStart(event)}
>
{truncateString(componentOption.Type, 5)}
{TruncateString(componentOption.Type, 5)}
</button>
);

View file

@ -1,6 +1,6 @@
import * as React from 'react';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { restoreX, transformX } from '../../utils/svg';
import { RestoreX, TransformX } from '../../utils/svg';
import { InputGroup } from '../InputGroup/InputGroup';
interface ISymbolFormProps {
@ -8,7 +8,8 @@ interface ISymbolFormProps {
symbols: Map<string, ISymbolModel>
onChange: (key: string, value: string | number | boolean) => void
}
const SymbolForm: React.FunctionComponent<ISymbolFormProps> = (props) => {
export function SymbolForm(props: ISymbolFormProps): JSX.Element {
return (
<div className='grid grid-cols-2 gap-y-4'>
<InputGroup
@ -18,17 +19,15 @@ const SymbolForm: React.FunctionComponent<ISymbolFormProps> = (props) => {
inputClassName=''
type='string'
value={props.symbol.id.toString()}
isDisabled={true}
/>
isDisabled={true} />
<InputGroup
labelText='x'
inputKey='x'
labelClassName=''
inputClassName=''
type='number'
value={transformX(props.symbol.x, props.symbol.width, props.symbol.config.XPositionReference).toString()}
onChange={(event) => props.onChange('x', restoreX(Number(event.target.value), props.symbol.width, props.symbol.config.XPositionReference))}
/>
value={TransformX(props.symbol.x, props.symbol.width, props.symbol.config.XPositionReference).toString()}
onChange={(event) => props.onChange('x', RestoreX(Number(event.target.value), props.symbol.width, props.symbol.config.XPositionReference))} />
<InputGroup
labelText='Height'
inputKey='height'
@ -37,8 +36,7 @@ const SymbolForm: React.FunctionComponent<ISymbolFormProps> = (props) => {
type='number'
min={0}
value={props.symbol.height.toString()}
onChange={(event) => props.onChange('height', Number(event.target.value))}
/>
onChange={(event) => props.onChange('height', Number(event.target.value))} />
<InputGroup
labelText='Width'
inputKey='width'
@ -47,10 +45,7 @@ const SymbolForm: React.FunctionComponent<ISymbolFormProps> = (props) => {
type='number'
min={0}
value={props.symbol.width.toString()}
onChange={(event) => props.onChange('width', Number(event.target.value))}
/>
onChange={(event) => props.onChange('width', Number(event.target.value))} />
</div>
);
};
export default SymbolForm;
}

View file

@ -1,6 +1,6 @@
import React from 'react';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import SymbolForm from './SymbolForm';
import { SymbolForm } from './SymbolForm';
interface ISymbolPropertiesProps {
symbol?: ISymbolModel
@ -8,7 +8,7 @@ interface ISymbolPropertiesProps {
onChange: (key: string, value: string | number | boolean) => void
}
export const SymbolProperties: React.FC<ISymbolPropertiesProps> = (props: ISymbolPropertiesProps) => {
export function SymbolProperties(props: ISymbolPropertiesProps): JSX.Element {
if (props.symbol === undefined) {
return <div></div>;
}
@ -18,8 +18,7 @@ export const SymbolProperties: React.FC<ISymbolPropertiesProps> = (props: ISymbo
<SymbolForm
symbol={props.symbol}
symbols={props.symbols}
onChange={props.onChange}
/>
onChange={props.onChange} />
</div>
);
};
}

View file

@ -1,6 +1,6 @@
import * as React from 'react';
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
import { truncateString } from '../../utils/stringtools';
import { TruncateString } from '../../utils/stringtools';
interface ISymbolsProps {
componentOptions: IAvailableSymbol[]
@ -8,11 +8,11 @@ interface ISymbolsProps {
buttonOnClick: (type: string) => void
}
function handleDragStart(event: React.DragEvent<HTMLButtonElement>): void {
function HandleDragStart(event: React.DragEvent<HTMLButtonElement>): void {
event.dataTransfer.setData('type', (event.target as HTMLButtonElement).id);
}
export const Symbols: React.FC<ISymbolsProps> = (props: ISymbolsProps) => {
export function Symbols(props: ISymbolsProps): JSX.Element {
const listElements = props.componentOptions.map(componentOption => {
if (componentOption.Image.Url !== undefined || componentOption.Image.Base64Image !== undefined) {
const url = componentOption.Image.Base64Image ?? componentOption.Image.Url;
@ -23,7 +23,7 @@ export const Symbols: React.FC<ISymbolsProps> = (props: ISymbolsProps) => {
title={componentOption.Name}
onClick={() => props.buttonOnClick(componentOption.Name)}
draggable={true}
onDragStart={(event) => handleDragStart(event)}
onDragStart={(event) => HandleDragStart(event)}
>
<div>
<img
@ -32,7 +32,7 @@ export const Symbols: React.FC<ISymbolsProps> = (props: ISymbolsProps) => {
/>
</div>
<div>
{truncateString(componentOption.Name, 5)}
{TruncateString(componentOption.Name, 5)}
</div>
</button>);
}
@ -44,10 +44,10 @@ export const Symbols: React.FC<ISymbolsProps> = (props: ISymbolsProps) => {
title={componentOption.Name}
onClick={() => props.buttonOnClick(componentOption.Name)}
draggable={true}
onDragStart={(event) => handleDragStart(event)}
onDragStart={(event) => HandleDragStart(event)}
>
{truncateString(componentOption.Name, 5)}
{TruncateString(componentOption.Name, 5)}
</button>);
});

View file

@ -1,7 +1,7 @@
import { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
import { IPoint } from '../../Interfaces/IPoint';
export function useMouseEvents(
export function UseMouseEvents(
isContextMenuOpen: boolean,
elementRef: RefObject<HTMLDivElement>,
setIsContextMenuOpen: Dispatch<SetStateAction<boolean>>,
@ -9,44 +9,48 @@ export function useMouseEvents(
setContextMenuPosition: Dispatch<SetStateAction<IPoint>>
): void {
useEffect(() => {
const onContextMenu = (event: MouseEvent): void => handleRightClick(
function OnContextMenu(event: MouseEvent): void {
return HandleRightClick(
event,
setIsContextMenuOpen,
setOnClickSymbolId,
setContextMenuPosition
);
}
const onLeftClick = (): void => handleLeftClick(
function OnLeftClick(): void {
return HandleLeftClick(
isContextMenuOpen,
setIsContextMenuOpen,
setOnClickSymbolId
);
}
elementRef.current?.addEventListener(
'contextmenu',
onContextMenu
OnContextMenu
);
window.addEventListener(
'click',
onLeftClick
OnLeftClick
);
return () => {
elementRef.current?.removeEventListener(
'contextmenu',
onContextMenu
OnContextMenu
);
window.removeEventListener(
'click',
onLeftClick
OnLeftClick
);
};
});
}
export function handleRightClick(
export function HandleRightClick(
event: MouseEvent,
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
setOnClickSymbolId: React.Dispatch<React.SetStateAction<string>>,
@ -66,7 +70,7 @@ export function handleRightClick(
setContextMenuPosition(contextMenuPosition);
}
export function handleLeftClick(
export function HandleLeftClick(
isContextMenuOpen: boolean,
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>

View file

@ -2,22 +2,22 @@ import * as React from 'react';
import { FixedSizeList as List } from 'react-window';
import { Menu } from '../Menu/Menu';
import { MenuItem } from '../Menu/MenuItem';
import { handleLeftClick, handleRightClick, useMouseEvents } from './MouseEventHandlers';
import { UseMouseEvents } from './MouseEventHandlers';
import { IPoint } from '../../Interfaces/IPoint';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { SymbolProperties } from '../SymbolProperties/SymbolProperties';
interface ISymbolsSidebarProps {
SelectedSymbolId: string
selectedSymbolId: string
symbols: Map<string, ISymbolModel>
isOpen: boolean
isHistoryOpen: boolean
OnPropertyChange: (key: string, value: string | number | boolean) => void
SelectSymbol: (symbolId: string) => void
DeleteSymbol: (containerid: string) => void
onPropertyChange: (key: string, value: string | number | boolean) => void
selectSymbol: (symbolId: string) => void
deleteSymbol: (containerid: string) => void
}
export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSidebarProps): JSX.Element => {
export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
// States
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
const [onClickSymbolId, setOnClickSymbolId] = React.useState<string>('');
@ -29,7 +29,7 @@ export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSi
const elementRef = React.useRef<HTMLDivElement>(null);
// Event listeners
useMouseEvents(
UseMouseEvents(
isContextMenuOpen,
elementRef,
setIsContextMenuOpen,
@ -46,30 +46,28 @@ export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSi
}
const containers = [...props.symbols.values()];
const Row = ({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element => {
function Row({ index, style }: { index: number, style: React.CSSProperties }): JSX.Element {
const container = containers[index];
const key = container.id.toString();
const text = key;
const selectedClass: string = props.SelectedSymbolId !== '' &&
props.SelectedSymbolId === container.id
const selectedClass: string = props.selectedSymbolId !== '' &&
props.selectedSymbolId === container.id
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
: 'bg-slate-300/60 hover:bg-slate-300';
return (
<button type="button"
className={
`w-full border-blue-500 elements-sidebar-row whitespace-pre
text-left text-sm font-medium transition-all ${selectedClass}`
}
className={`w-full border-blue-500 elements-sidebar-row whitespace-pre
text-left text-sm font-medium transition-all ${selectedClass}`}
id={key}
key={key}
style={style}
onClick={() => props.SelectSymbol(key)}
onClick={() => props.selectSymbol(key)}
>
{text}
</button>
);
};
}
return (
<div className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
@ -95,14 +93,13 @@ export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSi
>
<MenuItem className='contextmenu-item' text='Delete' onClick={() => {
setIsContextMenuOpen(false);
props.DeleteSymbol(onClickSymbolId);
props.deleteSymbol(onClickSymbolId);
} } />
</Menu>
<SymbolProperties
symbol={props.symbols.get(props.SelectedSymbolId)}
symbol={props.symbols.get(props.selectedSymbolId)}
symbols={props.symbols}
onChange={props.OnPropertyChange}
/>
onChange={props.onPropertyChange} />
</div>
);
};
}

View file

@ -1,26 +1,26 @@
import React, { FC } from 'react';
import React from 'react';
import './ToggleButton.scss';
interface IToggleButtonProps {
id: string
text: string
type?: TOGGLE_TYPE
type?: ToggleType
title: string
checked: boolean
onChange: React.ChangeEventHandler<HTMLInputElement>
}
export enum TOGGLE_TYPE {
MATERIAL,
export enum ToggleType {
Material,
IOS
}
export const ToggleButton: FC<IToggleButtonProps> = (props) => {
export function ToggleButton(props: IToggleButtonProps): JSX.Element {
const id = `toggle-${props.id}`;
const type = props.type ?? TOGGLE_TYPE.MATERIAL;
const type = props.type ?? ToggleType.Material;
let classLine = 'line w-10 h-4 bg-gray-400 rounded-full shadow-inner';
let classDot = 'dot absolute w-6 h-6 bg-white rounded-full shadow -left-1 -top-1 transition';
if (type === TOGGLE_TYPE.IOS) {
if (type === ToggleType.IOS) {
classLine = 'line block bg-gray-600 w-14 h-8 rounded-full';
classDot = 'dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition';
}
@ -49,4 +49,4 @@ export const ToggleButton: FC<IToggleButtonProps> = (props) => {
</div>
</div>
);
};
}

View file

@ -14,23 +14,23 @@ import { SymbolsSidebar } from '../SymbolsSidebar/SymbolsSidebar';
import { PropertyType } from '../../Enums/PropertyType';
interface IUIProps {
SelectedContainer: IContainerModel | undefined
selectedContainer: IContainerModel | undefined
current: IHistoryState
history: IHistoryState[]
historyCurrentStep: number
AvailableContainers: IAvailableContainer[]
AvailableSymbols: IAvailableSymbol[]
SelectContainer: (containerId: string) => void
DeleteContainer: (containerId: string) => void
OnPropertyChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
AddContainer: (type: string) => void
AddSymbol: (type: string) => void
OnSymbolPropertyChange: (key: string, value: string | number | boolean) => void
SelectSymbol: (symbolId: string) => void
DeleteSymbol: (symbolId: string) => void
SaveEditorAsJSON: () => void
SaveEditorAsSVG: () => void
LoadState: (move: number) => void
availableContainers: IAvailableContainer[]
availableSymbols: IAvailableSymbol[]
selectContainer: (containerId: string) => void
deleteContainer: (containerId: string) => void
onPropertyChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
addContainer: (type: string) => void
addSymbol: (type: string) => void
onSymbolPropertyChange: (key: string, value: string | number | boolean) => void
selectSymbol: (symbolId: string) => void
deleteSymbol: (symbolId: string) => void
saveEditorAsJSON: () => void
saveEditorAsSVG: () => void
loadState: (move: number) => void
}
function CloseOtherSidebars(
@ -41,7 +41,7 @@ function CloseOtherSidebars(
setIsSymbolsOpen(false);
}
export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
export function UI(props: IUIProps): JSX.Element {
const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
const [isSymbolsOpen, setIsSymbolsOpen] = React.useState(false);
const [isHistoryOpen, setIsHistoryOpen] = React.useState(false);
@ -61,71 +61,63 @@ export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
isSymbolsOpen={isSymbolsOpen}
isElementsSidebarOpen={isSidebarOpen}
isHistoryOpen={isHistoryOpen}
ToggleSidebar={() => {
toggleSidebar={() => {
CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen);
setIsSidebarOpen(!isSidebarOpen);
} }
ToggleSymbols={() => {
toggleSymbols={() => {
CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen);
setIsSymbolsOpen(!isSymbolsOpen);
} }
ToggleTimeline={() => setIsHistoryOpen(!isHistoryOpen)}
/>
toggleTimeline={() => setIsHistoryOpen(!isHistoryOpen)} />
<Sidebar
componentOptions={props.AvailableContainers}
componentOptions={props.availableContainers}
isOpen={isSidebarOpen}
buttonOnClick={props.AddContainer}
/>
buttonOnClick={props.addContainer} />
<Symbols
componentOptions={props.AvailableSymbols}
componentOptions={props.availableSymbols}
isOpen={isSymbolsOpen}
buttonOnClick={props.AddSymbol}
/>
buttonOnClick={props.addSymbol} />
<ElementsSidebar
MainContainer={props.current.MainContainer}
symbols={props.current.Symbols}
SelectedContainer={props.SelectedContainer}
mainContainer={props.current.mainContainer}
symbols={props.current.symbols}
selectedContainer={props.selectedContainer}
isOpen={isSidebarOpen}
isHistoryOpen={isHistoryOpen}
OnPropertyChange={props.OnPropertyChange}
SelectContainer={props.SelectContainer}
DeleteContainer={props.DeleteContainer}
/>
onPropertyChange={props.onPropertyChange}
selectContainer={props.selectContainer}
deleteContainer={props.deleteContainer} />
<SymbolsSidebar
SelectedSymbolId={props.current.SelectedSymbolId}
symbols={props.current.Symbols}
selectedSymbolId={props.current.selectedSymbolId}
symbols={props.current.symbols}
isOpen={isSymbolsOpen}
isHistoryOpen={isHistoryOpen}
OnPropertyChange={props.OnSymbolPropertyChange}
SelectSymbol={props.SelectSymbol}
DeleteSymbol={props.DeleteSymbol}
/>
onPropertyChange={props.onSymbolPropertyChange}
selectSymbol={props.selectSymbol}
deleteSymbol={props.deleteSymbol} />
<History
history={props.history}
historyCurrentStep={props.historyCurrentStep}
isOpen={isHistoryOpen}
jumpTo={props.LoadState}
/>
jumpTo={props.loadState} />
<FloatingButton className={`fixed z-10 flex flex-col gap-2 items-center bottom-40 ${buttonRightOffsetClasses}`}>
<button type="button"
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
title='Export as JSON'
onClick={props.SaveEditorAsJSON}
onClick={props.saveEditorAsJSON}
>
<UploadIcon className="heroicon text-white" />
</button>
<button type="button"
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
title='Export as SVG'
onClick={props.SaveEditorAsSVG}
onClick={props.saveEditorAsSVG}
>
<PhotographIcon className="heroicon text-white" />
</button>
</FloatingButton>
</>
);
};
export default UI;
}

View file

@ -8,15 +8,15 @@ export enum PropertyType {
/**
* Simple property: is not inside any object: id, x, width... (default)
*/
SIMPLE,
Simple,
/**
* Style property: is inside the style object: stroke, fillOpacity...
*/
STYLE,
Style,
/**
* Margin property: is inside the margin property: left, bottom, top, right...
*/
MARGIN,
Margin,
}

View file

@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from 'react';
import { getCurrentHistory } from '../Components/Editor/Editor';
import { GetCurrentHistory } from '../Components/Editor/Editor';
import { IConfiguration } from '../Interfaces/IConfiguration';
import { IEditorState } from '../Interfaces/IEditorState';
import { IHistoryState } from '../Interfaces/IHistoryState';
@ -38,7 +38,7 @@ const appendNewState = (
): void => {
const state: IHistoryState = JSON.parse(eventInitDict?.detail.state);
ReviveState(state);
const history = getCurrentHistory(editorState.history, editorState.historyCurrentStep);
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
history.push(state);
setHistory(history);

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
import React from 'react';
import { AddMethod } from '../Enums/AddMethod';
import { XPositionReference } from '../Enums/XPositionReference';

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { XPositionReference } from '../Enums/XPositionReference';
import { IImage } from './IImage';

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { IAvailableContainer } from './IAvailableContainer';
import { IAvailableSymbol } from './IAvailableSymbol';

View file

@ -1,4 +1,4 @@
import IContainerProperties from './IContainerProperties';
import { IContainerProperties } from './IContainerProperties';
export interface IContainerModel {
children: IContainerModel[]

View file

@ -5,7 +5,7 @@ import { IMargin } from './IMargin';
/**
* Properties of a container
*/
export default interface IContainerProperties {
export interface IContainerProperties {
/** id of the container */
id: string
@ -55,7 +55,7 @@ export default interface IContainerProperties {
isFlex: boolean
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
XPositionReference: XPositionReference
xPositionReference: XPositionReference
/**
* (optional)

View file

@ -3,20 +3,20 @@ import { ISymbolModel } from './ISymbolModel';
export interface IHistoryState {
/** Last editor action */
LastAction: string
lastAction: string
/** Reference to the main container */
MainContainer: IContainerModel
mainContainer: IContainerModel
/** Id of the selected container */
SelectedContainerId: string
selectedContainerId: string
/** Counter of type of container. Used for ids. */
TypeCounters: Record<string, number>
typeCounters: Record<string, number>
/** List of symbols */
Symbols: Map<string, ISymbolModel>
symbols: Map<string, ISymbolModel>
/** Selected symbols id */
SelectedSymbolId: string
selectedSymbolId: string
}

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
/**
* Model of an image with multiple source
* It must at least have one source.

View file

@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client';
import { App } from './Components/App/App';
import './index.scss';
function render(root: Element | Document): void {
function RenderRoot(root: Element | Document): void {
ReactDOM.createRoot(root.querySelector('#root') as HTMLDivElement).render(
<React.StrictMode>
<App root={root}/>
@ -12,10 +12,9 @@ function render(root: Element | Document): void {
}
namespace SVGLayoutDesigner {
export const Render = render;
export const Render = RenderRoot;
}
(window as any).SVGLayoutDesigner = SVGLayoutDesigner;
render(document);
RenderRoot(document);

View file

@ -3,7 +3,7 @@ import { IAvailableContainer } from '../Interfaces/IAvailableContainer';
import { IAvailableSymbol } from '../Interfaces/IAvailableSymbol';
import { IConfiguration } from '../Interfaces/IConfiguration';
import { ContainerModel, IContainerModel } from '../Interfaces/IContainerModel';
import IContainerProperties from '../Interfaces/IContainerProperties';
import { IContainerProperties } from '../Interfaces/IContainerProperties';
import { IEditorState } from '../Interfaces/IEditorState';
import { ISymbolModel } from '../Interfaces/ISymbolModel';
@ -36,7 +36,7 @@ export const APPLY_BEHAVIORS_ON_CHILDREN = true;
/**
* Returns the default editor state given the configuration
*/
export const GetDefaultEditorState = (configuration: IConfiguration): IEditorState => {
export function GetDefaultEditorState(configuration: IConfiguration): IEditorState {
const mainContainer = new ContainerModel(
null,
{
@ -50,22 +50,23 @@ export const GetDefaultEditorState = (configuration: IConfiguration): IEditorSta
configuration,
history: [
{
LastAction: '',
MainContainer: mainContainer,
SelectedContainerId: mainContainer.properties.id,
TypeCounters: {},
Symbols: new Map(),
SelectedSymbolId: ''
lastAction: '',
mainContainer: mainContainer,
selectedContainerId: mainContainer.properties.id,
typeCounters: {},
symbols: new Map(),
selectedSymbolId: ''
}
],
historyCurrentStep: 0
};
};
}
/**
* Default config when the API is not available
*/
export const DEFAULT_CONFIG: IConfiguration = {
/* eslint-disable @typescript-eslint/naming-convention */
AvailableContainers: [
{
Type: 'Container',
@ -87,6 +88,7 @@ export const DEFAULT_CONFIG: IConfiguration = {
stroke: 'black'
}
}
/* eslint-enable */
};
/**
@ -107,7 +109,7 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
height: Number(DEFAULT_CONFIG.MainContainer.Height),
isAnchor: false,
isFlex: false,
XPositionReference: XPositionReference.Left,
xPositionReference: XPositionReference.Left,
style: {
stroke: 'black',
fillOpacity: 0
@ -124,16 +126,15 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
* @param containerConfig default config of the container sent by the API
* @returns {IContainerProperties} Default properties of a newly created container
*/
export const GetDefaultContainerProps = (
type: string,
export function GetDefaultContainerProps(type: string,
typeCount: number,
parent: IContainerModel,
x: number,
y: number,
width: number,
height: number,
containerConfig: IAvailableContainer
): IContainerProperties => ({
containerConfig: IAvailableContainer): IContainerProperties {
return ({
id: `${type}-${typeCount}`,
type,
parentId: parent.properties.id,
@ -146,20 +147,19 @@ export const GetDefaultContainerProps = (
height,
isAnchor: false,
isFlex: containerConfig.IsFlex ?? containerConfig.Width === undefined,
XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
xPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
minWidth: containerConfig.MinWidth ?? 1,
maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
customSVG: containerConfig.CustomSVG,
style: structuredClone(containerConfig.Style),
userData: structuredClone(containerConfig.UserData)
});
}
export const GetDefaultSymbolModel = (
name: string,
export function GetDefaultSymbolModel(name: string,
newCounters: Record<string, number>,
type: string,
symbolConfig: IAvailableSymbol
): ISymbolModel => {
symbolConfig: IAvailableSymbol): ISymbolModel {
return {
id: `${name}-${newCounters[type]}`,
type: name,
@ -169,4 +169,4 @@ export const GetDefaultSymbolModel = (
height: symbolConfig.Height ?? DEFAULT_SYMBOL_HEIGHT,
linkedContainers: new Set()
};
};
}

View file

@ -54,7 +54,7 @@ export function * MakeBFSIterator(root: IContainerModel): Generator<ContainerAnd
* Returns the depth of the container
* @returns The depth of the container
*/
export function getDepth(parent: IContainerModel): number {
export function GetDepth(parent: IContainerModel): number {
let depth = 0;
let current: IContainerModel | null = parent;
@ -70,10 +70,10 @@ export function getDepth(parent: IContainerModel): number {
* Returns the absolute position by iterating to the parent
* @returns The absolute position of the container
*/
export function getAbsolutePosition(container: IContainerModel): [number, number] {
export function GetAbsolutePosition(container: IContainerModel): [number, number] {
const x = container.properties.x;
const y = container.properties.y;
return cancelParentTransform(container.parent, x, y);
return CancelParentTransform(container.parent, x, y);
}
/**
@ -83,7 +83,7 @@ export function getAbsolutePosition(container: IContainerModel): [number, number
* @param y value to be restored
* @returns x and y such that the transformations of the parent are cancelled
*/
export function cancelParentTransform(parent: IContainerModel | null, x: number, y: number): [number, number] {
export function CancelParentTransform(parent: IContainerModel | null, x: number, y: number): [number, number] {
let current = parent;
while (current != null) {
x += current.properties.x;
@ -100,7 +100,7 @@ export function cancelParentTransform(parent: IContainerModel | null, x: number,
* @param y value to be restored
* @returns x and y such that the transformations of the parent are cancelled
*/
export function applyParentTransform(parent: IContainerModel | null, x: number, y: number): [number, number] {
export function ApplyParentTransform(parent: IContainerModel | null, x: number, y: number): [number, number] {
let current = parent;
while (current != null) {
x -= current.properties.x;
@ -110,7 +110,7 @@ export function applyParentTransform(parent: IContainerModel | null, x: number,
return [x, y];
}
export function findContainerById(root: IContainerModel, id: string): IContainerModel | undefined {
export function FindContainerById(root: IContainerModel, id: string): IContainerModel | undefined {
const it = MakeIterator(root);
for (const container of it) {
if (container.properties.id === id) {
@ -125,13 +125,13 @@ export interface IPair<T> {
next: T
}
export function * pairwise<T>(arr: T[]): Generator<IPair<T>, void, unknown> {
export function * Pairwise<T>(arr: T[]): Generator<IPair<T>, void, unknown> {
for (let i = 0; i < arr.length - 1; i++) {
yield { cur: arr[i], next: arr[i + 1] };
}
}
export function * reversePairwise<T>(arr: T[]): Generator<IPair<T>, void, unknown> {
export function * ReversePairwise<T>(arr: T[]): Generator<IPair<T>, void, unknown> {
for (let i = arr.length - 1; i > 0; i--) {
yield { cur: arr[i], next: arr[i - 1] };
}

View file

@ -1,4 +1,4 @@
import { findContainerById, MakeIterator } from './itertools';
import { FindContainerById, MakeIterator } from './itertools';
import { IEditorState } from '../Interfaces/IEditorState';
import { IHistoryState } from '../Interfaces/IHistoryState';
@ -19,34 +19,32 @@ export function Revive(editorState: IEditorState): void {
}
}
export const ReviveState = (
state: IHistoryState
): void => {
if (state.MainContainer === null || state.MainContainer === undefined) {
export function ReviveState(state: IHistoryState): void {
if (state.mainContainer === null || state.mainContainer === undefined) {
return;
}
state.Symbols = new Map(state.Symbols);
for (const symbol of state.Symbols.values()) {
state.symbols = new Map(state.symbols);
for (const symbol of state.symbols.values()) {
symbol.linkedContainers = new Set(symbol.linkedContainers);
}
const it = MakeIterator(state.MainContainer);
const it = MakeIterator(state.mainContainer);
for (const container of it) {
const parentId = container.properties.parentId;
if (parentId === null) {
container.parent = null;
continue;
}
const parent = findContainerById(state.MainContainer, parentId);
const parent = FindContainerById(state.mainContainer, parentId);
if (parent === undefined) {
continue;
}
container.parent = parent;
}
};
}
export const getCircularReplacer = (): (key: any, value: object | Map<string, any> | null) => object | null | undefined => {
export function GetCircularReplacer(): (key: any, value: object | Map<string, any> | null) => object | null | undefined {
return (key: any, value: object | null) => {
if (key === 'parent') {
return;
@ -62,4 +60,4 @@ export const getCircularReplacer = (): (key: any, value: object | Map<string, an
return value;
};
};
}

View file

@ -89,7 +89,7 @@ function GetInitialMatrix(
return matrix;
}
function getAllIndexes(arr: number[], val: number): number[] {
function GetAllIndexes(arr: number[], val: number): number[] {
const indexes = []; let i = -1;
while ((i = arr.indexOf(val, i + 1)) !== -1) {
indexes.push(i);
@ -125,9 +125,9 @@ function ApplyMainLoop(oldMatrix: number[][], rowlength: number): number[][] {
// 1) find the index with smallest coefficient (O(n)+)
const lastRow = matrix[matrix.length - 1];
const min = Math.min(...lastRow);
const indexes = getAllIndexes(lastRow, min);
const indexes = GetAllIndexes(lastRow, min);
// to avoid infinite loop try to select the least used selected index
const pivotColIndex = getLeastUsedIndex(indexes, indexesTried);
const pivotColIndex = GetLeastUsedIndex(indexes, indexesTried);
// record the usage of index by incrementing
indexesTried[pivotColIndex] = indexesTried[pivotColIndex] !== undefined ? indexesTried[pivotColIndex] + 1 : 1;
@ -227,7 +227,7 @@ function GetSolutions(nCols: number, finalMatrix: number[][]): number[] {
* @param indexesTried Record of indexes. Count the number of times the index was used.
* @returns The least used index
*/
function getLeastUsedIndex(indexes: number[], indexesTried: Record<number, number>): number {
function GetLeastUsedIndex(indexes: number[], indexesTried: Record<number, number>): number {
let minUsed = Infinity;
let minIndex = -1;
for (const index of indexes) {

View file

@ -1,10 +1,10 @@
export function truncateString(str: string, num: number): string {
export function TruncateString(str: string, num: number): string {
if (str.length <= num) {
return str;
}
return `${str.slice(0, num)}...`;
}
export function camelize(str: string): any {
export function Camelize(str: string): any {
return str.split('-').map((word, index) => index > 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word).join('');
}

View file

@ -10,7 +10,7 @@ import { XPositionReference } from '../Enums/XPositionReference';
* it is better to fix serialization with the reviver.
*/
export function transformX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
export function TransformX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
let transformedX = x;
if (xPositionReference === XPositionReference.Center) {
transformedX += width / 2;
@ -20,7 +20,7 @@ export function transformX(x: number, width: number, xPositionReference = XPosit
return transformedX;
}
export function restoreX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
export function RestoreX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
let transformedX = x;
if (xPositionReference === XPositionReference.Center) {
transformedX -= width / 2;

View file

@ -7,14 +7,15 @@ afterEach(() => {
cleanup();
});
const customRender = (ui: React.ReactElement, options = {}): RenderResult =>
render(ui, {
function CustomRender(ui: React.ReactElement, options = {}): RenderResult {
return render(ui, {
// wrap provider(s) here if needed
wrapper: ({ children }) => children,
...options
});
}
export * from '@testing-library/react';
export { default as userEvent } from '@testing-library/user-event';
// override render export
export { customRender as render };
export { CustomRender as render };