Merged PR 170: Add new eslint rules
- naming-convention - prefer-arrow-callback - func-style - import/no-default-export
This commit is contained in:
parent
3f58c5ba5e
commit
ad126c6c28
65 changed files with 781 additions and 784 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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()}>
|
||||
<CubeIcon className='heroicon'/>
|
||||
onClick={() => props.toggleSidebar()}>
|
||||
<CubeIcon className='heroicon' />
|
||||
</BarIcon>
|
||||
<BarIcon
|
||||
isActive={props.isSymbolsOpen}
|
||||
title='Symbols'
|
||||
onClick={() => props.ToggleSymbols()}>
|
||||
<LinkIcon className='heroicon'/>
|
||||
onClick={() => props.toggleSymbols()}>
|
||||
<LinkIcon className='heroicon' />
|
||||
</BarIcon>
|
||||
<BarIcon
|
||||
isActive={props.isHistoryOpen}
|
||||
title='Timeline'
|
||||
onClick={() => props.ToggleTimeline()}>
|
||||
<ClockIcon className='heroicon'/>
|
||||
onClick={() => props.toggleTimeline()}>
|
||||
<ClockIcon className='heroicon' />
|
||||
</BarIcon>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -16,7 +16,7 @@ export const BarIcon: React.FC<IBarIconProps> = (props) => {
|
|||
onClick={() => props.onClick()}
|
||||
>
|
||||
<span className='sidebar-tooltip group-hover:scale-100'>{props.title}</span>
|
||||
{ props.children }
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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[] {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import Swal from 'sweetalert2';
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { Simplex } from '../../../utils/simplex';
|
||||
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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('');
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -23,14 +21,14 @@ export const FloatingButton: React.FC<IFloatingButtonProps> = (props: IFloatingB
|
|||
return (
|
||||
<div className={`transition-all ${props.className}`}>
|
||||
<div className={`transition-all flex flex-col gap-2 items-center ${buttonListClasses}`}>
|
||||
{ props.children }
|
||||
{props.children}
|
||||
</div>
|
||||
<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 }
|
||||
{icon}
|
||||
</button>
|
||||
</div>);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}`}>
|
||||
|
@ -48,8 +46,8 @@ export const History: React.FC<IHistoryProps> = (props: IHistoryProps) => {
|
|||
height={window.innerHeight}
|
||||
width={256}
|
||||
>
|
||||
{ Row }
|
||||
{Row}
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,14 +23,14 @@ 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}
|
||||
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
|
||||
htmlFor={props.inputKey}
|
||||
>
|
||||
{ props.labelText }
|
||||
{props.labelText}
|
||||
|
||||
</label>
|
||||
<input
|
||||
|
@ -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} />
|
||||
</>;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -17,7 +17,7 @@ export const Menu: React.FC<IMenuProps> = (props) => {
|
|||
left: props.x,
|
||||
top: props.y
|
||||
}}>
|
||||
{ props.children }
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
@ -59,8 +57,8 @@ export const RadioGroupButtons: React.FunctionComponent<IRadioGroupButtonsProps>
|
|||
<div id='XPositionReference'
|
||||
className='flex flex-col'
|
||||
>
|
||||
{ inputGroups }
|
||||
{inputGroups}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
{dimensions}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function AddNewDimension(currentDepth: number, min: number, max: number, lastY: number, dimensions: React.ReactNode[]): void {
|
||||
const id = `dim-depth-${currentDepth}`;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
{dimensions}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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} />
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
@ -64,9 +66,9 @@ export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
|
|||
|
||||
let children: React.ReactNode | React.ReactNode[] = [];
|
||||
if (Array.isArray(props.children)) {
|
||||
children = props.children.map(child => <Container key={`container-${child.properties.id}`} model={child}/>);
|
||||
children = props.children.map(child => <Container key={`container-${child.properties.id}`} model={child} />);
|
||||
} else if (props.children !== null) {
|
||||
children = <Container key={`container-${props.children.properties.id}`} model={props.children}/>;
|
||||
children = <Container key={`container-${props.children.properties.id}`} model={props.children} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -84,16 +86,14 @@ export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
|
|||
}}
|
||||
>
|
||||
<svg {...properties}>
|
||||
{ children }
|
||||
{
|
||||
SHOW_DIMENSIONS_PER_DEPTH
|
||||
? <DepthDimensionLayer roots={props.children}/>
|
||||
: null
|
||||
}
|
||||
{children}
|
||||
{SHOW_DIMENSIONS_PER_DEPTH
|
||||
? <DepthDimensionLayer roots={props.children} />
|
||||
: 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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)];
|
||||
|
@ -40,7 +40,7 @@ export const Select: React.FC<ISelectProps> = (props) => {
|
|||
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
|
||||
htmlFor={props.inputKey}
|
||||
>
|
||||
{ props.labelText }
|
||||
{props.labelText}
|
||||
</label>
|
||||
<select
|
||||
id={props.inputKey}
|
||||
|
@ -48,8 +48,8 @@ export const Select: React.FC<ISelectProps> = (props) => {
|
|||
onChange={props.onChange}
|
||||
className={className}
|
||||
>
|
||||
{ options }
|
||||
{options}
|
||||
</select>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>);
|
||||
});
|
||||
|
||||
|
|
|
@ -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>>
|
||||
|
|
|
@ -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 }
|
||||
{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}`}>
|
||||
|
@ -84,7 +82,7 @@ export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSi
|
|||
height={384}
|
||||
width={256}
|
||||
>
|
||||
{ Row }
|
||||
{Row}
|
||||
</List>
|
||||
</div>
|
||||
<Menu
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
@ -43,10 +43,10 @@ export const ToggleButton: FC<IToggleButtonProps> = (props) => {
|
|||
<div className={classDot}></div>
|
||||
</div>
|
||||
<div className="ml-3 text-gray-700 font-medium">
|
||||
{ props.text }
|
||||
{props.text}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import React from 'react';
|
||||
import { AddMethod } from '../Enums/AddMethod';
|
||||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { XPositionReference } from '../Enums/XPositionReference';
|
||||
import { IImage } from './IImage';
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { IAvailableContainer } from './IAvailableContainer';
|
||||
import { IAvailableSymbol } from './IAvailableSymbol';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import IContainerProperties from './IContainerProperties';
|
||||
import { IContainerProperties } from './IContainerProperties';
|
||||
|
||||
export interface IContainerModel {
|
||||
children: IContainerModel[]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/**
|
||||
* Model of an image with multiple source
|
||||
* It must at least have one source.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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] };
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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('');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue