Merge branch 'master' into master.7687.ReplaceBy

This commit is contained in:
Eric NGUYEN 2023-02-14 16:44:50 +01:00
commit 9aec5ae751
38 changed files with 2346 additions and 1434 deletions

View file

@ -19,7 +19,8 @@ module.exports = {
},
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.json'
project: './tsconfig.json',
tsconfigRootDir: __dirname
},
plugins: [
'only-warn',

View file

@ -9,7 +9,20 @@ You will be able to navigate through this document with the table of contents.
- [Table of contents](#table-of-contents)
- [I want to contribute](#i-want-to-contribute)
- [I want to contribute to the .NETFramework API](#i-want-to-contribute-to-the-netframework-api)
- [Getting Started](#getting-started)
- [Before developing](#before-developing)
- [Testing](#testing)
- [Releasing](#releasing)
- [I want to contribute to the React component](#i-want-to-contribute-to-the-react-component)
- [Getting Started](#getting-started-1)
- [Before developing](#before-developing-1)
- [CORS](#cors)
- [Develop with Vite and pnpm](#develop-with-vite-and-pnpm)
- [Develop with mprocs](#develop-with-mprocs)
- [Testing the external API without .NETFramework or Windows](#testing-the-external-api-without-netframework-or-windows)
- [Setup debugging with chrome](#setup-debugging-with-chrome)
- [Testing](#testing-1)
- [Releasing](#releasing-1)
- [I want to report a bug](#i-want-to-report-a-bug)
- [Before submitting a bug report](#before-submitting-a-bug-report)
- [How do i submit a good bug report?](#how-do-i-submit-a-good-bug-report)
@ -102,6 +115,14 @@ Then run the following command to run the projet in a dev environment:
pnpm dev
```
### Develop with mprocs
[Mprocs](https://github.com/pvolok/mprocs) runs multiple commands in parallel and shows output of each command separately.
It is useful to run `vite` and the test server at the same time with `mprocs`.
Run `pnpm d` or `pnpm mprocs` to run mprocs.
### Testing the external API without .NETFramework or Windows
Use the Node.js server in `/test-server` to simulate the api.

View file

@ -20,9 +20,12 @@ namespace SVGLDLibs.Models
[DataMember(EmitDefaultValue = false)]
public string color;
/** width */
[DataMember(EmitDefaultValue = false)]
public double width;
/** color */
[DataMember(EmitDefaultValue = false)]
public string dashArray;
}
}

View file

@ -1,4 +1,4 @@
using System.Runtime.Serialization;
using System.Runtime.Serialization;
using System.Collections.Generic;
namespace SVGLDLibs.Models
@ -26,5 +26,8 @@ namespace SVGLDLibs.Models
[DataMember(EmitDefaultValue = false)]
public List<string> linkedContainers { get; set; }
[DataMember(EmitDefaultValue = false)]
public List<bool> showDimension { get; set; }
}
}

7
mprocs.yaml Normal file
View file

@ -0,0 +1,7 @@
procs:
nvim:
shell: "nvim ."
vite:
shell: "npx vite"
test-server:
shell: "npx nodemon ./test-server/http.js"

View file

@ -4,10 +4,10 @@
"version": "v1.0.0",
"type": "module",
"scripts": {
"d": "mprocs",
"dev": "vite",
"build": "tsc && vite build",
"build:dotnet": "dotnet build ./csharp/SVGLDLibs/SVGLDLibs/SVGLDLibs.csproj",
"preview": "vite preview",
"linter": "eslint src",
"test": "vitest",
"test:ui": "vitest --ui",
@ -17,45 +17,48 @@
"coverage": "vitest run coverage"
},
"dependencies": {
"@heroicons/react": "^2.0.11",
"@heroicons/react": "^2.0.14",
"@react-hook/size": "^2.1.2",
"interweave": "^13.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-svg-pan-zoom": "^3.11.0",
"react-window": "^1.8.7",
"sweetalert2": "^11.4.34",
"sweetalert2-react-content": "^5.0.3"
"react-window": "^1.8.8",
"sweetalert2": "^11.7.1",
"sweetalert2-react-content": "^5.0.7"
},
"devDependencies": {
"@testing-library/dom": "^8.18.1",
"@testing-library/dom": "^8.20.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/react-svg-pan-zoom": "^3.3.5",
"@types/react-window": "^1.8.5",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"@vitejs/plugin-react": "^2.1.0",
"@vitest/ui": "^0.20.3",
"autoprefixer": "^10.4.12",
"eslint": "^8.24.0",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"@vitejs/plugin-react": "^3.1.0",
"@vitejs/plugin-react-swc": "^3.1.0",
"@vitest/ui": "^0.28.4",
"autoprefixer": "^10.4.13",
"eslint": "^8.33.0",
"eslint-config-standard": "^17.0.0",
"eslint-config-standard-with-typescript": "^22.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.3.0",
"eslint-plugin-only-warn": "^1.0.3",
"eslint-plugin-promise": "^6.0.1",
"eslint-plugin-react": "^7.31.8",
"eslint-config-standard-with-typescript": "^34.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-only-warn": "^1.1.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^20.0.0",
"postcss": "^8.4.16",
"sass": "^1.55.0",
"tailwindcss": "^3.1.8",
"typescript": "^4.8.3",
"vite": "^3.1.3",
"vitest": "^0.20.3"
"jsdom": "^21.1.0",
"mprocs": "^0.6.4",
"nodemon": "^2.0.20",
"postcss": "^8.4.21",
"sass": "^1.58.0",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.5",
"vite": "^4.1.1",
"vitest": "^0.28.4"
}
}

2378
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,7 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerModel';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { IPattern } from '../../Interfaces/IPattern';
import { DEFAULT_MAINCONTAINER_PROPS, GetDefaultContainerProps } from '../../utils/default';
import { DEFAULT_DIMENSION_OPTION, DEFAULT_MAINCONTAINER_PROPS, GetDefaultContainerProps } from '../../utils/default';
import { FetchConfiguration } from './api';
const CSHARP_WEB_API_BASE_URL = 'http://localhost:5209/';
@ -144,23 +144,11 @@ describe.concurrent('Models test suite', () => {
PositionReference: 0,
HideChildrenInTreeview: true,
DimensionOptions: {
childrenDimensions: {
color: '#000000',
positions: []
},
selfDimensions: {
color: '#000000',
positions: []
},
selfMarginsDimensions: {
color: '#000000',
positions: []
},
childrenDimensions: DEFAULT_DIMENSION_OPTION,
selfDimensions: DEFAULT_DIMENSION_OPTION,
selfMarginsDimensions: DEFAULT_DIMENSION_OPTION,
markPosition: [],
dimensionWithMarks: {
color: '#000000',
positions: []
}
dimensionWithMarks: DEFAULT_DIMENSION_OPTION
},
IsHidden: true,
Blacklist: [

View file

@ -1,14 +1,22 @@
import * as React from 'react';
import { PropertyType } from '../../Enums/PropertyType';
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { type IContainerProperties } from '../../Interfaces/IContainerProperties';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import {
SHOW_BORROWER_DIMENSIONS,
SHOW_CHILDREN_DIMENSIONS,
SHOW_SELF_DIMENSIONS,
SHOW_SELF_MARGINS_DIMENSIONS
SHOW_SELF_DIMENSIONS, SHOW_SELF_MARGINS_DIMENSIONS
} from '../../utils/default';
import { ApplyWidthMargin, ApplyXMargin, RemoveWidthMargin, RemoveXMargin, RestoreX, RestoreY, TransformX, TransformY } from '../../utils/svg';
import {
ApplyWidthMargin,
ApplyXMargin,
RemoveWidthMargin,
RemoveXMargin,
RestoreX,
RestoreY,
TransformX,
TransformY
} from '../../utils/svg';
import { Text } from '../Text/Text';
import { InputGroup } from '../InputGroup/InputGroup';
import { TextInputGroup } from '../InputGroup/TextInputGroup';
@ -38,7 +46,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName=''
type='string'
value={props.properties.displayedText?.toString()}
onChange={(value) => props.onChange('displayedText', value)} />
onChange={(value) => { props.onChange('displayedText', value); }}/>
<OrientationSelector
id='orientation'
name='Orientation'
@ -63,7 +71,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName=''
type='string'
value={props.properties.id.toString()}
isDisabled={true} />
isDisabled={true}/>
</div>
<div>
<InputGroup
@ -73,7 +81,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName=''
type='string'
value={props.properties.parentId}
isDisabled={true} />
isDisabled={true}/>
</div>
<div>
<InputGroup
@ -83,7 +91,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName=''
type='string'
value={props.properties.type}
isDisabled={true} />
isDisabled={true}/>
</div>
</div>
</Category>
@ -109,17 +117,19 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
props.properties.width,
props.properties.positionReference
).toString()}
onChange={(value) => props.onChange(
'x',
ApplyXMargin(
RestoreX(
Number(value),
props.properties.width,
props.properties.positionReference
),
props.properties.margin.left
)
)} />
onChange={(value) => {
props.onChange(
'x',
ApplyXMargin(
RestoreX(
Number(value),
props.properties.width,
props.properties.positionReference
),
props.properties.margin.left
)
);
}}/>
<TextInputGroup
id={`${props.properties.id}-y`}
labelText={Text({ textId: '@ContainerY' })}
@ -132,17 +142,19 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
props.properties.height,
props.properties.positionReference
).toString()}
onChange={(value) => props.onChange(
'y',
ApplyXMargin(
RestoreY(
Number(value),
props.properties.height,
props.properties.positionReference
),
props.properties.margin.top
)
)} />
onChange={(value) => {
props.onChange(
'y',
ApplyXMargin(
RestoreY(
Number(value),
props.properties.height,
props.properties.positionReference
),
props.properties.margin.top
)
);
}}/>
</div>
</Category>
@ -163,7 +175,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={1}
value={props.properties.minWidth.toString()}
onChange={(value) => props.onChange('minWidth', Number(value))} />
onChange={(value) => { props.onChange('minWidth', Number(value)); }}/>
<TextInputGroup
id={`${props.properties.id}-width`}
labelText={Text({ textId: '@ContainerWidth' })}
@ -174,8 +186,8 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
min={props.properties.minWidth}
max={props.properties.maxWidth}
value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()}
onChange={(value) => props.onChange('width', ApplyWidthMargin(Number(value), props.properties.margin.left, props.properties.margin.right))}
isDisabled={props.properties.isFlex} />
onChange={(value) => { props.onChange('width', ApplyWidthMargin(Number(value), props.properties.margin.left, props.properties.margin.right)); }}
isDisabled={props.properties.isFlex}/>
<TextInputGroup
id={`${props.properties.id}-maxWidth`}
labelText={Text({ textId: '@ContainerMaxWidth' })}
@ -185,7 +197,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={1}
value={props.properties.maxWidth.toString()}
onChange={(value) => props.onChange('maxWidth', Number(value))} />
onChange={(value) => { props.onChange('maxWidth', Number(value)); }}/>
<div className='col-span-5 p-3'></div>
<TextInputGroup
id={`${props.properties.id}-minHeight`}
@ -196,7 +208,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={1}
value={props.properties.minHeight.toString()}
onChange={(value) => props.onChange('minHeight', Number(value))} />
onChange={(value) => { props.onChange('minHeight', Number(value)); }}/>
<TextInputGroup
id={`${props.properties.id}-height`}
labelText={Text({ textId: '@ContainerHeight' })}
@ -207,7 +219,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
min={props.properties.minHeight}
max={props.properties.maxHeight}
value={(RemoveWidthMargin(props.properties.height, props.properties.margin.top, props.properties.margin.bottom)).toString()}
onChange={(value) => props.onChange('height', ApplyWidthMargin(Number(value), props.properties.margin.top, props.properties.margin.bottom))}
onChange={(value) => { props.onChange('height', ApplyWidthMargin(Number(value), props.properties.margin.top, props.properties.margin.bottom)); }}
isDisabled={props.properties.isFlex}
/>
<TextInputGroup
@ -219,7 +231,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={1}
value={props.properties.maxHeight.toString()}
onChange={(value) => props.onChange('maxHeight', Number(value))} />
onChange={(value) => { props.onChange('maxHeight', Number(value)); }}/>
</div>
</Category>
@ -239,7 +251,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={0}
value={(props.properties.margin.left ?? 0).toString()}
onChange={(value) => props.onChange('left', Number(value), PropertyType.Margin)} />
onChange={(value) => { props.onChange('left', Number(value), PropertyType.Margin); }}/>
<TextInputGroup
id={`${props.properties.id}-mb`}
labelText={Text({ textId: '@ContainerMarginBottom' })}
@ -249,7 +261,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={0}
value={(props.properties.margin.bottom ?? 0).toString()}
onChange={(value) => props.onChange('bottom', Number(value), PropertyType.Margin)} />
onChange={(value) => { props.onChange('bottom', Number(value), PropertyType.Margin); }}/>
<TextInputGroup
id={`${props.properties.id}-mt`}
labelText={Text({ textId: '@ContainerMarginTop' })}
@ -259,7 +271,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={0}
value={(props.properties.margin.top ?? 0).toString()}
onChange={(value) => props.onChange('top', Number(value), PropertyType.Margin)} />
onChange={(value) => { props.onChange('top', Number(value), PropertyType.Margin); }}/>
<TextInputGroup
id={`${props.properties.id}-mr`}
labelText={Text({ textId: '@ContainerMarginRight' })}
@ -269,7 +281,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
type='number'
min={0}
value={(props.properties.margin.right ?? 0).toString()}
onChange={(value) => props.onChange('right', Number(value), PropertyType.Margin)} />
onChange={(value) => { props.onChange('right', Number(value), PropertyType.Margin); }}/>
</div>
</Category>
@ -287,7 +299,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName='ml-auto mr-auto block'
type={ToggleType.Full}
checked={props.properties.isFlex}
onChange={(event) => props.onChange('isFlex', event.target.checked)}
onChange={(event) => { props.onChange('isFlex', event.target.checked); }}
/>
<ToggleButton
labelText={Text({ textId: '@ContainerAnchor' })}
@ -296,7 +308,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName='ml-auto mr-auto block'
type={ToggleType.Full}
checked={props.properties.isAnchor}
onChange={(event) => props.onChange('isAnchor', event.target.checked)} />
onChange={(event) => { props.onChange('isAnchor', event.target.checked); }}/>
</div>
</Category>
@ -326,7 +338,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
value: symbol.id
}))}
value={props.properties.linkedSymbolId ?? ''}
onChange={(event) => props.onChange('linkedSymbolId', event.target.value)} />
onChange={(event) => { props.onChange('linkedSymbolId', event.target.value); }}/>
</div>
</Category>
@ -346,38 +358,76 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
name='ShowSelfDimensions'
labelText={Text({ textId: '@ContainerShowDimension' })}
value={props.properties.dimensionOptions.selfDimensions.positions}
onChange={(key, value) => props.onChange(key, value, PropertyType.SelfDimension)}
onChange={(key, value) => { props.onChange(key, value, PropertyType.SelfDimension); }}
/>
<InputGroup
labelText={Text({ textId: '@Color' })}
labelText={Text({ textId: '@StyleStrokeColor' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.selfDimensions.color}
onChange={(e) => props.onChange('color', e.target.value, PropertyType.SelfDimension)}/>
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfDimension); }}/>
<TextInputGroup
id={`${props.properties.id}-selfDimensions-width`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='width'
labelClassName=''
inputClassName=''
type='number'
min={0}
value={(props.properties.dimensionOptions.selfDimensions.width ?? 0).toString()}
onChange={(value) => { props.onChange('width', Number(value), PropertyType.SelfDimension); }}/>
<TextInputGroup
id={`${props.properties.id}-selfDimensions-dasharray`}
labelText={Text({ textId: '@StyleStrokeDashArray' })}
inputKey='dashArray'
labelClassName=''
inputClassName=''
type='text'
value={props.properties.dimensionOptions.selfDimensions.dashArray ?? ''}
onChange={(value) => { props.onChange('dashArray', value, PropertyType.SelfDimension); }}/>
</div>
}
{
SHOW_SELF_MARGINS_DIMENSIONS &&
<div className='grid grid-cols-1 gap-2'>
<PositionCheckboxes
id='positionsSelfMarginsDimensions'
attribute='positions'
name='ShowSelfMarginsDimensions'
labelText={Text({ textId: '@ContainerShowMarginsDimension' })}
value={props.properties.dimensionOptions.selfMarginsDimensions.positions}
onChange={(key, value) => props.onChange(key, value, PropertyType.SelfMarginDimension)}
/>
<InputGroup
labelText={Text({ textId: '@Color' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.selfMarginsDimensions.color}
onChange={(e) => props.onChange('color', e.target.value, PropertyType.SelfMarginDimension)}/>
</div>
<div className='grid grid-cols-1 gap-2'>
<PositionCheckboxes
id='positionsSelfMarginsDimensions'
attribute='positions'
name='ShowSelfMarginsDimensions'
labelText={Text({ textId: '@ContainerShowMarginsDimension' })}
value={props.properties.dimensionOptions.selfMarginsDimensions.positions}
onChange={(key, value) => { props.onChange(key, value, PropertyType.SelfMarginDimension); }}
/>
<InputGroup
labelText={Text({ textId: '@StyleStrokeColor' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.selfMarginsDimensions.color}
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.SelfMarginDimension); }}/>
<TextInputGroup
id={`${props.properties.id}-selfMarginsDimensions-width`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='width'
labelClassName=''
inputClassName=''
type='number'
min={0}
value={(props.properties.dimensionOptions.selfMarginsDimensions.width ?? 0).toString()}
onChange={(value) => { props.onChange('width', Number(value), PropertyType.SelfMarginDimension); }}/>
<TextInputGroup
id={`${props.properties.id}-selfMarginsDimensions-dasharray`}
labelText={Text({ textId: '@StyleStrokeDashArray' })}
inputKey='dashArray'
labelClassName=''
inputClassName=''
type='text'
value={props.properties.dimensionOptions.selfMarginsDimensions.dashArray ?? ''}
onChange={(value) => { props.onChange('dashArray', value, PropertyType.SelfMarginDimension); }}/>
</div>
}
{
SHOW_CHILDREN_DIMENSIONS &&
@ -388,16 +438,35 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
name='ShowChildrenDimensions'
labelText={Text({ textId: '@ContainerShowChildrenDimension' })}
value={props.properties.dimensionOptions.childrenDimensions.positions}
onChange={(key, value) => props.onChange(key, value, PropertyType.ChildrenDimensions)}
onChange={(key, value) => { props.onChange(key, value, PropertyType.ChildrenDimensions); }}
/>
<InputGroup
labelText={Text({ textId: '@Color' })}
labelText={Text({ textId: '@StyleStrokeColor' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.childrenDimensions.color}
onChange={(e) => props.onChange('color', e.target.value, PropertyType.ChildrenDimensions)}/>
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.ChildrenDimensions); }}/>
<TextInputGroup
id={`${props.properties.id}-childrenDimensions-width`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='width'
labelClassName=''
inputClassName=''
type='number'
min={0}
value={(props.properties.dimensionOptions.childrenDimensions.width ?? 0).toString()}
onChange={(value) => { props.onChange('width', Number(value), PropertyType.ChildrenDimensions); }}/>
<TextInputGroup
id={`${props.properties.id}-childrenDimensions-dasharray`}
labelText={Text({ textId: '@StyleStrokeDashArray' })}
inputKey='dashArray'
labelClassName=''
inputClassName=''
type='text'
value={props.properties.dimensionOptions.childrenDimensions.dashArray ?? ''}
onChange={(value) => { props.onChange('dashArray', value, PropertyType.ChildrenDimensions); }}/>
</div>
}
{
@ -409,7 +478,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
name='MarkPosition'
value={props.properties.dimensionOptions.markPosition}
labelText={Text({ textId: '@ContainerMarkPosition' })}
onChange={(key, value) => props.onChange(key, value, PropertyType.DimensionOptions)}
onChange={(key, value) => { props.onChange(key, value, PropertyType.DimensionOptions); }}
/>
</div>
<div className='grid grid-cols-1 gap-2'>
@ -419,23 +488,42 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
name='ShowDimensionWithMarks'
labelText={Text({ textId: '@ContainerShowDimensionWithMarks' })}
value={props.properties.dimensionOptions.dimensionWithMarks.positions}
onChange={(key, value) => props.onChange(key, value, PropertyType.DimensionWithMarks)}
onChange={(key, value) => { props.onChange(key, value, PropertyType.DimensionWithMarks); }}
/>
<InputGroup
labelText={Text({ textId: '@Color' })}
labelText={Text({ textId: '@StyleStrokeColor' })}
inputKey='color'
labelClassName=''
inputClassName=''
type='color'
value={props.properties.dimensionOptions.dimensionWithMarks.color}
onChange={(e) => props.onChange('color', e.target.value, PropertyType.DimensionWithMarks)}/>
onChange={(e) => { props.onChange('color', e.target.value, PropertyType.DimensionWithMarks); }}/>
<TextInputGroup
id={`${props.properties.id}-dimensionWithMarks-width`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='width'
labelClassName=''
inputClassName=''
type='number'
min={0}
value={(props.properties.dimensionOptions.dimensionWithMarks.width ?? 0).toString()}
onChange={(value) => { props.onChange('width', Number(value), PropertyType.DimensionWithMarks); }}/>
<TextInputGroup
id={`${props.properties.id}-dimensionWithMarks-dasharray`}
labelText={Text({ textId: '@StyleStrokeDashArray' })}
inputKey='dashArray'
labelClassName=''
inputClassName=''
type='text'
value={props.properties.dimensionOptions.dimensionWithMarks.dashArray ?? ''}
onChange={(value) => { props.onChange('dashArray', value, PropertyType.DimensionWithMarks); }}/>
</div>
</>
}
</div>
</Category>
{ props.properties.style !== undefined &&
{props.properties.style !== undefined &&
<Category category={{
Type: 'Style',
DisplayedText: Text({ textId: '@ContainerStyle' })
@ -451,7 +539,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName='col-span-3'
type='string'
value={props.properties.style.stroke ?? 'black'}
onChange={(value) => props.onChange('stroke', value, PropertyType.Style)}
onChange={(value) => { props.onChange('stroke', value, PropertyType.Style); }}
/>
<InputGroup
labelKey={`${props.properties.id}-strokeOpacity`}
@ -464,7 +552,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
max={1}
step={0.01}
value={(props.properties.style.strokeOpacity ?? 1).toString()}
onChange={(event) => props.onChange('strokeOpacity', Number(event.target.value), PropertyType.Style)}
onChange={(event) => { props.onChange('strokeOpacity', Number(event.target.value), PropertyType.Style); }}
/>
<TextInputGroup
id={`${props.properties.id}-strokeWidth`}
@ -474,7 +562,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName='col-span-3'
type='number'
value={(props.properties.style.strokeWidth ?? 1).toString()}
onChange={(value) => props.onChange('strokeWidth', Number(value), PropertyType.Style)}
onChange={(value) => { props.onChange('strokeWidth', Number(value), PropertyType.Style); }}
/>
<TextInputGroup
id={`${props.properties.id}-fill`}
@ -484,7 +572,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName='col-span-3'
type='string'
value={props.properties.style.fill ?? 'black'}
onChange={(value) => props.onChange('fill', value, PropertyType.Style)}
onChange={(value) => { props.onChange('fill', value, PropertyType.Style); }}
/>
<InputGroup
labelKey={`${props.properties.id}-fillOpacity`}
@ -497,7 +585,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
max={1}
step={0.01}
value={(props.properties.style.fillOpacity ?? 1).toString()}
onChange={(event) => props.onChange('fillOpacity', Number(event.target.value), PropertyType.Style)}
onChange={(event) => { props.onChange('fillOpacity', Number(event.target.value), PropertyType.Style); }}
/>
</div>
</Category>

View file

@ -4,11 +4,12 @@ import { expect, describe, it, vi } from 'vitest';
import { PositionReference } from '../../Enums/PositionReference';
import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { Orientation } from '../../Enums/Orientation';
import { Properties } from './ContainerProperties';
import { ContainerProperties } from './ContainerProperties';
import { DEFAULT_DIMENSION_OPTION } from '../../utils/default';
describe.concurrent('Properties', () => {
it('No properties', () => {
render(<Properties
render(<ContainerProperties
properties={undefined}
onChange={() => {}}
symbols={new Map()}
@ -43,23 +44,11 @@ describe.concurrent('Properties', () => {
warning: '',
hideChildrenInTreeview: false,
dimensionOptions: {
childrenDimensions: {
color: '#000000',
positions: []
},
selfDimensions: {
color: '#000000',
positions: []
},
selfMarginsDimensions: {
color: '#000000',
positions: []
},
childrenDimensions: DEFAULT_DIMENSION_OPTION,
selfDimensions: DEFAULT_DIMENSION_OPTION,
selfMarginsDimensions: DEFAULT_DIMENSION_OPTION,
markPosition: [],
dimensionWithMarks: {
color: '#000000',
positions: []
}
dimensionWithMarks: DEFAULT_DIMENSION_OPTION
}
};
@ -67,7 +56,7 @@ describe.concurrent('Properties', () => {
(prop as any)[key] = value;
});
const { container, rerender } = render(<Properties
const { container, rerender } = render(<ContainerProperties
properties={prop}
onChange={handleChange}
symbols={new Map()}
@ -108,7 +97,7 @@ describe.concurrent('Properties', () => {
expect(prop.parentId).toBe('parentId');
expect(prop.x).toBe(2);
expect(prop.y).toBe(2);
rerender(<Properties
rerender(<ContainerProperties
properties={Object.assign({}, prop)}
onChange={handleChange}
symbols={new Map()}

View file

@ -10,7 +10,7 @@ interface IPropertiesProps {
onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
}
export function Properties(props: IPropertiesProps): JSX.Element {
export function ContainerProperties(props: IPropertiesProps): JSX.Element {
if (props.properties === undefined) {
return <div></div>;
}

View file

@ -38,6 +38,30 @@ export function SelectContainer(
return history;
}
/**
* Deselect a container
* @returns New history
*/
export function DeselectContainer(
containerId: string,
fullHistory: IHistoryState[],
historyCurrentStep: number
): IHistoryState[] {
const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1];
history.push({
lastAction: `Deselect ${containerId}`,
mainContainer: current.mainContainer,
containers: structuredClone(current.containers),
selectedContainerId: 'undefined',
typeCounters: Object.assign({}, current.typeCounters),
symbols: structuredClone(current.symbols),
selectedSymbolId: current.selectedSymbolId
});
return history;
}
/**
* Delete a container
* @param containerId containerId of the container to delete

View file

@ -53,6 +53,7 @@ export function SaveEditorAsSVG(): void {
svg.replaceChildren(...mainSvg);
// remove the selector
// TODO: Fix this with SelectorMode != Nothing or with some html magic
const group = svg.children[svg.children.length - 1];
group.removeChild(group.children[group.children.length - 1]);
if (SHOW_SELECTOR_TEXT) {

View file

@ -1,14 +1,17 @@
import * as React from 'react';
import { useState } from 'react';
import useSize from '@react-hook/size';
import { FixedSizeList as List } from 'react-window';
import { Properties } from '../ContainerProperties/ContainerProperties';
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
import { ContainerProperties } from '../ContainerProperties/ContainerProperties';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { PropertyType } from '../../Enums/PropertyType';
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar';
import { Text } from '../Text/Text';
interface IElementsListProps {
interface IElementsSideBarProps {
containers: Map<string, IContainerModel>
mainContainer: IContainerModel
symbols: Map<string, ISymbolModel>
@ -20,6 +23,8 @@ interface IElementsListProps {
) => void
selectContainer: (containerId: string) => void
addContainer: (index: number, type: string, parent: string) => void
isExpanded: boolean
onExpandChange: () => void
}
function RemoveBorderClasses(target: HTMLButtonElement, exception: string = ''): void {
@ -119,10 +124,11 @@ function HandleOnDrop(
}
}
export function ElementsList(props: IElementsListProps): JSX.Element {
export function ElementsSideBar(props: IElementsSideBarProps): JSX.Element {
// States
const divRef = React.useRef<HTMLDivElement>(null);
const [, height] = useSize(divRef);
const [,height] = useSize(divRef);
const [showProperties, setShowProperties] = useState(props.isExpanded);
// Render
const it = MakeRecursionDFSIterator(props.mainContainer, props.containers, 0, [0, 0], true);
@ -160,10 +166,21 @@ export function ElementsList(props: IElementsListProps): JSX.Element {
}
return (
<div className='h-full flex flex-col'>
<div ref={divRef} className='h-1/2'>
<div className='flex flex-row h-full w-full' >
{showProperties &&
<div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
<ContainerProperties
properties={props.selectedContainer?.properties}
symbols={props.symbols}
onChange={props.onPropertyChange}
/>
</div>
}
<div className='flex w-64' ref={divRef}>
<div className='w-6'>
<ToggleSideBar title={Text({ textId: '@Properties' })} checked={showProperties} onChange={(newValue) => { setShowProperties(newValue); props.onExpandChange(); }} />
</div>
<List
className="List divide-y divide-black overflow-y-auto"
itemCount={containers.length}
itemSize={35}
height={height}
@ -172,13 +189,6 @@ export function ElementsList(props: IElementsListProps): JSX.Element {
{Row}
</List>
</div>
<div className='grow overflow-auto'>
<Properties
properties={props.selectedContainer?.properties}
symbols={props.symbols}
onChange={props.onPropertyChange}
/>
</div>
</div>
);
}

View file

@ -1,100 +0,0 @@
import * as React from 'react';
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN } from '../../../utils/default';
import { GetAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
import { TransformX } from '../../../utils/svg';
import { Dimension } from './Dimension';
interface IDimensionLayerProps {
containers: Map<string, IContainerModel>
roots: IContainerModel | IContainerModel[] | null
scale?: number
}
function GetDimensionsNodes(
containers: Map<string, IContainerModel>,
root: IContainerModel,
scale: number
): React.ReactNode[] {
const it = MakeBFSIterator(root, containers);
const dimensions: React.ReactNode[] = [];
let currentDepth = 0;
let min = Infinity;
let max = -Infinity;
let lastY = 0;
for (const { container, depth } of it) {
if (currentDepth !== depth) {
AddNewDimension(currentDepth, min, max, lastY, scale, '#000000', dimensions);
currentDepth = depth;
min = Infinity;
max = -Infinity;
}
const absoluteX = GetAbsolutePosition(containers, container)[0];
const x = TransformX(absoluteX, container.properties.width, container.properties.positionReference);
lastY = container.properties.y + container.properties.height;
if (x < min) {
min = x;
}
if (x > max) {
max = x;
}
}
AddNewDimension(currentDepth, min, max, lastY, scale, '#000000', dimensions);
return dimensions;
}
/**
* A layer containing all dimension
* @param props
* @returns
*/
export function DepthDimensionLayer(props: IDimensionLayerProps): JSX.Element {
let dimensions: React.ReactNode[] = [];
const scale = props.scale ?? 1;
if (Array.isArray(props.roots)) {
props.roots.forEach(child => {
dimensions.concat(GetDimensionsNodes(props.containers, child, scale));
});
} else if (props.roots !== null) {
dimensions = GetDimensionsNodes(props.containers, props.roots, scale);
}
return (
<g>
{dimensions}
</g>
);
}
function AddNewDimension(currentDepth: number, min: number, max: number, lastY: number, scale: number, color: string, dimensions: React.ReactNode[]): void {
const id = `dim-depth-${currentDepth}`;
const xStart = min;
const xEnd = max;
const y = lastY + (DIMENSION_MARGIN * (currentDepth + 1)) / scale;
const width = xEnd - xStart;
const text = width
.toFixed(0)
.toString();
if (width === 0) {
return;
}
dimensions.push(
<Dimension
key={id}
id={id}
xStart={xStart}
yStart={y}
xEnd={xEnd}
yEnd={y}
text={text}
scale={scale}
color={color}
/>
);
}

View file

@ -1,6 +1,9 @@
import * as React from 'react';
import { type IDimensionOptions } from '../../../Interfaces/IDimensionOptions';
import { NOTCHES_LENGTH } from '../../../utils/default';
export type IDimensionStyle = Omit<IDimensionOptions, 'positions'>;
interface IDimensionProps {
id: string
xStart: number
@ -8,7 +11,7 @@ interface IDimensionProps {
xEnd: number
yEnd: number
text: string
color: string
style: IDimensionStyle
scale?: number
}
@ -28,8 +31,9 @@ function ApplyParametric(x0: number, t: number, vx: number): number {
export function Dimension(props: IDimensionProps): JSX.Element {
const scale = props.scale ?? 1;
const style: React.CSSProperties = {
stroke: props.color,
strokeWidth: 2 / scale
stroke: props.style.color,
strokeWidth: (props.style.width ?? 2) / scale,
strokeDasharray: props.style.dashArray
};
/// We need to find the points of the notches
@ -79,9 +83,11 @@ export function Dimension(props: IDimensionProps): JSX.Element {
x2={endBottomX}
y2={endBottomY}
style={style}/>
<text textAnchor={'middle'} alignmentBaseline={'central'}
<text
x={textX}
y={textY}
textAnchor={'middle'}
alignmentBaseline={'central'}
style={{
transform: `rotate(${rotation}turn) scale(${1 / scale})`,
transformOrigin: `${textX}px ${textY}px`

View file

@ -1,7 +1,6 @@
import * as React from 'react';
import { Orientation } from '../../../Enums/Orientation';
import { Position } from '../../../Enums/Position';
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import {
DIMENSION_MARGIN,
SHOW_BORROWER_DIMENSIONS,
@ -11,10 +10,13 @@ import {
} from '../../../utils/default';
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
import { TransformX, TransformY } from '../../../utils/svg';
import { Dimension } from './Dimension';
import { Dimension, type IDimensionStyle } from './Dimension';
import { type IContainerModel } from '../../../Interfaces/IContainerModel';
import { type ISymbolModel } from '../../../Interfaces/ISymbolModel';
interface IDimensionLayerProps {
containers: Map<string, IContainerModel>
symbols: Map<string, ISymbolModel>
root: IContainerModel
scale: number
}
@ -58,7 +60,7 @@ function ActionByPosition(
* @param param0 Object with the root container and the scale of the svg
* @returns A list of dimensions
*/
function Dimensions({ containers, root, scale }: IDimensionLayerProps): React.ReactNode[] {
function Dimensions({ containers, symbols, root, scale }: IDimensionLayerProps): React.ReactNode[] {
const it = MakeRecursionDFSIterator(root, containers, 0, [0, 0]);
const dimensions: React.ReactNode[] = [];
const topDim = root.properties.y;
@ -87,8 +89,8 @@ function Dimensions({ containers, root, scale }: IDimensionLayerProps): React.Re
container,
currentTransform,
dimensions,
scale,
container.properties.dimensionOptions.selfDimensions.color]
scale
]
);
}
@ -102,8 +104,8 @@ function Dimensions({ containers, root, scale }: IDimensionLayerProps): React.Re
container,
currentTransform,
dimensions,
scale,
container.properties.dimensionOptions.selfMarginsDimensions.color]
scale
]
);
}
@ -111,6 +113,7 @@ function Dimensions({ containers, root, scale }: IDimensionLayerProps): React.Re
ActionByPosition(
dimMapped,
container.properties.dimensionOptions.dimensionWithMarks.positions,
AddHorizontalBorrowerDimension,
AddVerticalBorrowerDimension,
[
@ -119,8 +122,8 @@ function Dimensions({ containers, root, scale }: IDimensionLayerProps): React.Re
depth,
currentTransform,
dimensions,
scale,
container.properties.dimensionOptions.dimensionWithMarks.color]
scale
]
);
}
@ -135,8 +138,21 @@ function Dimensions({ containers, root, scale }: IDimensionLayerProps): React.Re
container,
currentTransform,
dimensions,
scale,
container.properties.dimensionOptions.childrenDimensions.color]
scale
]
);
}
}
let startDepthSymbols: number = 0;
for (const symbol of symbols) {
if (symbol[1].showDimension) {
startDepthSymbols++;
AddHorizontalSymbolDimension(
symbol[1],
dimensions,
scale,
startDepthSymbols
);
}
}
@ -144,6 +160,40 @@ function Dimensions({ containers, root, scale }: IDimensionLayerProps): React.Re
return dimensions;
}
function AddHorizontalSymbolDimension(
symbol: ISymbolModel,
dimensions: React.ReactNode[],
scale: number,
depth: number
): void {
const width = symbol.x + (symbol.width / 2);
if (width != null && width > 0) {
const id = `dim-y-margin-left${symbol.width.toFixed(0)}-${symbol.id}`;
const offset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const text = width
.toFixed(0)
.toString();
// TODO: Put this in default.ts
const defaultDimensionSymbolStyle: IDimensionStyle = {
color: 'black'
};
dimensions.push(
<Dimension
key={id}
id={id}
xStart={0}
yStart={-offset}
xEnd={width}
yEnd={-offset}
text={text}
scale={scale}
style={defaultDimensionSymbolStyle}/>
);
}
}
/**
* A layer containing all dimension
* @param props
@ -152,7 +202,7 @@ function Dimensions({ containers, root, scale }: IDimensionLayerProps): React.Re
export function DimensionLayer(props: IDimensionLayerProps): JSX.Element {
return (
<g>
{ Dimensions(props) }
{Dimensions(props)}
</g>
);
}
@ -165,10 +215,10 @@ function AddHorizontalChildrenDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const childrenId = `dim-y${yDim.toFixed(0)}-children-${container.properties.id}`;
const style = container.properties.dimensionOptions.childrenDimensions;
const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId);
@ -218,7 +268,7 @@ function AddHorizontalChildrenDimension(
yEnd={yDim}
text={textChildren}
scale={scale}
color={color}/>);
style={style}/>);
}
function AddVerticalChildrenDimension(
@ -228,11 +278,10 @@ function AddVerticalChildrenDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const childrenId = `dim-x${xDim.toFixed(0)}-children-${container.properties.id}`;
const style = container.properties.dimensionOptions.childrenDimensions;
const lastChildId = container.children[container.children.length - 1];
const lastChild = FindContainerById(containers, lastChildId);
@ -287,7 +336,7 @@ function AddVerticalChildrenDimension(
yEnd={yChildrenEnd + offset}
text={textChildren}
scale={scale}
color={color}
style={style}
/>);
}
@ -298,9 +347,9 @@ function AddHorizontalBorrowerDimension(
depth: number,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.dimensionWithMarks;
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const marks = []; // list of vertical lines for the dimension
for (const {
@ -345,7 +394,7 @@ function AddHorizontalBorrowerDimension(
yEnd={yDim}
text={value.toFixed(0)}
scale={scale}
color={color}/>);
style={style}/>);
count++;
}
}
@ -358,9 +407,9 @@ function AddVerticalBorrowerDimension(
depth: number,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.dimensionWithMarks;
const it = MakeRecursionDFSIterator(container, containers, depth, currentTransform);
const marks = []; // list of vertical lines for the dimension
for (const {
@ -410,7 +459,7 @@ function AddVerticalBorrowerDimension(
yEnd={next}
text={value.toFixed(0)}
scale={scale}
color={color}/>);
style={style}/>);
count++;
}
}
@ -421,9 +470,9 @@ function AddVerticalSelfDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.selfDimensions;
const height = container.properties.height;
const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`;
let yStart = container.properties.y + currentTransform[1] + height;
@ -446,19 +495,18 @@ function AddVerticalSelfDimension(
yEnd={yEnd}
text={textVert}
scale={scale}
color={color}/>
style={style}/>
);
}
function AddHorizontalSelfDimension(
yDim: number,
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.selfDimensions;
const width = container.properties.width;
const id = `dim-y${yDim.toFixed(0)}-${container.properties.id}`;
const xStart = container.properties.x + currentTransform[0];
@ -476,19 +524,18 @@ function AddHorizontalSelfDimension(
yEnd={yDim}
text={text}
scale={scale}
color={color}/>
style={style}/>
);
}
function AddHorizontalSelfMarginsDimension(
yDim: number,
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.selfMarginsDimensions;
const left = container.properties.margin.left;
if (left != null) {
const id = `dim-y-margin-left${yDim.toFixed(0)}-${container.properties.id}`;
@ -507,7 +554,7 @@ function AddHorizontalSelfMarginsDimension(
yEnd={yDim}
text={text}
scale={scale}
color={color}/>
style={style}/>
);
}
@ -529,7 +576,7 @@ function AddHorizontalSelfMarginsDimension(
yEnd={yDim}
text={text}
scale={scale}
color={color}/>
style={style}/>
);
}
}
@ -540,9 +587,9 @@ function AddVerticalSelfMarginDimension(
container: IContainerModel,
currentTransform: [number, number],
dimensions: React.ReactNode[],
scale: number,
color: string
scale: number
): void {
const style = container.properties.dimensionOptions.selfMarginsDimensions;
const top = container.properties.margin.top;
if (top != null) {
const idVert = `dim-x-margin-top${xDim.toFixed(0)}-${container.properties.id}`;
@ -566,7 +613,7 @@ function AddVerticalSelfMarginDimension(
yEnd={yEnd}
text={textVert}
scale={scale}
color={color}/>
style={style}/>
);
}
const bottom = container.properties.margin.bottom;
@ -592,7 +639,7 @@ function AddVerticalSelfMarginDimension(
yEnd={yEnd}
text={textVert}
scale={scale}
color={color}/>
style={style}/>
);
}
}

View file

@ -1,38 +1,18 @@
import './Selector.scss';
import '../Selector.scss';
import * as React from 'react';
import { IContainerModel } from '../../../../Interfaces/IContainerModel';
import { SHOW_SELECTOR_TEXT } from '../../../../utils/default';
import { GetAbsolutePosition } from '../../../../utils/itertools';
import { RemoveMargin } from '../../../../utils/svg';
interface ISelectorProps {
containers: Map<string, IContainerModel>
selected?: IContainerModel
scale?: number
text: string
x: number
y: number
width: number
height: number
scale: number
style?: React.CSSProperties
}
export function Selector(props: ISelectorProps): JSX.Element {
if (props.selected === undefined || props.selected === null) {
return (
<rect visibility={'hidden'}>
</rect>
);
}
const scale = (props.scale ?? 1);
let [x, y] = GetAbsolutePosition(props.containers, props.selected);
let [width, height] = [
props.selected.properties.width,
props.selected.properties.height
];
({ x, y, width, height } = RemoveMargin(x, y, width, height,
props.selected.properties.margin.left,
props.selected.properties.margin.bottom,
props.selected.properties.margin.top,
props.selected.properties.margin.right
));
export function Selector({ text, x, y, width, height, scale, style: overrideStyle }: ISelectorProps): JSX.Element {
const xText = x + width / 2;
const yText = y + height / 2;
@ -43,7 +23,8 @@ export function Selector(props: ISelectorProps): JSX.Element {
transitionProperty: 'all',
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
transitionDuration: '150ms',
animation: 'fadein 750ms ease-in alternate infinite'
animation: 'fadein 750ms ease-in alternate infinite',
...overrideStyle
};
return (
@ -65,7 +46,7 @@ export function Selector(props: ISelectorProps): JSX.Element {
transformBox: 'fill-box'
}}
>
{props.selected.properties.displayedText}
{ text }
</text>
: null}
</>

View file

@ -0,0 +1,46 @@
import '../Selector.scss';
import * as React from 'react';
import { type IContainerModel } from '../../../../Interfaces/IContainerModel';
import { GetAbsolutePosition } from '../../../../utils/itertools';
import { RemoveMargin } from '../../../../utils/svg';
import { Selector } from '../Selector/Selector';
interface ISelectorContainerProps {
containers: Map<string, IContainerModel>
selected?: IContainerModel
scale?: number
}
export function SelectorContainer(props: ISelectorContainerProps): JSX.Element {
if (props.selected === undefined || props.selected === null) {
return (
<rect visibility={'hidden'}>
</rect>
);
}
const scale = (props.scale ?? 1);
let [x, y] = GetAbsolutePosition(props.containers, props.selected);
let [width, height] = [
props.selected.properties.width,
props.selected.properties.height
];
({ x, y, width, height } = RemoveMargin(x, y, width, height,
props.selected.properties.margin.left,
props.selected.properties.margin.bottom,
props.selected.properties.margin.top,
props.selected.properties.margin.right
));
return (
<Selector
text={props.selected.properties.displayedText}
x={x}
y={y}
width={width}
height={height}
scale={scale}
/>
);
}

View file

@ -0,0 +1,47 @@
import '../Selector.scss';
import * as React from 'react';
import { SYMBOL_MARGIN } from '../../../../utils/default';
import { type ISymbolModel } from '../../../../Interfaces/ISymbolModel';
import { Selector } from '../Selector/Selector';
interface ISelectorSymbolProps {
symbols: Map<string, ISymbolModel>
selected?: ISymbolModel
scale?: number
}
export function SelectorSymbol(props: ISelectorSymbolProps): JSX.Element {
if (props.selected === undefined || props.selected === null) {
return (
<rect visibility={'hidden'}>
</rect>
);
}
const scale = (props.scale ?? 1);
const [width, height] = [
props.selected.width / scale,
props.selected.height / scale
];
const [x, y] = [
props.selected.x + props.selected.width / 2,
-SYMBOL_MARGIN - height];
const style: React.CSSProperties = {
transform: 'translateX(-50%)',
transformBox: 'fill-box'
};
return (
<Selector
text={props.selected.displayedText}
x={x}
y={y}
width={width}
height={height}
scale={scale}
style={style}
/>
);
}

View file

@ -1,13 +1,13 @@
import * as React from 'react';
import { ReactSVGPanZoom, Tool, TOOL_PAN, Value } from 'react-svg-pan-zoom';
import { ReactSVGPanZoom, type Tool, TOOL_PAN, type Value } from 'react-svg-pan-zoom';
import { Container } from './Elements/Container';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { Selector } from './Elements/Selector/Selector';
import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
import { MAX_FRAMERATE, SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
import { SelectorContainer } from './Elements/SelectorContainer/SelectorContainer';
import { MAX_FRAMERATE } from '../../utils/default';
import { SymbolLayer } from './Elements/SymbolLayer';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { DimensionLayer } from './Elements/DimensionLayer';
import { SelectorSymbol } from './Elements/SelectorSymbol/SelectorSymbol';
interface ISVGProps {
className?: string
@ -17,11 +17,19 @@ interface ISVGProps {
height: number
containers: Map<string, IContainerModel>
children: IContainerModel
selected?: IContainerModel
selectedContainer?: IContainerModel
symbols: Map<string, ISymbolModel>
selectedSymbol?: ISymbolModel
selectorMode: SelectorMode
selectContainer: (containerId: string) => void
}
export enum SelectorMode {
Nothing,
Containers,
Symbols
}
export const ID = 'svg';
export function SVG(props: ISVGProps): JSX.Element {
@ -49,8 +57,7 @@ export function SVG(props: ISVGProps): JSX.Element {
xmlns
};
let children: React.ReactNode | React.ReactNode[] = [];
children = <Container
const children: React.ReactNode | React.ReactNode[] = <Container
key={`container-${props.children.properties.id}`}
containers={props.containers}
model={props.children}
@ -59,6 +66,25 @@ export function SVG(props: ISVGProps): JSX.Element {
selectContainer={props.selectContainer}
/>;
function Selector(): JSX.Element {
switch (props.selectorMode) {
case SelectorMode.Containers:
return <SelectorContainer
containers={props.containers}
scale={scale}
selected={props.selectedContainer}
/>;
case SelectorMode.Symbols:
return <SelectorSymbol
symbols={props.symbols}
scale={scale}
selected={props.selectedSymbol}
/>;
default:
return <></>;
}
}
return (
<div id={ID} className={props.className}>
<ReactSVGPanZoom
@ -94,12 +120,9 @@ export function SVG(props: ISVGProps): JSX.Element {
>
<svg {...properties}>
{children}
{SHOW_DIMENSIONS_PER_DEPTH
? <DepthDimensionLayer containers={props.containers} scale={scale} roots={props.children} />
: null}
<DimensionLayer containers={props.containers} scale={scale} root={props.children} />
<DimensionLayer containers={props.containers} symbols={props.symbols} scale={scale} root={props.children} />
<SymbolLayer scale={scale} symbols={props.symbols} />
<Selector containers={props.containers} scale={scale} selected={props.selected} /> {/* leave this at the end so it can be removed during the svg export */}
<Selector />
</svg>
</ReactSVGPanZoom>
</div>

View file

@ -0,0 +1,4 @@
.text-vertical{
text-align: right;
writing-mode: vertical-rl;
}

View file

@ -0,0 +1,23 @@
import * as React from 'react';
import './ToggleSideBar.scss';
interface IToggleSidebarProps {
title: string
checked: boolean
onChange: (newValue: boolean) => void
}
export function ToggleSideBar({ title, checked, onChange }: IToggleSidebarProps): JSX.Element {
return (
<div className={`${(checked ? 'bg-slate-400 hover:bg-slate-500' : 'bg-slate-300 hover:bg-slate-400')}`}>
<button
className={'w-full py-2'}
type='button'
onClick={() => onChange(!checked)}
>
<p className='text-vertical'>{title}
</p>
</button>
</div>
);
}

View file

@ -4,6 +4,8 @@ import { RestoreX, TransformX } from '../../utils/svg';
import { InputGroup } from '../InputGroup/InputGroup';
import { TextInputGroup } from '../InputGroup/TextInputGroup';
import { Text } from '../Text/Text';
import { PropertyType } from '../../Enums/PropertyType';
import { ToggleButton } from '../ToggleButton/ToggleButton';
interface ISymbolFormProps {
symbol: ISymbolModel
@ -60,6 +62,13 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
min={0}
value={props.symbol.width.toString()}
onChange={(value) => props.onChange('width', Number(value))} />
<ToggleButton
labelText={Text({ textId: '@ShowDimension' })}
inputKey='showDimension'
labelClassName=''
inputClassName=''
checked={props.symbol.showDimension}
onChange={(e) => props.onChange('showDimension', e.target.checked)}/>
</div>
);
}

View file

@ -14,7 +14,7 @@ export function SymbolProperties(props: ISymbolPropertiesProps): JSX.Element {
}
return (
<div className='h-full p-3 bg-slate-200 overflow-y-auto'>
<div className='h-full p-3 bg-slate-200 overflow-y-auto '>
<SymbolForm
symbol={props.symbol}
symbols={props.symbols}

View file

@ -1,28 +1,62 @@
import * as React from 'react';
import useSize from '@react-hook/size';
import { FixedSizeList as List } from 'react-window';
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
import { SymbolProperties } from '../SymbolProperties/SymbolProperties';
import { ToggleSideBar } from '../Sidebar/ToggleSideBar/ToggleSideBar';
import { Text } from '../Text/Text';
import { useState } from 'react';
interface ISymbolsSidebarProps {
selectedSymbolId: string
symbols: Map<string, ISymbolModel>
onPropertyChange: (key: string, value: string | number | boolean) => void
selectSymbol: (symbolId: string) => void
isExpanded: boolean
onExpandChange: (isExpanded: boolean) => void
}
export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
// States
const divRef = React.useRef<HTMLDivElement>(null);
const height = useSize(divRef)[1];
const [showProperties, setShowProperties] = useState(props.isExpanded);
// Render
const symbols = [...props.symbols.values()];
const selectedSymbol = props.symbols.get(props.selectedSymbolId);
return (
<div className='flex flex-row h-full w-full'>
{showProperties && <div className='flex flex-1 flex-col w-64 border-r-2 border-slate-400'>
{(selectedSymbol == null) && <h1 className={'p-4'}>{Text({ textId: '@NoSymbolSelected' })}</h1>}
<SymbolProperties
symbol={selectedSymbol}
symbols={props.symbols}
onChange={props.onPropertyChange}
/>
</div>}
<div className={'flex w-64'} ref={divRef}>
<div className='w-6'>
<ToggleSideBar title={Text({ textId: '@Properties' })} checked={showProperties} onChange={(newValue) => { setShowProperties(newValue); props.onExpandChange(newValue); }} />
</div>
<List
itemCount={symbols.length}
itemSize={35}
height={height}
width={'100%'}
>
{Row}
</List>
</div>
</div>
);
function Row({ index, style }: { index: number, style: React.CSSProperties }): JSX.Element {
const symbol = symbols[index];
const key = symbol.id;
const text = symbol.displayedText;
const selectedClass: string = props.selectedSymbolId !== '' &&
props.selectedSymbolId === symbol.id
props.selectedSymbolId === symbol.id
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
: 'bg-slate-300/60 hover:bg-slate-300';
@ -33,33 +67,10 @@ export function SymbolsSidebar(props: ISymbolsSidebarProps): JSX.Element {
id={key}
key={key}
style={style}
onClick={() => props.selectSymbol(key)}
onClick={() => { props.selectSymbol(key); }}
>
{text}
</button>
);
}
return (
<div className='h-full'>
<div ref={divRef} className='h-1/2 text-gray-800'>
<List
className='List divide-y divide-black'
itemCount={symbols.length}
itemSize={35}
height={height}
width={'100%'}
>
{Row}
</List>
</div>
<div>
<SymbolProperties
symbol={props.symbols.get(props.selectedSymbolId)}
symbols={props.symbols}
onChange={props.onPropertyChange}
/>
</div>
</div>
);
}

View file

@ -1,20 +1,20 @@
import * as React from 'react';
import { ElementsList } from '../ElementsList/ElementsList';
import { ElementsSideBar } from '../ElementsList/ElementsSideBar';
import { History } from '../History/History';
import { Bar } from '../Bar/Bar';
import { Bar, BAR_WIDTH } from '../Bar/Bar';
import { Symbols } from '../Symbols/Symbols';
import { SymbolsSidebar } from '../SymbolsList/SymbolsList';
import { PropertyType } from '../../Enums/PropertyType';
import { SymbolsSidebar } from '../SymbolsList/SymbolsSidebar';
import { type PropertyType } from '../../Enums/PropertyType';
import { Messages } from '../Messages/Messages';
import { Sidebar } from '../Sidebar/Sidebar';
import { Components } from '../Components/Components';
import { Viewer } from '../Viewer/Viewer';
import { Settings } from '../Settings/Settings';
import { IMessage } from '../../Interfaces/IMessage';
import { type IMessage } from '../../Interfaces/IMessage';
import { DISABLE_API } from '../../utils/default';
import { UseWorker, UseAsync } from './UseWorker';
import { FindContainerById } from '../../utils/itertools';
import { IEditorState } from '../../Interfaces/IEditorState';
import { type IEditorState } from '../../Interfaces/IEditorState';
import { GetCurrentHistoryState } from '../Editor/Editor';
import { Text } from '../Text/Text';
import { IReplaceContainer } from '../../Interfaces/IReplaceContainer';
@ -42,7 +42,9 @@ export interface IUIProps {
export enum SidebarType {
None,
Components,
ComponentsExpanded,
Symbols,
SymbolsExpanded,
History,
Messages,
Settings
@ -84,7 +86,7 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
);
}
// Please use setOrToggleSidebar rather than setSelectedSidebar so we can close the sidebar
// Please use setOrToggleSidebar rather than setSelectedSidebar, so we can close the sidebar
const setOrToggleSidebar = UseSetOrToggleSidebar(selectedSidebar, setSelectedSidebar);
let leftSidebarTitle = '';
@ -99,6 +101,7 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
}
const selectedContainer = FindContainerById(current.containers, current.selectedContainerId);
const selectedSymbol = current.symbols.get(current.selectedSymbolId);
switch (selectedSidebar) {
case SidebarType.Components:
@ -112,7 +115,7 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
replaceContainer={replaceContainer}
setReplaceContainer={setReplaceContainer}/>;
rightSidebarTitle = Text({ textId: '@Elements' });
rightChildren = <ElementsList
rightChildren = <ElementsSideBar
containers={current.containers}
mainContainer={mainContainer}
symbols={current.symbols}
@ -120,9 +123,32 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
onPropertyChange={methods.onPropertyChange}
selectContainer={methods.selectContainer}
addContainer={methods.addContainerAt}
isExpanded ={false}
onExpandChange={() => { setOrToggleSidebar(SidebarType.ComponentsExpanded); } }
/>;
break;
case SidebarType.ComponentsExpanded:
leftSidebarTitle = Text({ textId: '@Components' });
leftChildren = <Components
selectedContainer={selectedContainer}
componentOptions={configuration.AvailableContainers}
categories={configuration.Categories}
buttonOnClick={methods.addOrReplaceContainer}
replaceContainer={replaceContainer}
setReplaceContainer={setReplaceContainer}/>;
rightSidebarTitle = Text({ textId: '@Elements' });
rightChildren = <ElementsSideBar
containers={current.containers}
mainContainer={mainContainer}
symbols={current.symbols}
selectedContainer={selectedContainer}
onPropertyChange={methods.onPropertyChange}
selectContainer={methods.selectContainer}
addContainer={methods.addContainerAt}
isExpanded ={true}
onExpandChange={() => { setOrToggleSidebar(SidebarType.Components); } }
/>;
break;
case SidebarType.Symbols:
leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
leftChildren = <Symbols
@ -135,6 +161,24 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
symbols={current.symbols}
onPropertyChange={methods.onSymbolPropertyChange}
selectSymbol={methods.selectSymbol}
isExpanded ={false}
onExpandChange={() => { setOrToggleSidebar(SidebarType.SymbolsExpanded); } }
/>;
break;
case SidebarType.SymbolsExpanded:
leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
leftChildren = <Symbols
componentOptions={configuration.AvailableSymbols}
buttonOnClick={methods.addSymbol}
/>;
rightSidebarTitle = Text({ textId: '@SymbolsRight' });
rightChildren = <SymbolsSidebar
selectedSymbolId={current.selectedSymbolId}
symbols={current.symbols}
onPropertyChange={methods.onSymbolPropertyChange}
selectSymbol={methods.selectSymbol}
isExpanded ={true}
onExpandChange={() => { setOrToggleSidebar(SidebarType.Symbols); }}
/>;
break;
@ -152,7 +196,7 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
leftChildren = <Messages
historyState={current}
messages={messages}
clearMessage={() => setMessages([])}
clearMessage={() => { setMessages([]); }}
/>;
break;
@ -167,6 +211,7 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
const isLeftSidebarOpen = selectedSidebar !== SidebarType.None;
const isRightSidebarOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.Symbols;
const isRightSidebarOpenExpanded = selectedSidebar === SidebarType.ComponentsExpanded || selectedSidebar === SidebarType.SymbolsExpanded;
const isLeftSidebarOpenClasses = new Set<string>([
'left-sidebar',
@ -178,35 +223,60 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
let isRightSidebarOpenClasses = 'right-0 -bottom-full md:-right-80 md:bottom-0';
let marginSidebar = BAR_WIDTH;
const viewerMarginClasses = new Set<string>([
'ml-16'
]);
if (isLeftSidebarOpen) {
isLeftSidebarOpenClasses.delete('-bottom-full');
isLeftSidebarOpenClasses.delete('md:-left-64');
isLeftSidebarOpenClasses.delete('md:bottom-0');
marginSidebar += 256;
viewerMarginClasses.add(' md:ml-80');
}
if (isRightSidebarOpen) {
if (isRightSidebarOpen || isRightSidebarOpenExpanded) {
isRightSidebarOpenClasses = 'right-0';
if (isRightSidebarOpenExpanded) {
viewerMarginClasses.add(' md:mr-[32rem]');
marginSidebar += 512;
} else {
viewerMarginClasses.add(' md:mr-64');
marginSidebar += 256;
}
} else {
isLeftSidebarOpenClasses.delete('left-sidebar');
isLeftSidebarOpenClasses.add('left-sidebar-single');
}
const clickRestrictionsClasses = replaceContainer.isReplacing ? 'pointer-events-none opacity-50' : '';
const isComponentsOpen = selectedSidebar === SidebarType.Components || selectedSidebar === SidebarType.ComponentsExpanded;
const isSymbolsOpen = selectedSidebar === SidebarType.Symbols || selectedSidebar === SidebarType.SymbolsExpanded;
return (
<>
<Bar
className={clickRestrictionsClasses}
isComponentsOpen={selectedSidebar === SidebarType.Components}
isSymbolsOpen={selectedSidebar === SidebarType.Symbols}
isComponentsOpen={isComponentsOpen}
isSymbolsOpen={isSymbolsOpen}
isHistoryOpen={selectedSidebar === SidebarType.History}
isMessagesOpen={selectedSidebar === SidebarType.Messages}
isSettingsOpen={selectedSidebar === SidebarType.Settings}
toggleComponents={() => {
setOrToggleSidebar(SidebarType.Components);
if (selectedSidebar === SidebarType.ComponentsExpanded) {
setOrToggleSidebar(SidebarType.ComponentsExpanded);
} else {
setOrToggleSidebar(SidebarType.Components);
}
} }
toggleSymbols={() => {
setOrToggleSidebar(SidebarType.Symbols);
if (selectedSidebar === SidebarType.SymbolsExpanded) {
setOrToggleSidebar(SidebarType.SymbolsExpanded);
} else {
setOrToggleSidebar(SidebarType.Symbols);
}
} }
toggleTimeline={() => {
setOrToggleSidebar(SidebarType.History);
@ -225,12 +295,14 @@ export function UI({ editorState, replaceContainer, setReplaceContainer, ...meth
{ leftChildren }
</Sidebar>
<Viewer
className={clickRestrictionsClasses}
isLeftSidebarOpen={isLeftSidebarOpen}
isRightSidebarOpen={isRightSidebarOpen}
className={`${clickRestrictionsClasses} ${[...viewerMarginClasses.values()].join(' ')} w-full h-full`}
current={current}
isComponentsOpen={isComponentsOpen}
isSymbolsOpen={isSymbolsOpen}
selectedContainer={selectedContainer}
selectContainer={methods.selectContainer}
selectedSymbol={selectedSymbol}
margin={marginSidebar}
/>
<Sidebar
className={`right-sidebar ${isRightSidebarOpenClasses} ${clickRestrictionsClasses}`}

View file

@ -1,58 +1,54 @@
import * as React from 'react';
import { IContainerModel } from '../../Interfaces/IContainerModel';
import { IHistoryState } from '../../Interfaces/IHistoryState';
import { IPoint } from '../../Interfaces/IPoint';
import { type IContainerModel } from '../../Interfaces/IContainerModel';
import { type IHistoryState } from '../../Interfaces/IHistoryState';
import { type IPoint } from '../../Interfaces/IPoint';
import { DIMENSION_MARGIN, USE_EXPERIMENTAL_CANVAS_API } from '../../utils/default';
import { FindContainerById, MakeRecursionDFSIterator } from '../../utils/itertools';
import { BAR_WIDTH } from '../Bar/Bar';
import { Canvas } from '../Canvas/Canvas';
import { AddDimensions } from '../Canvas/DimensionLayer';
import { RenderSelector } from '../Canvas/Selector';
import { SVG } from '../SVG/SVG';
import { SelectorMode, SVG } from '../SVG/SVG';
import { RenderSymbol } from '../Canvas/Symbol';
import { useState } from 'react';
import { type ISymbolModel } from '../../Interfaces/ISymbolModel';
interface IViewerProps {
className: string
isLeftSidebarOpen: boolean
isRightSidebarOpen: boolean
current: IHistoryState
selectedContainer: IContainerModel | undefined
selectContainer: (containerId: string) => void
selectedSymbol: ISymbolModel | undefined
margin: number
isComponentsOpen: boolean
isSymbolsOpen: boolean
}
interface IViewer {
viewerWidth: number
viewerHeight: number
}
function OnResize(
isLeftSidebarOpen: boolean,
isRightSidebarOpen: boolean,
setViewer: React.Dispatch<React.SetStateAction<IViewer>>
): void {
let marginSidebar = BAR_WIDTH;
if (isLeftSidebarOpen) {
marginSidebar += 256;
}
if (isRightSidebarOpen) {
marginSidebar += 256;
export function Viewer({
className,
current,
selectedContainer,
selectContainer,
selectedSymbol,
margin,
isComponentsOpen,
isSymbolsOpen
}: IViewerProps): JSX.Element {
function computeWidth(margin: number): number {
return window.innerWidth - (window.innerWidth < 768 ? BAR_WIDTH : margin);
}
const margin = window.innerWidth < 768 ? BAR_WIDTH : marginSidebar;
setViewer({
viewerWidth: window.innerWidth - margin,
viewerHeight: window.innerHeight
});
}
const [windowSize, setWindowSize] = useState([
computeWidth(margin),
window.innerHeight
]);
function UseSVGAutoResizerOnWindowResize(
isLeftSidebarOpen: boolean,
isRightSidebarOpen: boolean,
setViewer: React.Dispatch<React.SetStateAction<IViewer>>
): void {
React.useEffect(() => {
function SVGAutoResizer(): void {
OnResize(isLeftSidebarOpen, isRightSidebarOpen, setViewer);
setWindowSize([
computeWidth(margin),
window.innerHeight
]);
}
window.addEventListener('resize', SVGAutoResizer);
@ -61,42 +57,13 @@ function UseSVGAutoResizerOnWindowResize(
window.removeEventListener('resize', SVGAutoResizer);
};
});
}
function UseSVGAutoResizerOnSidebar(
isLeftSidebarOpen: boolean,
isRightSidebarOpen: boolean,
setViewer: React.Dispatch<React.SetStateAction<IViewer>>
): void {
React.useEffect(() => {
OnResize(isLeftSidebarOpen, isRightSidebarOpen, setViewer);
}, [isLeftSidebarOpen, isRightSidebarOpen, setViewer]);
}
export function Viewer({
className,
isLeftSidebarOpen, isRightSidebarOpen,
current,
selectedContainer,
selectContainer
}: IViewerProps): JSX.Element {
let marginClasses = 'ml-16';
let marginSidebar = BAR_WIDTH;
if (isLeftSidebarOpen) {
marginClasses += ' md:ml-80';
marginSidebar += 256;
}
if (isRightSidebarOpen) {
marginClasses += ' md:mr-64';
marginSidebar += 256;
}
const margin = window.innerWidth < 768 ? BAR_WIDTH : marginSidebar;
const [viewer, setViewer] = React.useState<IViewer>({
viewerWidth: window.innerWidth - margin,
viewerHeight: window.innerHeight
});
setWindowSize([
computeWidth(margin),
window.innerHeight
]);
}, [margin]);
const mainContainer = FindContainerById(current.containers, current.mainContainer);
@ -104,8 +71,12 @@ export function Viewer({
return <></>;
}
UseSVGAutoResizerOnWindowResize(isLeftSidebarOpen, isRightSidebarOpen, setViewer);
UseSVGAutoResizerOnSidebar(isLeftSidebarOpen, isRightSidebarOpen, setViewer);
let selectorMode = SelectorMode.Nothing;
if (isComponentsOpen) {
selectorMode = SelectorMode.Containers;
} else if (isSymbolsOpen) {
selectorMode = SelectorMode.Symbols;
}
if (USE_EXPERIMENTAL_CANVAS_API) {
function Draw(ctx: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint): void {
@ -171,14 +142,16 @@ export function Viewer({
return (
<SVG
className={`${marginClasses} ${className}`}
viewerWidth={viewer.viewerWidth}
viewerHeight={viewer.viewerHeight}
className={className}
viewerWidth={windowSize[0]}
viewerHeight={windowSize[1]}
width={mainContainer.properties.width}
height={mainContainer.properties.height}
containers={current.containers}
selected={selectedContainer}
selectedContainer={selectedContainer}
symbols={current.symbols}
selectedSymbol={selectedSymbol}
selectorMode={selectorMode}
selectContainer={selectContainer}
>
{mainContainer}
@ -208,10 +181,11 @@ function RenderDimensions(
currentTransform: [number, number]
): void {
ctx.save();
const containerLeftDim = leftDim - (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerTopDim = topDim - (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerBottomDim = bottomDim + (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerRightDim = rightDim + (DIMENSION_MARGIN * (depth + 1)) / scale;
const depthOffset = (DIMENSION_MARGIN * (depth + 1)) / scale;
const containerLeftDim = leftDim - depthOffset;
const containerTopDim = topDim - depthOffset;
const containerBottomDim = bottomDim + depthOffset;
const containerRightDim = rightDim + depthOffset;
const dimMapped = [containerLeftDim, containerBottomDim, containerTopDim, containerRightDim];
AddDimensions(ctx, containers, container, dimMapped, currentTransform, scale, depth);
ctx.restore();

View file

@ -27,6 +27,6 @@ export enum PropertyType {
SelfMarginDimension,
ChildrenDimensions,
DimensionWithMarks,
DimensionOptions
DimensionOptions
}

View file

@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { AddMethod } from '../Enums/AddMethod';
import { PositionReference } from '../Enums/PositionReference';
import { IAction } from './IAction';
import { IMargin } from './IMargin';
import { Orientation } from '../Enums/Orientation';
import { IKeyValue } from './IKeyValue';
import { IStyle } from './IStyle';
import { IDimensions } from './IDimensions';
import { type AddMethod } from '../Enums/AddMethod';
import { type PositionReference } from '../Enums/PositionReference';
import { type IAction } from './IAction';
import { type IMargin } from './IMargin';
import { type Orientation } from '../Enums/Orientation';
import { type IKeyValue } from './IKeyValue';
import { type IStyle } from './IStyle';
import { type IDimensions } from './IDimensions';
/** Model of available container used in application configuration */
export interface IAvailableContainer {

View file

@ -1,10 +1,17 @@
import { Position } from '../Enums/Position';
import { type Position } from '../Enums/Position';
export interface IDimensionOptions {
positions: Position[]
/**
* Stroke color
*/
color: string
* Stroke color
*/
color?: string
/** stroke-width */
width?: number
/** stroke-dasharray */
dashArray?: string
}

View file

@ -1,4 +1,4 @@
import { IAvailableSymbol } from './IAvailableSymbol';
import { type IAvailableSymbol } from './IAvailableSymbol';
export interface ISymbolModel {
/** Identifier */
@ -26,4 +26,7 @@ export interface ISymbolModel {
/** List of linked container id */
linkedContainers: Set<string>
/** Dimensions options */
showDimension: boolean
}

View file

@ -2,12 +2,14 @@
"@StartFromScratch": "Start from scratch",
"@LoadConfigFile": "Load a configuration file",
"@GoBack": "Go back",
"@Properties" : "Properties",
"@Components": "Components",
"@Elements": "Elements",
"@Symbols": "Symbols",
"@SymbolsLeft": "Symbols",
"@SymbolsRight": "Symbols",
"@NoSymbolSelected": "No symbol selected",
"@Timeline": "Timeline",
"@Messages": "Messages",
"@Settings": "Settings",
@ -57,11 +59,14 @@
"@ContainerAlignmentInput": "Alignment",
"@ContainerAlignWithSymbol": "Align to symbol",
"@ContainerDimensions": "Dimensions",
"@ContainerShowDimension": "Show Dimension",
"@ContainerShowChildrenDimension": "Show surrounding dimension of children",
"@ContainerShowDimension": "Show dimensions",
"@ContainerShowChildrenDimension": "Show surrounding dimensions of children",
"@ContainerMarkPosition": "Mark the position for the parents",
"@ContainerShowDimensionWithMarks": "Show dimension with marked children",
"@ContainerShowDimensionWithMarks": "Show dimensions with marked children",
"@ContainerShowMarginsDimension": "Show margins dimensions",
"@ContainerStyle": "Style",
"@StyleStrokeColor": "Stroke Color",
"@StyleStrokeDashArray": "Stroke Dash Array",
"@StyleStroke": "Stroke",
"@StyleStrokeOpacity": "Stroke Opacity",
"@StyleStrokeWidth": "Stroke Width",

View file

@ -2,12 +2,14 @@
"@StartFromScratch": "Partir de zéro",
"@LoadConfigFile": "Charger un fichier de configuration",
"@GoBack": "Revenir",
"@Properties" : "Propriétés",
"@Components": "Composants",
"@Elements": "Éléments",
"@Symbols": "Symboles",
"@SymbolsLeft": "Symboles",
"@SymbolsRight": "Symboles",
"@NoSymbolSelected": "Pas de symbol sélectionné",
"@Timeline": "Chronologie",
"@Messages": "Messages",
"@Settings": "Paramètres",
@ -61,7 +63,10 @@
"@ContainerShowChildrenDimension": "Afficher les cotations englobante des enfants",
"@ContainerMarkPosition": "Marquer la position pour les parents",
"@ContainerShowDimensionWithMarks": "Afficher les cotations avec les enfants marqués",
"@ContainerShowMarginsDimension": "Afficher les cotations des marges",
"@ContainerStyle": "Style",
"@StyleStrokeColor": "Couleur du tracé",
"@StyleStrokeDashArray": "Tableau de traits",
"@StyleStroke": "Tracé",
"@StyleStrokeOpacity": "Opacité du tracé",
"@StyleStrokeWidth": "Epaisseur du tracé",

View file

@ -21,10 +21,11 @@
.right-sidebar {
@apply fixed shadow-lg z-20
w-[calc(100%_-_4rem)] md:w-64
h-1/2 md:h-full bottom-0 md:bottom-0
}
.sidebar-title {
@apply p-3 md:p-5 font-bold h-12 md:h-16
}

View file

@ -1,13 +1,14 @@
import { PositionReference } from '../Enums/PositionReference';
import { IAvailableContainer } from '../Interfaces/IAvailableContainer';
import { IAvailableSymbol } from '../Interfaces/IAvailableSymbol';
import { IConfiguration } from '../Interfaces/IConfiguration';
import { ContainerModel, IContainerModel } from '../Interfaces/IContainerModel';
import { IContainerProperties } from '../Interfaces/IContainerProperties';
import { IEditorState } from '../Interfaces/IEditorState';
import { ISymbolModel } from '../Interfaces/ISymbolModel';
import { type IAvailableContainer } from '../Interfaces/IAvailableContainer';
import { type IAvailableSymbol } from '../Interfaces/IAvailableSymbol';
import { type IConfiguration } from '../Interfaces/IConfiguration';
import { ContainerModel, type IContainerModel } from '../Interfaces/IContainerModel';
import { type IContainerProperties } from '../Interfaces/IContainerProperties';
import { type IEditorState } from '../Interfaces/IEditorState';
import { type ISymbolModel } from '../Interfaces/ISymbolModel';
import { Orientation } from '../Enums/Orientation';
import { AppState } from '../Enums/AppState';
import { type IDimensionOptions } from '../Interfaces/IDimensionOptions';
/// EDITOR DEFAULTS ///
@ -65,7 +66,6 @@ export const SHOW_SELF_DIMENSIONS = true;
export const SHOW_SELF_MARGINS_DIMENSIONS = true;
export const SHOW_CHILDREN_DIMENSIONS = true;
export const SHOW_BORROWER_DIMENSIONS = true;
export const SHOW_DIMENSIONS_PER_DEPTH = false;
export const DIMENSION_MARGIN = 50;
export const SYMBOL_MARGIN = 25;
export const NOTCHES_LENGTH = 10;
@ -187,6 +187,12 @@ const DEFAULT_CONTAINER_STYLE = {
strokeWidth: 2
};
export const DEFAULT_DIMENSION_OPTION: IDimensionOptions = {
positions: [],
color: '#000000',
width: 2
};
/**
* Default Main container properties
*/
@ -211,23 +217,11 @@ export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
positionReference: PositionReference.TopLeft,
hideChildrenInTreeview: false,
dimensionOptions: {
childrenDimensions: {
color: '#000000',
positions: []
},
selfDimensions: {
color: '#000000',
positions: []
},
selfMarginsDimensions: {
color: '#000000',
positions: []
},
childrenDimensions: clone(DEFAULT_DIMENSION_OPTION),
selfDimensions: clone(DEFAULT_DIMENSION_OPTION),
selfMarginsDimensions: clone(DEFAULT_DIMENSION_OPTION),
markPosition: [],
dimensionWithMarks: {
color: '#000000',
positions: []
}
dimensionWithMarks: clone(DEFAULT_DIMENSION_OPTION)
},
warning: '',
style: DEFAULT_CONTAINER_STYLE
@ -276,20 +270,20 @@ export function GetDefaultContainerProps(type: string,
hideChildrenInTreeview: containerConfig.HideChildrenInTreeview ?? false,
dimensionOptions: {
childrenDimensions: {
color: containerConfig.DimensionOptions?.childrenDimensions.color ?? '#000000',
...containerConfig.DimensionOptions?.selfDimensions,
positions: containerConfig.DimensionOptions?.childrenDimensions.positions ?? []
},
selfDimensions: {
color: containerConfig.DimensionOptions?.selfDimensions.color ?? '#000000',
...containerConfig.DimensionOptions?.selfDimensions,
positions: containerConfig.DimensionOptions?.selfDimensions.positions ?? []
},
selfMarginsDimensions: {
color: containerConfig.DimensionOptions?.selfMarginsDimensions.color ?? '#000000',
...containerConfig.DimensionOptions?.selfMarginsDimensions,
positions: containerConfig.DimensionOptions?.selfMarginsDimensions.positions ?? []
},
markPosition: containerConfig.DimensionOptions?.markPosition ?? [],
dimensionWithMarks: {
color: containerConfig.DimensionOptions?.dimensionWithMarks.color ?? '#000000',
...containerConfig.DimensionOptions?.dimensionWithMarks,
positions: containerConfig.DimensionOptions?.dimensionWithMarks.positions ?? []
}
},
@ -313,6 +307,14 @@ export function GetDefaultSymbolModel(name: string,
x: 0,
width: symbolConfig.Width ?? DEFAULT_SYMBOL_WIDTH,
height: symbolConfig.Height ?? DEFAULT_SYMBOL_HEIGHT,
linkedContainers: new Set()
linkedContainers: new Set(),
showDimension: false
};
}
/**
* Macro function for JSON.parse(JSON.stringify(obj))
*/
function clone<T>(object: T): T {
return JSON.parse(JSON.stringify(object));
}

View file

@ -1,5 +1,5 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import react from '@vitejs/plugin-react-swc';
// https://vitejs.dev/config/
export default defineConfig({