Merged PR 235: Update master

This commit is contained in:
Eric Nguyen 2022-11-10 14:19:37 +00:00
commit 69e80af773
51 changed files with 1183 additions and 495 deletions

View file

@ -7,13 +7,26 @@ namespace SVGLDLibs.Models
{ {
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public string Name { get; set; } public string Name { get; set; }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public ImageModel Image { get; set; } public ImageModel Image { get; set; }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public PositionReferenceEnumModel PositionReference { get; set; } public double X { get; set; }
[DataMember(EmitDefaultValue = false)]
public double Y { get; set; }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public double Width { get; set; } public double Width { get; set; }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public double Height { get; set; } public double Height { get; set; }
[DataMember(EmitDefaultValue = false)]
public PositionReferenceEnumModel PositionReference { get; set; }
[DataMember(EmitDefaultValue = false)]
public AvailableContainerModel AssociatedContainer { get; set; }
} }
} }

View file

@ -5,16 +5,19 @@ namespace SVGLDLibs.Models
[DataContract] [DataContract]
public class CSSStyle public class CSSStyle
{ {
[DataMember(EmitDefaultValue = false)]
public double? strokeWidth;
[DataMember(EmitDefaultValue = false)]
public double? fillOpacity;
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public string stroke; public string stroke;
[DataMember(EmitDefaultValue = false)]
public double? strokeOpacity;
[DataMember(EmitDefaultValue = false)]
public double? strokeWidth;
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public string fill; public string fill;
[DataMember(EmitDefaultValue = false)]
public double? fillOpacity;
} }
} }

View file

@ -8,16 +8,22 @@ namespace SVGLDLibs.Models
{ {
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public string id { get; set; } public string id { get; set; }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public string type { get; set; } public string type { get; set; }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public AvailableSymbolModel config { get; set; } public AvailableSymbolModel config { get; set; }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public double x { get; set; } public double x { get; set; }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public double width { get; set; } public double width { get; set; }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public double height { get; set; } public double height { get; set; }
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public List<string> linkedContainers { get; set; } public List<string> linkedContainers { get; set; }
} }

View file

@ -15,6 +15,8 @@ Liens :
- [Système de comportement](Pages/Behaviors.md) - [Système de comportement](Pages/Behaviors.md)
- [Cycle de vie de l'application](Pages/Application.md) - [Cycle de vie de l'application](Pages/Application.md)
- [Implémentation du menu contextuel](Pages/ContextMenu.md) - [Implémentation du menu contextuel](Pages/ContextMenu.md)
- [Web workers](Pages/WebWorkers.md) - [Implémentation du système de cote](Pages/SVGLD_Cotes.pdf)
- [Web workers](Pages/WebWorkers.md) (pdf)
- [Traductions](Pages/Translations.drawio) (nécessite diagrams.net)
- [Système de CI/CD](Pages/Behaviors.md) - [Système de CI/CD](Pages/Behaviors.md)
- [Mise en place du SmartComponent sur Modeler](Pages/SmartComponent.md) - [Mise en place du SmartComponent sur Modeler](Pages/SmartComponent.md)

BIN
docs/#Project/Pages/Translations.drawio (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -5,6 +5,7 @@
type IHistoryState = SVGLD.IHistoryState; type IHistoryState = SVGLD.IHistoryState;
type IEditorState = SVGLD.IEditorState; type IEditorState = SVGLD.IEditorState;
type IConfiguration = SVGLD.IConfiguration; type IConfiguration = SVGLD.IConfiguration;
type ILanguage = SVGLD.ILanguage;
export class SVGLayoutDesigner extends Components.ComponentBase { export class SVGLayoutDesigner extends Components.ComponentBase {
@ -137,10 +138,14 @@
public LoadEditor(editorState: IEditorState) { public LoadEditor(editorState: IEditorState) {
this.ReviveEditorState(editorState, (state) => { this.ReviveEditorState(editorState, (state) => {
this.SetEditor(state, (currentState) => { this.SetEditor(state, (currentState) => {
setTimeout(() => {
this.app.App.SetAppState(2, () => {
setTimeout(() => { setTimeout(() => {
this.app.Editor.SetHistory({ history: state.history, historyCurrentStep: state.historyCurrentStep }); this.app.Editor.SetHistory({ history: state.history, historyCurrentStep: state.historyCurrentStep });
}, 200); }, 200);
}); });
}, 200);
});
}); });
} }
@ -161,14 +166,14 @@
/** /**
* Hide the main menu to go to the application. * Hide the main menu to go to the application.
* SetEditor must be called first or the application will crash. * SetEditor must be called first or the application will crash.
* @param isLoaded * @param appState
* @param callback * @param callback
*/ */
public SetLoaded(isLoaded: boolean, callback?: (state: IEditorState) => void) { public SetAppState(appState: SVGLD.AppState, callback?: (state: IEditorState) => void) {
const eventType = 'setLoaded'; const eventType = 'setAppState';
this.app.AddEventListener(eventType, callback); this.app.AddEventListener(eventType, callback);
const component = this.GetAppComponent(); const component = this.GetAppComponent();
component.dispatchEvent(new CustomEvent(eventType, { detail: isLoaded })) component.dispatchEvent(new CustomEvent(eventType, { detail: appState }))
} }
/** /**
@ -210,6 +215,43 @@
const component = this.GetAppComponent(); const component = this.GetAppComponent();
component.dispatchEvent(new CustomEvent(eventType, { detail: configuration })); component.dispatchEvent(new CustomEvent(eventType, { detail: configuration }));
} }
/**
* Add a language to the app
* @param option Displayed string of the language
* @param language Language containing an id and a dictionary
* @param callback Callback
*/
public AddLanguage(option: string, language: ILanguage, callback?: () => void) {
const eventType = 'addLanguage';
this.app.AddEventListener(eventType, callback);
const component = this.GetAppComponent();
component.dispatchEvent(new CustomEvent(eventType, { detail: { language, option } }));
}
/**
* Set the language of the app (defaults: ['fr', 'en'])
* @param languageId Language identifier for the language to select
* @param callback Callback
*/
public SetLanguage(languageId: string, callback?: (success: boolean) => void) {
const eventType = 'setLanguage';
this.app.AddEventListener(eventType, callback);
const component = this.GetAppComponent();
component.dispatchEvent(new CustomEvent(eventType, { detail: languageId }));
}
/**
* Set the language of the app (defaults: ['fr', 'en'])
* @param languageId Language identifier for the language to select
* @param callback Callback
*/
public GetLanguages(callback: (languages: Record<string, Record<string, string>>) => void) {
const eventType = 'getLanguages';
this.app.AddEventListener(eventType, callback);
const component = this.GetAppComponent();
component.dispatchEvent(new CustomEvent(eventType));
}
} }

27
public/svgld.d.ts vendored
View file

@ -13,6 +13,12 @@ export enum AddMethod {
ReplaceParent = 3 ReplaceParent = 3
} }
export enum AppState {
MainMenu = 0,
Loading = 1,
Loaded = 2
}
export enum MessageType { export enum MessageType {
Normal = 0, Normal = 0,
Success = 1, Success = 1,
@ -88,6 +94,7 @@ export interface IAPIConfiguration {
/** Model of available container used in application configuration */ /** Model of available container used in application configuration */
export interface IAvailableContainer { export interface IAvailableContainer {
/** type */ /** type */
@ -212,7 +219,7 @@ export interface IAvailableContainer {
* (optional) * (optional)
* User data that can be used for data storage or custom SVG * User data that can be used for data storage or custom SVG
*/ */
UserData?: object; UserData?: IKeyValue[];
} }
@ -250,7 +257,6 @@ export interface IConfiguration {
export interface IContainerModel { export interface IContainerModel {
children: string[]; children: string[];
parent: IContainerModel | null;
properties: IContainerProperties; properties: IContainerProperties;
userData: Record<string, string | number>; userData: Record<string, string | number>;
} }
@ -260,10 +266,9 @@ export interface IContainerModel {
*/ */
export class ContainerModel implements IContainerModel { export class ContainerModel implements IContainerModel {
children: string[]; children: string[];
parent: IContainerModel | null;
properties: IContainerProperties; properties: IContainerProperties;
userData: Record<string, string | number>; userData: Record<string, string | number>;
constructor(parent: IContainerModel | null, properties: IContainerProperties, children?: string[], userData?: {}); constructor(properties: IContainerProperties, children?: string[], userData?: {});
} }
@ -271,6 +276,7 @@ export class ContainerModel implements IContainerModel {
/** /**
* Properties of a container * Properties of a container
*/ */
@ -363,7 +369,7 @@ export interface IContainerProperties {
* (optional) * (optional)
* User data that can be used for data storage or custom SVG * User data that can be used for data storage or custom SVG
*/ */
userData?: object; userData?: IKeyValue[];
} }
@ -428,6 +434,17 @@ export interface IInputGroup {
value: string; value: string;
} }
export interface IKeyValue {
Key: string;
Value: string;
}
export interface ILanguage {
language: string;
dictionary: Record<string, string>;
languageChange?: (selected: string) => void;
}
export interface IMargin { export interface IMargin {
left?: number; left?: number;
bottom?: number; bottom?: number;

View file

@ -1,13 +1,14 @@
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { AppState } from '../../../Enums/AppState';
import { IEditorState } from '../../../Interfaces/IEditorState'; import { IEditorState } from '../../../Interfaces/IEditorState';
import { Revive } from '../../../utils/saveload'; import { Revive } from '../../../utils/saveload';
export function LoadState( export function LoadState(
editorState: IEditorState, editorState: IEditorState,
setEditorState: Dispatch<SetStateAction<IEditorState>>, setEditorState: Dispatch<SetStateAction<IEditorState>>,
setLoaded: Dispatch<SetStateAction<boolean>> setAppState: Dispatch<SetStateAction<AppState>>
): void { ): void {
Revive(editorState); Revive(editorState);
setEditorState(editorState); setEditorState(editorState);
setLoaded(true); setAppState(AppState.Loaded);
} }

View file

@ -1,9 +1,10 @@
import { Dispatch, SetStateAction, useEffect } 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 { DISABLE_API, GetDefaultEditorState } from '../../../utils/default'; import { DISABLE_API, GetDefaultEditorState } from '../../../utils/default';
import { AppState } from '../../../Enums/AppState';
export function NewEditor( export function NewEditor(
editorState: IEditorState, editorState: IEditorState,
@ -36,7 +37,7 @@ export function NewEditor(
export function LoadEditor( export function LoadEditor(
files: FileList | null, files: FileList | null,
setEditorState: Dispatch<SetStateAction<IEditorState>>, setEditorState: Dispatch<SetStateAction<IEditorState>>,
setLoaded: Dispatch<SetStateAction<boolean>> setAppState: Dispatch<SetStateAction<AppState>>
): void { ): void {
if (files === null) { if (files === null) {
return; return;
@ -47,7 +48,7 @@ export function LoadEditor(
const result = reader.result as string; const result = reader.result as string;
const editorState: IEditorState = JSON.parse(result); const editorState: IEditorState = JSON.parse(result);
LoadState(editorState, setEditorState, setLoaded); LoadState(editorState, setEditorState, setAppState);
}); });
reader.readAsText(file); reader.readAsText(file);
} }

View file

@ -1,4 +1,4 @@
import React, { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; import React, { Dispatch, SetStateAction, useContext, useEffect, useRef, useState } from 'react';
import { UseCustomEvents } from '../../Events/AppEvents'; import { UseCustomEvents } from '../../Events/AppEvents';
import { MainMenu } from '../MainMenu/MainMenu'; import { MainMenu } from '../MainMenu/MainMenu';
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel'; import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
@ -6,7 +6,10 @@ 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';
import { DEFAULT_CONFIG, DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default'; import { DEFAULT_CONFIG, DEFAULT_MAINCONTAINER_PROPS, FAST_BOOT } from '../../utils/default';
import { AppState } from '../../Enums/AppState';
import { Loader } from '../Loader/Loader';
import { LanguageContext } from '../LanguageProvider/LanguageProvider';
// App will never have props // App will never have props
// eslint-disable-next-line @typescript-eslint/no-empty-interface // eslint-disable-next-line @typescript-eslint/no-empty-interface
@ -15,9 +18,9 @@ interface IAppProps {
} }
function UseHTTPGETStatePreloading( function UseHTTPGETStatePreloading(
isLoaded: boolean, appState: AppState,
setEditorState: Dispatch<SetStateAction<IEditorState>>, setEditorState: Dispatch<SetStateAction<IEditorState>>,
setLoaded: Dispatch<SetStateAction<boolean>> setAppState: Dispatch<SetStateAction<AppState>>
): void { ): void {
useEffect(() => { useEffect(() => {
const queryString = window.location.search; const queryString = window.location.search;
@ -28,22 +31,23 @@ function UseHTTPGETStatePreloading(
return; return;
} }
if (!isLoaded) { if (appState !== AppState.Loaded) {
fetch(state) fetch(state)
.then( .then(
async(response) => await response.json(), async(response) => await response.json(),
(error) => { throw new Error(error); } (error) => { throw new Error(error); }
) )
.then((data: IEditorState) => { .then((data: IEditorState) => {
LoadState(data, setEditorState, setLoaded); LoadState(data, setEditorState, setAppState);
}, (error) => { throw new Error(error); }); }, (error) => { throw new Error(error); });
} }
}); });
}; };
export function App(props: IAppProps): JSX.Element { export function App(props: IAppProps): JSX.Element {
const [isLoaded, setLoaded] = useState<boolean>(false); const [appState, setAppState] = useState<AppState>(FAST_BOOT);
const appRef = useRef<HTMLDivElement>(null); const appRef = useRef<HTMLDivElement>(null);
const languageContext = useContext(LanguageContext);
const defaultMainContainer = new ContainerModel( const defaultMainContainer = new ContainerModel(
DEFAULT_MAINCONTAINER_PROPS DEFAULT_MAINCONTAINER_PROPS
@ -68,17 +72,15 @@ export function App(props: IAppProps): JSX.Element {
UseCustomEvents( UseCustomEvents(
props.root, props.root,
appRef, appRef,
languageContext,
setEditorState, setEditorState,
setLoaded setAppState
); );
UseHTTPGETStatePreloading(isLoaded, setEditorState, setLoaded); UseHTTPGETStatePreloading(appState, setEditorState, setAppState);
const enableLoaded = useCallback(() => { switch (appState) {
setLoaded(true); case AppState.Loaded:
}, []);
if (isLoaded) {
return ( return (
<div <div
ref={appRef} ref={appRef}
@ -92,25 +94,39 @@ export function App(props: IAppProps): JSX.Element {
/> />
</div> </div>
); );
} case AppState.Loading:
return (
<div
ref={appRef}
className='App mainmenu-bg'
>
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
<Loader />
</div>
</div>
);
default:
return ( return (
<div <div
ref={appRef} ref={appRef}
className='App mainmenu-bg' className='App mainmenu-bg'
> >
<MainMenu <MainMenu
newEditor={() => NewEditor( newEditor={() => {
setAppState(AppState.Loading);
NewEditor(
editorState, editorState,
(newEditor) => setEditorState(newEditor), (newEditor) => setEditorState(newEditor),
enableLoaded () => setAppState(AppState.Loaded)
)} );
}}
loadEditor={(files: FileList | null) => LoadEditor( loadEditor={(files: FileList | null) => LoadEditor(
files, files,
setEditorState, setEditorState,
setLoaded setAppState
)} )}
/> />
</div> </div>
); );
}
}; };

View file

@ -14,6 +14,7 @@ import {
Cog8ToothIcon as Cog8ToothIconS Cog8ToothIcon as Cog8ToothIconS
} from '@heroicons/react/24/solid'; } from '@heroicons/react/24/solid';
import { BarIcon } from './BarIcon'; import { BarIcon } from './BarIcon';
import { Text } from '../Text/Text';
interface IBarProps { interface IBarProps {
isComponentsOpen: boolean isComponentsOpen: boolean
@ -35,7 +36,7 @@ export function Bar(props: IBarProps): JSX.Element {
<div className='bar'> <div className='bar'>
<BarIcon <BarIcon
isActive={props.isComponentsOpen} isActive={props.isComponentsOpen}
title='Components' title={Text({ textId: '@Components' })}
onClick={() => props.toggleComponents()}> onClick={() => props.toggleComponents()}>
{ {
props.isComponentsOpen props.isComponentsOpen
@ -45,7 +46,7 @@ export function Bar(props: IBarProps): JSX.Element {
</BarIcon> </BarIcon>
<BarIcon <BarIcon
isActive={props.isSymbolsOpen} isActive={props.isSymbolsOpen}
title='Symbols' title={Text({ textId: '@Symbols' })}
onClick={() => props.toggleSymbols()}> onClick={() => props.toggleSymbols()}>
{ {
props.isSymbolsOpen props.isSymbolsOpen
@ -55,7 +56,7 @@ export function Bar(props: IBarProps): JSX.Element {
</BarIcon> </BarIcon>
<BarIcon <BarIcon
isActive={props.isMessagesOpen} isActive={props.isMessagesOpen}
title='Messages' title={Text({ textId: '@Messages' })}
onClick={() => props.toggleMessages()}> onClick={() => props.toggleMessages()}>
{ {
props.isMessagesOpen props.isMessagesOpen
@ -66,7 +67,7 @@ export function Bar(props: IBarProps): JSX.Element {
<div className='grow'></div> <div className='grow'></div>
<BarIcon <BarIcon
isActive={props.isHistoryOpen} isActive={props.isHistoryOpen}
title='Timeline' title={Text({ textId: '@Timeline' })}
onClick={() => props.toggleTimeline()}> onClick={() => props.toggleTimeline()}>
{ {
props.isHistoryOpen props.isHistoryOpen
@ -76,7 +77,7 @@ export function Bar(props: IBarProps): JSX.Element {
</BarIcon> </BarIcon>
<BarIcon <BarIcon
isActive={props.isSettingsOpen} isActive={props.isSettingsOpen}
title='Settings' title={Text({ textId: '@Settings' })}
onClick={() => props.toggleSettings()}> onClick={() => props.toggleSettings()}>
{ {
props.isSettingsOpen props.isSettingsOpen

View file

@ -79,18 +79,20 @@ function ActionByPosition(
dimMapped: number[], dimMapped: number[],
positions: Position[], positions: Position[],
horizontalAction: (ctx: CanvasRenderingContext2D, dim: number, ...params: any[]) => void, horizontalAction: (ctx: CanvasRenderingContext2D, dim: number, ...params: any[]) => void,
verticalAction: (ctx: CanvasRenderingContext2D, dim: number, ...params: any[]) => void, verticalAction: (ctx: CanvasRenderingContext2D, dim: number, isRight: boolean, ...params: any[]) => void,
params: any[] params: any[]
): void { ): void {
positions.forEach((position: Position) => { positions.forEach((position: Position) => {
const dim = dimMapped[position]; const dim = dimMapped[position];
switch (position) { switch (position) {
case Position.Left: case Position.Left:
case Position.Right: case Position.Right: {
verticalAction(ctx, dim, ...params); const isRight = position === Position.Right;
verticalAction(ctx, dim, isRight, ...params);
}
break; break;
case Position.Down:
case Position.Up: case Position.Up:
case Position.Down:
horizontalAction(ctx, dim, ...params); horizontalAction(ctx, dim, ...params);
break; break;
} }
@ -140,10 +142,10 @@ function AddHorizontalChildrenDimension(
} }
const textChildren = (xChildrenEnd - xChildrenStart) const textChildren = (xChildrenEnd - xChildrenStart)
.toFixed(2) .toFixed(0);
.toString();
const offset = currentTransform[0] + container.properties.x; const offset = currentTransform[0] + container.properties.x;
RenderDimension(ctx, { RenderDimension(ctx, {
id: childrenId, id: childrenId,
xStart: xChildrenStart + offset, xStart: xChildrenStart + offset,
@ -159,6 +161,7 @@ function AddHorizontalChildrenDimension(
function AddVerticalChildrenDimension( function AddVerticalChildrenDimension(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
xDim: number, xDim: number,
isRight: boolean,
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
container: IContainerModel, container: IContainerModel,
currentTransform: [number, number], currentTransform: [number, number],
@ -174,7 +177,7 @@ function AddVerticalChildrenDimension(
} }
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference); let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
let yChildrenEnd = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference); let yChildrenEnd = yChildrenStart;
// Find the min and max // Find the min and max
for (let i = container.children.length - 2; i >= 0; i--) { for (let i = container.children.length - 2; i >= 0; i--) {
@ -201,11 +204,14 @@ function AddVerticalChildrenDimension(
} }
const textChildren = (yChildrenEnd - yChildrenStart) const textChildren = (yChildrenEnd - yChildrenStart)
.toFixed(2) .toFixed(0);
.toString();
const offset = currentTransform[0] + container.properties.x; const offset = currentTransform[0] + container.properties.x;
if (!isRight) {
[yChildrenStart, yChildrenEnd] = [yChildrenEnd, yChildrenStart];
}
RenderDimension(ctx, { RenderDimension(ctx, {
id: childrenId, id: childrenId,
xStart: xDim, xStart: xDim,
@ -257,6 +263,11 @@ function AddHorizontalBorrowerDimension(
let count = 0; let count = 0;
for (const { cur, next } of Pairwise(marks)) { for (const { cur, next } of Pairwise(marks)) {
const id = `dim-y${yDim.toFixed(0)}-borrow-${container.properties.id}-{${count}}`; const id = `dim-y${yDim.toFixed(0)}-borrow-${container.properties.id}-{${count}}`;
const value = next - cur;
if (value === 0) {
return;
}
RenderDimension(ctx, { RenderDimension(ctx, {
id, id,
xStart: cur, xStart: cur,
@ -264,7 +275,7 @@ function AddHorizontalBorrowerDimension(
yStart: yDim, yStart: yDim,
yEnd: yDim, yEnd: yDim,
strokeWidth: MODULE_STROKE_WIDTH, strokeWidth: MODULE_STROKE_WIDTH,
text: (next - cur).toFixed(0).toString(), text: value.toFixed(0),
scale scale
}); });
count++; count++;
@ -274,6 +285,7 @@ function AddHorizontalBorrowerDimension(
function AddVerticalBorrowerDimension( function AddVerticalBorrowerDimension(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
xDim: number, xDim: number,
isRight: boolean,
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
container: IContainerModel, container: IContainerModel,
depth: number, depth: number,
@ -307,9 +319,19 @@ function AddVerticalBorrowerDimension(
marks.push(restoredY); marks.push(restoredY);
marks.push(restoredY + container.properties.height); marks.push(restoredY + container.properties.height);
marks.sort((a, b) => a - b); marks.sort((a, b) => a - b);
let count = 0; let count = 0;
for (const { cur, next } of Pairwise(marks)) { for (let { cur, next } of Pairwise(marks)) {
const id = `dim-x${xDim.toFixed(0)}-borrow-${container.properties.id}-{${count}}`; const id = `dim-x${xDim.toFixed(0)}-borrow-${container.properties.id}-{${count}}`;
const value = next - cur;
if (value === 0) {
return;
}
if (!isRight) {
[cur, next] = [next, cur];
}
RenderDimension(ctx, { RenderDimension(ctx, {
id, id,
xStart: xDim, xStart: xDim,
@ -317,7 +339,7 @@ function AddVerticalBorrowerDimension(
yStart: cur, yStart: cur,
yEnd: next, yEnd: next,
strokeWidth: MODULE_STROKE_WIDTH, strokeWidth: MODULE_STROKE_WIDTH,
text: (next - cur).toFixed(0).toString(), text: value.toFixed(0),
scale scale
}); });
count++; count++;
@ -327,22 +349,28 @@ function AddVerticalBorrowerDimension(
function AddVerticalSelfDimension( function AddVerticalSelfDimension(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
xDim: number, xDim: number,
isRight: boolean,
container: IContainerModel, container: IContainerModel,
currentTransform: [number, number], currentTransform: [number, number],
scale: number scale: number
): void { ): void {
const height = container.properties.height; const height = container.properties.height;
const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`; const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`;
const yStart = container.properties.y + currentTransform[1]; let yStart = container.properties.y + currentTransform[1] + height;
const yEnd = yStart + height; let yEnd = container.properties.y + currentTransform[1];
const textVert = height const textVert = height
.toFixed(0) .toFixed(0)
.toString(); .toString();
if (isRight) {
[yStart, yEnd] = [yEnd, yStart];
}
RenderDimension(ctx, { RenderDimension(ctx, {
id: idVert, id: idVert,
xStart: xDim, xStart: xDim,
yStart,
xEnd: xDim, xEnd: xDim,
yStart,
yEnd, yEnd,
strokeWidth: MODULE_STROKE_WIDTH, strokeWidth: MODULE_STROKE_WIDTH,
text: textVert, text: textVert,

View file

@ -33,7 +33,7 @@ export function RenderSelector(ctx: CanvasRenderingContext2D, frameCount: number
ctx.strokeStyle = '#3B82F6'; ctx.strokeStyle = '#3B82F6';
ctx.lineWidth = 4 / scale; ctx.lineWidth = 4 / scale;
ctx.globalAlpha = 0.25 * (Math.sin(frameCount * 0.0125) ** 2); ctx.globalAlpha = (Math.sin(frameCount * 0.0450) ** 2);
ctx.strokeRect(x, y, width, height); ctx.strokeRect(x, y, width, height);
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
ctx.lineWidth = 1; ctx.lineWidth = 1;

View file

@ -63,9 +63,7 @@ export function CheckboxGroupButtons(props: ICheckboxGroupButtonsProps): JSX.Ele
<label className='text-xs font-medium text-gray-800'> <label className='text-xs font-medium text-gray-800'>
{props.labelText} {props.labelText}
</label> </label>
<div id='XPositionReference' <div className={`grid ${gridColsClass}`}>
className={`grid ${gridColsClass}`}
>
{inputGroups} {inputGroups}
</div> </div>
</> </>

View file

@ -5,6 +5,7 @@ import { ICategory } from '../../Interfaces/ICategory';
import { IContainerModel } from '../../Interfaces/IContainerModel'; import { IContainerModel } from '../../Interfaces/IContainerModel';
import { TruncateString } from '../../utils/stringtools'; import { TruncateString } from '../../utils/stringtools';
import { Category } from '../Category/Category'; import { Category } from '../Category/Category';
import { Text } from '../Text/Text';
interface IComponentsProps { interface IComponentsProps {
selectedContainer: IContainerModel | undefined selectedContainer: IContainerModel | undefined
@ -24,7 +25,7 @@ interface SidebarCategory {
export function Components(props: IComponentsProps): JSX.Element { export function Components(props: IComponentsProps): JSX.Element {
const [hideDisabled, setHideDisabled] = React.useState<boolean>(false); const [hideDisabled, setHideDisabled] = React.useState<boolean>(false);
const disabledTitle = hideDisabled ? 'Show disabled components' : 'Hide disabled components'; const disabledTitle = hideDisabled ? Text({ textId: '@ShowDisabledComponents' }) : Text({ textId: '@HideDisabledComponents' });
const rootElements: Array<JSX.Element | undefined> = []; const rootElements: Array<JSX.Element | undefined> = [];
const categories = new Map<string, SidebarCategory>(props.categories.map(category => [ const categories = new Map<string, SidebarCategory>(props.categories.map(category => [

View file

@ -4,6 +4,7 @@ import { IContainerProperties } from '../../Interfaces/IContainerProperties';
import { ISymbolModel } from '../../Interfaces/ISymbolModel'; import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../utils/default'; import { SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_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 { InputGroup } from '../InputGroup/InputGroup';
import { TextInputGroup } from '../InputGroup/TextInputGroup'; import { TextInputGroup } from '../InputGroup/TextInputGroup';
import { Select } from '../Select/Select'; import { Select } from '../Select/Select';
@ -20,31 +21,13 @@ interface IContainerFormProps {
onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void onChange: (key: string, value: string | number | boolean | number[], type?: PropertyType) => void
} }
function GetCSSInputs(properties: IContainerProperties,
onChange: (key: string, value: string | number | boolean, type: PropertyType) => void): JSX.Element[] {
const groupInput: JSX.Element[] = [];
for (const key in properties.style) {
groupInput.push(<TextInputGroup
key={key}
id={key}
labelText={key}
inputKey={key}
labelClassName='col-span-2'
inputClassName='col-span-3'
type='string'
value={(properties.style as any)[key]}
onChange={(value) => onChange(key, value, PropertyType.Style)} />);
}
return groupInput;
}
export function ContainerForm(props: IContainerFormProps): JSX.Element { export function ContainerForm(props: IContainerFormProps): JSX.Element {
const categoryHeight = 'h-11'; const categoryHeight = 'h-11';
return ( return (
<div className='grid grid-cols-1 gap-y-4 items-center'> <div className='grid grid-cols-1 gap-y-4 items-center'>
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-displayedText`} id={`${props.properties.id}-displayedText`}
labelText='Displayed text' labelText={Text({ textId: '@ContainerDisplayedText' })}
inputKey='displayedText' inputKey='displayedText'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -54,7 +37,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<OrientationSelector <OrientationSelector
id='orientation' id='orientation'
name='Orientation' name='Orientation'
labelText='Orientation' labelText={Text({ textId: '@ContainerOrientation' })}
value={props.properties.orientation} value={props.properties.orientation}
onChange={props.onChange} onChange={props.onChange}
/> />
@ -62,14 +45,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<Category <Category
category={{ category={{
Type: 'Properties', Type: 'Properties',
DisplayedText: 'Properties' DisplayedText: Text({ textId: '@ContainerProperties' })
}} }}
heightClass={`${categoryHeight}`} heightClass={`${categoryHeight}`}
> >
<div className='grid grid-cols-1 gap-y-6 items-center prop-category-body'> <div className='grid grid-cols-1 gap-y-6 items-center prop-category-body'>
<div> <div>
<InputGroup <InputGroup
labelText='Name' labelText={Text({ textId: '@ContainerName' })}
inputKey='id' inputKey='id'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -79,7 +62,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
</div> </div>
<div> <div>
<InputGroup <InputGroup
labelText='Parent name' labelText={Text({ textId: '@ContainerParentName' })}
inputKey='parentId' inputKey='parentId'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -89,7 +72,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
</div> </div>
<div> <div>
<InputGroup <InputGroup
labelText='Type' labelText={Text({ textId: '@ContainerType' })}
inputKey='type' inputKey='type'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -102,7 +85,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<Category category={{ <Category category={{
Type: 'Position', Type: 'Position',
DisplayedText: 'Position' DisplayedText: Text({ textId: '@ContainerPosition' })
}} }}
defaultIsOpen={true} defaultIsOpen={true}
heightClass={`${categoryHeight}`} heightClass={`${categoryHeight}`}
@ -110,7 +93,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<div className='grid grid-cols-3 gap-y-2 items-center prop-category-body'> <div className='grid grid-cols-3 gap-y-2 items-center prop-category-body'>
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-x`} id={`${props.properties.id}-x`}
labelText='x' labelText={Text({ textId: '@ContainerX' })}
inputKey='x' inputKey='x'
labelClassName='' labelClassName=''
inputClassName='col-span-2' inputClassName='col-span-2'
@ -134,7 +117,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
)} /> )} />
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-y`} id={`${props.properties.id}-y`}
labelText='y' labelText={Text({ textId: '@ContainerY' })}
inputKey='y' inputKey='y'
labelClassName='' labelClassName=''
inputClassName='col-span-2' inputClassName='col-span-2'
@ -158,16 +141,17 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
</div> </div>
</Category> </Category>
<Category category={{ <Category
category={{
Type: 'Size', Type: 'Size',
DisplayedText: 'Size' DisplayedText: Text({ textId: '@ContainerSize' })
}} }}
heightClass={`${categoryHeight}`} heightClass={`${categoryHeight}`}
> >
<div className='grid grid-cols-5 gap-y-2 items-center prop-category-body'> <div className='grid grid-cols-5 gap-y-2 items-center prop-category-body'>
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-minWidth`} id={`${props.properties.id}-minWidth`}
labelText='Minimum Width' labelText={Text({ textId: '@ContainerMinWidth' })}
inputKey='minWidth' inputKey='minWidth'
labelClassName='col-span-2' labelClassName='col-span-2'
inputClassName='col-span-3' inputClassName='col-span-3'
@ -177,7 +161,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
onChange={(value) => props.onChange('minWidth', Number(value))} /> onChange={(value) => props.onChange('minWidth', Number(value))} />
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-width`} id={`${props.properties.id}-width`}
labelText='Width' labelText={Text({ textId: '@ContainerWidth' })}
inputKey='width' inputKey='width'
labelClassName='col-span-2' labelClassName='col-span-2'
inputClassName='col-span-3' inputClassName='col-span-3'
@ -189,7 +173,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
isDisabled={props.properties.isFlex} /> isDisabled={props.properties.isFlex} />
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-maxWidth`} id={`${props.properties.id}-maxWidth`}
labelText='Maximum Width' labelText={Text({ textId: '@ContainerMaxWidth' })}
inputKey='maxWidth' inputKey='maxWidth'
labelClassName='col-span-2' labelClassName='col-span-2'
inputClassName='col-span-3' inputClassName='col-span-3'
@ -200,7 +184,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<div className='col-span-5 p-3'></div> <div className='col-span-5 p-3'></div>
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-minHeight`} id={`${props.properties.id}-minHeight`}
labelText='Minimum Height' labelText={Text({ textId: '@ContainerMinHeight' })}
inputKey='minHeight' inputKey='minHeight'
labelClassName='col-span-2' labelClassName='col-span-2'
inputClassName='col-span-3' inputClassName='col-span-3'
@ -210,7 +194,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
onChange={(value) => props.onChange('minHeight', Number(value))} /> onChange={(value) => props.onChange('minHeight', Number(value))} />
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-height`} id={`${props.properties.id}-height`}
labelText='Height' labelText={Text({ textId: '@ContainerHeight' })}
inputKey='height' inputKey='height'
labelClassName='col-span-2' labelClassName='col-span-2'
inputClassName='col-span-3' inputClassName='col-span-3'
@ -223,7 +207,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
/> />
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-maxHeight`} id={`${props.properties.id}-maxHeight`}
labelText='Maximum Height' labelText={Text({ textId: '@ContainerMaxHeight' })}
inputKey='maxHeight' inputKey='maxHeight'
labelClassName='col-span-2' labelClassName='col-span-2'
inputClassName='col-span-3' inputClassName='col-span-3'
@ -236,14 +220,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<Category category={{ <Category category={{
Type: 'Margins', Type: 'Margins',
DisplayedText: 'Margins' DisplayedText: Text({ textId: '@ContainerMargins' })
}} }}
heightClass={`${categoryHeight}`} heightClass={`${categoryHeight}`}
> >
<div className='grid grid-cols-2 items-center gap-y-2 prop-category-body'> <div className='grid grid-cols-2 items-center gap-y-2 prop-category-body'>
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-ml`} id={`${props.properties.id}-ml`}
labelText='Margin left' labelText={Text({ textId: '@ContainerMarginLeft' })}
inputKey='left' inputKey='left'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -253,7 +237,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
onChange={(value) => props.onChange('left', Number(value), PropertyType.Margin)} /> onChange={(value) => props.onChange('left', Number(value), PropertyType.Margin)} />
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-mb`} id={`${props.properties.id}-mb`}
labelText='Margin bottom' labelText={Text({ textId: '@ContainerMarginBottom' })}
inputKey='bottom' inputKey='bottom'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -263,7 +247,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
onChange={(value) => props.onChange('bottom', Number(value), PropertyType.Margin)} /> onChange={(value) => props.onChange('bottom', Number(value), PropertyType.Margin)} />
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-mt`} id={`${props.properties.id}-mt`}
labelText='Margin top' labelText={Text({ textId: '@ContainerMarginTop' })}
inputKey='top' inputKey='top'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -273,7 +257,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
onChange={(value) => props.onChange('top', Number(value), PropertyType.Margin)} /> onChange={(value) => props.onChange('top', Number(value), PropertyType.Margin)} />
<TextInputGroup <TextInputGroup
id={`${props.properties.id}-mr`} id={`${props.properties.id}-mr`}
labelText='Margin right' labelText={Text({ textId: '@ContainerMarginRight' })}
inputKey='right' inputKey='right'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -286,13 +270,13 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<Category category={{ <Category category={{
Type: 'Behaviors', Type: 'Behaviors',
DisplayedText: 'Behaviors' DisplayedText: Text({ textId: '@ContainerBehaviors' })
}} }}
heightClass={`${categoryHeight}`} heightClass={`${categoryHeight}`}
> >
<div className='grid grid-cols-2 items-center gap-y-2 prop-category-body'> <div className='grid grid-cols-2 items-center gap-y-2 prop-category-body'>
<ToggleButton <ToggleButton
labelText='Flex' labelText={Text({ textId: '@ContainerFlex' })}
inputKey='isFlex' inputKey='isFlex'
labelClassName='' labelClassName=''
inputClassName='ml-auto mr-auto block' inputClassName='ml-auto mr-auto block'
@ -301,7 +285,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
onChange={(event) => props.onChange('isFlex', event.target.checked)} onChange={(event) => props.onChange('isFlex', event.target.checked)}
/> />
<ToggleButton <ToggleButton
labelText='Anchor' labelText={Text({ textId: '@ContainerAnchor' })}
inputKey='isAnchor' inputKey='isAnchor'
labelClassName='' labelClassName=''
inputClassName='ml-auto mr-auto block' inputClassName='ml-auto mr-auto block'
@ -313,7 +297,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<Category category={{ <Category category={{
Type: 'Alignment', Type: 'Alignment',
DisplayedText: 'Alignment' DisplayedText: Text({ textId: '@ContainerAlignment' })
}} }}
heightClass={`${categoryHeight}`} heightClass={`${categoryHeight}`}
> >
@ -321,14 +305,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<PositionReferenceSelector <PositionReferenceSelector
id='positionReference' id='positionReference'
name='PositionReference' name='PositionReference'
labelText='Alignment' labelText={Text({ textId: '@ContainerAlignmentInput' })}
value={props.properties.positionReference} value={props.properties.positionReference}
onChange={props.onChange} onChange={props.onChange}
/> />
<div className='p-3'></div> <div className='p-3'></div>
<Select <Select
inputKey='linkedSymbolId' inputKey='linkedSymbolId'
labelText='Align with symbol' labelText={Text({ textId: '@ContainerAlignWithSymbol' })}
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
inputs={[...props.symbols.values()].map(symbol => ({ inputs={[...props.symbols.values()].map(symbol => ({
@ -343,7 +327,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<Category category={{ <Category category={{
Type: 'Dimensions', Type: 'Dimensions',
DisplayedText: 'Dimensions' DisplayedText: Text({ textId: '@ContainerDimensions' })
}} }}
heightClass={`${categoryHeight}`} heightClass={`${categoryHeight}`}
> >
@ -354,7 +338,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<PositionCheckboxes <PositionCheckboxes
id='showSelfDimensions' id='showSelfDimensions'
name='ShowSelfDimensions' name='ShowSelfDimensions'
labelText='Show dimension' labelText={Text({ textId: '@ContainerShowDimension' })}
value={props.properties.showSelfDimensions} value={props.properties.showSelfDimensions}
onChange={props.onChange} onChange={props.onChange}
/> />
@ -366,7 +350,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<PositionCheckboxes <PositionCheckboxes
id='showChildrenDimensions' id='showChildrenDimensions'
name='ShowChildrenDimensions' name='ShowChildrenDimensions'
labelText='Show overall dimension of its children' labelText={Text({ textId: '@ContainerShowChildrenDimension' })}
value={props.properties.showChildrenDimensions} value={props.properties.showChildrenDimensions}
onChange={props.onChange} onChange={props.onChange}
/> />
@ -380,7 +364,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
id='markPosition' id='markPosition'
name='MarkPosition' name='MarkPosition'
value={props.properties.markPosition} value={props.properties.markPosition}
labelText='Mark the position' labelText={Text({ textId: '@ContainerMarkPosition' })}
onChange={props.onChange} onChange={props.onChange}
/> />
</div> </div>
@ -388,7 +372,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
<PositionCheckboxes <PositionCheckboxes
id='showDimensionWithMarks' id='showDimensionWithMarks'
name='ShowDimensionWithMarks' name='ShowDimensionWithMarks'
labelText='Show dimension with marked children' labelText={Text({ textId: '@ContainerShowDimensionWithMarks' })}
value={props.properties.showDimensionWithMarks} value={props.properties.showDimensionWithMarks}
onChange={props.onChange} onChange={props.onChange}
/> />
@ -398,16 +382,73 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
</div> </div>
</Category> </Category>
{ props.properties.style !== undefined &&
<Category category={{ <Category category={{
Type: 'Style', Type: 'Style',
DisplayedText: 'Style' DisplayedText: Text({ textId: '@ContainerStyle' })
}} }}
heightClass={`${categoryHeight}`} heightClass={`${categoryHeight}`}
> >
<div className='grid grid-cols-5 gap-6 items-center prop-category-body'> <div className='grid grid-cols-5 gap-6 items-center prop-category-body'>
{GetCSSInputs(props.properties, props.onChange)} <TextInputGroup
id={`${props.properties.id}-stroke`}
labelText={Text({ textId: '@StyleStroke' })}
inputKey='stroke'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='string'
value={props.properties.style.stroke ?? 'black'}
onChange={(value) => props.onChange('stroke', value, PropertyType.Style)}
/>
<InputGroup
labelKey={`${props.properties.id}-strokeOpacity`}
labelText={Text({ textId: '@StyleStrokeOpacity' })}
inputKey='strokeOpacity'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='range'
min={0}
max={1}
step={0.01}
value={(props.properties.style.strokeOpacity ?? 1).toString()}
onChange={(event) => props.onChange('strokeOpacity', Number(event.target.value), PropertyType.Style)}
/>
<TextInputGroup
id={`${props.properties.id}-strokeWidth`}
labelText={Text({ textId: '@StyleStrokeWidth' })}
inputKey='strokeWidth'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='number'
value={(props.properties.style.strokeWidth ?? 1).toString()}
onChange={(value) => props.onChange('strokeWidth', Number(value), PropertyType.Style)}
/>
<TextInputGroup
id={`${props.properties.id}-fill`}
labelText={Text({ textId: '@StyleFill' })}
inputKey='fill'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='string'
value={props.properties.style.fill ?? 'black'}
onChange={(value) => props.onChange('fill', value, PropertyType.Style)}
/>
<InputGroup
labelKey={`${props.properties.id}-fillOpacity`}
labelText={Text({ textId: '@StyleFillOpacity' })}
inputKey='fillOpacity'
labelClassName='col-span-2'
inputClassName='col-span-3'
type='range'
min={0}
max={1}
step={0.01}
value={(props.properties.style.fillOpacity ?? 1).toString()}
onChange={(event) => props.onChange('fillOpacity', Number(event.target.value), PropertyType.Style)}
/>
</div> </div>
</Category> </Category>
}
</div> </div>
); );
} }

View file

@ -5,14 +5,14 @@
import { AddMethod } from '../../../Enums/AddMethod'; import { AddMethod } from '../../../Enums/AddMethod';
import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer'; import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer';
import { IConfiguration } from '../../../Interfaces/IConfiguration'; import { IConfiguration } from '../../../Interfaces/IConfiguration';
import { IContainerModel, ContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { IHistoryState } from '../../../Interfaces/IHistoryState'; import { IHistoryState } from '../../../Interfaces/IHistoryState';
import { IPattern, GetPattern, ContainerOrPattern } from '../../../Interfaces/IPattern'; import { IPattern, GetPattern, ContainerOrPattern } from '../../../Interfaces/IPattern';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { Orientation } from '../../../Enums/Orientation'; import { Orientation } from '../../../Enums/Orientation';
import { GetDefaultContainerProps } from '../../../utils/default'; import { GetDefaultContainerProps } from '../../../utils/default';
import { FindContainerById } from '../../../utils/itertools'; import { FindContainerById } from '../../../utils/itertools';
import { ApplyMargin } from '../../../utils/svg'; import { ApplyMargin, RestoreX, RestoreY } from '../../../utils/svg';
import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors'; import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
import { GetCurrentHistory, UpdateCounters } from '../Editor'; import { GetCurrentHistory, UpdateCounters } from '../Editor';
import { SortChildren } from './ContainerOperations'; import { SortChildren } from './ContainerOperations';
@ -59,7 +59,10 @@ export function AddContainers(
configuration: IConfiguration, configuration: IConfiguration,
fullHistory: IHistoryState[], fullHistory: IHistoryState[],
historyCurrentStep: number historyCurrentStep: number
): IHistoryState[] { ): {
history: IHistoryState[]
newContainers: IContainerModel[]
} {
const history = GetCurrentHistory(fullHistory, historyCurrentStep); const history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1]; const current = history[history.length - 1];
@ -77,18 +80,27 @@ export function AddContainers(
// Deep clone the counters // Deep clone the counters
const newCounters = Object.assign({}, current.typeCounters); const newCounters = Object.assign({}, current.typeCounters);
// containerIds is used for logging purpose (see setHistory below)
const containerIds: string[] = [];
// Iterate over the containers // Iterate over the containers
const newContainers: IContainerModel[] = [];
availableContainers.forEach((availableContainer, typeIndex) => { availableContainers.forEach((availableContainer, typeIndex) => {
// Get the preset properties from the API // Get the preset properties from the API
AddNewContainerToParent(availableContainer, configuration, containers, parentClone, index, typeIndex, newCounters, current.symbols, containerIds); const newContainer = AddNewContainerToParent(
availableContainer,
configuration,
containers,
parentClone,
index,
typeIndex,
newCounters,
current.symbols
);
newContainers.push(newContainer);
}); });
// Update the state // Update the state
const containersIds = newContainers.map(container => container.properties.id);
history.push({ history.push({
lastAction: `Add [${containerIds.join(', ')}] in ${parentClone.properties.id}`, lastAction: `Add [${containersIds.join(', ')}] in ${parentClone.properties.id}`,
mainContainer: current.mainContainer, mainContainer: current.mainContainer,
selectedContainerId: parentClone.properties.id, selectedContainerId: parentClone.properties.id,
containers, containers,
@ -97,7 +109,10 @@ export function AddContainers(
selectedSymbolId: current.selectedSymbolId selectedSymbolId: current.selectedSymbolId
}); });
return history; return {
history,
newContainers
};
} }
function AddNewContainerToParent( function AddNewContainerToParent(
@ -109,9 +124,8 @@ function AddNewContainerToParent(
typeIndex: number, typeIndex: number,
newCounters: Record<string, number>, newCounters: Record<string, number>,
symbols: Map<string, ISymbolModel>, symbols: Map<string, ISymbolModel>,
containerIds: string[] = [],
initChilds: boolean = true initChilds: boolean = true
): ContainerModel { ): IContainerModel {
const type = availableContainer.Type; const type = availableContainer.Type;
const defaultConfig = configuration.AvailableContainers const defaultConfig = configuration.AvailableContainers
@ -121,7 +135,7 @@ function AddNewContainerToParent(
throw new Error(`[AddContainer] Object type not found among default config. Found: ${type}`); throw new Error(`[AddContainer] Object type not found among default config. Found: ${type}`);
} }
const containerConfig = Object.assign(defaultConfig, availableContainer); const containerConfig = Object.assign(structuredClone(defaultConfig), availableContainer);
// Default margin // Default margin
const left: number = containerConfig.Margin?.left ?? 0; const left: number = containerConfig.Margin?.left ?? 0;
@ -130,10 +144,10 @@ function AddNewContainerToParent(
const right: number = containerConfig.Margin?.right ?? 0; const right: number = containerConfig.Margin?.right ?? 0;
// Default coordinates // Default coordinates
let x = containerConfig.X ?? 0;
let y = containerConfig.Y ?? 0;
let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width; let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width;
let height = containerConfig.Height ?? containerConfig.MaxHeight ?? containerConfig.MinHeight ?? parentClone.properties.height; let height = containerConfig.Height ?? containerConfig.MaxHeight ?? containerConfig.MinHeight ?? parentClone.properties.height;
let x = RestoreX(containerConfig.X ?? 0, width, containerConfig.PositionReference);
let y = RestoreY(containerConfig.Y ?? 0, height, containerConfig.PositionReference);
({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right)); ({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
@ -156,13 +170,11 @@ function AddNewContainerToParent(
); );
// Create the container // Create the container
const newContainer = new ContainerModel( const newContainer: IContainerModel = {
defaultProperties, properties: defaultProperties,
[], children: [],
{ userData: {}
type };
}
);
// Register the container in the hashmap // Register the container in the hashmap
containers.set(newContainer.properties.id, newContainer); containers.set(newContainer.properties.id, newContainer);
@ -207,9 +219,6 @@ function AddNewContainerToParent(
} }
} }
// Add to the list of container id for logging purpose
containerIds.push(newContainer.properties.id);
return newContainer; return newContainer;
} }
@ -234,7 +243,7 @@ export function AddContainer(
historyCurrentStep: number historyCurrentStep: number
): IHistoryState[] { ): IHistoryState[] {
// just call AddContainers with an array on a single element // just call AddContainers with an array on a single element
return AddContainers( const { history } = AddContainers(
index, index,
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
[{ Type: type }], [{ Type: type }],
@ -243,6 +252,7 @@ export function AddContainer(
fullHistory, fullHistory,
historyCurrentStep historyCurrentStep
); );
return history;
} }
/** /**
@ -254,7 +264,7 @@ export function AddContainer(
* @returns * @returns
*/ */
function InitializeDefaultChild( function InitializeDefaultChild(
newContainer: ContainerModel, newContainer: IContainerModel,
configuration: IConfiguration, configuration: IConfiguration,
containerConfig: IAvailableContainer, containerConfig: IAvailableContainer,
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
@ -285,7 +295,7 @@ function InitializeDefaultChild(
} }
function InitializeChildrenWithPattern( function InitializeChildrenWithPattern(
newContainer: ContainerModel, newContainer: IContainerModel,
configuration: IConfiguration, configuration: IConfiguration,
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
containerConfig: IAvailableContainer, containerConfig: IAvailableContainer,
@ -326,7 +336,7 @@ function InitializeChildrenWithPattern(
*/ */
function BuildPatterns( function BuildPatterns(
rootPattern: ContainerOrPattern, rootPattern: ContainerOrPattern,
newContainer: ContainerModel, newContainer: IContainerModel,
configuration: IConfiguration, configuration: IConfiguration,
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
newCounters: Record<string, number>, newCounters: Record<string, number>,
@ -432,7 +442,6 @@ function AddContainerInLevel(
0, 0, 0, 0,
newCounters, newCounters,
symbols, symbols,
undefined,
false false
); );

View file

@ -1,5 +1,5 @@
import { IHistoryState } from '../../../Interfaces/IHistoryState'; import { IHistoryState } from '../../../Interfaces/IHistoryState';
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { FindContainerById, MakeDFSIterator } from '../../../utils/itertools'; import { FindContainerById, MakeDFSIterator } from '../../../utils/itertools';
import { GetCurrentHistory } from '../Editor'; import { GetCurrentHistory } from '../Editor';
import { ApplyBehaviors, ApplyBehaviorsOnSiblings, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors'; import { ApplyBehaviors, ApplyBehaviorsOnSiblings, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
@ -176,7 +176,7 @@ export function OnPropertyChange(
} }
const containers = structuredClone(current.containers); const containers = structuredClone(current.containers);
const container: ContainerModel | undefined = FindContainerById(containers, selected.properties.id); const container: IContainerModel | undefined = FindContainerById(containers, 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!');
@ -270,7 +270,7 @@ export function SortChildren(
*/ */
function SetContainer( function SetContainer(
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
container: ContainerModel, container: IContainerModel,
key: string, value: string | number | boolean | number[], key: string, value: string | number | boolean | number[],
type: PropertyType, type: PropertyType,
symbols: Map<string, ISymbolModel> symbols: Map<string, ISymbolModel>
@ -282,11 +282,12 @@ function SetContainer(
AssignProperty(container, key, value, type); AssignProperty(container, key, value, type);
// link the symbol if it exists // link the symbol if it exists
const oldSymbol = symbols.get(oldSymbolId);
const newSymbol = symbols.get(container.properties.linkedSymbolId);
LinkSymbol( LinkSymbol(
container.properties.id, container.properties.id,
oldSymbolId, oldSymbol,
container.properties.linkedSymbolId, newSymbol
symbols
); );
// Apply special behaviors: rigid, flex, symbol, anchor // Apply special behaviors: rigid, flex, symbol, anchor
@ -309,7 +310,7 @@ function SetContainer(
* @param value Value of the property * @param value Value of the property
* @param type Type of the property * @param type Type of the property
*/ */
function AssignProperty(container: ContainerModel, key: string, value: string | number | boolean | number[], type: PropertyType): void { function AssignProperty(container: IContainerModel, key: string, value: string | number | boolean | number[], 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;
@ -357,15 +358,11 @@ function AssignProperty(container: ContainerModel, key: string, value: string |
* @param symbols Current list of symbols * @param symbols Current list of symbols
* @returns * @returns
*/ */
function LinkSymbol( export function LinkSymbol(
containerId: string, containerId: string,
oldSymbolId: string, oldSymbol: ISymbolModel | undefined,
newSymbolId: string, newSymbol: ISymbolModel | undefined
symbols: Map<string, ISymbolModel>
): void { ): void {
const oldSymbol = symbols.get(oldSymbolId);
const newSymbol = symbols.get(newSymbolId);
if (newSymbol === undefined) { if (newSymbol === undefined) {
if (oldSymbol !== undefined) { if (oldSymbol !== undefined) {
oldSymbol.linkedContainers.delete(containerId); oldSymbol.linkedContainers.delete(containerId);

View file

@ -15,6 +15,7 @@ import { GetCurrentHistoryState } from '../Editor';
import { AddContainers } from './AddContainer'; import { AddContainers } from './AddContainer';
import { DeleteContainer } from './ContainerOperations'; import { DeleteContainer } from './ContainerOperations';
import { DeleteSymbol } from './SymbolOperations'; import { DeleteSymbol } from './SymbolOperations';
import { Text } from '../../Text/Text';
export function InitActions( export function InitActions(
menuActions: Map<string, IMenuAction[]>, menuActions: Map<string, IMenuAction[]>,
@ -28,8 +29,8 @@ export function InitActions(
'', '',
[ [
{ {
text: 'Undo', text: Text({ textId: '@Undo' }),
title: 'Undo last action', title: Text({ textId: '@UndoTitle' }),
shortcut: '<kbd>Ctrl</kbd>+<kbd>Z</kbd>', shortcut: '<kbd>Ctrl</kbd>+<kbd>Z</kbd>',
action: () => { action: () => {
if (historyCurrentStep <= 0) { if (historyCurrentStep <= 0) {
@ -39,8 +40,8 @@ export function InitActions(
} }
}, },
{ {
text: 'Redo', text: Text({ textId: '@Redo' }),
title: 'Redo last action', title: Text({ textId: '@RedoTitle' }),
shortcut: '<kbd>Ctrl</kbd>+<kbd>Y</kbd>', shortcut: '<kbd>Ctrl</kbd>+<kbd>Y</kbd>',
action: () => { action: () => {
if (historyCurrentStep >= history.length - 1) { if (historyCurrentStep >= history.length - 1) {
@ -55,8 +56,8 @@ export function InitActions(
menuActions.set( menuActions.set(
'elements-sidebar-row', 'elements-sidebar-row',
[{ [{
text: 'Delete', text: Text({ textId: '@DeleteContainer' }),
title: 'Delete the container', title: Text({ textId: '@DeleteContainerTitle' }),
shortcut: '<kbd>Suppr</kbd>', shortcut: '<kbd>Suppr</kbd>',
action: (target: HTMLElement) => { action: (target: HTMLElement) => {
const id = target.id; const id = target.id;
@ -73,8 +74,8 @@ export function InitActions(
menuActions.set( menuActions.set(
'symbols-sidebar-row', 'symbols-sidebar-row',
[{ [{
text: 'Delete', text: Text({ textId: '@DeleteSymbol' }),
title: 'Delete the container', title: Text({ textId: '@DeleteSymbolTitle' }),
shortcut: '<kbd>Suppr</kbd>', shortcut: '<kbd>Suppr</kbd>',
action: (target: HTMLElement) => { action: (target: HTMLElement) => {
const id = target.id; const id = target.id;
@ -200,21 +201,23 @@ function HandleSetContainerList(
const addingBehavior = response.AddingBehavior ?? action.AddingBehavior; const addingBehavior = response.AddingBehavior ?? action.AddingBehavior;
const current = GetCurrentHistoryState(history, historyCurrentStep); const current = GetCurrentHistoryState(history, historyCurrentStep);
const containers = current.containers; const containers = current.containers;
const parent = FindContainerById(containers, selectedContainer.properties.parentId);
switch (addingBehavior) { switch (addingBehavior) {
case AddMethod.Append: case AddMethod.Insert:
setNewHistory( case AddMethod.Append: {
AddContainers( response.Containers.forEach(config => {
config.AddMethod = config.AddMethod ?? addingBehavior;
});
const { history: newHistory } = AddContainers(
selectedContainer.children.length, selectedContainer.children.length,
response.Containers, response.Containers,
selectedContainer.properties.id, selectedContainer.properties.id,
configuration, configuration,
history, history,
historyCurrentStep historyCurrentStep);
));
setNewHistory(newHistory);
break; break;
case AddMethod.Insert: }
throw new Error('Not yet supported');
case AddMethod.Replace: case AddMethod.Replace:
setNewHistory( setNewHistory(
HandleReplace( HandleReplace(
@ -227,7 +230,8 @@ function HandleSetContainerList(
) )
); );
break; break;
case AddMethod.ReplaceParent: case AddMethod.ReplaceParent: {
const parent = FindContainerById(containers, selectedContainer.properties.parentId);
if (parent === undefined || parent === null) { if (parent === undefined || parent === null) {
Swal.fire({ Swal.fire({
title: 'Error', title: 'Error',
@ -249,6 +253,7 @@ function HandleSetContainerList(
break; break;
} }
} }
}
function HandleReplace( function HandleReplace(
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
@ -271,7 +276,50 @@ function HandleReplace(
historyCurrentStep historyCurrentStep
); );
const newHistoryBeforeDelete = AddContainers( const { history: newHistoryBeforeDelete } = AddContainers(
index,
response.Containers,
selectedContainer.properties.parentId,
configuration,
newHistoryAfterDelete,
newHistoryAfterDelete.length - 1
);
// Remove AddContainers from history
if (import.meta.env.PROD) {
newHistoryBeforeDelete.splice(newHistoryBeforeDelete.length - 2, 1);
}
// Rename the last action by Replace
const types = response.Containers.map(container => container.Type);
newHistoryBeforeDelete[newHistoryBeforeDelete.length - 1].lastAction =
`Replace ${selectedContainer.properties.id} by [${types.join(', ')}]`;
return newHistoryBeforeDelete;
}
function HandleInsert(
containers: Map<string, IContainerModel>,
selectedContainer: IContainerModel,
response: ISetContainerListResponse,
configuration: IConfiguration,
history: IHistoryState[],
historyCurrentStep: number
): IHistoryState[] {
const parent = FindContainerById(containers, selectedContainer.properties.id);
if (parent === undefined || parent === null) {
throw new Error('[InsertContainer] Cannot insert in a container that does not exists');
}
const index = parent.children.indexOf(selectedContainer.properties.id);
const newHistoryAfterDelete = DeleteContainer(
selectedContainer.properties.id,
history,
historyCurrentStep
);
const { history: newHistoryBeforeDelete } = AddContainers(
index, index,
response.Containers, response.Containers,
selectedContainer.properties.parentId, selectedContainer.properties.parentId,

View file

@ -6,7 +6,9 @@ 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, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors'; import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
import { GetCurrentHistory, UpdateCounters } from '../Editor'; import { GetCurrentHistory, GetCurrentHistoryState, UpdateCounters } from '../Editor';
import { AddContainers } from './AddContainer';
import { LinkSymbol } from './ContainerOperations';
export function AddSymbol( export function AddSymbol(
name: string, name: string,
@ -14,7 +16,7 @@ export function AddSymbol(
fullHistory: IHistoryState[], fullHistory: IHistoryState[],
historyCurrentStep: number historyCurrentStep: number
): IHistoryState[] { ): IHistoryState[] {
const history = GetCurrentHistory(fullHistory, historyCurrentStep); let history = GetCurrentHistory(fullHistory, historyCurrentStep);
const current = history[history.length - 1]; const current = history[history.length - 1];
const symbolConfig = configuration.AvailableSymbols const symbolConfig = configuration.AvailableSymbols
@ -24,24 +26,48 @@ 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 typeCounters = structuredClone(current.typeCounters);
UpdateCounters(newCounters, type); UpdateCounters(typeCounters, type);
const newSymbols = structuredClone(current.symbols); const newSymbols = structuredClone(current.symbols);
const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, newCounters, type, symbolConfig); const newSymbol: ISymbolModel = GetDefaultSymbolModel(name, typeCounters, type, symbolConfig);
const containers = structuredClone(current.containers);
newSymbol.x = RestoreX(newSymbol.x, newSymbol.width, newSymbol.config.PositionReference); newSymbol.x = RestoreX(newSymbol.x, newSymbol.width, newSymbol.config.PositionReference);
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: current.mainContainer,
containers: structuredClone(current.containers), containers,
selectedContainerId: current.selectedContainerId, selectedContainerId: current.selectedContainerId,
typeCounters: newCounters, typeCounters,
symbols: newSymbols, symbols: newSymbols,
selectedSymbolId: newSymbol.id selectedSymbolId: newSymbol.id
}); });
if (symbolConfig.AssociatedContainer !== undefined) {
const {
history: newHistory,
newContainers
} = AddContainers(
0,
[symbolConfig.AssociatedContainer],
current.mainContainer,
configuration,
history,
historyCurrentStep + 1
);
history = newHistory;
const newCurrent = GetCurrentHistoryState(newHistory, historyCurrentStep + 2);
const newerSymbol = newCurrent.symbols.get(newSymbol.id);
newContainers.forEach((newContainer) => {
LinkContainer(newerSymbol, newContainer, newSymbols);
});
}
return history; return history;
} }
@ -55,7 +81,7 @@ export function SelectSymbol(
history.push({ history.push({
lastAction: `Select ${symbolId}`, lastAction: `Select ${symbolId}`,
mainContainer: structuredClone(current.mainContainer), mainContainer: current.mainContainer,
containers: structuredClone(current.containers), containers: structuredClone(current.containers),
selectedContainerId: current.selectedContainerId, selectedContainerId: current.selectedContainerId,
typeCounters: structuredClone(current.typeCounters), typeCounters: structuredClone(current.typeCounters),
@ -166,3 +192,20 @@ export function OnPropertyChange(
}); });
return history; return history;
} }
/**
* Link a container to a symbol.
* If symbol is undefined, unlink the previous symbol of the container
* @param symbol
* @param container
* @param symbols
*/
function LinkContainer(
symbol: ISymbolModel | undefined,
container: IContainerModel,
symbols: Map<string, ISymbolModel>
): void {
const oldSymbol = symbols.get(container.properties.linkedSymbolId);
LinkSymbol(container.properties.id, oldSymbol, symbol);
container.properties.linkedSymbolId = symbol !== undefined ? symbol.id : '';
}

View file

@ -135,7 +135,6 @@ export function ElementsList(props: IElementsListProps): JSX.Element {
}): JSX.Element { }): JSX.Element {
const { container, depth } = containers[index]; const { container, depth } = containers[index];
const key = container.properties.id.toString(); const key = container.properties.id.toString();
const tabs = '|\t'.repeat(depth);
const text = container.properties.displayedText === key const text = container.properties.displayedText === key
? `${key}` ? `${key}`
: `${container.properties.displayedText}`; : `${container.properties.displayedText}`;
@ -144,29 +143,19 @@ export function ElementsList(props: IElementsListProps): JSX.Element {
props.selectedContainer !== null && props.selectedContainer !== null &&
props.selectedContainer.properties.id === container.properties.id; props.selectedContainer.properties.id === container.properties.id;
const selectedClass: string = isSelected
? 'border-l-4 bg-blue-500 shadow-lg shadow-blue-500/60 hover:bg-blue-600 hover:shadow-blue-500 text-slate-50'
: 'bg-slate-300/60 hover:bg-slate-400 hover:shadow-slate-400';
return ( return (
<button type="button" ElementsListRow(
className={`transition-all w-full border-blue-500 hover:shadow-lg elements-sidebar-row whitespace-pre key,
text-left text-sm font-medium inline-flex ${container.properties.type} ${selectedClass}`} container,
id={key} depth,
key={key} isSelected,
style={style} style,
title={container.properties.warning} text,
onClick={() => props.selectContainer(container.properties.id)} props.containers,
onDrop={(event) => HandleOnDrop(event, props.containers, props.mainContainer, props.addContainer)} props.mainContainer,
onDragOver={(event) => HandleDragOver(event, props.mainContainer)} props.addContainer,
onDragLeave={(event) => HandleDragLeave(event)} props.selectContainer
> )
{tabs}
{text}
{container.properties.warning.length > 0 &&
<ExclamationTriangleIcon className='w-8'/>
}
</button>
); );
} }
@ -193,3 +182,51 @@ export function ElementsList(props: IElementsListProps): JSX.Element {
</div> </div>
); );
} }
function ElementsListRow(
key: string,
container: IContainerModel,
depth: number,
isSelected: boolean,
style: React.CSSProperties,
text: string,
containers: Map<string, IContainerModel>,
mainContainer: IContainerModel,
addContainer: (index: number, type: string, parent: string) => void,
selectContainer: (containerId: string) => void
): JSX.Element {
const verticalBars: JSX.Element[] = [];
const verticalBarSelectedClass = isSelected
? 'border-l-blue-400 group-hover:border-l-blue-300'
: 'border-l-slate-400 group-hover:border-l-slate-300';
for (let i = 0; i < depth; i++) {
verticalBars.push(
<span
key={`${key}-${i}`}
className={`h-full border-l-2 pr-2 ${verticalBarSelectedClass}`}
></span>
);
}
const buttonSelectedClass: string = isSelected
? 'bg-blue-500 shadow-lg shadow-blue-500/60 hover:bg-blue-600 hover:shadow-blue-500 text-slate-50'
: 'bg-slate-300/60 hover:bg-slate-400 hover:shadow-slate-400';
return <button type="button"
className={`transition-all border-blue-500 hover:shadow-lg elements-sidebar-row whitespace-pre
text-left text-sm font-medium flex items-center align-middle group ${container.properties.type} ${buttonSelectedClass}`}
id={key}
key={key}
style={style}
title={container.properties.warning}
onClick={() => selectContainer(container.properties.id)}
onDrop={(event) => HandleOnDrop(event, containers, mainContainer, addContainer)}
onDragOver={(event) => HandleDragOver(event, mainContainer)}
onDragLeave={(event) => HandleDragLeave(event)}
>
{verticalBars}
{text}
{container.properties.warning.length > 0 &&
<ExclamationTriangleIcon className='pl-2 w-7' />}
</button>;
}

View file

@ -13,6 +13,7 @@ interface IInputGroupProps {
defaultChecked?: boolean defaultChecked?: boolean
min?: number min?: number
max?: number max?: number
step?: number
isDisabled?: boolean isDisabled?: boolean
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
} }
@ -39,6 +40,7 @@ export function InputGroup(props: IInputGroupProps): JSX.Element {
onChange={props.onChange} onChange={props.onChange}
min={props.min} min={props.min}
max={props.max} max={props.max}
step={props.step}
disabled={props.isDisabled} /> disabled={props.isDisabled} />
</>; </>;
} }

View file

@ -0,0 +1,31 @@
import * as React from 'react';
import { createContext, useState } from 'react';
import { ILanguage } from '../../Interfaces/ILanguage';
import { languageOptions, translations } from '../../Translations/Translations';
import { DEFAULT_LANGUAGE } from '../../utils/default';
// eslint-disable-next-line @typescript-eslint/naming-convention
export const LanguageContext = createContext<ILanguage>({
language: DEFAULT_LANGUAGE,
dictionary: translations.en
});
export function LanguageProvider({ children }: { children: React.ReactNode | React.ReactNode[] | undefined }): JSX.Element {
const [language, setLanguage] = useState(DEFAULT_LANGUAGE);
const provider = {
language,
dictionary: translations[language],
languageChange: (selected: string) => {
const newLanguage = languageOptions[selected] !== undefined ? selected : DEFAULT_LANGUAGE;
setLanguage(newLanguage);
}
};
return (
<LanguageContext.Provider
value={provider}
>
{ children }
</LanguageContext.Provider>
);
};

View file

@ -1,6 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { FAST_BOOT } from '../../utils/default'; import { Text } from '../Text/Text';
import { Loader } from '../Loader/Loader';
interface IMainMenuProps { interface IMainMenuProps {
newEditor: () => void newEditor: () => void
@ -10,21 +9,11 @@ interface IMainMenuProps {
enum WindowState { enum WindowState {
Main, Main,
Load, Load,
Loading,
} }
export function MainMenu(props: IMainMenuProps): JSX.Element { export function MainMenu(props: IMainMenuProps): JSX.Element {
const [windowState, setWindowState] = React.useState(WindowState.Main); const [windowState, setWindowState] = React.useState(WindowState.Main);
if (FAST_BOOT) {
props.newEditor();
return (
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
<Loader />
</div>
);
}
switch (windowState) { switch (windowState) {
case WindowState.Load: case WindowState.Load:
return ( return (
@ -65,12 +54,6 @@ export function MainMenu(props: IMainMenuProps): JSX.Element {
</div> </div>
); );
case WindowState.Loading:
return (
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
<Loader />
</div>
);
default: default:
return ( return (
<div className='absolute bg-blue-50 p-12 <div className='absolute bg-blue-50 p-12
@ -80,10 +63,17 @@ export function MainMenu(props: IMainMenuProps): JSX.Element {
w-full sm:w-auto w-full sm:w-auto
sm:top-1/2 sm:left-1/2 sm:-translate-x-1/2 sm:-translate-y-1/2'> sm:top-1/2 sm:left-1/2 sm:-translate-x-1/2 sm:-translate-y-1/2'>
<button type="button" className='mainmenu-btn' onClick={() => { <button type="button" className='mainmenu-btn' onClick={() => {
setWindowState(WindowState.Loading);
props.newEditor(); props.newEditor();
}}>Start from scratch</button> }}>
<button type="button" className='mainmenu-btn' onClick={() => setWindowState(WindowState.Load)}>Load a configuration file</button> {Text({ textId: '@StartFromScratch' })}
</button>
<button
type="button"
className='mainmenu-btn'
onClick={() => setWindowState(WindowState.Load)}
>
{Text({ textId: '@LoadConfigFile' })}
</button>
</div> </div>
); );
} }

View file

@ -7,6 +7,7 @@ import { IMessage } from '../../Interfaces/IMessage';
import { DISABLE_API } from '../../utils/default'; import { DISABLE_API } from '../../utils/default';
import { GetCircularReplacer } from '../../utils/saveload'; import { GetCircularReplacer } from '../../utils/saveload';
import { TITLE_BAR_HEIGHT } from '../Sidebar/Sidebar'; import { TITLE_BAR_HEIGHT } from '../Sidebar/Sidebar';
import { Text } from '../Text/Text';
interface IMessagesProps { interface IMessagesProps {
historyState: IHistoryState historyState: IHistoryState
@ -49,8 +50,8 @@ export function Messages(props: IMessagesProps): JSX.Element {
<button <button
onClick={() => { props.clearMessage(); }} onClick={() => { props.clearMessage(); }}
className='h-full hover:bg-slate-400 rounded-lg p-1' className='h-full hover:bg-slate-400 rounded-lg p-1'
aria-label='Clear all messages' aria-label={Text({ textId: '@ClearAllMessages' })}
title='Clear all messages' title={Text({ textId: '@ClearAllMessages' })}
> >
<TrashIcon className='heroicon'></TrashIcon> <TrashIcon className='heroicon'></TrashIcon>
</button> </button>

View file

@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } 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';
@ -7,13 +7,13 @@ import { Dimension } from './Dimension';
interface IDimensionLayerProps { interface IDimensionLayerProps {
containers: Map<string, IContainerModel> containers: Map<string, IContainerModel>
roots: ContainerModel | ContainerModel[] | null roots: IContainerModel | IContainerModel[] | null
scale?: number scale?: number
} }
function GetDimensionsNodes( function GetDimensionsNodes(
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
root: ContainerModel, root: IContainerModel,
scale: number scale: number
): React.ReactNode[] { ): React.ReactNode[] {
const it = MakeBFSIterator(root, containers); const it = MakeBFSIterator(root, containers);

View file

@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { Orientation } from '../../../Enums/Orientation'; import { Orientation } from '../../../Enums/Orientation';
import { Position } from '../../../Enums/Position'; import { Position } from '../../../Enums/Position';
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { DIMENSION_MARGIN, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../../utils/default'; import { DIMENSION_MARGIN, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS, SHOW_SELF_DIMENSIONS } from '../../../utils/default';
import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools'; import { FindContainerById, MakeRecursionDFSIterator, Pairwise } from '../../../utils/itertools';
import { TransformX, TransformY } from '../../../utils/svg'; import { TransformX, TransformY } from '../../../utils/svg';
@ -9,7 +9,7 @@ import { Dimension } from './Dimension';
interface IDimensionLayerProps { interface IDimensionLayerProps {
containers: Map<string, IContainerModel> containers: Map<string, IContainerModel>
root: ContainerModel root: IContainerModel
scale: number scale: number
} }
@ -29,16 +29,18 @@ function ActionByPosition(
dimMapped: number[], dimMapped: number[],
positions: Position[], positions: Position[],
horizontalAction: (dim: number, ...params: any[]) => void, horizontalAction: (dim: number, ...params: any[]) => void,
verticalAction: (dim: number, ...params: any[]) => void, verticalAction: (dim: number, isRight: boolean, ...params: any[]) => void,
params: any[] params: any[]
): void { ): void {
positions.forEach((position: Position) => { positions.forEach((position: Position) => {
const dim = dimMapped[position]; const dim = dimMapped[position];
switch (position) { switch (position) {
case Position.Left: case Position.Left:
case Position.Right: case Position.Right: {
verticalAction(dim, ...params); const isRight = position === Position.Right;
verticalAction(dim, isRight, ...params);
break; break;
}
case Position.Down: case Position.Down:
case Position.Up: case Position.Up:
horizontalAction(dim, ...params); horizontalAction(dim, ...params);
@ -179,7 +181,7 @@ function AddHorizontalChildrenDimension(
} }
const textChildren = (xChildrenEnd - xChildrenStart) const textChildren = (xChildrenEnd - xChildrenStart)
.toFixed(2) .toFixed(0)
.toString(); .toString();
const offset = currentTransform[0] + container.properties.x; const offset = currentTransform[0] + container.properties.x;
@ -197,6 +199,7 @@ function AddHorizontalChildrenDimension(
function AddVerticalChildrenDimension( function AddVerticalChildrenDimension(
xDim: number, xDim: number,
isRight: boolean,
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
container: IContainerModel, container: IContainerModel,
currentTransform: [number, number], currentTransform: [number, number],
@ -240,10 +243,15 @@ function AddVerticalChildrenDimension(
} }
const textChildren = (yChildrenEnd - yChildrenStart) const textChildren = (yChildrenEnd - yChildrenStart)
.toFixed(2) .toFixed(0)
.toString(); .toString();
const offset = currentTransform[0] + container.properties.x; const offset = currentTransform[0] + container.properties.x;
if (!isRight) {
[yChildrenStart, yChildrenEnd] = [yChildrenEnd, yChildrenStart];
}
dimensions.push(<Dimension dimensions.push(<Dimension
key={childrenId} key={childrenId}
id={childrenId} id={childrenId}
@ -296,6 +304,11 @@ function AddHorizontalBorrowerDimension(
let count = 0; let count = 0;
for (const { cur, next } of Pairwise(marks)) { for (const { cur, next } of Pairwise(marks)) {
const id = `dim-y${yDim.toFixed(0)}-borrow-${container.properties.id}-{${count}}`; const id = `dim-y${yDim.toFixed(0)}-borrow-${container.properties.id}-{${count}}`;
const value = next - cur;
if (value === 0) {
return;
}
dimensions.push(<Dimension dimensions.push(<Dimension
key={id} key={id}
id={id} id={id}
@ -304,7 +317,7 @@ function AddHorizontalBorrowerDimension(
yStart={yDim} yStart={yDim}
yEnd={yDim} yEnd={yDim}
strokeWidth={MODULE_STROKE_WIDTH} strokeWidth={MODULE_STROKE_WIDTH}
text={(next - cur).toFixed(0).toString()} text={value.toFixed(0)}
scale={scale} />); scale={scale} />);
count++; count++;
} }
@ -312,6 +325,7 @@ function AddHorizontalBorrowerDimension(
function AddVerticalBorrowerDimension( function AddVerticalBorrowerDimension(
xDim: number, xDim: number,
isRight: boolean,
containers: Map<string, IContainerModel>, containers: Map<string, IContainerModel>,
container: IContainerModel, container: IContainerModel,
depth: number, depth: number,
@ -346,9 +360,19 @@ function AddVerticalBorrowerDimension(
marks.push(restoredY); marks.push(restoredY);
marks.push(restoredY + container.properties.height); marks.push(restoredY + container.properties.height);
marks.sort((a, b) => a - b); marks.sort((a, b) => a - b);
let count = 0; let count = 0;
for (const { cur, next } of Pairwise(marks)) { for (let { cur, next } of Pairwise(marks)) {
const id = `dim-x${xDim.toFixed(0)}-borrow-${container.properties.id}-{${count}}`; const id = `dim-x${xDim.toFixed(0)}-borrow-${container.properties.id}-{${count}}`;
const value = next - cur;
if (value === 0) {
return;
}
if (!isRight) {
[cur, next] = [next, cur];
}
dimensions.push(<Dimension dimensions.push(<Dimension
key={id} key={id}
id={id} id={id}
@ -357,7 +381,7 @@ function AddVerticalBorrowerDimension(
yStart={cur} yStart={cur}
yEnd={next} yEnd={next}
strokeWidth={MODULE_STROKE_WIDTH} strokeWidth={MODULE_STROKE_WIDTH}
text={(next - cur).toFixed(0).toString()} text={value.toFixed(0)}
scale={scale} />); scale={scale} />);
count++; count++;
} }
@ -365,6 +389,7 @@ function AddVerticalBorrowerDimension(
function AddVerticalSelfDimension( function AddVerticalSelfDimension(
xDim: number, xDim: number,
isRight: boolean,
container: IContainerModel, container: IContainerModel,
currentTransform: [number, number], currentTransform: [number, number],
dimensions: React.ReactNode[], dimensions: React.ReactNode[],
@ -372,11 +397,16 @@ function AddVerticalSelfDimension(
): void { ): void {
const height = container.properties.height; const height = container.properties.height;
const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`; const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`;
const yStart = container.properties.y + currentTransform[1]; let yStart = container.properties.y + currentTransform[1] + height;
const yEnd = yStart + height; let yEnd = container.properties.y + currentTransform[1];
const textVert = height const textVert = height
.toFixed(0) .toFixed(0)
.toString(); .toString();
if (isRight) {
[yStart, yEnd] = [yEnd, yStart];
}
dimensions.push( dimensions.push(
<Dimension <Dimension
key={idVert} key={idVert}

View file

@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { ReactSVGPanZoom, Tool, TOOL_PAN, Value } from 'react-svg-pan-zoom'; import { ReactSVGPanZoom, Tool, TOOL_PAN, Value } from 'react-svg-pan-zoom';
import { Container } from './Elements/Container'; import { Container } from './Elements/Container';
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel'; import { IContainerModel } from '../../Interfaces/IContainerModel';
import { Selector } from './Elements/Selector/Selector'; import { Selector } from './Elements/Selector/Selector';
import { DepthDimensionLayer } from './Elements/DepthDimensionLayer'; import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
import { MAX_FRAMERATE, SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default'; import { MAX_FRAMERATE, SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
@ -16,8 +16,8 @@ interface ISVGProps {
width: number width: number
height: number height: number
containers: Map<string, IContainerModel> containers: Map<string, IContainerModel>
children: ContainerModel children: IContainerModel
selected?: ContainerModel selected?: IContainerModel
symbols: Map<string, ISymbolModel> symbols: Map<string, ISymbolModel>
selectContainer: (containerId: string) => void selectContainer: (containerId: string) => void
} }

View file

@ -1,5 +1,6 @@
import { ArrowUpOnSquareIcon, CameraIcon } from '@heroicons/react/24/outline'; import { ArrowUpOnSquareIcon, CameraIcon } from '@heroicons/react/24/outline';
import React from 'react'; import React from 'react';
import { Text } from '../Text/Text';
interface ISettingsProps { interface ISettingsProps {
saveEditorAsJSON: () => void saveEditorAsJSON: () => void
@ -12,19 +13,19 @@ export function Settings(props: ISettingsProps): JSX.Element {
m-2 md:text-xs font-bold'> m-2 md:text-xs font-bold'>
<button type="button" <button type="button"
className={'w-full transition-all flex sidebar-component'} className={'w-full transition-all flex sidebar-component'}
title='Export as JSON' title={Text({ textId: '@ExportAsJSON' })}
onClick={props.saveEditorAsJSON} onClick={props.saveEditorAsJSON}
> >
<ArrowUpOnSquareIcon className="heroicon w-16 h-7" /> <ArrowUpOnSquareIcon className="heroicon w-16 h-7" />
Export as JSON {Text({ textId: '@ExportAsJSON' })}
</button> </button>
<button type="button" <button type="button"
className={'w-full transition-all flex sidebar-component'} className={'w-full transition-all flex sidebar-component'}
title='Export as SVG' title={Text({ textId: '@ExportAsSVG' })}
onClick={props.saveEditorAsSVG} onClick={props.saveEditorAsSVG}
> >
<CameraIcon className="heroicon w-16 h-7" /> <CameraIcon className="heroicon w-16 h-7" />
Export as SVG {Text({ textId: '@ExportAsSVG' })}
</button> </button>
</div> </div>
); );

View file

@ -4,6 +4,7 @@ import { RestoreX, TransformX } from '../../utils/svg';
import { InputGroup } from '../InputGroup/InputGroup'; import { InputGroup } from '../InputGroup/InputGroup';
import { TextInputGroup } from '../InputGroup/TextInputGroup'; import { TextInputGroup } from '../InputGroup/TextInputGroup';
import { PositionReferenceSelector } from '../RadioGroupButtons/PositionReferenceSelector'; import { PositionReferenceSelector } from '../RadioGroupButtons/PositionReferenceSelector';
import { Text } from '../Text/Text';
interface ISymbolFormProps { interface ISymbolFormProps {
symbol: ISymbolModel symbol: ISymbolModel
@ -15,7 +16,7 @@ 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
labelText='Name' labelText={Text({ textId: '@SymbolName' })}
inputKey='id' inputKey='id'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -24,7 +25,7 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
isDisabled={true} /> isDisabled={true} />
<TextInputGroup <TextInputGroup
id='x' id='x'
labelText='x' labelText={Text({ textId: '@SymbolX' })}
inputKey='x' inputKey='x'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -33,7 +34,7 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
onChange={(value) => props.onChange('x', RestoreX(Number(value), props.symbol.width, props.symbol.config.PositionReference))} /> onChange={(value) => props.onChange('x', RestoreX(Number(value), props.symbol.width, props.symbol.config.PositionReference))} />
<TextInputGroup <TextInputGroup
id='height' id='height'
labelText='Height' labelText={Text({ textId: '@SymbolHeight' })}
inputKey='height' inputKey='height'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
@ -43,7 +44,7 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
onChange={(value) => props.onChange('height', Number(value))} /> onChange={(value) => props.onChange('height', Number(value))} />
<TextInputGroup <TextInputGroup
id='width' id='width'
labelText='Width' labelText={Text({ textId: '@SymbolWidth' })}
inputKey='width' inputKey='width'
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''

View file

@ -16,7 +16,7 @@ export function Symbols(props: ISymbolsProps): JSX.Element {
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;
return (<button type="button" return (<button type="button"
className='justify-center sidebar-component-card hover:h-full' className='flex justify-start items-center sidebar-component-card hover:h-full'
key={componentOption.Name} key={componentOption.Name}
id={componentOption.Name} id={componentOption.Name}
title={componentOption.Name} title={componentOption.Name}
@ -30,8 +30,8 @@ export function Symbols(props: ISymbolsProps): JSX.Element {
src={url} src={url}
/> />
</div> </div>
<div> <div className='ml-5'>
{TruncateString(componentOption.Name, 5)} {TruncateString(componentOption.Name, 20)}
</div> </div>
</button>); </button>);
} }
@ -52,7 +52,7 @@ export function Symbols(props: ISymbolsProps): JSX.Element {
return ( return (
<div className='h-full overflow-y-auto'> <div className='h-full overflow-y-auto'>
<div className='grid grid-cols-1 md:grid-cols-3 gap-2 <div className='grid grid-cols-1 md:grid-cols-1 gap-2
overflow-auto m-2 md:text-xs font-bold'> overflow-auto m-2 md:text-xs font-bold'>
{listElements} {listElements}
</div> </div>

View file

@ -0,0 +1,11 @@
import { useContext } from 'react';
import { LanguageContext } from '../LanguageProvider/LanguageProvider';
interface TextProps {
textId: string
};
export function Text({ textId }: TextProps): string {
const languageContext = useContext(LanguageContext);
return languageContext.dictionary[textId] ?? textId;
};

View file

@ -16,6 +16,7 @@ import { UseWorker, UseAsync } from './UseWorker';
import { FindContainerById } from '../../utils/itertools'; import { FindContainerById } from '../../utils/itertools';
import { IEditorState } from '../../Interfaces/IEditorState'; import { IEditorState } from '../../Interfaces/IEditorState';
import { GetCurrentHistoryState } from '../Editor/Editor'; import { GetCurrentHistoryState } from '../Editor/Editor';
import { Text } from '../Text/Text';
export interface IUIProps { export interface IUIProps {
editorState: IEditorState editorState: IEditorState
@ -95,14 +96,14 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
switch (selectedSidebar) { switch (selectedSidebar) {
case SidebarType.Components: case SidebarType.Components:
leftSidebarTitle = 'Components'; leftSidebarTitle = Text({ textId: '@Components' });
leftChildren = <Components leftChildren = <Components
selectedContainer={selectedContainer} selectedContainer={selectedContainer}
componentOptions={configuration.AvailableContainers} componentOptions={configuration.AvailableContainers}
categories={configuration.Categories} categories={configuration.Categories}
buttonOnClick={methods.addContainer} buttonOnClick={methods.addContainer}
/>; />;
rightSidebarTitle = 'Elements'; rightSidebarTitle = Text({ textId: '@Elements' });
rightChildren = <ElementsList rightChildren = <ElementsList
containers={current.containers} containers={current.containers}
mainContainer={mainContainer} mainContainer={mainContainer}
@ -115,12 +116,12 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
break; break;
case SidebarType.Symbols: case SidebarType.Symbols:
leftSidebarTitle = 'Symbols'; leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
leftChildren = <Symbols leftChildren = <Symbols
componentOptions={configuration.AvailableSymbols} componentOptions={configuration.AvailableSymbols}
buttonOnClick={methods.addSymbol} buttonOnClick={methods.addSymbol}
/>; />;
rightSidebarTitle = 'Symbols'; rightSidebarTitle = Text({ textId: '@SymbolsRight' });
rightChildren = <SymbolsSidebar rightChildren = <SymbolsSidebar
selectedSymbolId={current.selectedSymbolId} selectedSymbolId={current.selectedSymbolId}
symbols={current.symbols} symbols={current.symbols}
@ -130,7 +131,7 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
break; break;
case SidebarType.History: case SidebarType.History:
leftSidebarTitle = 'Timeline'; leftSidebarTitle = Text({ textId: '@Timeline' });
leftChildren = <History leftChildren = <History
history={editorState.history} history={editorState.history}
historyCurrentStep={editorState.historyCurrentStep} historyCurrentStep={editorState.historyCurrentStep}
@ -139,7 +140,7 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
break; break;
case SidebarType.Messages: case SidebarType.Messages:
leftSidebarTitle = 'Messages'; leftSidebarTitle = Text({ textId: '@Messages' });
leftChildren = <Messages leftChildren = <Messages
historyState={current} historyState={current}
messages={messages} messages={messages}
@ -148,7 +149,7 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
break; break;
case SidebarType.Settings: case SidebarType.Settings:
leftSidebarTitle = 'Settings'; leftSidebarTitle = Text({ textId: '@Settings' });
leftChildren = <Settings leftChildren = <Settings
saveEditorAsJSON={methods.saveEditorAsJSON} saveEditorAsJSON={methods.saveEditorAsJSON}
saveEditorAsSVG={methods.saveEditorAsSVG} saveEditorAsSVG={methods.saveEditorAsSVG}

View file

@ -16,7 +16,7 @@ export function UseWorker(
// use webworker for the stringify to avoid freezing // use webworker for the stringify to avoid freezing
myWorker.postMessage({ myWorker.postMessage({
state, state,
url: import.meta.env.VITE_API_GET_FEEDBACK_URL url: configurationUrl ?? import.meta.env.VITE_API_GET_FEEDBACK_URL
}); });
return () => { return () => {
@ -39,7 +39,8 @@ export function UseAsync(
ApplicationState: state ApplicationState: state
}; };
const dataParsed = JSON.stringify(request, GetCircularReplacer()); const dataParsed = JSON.stringify(request, GetCircularReplacer());
fetch(import.meta.env.VITE_API_GET_FEEDBACK_URL, { const url = configurationUrl ?? import.meta.env.VITE_API_GET_FEEDBACK_URL;
fetch(url, {
method: 'POST', method: 'POST',
headers: new Headers({ headers: new Headers({
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention

5
src/Enums/AppState.ts Normal file
View file

@ -0,0 +1,5 @@
export enum AppState {
MainMenu,
Loading,
Loaded
}

View file

@ -1,44 +1,55 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { AppState } from '../Enums/AppState';
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';
import { ILanguage } from '../Interfaces/ILanguage';
import { languageOptions, translations } from '../Translations/Translations';
import { GetDefaultEditorState as GetDefaultEditorStateAction } from '../utils/default'; import { GetDefaultEditorState as GetDefaultEditorStateAction } from '../utils/default';
import { Revive, ReviveHistory as ReviveHistoryAction } from '../utils/saveload'; import { Revive, ReviveHistory as ReviveHistoryAction } from '../utils/saveload';
interface IAppEventParams {
root: Element | Document
languageContext: ILanguage
setEditor: (newState: IEditorState) => void
setAppState: (appState: AppState) => void
eventInitDict?: CustomEventInit
}
export interface IAppEvent { export interface IAppEvent {
name: string name: string
func: ( func: (params: IAppEventParams) => void
root: Element | Document,
setEditor: (newState: IEditorState) => void,
setLoaded: (loaded: boolean) => void,
eventInitDict?: CustomEventInit
) => void
} }
export const events: IAppEvent[] = [ export const events: IAppEvent[] = [
{ name: 'setEditor', func: SetEditor }, { name: 'setEditor', func: SetEditor },
{ name: 'setLoaded', func: SetLoaded }, { name: 'setAppState', func: SetAppState },
{ name: 'reviveEditorState', func: ReviveEditorState }, { name: 'reviveEditorState', func: ReviveEditorState },
{ name: 'reviveHistory', func: ReviveHistory }, { name: 'reviveHistory', func: ReviveHistory },
{ name: 'getDefaultEditorState', func: GetDefaultEditorState } { name: 'getDefaultEditorState', func: GetDefaultEditorState },
{ name: 'addLanguage', func: AddLanguage },
{ name: 'setLanguage', func: SetLanguage },
{ name: 'getLanguages', func: GetLanguages }
]; ];
export function UseCustomEvents( export function UseCustomEvents(
root: Element | Document, root: Element | Document,
appRef: React.RefObject<HTMLDivElement>, appRef: React.RefObject<HTMLDivElement>,
languageContext: ILanguage,
setEditor: (newState: IEditorState) => void, setEditor: (newState: IEditorState) => void,
setLoaded: (loaded: boolean) => void setAppState: (appState: AppState) => void
): void { ): void {
useEffect(() => { useEffect(() => {
const funcs = new Map<string, () => void>(); const funcs = new Map<string, () => void>();
for (const event of events) { for (const event of events) {
function Func(eventInitDict?: CustomEventInit): void { function Func(eventInitDict?: CustomEventInit): void {
return event.func( return event.func({
root, root,
languageContext,
setEditor, setEditor,
setLoaded, setAppState,
eventInitDict eventInitDict
); });
} }
appRef.current?.addEventListener(event.name, Func); appRef.current?.addEventListener(event.name, Func);
funcs.set(event.name, Func); funcs.set(event.name, Func);
@ -55,57 +66,87 @@ export function UseCustomEvents(
}); });
} }
function SetEditor( function SetEditor({
root: Element | Document, root,
setEditor: (newState: IEditorState) => void, setEditor,
setLoaded: (loaded: boolean) => void, setAppState,
eventInitDict?: CustomEventInit eventInitDict
): void { }: IAppEventParams): void {
const editor: IEditorState = eventInitDict?.detail; const editor: IEditorState = eventInitDict?.detail;
setEditor(editor); setEditor(editor);
setAppState(AppState.Loading);
const customEvent = new CustomEvent<IEditorState>('setEditor', { detail: editor }); const customEvent = new CustomEvent<IEditorState>('setEditor', { detail: editor });
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function SetLoaded( function SetAppState({
root: Element | Document, setAppState,
setEditor: (newState: IEditorState) => void, eventInitDict
setLoaded: (loaded: boolean) => void, }: IAppEventParams): void {
eventInitDict?: CustomEventInit const appState: AppState = eventInitDict?.detail;
): void { setAppState(appState);
const isLoaded: boolean = eventInitDict?.detail;
setLoaded(isLoaded);
} }
function ReviveEditorState( function ReviveEditorState({
root: Element | Document, root,
setEditor: (newState: IEditorState) => void, eventInitDict
setLoaded: (loaded: boolean) => void, }: IAppEventParams): void {
eventInitDict?: CustomEventInit): void {
const anEditorState: IEditorState = eventInitDict?.detail; const anEditorState: IEditorState = eventInitDict?.detail;
Revive(anEditorState); Revive(anEditorState);
const customEvent = new CustomEvent<IEditorState>('reviveEditorState', { detail: anEditorState }); const customEvent = new CustomEvent<IEditorState>('reviveEditorState', { detail: anEditorState });
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function ReviveHistory( function ReviveHistory({
root: Element | Document, root,
setEditor: (newState: IEditorState) => void, eventInitDict
setLoaded: (loaded: boolean) => void, }: IAppEventParams): void {
eventInitDict?: CustomEventInit): void {
const history: IHistoryState[] = eventInitDict?.detail; const history: IHistoryState[] = eventInitDict?.detail;
ReviveHistoryAction(history); ReviveHistoryAction(history);
const customEvent = new CustomEvent<IHistoryState[]>('reviveHistory', { detail: history }); const customEvent = new CustomEvent<IHistoryState[]>('reviveHistory', { detail: history });
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function GetDefaultEditorState( function GetDefaultEditorState({
root: Element | Document, root,
setEditor: (newState: IEditorState) => void, eventInitDict
setLoaded: (loaded: boolean) => void, }: IAppEventParams): void {
eventInitDict?: CustomEventInit): void {
const configuration: IConfiguration = eventInitDict?.detail; const configuration: IConfiguration = eventInitDict?.detail;
const editorState = GetDefaultEditorStateAction(configuration); const editorState = GetDefaultEditorStateAction(configuration);
const customEvent = new CustomEvent<IEditorState>('getDefaultEditorState', { detail: editorState }); const customEvent = new CustomEvent<IEditorState>('getDefaultEditorState', { detail: editorState });
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function AddLanguage({
root,
eventInitDict
}: IAppEventParams): void {
const language: ILanguage = eventInitDict?.detail.language;
const option: string = eventInitDict?.detail.option;
languageOptions[language.language] = option;
translations[language.language] = language.dictionary;
const customEvent = new CustomEvent('addLanguage');
root.dispatchEvent(customEvent);
}
function SetLanguage({
root,
languageContext,
eventInitDict
}: IAppEventParams): void {
const language: string = eventInitDict?.detail;
let success = false;
if (languageContext.languageChange !== undefined) {
languageContext.languageChange(language);
success = true;
}
const customEvent = new CustomEvent<boolean>('setLanguage', { detail: success });
root.dispatchEvent(customEvent);
}
function GetLanguages({
root
}: IAppEventParams): void {
const customEvent = new CustomEvent<Record<string, Record<string, string>>>('getLanguages', { detail: translations });
root.dispatchEvent(customEvent);
}

View file

@ -9,14 +9,15 @@ import { IHistoryState } from '../Interfaces/IHistoryState';
import { FindContainerById } from '../utils/itertools'; import { FindContainerById } from '../utils/itertools';
import { GetCircularReplacer } from '../utils/saveload'; import { GetCircularReplacer } from '../utils/saveload';
interface IEditorEventParams {
root: Element | Document
editorState: IEditorState
setNewHistory: (newHistory: IHistoryState[], historyCurrentStep?: number) => void
eventInitDict?: CustomEventInit
}
export interface IEditorEvent { export interface IEditorEvent {
name: string name: string
func: ( func: (params: IEditorEventParams) => void
root: Element | Document,
editorState: IEditorState,
setNewHistory: (newHistory: IHistoryState[], historyCurrentStep?: number) => void,
eventInitDict?: CustomEventInit
) => void
} }
export const events: IEditorEvent[] = [ export const events: IEditorEvent[] = [
@ -54,12 +55,12 @@ export function UseCustomEvents(
const funcs = new Map<string, () => void>(); const funcs = new Map<string, () => void>();
for (const event of events) { for (const event of events) {
function Func(eventInitDict?: CustomEventInit): void { function Func(eventInitDict?: CustomEventInit): void {
return event.func( return event.func({
root, root,
editorState, editorState,
setNewHistory, setNewHistory,
eventInitDict eventInitDict
); });
} }
editorRef.current?.addEventListener(event.name, Func); editorRef.current?.addEventListener(event.name, Func);
funcs.set(event.name, Func); funcs.set(event.name, Func);
@ -93,24 +94,30 @@ export function UseEditorListener(
}); });
} }
function GetEditorState(root: Element | Document, function GetEditorState({
editorState: IEditorState): void { root,
editorState
}: IEditorEventParams): void {
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: structuredClone(editorState) }); const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: structuredClone(editorState) });
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function GetEditorStateAsString(root: Element | Document, function GetEditorStateAsString({
editorState: IEditorState): void { root,
editorState
}: IEditorEventParams): void {
const spaces = import.meta.env.DEV ? 4 : 0; const spaces = import.meta.env.DEV ? 4 : 0;
const data = JSON.stringify(editorState, GetCircularReplacer(), spaces); const data = JSON.stringify(editorState, GetCircularReplacer(), spaces);
const customEvent = new CustomEvent<string>('getEditorStateAsString', { detail: data }); const customEvent = new CustomEvent<string>('getEditorStateAsString', { detail: data });
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function SetHistory(root: Element | Document, function SetHistory({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[], historyCurrentStep?: number) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const history: IHistoryState[] = eventInitDict?.detail.history; const history: IHistoryState[] = eventInitDict?.detail.history;
const historyCurrentStep: number | undefined = eventInitDict?.detail.historyCurrentStep; const historyCurrentStep: number | undefined = eventInitDict?.detail.historyCurrentStep;
setNewHistory(history, historyCurrentStep); setNewHistory(history, historyCurrentStep);
@ -118,18 +125,22 @@ function SetHistory(root: Element | Document,
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function GetCurrentHistoryState(root: Element | Document, function GetCurrentHistoryState({
editorState: IEditorState): void { root,
editorState
}: IEditorEventParams): void {
const customEvent = new CustomEvent<IHistoryState>( const customEvent = new CustomEvent<IHistoryState>(
'getCurrentHistoryState', 'getCurrentHistoryState',
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) }); { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) });
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function AppendNewState(root: Element | Document, function AppendNewState({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[]) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const state: IHistoryState = eventInitDict?.detail.state; const state: IHistoryState = eventInitDict?.detail.state;
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep); const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
@ -142,10 +153,12 @@ function AppendNewState(root: Element | Document,
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function AddContainer(root: Element | Document, function AddContainer({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[]) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const { const {
index, index,
type, type,
@ -169,10 +182,12 @@ function AddContainer(root: Element | Document,
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function AddContainerToSelectedContainer(root: Element | Document, function AddContainerToSelectedContainer({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[]) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const { const {
index, index,
type type
@ -197,10 +212,12 @@ function AddContainerToSelectedContainer(root: Element | Document,
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function AppendContainer(root: Element | Document, function AppendContainer({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[]) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const { const {
type, type,
parentId parentId
@ -227,10 +244,12 @@ function AppendContainer(root: Element | Document,
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function AppendContainerToSelectedContainer(root: Element | Document, function AppendContainerToSelectedContainer({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[]) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const { const {
type type
} = eventInitDict?.detail; } = eventInitDict?.detail;
@ -256,10 +275,12 @@ function AppendContainerToSelectedContainer(root: Element | Document,
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function SelectContainer(root: Element | Document, function SelectContainer({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[]) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const { const {
containerId containerId
} = eventInitDict?.detail; } = eventInitDict?.detail;
@ -279,10 +300,12 @@ function SelectContainer(root: Element | Document,
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function DeleteContainer(root: Element | Document, function DeleteContainer({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[]) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const { const {
containerId containerId
} = eventInitDict?.detail; } = eventInitDict?.detail;
@ -302,10 +325,12 @@ function DeleteContainer(root: Element | Document,
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function AddSymbol(root: Element | Document, function AddSymbol({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[]) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const { const {
name name
} = eventInitDict?.detail; } = eventInitDict?.detail;
@ -326,10 +351,12 @@ function AddSymbol(root: Element | Document,
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function SelectSymbol(root: Element | Document, function SelectSymbol({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[]) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const { const {
symbolId symbolId
} = eventInitDict?.detail; } = eventInitDict?.detail;
@ -349,10 +376,12 @@ function SelectSymbol(root: Element | Document,
root.dispatchEvent(customEvent); root.dispatchEvent(customEvent);
} }
function DeleteSymbol(root: Element | Document, function DeleteSymbol({
editorState: IEditorState, root,
setNewHistory: (newHistory: IHistoryState[]) => void, editorState,
eventInitDict?: CustomEventInit): void { setNewHistory,
eventInitDict
}: IEditorEventParams): void {
const { const {
symbolId symbolId
} = eventInitDict?.detail; } = eventInitDict?.detail;

View file

@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
import React from 'react';
import { AddMethod } from '../Enums/AddMethod'; import { AddMethod } from '../Enums/AddMethod';
import { PositionReference } from '../Enums/PositionReference'; import { PositionReference } from '../Enums/PositionReference';
import { IAction } from './IAction'; import { IAction } from './IAction';
@ -7,6 +6,7 @@ import { IMargin } from './IMargin';
import { Orientation } from '../Enums/Orientation'; import { Orientation } from '../Enums/Orientation';
import { Position } from '../Enums/Position'; import { Position } from '../Enums/Position';
import { IKeyValue } from './IKeyValue'; import { IKeyValue } from './IKeyValue';
import { IStyle } from './IStyle';
/** Model of available container used in application configuration */ /** Model of available container used in application configuration */
export interface IAvailableContainer { export interface IAvailableContainer {
@ -151,7 +151,7 @@ export interface IAvailableContainer {
* (optional) * (optional)
* Style of the <rect> * Style of the <rect>
*/ */
Style?: React.CSSProperties Style?: IStyle
/** /**
* List of possible actions shown on right-click * List of possible actions shown on right-click

View file

@ -1,13 +1,25 @@
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
import { PositionReference } from '../Enums/PositionReference'; import { PositionReference } from '../Enums/PositionReference';
import { IAvailableContainer } from './IAvailableContainer';
import { IImage } from './IImage'; import { IImage } from './IImage';
/** /**
* Model of available symbol to configure the application */ * Model of available symbol to configure the application */
export interface IAvailableSymbol { export interface IAvailableSymbol {
Name: string Name: string
Image: IImage Image: IImage
X?: number
Y?: number
Width?: number Width?: number
Height?: number Height?: number
PositionReference?: PositionReference PositionReference?: PositionReference
/** An existing or new available container */
AssociatedContainer?: IAvailableContainer
} }

View file

@ -1,9 +1,9 @@
import * as React from 'react';
import { PositionReference } from '../Enums/PositionReference'; import { PositionReference } from '../Enums/PositionReference';
import { IMargin } from './IMargin'; import { IMargin } from './IMargin';
import { Orientation } from '../Enums/Orientation'; import { Orientation } from '../Enums/Orientation';
import { Position } from '../Enums/Position'; import { Position } from '../Enums/Position';
import { IKeyValue } from './IKeyValue'; import { IKeyValue } from './IKeyValue';
import { IStyle } from './IStyle';
/** /**
* Properties of a container * Properties of a container
@ -27,6 +27,7 @@ export interface IContainerProperties {
/** orientation */ /** orientation */
orientation: Orientation orientation: Orientation
// TODO: Refactor x, y in IPoint interface
/** horizontal offset */ /** horizontal offset */
x: number x: number
@ -36,6 +37,7 @@ export interface IContainerProperties {
/** margin */ /** margin */
margin: IMargin margin: IMargin
// TODO: Refactor width, height, minWidth... in ISize interface
/** width */ /** width */
width: number width: number
@ -62,6 +64,7 @@ export interface IContainerProperties {
*/ */
maxHeight: number maxHeight: number
// TODO: Refactor isAnchor, isFlex in IBehaviors interface
/** true if anchor, false otherwise */ /** true if anchor, false otherwise */
isAnchor: boolean isAnchor: boolean
@ -71,9 +74,11 @@ export interface IContainerProperties {
/** 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 } */
positionReference: PositionReference positionReference: PositionReference
// TODO: Refactor hideChildrenInTreeview in IUserInterface interface
/** Hide the children in the treeview */ /** Hide the children in the treeview */
hideChildrenInTreeview: boolean hideChildrenInTreeview: boolean
// TODO: Refactor showSelf., showChildren., markPosition, showDimensionWithMarks in IDimensionOptions interface
/** if true, show the dimension of the container */ /** if true, show the dimension of the container */
showSelfDimensions: Position[] showSelfDimensions: Position[]
@ -117,7 +122,7 @@ export interface IContainerProperties {
* (optional) * (optional)
* Style of the <rect> * Style of the <rect>
*/ */
style?: React.CSSProperties style?: IStyle
/** /**
* (optional) * (optional)

View file

@ -0,0 +1,5 @@
export interface ILanguage {
language: string
dictionary: Record<string, string>
languageChange?: (selected: string) => void
}

11
src/Interfaces/IStyle.ts Normal file
View file

@ -0,0 +1,11 @@
export interface IStyle {
stroke?: string
strokeOpacity?: number
strokeWidth?: number
fill?: string
fillOpacity?: number
}

View file

@ -13,6 +13,8 @@ export interface ISymbolModel {
/** Horizontal offset */ /** Horizontal offset */
x: number x: number
// TODO: Implement Y and verticality
/** Width */ /** Width */
width: number width: number

View file

@ -0,0 +1,14 @@
import en from './translation.en.json';
import fr from './translation.fr.json';
export const translations: Record<string, Record<string, string>> = {
en,
fr
};
// Use languageOptions to restrict existing dictionary
// and create a select input
export const languageOptions: Record<string, string> = {
en: 'English',
fr: 'French'
};

View file

@ -0,0 +1,75 @@
{
"@StartFromScratch": "Start from scratch",
"@LoadConfigFile": "Load a configuration file",
"@GoBack": "Go back",
"@Components": "Components",
"@Elements": "Elements",
"@Symbols": "Symbols",
"@SymbolsLeft": "Symbols",
"@SymbolsRight": "Symbols",
"@Timeline": "Timeline",
"@Messages": "Messages",
"@Settings": "Settings",
"@Undo": "Undo",
"@UndoTitle": "Undo last action",
"@Redo": "Redo",
"@RedoTitle": "Redo last action",
"@DeleteContainer": "Delete",
"@DeleteContainerTitle": "Delete the container",
"@DeleteSymbol": "Delete",
"@DeleteSymbolTitle": "Delete the container",
"@ExportAsJSON": "Export as JSON",
"@ExportAsSVG": "Export as SVG",
"@HideDisabledComponents": "Hide disabled components",
"@ShowDisabledComponents": "Show disabled components",
"@ClearAllMessages": "Clear all messages",
"@ContainerDisplayedText": "Displayed text",
"@ContainerOrientation": "Orientation",
"@ContainerProperties": "Properties",
"@ContainerName": "Name",
"@ContainerParentName": "Parent name",
"@ContainerType": "Type",
"@ContainerPosition": "Position",
"@ContainerX": "x",
"@ContainerY": "y",
"@ContainerSize": "Size",
"@ContainerMinWidth": "Minimum Width",
"@ContainerWidth": "Width",
"@ContainerMaxWidth": "Maximum Width",
"@ContainerMinHeight": "Minimum Height",
"@ContainerHeight": "Height",
"@ContainerMaxHeight": "Maximum Height",
"@ContainerMargins": "Margins",
"@ContainerMarginLeft": "Margin Left",
"@ContainerMarginBottom": "Margin Bottom",
"@ContainerMarginTop": "Margin Top",
"@ContainerMarginRight": "Margin Right",
"@ContainerBehaviors": "Behaviors",
"@ContainerFlex": "Flex",
"@ContainerAnchor": "Lock position and size",
"@ContainerAlignment": "Alignment",
"@ContainerAlignmentInput": "Alignment",
"@ContainerAlignWithSymbol": "Align to symbol",
"@ContainerDimensions": "Dimensions",
"@ContainerShowDimension": "Show Dimension",
"@ContainerShowChildrenDimension": "Show surrounding dimension of children",
"@ContainerMarkPosition": "Mark the position for the parents",
"@ContainerShowDimensionWithMarks": "Show dimension with marked children",
"@ContainerStyle": "Style",
"@StyleStroke": "Stroke",
"@StyleStrokeOpacity": "Stroke Opacity",
"@StyleStrokeWidth": "Stroke Width",
"@StyleFill": "Fill",
"@StyleFillOpacity": "Fill Opacity",
"@SymbolName": "Name",
"@SymbolX": "x",
"@SymbolHeight": "Height",
"@SymbolWidth": "Width"
}

View file

@ -0,0 +1,75 @@
{
"@StartFromScratch": "Partir de zéro",
"@LoadConfigFile": "Charger un fichier de configuration",
"@GoBack": "Revenir",
"@Components": "Composants",
"@Elements": "Éléments",
"@Symbols": "Symboles",
"@SymbolsLeft": "Symboles",
"@SymbolsRight": "Symboles",
"@Timeline": "Chronologie",
"@Messages": "Messages",
"@Settings": "Paramètres",
"@Undo": "Annuler",
"@UndoTitle": "Annuler la dernière action",
"@Redo": "Refaire",
"@RedoTitle": "Refaire la dernière action",
"@DeleteContainer": "Supprimer",
"@DeleteContainerTitle": "Supprimer le conteneur",
"@DeleteSymbol": "Supprimer",
"@DeleteSymbolTitle": "Supprimer le symbole",
"@ExportAsJSON": "Exporter en JSON",
"@ExportAsSVG": "Exporter en SVG",
"@HideDisabledComponents": "Cacher les composants désactivés",
"@ShowDisabledComponents": "Montrer les composants activés",
"@ClearAllMessages": "Effacer tous les messages",
"@ContainerDisplayedText": "Texte affiché",
"@ContainerOrientation": "Orientation",
"@ContainerProperties": "Propriétés",
"@ContainerName": "Nom",
"@ContainerParentName": "Nom du parent",
"@ContainerType": "Type",
"@ContainerPosition": "Position",
"@ContainerX": "x",
"@ContainerY": "y",
"@ContainerSize": "Taille",
"@ContainerMinWidth": "Largeur minimale",
"@ContainerWidth": "Largeur",
"@ContainerMaxWidth": "Largeur maximale",
"@ContainerMinHeight": "Hauteur minimale",
"@ContainerHeight": "Hauteur",
"@ContainerMaxHeight": "Hauteur maximale",
"@ContainerMargins": "Marges",
"@ContainerMarginLeft": "Marge gauche",
"@ContainerMarginBottom": "Marge inférieure",
"@ContainerMarginTop": "Marge supérieure",
"@ContainerMarginRight": "Marge droite",
"@ContainerBehaviors": "Comportements",
"@ContainerFlex": "Flexible",
"@ContainerAnchor": "Verrouiller la position et la taille",
"@ContainerAlignment": "Alignement",
"@ContainerAlignmentInput": "Alignement",
"@ContainerAlignWithSymbol": "Aligner au symbole",
"@ContainerDimensions": "Cotations",
"@ContainerShowDimension": "Afficher les cotations",
"@ContainerShowChildrenDimension": "Afficher les cotations englobante des enfants",
"@ContainerMarkPosition": "Marquer la position pour les parents",
"@ContainerShowDimensionWithMarks": "Afficher les cotations avec les enfants marqués",
"@ContainerStyle": "Style",
"@StyleStroke": "Tracé",
"@StyleStrokeOpacity": "Opacité du tracé",
"@StyleStrokeWidth": "Epaisseur du tracé",
"@StyleFill": "Remplissage",
"@StyleFillOpacity": "Opacité du remplissage",
"@SymbolName": "Nom",
"@SymbolX": "x",
"@SymbolHeight": "Hauteur",
"@SymbolWidth": "Largeur"
}

27
src/dts/svgld.d.ts vendored
View file

@ -13,6 +13,12 @@ export enum AddMethod {
ReplaceParent = 3 ReplaceParent = 3
} }
export enum AppState {
MainMenu = 0,
Loading = 1,
Loaded = 2
}
export enum MessageType { export enum MessageType {
Normal = 0, Normal = 0,
Success = 1, Success = 1,
@ -88,6 +94,7 @@ export interface IAPIConfiguration {
/** Model of available container used in application configuration */ /** Model of available container used in application configuration */
export interface IAvailableContainer { export interface IAvailableContainer {
/** type */ /** type */
@ -212,7 +219,7 @@ export interface IAvailableContainer {
* (optional) * (optional)
* User data that can be used for data storage or custom SVG * User data that can be used for data storage or custom SVG
*/ */
UserData?: object; UserData?: IKeyValue[];
} }
@ -250,7 +257,6 @@ export interface IConfiguration {
export interface IContainerModel { export interface IContainerModel {
children: string[]; children: string[];
parent: IContainerModel | null;
properties: IContainerProperties; properties: IContainerProperties;
userData: Record<string, string | number>; userData: Record<string, string | number>;
} }
@ -260,10 +266,9 @@ export interface IContainerModel {
*/ */
export class ContainerModel implements IContainerModel { export class ContainerModel implements IContainerModel {
children: string[]; children: string[];
parent: IContainerModel | null;
properties: IContainerProperties; properties: IContainerProperties;
userData: Record<string, string | number>; userData: Record<string, string | number>;
constructor(parent: IContainerModel | null, properties: IContainerProperties, children?: string[], userData?: {}); constructor(properties: IContainerProperties, children?: string[], userData?: {});
} }
@ -271,6 +276,7 @@ export class ContainerModel implements IContainerModel {
/** /**
* Properties of a container * Properties of a container
*/ */
@ -363,7 +369,7 @@ export interface IContainerProperties {
* (optional) * (optional)
* User data that can be used for data storage or custom SVG * User data that can be used for data storage or custom SVG
*/ */
userData?: object; userData?: IKeyValue[];
} }
@ -428,6 +434,17 @@ export interface IInputGroup {
value: string; value: string;
} }
export interface IKeyValue {
Key: string;
Value: string;
}
export interface ILanguage {
language: string;
dictionary: Record<string, string>;
languageChange?: (selected: string) => void;
}
export interface IMargin { export interface IMargin {
left?: number; left?: number;
bottom?: number; bottom?: number;

View file

@ -49,7 +49,7 @@
} }
.elements-sidebar-row { .elements-sidebar-row {
@apply pl-6 pr-6 pt-2 pb-2 w-full @apply pl-6 pr-6 w-full
} }
.symbols-sidebar-row { .symbols-sidebar-row {
@apply elements-sidebar-row @apply elements-sidebar-row

View file

@ -1,12 +1,15 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import { App } from './Components/App/App'; import { App } from './Components/App/App';
import { LanguageProvider } from './Components/LanguageProvider/LanguageProvider';
import './index.scss'; import './index.scss';
function RenderRoot(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>
<LanguageProvider>
<App root={root}/> <App root={root}/>
</LanguageProvider>
</React.StrictMode> </React.StrictMode>
); );
} }

View file

@ -11,12 +11,14 @@ import { Position } from '../Enums/Position';
/// EDITOR DEFAULTS /// /// EDITOR DEFAULTS ///
/** Enable fast boot and disable main menu (default = false) */ /** Enable fast boot and disable main menu (0 = disabled, 1 = loading, 2 = loaded) */
export const FAST_BOOT = true; export const FAST_BOOT = import.meta.env.PROD ? 2 : 0;
/** Disable any call to the API (default = false) */ /** Disable any call to the API (default = false) */
export const DISABLE_API = false; export const DISABLE_API = false;
export const DEFAULT_LANGUAGE = 'fr';
/** /**
* Replace the SVG viewer by a canvas * Replace the SVG viewer by a canvas
* Better compatibility with Gecko and WebKit engines like Firefox and Safari. * Better compatibility with Gecko and WebKit engines like Firefox and Safari.

View file

@ -22,6 +22,9 @@ const requestListener = async (request, response) => {
case 'FillHoleWithChassis': case 'FillHoleWithChassis':
json = FillHoleWithChassis(bodyParsed); json = FillHoleWithChassis(bodyParsed);
break; break;
case 'Insert':
json = Insert(bodyParsed);
break;
case 'SplitRemplissage': case 'SplitRemplissage':
json = SplitRemplissage(bodyParsed); json = SplitRemplissage(bodyParsed);
break; break;
@ -118,6 +121,19 @@ const GetSVGLayoutConfiguration = () => {
}, },
Category: "Stuff", Category: "Stuff",
Actions: [ Actions: [
{
Id: "Insert",
Action: "Insert",
Label: "Insert containers",
Description: "Insert containers",
CustomLogo: {
Base64Image: null,
Name: 'Image1',
Svg: null,
Url: ""
},
AddingBehavior: 1
},
{ {
Id: "SplitRemplissage", Id: "SplitRemplissage",
Action: "SplitRemplissage", Action: "SplitRemplissage",
@ -131,32 +147,6 @@ const GetSVGLayoutConfiguration = () => {
}, },
AddingBehavior: 2 AddingBehavior: 2
}, },
{
Id: "SplitRemplissageParent",
Action: "SplitRemplissageParent",
Label: "Diviser le remplissage en insérant un montant",
Description: "Diviser le remplissage en insérant un montant",
CustomLogo: {
Base64Image: null,
Name: 'Image1',
Svg: null,
Url: ""
},
AddingBehavior: 3
},
{
Id: "SplitRemplissageParent",
Action: "SplitRemplissageParent",
Label: "Diviser le remplissage en insérant un montant",
Description: "Diviser le remplissage en insérant un montant",
CustomLogo: {
Base64Image: null,
Name: 'Image1',
Svg: null,
Url: ""
},
AddingBehavior: 3
},
{ {
Id: "SplitRemplissageParent", Id: "SplitRemplissageParent",
Action: "SplitRemplissageParent", Action: "SplitRemplissageParent",
@ -293,7 +283,10 @@ const GetSVGLayoutConfiguration = () => {
Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg' Url: 'https://www.manutan.fr/img/S/GRP/ST/AIG3930272.jpg'
}, },
Name: 'Poteau structure', Name: 'Poteau structure',
PositionReference: 1 PositionReference: 1,
AssociatedContainer: {
Type: 'Montant'
}
}, },
{ {
Width: 32, Width: 32,
@ -369,13 +362,15 @@ const FillHoleWithChassis = (request) => {
const SplitRemplissage = (request) => { const SplitRemplissage = (request) => {
const lstModels = [ const lstModels = [
{ {
Type: 'Remplissage' Type: 'Remplissage',
IsFlex: true
}, },
{ {
Type: 'Montant' Type: 'Montant'
}, },
{ {
Type: 'Remplissage' Type: 'Remplissage',
IsFlex: true
}, },
]; ];
@ -385,3 +380,24 @@ const SplitRemplissage = (request) => {
}; };
const Insert = (request) => {
const lstModels = [
{
Type: 'Remplissage',
IsFlex: true
},
{
Type: 'Montant',
X: 5,
IsAnchor: true
},
{
Type: 'Remplissage',
IsFlex: true
},
];
return {
Containers: lstModels
};
};