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 = {
|
module.exports = {
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es2021: true
|
es2021: true
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
|
'only-warn',
|
||||||
'plugin:react/recommended',
|
'plugin:react/recommended',
|
||||||
'standard-with-typescript'
|
'standard-with-typescript'
|
||||||
],
|
],
|
||||||
|
@ -20,22 +18,67 @@ module.exports = {
|
||||||
project: './tsconfig.json'
|
project: './tsconfig.json'
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
'only-warn',
|
|
||||||
'react',
|
'react',
|
||||||
'react-hooks',
|
'react-hooks',
|
||||||
'@typescript-eslint'
|
'@typescript-eslint'
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
|
'prefer-arrow-callback': 'error',
|
||||||
|
'func-style': ['error', 'declaration'],
|
||||||
'space-before-function-paren': ['error', 'never'],
|
'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',
|
indent: 'off',
|
||||||
'@typescript-eslint/indent': ['warn', 2, {SwitchCase: 1}],
|
|
||||||
semi: 'off',
|
semi: 'off',
|
||||||
'@typescript-eslint/semi': ['warn', 'always'],
|
"camelcase": "off",
|
||||||
'no-unused-vars': '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/no-unused-vars': 'error',
|
||||||
'@typescript-eslint/ban-types': ['error'],
|
'@typescript-eslint/ban-types': ['error'],
|
||||||
'@typescript-eslint/no-floating-promises': 'off', // disabled cuz troublesome for SweetAlert since they never reject
|
'@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/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||||
'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies
|
'react-hooks/exhaustive-deps': 'warn' // Checks effect dependencies
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { fetchConfiguration } from './api';
|
import { FetchConfiguration } from './api';
|
||||||
|
|
||||||
describe.concurrent('API test', () => {
|
describe.concurrent('API test', () => {
|
||||||
it('Load environment', () => {
|
it('Load environment', () => {
|
||||||
|
@ -8,7 +8,7 @@ describe.concurrent('API test', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Fetch configuration', async() => {
|
it('Fetch configuration', async() => {
|
||||||
const configuration = await fetchConfiguration();
|
const configuration = await FetchConfiguration();
|
||||||
expect(configuration.MainContainer).toBeDefined();
|
expect(configuration.MainContainer).toBeDefined();
|
||||||
expect(configuration.MainContainer.Height).toBeGreaterThan(0);
|
expect(configuration.MainContainer.Height).toBeGreaterThan(0);
|
||||||
expect(configuration.MainContainer.Width).toBeGreaterThan(0);
|
expect(configuration.MainContainer.Width).toBeGreaterThan(0);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||||
* Fetch the configuration from the API
|
* Fetch the configuration from the API
|
||||||
* @returns {Configation} The model of the configuration for the application
|
* @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}`;
|
const url = `${import.meta.env.VITE_API_URL}`;
|
||||||
// The test library cannot use the Fetch API
|
// The test library cannot use the Fetch API
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
||||||
import { fetchConfiguration } from '../../API/api';
|
import { FetchConfiguration } from '../../API/api';
|
||||||
import { IEditorState } from '../../../Interfaces/IEditorState';
|
import { IEditorState } from '../../../Interfaces/IEditorState';
|
||||||
import { LoadState } from './Load';
|
import { LoadState } from './Load';
|
||||||
import { GetDefaultEditorState } from '../../../utils/default';
|
import { GetDefaultEditorState } from '../../../utils/default';
|
||||||
|
@ -10,7 +10,7 @@ export function NewEditor(
|
||||||
setLoaded: Dispatch<SetStateAction<boolean>>
|
setLoaded: Dispatch<SetStateAction<boolean>>
|
||||||
): void {
|
): void {
|
||||||
// Fetch the configuration from the API
|
// Fetch the configuration from the API
|
||||||
fetchConfiguration()
|
FetchConfiguration()
|
||||||
.then((configuration: IConfiguration) => {
|
.then((configuration: IConfiguration) => {
|
||||||
// Set the editor from the given properties of the API
|
// Set the editor from the given properties of the API
|
||||||
const editorState: IEditorState = GetDefaultEditorState(configuration);
|
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 './App.scss';
|
||||||
import { MainMenu } from '../MainMenu/MainMenu';
|
import { MainMenu } from '../MainMenu/MainMenu';
|
||||||
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import Editor from '../Editor/Editor';
|
import { Editor } from '../Editor/Editor';
|
||||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||||
import { LoadState } from './Actions/Load';
|
import { LoadState } from './Actions/Load';
|
||||||
import { LoadEditor, NewEditor } from './Actions/MenuActions';
|
import { LoadEditor, NewEditor } from './Actions/MenuActions';
|
||||||
|
@ -14,28 +14,11 @@ interface IAppProps {
|
||||||
root: Element | Document
|
root: Element | Document
|
||||||
}
|
}
|
||||||
|
|
||||||
export const App: React.FunctionComponent<IAppProps> = (props) => {
|
function UseHTTPGETStatePreloading(
|
||||||
const [isLoaded, setLoaded] = useState<boolean>(false);
|
isLoaded: boolean,
|
||||||
|
setEditorState: Dispatch<SetStateAction<IEditorState>>,
|
||||||
const defaultMainContainer = new ContainerModel(
|
setLoaded: Dispatch<SetStateAction<boolean>>
|
||||||
null,
|
): void {
|
||||||
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
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const queryString = window.location.search;
|
const queryString = window.location.search;
|
||||||
const urlParams = new URLSearchParams(queryString);
|
const urlParams = new URLSearchParams(queryString);
|
||||||
|
@ -56,6 +39,30 @@ export const App: React.FunctionComponent<IAppProps> = (props) => {
|
||||||
}, (error) => { throw new Error(error); });
|
}, (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) {
|
if (isLoaded) {
|
||||||
return (
|
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 * as React from 'react';
|
||||||
import { BarIcon } from './BarIcon';
|
import { BarIcon } from './BarIcon';
|
||||||
|
|
||||||
|
@ -7,34 +7,34 @@ interface IBarProps {
|
||||||
isSymbolsOpen: boolean
|
isSymbolsOpen: boolean
|
||||||
isElementsSidebarOpen: boolean
|
isElementsSidebarOpen: boolean
|
||||||
isHistoryOpen: boolean
|
isHistoryOpen: boolean
|
||||||
ToggleSidebar: () => void
|
toggleSidebar: () => void
|
||||||
ToggleSymbols: () => void
|
toggleSymbols: () => void
|
||||||
ToggleTimeline: () => void
|
toggleTimeline: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BAR_WIDTH = 64; // 4rem
|
export const BAR_WIDTH = 64; // 4rem
|
||||||
|
|
||||||
export const Bar: React.FC<IBarProps> = (props) => {
|
export function Bar(props: IBarProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className='fixed z-20 flex flex-col top-0 left-0 h-full w-16 bg-slate-100'>
|
<div className='fixed z-20 flex flex-col top-0 left-0 h-full w-16 bg-slate-100'>
|
||||||
<BarIcon
|
<BarIcon
|
||||||
isActive={props.isSidebarOpen}
|
isActive={props.isSidebarOpen}
|
||||||
title='Components'
|
title='Components'
|
||||||
onClick={() => props.ToggleSidebar()}>
|
onClick={() => props.toggleSidebar()}>
|
||||||
<CubeIcon className='heroicon'/>
|
<CubeIcon className='heroicon' />
|
||||||
</BarIcon>
|
</BarIcon>
|
||||||
<BarIcon
|
<BarIcon
|
||||||
isActive={props.isSymbolsOpen}
|
isActive={props.isSymbolsOpen}
|
||||||
title='Symbols'
|
title='Symbols'
|
||||||
onClick={() => props.ToggleSymbols()}>
|
onClick={() => props.toggleSymbols()}>
|
||||||
<LinkIcon className='heroicon'/>
|
<LinkIcon className='heroicon' />
|
||||||
</BarIcon>
|
</BarIcon>
|
||||||
<BarIcon
|
<BarIcon
|
||||||
isActive={props.isHistoryOpen}
|
isActive={props.isHistoryOpen}
|
||||||
title='Timeline'
|
title='Timeline'
|
||||||
onClick={() => props.ToggleTimeline()}>
|
onClick={() => props.toggleTimeline()}>
|
||||||
<ClockIcon className='heroicon'/>
|
<ClockIcon className='heroicon' />
|
||||||
</BarIcon>
|
</BarIcon>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ interface IBarIconProps {
|
||||||
onClick: () => void
|
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' : '';
|
const isActiveClasses = props.isActive ? 'border-l-4 border-blue-500 bg-slate-200' : '';
|
||||||
return (
|
return (
|
||||||
<button type="button"
|
<button type="button"
|
||||||
|
@ -16,7 +16,7 @@ export const BarIcon: React.FC<IBarIconProps> = (props) => {
|
||||||
onClick={() => props.onClick()}
|
onClick={() => props.onClick()}
|
||||||
>
|
>
|
||||||
<span className='sidebar-tooltip group-hover:scale-100'>{props.title}</span>
|
<span className='sidebar-tooltip group-hover:scale-100'>{props.title}</span>
|
||||||
{ props.children }
|
{props.children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { MenuAlt2Icon, MenuAlt3Icon, MenuIcon } from '@heroicons/react/outline';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { PropertyType } from '../../Enums/PropertyType';
|
import { PropertyType } from '../../Enums/PropertyType';
|
||||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||||
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
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 { InputGroup } from '../InputGroup/InputGroup';
|
||||||
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
|
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
|
||||||
import { Select } from '../Select/Select';
|
import { Select } from '../Select/Select';
|
||||||
|
@ -15,10 +15,8 @@ interface IContainerFormProps {
|
||||||
onChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
|
onChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCSSInputs = (
|
function GetCSSInputs(properties: IContainerProperties,
|
||||||
properties: IContainerProperties,
|
onChange: (key: string, value: string | number | boolean, type: PropertyType) => void): JSX.Element[] {
|
||||||
onChange: (key: string, value: string | number | boolean, type: PropertyType) => void
|
|
||||||
): JSX.Element[] => {
|
|
||||||
const groupInput: JSX.Element[] = [];
|
const groupInput: JSX.Element[] = [];
|
||||||
for (const key in properties.style) {
|
for (const key in properties.style) {
|
||||||
groupInput.push(<InputGroup
|
groupInput.push(<InputGroup
|
||||||
|
@ -29,13 +27,12 @@ const getCSSInputs = (
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='string'
|
type='string'
|
||||||
value={(properties.style as any)[key]}
|
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;
|
return groupInput;
|
||||||
};
|
}
|
||||||
|
|
||||||
const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className='grid grid-cols-2 gap-y-4'>
|
<div className='grid grid-cols-2 gap-y-4'>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
|
@ -45,8 +42,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='string'
|
type='string'
|
||||||
value={props.properties.id.toString()}
|
value={props.properties.id.toString()}
|
||||||
isDisabled={true}
|
isDisabled={true} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Parent name'
|
labelText='Parent name'
|
||||||
inputKey='parentId'
|
inputKey='parentId'
|
||||||
|
@ -54,8 +50,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='string'
|
type='string'
|
||||||
value={props.properties.parentId}
|
value={props.properties.parentId}
|
||||||
isDisabled={true}
|
isDisabled={true} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Type'
|
labelText='Type'
|
||||||
inputKey='type'
|
inputKey='type'
|
||||||
|
@ -63,8 +58,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='string'
|
type='string'
|
||||||
value={props.properties.type}
|
value={props.properties.type}
|
||||||
isDisabled={true}
|
isDisabled={true} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Displayed text'
|
labelText='Displayed text'
|
||||||
inputKey='displayedText'
|
inputKey='displayedText'
|
||||||
|
@ -72,8 +66,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='string'
|
type='string'
|
||||||
value={props.properties.displayedText?.toString()}
|
value={props.properties.displayedText?.toString()}
|
||||||
onChange={(event) => props.onChange('displayedText', event.target.value)}
|
onChange={(event) => props.onChange('displayedText', event.target.value)} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='x'
|
labelText='x'
|
||||||
inputKey='x'
|
inputKey='x'
|
||||||
|
@ -81,19 +74,18 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
isDisabled={props.properties.linkedSymbolId !== ''}
|
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(
|
onChange={(event) => props.onChange(
|
||||||
'x',
|
'x',
|
||||||
ApplyXMargin(
|
ApplyXMargin(
|
||||||
restoreX(
|
RestoreX(
|
||||||
Number(event.target.value),
|
Number(event.target.value),
|
||||||
props.properties.width,
|
props.properties.width,
|
||||||
props.properties.XPositionReference
|
props.properties.xPositionReference
|
||||||
),
|
),
|
||||||
props.properties.margin.left
|
props.properties.margin.left
|
||||||
)
|
)
|
||||||
)}
|
)} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='y'
|
labelText='y'
|
||||||
inputKey='y'
|
inputKey='y'
|
||||||
|
@ -101,8 +93,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
value={(props.properties.y - (props.properties.margin?.top ?? 0)).toString()}
|
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
|
<InputGroup
|
||||||
labelText='Minimum width'
|
labelText='Minimum width'
|
||||||
inputKey='minWidth'
|
inputKey='minWidth'
|
||||||
|
@ -111,8 +102,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
type='number'
|
type='number'
|
||||||
min={1}
|
min={1}
|
||||||
value={props.properties.minWidth.toString()}
|
value={props.properties.minWidth.toString()}
|
||||||
onChange={(event) => props.onChange('minWidth', Number(event.target.value))}
|
onChange={(event) => props.onChange('minWidth', Number(event.target.value))} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Maximum width'
|
labelText='Maximum width'
|
||||||
inputKey='maxWidth'
|
inputKey='maxWidth'
|
||||||
|
@ -121,8 +111,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
type='number'
|
type='number'
|
||||||
min={1}
|
min={1}
|
||||||
value={props.properties.maxWidth.toString()}
|
value={props.properties.maxWidth.toString()}
|
||||||
onChange={(event) => props.onChange('maxWidth', Number(event.target.value))}
|
onChange={(event) => props.onChange('maxWidth', Number(event.target.value))} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Width'
|
labelText='Width'
|
||||||
inputKey='width'
|
inputKey='width'
|
||||||
|
@ -132,8 +121,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
min={props.properties.minWidth}
|
min={props.properties.minWidth}
|
||||||
value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()}
|
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))}
|
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
|
<InputGroup
|
||||||
labelText='Height'
|
labelText='Height'
|
||||||
inputKey='height'
|
inputKey='height'
|
||||||
|
@ -142,8 +130,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
type='number'
|
type='number'
|
||||||
min={0}
|
min={0}
|
||||||
value={(props.properties.height + (props.properties.margin?.top ?? 0) + (props.properties.margin?.bottom ?? 0)).toString()}
|
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
|
<InputGroup
|
||||||
labelText='Margin left'
|
labelText='Margin left'
|
||||||
inputKey='left'
|
inputKey='left'
|
||||||
|
@ -152,8 +139,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
type='number'
|
type='number'
|
||||||
min={0}
|
min={0}
|
||||||
value={(props.properties.margin.left ?? 0).toString()}
|
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
|
<InputGroup
|
||||||
labelText='Margin bottom'
|
labelText='Margin bottom'
|
||||||
inputKey='bottom'
|
inputKey='bottom'
|
||||||
|
@ -162,8 +148,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
type='number'
|
type='number'
|
||||||
min={0}
|
min={0}
|
||||||
value={(props.properties.margin.bottom ?? 0).toString()}
|
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
|
<InputGroup
|
||||||
labelText='Margin top'
|
labelText='Margin top'
|
||||||
inputKey='top'
|
inputKey='top'
|
||||||
|
@ -172,8 +157,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
type='number'
|
type='number'
|
||||||
min={0}
|
min={0}
|
||||||
value={(props.properties.margin.top ?? 0).toString()}
|
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
|
<InputGroup
|
||||||
labelText='Margin right'
|
labelText='Margin right'
|
||||||
inputKey='right'
|
inputKey='right'
|
||||||
|
@ -182,8 +166,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
type='number'
|
type='number'
|
||||||
min={0}
|
min={0}
|
||||||
value={(props.properties.margin.right ?? 0).toString()}
|
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
|
<InputGroup
|
||||||
labelText='Flex'
|
labelText='Flex'
|
||||||
inputKey='isFlex'
|
inputKey='isFlex'
|
||||||
|
@ -191,8 +174,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={props.properties.isFlex}
|
checked={props.properties.isFlex}
|
||||||
onChange={(event) => props.onChange('isFlex', event.target.checked)}
|
onChange={(event) => props.onChange('isFlex', event.target.checked)} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Anchor'
|
labelText='Anchor'
|
||||||
inputKey='isAnchor'
|
inputKey='isAnchor'
|
||||||
|
@ -200,11 +182,10 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={props.properties.isAnchor}
|
checked={props.properties.isAnchor}
|
||||||
onChange={(event) => props.onChange('isAnchor', event.target.checked)}
|
onChange={(event) => props.onChange('isAnchor', event.target.checked)} />
|
||||||
/>
|
|
||||||
<RadioGroupButtons
|
<RadioGroupButtons
|
||||||
name='XPositionReference'
|
name='XPositionReference'
|
||||||
value={props.properties.XPositionReference.toString()}
|
value={props.properties.xPositionReference.toString()}
|
||||||
inputClassName='hidden'
|
inputClassName='hidden'
|
||||||
labelText='Horizontal alignment'
|
labelText='Horizontal alignment'
|
||||||
inputGroups={[
|
inputGroups={[
|
||||||
|
@ -233,8 +214,7 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
value: XPositionReference.Right.toString()
|
value: XPositionReference.Right.toString()
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
onChange={(event) => props.onChange('XPositionReference', Number(event.target.value))}
|
onChange={(event) => props.onChange('xPositionReference', Number(event.target.value))} />
|
||||||
/>
|
|
||||||
<Select
|
<Select
|
||||||
inputKey='linkedSymbolId'
|
inputKey='linkedSymbolId'
|
||||||
labelText='Align with symbol'
|
labelText='Align with symbol'
|
||||||
|
@ -245,11 +225,8 @@ const ContainerForm: React.FunctionComponent<IContainerFormProps> = (props) => {
|
||||||
value: symbol.id
|
value: symbol.id
|
||||||
}))}
|
}))}
|
||||||
value={props.properties.linkedSymbolId ?? ''}
|
value={props.properties.linkedSymbolId ?? ''}
|
||||||
onChange={(event) => props.onChange('linkedSymbolId', event.target.value)}
|
onChange={(event) => props.onChange('linkedSymbolId', event.target.value)} />
|
||||||
/>
|
{GetCSSInputs(props.properties, props.onChange)}
|
||||||
{ getCSSInputs(props.properties, props.onChange) }
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ContainerForm;
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { expect, describe, it, vi } from 'vitest';
|
import { expect, describe, it, vi } from 'vitest';
|
||||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||||
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
|
||||||
import { Properties } from './ContainerProperties';
|
import { Properties } from './ContainerProperties';
|
||||||
|
|
||||||
describe.concurrent('Properties', () => {
|
describe.concurrent('Properties', () => {
|
||||||
|
@ -33,7 +33,7 @@ describe.concurrent('Properties', () => {
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
margin: {},
|
margin: {},
|
||||||
XPositionReference: XPositionReference.Left,
|
xPositionReference: XPositionReference.Left,
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { PropertyType } from '../../Enums/PropertyType';
|
import { PropertyType } from '../../Enums/PropertyType';
|
||||||
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import ContainerForm from './ContainerForm';
|
import { ContainerForm } from './ContainerForm';
|
||||||
|
|
||||||
interface IPropertiesProps {
|
interface IPropertiesProps {
|
||||||
properties?: IContainerProperties
|
properties?: IContainerProperties
|
||||||
|
@ -10,7 +10,7 @@ interface IPropertiesProps {
|
||||||
onChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
|
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) {
|
if (props.properties === undefined) {
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,7 @@ export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps)
|
||||||
<ContainerForm
|
<ContainerForm
|
||||||
properties={props.properties}
|
properties={props.properties}
|
||||||
symbols={props.symbols}
|
symbols={props.symbols}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange} />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -2,16 +2,15 @@ import { Dispatch, SetStateAction } from 'react';
|
||||||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||||
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
||||||
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { findContainerById, MakeIterator } from '../../../utils/itertools';
|
import { FindContainerById, MakeIterator } from '../../../utils/itertools';
|
||||||
import { getCurrentHistory, UpdateCounters } from '../Editor';
|
import { GetCurrentHistory, UpdateCounters } from '../Editor';
|
||||||
import { AddMethod } from '../../../Enums/AddMethod';
|
import { AddMethod } from '../../../Enums/AddMethod';
|
||||||
import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer';
|
import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer';
|
||||||
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../../utils/default';
|
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../../utils/default';
|
||||||
import { ApplyBehaviors } from '../Behaviors/Behaviors';
|
import { ApplyBehaviors } from '../Behaviors/Behaviors';
|
||||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
import Swal from 'sweetalert2';
|
import Swal from 'sweetalert2';
|
||||||
import { ApplyMargin, transformX } from '../../../utils/svg';
|
import { ApplyMargin, TransformX } from '../../../utils/svg';
|
||||||
import { Flex } from '../Behaviors/FlexBehaviors';
|
|
||||||
import { PropertyType } from '../../../Enums/PropertyType';
|
import { PropertyType } from '../../../Enums/PropertyType';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,16 +24,16 @@ export function SelectContainer(
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Select ${containerId}`,
|
lastAction: `Select ${containerId}`,
|
||||||
MainContainer: structuredClone(current.MainContainer),
|
mainContainer: structuredClone(current.mainContainer),
|
||||||
SelectedContainerId: containerId,
|
selectedContainerId: containerId,
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters),
|
typeCounters: Object.assign({}, current.typeCounters),
|
||||||
Symbols: structuredClone(current.Symbols),
|
symbols: structuredClone(current.symbols),
|
||||||
SelectedSymbolId: current.SelectedSymbolId
|
selectedSymbolId: current.selectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
@ -55,11 +54,11 @@ export function DeleteContainer(
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
const mainContainerClone: IContainerModel = structuredClone(current.mainContainer);
|
||||||
const container = findContainerById(mainContainerClone, containerId);
|
const container = FindContainerById(mainContainerClone, containerId);
|
||||||
|
|
||||||
if (container === undefined) {
|
if (container === undefined) {
|
||||||
throw new Error(`[DeleteContainer] Tried to delete a container that is not present in the main container: ${containerId}`);
|
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!');
|
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);
|
UnlinkSymbol(newSymbols, container);
|
||||||
|
|
||||||
const index = container.parent.children.indexOf(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');
|
throw new Error('[DeleteContainer] Could not find container among parent\'s children');
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyBehaviorsOnSiblings(container, current.Symbols);
|
ApplyBehaviorsOnSiblings(container, current.symbols);
|
||||||
|
|
||||||
// Select the previous container
|
// Select the previous container
|
||||||
// or select the one above
|
// or select the one above
|
||||||
const SelectedContainerId = GetSelectedContainerOnDelete(
|
const selectedContainerId = GetSelectedContainerOnDelete(
|
||||||
mainContainerClone,
|
mainContainerClone,
|
||||||
current.SelectedContainerId,
|
current.selectedContainerId,
|
||||||
container.parent,
|
container.parent,
|
||||||
index
|
index
|
||||||
);
|
);
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Delete ${containerId}`,
|
lastAction: `Delete ${containerId}`,
|
||||||
MainContainer: mainContainerClone,
|
mainContainer: mainContainerClone,
|
||||||
SelectedContainerId,
|
selectedContainerId,
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters),
|
typeCounters: Object.assign({}, current.typeCounters),
|
||||||
Symbols: newSymbols,
|
symbols: newSymbols,
|
||||||
SelectedSymbolId: current.SelectedSymbolId
|
selectedSymbolId: current.selectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetSelectedContainerOnDelete(mainContainerClone: IContainerModel, selectedContainerId: string, parent: IContainerModel, index: number): string {
|
function GetSelectedContainerOnDelete(
|
||||||
const SelectedContainer = findContainerById(mainContainerClone, selectedContainerId) ??
|
mainContainerClone: IContainerModel,
|
||||||
|
selectedContainerId: string,
|
||||||
|
parent: IContainerModel,
|
||||||
|
index: number
|
||||||
|
): string {
|
||||||
|
const newSelectedContainer = FindContainerById(mainContainerClone, selectedContainerId) ??
|
||||||
parent.children.at(index - 1) ??
|
parent.children.at(index - 1) ??
|
||||||
parent;
|
parent;
|
||||||
const SelectedContainerId = SelectedContainer.properties.id;
|
const newSelectedContainerId = newSelectedContainer.properties.id;
|
||||||
return SelectedContainerId;
|
return newSelectedContainerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function UnlinkSymbol(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
|
function UnlinkSymbol(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
|
||||||
|
@ -191,7 +195,7 @@ export function AddContainer(
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
// Get the preset properties from the API
|
// 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
|
// 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);
|
UpdateCounters(newCounters, type);
|
||||||
const count = newCounters[type];
|
const count = newCounters[type];
|
||||||
|
|
||||||
// Create maincontainer model
|
// Create maincontainer model
|
||||||
const clone: IContainerModel = structuredClone(current.MainContainer);
|
const clone: IContainerModel = structuredClone(current.mainContainer);
|
||||||
|
|
||||||
// Find the parent
|
// Find the parent
|
||||||
const parentClone: IContainerModel | undefined = findContainerById(
|
const parentClone: IContainerModel | undefined = FindContainerById(
|
||||||
clone, parentId
|
clone, parentId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -258,18 +262,18 @@ export function AddContainer(
|
||||||
|
|
||||||
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
|
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
|
||||||
|
|
||||||
ApplyBehaviors(newContainer, current.Symbols);
|
ApplyBehaviors(newContainer, current.symbols);
|
||||||
|
|
||||||
ApplyBehaviorsOnSiblings(newContainer, current.Symbols);
|
ApplyBehaviorsOnSiblings(newContainer, current.symbols);
|
||||||
|
|
||||||
// Update the state
|
// Update the state
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
lastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
||||||
MainContainer: clone,
|
mainContainer: clone,
|
||||||
SelectedContainerId: parentClone.properties.id,
|
selectedContainerId: parentClone.properties.id,
|
||||||
TypeCounters: newCounters,
|
typeCounters: newCounters,
|
||||||
Symbols: structuredClone(current.Symbols),
|
symbols: structuredClone(current.symbols),
|
||||||
SelectedSymbolId: current.SelectedSymbolId
|
selectedSymbolId: current.selectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
@ -280,8 +284,8 @@ function UpdateParentChildrenList(parentClone: IContainerModel | null | undefine
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
parentClone.children.sort(
|
parentClone.children.sort(
|
||||||
(a, b) => transformX(a.properties.x, a.properties.width, a.properties.XPositionReference) -
|
(a, b) => TransformX(a.properties.x, a.properties.width, a.properties.xPositionReference) -
|
||||||
transformX(b.properties.x, b.properties.width, b.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(
|
export function OnPropertyChange(
|
||||||
key: string,
|
key: string,
|
||||||
value: string | number | boolean,
|
value: string | number | boolean,
|
||||||
type: PropertyType = PropertyType.SIMPLE,
|
type: PropertyType = PropertyType.Simple,
|
||||||
selected: IContainerModel | undefined,
|
selected: IContainerModel | undefined,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
if (selected === null ||
|
if (selected === null ||
|
||||||
|
@ -402,22 +406,22 @@ export function OnPropertyChange(
|
||||||
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
const mainContainerClone: IContainerModel = structuredClone(current.mainContainer);
|
||||||
const container: ContainerModel | undefined = findContainerById(mainContainerClone, selected.properties.id);
|
const container: ContainerModel | undefined = FindContainerById(mainContainerClone, selected.properties.id);
|
||||||
|
|
||||||
if (container === null || container === undefined) {
|
if (container === null || container === undefined) {
|
||||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
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({
|
history.push({
|
||||||
LastAction: `Change ${key} of ${container.properties.id}`,
|
lastAction: `Change ${key} of ${container.properties.id}`,
|
||||||
MainContainer: mainContainerClone,
|
mainContainer: mainContainerClone,
|
||||||
SelectedContainerId: container.properties.id,
|
selectedContainerId: container.properties.id,
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters),
|
typeCounters: Object.assign({}, current.typeCounters),
|
||||||
Symbols: structuredClone(current.Symbols),
|
symbols: structuredClone(current.symbols),
|
||||||
SelectedSymbolId: current.SelectedSymbolId
|
selectedSymbolId: current.selectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
@ -463,10 +467,10 @@ function SetContainer(
|
||||||
|
|
||||||
function AssignProperty(container: ContainerModel, key: string, value: string | number | boolean, type: PropertyType): void {
|
function AssignProperty(container: ContainerModel, key: string, value: string | number | boolean, type: PropertyType): void {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PropertyType.STYLE:
|
case PropertyType.Style:
|
||||||
(container.properties.style as any)[key] = value;
|
(container.properties.style as any)[key] = value;
|
||||||
break;
|
break;
|
||||||
case PropertyType.MARGIN:
|
case PropertyType.Margin:
|
||||||
SetMargin();
|
SetMargin();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -514,10 +518,11 @@ function LinkSymbol(
|
||||||
|
|
||||||
newSymbol.linkedContainers.add(containerId);
|
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) {
|
if (newContainer.parent === null || newContainer.parent === undefined) {
|
||||||
return;
|
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 { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||||
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
||||||
import { getCircularReplacer } from '../../../utils/saveload';
|
import { GetCircularReplacer } from '../../../utils/saveload';
|
||||||
import { ID } from '../../SVG/SVG';
|
import { ID } from '../../SVG/SVG';
|
||||||
import { IEditorState } from '../../../Interfaces/IEditorState';
|
import { IEditorState } from '../../../Interfaces/IEditorState';
|
||||||
import { SHOW_SELECTOR_TEXT } from '../../../utils/default';
|
import { SHOW_SELECTOR_TEXT } from '../../../utils/default';
|
||||||
|
@ -26,15 +26,15 @@ export function SaveEditorAsJSON(
|
||||||
myWorker.onmessage = (event) => {
|
myWorker.onmessage = (event) => {
|
||||||
const data = event.data;
|
const data = event.data;
|
||||||
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
|
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
|
||||||
createDownloadNode(exportName, dataStr);
|
CreateDownloadNode(exportName, dataStr);
|
||||||
myWorker.terminate();
|
myWorker.terminate();
|
||||||
};
|
};
|
||||||
return;
|
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)}`;
|
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
|
||||||
createDownloadNode(exportName, dataStr);
|
CreateDownloadNode(exportName, dataStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SaveEditorAsSVG(): void {
|
export function SaveEditorAsSVG(): void {
|
||||||
|
@ -64,10 +64,10 @@ export function SaveEditorAsSVG(): void {
|
||||||
let source = serializer.serializeToString(svg);
|
let source = serializer.serializeToString(svg);
|
||||||
|
|
||||||
// add name spaces.
|
// 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"');
|
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"');
|
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.
|
// convert svg source to URI data scheme.
|
||||||
const url = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source);
|
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');
|
const downloadAnchorNode = document.createElement('a');
|
||||||
downloadAnchorNode.href = datastring;
|
downloadAnchorNode.href = datastring;
|
||||||
downloadAnchorNode.download = filename;
|
downloadAnchorNode.download = filename;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Dispatch, SetStateAction } from 'react';
|
||||||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||||
import { ENABLE_SHORTCUTS } from '../../../utils/default';
|
import { ENABLE_SHORTCUTS } from '../../../utils/default';
|
||||||
|
|
||||||
export function onKeyDown(
|
export function OnKey(
|
||||||
event: KeyboardEvent,
|
event: KeyboardEvent,
|
||||||
history: IHistoryState[],
|
history: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
|
|
|
@ -4,10 +4,10 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
import { GetDefaultSymbolModel } from '../../../utils/default';
|
import { GetDefaultSymbolModel } from '../../../utils/default';
|
||||||
import { findContainerById } from '../../../utils/itertools';
|
import { FindContainerById } from '../../../utils/itertools';
|
||||||
import { restoreX } from '../../../utils/svg';
|
import { RestoreX } from '../../../utils/svg';
|
||||||
import { ApplyBehaviors } from '../Behaviors/Behaviors';
|
import { ApplyBehaviors } from '../Behaviors/Behaviors';
|
||||||
import { getCurrentHistory, UpdateCounters } from '../Editor';
|
import { GetCurrentHistory, UpdateCounters } from '../Editor';
|
||||||
|
|
||||||
export function AddSymbol(
|
export function AddSymbol(
|
||||||
name: string,
|
name: string,
|
||||||
|
@ -17,7 +17,7 @@ export function AddSymbol(
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
const symbolConfig = configuration.AvailableSymbols
|
const symbolConfig = configuration.AvailableSymbols
|
||||||
|
@ -27,22 +27,22 @@ export function AddSymbol(
|
||||||
throw new Error('[AddSymbol] Symbol could not be found in the config');
|
throw new Error('[AddSymbol] Symbol could not be found in the config');
|
||||||
}
|
}
|
||||||
const type = `symbol-${name}`;
|
const type = `symbol-${name}`;
|
||||||
const newCounters = structuredClone(current.TypeCounters);
|
const newCounters = structuredClone(current.typeCounters);
|
||||||
UpdateCounters(newCounters, type);
|
UpdateCounters(newCounters, type);
|
||||||
|
|
||||||
const newSymbols = structuredClone(current.Symbols);
|
const newSymbols = structuredClone(current.symbols);
|
||||||
const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, newCounters, type, symbolConfig);
|
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);
|
newSymbols.set(newSymbol.id, newSymbol);
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Add ${name}`,
|
lastAction: `Add ${name}`,
|
||||||
MainContainer: structuredClone(current.MainContainer),
|
mainContainer: structuredClone(current.mainContainer),
|
||||||
SelectedContainerId: current.SelectedContainerId,
|
selectedContainerId: current.selectedContainerId,
|
||||||
TypeCounters: newCounters,
|
typeCounters: newCounters,
|
||||||
Symbols: newSymbols,
|
symbols: newSymbols,
|
||||||
SelectedSymbolId: newSymbol.id
|
selectedSymbolId: newSymbol.id
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
@ -55,16 +55,16 @@ export function SelectSymbol(
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Select ${symbolId}`,
|
lastAction: `Select ${symbolId}`,
|
||||||
MainContainer: structuredClone(current.MainContainer),
|
mainContainer: structuredClone(current.mainContainer),
|
||||||
SelectedContainerId: current.SelectedContainerId,
|
selectedContainerId: current.selectedContainerId,
|
||||||
TypeCounters: structuredClone(current.TypeCounters),
|
typeCounters: structuredClone(current.typeCounters),
|
||||||
Symbols: structuredClone(current.Symbols),
|
symbols: structuredClone(current.symbols),
|
||||||
SelectedSymbolId: symbolId
|
selectedSymbolId: symbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
@ -77,29 +77,29 @@ export function DeleteSymbol(
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
const newSymbols = structuredClone(current.Symbols);
|
const newSymbols = structuredClone(current.symbols);
|
||||||
const symbol = newSymbols.get(symbolId);
|
const symbol = newSymbols.get(symbolId);
|
||||||
|
|
||||||
if (symbol === undefined) {
|
if (symbol === undefined) {
|
||||||
throw new Error(`[DeleteSymbol] Could not find symbol in the current state!: ${symbolId}`);
|
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);
|
UnlinkContainers(symbol, newMainContainer);
|
||||||
|
|
||||||
newSymbols.delete(symbolId);
|
newSymbols.delete(symbolId);
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Select ${symbolId}`,
|
lastAction: `Select ${symbolId}`,
|
||||||
MainContainer: newMainContainer,
|
mainContainer: newMainContainer,
|
||||||
SelectedContainerId: current.SelectedContainerId,
|
selectedContainerId: current.selectedContainerId,
|
||||||
TypeCounters: structuredClone(current.TypeCounters),
|
typeCounters: structuredClone(current.typeCounters),
|
||||||
Symbols: newSymbols,
|
symbols: newSymbols,
|
||||||
SelectedSymbolId: symbolId
|
selectedSymbolId: symbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
@ -107,7 +107,7 @@ export function DeleteSymbol(
|
||||||
|
|
||||||
function UnlinkContainers(symbol: ISymbolModel, newMainContainer: IContainerModel): void {
|
function UnlinkContainers(symbol: ISymbolModel, newMainContainer: IContainerModel): void {
|
||||||
symbol.linkedContainers.forEach((containerId) => {
|
symbol.linkedContainers.forEach((containerId) => {
|
||||||
const container = findContainerById(newMainContainer, containerId);
|
const container = FindContainerById(newMainContainer, containerId);
|
||||||
|
|
||||||
if (container === undefined) {
|
if (container === undefined) {
|
||||||
return;
|
return;
|
||||||
|
@ -131,15 +131,15 @@ export function OnPropertyChange(
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
if (current.SelectedSymbolId === '') {
|
if (current.selectedSymbolId === '') {
|
||||||
throw new Error('[OnSymbolPropertyChange] Property was changed before selecting a symbol');
|
throw new Error('[OnSymbolPropertyChange] Property was changed before selecting a symbol');
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSymbols: Map<string, ISymbolModel> = structuredClone(current.Symbols);
|
const newSymbols: Map<string, ISymbolModel> = structuredClone(current.symbols);
|
||||||
const symbol = newSymbols.get(current.SelectedSymbolId);
|
const symbol = newSymbols.get(current.selectedSymbolId);
|
||||||
|
|
||||||
if (symbol === null || symbol === undefined) {
|
if (symbol === null || symbol === undefined) {
|
||||||
throw new Error('[OnSymbolPropertyChange] Symbol model was not found in state!');
|
throw new Error('[OnSymbolPropertyChange] Symbol model was not found in state!');
|
||||||
|
@ -147,9 +147,9 @@ export function OnPropertyChange(
|
||||||
|
|
||||||
(symbol as any)[key] = value;
|
(symbol as any)[key] = value;
|
||||||
|
|
||||||
const newMainContainer = structuredClone(current.MainContainer);
|
const newMainContainer = structuredClone(current.mainContainer);
|
||||||
symbol.linkedContainers.forEach((containerId) => {
|
symbol.linkedContainers.forEach((containerId) => {
|
||||||
const container = findContainerById(newMainContainer, containerId);
|
const container = FindContainerById(newMainContainer, containerId);
|
||||||
|
|
||||||
if (container === undefined) {
|
if (container === undefined) {
|
||||||
return;
|
return;
|
||||||
|
@ -159,12 +159,12 @@ export function OnPropertyChange(
|
||||||
});
|
});
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Change ${key} of ${symbol.id}`,
|
lastAction: `Change ${key} of ${symbol.id}`,
|
||||||
MainContainer: newMainContainer,
|
mainContainer: newMainContainer,
|
||||||
SelectedContainerId: current.SelectedContainerId,
|
selectedContainerId: current.selectedContainerId,
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters),
|
typeCounters: Object.assign({}, current.typeCounters),
|
||||||
Symbols: newSymbols,
|
symbols: newSymbols,
|
||||||
SelectedSymbolId: symbol.id
|
selectedSymbolId: symbol.id
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { constraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
|
import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Impose the container position to its siblings
|
* Impose the container position to its siblings
|
||||||
|
@ -31,9 +31,9 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
|
||||||
child => !child.properties.isAnchor
|
child => !child.properties.isAnchor
|
||||||
);
|
);
|
||||||
|
|
||||||
const overlappingContainers = getOverlappingContainers(container, rigidBodies);
|
const overlappingContainers = GetOverlappingContainers(container, rigidBodies);
|
||||||
for (const overlappingContainer of overlappingContainers) {
|
for (const overlappingContainer of overlappingContainers) {
|
||||||
constraintBodyInsideUnallocatedWidth(overlappingContainer);
|
ConstraintBodyInsideUnallocatedWidth(overlappingContainer);
|
||||||
}
|
}
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
|
||||||
* @param containers A list of containers
|
* @param containers A list of containers
|
||||||
* @returns A list of overlapping containers
|
* @returns A list of overlapping containers
|
||||||
*/
|
*/
|
||||||
function getOverlappingContainers(
|
function GetOverlappingContainers(
|
||||||
container: IContainerModel,
|
container: IContainerModel,
|
||||||
containers: IContainerModel[]
|
containers: IContainerModel[]
|
||||||
): IContainerModel[] {
|
): IContainerModel[] {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import Swal from 'sweetalert2';
|
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { Simplex } from '../../../utils/simplex';
|
import { Simplex } from '../../../utils/simplex';
|
||||||
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
|
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { reversePairwise } from '../../../utils/itertools';
|
import { ReversePairwise } from '../../../utils/itertools';
|
||||||
import { Flex } from './FlexBehaviors';
|
import { Flex } from './FlexBehaviors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,7 +32,7 @@ export function PushContainers(container: IContainerModel): IContainerModel {
|
||||||
// FIXME: A fix was applied using toFixed(2).
|
// FIXME: A fix was applied using toFixed(2).
|
||||||
// FIXME: A coverture check must be done to ensure that all scenarios are covered
|
// 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) {
|
for (const { cur, next } of it) {
|
||||||
const hasSpaceBetween = next.properties.x + next.properties.width < cur.properties.x;
|
const hasSpaceBetween = next.properties.x + next.properties.width < cur.properties.x;
|
||||||
|
|
|
@ -22,8 +22,8 @@ import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
||||||
export function ApplyRigidBody(
|
export function ApplyRigidBody(
|
||||||
container: IContainerModel
|
container: IContainerModel
|
||||||
): IContainerModel {
|
): IContainerModel {
|
||||||
container = constraintBodyInsideParent(container);
|
container = ConstraintBodyInsideParent(container);
|
||||||
container = constraintBodyInsideUnallocatedWidth(container);
|
container = ConstraintBodyInsideUnallocatedWidth(container);
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export function ApplyRigidBody(
|
||||||
* @param container
|
* @param container
|
||||||
* @returns Updated container
|
* @returns Updated container
|
||||||
*/
|
*/
|
||||||
function constraintBodyInsideParent(
|
function ConstraintBodyInsideParent(
|
||||||
container: IContainerModel
|
container: IContainerModel
|
||||||
): IContainerModel {
|
): IContainerModel {
|
||||||
if (container.parent === null || container.parent === undefined) {
|
if (container.parent === null || container.parent === undefined) {
|
||||||
|
@ -46,7 +46,7 @@ function constraintBodyInsideParent(
|
||||||
const parentWidth = parentProperties.width;
|
const parentWidth = parentProperties.width;
|
||||||
const parentHeight = parentProperties.height;
|
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
|
* @param height height of the rectangle
|
||||||
* @returns Updated container
|
* @returns Updated container
|
||||||
*/
|
*/
|
||||||
function constraintBodyInsideSpace(
|
function ConstraintBodyInsideSpace(
|
||||||
container: IContainerModel,
|
container: IContainerModel,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
|
@ -113,7 +113,7 @@ function constraintBodyInsideSpace(
|
||||||
* @param container
|
* @param container
|
||||||
* @returns Updated container
|
* @returns Updated container
|
||||||
*/
|
*/
|
||||||
export function constraintBodyInsideUnallocatedWidth(
|
export function ConstraintBodyInsideUnallocatedWidth(
|
||||||
container: IContainerModel
|
container: IContainerModel
|
||||||
): IContainerModel {
|
): IContainerModel {
|
||||||
if (container.parent === null || container.parent === undefined) {
|
if (container.parent === null || container.parent === undefined) {
|
||||||
|
@ -121,7 +121,7 @@ export function constraintBodyInsideUnallocatedWidth(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the available spaces of the parent
|
// 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 containerX = container.properties.x;
|
||||||
const containerWidth = container.properties.width;
|
const containerWidth = container.properties.width;
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ export function constraintBodyInsideUnallocatedWidth(
|
||||||
// Check if the container actually fit inside
|
// Check if the container actually fit inside
|
||||||
// It will usually fit if it was alrady fitting
|
// It will usually fit if it was alrady fitting
|
||||||
const availableWidthFound = availableWidths.find((width) =>
|
const availableWidthFound = availableWidths.find((width) =>
|
||||||
isFitting(container.properties.width, width)
|
IsFitting(container.properties.width, width)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (availableWidthFound === undefined) {
|
if (availableWidthFound === undefined) {
|
||||||
|
@ -170,7 +170,7 @@ export function constraintBodyInsideUnallocatedWidth(
|
||||||
// We want the container to fit automatically inside the available space
|
// We want the container to fit automatically inside the available space
|
||||||
// even if it means to resize the container
|
// even if it means to resize the container
|
||||||
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
|
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
|
||||||
return isFitting(container.properties.minWidth, width);
|
return IsFitting(container.properties.minWidth, width);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (availableWidth === undefined) {
|
if (availableWidth === undefined) {
|
||||||
|
@ -191,7 +191,7 @@ export function constraintBodyInsideUnallocatedWidth(
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
return constraintBodyInsideSpace(
|
return ConstraintBodyInsideSpace(
|
||||||
container,
|
container,
|
||||||
availableWidthFound.x,
|
availableWidthFound.x,
|
||||||
0,
|
0,
|
||||||
|
@ -206,10 +206,10 @@ export function constraintBodyInsideUnallocatedWidth(
|
||||||
* @param sizePointer Size space to check
|
* @param sizePointer Size space to check
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const isFitting = (
|
function IsFitting(containerWidth: number,
|
||||||
containerWidth: number,
|
sizePointer: ISizePointer): boolean {
|
||||||
sizePointer: ISizePointer
|
return containerWidth <= sizePointer.width;
|
||||||
): boolean => containerWidth <= sizePointer.width;
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the unallocated widths inside a container
|
* 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)
|
* @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)
|
* @returns {ISizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
|
||||||
*/
|
*/
|
||||||
function getAvailableWidths(
|
function GetAvailableWidths(
|
||||||
container: IContainerModel,
|
container: IContainerModel,
|
||||||
exception: IContainerModel
|
exception: IContainerModel
|
||||||
): ISizePointer[] {
|
): ISizePointer[] {
|
||||||
|
@ -251,7 +251,7 @@ function getAvailableWidths(
|
||||||
// In order to find unallocated space,
|
// In order to find unallocated space,
|
||||||
// We need to calculate the overlap between the two containers
|
// We need to calculate the overlap between the two containers
|
||||||
// We only works with widths meaning in 1D (with lines)
|
// We only works with widths meaning in 1D (with lines)
|
||||||
const newUnallocatedWidths = getAvailableWidthsTwoLines(
|
const newUnallocatedWidths = GetAvailableWidthsTwoLines(
|
||||||
unallocatedSpace,
|
unallocatedSpace,
|
||||||
childX,
|
childX,
|
||||||
childWidth
|
childWidth
|
||||||
|
@ -274,7 +274,7 @@ function getAvailableWidths(
|
||||||
* @param rectWidth width of the second line
|
* @param rectWidth width of the second line
|
||||||
* @returns Available widths
|
* @returns Available widths
|
||||||
*/
|
*/
|
||||||
function getAvailableWidthsTwoLines(
|
function GetAvailableWidthsTwoLines(
|
||||||
unallocatedSpace: ISizePointer,
|
unallocatedSpace: ISizePointer,
|
||||||
rectX: number,
|
rectX: number,
|
||||||
rectWidth: number
|
rectWidth: number
|
||||||
|
@ -295,18 +295,18 @@ function getAvailableWidthsTwoLines(
|
||||||
|
|
||||||
const isOverlappingOnTheLeft = unallocatedSpace.x >= rectX;
|
const isOverlappingOnTheLeft = unallocatedSpace.x >= rectX;
|
||||||
if (isOverlappingOnTheLeft) {
|
if (isOverlappingOnTheLeft) {
|
||||||
return getAvailableWidthsLeft(unallocatedSpaceRight, rectRight);
|
return GetAvailableWidthsLeft(unallocatedSpaceRight, rectRight);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOverlappingOnTheRight = rectRight >= unallocatedSpaceRight;
|
const isOverlappingOnTheRight = rectRight >= unallocatedSpaceRight;
|
||||||
if (isOverlappingOnTheRight) {
|
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) {
|
if (unallocatedSpaceRight - rectRight <= 0) {
|
||||||
return [];
|
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) {
|
if (rectX - unallocatedSpaceX <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -332,7 +332,7 @@ function getAvailableWidthsRight(unallocatedSpaceX: number, rectX: number): ISiz
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableWidthsMiddle(
|
function GetAvailableWidthsMiddle(
|
||||||
unallocatedSpaceX: number,
|
unallocatedSpaceX: number,
|
||||||
unallocatedSpaceRight: number,
|
unallocatedSpaceRight: number,
|
||||||
rectX: number,
|
rectX: number,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
import { applyParentTransform } from '../../../utils/itertools';
|
import { ApplyParentTransform } from '../../../utils/itertools';
|
||||||
import { restoreX, transformX } from '../../../utils/svg';
|
import { RestoreX, TransformX } from '../../../utils/svg';
|
||||||
|
|
||||||
export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel {
|
export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel {
|
||||||
container.properties.x = transformX(symbol.x, symbol.width, symbol.config.XPositionReference);
|
container.properties.x = TransformX(symbol.x, symbol.width, symbol.config.XPositionReference);
|
||||||
container.properties.x = restoreX(container.properties.x, container.properties.width, container.properties.XPositionReference);
|
container.properties.x = RestoreX(container.properties.x, container.properties.width, container.properties.xPositionReference);
|
||||||
const [x] = applyParentTransform(container.parent, container.properties.x, 0);
|
const [x] = ApplyParentTransform(container.parent, container.properties.x, 0);
|
||||||
container.properties.x = x;
|
container.properties.x = x;
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@ import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
import { UI } from '../UI/UI';
|
import { UI } from '../UI/UI';
|
||||||
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, OnPropertyChange } from './Actions/ContainerOperations';
|
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, OnPropertyChange } from './Actions/ContainerOperations';
|
||||||
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
|
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
|
||||||
import { onKeyDown } from './Actions/Shortcuts';
|
import { OnKey } from './Actions/Shortcuts';
|
||||||
import EditorEvents from '../../Events/EditorEvents';
|
import EditorEvents from '../../Events/EditorEvents';
|
||||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||||
import { MAX_HISTORY } from '../../utils/default';
|
import { MAX_HISTORY } from '../../utils/default';
|
||||||
import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations';
|
import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations';
|
||||||
import { findContainerById } from '../../utils/itertools';
|
import { FindContainerById } from '../../utils/itertools';
|
||||||
|
|
||||||
interface IEditorProps {
|
interface IEditorProps {
|
||||||
root: Element | Document
|
root: Element | Document
|
||||||
|
@ -29,35 +29,40 @@ export function UpdateCounters(counters: Record<string, number>, type: string):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCurrentHistory = (history: IHistoryState[], historyCurrentStep: number): IHistoryState[] =>
|
export function GetCurrentHistory(history: IHistoryState[], historyCurrentStep: number): IHistoryState[] {
|
||||||
history.slice(
|
return history.slice(
|
||||||
Math.max(0, history.length - MAX_HISTORY), // change this to 0 for unlimited (not recommanded because of overflow)
|
Math.max(0, history.length - MAX_HISTORY),
|
||||||
historyCurrentStep + 1
|
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[],
|
history: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyUp = (event: KeyboardEvent): void => onKeyDown(
|
function OnKeyUp(event: KeyboardEvent): void {
|
||||||
event,
|
return OnKey(
|
||||||
history,
|
event,
|
||||||
historyCurrentStep,
|
history,
|
||||||
setHistoryCurrentStep
|
historyCurrentStep,
|
||||||
);
|
setHistoryCurrentStep
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('keyup', onKeyUp);
|
window.addEventListener('keyup', OnKeyUp);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keyup', onKeyUp);
|
window.removeEventListener('keyup', OnKeyUp);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function useWindowEvents(
|
function UseWindowEvents(
|
||||||
root: Element | Document,
|
root: Element | Document,
|
||||||
history: IHistoryState[],
|
history: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
|
@ -76,15 +81,17 @@ function useWindowEvents(
|
||||||
|
|
||||||
const funcs = new Map<string, () => void>();
|
const funcs = new Map<string, () => void>();
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
const func = (eventInitDict?: CustomEventInit): void => event.func(
|
function Func(eventInitDict?: CustomEventInit): void {
|
||||||
root,
|
return event.func(
|
||||||
editorState,
|
root,
|
||||||
setHistory,
|
editorState,
|
||||||
setHistoryCurrentStep,
|
setHistory,
|
||||||
eventInitDict
|
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 () => {
|
return () => {
|
||||||
for (const event of events) {
|
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 [history, setHistory] = React.useState<IHistoryState[]>(structuredClone(props.history));
|
||||||
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
|
const [historyCurrentStep, setHistoryCurrentStep] = React.useState<number>(props.historyCurrentStep);
|
||||||
const editorRef = useRef<HTMLDivElement>(null);
|
const editorRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
|
UseShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
|
||||||
useWindowEvents(
|
UseWindowEvents(
|
||||||
props.root,
|
props.root,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
|
@ -115,32 +122,32 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const configuration = props.configuration;
|
const configuration = props.configuration;
|
||||||
const current = getCurrentHistoryState(history, historyCurrentStep);
|
const current = GetCurrentHistoryState(history, historyCurrentStep);
|
||||||
const selected = findContainerById(current.MainContainer, current.SelectedContainerId);
|
const selected = FindContainerById(current.mainContainer, current.selectedContainerId);
|
||||||
return (
|
return (
|
||||||
<div ref={editorRef} className="Editor font-sans h-full">
|
<div ref={editorRef} className="Editor font-sans h-full">
|
||||||
<UI
|
<UI
|
||||||
SelectedContainer={selected}
|
selectedContainer={selected}
|
||||||
current={current}
|
current={current}
|
||||||
history={history}
|
history={history}
|
||||||
historyCurrentStep={historyCurrentStep}
|
historyCurrentStep={historyCurrentStep}
|
||||||
AvailableContainers={configuration.AvailableContainers}
|
availableContainers={configuration.AvailableContainers}
|
||||||
AvailableSymbols={configuration.AvailableSymbols}
|
availableSymbols={configuration.AvailableSymbols}
|
||||||
SelectContainer={(container) => SelectContainer(
|
selectContainer={(container) => SelectContainer(
|
||||||
container,
|
container,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
DeleteContainer={(containerId: string) => DeleteContainer(
|
deleteContainer={(containerId: string) => DeleteContainer(
|
||||||
containerId,
|
containerId,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
OnPropertyChange={(key, value, type) => OnPropertyChange(
|
onPropertyChange={(key, value, type) => OnPropertyChange(
|
||||||
key, value, type,
|
key, value, type,
|
||||||
selected,
|
selected,
|
||||||
history,
|
history,
|
||||||
|
@ -148,7 +155,7 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
AddContainer={(type) => AddContainerToSelectedContainer(
|
addContainer={(type) => AddContainerToSelectedContainer(
|
||||||
type,
|
type,
|
||||||
selected,
|
selected,
|
||||||
configuration,
|
configuration,
|
||||||
|
@ -157,7 +164,7 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
AddSymbol={(type) => AddSymbol(
|
addSymbol={(type) => AddSymbol(
|
||||||
type,
|
type,
|
||||||
configuration,
|
configuration,
|
||||||
history,
|
history,
|
||||||
|
@ -165,45 +172,42 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
OnSymbolPropertyChange={(key, value) => OnSymbolPropertyChange(
|
onSymbolPropertyChange={(key, value) => OnSymbolPropertyChange(
|
||||||
key, value,
|
key, value,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
SelectSymbol={(symbolId) => SelectSymbol(
|
selectSymbol={(symbolId) => SelectSymbol(
|
||||||
symbolId,
|
symbolId,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
DeleteSymbol={(symbolId) => DeleteSymbol(
|
deleteSymbol={(symbolId) => DeleteSymbol(
|
||||||
symbolId,
|
symbolId,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
SaveEditorAsJSON={() => SaveEditorAsJSON(
|
saveEditorAsJSON={() => SaveEditorAsJSON(
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
configuration
|
configuration
|
||||||
)}
|
)}
|
||||||
SaveEditorAsSVG={() => SaveEditorAsSVG()}
|
saveEditorAsSVG={() => SaveEditorAsSVG()}
|
||||||
LoadState={(move) => setHistoryCurrentStep(move)}
|
loadState={(move) => setHistoryCurrentStep(move)} />
|
||||||
/>
|
|
||||||
<SVG
|
<SVG
|
||||||
width={current.MainContainer?.properties.width}
|
width={current.mainContainer?.properties.width}
|
||||||
height={current.MainContainer?.properties.height}
|
height={current.mainContainer?.properties.height}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
symbols={current.Symbols}
|
symbols={current.symbols}
|
||||||
>
|
>
|
||||||
{ current.MainContainer }
|
{current.mainContainer}
|
||||||
</SVG>
|
</SVG>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Editor;
|
|
||||||
|
|
|
@ -4,13 +4,13 @@ import { fireEvent, render, screen } from '../../utils/test-utils';
|
||||||
import { ElementsSidebar } from './ElementsSidebar';
|
import { ElementsSidebar } from './ElementsSidebar';
|
||||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||||
import { findContainerById } from '../../utils/itertools';
|
import { FindContainerById } from '../../utils/itertools';
|
||||||
|
|
||||||
describe.concurrent('Elements sidebar', () => {
|
describe.concurrent('Elements sidebar', () => {
|
||||||
it('With a MainContainer', () => {
|
it('With a MainContainer', () => {
|
||||||
render(<ElementsSidebar
|
render(<ElementsSidebar
|
||||||
symbols={new Map()}
|
symbols={new Map()}
|
||||||
MainContainer={{
|
mainContainer={{
|
||||||
children: [],
|
children: [],
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -27,17 +27,17 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
type: 'type',
|
type: 'type',
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
XPositionReference: XPositionReference.Left,
|
xPositionReference: XPositionReference.Left,
|
||||||
isAnchor: false
|
isAnchor: false
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
}}
|
}}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={undefined}
|
selectedContainer={undefined}
|
||||||
OnPropertyChange={() => {}}
|
onPropertyChange={() => {}}
|
||||||
SelectContainer={() => {}}
|
selectContainer={() => {}}
|
||||||
DeleteContainer={() => {}}
|
deleteContainer={() => {}}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -46,7 +46,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('With a selected MainContainer', () => {
|
it('With a selected MainContainer', () => {
|
||||||
const MainContainer: IContainerModel = {
|
const mainContainer: IContainerModel = {
|
||||||
children: [],
|
children: [],
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -64,20 +64,20 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
type: 'type',
|
type: 'type',
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
XPositionReference: XPositionReference.Left
|
xPositionReference: XPositionReference.Left
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const { container } = render(<ElementsSidebar
|
const { container } = render(<ElementsSidebar
|
||||||
symbols={new Map()}
|
symbols={new Map()}
|
||||||
MainContainer={MainContainer}
|
mainContainer={mainContainer}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={MainContainer}
|
selectedContainer={mainContainer}
|
||||||
OnPropertyChange={() => {}}
|
onPropertyChange={() => {}}
|
||||||
SelectContainer={() => {}}
|
selectContainer={() => {}}
|
||||||
DeleteContainer={() => {}}
|
deleteContainer={() => {}}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -94,22 +94,22 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
const propertyY = container.querySelector('#y');
|
const propertyY = container.querySelector('#y');
|
||||||
const propertyWidth = container.querySelector('#width');
|
const propertyWidth = container.querySelector('#width');
|
||||||
const propertyHeight = container.querySelector('#height');
|
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).toBeDefined();
|
||||||
expect((propertyParentId as HTMLInputElement).value).toBe('');
|
expect((propertyParentId as HTMLInputElement).value).toBe('');
|
||||||
expect(propertyX).toBeDefined();
|
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).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).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).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', () => {
|
it('With multiple containers', () => {
|
||||||
const children: IContainerModel[] = [];
|
const children: IContainerModel[] = [];
|
||||||
const MainContainer: IContainerModel = {
|
const mainContainer: IContainerModel = {
|
||||||
children,
|
children,
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -122,7 +122,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100,
|
height: 100,
|
||||||
XPositionReference: XPositionReference.Left,
|
xPositionReference: XPositionReference.Left,
|
||||||
margin: {},
|
margin: {},
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
|
@ -135,7 +135,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
children.push(
|
children.push(
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
parent: MainContainer,
|
parent: mainContainer,
|
||||||
properties: {
|
properties: {
|
||||||
id: 'child-1',
|
id: 'child-1',
|
||||||
parentId: 'main',
|
parentId: 'main',
|
||||||
|
@ -151,7 +151,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
type: 'type',
|
type: 'type',
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
XPositionReference: XPositionReference.Left
|
xPositionReference: XPositionReference.Left
|
||||||
},
|
},
|
||||||
userData: {}
|
userData: {}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
children.push(
|
children.push(
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
parent: MainContainer,
|
parent: mainContainer,
|
||||||
properties: {
|
properties: {
|
||||||
id: 'child-2',
|
id: 'child-2',
|
||||||
parentId: 'main',
|
parentId: 'main',
|
||||||
|
@ -172,7 +172,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
XPositionReference: XPositionReference.Left,
|
xPositionReference: XPositionReference.Left,
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
type: 'type',
|
type: 'type',
|
||||||
|
@ -184,13 +184,13 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
|
|
||||||
render(<ElementsSidebar
|
render(<ElementsSidebar
|
||||||
symbols={new Map()}
|
symbols={new Map()}
|
||||||
MainContainer={MainContainer}
|
mainContainer={mainContainer}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={MainContainer}
|
selectedContainer={mainContainer}
|
||||||
OnPropertyChange={() => {}}
|
onPropertyChange={() => {}}
|
||||||
SelectContainer={() => {}}
|
selectContainer={() => {}}
|
||||||
DeleteContainer={() => {}}
|
deleteContainer={() => {}}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -202,7 +202,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
|
|
||||||
it('With multiple containers, change selection', () => {
|
it('With multiple containers, change selection', () => {
|
||||||
const children: IContainerModel[] = [];
|
const children: IContainerModel[] = [];
|
||||||
const MainContainer: IContainerModel = {
|
const mainContainer: IContainerModel = {
|
||||||
children,
|
children,
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -215,7 +215,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
width: 2000,
|
width: 2000,
|
||||||
height: 100,
|
height: 100,
|
||||||
XPositionReference: XPositionReference.Left,
|
xPositionReference: XPositionReference.Left,
|
||||||
margin: {},
|
margin: {},
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
|
@ -227,7 +227,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
|
|
||||||
const child1Model: IContainerModel = {
|
const child1Model: IContainerModel = {
|
||||||
children: [],
|
children: [],
|
||||||
parent: MainContainer,
|
parent: mainContainer,
|
||||||
properties: {
|
properties: {
|
||||||
id: 'child-1',
|
id: 'child-1',
|
||||||
parentId: 'main',
|
parentId: 'main',
|
||||||
|
@ -238,7 +238,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
minWidth: 1,
|
minWidth: 1,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
XPositionReference: XPositionReference.Left,
|
xPositionReference: XPositionReference.Left,
|
||||||
margin: {},
|
margin: {},
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
maxWidth: Infinity,
|
maxWidth: Infinity,
|
||||||
|
@ -249,20 +249,20 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
};
|
};
|
||||||
children.push(child1Model);
|
children.push(child1Model);
|
||||||
|
|
||||||
let SelectedContainer: IContainerModel | undefined = MainContainer;
|
let selectedContainer: IContainerModel | undefined = mainContainer;
|
||||||
const selectContainer = vi.fn((containerId: string) => {
|
const selectContainer = vi.fn((containerId: string) => {
|
||||||
SelectedContainer = findContainerById(MainContainer, containerId);
|
selectedContainer = FindContainerById(mainContainer, containerId);
|
||||||
});
|
});
|
||||||
|
|
||||||
const { container, rerender } = render(<ElementsSidebar
|
const { container, rerender } = render(<ElementsSidebar
|
||||||
symbols={new Map()}
|
symbols={new Map()}
|
||||||
MainContainer={MainContainer}
|
mainContainer={mainContainer}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={SelectedContainer}
|
selectedContainer={selectedContainer}
|
||||||
OnPropertyChange={() => {}}
|
onPropertyChange={() => {}}
|
||||||
SelectContainer={selectContainer}
|
selectContainer={selectContainer}
|
||||||
DeleteContainer={() => {}}
|
deleteContainer={() => {}}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.getByText(/Elements/i));
|
expect(screen.getByText(/Elements/i));
|
||||||
|
@ -272,20 +272,20 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
expect(child1);
|
expect(child1);
|
||||||
const propertyId = container.querySelector('#id');
|
const propertyId = container.querySelector('#id');
|
||||||
const propertyParentId = container.querySelector('#parentId');
|
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('');
|
expect((propertyParentId as HTMLInputElement).value).toBe('');
|
||||||
|
|
||||||
fireEvent.click(child1);
|
fireEvent.click(child1);
|
||||||
|
|
||||||
rerender(<ElementsSidebar
|
rerender(<ElementsSidebar
|
||||||
symbols={new Map()}
|
symbols={new Map()}
|
||||||
MainContainer={MainContainer}
|
mainContainer={mainContainer}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={SelectedContainer}
|
selectedContainer={selectedContainer}
|
||||||
OnPropertyChange={() => {}}
|
onPropertyChange={() => {}}
|
||||||
SelectContainer={selectContainer}
|
selectContainer={selectContainer}
|
||||||
DeleteContainer={() => {}}
|
deleteContainer={() => {}}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy();
|
expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy();
|
||||||
|
|
|
@ -2,37 +2,33 @@ import * as React from 'react';
|
||||||
import { FixedSizeList as List } from 'react-window';
|
import { FixedSizeList as List } from 'react-window';
|
||||||
import { Properties } from '../ContainerProperties/ContainerProperties';
|
import { Properties } from '../ContainerProperties/ContainerProperties';
|
||||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { getDepth, MakeIterator } from '../../utils/itertools';
|
import { GetDepth, MakeIterator } from '../../utils/itertools';
|
||||||
import { Menu } from '../Menu/Menu';
|
import { Menu } from '../Menu/Menu';
|
||||||
import { MenuItem } from '../Menu/MenuItem';
|
import { MenuItem } from '../Menu/MenuItem';
|
||||||
import { useMouseEvents } from './MouseEventHandlers';
|
import { UseMouseEvents } from './MouseEventHandlers';
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
import { IPoint } from '../../Interfaces/IPoint';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { PropertyType } from '../../Enums/PropertyType';
|
import { PropertyType } from '../../Enums/PropertyType';
|
||||||
|
|
||||||
interface IElementsSidebarProps {
|
interface IElementsSidebarProps {
|
||||||
MainContainer: IContainerModel
|
mainContainer: IContainerModel
|
||||||
symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
isHistoryOpen: boolean
|
isHistoryOpen: boolean
|
||||||
SelectedContainer: IContainerModel | undefined
|
selectedContainer: IContainerModel | undefined
|
||||||
OnPropertyChange: (
|
onPropertyChange: (
|
||||||
key: string,
|
key: string,
|
||||||
value: string | number | boolean,
|
value: string | number | boolean,
|
||||||
type?: PropertyType
|
type?: PropertyType
|
||||||
) => void
|
) => void
|
||||||
SelectContainer: (containerId: string) => void
|
selectContainer: (containerId: string) => void
|
||||||
DeleteContainer: (containerid: string) => void
|
deleteContainer: (containerid: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
|
export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
|
||||||
props: IElementsSidebarProps
|
|
||||||
): JSX.Element => {
|
|
||||||
// States
|
// States
|
||||||
const [isContextMenuOpen, setIsContextMenuOpen] =
|
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
|
||||||
React.useState<boolean>(false);
|
const [onClickContainerId, setOnClickContainerId] = React.useState<string>('');
|
||||||
const [onClickContainerId, setOnClickContainerId] =
|
|
||||||
React.useState<string>('');
|
|
||||||
const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
|
const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0
|
y: 0
|
||||||
|
@ -41,7 +37,7 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
|
||||||
const elementRef = React.useRef<HTMLDivElement>(null);
|
const elementRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Event listeners
|
// Event listeners
|
||||||
useMouseEvents(
|
UseMouseEvents(
|
||||||
isContextMenuOpen,
|
isContextMenuOpen,
|
||||||
elementRef,
|
elementRef,
|
||||||
setIsContextMenuOpen,
|
setIsContextMenuOpen,
|
||||||
|
@ -55,30 +51,25 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
|
||||||
isOpenClasses = props.isHistoryOpen ? 'right-64' : 'right-0';
|
isOpenClasses = props.isHistoryOpen ? 'right-64' : 'right-0';
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = MakeIterator(props.MainContainer);
|
const it = MakeIterator(props.mainContainer);
|
||||||
const containers = [...it];
|
const containers = [...it];
|
||||||
const Row = ({
|
function Row({
|
||||||
index,
|
index, style
|
||||||
style
|
|
||||||
}: {
|
}: {
|
||||||
index: number
|
index: number
|
||||||
style: React.CSSProperties
|
style: React.CSSProperties
|
||||||
}): JSX.Element => {
|
}): JSX.Element {
|
||||||
const container = containers[index];
|
const container = containers[index];
|
||||||
const depth: number = getDepth(container);
|
const depth: number = GetDepth(container);
|
||||||
const key = container.properties.id.toString();
|
const key = container.properties.id.toString();
|
||||||
const text =
|
const text = container.properties.displayedText === key
|
||||||
container.properties.displayedText === key
|
? `${'|\t'.repeat(depth)} ${key}`
|
||||||
? `${'|\t'.repeat(depth)} ${key}`
|
: `${'|\t'.repeat(depth)} ${container.properties.displayedText} (${key})`;
|
||||||
: `${'|\t'.repeat(depth)} ${
|
const selectedClass: string = props.selectedContainer !== undefined &&
|
||||||
container.properties.displayedText
|
props.selectedContainer !== null &&
|
||||||
} (${key})`;
|
props.selectedContainer.properties.id === container.properties.id
|
||||||
const selectedClass: string =
|
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
|
||||||
props.SelectedContainer !== undefined &&
|
: 'bg-slate-300/60 hover:bg-slate-300';
|
||||||
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';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type="button"
|
<button type="button"
|
||||||
|
@ -87,12 +78,12 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
|
||||||
id={key}
|
id={key}
|
||||||
key={key}
|
key={key}
|
||||||
style={style}
|
style={style}
|
||||||
onClick={() => props.SelectContainer(container.properties.id)}
|
onClick={() => props.selectContainer(container.properties.id)}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -121,15 +112,13 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
|
||||||
text="Delete"
|
text="Delete"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsContextMenuOpen(false);
|
setIsContextMenuOpen(false);
|
||||||
props.DeleteContainer(onClickContainerId);
|
props.deleteContainer(onClickContainerId);
|
||||||
}}
|
} } />
|
||||||
/>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
<Properties
|
<Properties
|
||||||
properties={props.SelectedContainer?.properties}
|
properties={props.selectedContainer?.properties}
|
||||||
symbols={props.symbols}
|
symbols={props.symbols}
|
||||||
onChange={props.OnPropertyChange}
|
onChange={props.onPropertyChange} />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
|
import React, { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
import { IPoint } from '../../Interfaces/IPoint';
|
||||||
|
|
||||||
export function useMouseEvents(
|
export function UseMouseEvents(
|
||||||
isContextMenuOpen: boolean,
|
isContextMenuOpen: boolean,
|
||||||
elementRef: RefObject<HTMLDivElement>,
|
elementRef: RefObject<HTMLDivElement>,
|
||||||
setIsContextMenuOpen: Dispatch<SetStateAction<boolean>>,
|
setIsContextMenuOpen: Dispatch<SetStateAction<boolean>>,
|
||||||
|
@ -9,44 +9,48 @@ export function useMouseEvents(
|
||||||
setContextMenuPosition: Dispatch<SetStateAction<IPoint>>
|
setContextMenuPosition: Dispatch<SetStateAction<IPoint>>
|
||||||
): void {
|
): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onContextMenu = (event: MouseEvent): void => handleRightClick(
|
function OnContextMenu(event: MouseEvent): void {
|
||||||
event,
|
return HandleRightClick(
|
||||||
setIsContextMenuOpen,
|
event,
|
||||||
setOnClickContainerId,
|
setIsContextMenuOpen,
|
||||||
setContextMenuPosition
|
setOnClickContainerId,
|
||||||
);
|
setContextMenuPosition
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const onLeftClick = (): void => handleLeftClick(
|
function OnLeftClick(): void {
|
||||||
isContextMenuOpen,
|
return HandleLeftClick(
|
||||||
setIsContextMenuOpen,
|
isContextMenuOpen,
|
||||||
setOnClickContainerId
|
setIsContextMenuOpen,
|
||||||
);
|
setOnClickContainerId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
elementRef.current?.addEventListener(
|
elementRef.current?.addEventListener(
|
||||||
'contextmenu',
|
'contextmenu',
|
||||||
onContextMenu
|
OnContextMenu
|
||||||
);
|
);
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
'click',
|
'click',
|
||||||
onLeftClick
|
OnLeftClick
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
elementRef.current?.removeEventListener(
|
elementRef.current?.removeEventListener(
|
||||||
'contextmenu',
|
'contextmenu',
|
||||||
onContextMenu
|
OnContextMenu
|
||||||
);
|
);
|
||||||
|
|
||||||
window.removeEventListener(
|
window.removeEventListener(
|
||||||
'click',
|
'click',
|
||||||
onLeftClick
|
OnLeftClick
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleRightClick(
|
export function HandleRightClick(
|
||||||
event: MouseEvent,
|
event: MouseEvent,
|
||||||
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||||
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>,
|
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>,
|
||||||
|
@ -66,7 +70,7 @@ export function handleRightClick(
|
||||||
setContextMenuPosition(contextMenuPosition);
|
setContextMenuPosition(contextMenuPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleLeftClick(
|
export function HandleLeftClick(
|
||||||
isContextMenuOpen: boolean,
|
isContextMenuOpen: boolean,
|
||||||
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||||
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>
|
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
@ -78,4 +82,3 @@ export function handleLeftClick(
|
||||||
setIsContextMenuOpen(false);
|
setIsContextMenuOpen(false);
|
||||||
setOnClickContainerId('');
|
setOnClickContainerId('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,12 @@ interface IFloatingButtonProps {
|
||||||
className: string
|
className: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleState = (
|
function ToggleState(isHidden: boolean,
|
||||||
isHidden: boolean,
|
setHidden: React.Dispatch<React.SetStateAction<boolean>>): void {
|
||||||
setHidden: React.Dispatch<React.SetStateAction<boolean>>
|
|
||||||
): void => {
|
|
||||||
setHidden(!isHidden);
|
setHidden(!isHidden);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const FloatingButton: React.FC<IFloatingButtonProps> = (props: IFloatingButtonProps) => {
|
export function FloatingButton(props: IFloatingButtonProps): JSX.Element {
|
||||||
const [isHidden, setHidden] = React.useState(true);
|
const [isHidden, setHidden] = React.useState(true);
|
||||||
const buttonListClasses = isHidden ? 'invisible opacity-0' : 'visible opacity-100';
|
const buttonListClasses = isHidden ? 'invisible opacity-0' : 'visible opacity-100';
|
||||||
const icon = isHidden
|
const icon = isHidden
|
||||||
|
@ -23,14 +21,14 @@ export const FloatingButton: React.FC<IFloatingButtonProps> = (props: IFloatingB
|
||||||
return (
|
return (
|
||||||
<div className={`transition-all ${props.className}`}>
|
<div className={`transition-all ${props.className}`}>
|
||||||
<div className={`transition-all flex flex-col gap-2 items-center ${buttonListClasses}`}>
|
<div className={`transition-all flex flex-col gap-2 items-center ${buttonListClasses}`}>
|
||||||
{ props.children }
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
<button type="button"
|
<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'}
|
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'
|
title='Open menu'
|
||||||
onClick={() => toggleState(isHidden, setHidden)}
|
onClick={() => ToggleState(isHidden, setHidden)}
|
||||||
>
|
>
|
||||||
{ icon }
|
{icon}
|
||||||
</button>
|
</button>
|
||||||
</div>);
|
</div>);
|
||||||
};
|
}
|
||||||
|
|
|
@ -9,12 +9,12 @@ interface IHistoryProps {
|
||||||
jumpTo: (move: number) => void
|
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 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 reversedIndex = (props.history.length - 1) - index;
|
||||||
const step = props.history[reversedIndex];
|
const step = props.history[reversedIndex];
|
||||||
const desc = step.LastAction;
|
const desc = step.lastAction;
|
||||||
|
|
||||||
const selectedClass = reversedIndex === props.historyCurrentStep
|
const selectedClass = reversedIndex === props.historyCurrentStep
|
||||||
? 'bg-blue-500 hover:bg-blue-600'
|
? 'bg-blue-500 hover:bg-blue-600'
|
||||||
|
@ -25,21 +25,19 @@ export const History: React.FC<IHistoryProps> = (props: IHistoryProps) => {
|
||||||
key={reversedIndex}
|
key={reversedIndex}
|
||||||
style={style}
|
style={style}
|
||||||
onClick={() => props.jumpTo(reversedIndex)}
|
onClick={() => props.jumpTo(reversedIndex)}
|
||||||
title={step.LastAction}
|
title={step.lastAction}
|
||||||
className={
|
className={`w-full elements-sidebar-row whitespace-pre overflow-hidden
|
||||||
`w-full elements-sidebar-row whitespace-pre overflow-hidden
|
text-left text-sm font-medium transition-all ${selectedClass}`}
|
||||||
text-left text-sm font-medium transition-all ${selectedClass}`
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{desc}
|
{desc}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`fixed flex flex-col bg-slate-300 text-white transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
|
<div className={`fixed flex flex-col bg-slate-300 text-white transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
|
||||||
<div className='bg-slate-600 font-bold sidebar-title'>
|
<div className='bg-slate-600 font-bold sidebar-title'>
|
||||||
Timeline
|
Timeline
|
||||||
</div>
|
</div>
|
||||||
<List
|
<List
|
||||||
className='List overflow-x-hidden'
|
className='List overflow-x-hidden'
|
||||||
|
@ -48,8 +46,8 @@ export const History: React.FC<IHistoryProps> = (props: IHistoryProps) => {
|
||||||
height={window.innerHeight}
|
height={window.innerHeight}
|
||||||
width={256}
|
width={256}
|
||||||
>
|
>
|
||||||
{ Row }
|
{Row}
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -23,14 +23,14 @@ const className = `
|
||||||
focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500
|
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`;
|
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 <>
|
return <>
|
||||||
<label
|
<label
|
||||||
key={props.labelKey}
|
key={props.labelKey}
|
||||||
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
|
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
|
||||||
htmlFor={props.inputKey}
|
htmlFor={props.inputKey}
|
||||||
>
|
>
|
||||||
{ props.labelText }
|
{props.labelText}
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
@ -44,7 +44,6 @@ export const InputGroup: React.FunctionComponent<IInputGroupProps> = (props) =>
|
||||||
defaultChecked={props.defaultChecked}
|
defaultChecked={props.defaultChecked}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
min={props.min}
|
min={props.min}
|
||||||
disabled={props.isDisabled}
|
disabled={props.isDisabled} />
|
||||||
/>
|
|
||||||
</>;
|
</>;
|
||||||
};
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@ interface IMainMenuProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WindowState {
|
enum WindowState {
|
||||||
MAIN,
|
Main,
|
||||||
LOAD,
|
Load,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MainMenu: React.FC<IMainMenuProps> = (props) => {
|
export function MainMenu(props: IMainMenuProps): JSX.Element {
|
||||||
const [windowState, setWindowState] = React.useState(WindowState.MAIN);
|
const [windowState, setWindowState] = React.useState(WindowState.Main);
|
||||||
switch (windowState) {
|
switch (windowState) {
|
||||||
case WindowState.LOAD:
|
case WindowState.Load:
|
||||||
return (
|
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'>
|
<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">
|
<form className="flex items-center space-x-6">
|
||||||
|
@ -37,7 +37,7 @@ export const MainMenu: React.FC<IMainMenuProps> = (props) => {
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
onClick={() => setWindowState(WindowState.MAIN)}
|
onClick={() => setWindowState(WindowState.Main)}
|
||||||
className='normal-btn block
|
className='normal-btn block
|
||||||
mt-8 '
|
mt-8 '
|
||||||
>
|
>
|
||||||
|
@ -50,7 +50,7 @@ export const MainMenu: React.FC<IMainMenuProps> = (props) => {
|
||||||
return (
|
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'>
|
<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={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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface IMenuProps {
|
||||||
children: React.ReactNode[] | React.ReactNode
|
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';
|
const visible = props.isOpen ? 'visible opacity-1' : 'invisible opacity-0';
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -17,7 +17,7 @@ export const Menu: React.FC<IMenuProps> = (props) => {
|
||||||
left: props.x,
|
left: props.x,
|
||||||
top: props.y
|
top: props.y
|
||||||
}}>
|
}}>
|
||||||
{ props.children }
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -6,11 +6,11 @@ interface IMenuItemProps {
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MenuItem: React.FC<IMenuItemProps> = (props) => {
|
export function MenuItem(props: IMenuItemProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<button type="button"
|
<button type="button"
|
||||||
className={props.className}
|
className={props.className}
|
||||||
onClick={() => props.onClick()}>{props.text}
|
onClick={() => props.onClick()}>{props.text}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ interface IRadioGroupButtonsProps {
|
||||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RadioGroupButtons: React.FunctionComponent<IRadioGroupButtonsProps> = (props) => {
|
export function RadioGroupButtons(props: IRadioGroupButtonsProps): JSX.Element {
|
||||||
let inputGroups;
|
let inputGroups;
|
||||||
if (props.value !== undefined) {
|
if (props.value !== undefined) {
|
||||||
// dynamic
|
// dynamic
|
||||||
|
@ -24,8 +24,7 @@ export const RadioGroupButtons: React.FunctionComponent<IRadioGroupButtonsProps>
|
||||||
className={`peer m-2 ${props.inputClassName}`}
|
className={`peer m-2 ${props.inputClassName}`}
|
||||||
value={inputGroup.value}
|
value={inputGroup.value}
|
||||||
checked={props.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'>
|
<label htmlFor={inputGroup.value} className='text-gray-400 peer-checked:text-blue-500'>
|
||||||
{inputGroup.text}
|
{inputGroup.text}
|
||||||
</label>
|
</label>
|
||||||
|
@ -42,8 +41,7 @@ export const RadioGroupButtons: React.FunctionComponent<IRadioGroupButtonsProps>
|
||||||
name={props.name}
|
name={props.name}
|
||||||
className={`peer m-2 ${props.inputClassName}`}
|
className={`peer m-2 ${props.inputClassName}`}
|
||||||
value={inputGroup.value}
|
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'>
|
<label htmlFor={inputGroup.value} className='text-gray-400 peer-checked:text-blue-500'>
|
||||||
{inputGroup.text}
|
{inputGroup.text}
|
||||||
</label>
|
</label>
|
||||||
|
@ -59,8 +57,8 @@ export const RadioGroupButtons: React.FunctionComponent<IRadioGroupButtonsProps>
|
||||||
<div id='XPositionReference'
|
<div id='XPositionReference'
|
||||||
className='flex flex-col'
|
className='flex flex-col'
|
||||||
>
|
>
|
||||||
{ inputGroups }
|
{inputGroups}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@ import * as React from 'react';
|
||||||
import { Interweave, Node } from 'interweave';
|
import { Interweave, Node } from 'interweave';
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { DIMENSION_MARGIN, SHOW_CHILDREN_DIMENSIONS, SHOW_PARENT_DIMENSION, SHOW_TEXT } from '../../../utils/default';
|
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 { Dimension } from './Dimension';
|
||||||
import IContainerProperties from '../../../Interfaces/IContainerProperties';
|
import { IContainerProperties } from '../../../Interfaces/IContainerProperties';
|
||||||
import { transformX } from '../../../utils/svg';
|
import { TransformX } from '../../../utils/svg';
|
||||||
import { camelize } from '../../../utils/stringtools';
|
import { Camelize } from '../../../utils/stringtools';
|
||||||
|
|
||||||
interface IContainerProps {
|
interface IContainerProps {
|
||||||
model: IContainerModel
|
model: IContainerModel
|
||||||
|
@ -16,7 +16,7 @@ interface IContainerProps {
|
||||||
* Render the container
|
* Render the container
|
||||||
* @returns 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 containersElements = props.model.children.map(child => <Container key={`container-${child.properties.id}`} model={child} />);
|
||||||
|
|
||||||
const width: number = props.model.properties.width;
|
const width: number = props.model.properties.width;
|
||||||
|
@ -52,7 +52,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
>
|
>
|
||||||
</rect>);
|
</rect>);
|
||||||
// Dimension props
|
// Dimension props
|
||||||
const depth = getDepth(props.model);
|
const depth = GetDepth(props.model);
|
||||||
const dimensionMargin = DIMENSION_MARGIN * depth;
|
const dimensionMargin = DIMENSION_MARGIN * depth;
|
||||||
const id = `dim-${props.model.properties.id}`;
|
const id = `dim-${props.model.properties.id}`;
|
||||||
const xStart: number = 0;
|
const xStart: number = 0;
|
||||||
|
@ -64,11 +64,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
let dimensionChildren: JSX.Element | null = null;
|
let dimensionChildren: JSX.Element | null = null;
|
||||||
if (props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
|
if (props.model.children.length > 1 && SHOW_CHILDREN_DIMENSIONS) {
|
||||||
const {
|
const {
|
||||||
childrenId,
|
childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren
|
||||||
xChildrenStart,
|
|
||||||
xChildrenEnd,
|
|
||||||
yChildren,
|
|
||||||
textChildren
|
|
||||||
} = GetChildrenDimensionProps(props, dimensionMargin);
|
} = GetChildrenDimensionProps(props, dimensionMargin);
|
||||||
dimensionChildren = <Dimension
|
dimensionChildren = <Dimension
|
||||||
id={childrenId}
|
id={childrenId}
|
||||||
|
@ -77,8 +73,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
yStart={yChildren}
|
yStart={yChildren}
|
||||||
yEnd={yChildren}
|
yEnd={yChildren}
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
text={textChildren}
|
text={textChildren} />;
|
||||||
/>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -95,10 +90,8 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
yStart={yDim}
|
yStart={yDim}
|
||||||
yEnd={yDim}
|
yEnd={yDim}
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
text={text}
|
text={text} />
|
||||||
/>
|
: null}
|
||||||
: null
|
|
||||||
}
|
|
||||||
{dimensionChildren}
|
{dimensionChildren}
|
||||||
{svg}
|
{svg}
|
||||||
{SHOW_TEXT
|
{SHOW_TEXT
|
||||||
|
@ -112,23 +105,23 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
{containersElements}
|
{containersElements}
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: number): { childrenId: string, xChildrenStart: number, xChildrenEnd: number, yChildren: number, textChildren: string } {
|
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 childrenId = `dim-children-${props.model.properties.id}`;
|
||||||
|
|
||||||
const lastChild = props.model.children[props.model.children.length - 1];
|
const lastChild = props.model.children[props.model.children.length - 1];
|
||||||
let xChildrenStart = 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);
|
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.xPositionReference);
|
||||||
|
|
||||||
// Find the min and max
|
// Find the min and max
|
||||||
for (let i = props.model.children.length - 2; i >= 0; i--) {
|
for (let i = props.model.children.length - 2; i >= 0; i--) {
|
||||||
const child = props.model.children[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) {
|
if (left < xChildrenStart) {
|
||||||
xChildrenStart = left;
|
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) {
|
if (right > xChildrenEnd) {
|
||||||
xChildrenEnd = right;
|
xChildrenEnd = right;
|
||||||
}
|
}
|
||||||
|
@ -145,11 +138,11 @@ function CreateReactCustomSVG(customSVG: string, props: IContainerProperties): R
|
||||||
disableLineBreaks={true}
|
disableLineBreaks={true}
|
||||||
content={customSVG}
|
content={customSVG}
|
||||||
allowElements={true}
|
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'];
|
const supportedTags = ['line', 'path', 'rect'];
|
||||||
if (supportedTags.includes(node.tagName.toLowerCase())) {
|
if (supportedTags.includes(node.tagName.toLowerCase())) {
|
||||||
const attributes: { [att: string]: string | object | null } = {};
|
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);
|
const prop = Object.entries(props.userData).find(([key]) => `{${key}}` === userDataKey);
|
||||||
if (prop !== undefined) {
|
if (prop !== undefined) {
|
||||||
attributes[camelize(attName)] = prop[1];
|
attributes[Camelize(attName)] = prop[1];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,16 +172,16 @@ function transform(node: HTMLElement, children: Node[], props: IContainerPropert
|
||||||
// support for object
|
// support for object
|
||||||
const stringObject = attributeValue.slice(1, -1);
|
const stringObject = attributeValue.slice(1, -1);
|
||||||
const object: JSON = JSON.parse(stringObject);
|
const object: JSON = JSON.parse(stringObject);
|
||||||
attributes[camelize(attName)] = object;
|
attributes[Camelize(attName)] = object;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prop = Object.entries(props).find(([key]) => `{${key}}` === attributeValue);
|
const prop = Object.entries(props).find(([key]) => `{${key}}` === attributeValue);
|
||||||
if (prop !== undefined) {
|
if (prop !== undefined) {
|
||||||
attributes[camelize(attName)] = prop[1];
|
attributes[Camelize(attName)] = prop[1];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attributes[camelize(attName)] = attributeValue;
|
attributes[Camelize(attName)] = attributeValue;
|
||||||
});
|
});
|
||||||
return React.createElement(node.tagName.toLowerCase(), attributes, children);
|
return React.createElement(node.tagName.toLowerCase(), attributes, children);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ContainerModel } from '../../../Interfaces/IContainerModel';
|
import { ContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { DIMENSION_MARGIN } from '../../../utils/default';
|
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||||
import { getAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
||||||
import { transformX } from '../../../utils/svg';
|
import { TransformX } from '../../../utils/svg';
|
||||||
import { Dimension } from './Dimension';
|
import { Dimension } from './Dimension';
|
||||||
|
|
||||||
interface IDimensionLayerProps {
|
interface IDimensionLayerProps {
|
||||||
roots: ContainerModel | ContainerModel[] | null
|
roots: ContainerModel | ContainerModel[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
|
function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
|
||||||
const it = MakeBFSIterator(root);
|
const it = MakeBFSIterator(root);
|
||||||
const dimensions: React.ReactNode[] = [];
|
const dimensions: React.ReactNode[] = [];
|
||||||
let currentDepth = 0;
|
let currentDepth = 0;
|
||||||
|
@ -25,8 +25,8 @@ const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
|
||||||
max = -Infinity;
|
max = -Infinity;
|
||||||
}
|
}
|
||||||
|
|
||||||
const absoluteX = getAbsolutePosition(container)[0];
|
const absoluteX = GetAbsolutePosition(container)[0];
|
||||||
const x = transformX(absoluteX, container.properties.width, container.properties.XPositionReference);
|
const x = TransformX(absoluteX, container.properties.width, container.properties.xPositionReference);
|
||||||
lastY = container.properties.y + container.properties.height;
|
lastY = container.properties.y + container.properties.height;
|
||||||
if (x < min) {
|
if (x < min) {
|
||||||
min = x;
|
min = x;
|
||||||
|
@ -40,28 +40,28 @@ const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
|
||||||
AddNewDimension(currentDepth, min, max, lastY, dimensions);
|
AddNewDimension(currentDepth, min, max, lastY, dimensions);
|
||||||
|
|
||||||
return dimensions;
|
return dimensions;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A layer containing all dimension
|
* A layer containing all dimension
|
||||||
* @param props
|
* @param props
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const DepthDimensionLayer: React.FC<IDimensionLayerProps> = (props: IDimensionLayerProps) => {
|
export function DepthDimensionLayer(props: IDimensionLayerProps): JSX.Element {
|
||||||
let dimensions: React.ReactNode[] = [];
|
let dimensions: React.ReactNode[] = [];
|
||||||
if (Array.isArray(props.roots)) {
|
if (Array.isArray(props.roots)) {
|
||||||
props.roots.forEach(child => {
|
props.roots.forEach(child => {
|
||||||
dimensions.concat(getDimensionsNodes(child));
|
dimensions.concat(GetDimensionsNodes(child));
|
||||||
});
|
});
|
||||||
} else if (props.roots !== null) {
|
} else if (props.roots !== null) {
|
||||||
dimensions = getDimensionsNodes(props.roots);
|
dimensions = GetDimensionsNodes(props.roots);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<g>
|
<g>
|
||||||
{ dimensions }
|
{dimensions}
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
function AddNewDimension(currentDepth: number, min: number, max: number, lastY: number, dimensions: React.ReactNode[]): void {
|
function AddNewDimension(currentDepth: number, min: number, max: number, lastY: number, dimensions: React.ReactNode[]): void {
|
||||||
const id = `dim-depth-${currentDepth}`;
|
const id = `dim-depth-${currentDepth}`;
|
||||||
|
|
|
@ -20,9 +20,11 @@ interface IDimensionProps {
|
||||||
* @param vx Transform vector
|
* @param vx Transform vector
|
||||||
* @returns Returns a new coordinate from the origin coordinate
|
* @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 = {
|
const style: React.CSSProperties = {
|
||||||
stroke: 'black'
|
stroke: 'black'
|
||||||
};
|
};
|
||||||
|
@ -39,15 +41,15 @@ export const Dimension: React.FC<IDimensionProps> = (props: IDimensionProps) =>
|
||||||
const [perpVecX, perpVecY] = [unitY, -unitX];
|
const [perpVecX, perpVecY] = [unitY, -unitX];
|
||||||
|
|
||||||
// Use the parametric function to get the coordinates (x = x0 + t * v.x)
|
// Use the parametric function to get the coordinates (x = x0 + t * v.x)
|
||||||
const startTopX = applyParametric(props.xStart, NOTCHES_LENGTH, perpVecX);
|
const startTopX = ApplyParametric(props.xStart, NOTCHES_LENGTH, perpVecX);
|
||||||
const startTopY = applyParametric(props.yStart, NOTCHES_LENGTH, perpVecY);
|
const startTopY = ApplyParametric(props.yStart, NOTCHES_LENGTH, perpVecY);
|
||||||
const startBottomX = applyParametric(props.xStart, -NOTCHES_LENGTH, perpVecX);
|
const startBottomX = ApplyParametric(props.xStart, -NOTCHES_LENGTH, perpVecX);
|
||||||
const startBottomY = applyParametric(props.yStart, -NOTCHES_LENGTH, perpVecY);
|
const startBottomY = ApplyParametric(props.yStart, -NOTCHES_LENGTH, perpVecY);
|
||||||
|
|
||||||
const endTopX = applyParametric(props.xEnd, NOTCHES_LENGTH, perpVecX);
|
const endTopX = ApplyParametric(props.xEnd, NOTCHES_LENGTH, perpVecX);
|
||||||
const endTopY = applyParametric(props.yEnd, NOTCHES_LENGTH, perpVecY);
|
const endTopY = ApplyParametric(props.yEnd, NOTCHES_LENGTH, perpVecY);
|
||||||
const endBottomX = applyParametric(props.xEnd, -NOTCHES_LENGTH, perpVecX);
|
const endBottomX = ApplyParametric(props.xEnd, -NOTCHES_LENGTH, perpVecX);
|
||||||
const endBottomY = applyParametric(props.yEnd, -NOTCHES_LENGTH, perpVecY);
|
const endBottomY = ApplyParametric(props.yEnd, -NOTCHES_LENGTH, perpVecY);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g key={props.id}>
|
<g key={props.id}>
|
||||||
|
@ -57,24 +59,21 @@ export const Dimension: React.FC<IDimensionProps> = (props: IDimensionProps) =>
|
||||||
x2={startBottomX}
|
x2={startBottomX}
|
||||||
y2={startBottomY}
|
y2={startBottomY}
|
||||||
strokeWidth={props.strokeWidth}
|
strokeWidth={props.strokeWidth}
|
||||||
style={style}
|
style={style} />
|
||||||
/>
|
|
||||||
<line
|
<line
|
||||||
x1={props.xStart}
|
x1={props.xStart}
|
||||||
y1={props.yStart}
|
y1={props.yStart}
|
||||||
x2={props.xEnd}
|
x2={props.xEnd}
|
||||||
y2={props.yEnd}
|
y2={props.yEnd}
|
||||||
strokeWidth={props.strokeWidth}
|
strokeWidth={props.strokeWidth}
|
||||||
style={style}
|
style={style} />
|
||||||
/>
|
|
||||||
<line
|
<line
|
||||||
x1={endTopX}
|
x1={endTopX}
|
||||||
y1={endTopY}
|
y1={endTopY}
|
||||||
x2={endBottomX}
|
x2={endBottomX}
|
||||||
y2={endBottomY}
|
y2={endBottomY}
|
||||||
strokeWidth={props.strokeWidth}
|
strokeWidth={props.strokeWidth}
|
||||||
style={style}
|
style={style} />
|
||||||
/>
|
|
||||||
<text
|
<text
|
||||||
x={(props.xStart + props.xEnd) / 2}
|
x={(props.xStart + props.xEnd) / 2}
|
||||||
y={props.yStart}
|
y={props.yStart}
|
||||||
|
@ -83,4 +82,4 @@ export const Dimension: React.FC<IDimensionProps> = (props: IDimensionProps) =>
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ContainerModel } from '../../../Interfaces/IContainerModel';
|
import { ContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { DIMENSION_MARGIN } from '../../../utils/default';
|
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||||
import { getAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
||||||
import { Dimension } from './Dimension';
|
import { Dimension } from './Dimension';
|
||||||
|
|
||||||
interface IDimensionLayerProps {
|
interface IDimensionLayerProps {
|
||||||
roots: ContainerModel | ContainerModel[] | null
|
roots: ContainerModel | ContainerModel[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
|
function GetDimensionsNodes(root: ContainerModel): React.ReactNode[] {
|
||||||
const it = MakeBFSIterator(root);
|
const it = MakeBFSIterator(root);
|
||||||
const dimensions: React.ReactNode[] = [];
|
const dimensions: React.ReactNode[] = [];
|
||||||
for (const { container, depth } of it) {
|
for (const { container, depth } of it) {
|
||||||
const width = container.properties.width;
|
const width = container.properties.width;
|
||||||
const id = `dim-${container.properties.id}`;
|
const id = `dim-${container.properties.id}`;
|
||||||
const xStart = getAbsolutePosition(container)[0];
|
const xStart = GetAbsolutePosition(container)[0];
|
||||||
const xEnd = xStart + width;
|
const xEnd = xStart + width;
|
||||||
const y = (container.properties.y + container.properties.height) + (DIMENSION_MARGIN * (depth + 1));
|
const y = (container.properties.y + container.properties.height) + (DIMENSION_MARGIN * (depth + 1));
|
||||||
const strokeWidth = 1;
|
const strokeWidth = 1;
|
||||||
|
@ -28,30 +28,29 @@ const getDimensionsNodes = (root: ContainerModel): React.ReactNode[] => {
|
||||||
xEnd={xEnd}
|
xEnd={xEnd}
|
||||||
yEnd={y}
|
yEnd={y}
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
text={text}
|
text={text} />
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return dimensions;
|
return dimensions;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A layer containing all dimension
|
* A layer containing all dimension
|
||||||
* @param props
|
* @param props
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const DimensionLayer: React.FC<IDimensionLayerProps> = (props: IDimensionLayerProps) => {
|
export function DimensionLayer(props: IDimensionLayerProps): JSX.Element {
|
||||||
let dimensions: React.ReactNode[] = [];
|
let dimensions: React.ReactNode[] = [];
|
||||||
if (Array.isArray(props.roots)) {
|
if (Array.isArray(props.roots)) {
|
||||||
props.roots.forEach(child => {
|
props.roots.forEach(child => {
|
||||||
dimensions.concat(getDimensionsNodes(child));
|
dimensions.concat(GetDimensionsNodes(child));
|
||||||
});
|
});
|
||||||
} else if (props.roots !== null) {
|
} else if (props.roots !== null) {
|
||||||
dimensions = getDimensionsNodes(props.roots);
|
dimensions = GetDimensionsNodes(props.roots);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<g>
|
<g>
|
||||||
{ dimensions }
|
{dimensions}
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ import './Selector.scss';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { IContainerModel } from '../../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../../Interfaces/IContainerModel';
|
||||||
import { SHOW_SELECTOR_TEXT } from '../../../../utils/default';
|
import { SHOW_SELECTOR_TEXT } from '../../../../utils/default';
|
||||||
import { getAbsolutePosition } from '../../../../utils/itertools';
|
import { GetAbsolutePosition } from '../../../../utils/itertools';
|
||||||
import { RemoveMargin } from '../../../../utils/svg';
|
import { RemoveMargin } from '../../../../utils/svg';
|
||||||
|
|
||||||
interface ISelectorProps {
|
interface ISelectorProps {
|
||||||
selected?: IContainerModel
|
selected?: IContainerModel
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Selector: React.FC<ISelectorProps> = (props) => {
|
export function Selector(props: ISelectorProps): JSX.Element {
|
||||||
if (props.selected === undefined || props.selected === null) {
|
if (props.selected === undefined || props.selected === null) {
|
||||||
return (
|
return (
|
||||||
<rect visibility={'hidden'}>
|
<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] = [
|
let [width, height] = [
|
||||||
props.selected.properties.width,
|
props.selected.properties.width,
|
||||||
props.selected.properties.height
|
props.selected.properties.height
|
||||||
|
@ -34,7 +34,7 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
|
||||||
const yText = y + height / 2;
|
const yText = y + height / 2;
|
||||||
|
|
||||||
const style: React.CSSProperties = {
|
const style: React.CSSProperties = {
|
||||||
stroke: '#3B82F6', // tw blue-500
|
stroke: '#3B82F6',
|
||||||
strokeWidth: 4,
|
strokeWidth: 4,
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
transitionProperty: 'all',
|
transitionProperty: 'all',
|
||||||
|
@ -63,4 +63,4 @@ export const Selector: React.FC<ISelectorProps> = (props) => {
|
||||||
: null}
|
: null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ interface ISymbolProps {
|
||||||
model: ISymbolModel
|
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 href = props.model.config.Image.Base64Image ?? props.model.config.Image.Url;
|
||||||
const hasSVG = props.model.config.Image.Svg !== undefined &&
|
const hasSVG = props.model.config.Image.Svg !== undefined &&
|
||||||
props.model.config.Image.Svg !== null;
|
props.model.config.Image.Svg !== null;
|
||||||
|
@ -21,8 +21,7 @@ export const Symbol: React.FC<ISymbolProps> = (props) => {
|
||||||
noWrap={true}
|
noWrap={true}
|
||||||
disableLineBreaks={true}
|
disableLineBreaks={true}
|
||||||
content={props.model.config.Image.Svg}
|
content={props.model.config.Image.Svg}
|
||||||
allowElements={true}
|
allowElements={true} />
|
||||||
/>
|
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +32,6 @@ export const Symbol: React.FC<ISymbolProps> = (props) => {
|
||||||
x={props.model.x}
|
x={props.model.x}
|
||||||
y={-DIMENSION_MARGIN}
|
y={-DIMENSION_MARGIN}
|
||||||
height={props.model.height}
|
height={props.model.height}
|
||||||
width={props.model.width}
|
width={props.model.width} />
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ interface ISymbolLayerProps {
|
||||||
symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SymbolLayer: React.FC<ISymbolLayerProps> = (props) => {
|
export function SymbolLayer(props: ISymbolLayerProps): JSX.Element {
|
||||||
const symbols: JSX.Element[] = [];
|
const symbols: JSX.Element[] = [];
|
||||||
props.symbols.forEach((symbol) => {
|
props.symbols.forEach((symbol) => {
|
||||||
symbols.push(
|
symbols.push(
|
||||||
|
@ -15,9 +15,7 @@ export const SymbolLayer: React.FC<ISymbolLayerProps> = (props) => {
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<g>
|
<g>
|
||||||
{
|
{symbols}
|
||||||
symbols
|
|
||||||
}
|
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ interface Viewer {
|
||||||
|
|
||||||
export const ID = 'svg';
|
export const ID = 'svg';
|
||||||
|
|
||||||
function resizeViewBox(
|
function ResizeViewBox(
|
||||||
setViewer: React.Dispatch<React.SetStateAction<Viewer>>
|
setViewer: React.Dispatch<React.SetStateAction<Viewer>>
|
||||||
): void {
|
): void {
|
||||||
setViewer({
|
setViewer({
|
||||||
|
@ -34,26 +34,28 @@ function resizeViewBox(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function useSVGAutoResizer(
|
function UseSVGAutoResizer(
|
||||||
setViewer: React.Dispatch<React.SetStateAction<Viewer>>
|
setViewer: React.Dispatch<React.SetStateAction<Viewer>>
|
||||||
): void {
|
): void {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const onResize = (): void => resizeViewBox(setViewer);
|
function OnResize(): void {
|
||||||
window.addEventListener('resize', onResize);
|
return ResizeViewBox(setViewer);
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', OnResize);
|
||||||
|
|
||||||
return () => {
|
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>({
|
const [viewer, setViewer] = React.useState<Viewer>({
|
||||||
viewerWidth: window.innerWidth - BAR_WIDTH,
|
viewerWidth: window.innerWidth - BAR_WIDTH,
|
||||||
viewerHeight: window.innerHeight
|
viewerHeight: window.innerHeight
|
||||||
});
|
});
|
||||||
|
|
||||||
useSVGAutoResizer(setViewer);
|
UseSVGAutoResizer(setViewer);
|
||||||
|
|
||||||
const xmlns = '<http://www.w3.org/2000/svg>';
|
const xmlns = '<http://www.w3.org/2000/svg>';
|
||||||
const properties = {
|
const properties = {
|
||||||
|
@ -64,9 +66,9 @@ export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
|
||||||
|
|
||||||
let children: React.ReactNode | React.ReactNode[] = [];
|
let children: React.ReactNode | React.ReactNode[] = [];
|
||||||
if (Array.isArray(props.children)) {
|
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) {
|
} 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 (
|
return (
|
||||||
|
@ -84,16 +86,14 @@ export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg {...properties}>
|
<svg {...properties}>
|
||||||
{ children }
|
{children}
|
||||||
{
|
{SHOW_DIMENSIONS_PER_DEPTH
|
||||||
SHOW_DIMENSIONS_PER_DEPTH
|
? <DepthDimensionLayer roots={props.children} />
|
||||||
? <DepthDimensionLayer roots={props.children}/>
|
: null}
|
||||||
: null
|
|
||||||
}
|
|
||||||
<SymbolLayer symbols={props.symbols} />
|
<SymbolLayer symbols={props.symbols} />
|
||||||
<Selector selected={props.selected} /> {/* leave this at the end so it can be removed during the svg export */}
|
<Selector selected={props.selected} /> {/* leave this at the end so it can be removed during the svg export */}
|
||||||
</svg>
|
</svg>
|
||||||
</UncontrolledReactSVGPanZoom>
|
</UncontrolledReactSVGPanZoom>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ const className = `
|
||||||
focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500
|
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`;
|
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 = [(
|
const options = [(
|
||||||
<option key='symbol-none' value=''>None</option>
|
<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}`}
|
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
|
||||||
htmlFor={props.inputKey}
|
htmlFor={props.inputKey}
|
||||||
>
|
>
|
||||||
{ props.labelText }
|
{props.labelText}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id={props.inputKey}
|
id={props.inputKey}
|
||||||
|
@ -48,8 +48,8 @@ export const Select: React.FC<ISelectProps> = (props) => {
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
{ options }
|
{options}
|
||||||
</select>
|
</select>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -31,15 +31,17 @@ describe.concurrent('Sidebar', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('With stuff', () => {
|
it('With stuff', () => {
|
||||||
const Type = 'stuff';
|
const type = 'stuff';
|
||||||
const handleButtonClick = vi.fn();
|
const handleButtonClick = vi.fn();
|
||||||
render(<Sidebar
|
render(<Sidebar
|
||||||
componentOptions={[
|
componentOptions={[
|
||||||
{
|
{
|
||||||
Type,
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
Type: type,
|
||||||
Width: 30,
|
Width: 30,
|
||||||
Height: 30,
|
Height: 30,
|
||||||
Style: {}
|
Style: {}
|
||||||
|
/* eslint-enable */
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
||||||
import { truncateString } from '../../utils/stringtools';
|
import { TruncateString } from '../../utils/stringtools';
|
||||||
|
|
||||||
interface ISidebarProps {
|
interface ISidebarProps {
|
||||||
componentOptions: IAvailableContainer[]
|
componentOptions: IAvailableContainer[]
|
||||||
|
@ -8,11 +8,11 @@ interface ISidebarProps {
|
||||||
buttonOnClick: (type: string) => void
|
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);
|
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 =>
|
const listElements = props.componentOptions.map(componentOption =>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
className='justify-center transition-all sidebar-component'
|
className='justify-center transition-all sidebar-component'
|
||||||
|
@ -21,9 +21,9 @@ export const Sidebar: React.FC<ISidebarProps> = (props: ISidebarProps) => {
|
||||||
title={componentOption.Type}
|
title={componentOption.Type}
|
||||||
onClick={() => props.buttonOnClick(componentOption.Type)}
|
onClick={() => props.buttonOnClick(componentOption.Type)}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
onDragStart={(event) => handleDragStart(event)}
|
onDragStart={(event) => HandleDragStart(event)}
|
||||||
>
|
>
|
||||||
{truncateString(componentOption.Type, 5)}
|
{TruncateString(componentOption.Type, 5)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { restoreX, transformX } from '../../utils/svg';
|
import { RestoreX, TransformX } from '../../utils/svg';
|
||||||
import { InputGroup } from '../InputGroup/InputGroup';
|
import { InputGroup } from '../InputGroup/InputGroup';
|
||||||
|
|
||||||
interface ISymbolFormProps {
|
interface ISymbolFormProps {
|
||||||
|
@ -8,7 +8,8 @@ interface ISymbolFormProps {
|
||||||
symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
onChange: (key: string, value: string | number | boolean) => void
|
onChange: (key: string, value: string | number | boolean) => void
|
||||||
}
|
}
|
||||||
const SymbolForm: React.FunctionComponent<ISymbolFormProps> = (props) => {
|
|
||||||
|
export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className='grid grid-cols-2 gap-y-4'>
|
<div className='grid grid-cols-2 gap-y-4'>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
|
@ -18,17 +19,15 @@ const SymbolForm: React.FunctionComponent<ISymbolFormProps> = (props) => {
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='string'
|
type='string'
|
||||||
value={props.symbol.id.toString()}
|
value={props.symbol.id.toString()}
|
||||||
isDisabled={true}
|
isDisabled={true} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='x'
|
labelText='x'
|
||||||
inputKey='x'
|
inputKey='x'
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
value={transformX(props.symbol.x, props.symbol.width, props.symbol.config.XPositionReference).toString()}
|
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))}
|
onChange={(event) => props.onChange('x', RestoreX(Number(event.target.value), props.symbol.width, props.symbol.config.XPositionReference))} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Height'
|
labelText='Height'
|
||||||
inputKey='height'
|
inputKey='height'
|
||||||
|
@ -37,8 +36,7 @@ const SymbolForm: React.FunctionComponent<ISymbolFormProps> = (props) => {
|
||||||
type='number'
|
type='number'
|
||||||
min={0}
|
min={0}
|
||||||
value={props.symbol.height.toString()}
|
value={props.symbol.height.toString()}
|
||||||
onChange={(event) => props.onChange('height', Number(event.target.value))}
|
onChange={(event) => props.onChange('height', Number(event.target.value))} />
|
||||||
/>
|
|
||||||
<InputGroup
|
<InputGroup
|
||||||
labelText='Width'
|
labelText='Width'
|
||||||
inputKey='width'
|
inputKey='width'
|
||||||
|
@ -47,10 +45,7 @@ const SymbolForm: React.FunctionComponent<ISymbolFormProps> = (props) => {
|
||||||
type='number'
|
type='number'
|
||||||
min={0}
|
min={0}
|
||||||
value={props.symbol.width.toString()}
|
value={props.symbol.width.toString()}
|
||||||
onChange={(event) => props.onChange('width', Number(event.target.value))}
|
onChange={(event) => props.onChange('width', Number(event.target.value))} />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default SymbolForm;
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import SymbolForm from './SymbolForm';
|
import { SymbolForm } from './SymbolForm';
|
||||||
|
|
||||||
interface ISymbolPropertiesProps {
|
interface ISymbolPropertiesProps {
|
||||||
symbol?: ISymbolModel
|
symbol?: ISymbolModel
|
||||||
|
@ -8,7 +8,7 @@ interface ISymbolPropertiesProps {
|
||||||
onChange: (key: string, value: string | number | boolean) => void
|
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) {
|
if (props.symbol === undefined) {
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,7 @@ export const SymbolProperties: React.FC<ISymbolPropertiesProps> = (props: ISymbo
|
||||||
<SymbolForm
|
<SymbolForm
|
||||||
symbol={props.symbol}
|
symbol={props.symbol}
|
||||||
symbols={props.symbols}
|
symbols={props.symbols}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange} />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
|
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
|
||||||
import { truncateString } from '../../utils/stringtools';
|
import { TruncateString } from '../../utils/stringtools';
|
||||||
|
|
||||||
interface ISymbolsProps {
|
interface ISymbolsProps {
|
||||||
componentOptions: IAvailableSymbol[]
|
componentOptions: IAvailableSymbol[]
|
||||||
|
@ -8,11 +8,11 @@ interface ISymbolsProps {
|
||||||
buttonOnClick: (type: string) => void
|
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);
|
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 => {
|
const listElements = props.componentOptions.map(componentOption => {
|
||||||
if (componentOption.Image.Url !== undefined || componentOption.Image.Base64Image !== undefined) {
|
if (componentOption.Image.Url !== undefined || componentOption.Image.Base64Image !== undefined) {
|
||||||
const url = componentOption.Image.Base64Image ?? componentOption.Image.Url;
|
const url = componentOption.Image.Base64Image ?? componentOption.Image.Url;
|
||||||
|
@ -23,7 +23,7 @@ export const Symbols: React.FC<ISymbolsProps> = (props: ISymbolsProps) => {
|
||||||
title={componentOption.Name}
|
title={componentOption.Name}
|
||||||
onClick={() => props.buttonOnClick(componentOption.Name)}
|
onClick={() => props.buttonOnClick(componentOption.Name)}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
onDragStart={(event) => handleDragStart(event)}
|
onDragStart={(event) => HandleDragStart(event)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
|
@ -32,7 +32,7 @@ export const Symbols: React.FC<ISymbolsProps> = (props: ISymbolsProps) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{truncateString(componentOption.Name, 5)}
|
{TruncateString(componentOption.Name, 5)}
|
||||||
</div>
|
</div>
|
||||||
</button>);
|
</button>);
|
||||||
}
|
}
|
||||||
|
@ -44,10 +44,10 @@ export const Symbols: React.FC<ISymbolsProps> = (props: ISymbolsProps) => {
|
||||||
title={componentOption.Name}
|
title={componentOption.Name}
|
||||||
onClick={() => props.buttonOnClick(componentOption.Name)}
|
onClick={() => props.buttonOnClick(componentOption.Name)}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
onDragStart={(event) => handleDragStart(event)}
|
onDragStart={(event) => HandleDragStart(event)}
|
||||||
>
|
>
|
||||||
|
|
||||||
{truncateString(componentOption.Name, 5)}
|
{TruncateString(componentOption.Name, 5)}
|
||||||
</button>);
|
</button>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
|
import { RefObject, Dispatch, SetStateAction, useEffect } from 'react';
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
import { IPoint } from '../../Interfaces/IPoint';
|
||||||
|
|
||||||
export function useMouseEvents(
|
export function UseMouseEvents(
|
||||||
isContextMenuOpen: boolean,
|
isContextMenuOpen: boolean,
|
||||||
elementRef: RefObject<HTMLDivElement>,
|
elementRef: RefObject<HTMLDivElement>,
|
||||||
setIsContextMenuOpen: Dispatch<SetStateAction<boolean>>,
|
setIsContextMenuOpen: Dispatch<SetStateAction<boolean>>,
|
||||||
|
@ -9,44 +9,48 @@ export function useMouseEvents(
|
||||||
setContextMenuPosition: Dispatch<SetStateAction<IPoint>>
|
setContextMenuPosition: Dispatch<SetStateAction<IPoint>>
|
||||||
): void {
|
): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onContextMenu = (event: MouseEvent): void => handleRightClick(
|
function OnContextMenu(event: MouseEvent): void {
|
||||||
event,
|
return HandleRightClick(
|
||||||
setIsContextMenuOpen,
|
event,
|
||||||
setOnClickSymbolId,
|
setIsContextMenuOpen,
|
||||||
setContextMenuPosition
|
setOnClickSymbolId,
|
||||||
);
|
setContextMenuPosition
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const onLeftClick = (): void => handleLeftClick(
|
function OnLeftClick(): void {
|
||||||
isContextMenuOpen,
|
return HandleLeftClick(
|
||||||
setIsContextMenuOpen,
|
isContextMenuOpen,
|
||||||
setOnClickSymbolId
|
setIsContextMenuOpen,
|
||||||
);
|
setOnClickSymbolId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
elementRef.current?.addEventListener(
|
elementRef.current?.addEventListener(
|
||||||
'contextmenu',
|
'contextmenu',
|
||||||
onContextMenu
|
OnContextMenu
|
||||||
);
|
);
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
'click',
|
'click',
|
||||||
onLeftClick
|
OnLeftClick
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
elementRef.current?.removeEventListener(
|
elementRef.current?.removeEventListener(
|
||||||
'contextmenu',
|
'contextmenu',
|
||||||
onContextMenu
|
OnContextMenu
|
||||||
);
|
);
|
||||||
|
|
||||||
window.removeEventListener(
|
window.removeEventListener(
|
||||||
'click',
|
'click',
|
||||||
onLeftClick
|
OnLeftClick
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleRightClick(
|
export function HandleRightClick(
|
||||||
event: MouseEvent,
|
event: MouseEvent,
|
||||||
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||||
setOnClickSymbolId: React.Dispatch<React.SetStateAction<string>>,
|
setOnClickSymbolId: React.Dispatch<React.SetStateAction<string>>,
|
||||||
|
@ -66,7 +70,7 @@ export function handleRightClick(
|
||||||
setContextMenuPosition(contextMenuPosition);
|
setContextMenuPosition(contextMenuPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleLeftClick(
|
export function HandleLeftClick(
|
||||||
isContextMenuOpen: boolean,
|
isContextMenuOpen: boolean,
|
||||||
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||||
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>
|
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
|
|
@ -2,22 +2,22 @@ import * as React from 'react';
|
||||||
import { FixedSizeList as List } from 'react-window';
|
import { FixedSizeList as List } from 'react-window';
|
||||||
import { Menu } from '../Menu/Menu';
|
import { Menu } from '../Menu/Menu';
|
||||||
import { MenuItem } from '../Menu/MenuItem';
|
import { MenuItem } from '../Menu/MenuItem';
|
||||||
import { handleLeftClick, handleRightClick, useMouseEvents } from './MouseEventHandlers';
|
import { UseMouseEvents } from './MouseEventHandlers';
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
import { IPoint } from '../../Interfaces/IPoint';
|
||||||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { SymbolProperties } from '../SymbolProperties/SymbolProperties';
|
import { SymbolProperties } from '../SymbolProperties/SymbolProperties';
|
||||||
|
|
||||||
interface ISymbolsSidebarProps {
|
interface ISymbolsSidebarProps {
|
||||||
SelectedSymbolId: string
|
selectedSymbolId: string
|
||||||
symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
isHistoryOpen: boolean
|
isHistoryOpen: boolean
|
||||||
OnPropertyChange: (key: string, value: string | number | boolean) => void
|
onPropertyChange: (key: string, value: string | number | boolean) => void
|
||||||
SelectSymbol: (symbolId: string) => void
|
selectSymbol: (symbolId: string) => void
|
||||||
DeleteSymbol: (containerid: string) => void
|
deleteSymbol: (containerid: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSidebarProps): JSX.Element => {
|
export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
|
||||||
// States
|
// States
|
||||||
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
|
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
|
||||||
const [onClickSymbolId, setOnClickSymbolId] = React.useState<string>('');
|
const [onClickSymbolId, setOnClickSymbolId] = React.useState<string>('');
|
||||||
|
@ -29,7 +29,7 @@ export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSi
|
||||||
const elementRef = React.useRef<HTMLDivElement>(null);
|
const elementRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Event listeners
|
// Event listeners
|
||||||
useMouseEvents(
|
UseMouseEvents(
|
||||||
isContextMenuOpen,
|
isContextMenuOpen,
|
||||||
elementRef,
|
elementRef,
|
||||||
setIsContextMenuOpen,
|
setIsContextMenuOpen,
|
||||||
|
@ -46,35 +46,33 @@ export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSi
|
||||||
}
|
}
|
||||||
|
|
||||||
const containers = [...props.symbols.values()];
|
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 container = containers[index];
|
||||||
const key = container.id.toString();
|
const key = container.id.toString();
|
||||||
const text = key;
|
const text = key;
|
||||||
const selectedClass: string = props.SelectedSymbolId !== '' &&
|
const selectedClass: string = props.selectedSymbolId !== '' &&
|
||||||
props.SelectedSymbolId === container.id
|
props.selectedSymbolId === container.id
|
||||||
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
|
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
|
||||||
: 'bg-slate-300/60 hover:bg-slate-300';
|
: 'bg-slate-300/60 hover:bg-slate-300';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type="button"
|
<button type="button"
|
||||||
className={
|
className={`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
||||||
`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
text-left text-sm font-medium transition-all ${selectedClass}`}
|
||||||
text-left text-sm font-medium transition-all ${selectedClass}`
|
|
||||||
}
|
|
||||||
id={key}
|
id={key}
|
||||||
key={key}
|
key={key}
|
||||||
style={style}
|
style={style}
|
||||||
onClick={() => props.SelectSymbol(key)}
|
onClick={() => props.selectSymbol(key)}
|
||||||
>
|
>
|
||||||
{ text }
|
{text}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
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}`}>
|
<div className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
|
||||||
<div className='bg-slate-100 font-bold sidebar-title'>
|
<div className='bg-slate-100 font-bold sidebar-title'>
|
||||||
Elements
|
Elements
|
||||||
</div>
|
</div>
|
||||||
<div ref={elementRef} className='h-96 text-gray-800'>
|
<div ref={elementRef} className='h-96 text-gray-800'>
|
||||||
<List
|
<List
|
||||||
|
@ -84,7 +82,7 @@ export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSi
|
||||||
height={384}
|
height={384}
|
||||||
width={256}
|
width={256}
|
||||||
>
|
>
|
||||||
{ Row }
|
{Row}
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
<Menu
|
<Menu
|
||||||
|
@ -95,14 +93,13 @@ export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSi
|
||||||
>
|
>
|
||||||
<MenuItem className='contextmenu-item' text='Delete' onClick={() => {
|
<MenuItem className='contextmenu-item' text='Delete' onClick={() => {
|
||||||
setIsContextMenuOpen(false);
|
setIsContextMenuOpen(false);
|
||||||
props.DeleteSymbol(onClickSymbolId);
|
props.deleteSymbol(onClickSymbolId);
|
||||||
}} />
|
} } />
|
||||||
</Menu>
|
</Menu>
|
||||||
<SymbolProperties
|
<SymbolProperties
|
||||||
symbol={props.symbols.get(props.SelectedSymbolId)}
|
symbol={props.symbols.get(props.selectedSymbolId)}
|
||||||
symbols={props.symbols}
|
symbols={props.symbols}
|
||||||
onChange={props.OnPropertyChange}
|
onChange={props.onPropertyChange} />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
import React, { FC } from 'react';
|
import React from 'react';
|
||||||
import './ToggleButton.scss';
|
import './ToggleButton.scss';
|
||||||
|
|
||||||
interface IToggleButtonProps {
|
interface IToggleButtonProps {
|
||||||
id: string
|
id: string
|
||||||
text: string
|
text: string
|
||||||
type?: TOGGLE_TYPE
|
type?: ToggleType
|
||||||
title: string
|
title: string
|
||||||
checked: boolean
|
checked: boolean
|
||||||
onChange: React.ChangeEventHandler<HTMLInputElement>
|
onChange: React.ChangeEventHandler<HTMLInputElement>
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TOGGLE_TYPE {
|
export enum ToggleType {
|
||||||
MATERIAL,
|
Material,
|
||||||
IOS
|
IOS
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToggleButton: FC<IToggleButtonProps> = (props) => {
|
export function ToggleButton(props: IToggleButtonProps): JSX.Element {
|
||||||
const id = `toggle-${props.id}`;
|
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 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';
|
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';
|
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';
|
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 className={classDot}></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 text-gray-700 font-medium">
|
<div className="ml-3 text-gray-700 font-medium">
|
||||||
{ props.text }
|
{props.text}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -14,23 +14,23 @@ import { SymbolsSidebar } from '../SymbolsSidebar/SymbolsSidebar';
|
||||||
import { PropertyType } from '../../Enums/PropertyType';
|
import { PropertyType } from '../../Enums/PropertyType';
|
||||||
|
|
||||||
interface IUIProps {
|
interface IUIProps {
|
||||||
SelectedContainer: IContainerModel | undefined
|
selectedContainer: IContainerModel | undefined
|
||||||
current: IHistoryState
|
current: IHistoryState
|
||||||
history: IHistoryState[]
|
history: IHistoryState[]
|
||||||
historyCurrentStep: number
|
historyCurrentStep: number
|
||||||
AvailableContainers: IAvailableContainer[]
|
availableContainers: IAvailableContainer[]
|
||||||
AvailableSymbols: IAvailableSymbol[]
|
availableSymbols: IAvailableSymbol[]
|
||||||
SelectContainer: (containerId: string) => void
|
selectContainer: (containerId: string) => void
|
||||||
DeleteContainer: (containerId: string) => void
|
deleteContainer: (containerId: string) => void
|
||||||
OnPropertyChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
|
onPropertyChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
|
||||||
AddContainer: (type: string) => void
|
addContainer: (type: string) => void
|
||||||
AddSymbol: (type: string) => void
|
addSymbol: (type: string) => void
|
||||||
OnSymbolPropertyChange: (key: string, value: string | number | boolean) => void
|
onSymbolPropertyChange: (key: string, value: string | number | boolean) => void
|
||||||
SelectSymbol: (symbolId: string) => void
|
selectSymbol: (symbolId: string) => void
|
||||||
DeleteSymbol: (symbolId: string) => void
|
deleteSymbol: (symbolId: string) => void
|
||||||
SaveEditorAsJSON: () => void
|
saveEditorAsJSON: () => void
|
||||||
SaveEditorAsSVG: () => void
|
saveEditorAsSVG: () => void
|
||||||
LoadState: (move: number) => void
|
loadState: (move: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function CloseOtherSidebars(
|
function CloseOtherSidebars(
|
||||||
|
@ -41,7 +41,7 @@ function CloseOtherSidebars(
|
||||||
setIsSymbolsOpen(false);
|
setIsSymbolsOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
export function UI(props: IUIProps): JSX.Element {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
|
const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
|
||||||
const [isSymbolsOpen, setIsSymbolsOpen] = React.useState(false);
|
const [isSymbolsOpen, setIsSymbolsOpen] = React.useState(false);
|
||||||
const [isHistoryOpen, setIsHistoryOpen] = React.useState(false);
|
const [isHistoryOpen, setIsHistoryOpen] = React.useState(false);
|
||||||
|
@ -61,71 +61,63 @@ export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
||||||
isSymbolsOpen={isSymbolsOpen}
|
isSymbolsOpen={isSymbolsOpen}
|
||||||
isElementsSidebarOpen={isSidebarOpen}
|
isElementsSidebarOpen={isSidebarOpen}
|
||||||
isHistoryOpen={isHistoryOpen}
|
isHistoryOpen={isHistoryOpen}
|
||||||
ToggleSidebar={() => {
|
toggleSidebar={() => {
|
||||||
CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen);
|
CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen);
|
||||||
setIsSidebarOpen(!isSidebarOpen);
|
setIsSidebarOpen(!isSidebarOpen);
|
||||||
}}
|
} }
|
||||||
ToggleSymbols={() => {
|
toggleSymbols={() => {
|
||||||
CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen);
|
CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen);
|
||||||
setIsSymbolsOpen(!isSymbolsOpen);
|
setIsSymbolsOpen(!isSymbolsOpen);
|
||||||
}}
|
} }
|
||||||
ToggleTimeline={() => setIsHistoryOpen(!isHistoryOpen)}
|
toggleTimeline={() => setIsHistoryOpen(!isHistoryOpen)} />
|
||||||
/>
|
|
||||||
|
|
||||||
<Sidebar
|
<Sidebar
|
||||||
componentOptions={props.AvailableContainers}
|
componentOptions={props.availableContainers}
|
||||||
isOpen={isSidebarOpen}
|
isOpen={isSidebarOpen}
|
||||||
buttonOnClick={props.AddContainer}
|
buttonOnClick={props.addContainer} />
|
||||||
/>
|
|
||||||
<Symbols
|
<Symbols
|
||||||
componentOptions={props.AvailableSymbols}
|
componentOptions={props.availableSymbols}
|
||||||
isOpen={isSymbolsOpen}
|
isOpen={isSymbolsOpen}
|
||||||
buttonOnClick={props.AddSymbol}
|
buttonOnClick={props.addSymbol} />
|
||||||
/>
|
|
||||||
<ElementsSidebar
|
<ElementsSidebar
|
||||||
MainContainer={props.current.MainContainer}
|
mainContainer={props.current.mainContainer}
|
||||||
symbols={props.current.Symbols}
|
symbols={props.current.symbols}
|
||||||
SelectedContainer={props.SelectedContainer}
|
selectedContainer={props.selectedContainer}
|
||||||
isOpen={isSidebarOpen}
|
isOpen={isSidebarOpen}
|
||||||
isHistoryOpen={isHistoryOpen}
|
isHistoryOpen={isHistoryOpen}
|
||||||
OnPropertyChange={props.OnPropertyChange}
|
onPropertyChange={props.onPropertyChange}
|
||||||
SelectContainer={props.SelectContainer}
|
selectContainer={props.selectContainer}
|
||||||
DeleteContainer={props.DeleteContainer}
|
deleteContainer={props.deleteContainer} />
|
||||||
/>
|
|
||||||
<SymbolsSidebar
|
<SymbolsSidebar
|
||||||
SelectedSymbolId={props.current.SelectedSymbolId}
|
selectedSymbolId={props.current.selectedSymbolId}
|
||||||
symbols={props.current.Symbols}
|
symbols={props.current.symbols}
|
||||||
isOpen={isSymbolsOpen}
|
isOpen={isSymbolsOpen}
|
||||||
isHistoryOpen={isHistoryOpen}
|
isHistoryOpen={isHistoryOpen}
|
||||||
OnPropertyChange={props.OnSymbolPropertyChange}
|
onPropertyChange={props.onSymbolPropertyChange}
|
||||||
SelectSymbol={props.SelectSymbol}
|
selectSymbol={props.selectSymbol}
|
||||||
DeleteSymbol={props.DeleteSymbol}
|
deleteSymbol={props.deleteSymbol} />
|
||||||
/>
|
|
||||||
<History
|
<History
|
||||||
history={props.history}
|
history={props.history}
|
||||||
historyCurrentStep={props.historyCurrentStep}
|
historyCurrentStep={props.historyCurrentStep}
|
||||||
isOpen={isHistoryOpen}
|
isOpen={isHistoryOpen}
|
||||||
jumpTo={props.LoadState}
|
jumpTo={props.loadState} />
|
||||||
/>
|
|
||||||
|
|
||||||
<FloatingButton className={`fixed z-10 flex flex-col gap-2 items-center bottom-40 ${buttonRightOffsetClasses}`}>
|
<FloatingButton className={`fixed z-10 flex flex-col gap-2 items-center bottom-40 ${buttonRightOffsetClasses}`}>
|
||||||
<button type="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'}
|
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'
|
title='Export as JSON'
|
||||||
onClick={props.SaveEditorAsJSON}
|
onClick={props.saveEditorAsJSON}
|
||||||
>
|
>
|
||||||
<UploadIcon className="heroicon text-white" />
|
<UploadIcon className="heroicon text-white" />
|
||||||
</button>
|
</button>
|
||||||
<button type="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'}
|
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'
|
title='Export as SVG'
|
||||||
onClick={props.SaveEditorAsSVG}
|
onClick={props.saveEditorAsSVG}
|
||||||
>
|
>
|
||||||
<PhotographIcon className="heroicon text-white" />
|
<PhotographIcon className="heroicon text-white" />
|
||||||
</button>
|
</button>
|
||||||
</FloatingButton>
|
</FloatingButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default UI;
|
|
||||||
|
|
|
@ -8,15 +8,15 @@ export enum PropertyType {
|
||||||
/**
|
/**
|
||||||
* Simple property: is not inside any object: id, x, width... (default)
|
* Simple property: is not inside any object: id, x, width... (default)
|
||||||
*/
|
*/
|
||||||
SIMPLE,
|
Simple,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Style property: is inside the style object: stroke, fillOpacity...
|
* Style property: is inside the style object: stroke, fillOpacity...
|
||||||
*/
|
*/
|
||||||
STYLE,
|
Style,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Margin property: is inside the margin property: left, bottom, top, right...
|
* Margin property: is inside the margin property: left, bottom, top, right...
|
||||||
*/
|
*/
|
||||||
MARGIN,
|
Margin,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
import { getCurrentHistory } from '../Components/Editor/Editor';
|
import { GetCurrentHistory } from '../Components/Editor/Editor';
|
||||||
import { IConfiguration } from '../Interfaces/IConfiguration';
|
import { IConfiguration } from '../Interfaces/IConfiguration';
|
||||||
import { IEditorState } from '../Interfaces/IEditorState';
|
import { IEditorState } from '../Interfaces/IEditorState';
|
||||||
import { IHistoryState } from '../Interfaces/IHistoryState';
|
import { IHistoryState } from '../Interfaces/IHistoryState';
|
||||||
|
@ -38,7 +38,7 @@ const appendNewState = (
|
||||||
): void => {
|
): void => {
|
||||||
const state: IHistoryState = JSON.parse(eventInitDict?.detail.state);
|
const state: IHistoryState = JSON.parse(eventInitDict?.detail.state);
|
||||||
ReviveState(state);
|
ReviveState(state);
|
||||||
const history = getCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
|
||||||
|
|
||||||
history.push(state);
|
history.push(state);
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AddMethod } from '../Enums/AddMethod';
|
import { AddMethod } from '../Enums/AddMethod';
|
||||||
import { XPositionReference } from '../Enums/XPositionReference';
|
import { XPositionReference } from '../Enums/XPositionReference';
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { XPositionReference } from '../Enums/XPositionReference';
|
import { XPositionReference } from '../Enums/XPositionReference';
|
||||||
import { IImage } from './IImage';
|
import { IImage } from './IImage';
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { IAvailableContainer } from './IAvailableContainer';
|
import { IAvailableContainer } from './IAvailableContainer';
|
||||||
import { IAvailableSymbol } from './IAvailableSymbol';
|
import { IAvailableSymbol } from './IAvailableSymbol';
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import IContainerProperties from './IContainerProperties';
|
import { IContainerProperties } from './IContainerProperties';
|
||||||
|
|
||||||
export interface IContainerModel {
|
export interface IContainerModel {
|
||||||
children: IContainerModel[]
|
children: IContainerModel[]
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { IMargin } from './IMargin';
|
||||||
/**
|
/**
|
||||||
* Properties of a container
|
* Properties of a container
|
||||||
*/
|
*/
|
||||||
export default interface IContainerProperties {
|
export interface IContainerProperties {
|
||||||
/** id of the container */
|
/** id of the container */
|
||||||
id: string
|
id: string
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ export default interface IContainerProperties {
|
||||||
isFlex: boolean
|
isFlex: boolean
|
||||||
|
|
||||||
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
/** Horizontal alignment, also determines the visual location of x {Left = 0, Center, Right } */
|
||||||
XPositionReference: XPositionReference
|
xPositionReference: XPositionReference
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (optional)
|
* (optional)
|
||||||
|
|
|
@ -3,20 +3,20 @@ import { ISymbolModel } from './ISymbolModel';
|
||||||
|
|
||||||
export interface IHistoryState {
|
export interface IHistoryState {
|
||||||
/** Last editor action */
|
/** Last editor action */
|
||||||
LastAction: string
|
lastAction: string
|
||||||
|
|
||||||
/** Reference to the main container */
|
/** Reference to the main container */
|
||||||
MainContainer: IContainerModel
|
mainContainer: IContainerModel
|
||||||
|
|
||||||
/** Id of the selected container */
|
/** Id of the selected container */
|
||||||
SelectedContainerId: string
|
selectedContainerId: string
|
||||||
|
|
||||||
/** Counter of type of container. Used for ids. */
|
/** Counter of type of container. Used for ids. */
|
||||||
TypeCounters: Record<string, number>
|
typeCounters: Record<string, number>
|
||||||
|
|
||||||
/** List of symbols */
|
/** List of symbols */
|
||||||
Symbols: Map<string, ISymbolModel>
|
symbols: Map<string, ISymbolModel>
|
||||||
|
|
||||||
/** Selected symbols id */
|
/** Selected symbols id */
|
||||||
SelectedSymbolId: string
|
selectedSymbolId: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
/**
|
/**
|
||||||
* Model of an image with multiple source
|
* Model of an image with multiple source
|
||||||
* It must at least have one 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 { App } from './Components/App/App';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
function render(root: Element | Document): void {
|
function RenderRoot(root: Element | Document): void {
|
||||||
ReactDOM.createRoot(root.querySelector('#root') as HTMLDivElement).render(
|
ReactDOM.createRoot(root.querySelector('#root') as HTMLDivElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App root={root}/>
|
<App root={root}/>
|
||||||
|
@ -12,10 +12,9 @@ function render(root: Element | Document): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace SVGLayoutDesigner {
|
namespace SVGLayoutDesigner {
|
||||||
export const Render = render;
|
export const Render = RenderRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
(window as any).SVGLayoutDesigner = SVGLayoutDesigner;
|
(window as any).SVGLayoutDesigner = SVGLayoutDesigner;
|
||||||
|
|
||||||
render(document);
|
RenderRoot(document);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { IAvailableContainer } from '../Interfaces/IAvailableContainer';
|
||||||
import { IAvailableSymbol } from '../Interfaces/IAvailableSymbol';
|
import { IAvailableSymbol } from '../Interfaces/IAvailableSymbol';
|
||||||
import { IConfiguration } from '../Interfaces/IConfiguration';
|
import { IConfiguration } from '../Interfaces/IConfiguration';
|
||||||
import { ContainerModel, IContainerModel } from '../Interfaces/IContainerModel';
|
import { ContainerModel, IContainerModel } from '../Interfaces/IContainerModel';
|
||||||
import IContainerProperties from '../Interfaces/IContainerProperties';
|
import { IContainerProperties } from '../Interfaces/IContainerProperties';
|
||||||
import { IEditorState } from '../Interfaces/IEditorState';
|
import { IEditorState } from '../Interfaces/IEditorState';
|
||||||
import { ISymbolModel } from '../Interfaces/ISymbolModel';
|
import { ISymbolModel } from '../Interfaces/ISymbolModel';
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export const APPLY_BEHAVIORS_ON_CHILDREN = true;
|
||||||
/**
|
/**
|
||||||
* Returns the default editor state given the configuration
|
* Returns the default editor state given the configuration
|
||||||
*/
|
*/
|
||||||
export const GetDefaultEditorState = (configuration: IConfiguration): IEditorState => {
|
export function GetDefaultEditorState(configuration: IConfiguration): IEditorState {
|
||||||
const mainContainer = new ContainerModel(
|
const mainContainer = new ContainerModel(
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
|
@ -50,22 +50,23 @@ export const GetDefaultEditorState = (configuration: IConfiguration): IEditorSta
|
||||||
configuration,
|
configuration,
|
||||||
history: [
|
history: [
|
||||||
{
|
{
|
||||||
LastAction: '',
|
lastAction: '',
|
||||||
MainContainer: mainContainer,
|
mainContainer: mainContainer,
|
||||||
SelectedContainerId: mainContainer.properties.id,
|
selectedContainerId: mainContainer.properties.id,
|
||||||
TypeCounters: {},
|
typeCounters: {},
|
||||||
Symbols: new Map(),
|
symbols: new Map(),
|
||||||
SelectedSymbolId: ''
|
selectedSymbolId: ''
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
historyCurrentStep: 0
|
historyCurrentStep: 0
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default config when the API is not available
|
* Default config when the API is not available
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_CONFIG: IConfiguration = {
|
export const DEFAULT_CONFIG: IConfiguration = {
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
AvailableContainers: [
|
AvailableContainers: [
|
||||||
{
|
{
|
||||||
Type: 'Container',
|
Type: 'Container',
|
||||||
|
@ -87,6 +88,7 @@ export const DEFAULT_CONFIG: IConfiguration = {
|
||||||
stroke: 'black'
|
stroke: 'black'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* eslint-enable */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,7 +109,7 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
|
||||||
height: Number(DEFAULT_CONFIG.MainContainer.Height),
|
height: Number(DEFAULT_CONFIG.MainContainer.Height),
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
isFlex: false,
|
isFlex: false,
|
||||||
XPositionReference: XPositionReference.Left,
|
xPositionReference: XPositionReference.Left,
|
||||||
style: {
|
style: {
|
||||||
stroke: 'black',
|
stroke: 'black',
|
||||||
fillOpacity: 0
|
fillOpacity: 0
|
||||||
|
@ -124,42 +126,40 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
|
||||||
* @param containerConfig default config of the container sent by the API
|
* @param containerConfig default config of the container sent by the API
|
||||||
* @returns {IContainerProperties} Default properties of a newly created container
|
* @returns {IContainerProperties} Default properties of a newly created container
|
||||||
*/
|
*/
|
||||||
export const GetDefaultContainerProps = (
|
export function GetDefaultContainerProps(type: string,
|
||||||
type: string,
|
|
||||||
typeCount: number,
|
typeCount: number,
|
||||||
parent: IContainerModel,
|
parent: IContainerModel,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
containerConfig: IAvailableContainer
|
containerConfig: IAvailableContainer): IContainerProperties {
|
||||||
): IContainerProperties => ({
|
return ({
|
||||||
id: `${type}-${typeCount}`,
|
id: `${type}-${typeCount}`,
|
||||||
type,
|
type,
|
||||||
parentId: parent.properties.id,
|
parentId: parent.properties.id,
|
||||||
linkedSymbolId: '',
|
linkedSymbolId: '',
|
||||||
displayedText: `${type}-${typeCount}`,
|
displayedText: `${type}-${typeCount}`,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
margin: containerConfig.Margin ?? {},
|
margin: containerConfig.Margin ?? {},
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
isAnchor: false,
|
isAnchor: false,
|
||||||
isFlex: containerConfig.IsFlex ?? containerConfig.Width === undefined,
|
isFlex: containerConfig.IsFlex ?? containerConfig.Width === undefined,
|
||||||
XPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
xPositionReference: containerConfig.XPositionReference ?? XPositionReference.Left,
|
||||||
minWidth: containerConfig.MinWidth ?? 1,
|
minWidth: containerConfig.MinWidth ?? 1,
|
||||||
maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
|
maxWidth: containerConfig.MaxWidth ?? Number.MAX_SAFE_INTEGER,
|
||||||
customSVG: containerConfig.CustomSVG,
|
customSVG: containerConfig.CustomSVG,
|
||||||
style: structuredClone(containerConfig.Style),
|
style: structuredClone(containerConfig.Style),
|
||||||
userData: structuredClone(containerConfig.UserData)
|
userData: structuredClone(containerConfig.UserData)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const GetDefaultSymbolModel = (
|
export function GetDefaultSymbolModel(name: string,
|
||||||
name: string,
|
|
||||||
newCounters: Record<string, number>,
|
newCounters: Record<string, number>,
|
||||||
type: string,
|
type: string,
|
||||||
symbolConfig: IAvailableSymbol
|
symbolConfig: IAvailableSymbol): ISymbolModel {
|
||||||
): ISymbolModel => {
|
|
||||||
return {
|
return {
|
||||||
id: `${name}-${newCounters[type]}`,
|
id: `${name}-${newCounters[type]}`,
|
||||||
type: name,
|
type: name,
|
||||||
|
@ -169,4 +169,4 @@ export const GetDefaultSymbolModel = (
|
||||||
height: symbolConfig.Height ?? DEFAULT_SYMBOL_HEIGHT,
|
height: symbolConfig.Height ?? DEFAULT_SYMBOL_HEIGHT,
|
||||||
linkedContainers: new Set()
|
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
|
||||||
* @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 depth = 0;
|
||||||
|
|
||||||
let current: IContainerModel | null = parent;
|
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 by iterating to the parent
|
||||||
* @returns The absolute position of the container
|
* @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 x = container.properties.x;
|
||||||
const y = container.properties.y;
|
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
|
* @param y value to be restored
|
||||||
* @returns x and y such that the transformations of the parent are cancelled
|
* @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;
|
let current = parent;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
x += current.properties.x;
|
x += current.properties.x;
|
||||||
|
@ -100,7 +100,7 @@ export function cancelParentTransform(parent: IContainerModel | null, x: number,
|
||||||
* @param y value to be restored
|
* @param y value to be restored
|
||||||
* @returns x and y such that the transformations of the parent are cancelled
|
* @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;
|
let current = parent;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
x -= current.properties.x;
|
x -= current.properties.x;
|
||||||
|
@ -110,7 +110,7 @@ export function applyParentTransform(parent: IContainerModel | null, x: number,
|
||||||
return [x, y];
|
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);
|
const it = MakeIterator(root);
|
||||||
for (const container of it) {
|
for (const container of it) {
|
||||||
if (container.properties.id === id) {
|
if (container.properties.id === id) {
|
||||||
|
@ -125,13 +125,13 @@ export interface IPair<T> {
|
||||||
next: 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++) {
|
for (let i = 0; i < arr.length - 1; i++) {
|
||||||
yield { cur: arr[i], next: arr[i + 1] };
|
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--) {
|
for (let i = arr.length - 1; i > 0; i--) {
|
||||||
yield { cur: arr[i], next: arr[i - 1] };
|
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 { IEditorState } from '../Interfaces/IEditorState';
|
||||||
import { IHistoryState } from '../Interfaces/IHistoryState';
|
import { IHistoryState } from '../Interfaces/IHistoryState';
|
||||||
|
|
||||||
|
@ -19,34 +19,32 @@ export function Revive(editorState: IEditorState): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReviveState = (
|
export function ReviveState(state: IHistoryState): void {
|
||||||
state: IHistoryState
|
if (state.mainContainer === null || state.mainContainer === undefined) {
|
||||||
): void => {
|
|
||||||
if (state.MainContainer === null || state.MainContainer === undefined) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Symbols = new Map(state.Symbols);
|
state.symbols = new Map(state.symbols);
|
||||||
for (const symbol of state.Symbols.values()) {
|
for (const symbol of state.symbols.values()) {
|
||||||
symbol.linkedContainers = new Set(symbol.linkedContainers);
|
symbol.linkedContainers = new Set(symbol.linkedContainers);
|
||||||
}
|
}
|
||||||
|
|
||||||
const it = MakeIterator(state.MainContainer);
|
const it = MakeIterator(state.mainContainer);
|
||||||
for (const container of it) {
|
for (const container of it) {
|
||||||
const parentId = container.properties.parentId;
|
const parentId = container.properties.parentId;
|
||||||
if (parentId === null) {
|
if (parentId === null) {
|
||||||
container.parent = null;
|
container.parent = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const parent = findContainerById(state.MainContainer, parentId);
|
const parent = FindContainerById(state.mainContainer, parentId);
|
||||||
if (parent === undefined) {
|
if (parent === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
container.parent = parent;
|
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) => {
|
return (key: any, value: object | null) => {
|
||||||
if (key === 'parent') {
|
if (key === 'parent') {
|
||||||
return;
|
return;
|
||||||
|
@ -62,4 +60,4 @@ export const getCircularReplacer = (): (key: any, value: object | Map<string, an
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ function GetInitialMatrix(
|
||||||
return matrix;
|
return matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllIndexes(arr: number[], val: number): number[] {
|
function GetAllIndexes(arr: number[], val: number): number[] {
|
||||||
const indexes = []; let i = -1;
|
const indexes = []; let i = -1;
|
||||||
while ((i = arr.indexOf(val, i + 1)) !== -1) {
|
while ((i = arr.indexOf(val, i + 1)) !== -1) {
|
||||||
indexes.push(i);
|
indexes.push(i);
|
||||||
|
@ -125,9 +125,9 @@ function ApplyMainLoop(oldMatrix: number[][], rowlength: number): number[][] {
|
||||||
// 1) find the index with smallest coefficient (O(n)+)
|
// 1) find the index with smallest coefficient (O(n)+)
|
||||||
const lastRow = matrix[matrix.length - 1];
|
const lastRow = matrix[matrix.length - 1];
|
||||||
const min = Math.min(...lastRow);
|
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
|
// 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
|
// record the usage of index by incrementing
|
||||||
indexesTried[pivotColIndex] = indexesTried[pivotColIndex] !== undefined ? indexesTried[pivotColIndex] + 1 : 1;
|
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.
|
* @param indexesTried Record of indexes. Count the number of times the index was used.
|
||||||
* @returns The least used index
|
* @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 minUsed = Infinity;
|
||||||
let minIndex = -1;
|
let minIndex = -1;
|
||||||
for (const index of indexes) {
|
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) {
|
if (str.length <= num) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
return `${str.slice(0, num)}...`;
|
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('');
|
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.
|
* 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;
|
let transformedX = x;
|
||||||
if (xPositionReference === XPositionReference.Center) {
|
if (xPositionReference === XPositionReference.Center) {
|
||||||
transformedX += width / 2;
|
transformedX += width / 2;
|
||||||
|
@ -20,7 +20,7 @@ export function transformX(x: number, width: number, xPositionReference = XPosit
|
||||||
return transformedX;
|
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;
|
let transformedX = x;
|
||||||
if (xPositionReference === XPositionReference.Center) {
|
if (xPositionReference === XPositionReference.Center) {
|
||||||
transformedX -= width / 2;
|
transformedX -= width / 2;
|
||||||
|
|
|
@ -7,14 +7,15 @@ afterEach(() => {
|
||||||
cleanup();
|
cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
const customRender = (ui: React.ReactElement, options = {}): RenderResult =>
|
function CustomRender(ui: React.ReactElement, options = {}): RenderResult {
|
||||||
render(ui, {
|
return render(ui, {
|
||||||
// wrap provider(s) here if needed
|
// wrap provider(s) here if needed
|
||||||
wrapper: ({ children }) => children,
|
wrapper: ({ children }) => children,
|
||||||
...options
|
...options
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export * from '@testing-library/react';
|
export * from '@testing-library/react';
|
||||||
export { default as userEvent } from '@testing-library/user-event';
|
export { default as userEvent } from '@testing-library/user-event';
|
||||||
// override render export
|
// override render export
|
||||||
export { customRender as render };
|
export { CustomRender as render };
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue