Merged PR 225: Implement translations
Implement translations with useContext in React +Add events to allow changing the language in the app +Refactor AppEvents +Redesign vertical bars in elements
This commit is contained in:
parent
60a3ead6aa
commit
505813d530
26 changed files with 527 additions and 160 deletions
BIN
docs/#Project/Pages/Translations.drawio
(Stored with Git LFS)
Normal file
BIN
docs/#Project/Pages/Translations.drawio
(Stored with Git LFS)
Normal file
Binary file not shown.
|
@ -5,6 +5,7 @@
|
|||
type IHistoryState = SVGLD.IHistoryState;
|
||||
type IEditorState = SVGLD.IEditorState;
|
||||
type IConfiguration = SVGLD.IConfiguration;
|
||||
type ILanguage = SVGLD.ILanguage;
|
||||
|
||||
export class SVGLayoutDesigner extends Components.ComponentBase {
|
||||
|
||||
|
@ -210,6 +211,43 @@
|
|||
const component = this.GetAppComponent();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
21
public/svgld.d.ts
vendored
21
public/svgld.d.ts
vendored
|
@ -88,6 +88,7 @@ export interface IAPIConfiguration {
|
|||
|
||||
|
||||
|
||||
|
||||
/** Model of available container used in application configuration */
|
||||
export interface IAvailableContainer {
|
||||
/** type */
|
||||
|
@ -212,7 +213,7 @@ export interface IAvailableContainer {
|
|||
* (optional)
|
||||
* User data that can be used for data storage or custom SVG
|
||||
*/
|
||||
UserData?: object;
|
||||
UserData?: IKeyValue[];
|
||||
}
|
||||
|
||||
|
||||
|
@ -250,7 +251,6 @@ export interface IConfiguration {
|
|||
|
||||
export interface IContainerModel {
|
||||
children: string[];
|
||||
parent: IContainerModel | null;
|
||||
properties: IContainerProperties;
|
||||
userData: Record<string, string | number>;
|
||||
}
|
||||
|
@ -260,10 +260,9 @@ export interface IContainerModel {
|
|||
*/
|
||||
export class ContainerModel implements IContainerModel {
|
||||
children: string[];
|
||||
parent: IContainerModel | null;
|
||||
properties: IContainerProperties;
|
||||
userData: Record<string, string | number>;
|
||||
constructor(parent: IContainerModel | null, properties: IContainerProperties, children?: string[], userData?: {});
|
||||
constructor(properties: IContainerProperties, children?: string[], userData?: {});
|
||||
}
|
||||
|
||||
|
||||
|
@ -271,6 +270,7 @@ export class ContainerModel implements IContainerModel {
|
|||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Properties of a container
|
||||
*/
|
||||
|
@ -363,7 +363,7 @@ export interface IContainerProperties {
|
|||
* (optional)
|
||||
* User data that can be used for data storage or custom SVG
|
||||
*/
|
||||
userData?: object;
|
||||
userData?: IKeyValue[];
|
||||
}
|
||||
|
||||
|
||||
|
@ -428,6 +428,17 @@ export interface IInputGroup {
|
|||
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 {
|
||||
left?: number;
|
||||
bottom?: number;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { Dispatch, SetStateAction, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { UseCustomEvents } from '../../Events/AppEvents';
|
||||
import { MainMenu } from '../MainMenu/MainMenu';
|
||||
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
|
@ -7,6 +7,7 @@ import { IEditorState } from '../../Interfaces/IEditorState';
|
|||
import { LoadState } from './Actions/Load';
|
||||
import { LoadEditor, NewEditor } from './Actions/MenuActions';
|
||||
import { DEFAULT_CONFIG, DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default';
|
||||
import { LanguageContext } from '../LanguageProvider/LanguageProvider';
|
||||
|
||||
// App will never have props
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
|
@ -44,6 +45,7 @@ function UseHTTPGETStatePreloading(
|
|||
export function App(props: IAppProps): JSX.Element {
|
||||
const [isLoaded, setLoaded] = useState<boolean>(false);
|
||||
const appRef = useRef<HTMLDivElement>(null);
|
||||
const languageContext = useContext(LanguageContext);
|
||||
|
||||
const defaultMainContainer = new ContainerModel(
|
||||
DEFAULT_MAINCONTAINER_PROPS
|
||||
|
@ -68,6 +70,7 @@ export function App(props: IAppProps): JSX.Element {
|
|||
UseCustomEvents(
|
||||
props.root,
|
||||
appRef,
|
||||
languageContext,
|
||||
setEditorState,
|
||||
setLoaded
|
||||
);
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
Cog8ToothIcon as Cog8ToothIconS
|
||||
} from '@heroicons/react/24/solid';
|
||||
import { BarIcon } from './BarIcon';
|
||||
import { Text } from '../Text/Text';
|
||||
|
||||
interface IBarProps {
|
||||
isComponentsOpen: boolean
|
||||
|
@ -35,7 +36,7 @@ export function Bar(props: IBarProps): JSX.Element {
|
|||
<div className='bar'>
|
||||
<BarIcon
|
||||
isActive={props.isComponentsOpen}
|
||||
title='Components'
|
||||
title={Text({ textId: '@Components' })}
|
||||
onClick={() => props.toggleComponents()}>
|
||||
{
|
||||
props.isComponentsOpen
|
||||
|
@ -45,7 +46,7 @@ export function Bar(props: IBarProps): JSX.Element {
|
|||
</BarIcon>
|
||||
<BarIcon
|
||||
isActive={props.isSymbolsOpen}
|
||||
title='Symbols'
|
||||
title={Text({ textId: '@Symbols' })}
|
||||
onClick={() => props.toggleSymbols()}>
|
||||
{
|
||||
props.isSymbolsOpen
|
||||
|
@ -55,7 +56,7 @@ export function Bar(props: IBarProps): JSX.Element {
|
|||
</BarIcon>
|
||||
<BarIcon
|
||||
isActive={props.isMessagesOpen}
|
||||
title='Messages'
|
||||
title={Text({ textId: '@Messages' })}
|
||||
onClick={() => props.toggleMessages()}>
|
||||
{
|
||||
props.isMessagesOpen
|
||||
|
@ -66,7 +67,7 @@ export function Bar(props: IBarProps): JSX.Element {
|
|||
<div className='grow'></div>
|
||||
<BarIcon
|
||||
isActive={props.isHistoryOpen}
|
||||
title='Timeline'
|
||||
title={Text({ textId: '@Timeline' })}
|
||||
onClick={() => props.toggleTimeline()}>
|
||||
{
|
||||
props.isHistoryOpen
|
||||
|
@ -76,7 +77,7 @@ export function Bar(props: IBarProps): JSX.Element {
|
|||
</BarIcon>
|
||||
<BarIcon
|
||||
isActive={props.isSettingsOpen}
|
||||
title='Settings'
|
||||
title={Text({ textId: '@Settings' })}
|
||||
onClick={() => props.toggleSettings()}>
|
||||
{
|
||||
props.isSettingsOpen
|
||||
|
|
|
@ -5,6 +5,7 @@ import { ICategory } from '../../Interfaces/ICategory';
|
|||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { TruncateString } from '../../utils/stringtools';
|
||||
import { Category } from '../Category/Category';
|
||||
import { Text } from '../Text/Text';
|
||||
|
||||
interface IComponentsProps {
|
||||
selectedContainer: IContainerModel | undefined
|
||||
|
@ -24,7 +25,7 @@ interface SidebarCategory {
|
|||
|
||||
export function Components(props: IComponentsProps): JSX.Element {
|
||||
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 categories = new Map<string, SidebarCategory>(props.categories.map(category => [
|
||||
|
|
|
@ -4,6 +4,7 @@ import { IContainerProperties } from '../../Interfaces/IContainerProperties';
|
|||
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||
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 { Text } from '../Text/Text';
|
||||
import { InputGroup } from '../InputGroup/InputGroup';
|
||||
import { TextInputGroup } from '../InputGroup/TextInputGroup';
|
||||
import { Select } from '../Select/Select';
|
||||
|
@ -44,7 +45,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
<div className='grid grid-cols-1 gap-y-4 items-center'>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-displayedText`}
|
||||
labelText='Displayed text'
|
||||
labelText={Text({ textId: '@ContainerDisplayedText' })}
|
||||
inputKey='displayedText'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -54,7 +55,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
<OrientationSelector
|
||||
id='orientation'
|
||||
name='Orientation'
|
||||
labelText='Orientation'
|
||||
labelText={Text({ textId: '@ContainerOrientation' })}
|
||||
value={props.properties.orientation}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
|
@ -62,14 +63,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
<Category
|
||||
category={{
|
||||
Type: 'Properties',
|
||||
DisplayedText: 'Properties'
|
||||
DisplayedText: Text({ textId: '@ContainerProperties' })
|
||||
}}
|
||||
heightClass={`${categoryHeight}`}
|
||||
>
|
||||
<div className='grid grid-cols-1 gap-y-6 items-center prop-category-body'>
|
||||
<div>
|
||||
<InputGroup
|
||||
labelText='Name'
|
||||
labelText={Text({ textId: '@ContainerName' })}
|
||||
inputKey='id'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -79,7 +80,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
</div>
|
||||
<div>
|
||||
<InputGroup
|
||||
labelText='Parent name'
|
||||
labelText={Text({ textId: '@ContainerParentName' })}
|
||||
inputKey='parentId'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -89,7 +90,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
</div>
|
||||
<div>
|
||||
<InputGroup
|
||||
labelText='Type'
|
||||
labelText={Text({ textId: '@ContainerType' })}
|
||||
inputKey='type'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -102,7 +103,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
|
||||
<Category category={{
|
||||
Type: 'Position',
|
||||
DisplayedText: 'Position'
|
||||
DisplayedText: Text({ textId: '@ContainerPosition' })
|
||||
}}
|
||||
defaultIsOpen={true}
|
||||
heightClass={`${categoryHeight}`}
|
||||
|
@ -110,7 +111,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
<div className='grid grid-cols-3 gap-y-2 items-center prop-category-body'>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-x`}
|
||||
labelText='x'
|
||||
labelText={Text({ textId: '@ContainerX' })}
|
||||
inputKey='x'
|
||||
labelClassName=''
|
||||
inputClassName='col-span-2'
|
||||
|
@ -134,7 +135,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
)} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-y`}
|
||||
labelText='y'
|
||||
labelText={Text({ textId: '@ContainerY' })}
|
||||
inputKey='y'
|
||||
labelClassName=''
|
||||
inputClassName='col-span-2'
|
||||
|
@ -158,16 +159,17 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
</div>
|
||||
</Category>
|
||||
|
||||
<Category category={{
|
||||
Type: 'Size',
|
||||
DisplayedText: 'Size'
|
||||
}}
|
||||
heightClass={`${categoryHeight}`}
|
||||
<Category
|
||||
category={{
|
||||
Type: 'Size',
|
||||
DisplayedText: Text({ textId: '@ContainerSize' })
|
||||
}}
|
||||
heightClass={`${categoryHeight}`}
|
||||
>
|
||||
<div className='grid grid-cols-5 gap-y-2 items-center prop-category-body'>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-minWidth`}
|
||||
labelText='Minimum Width'
|
||||
labelText={Text({ textId: '@ContainerMinWidth' })}
|
||||
inputKey='minWidth'
|
||||
labelClassName='col-span-2'
|
||||
inputClassName='col-span-3'
|
||||
|
@ -177,7 +179,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
onChange={(value) => props.onChange('minWidth', Number(value))} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-width`}
|
||||
labelText='Width'
|
||||
labelText={Text({ textId: '@ContainerWidth' })}
|
||||
inputKey='width'
|
||||
labelClassName='col-span-2'
|
||||
inputClassName='col-span-3'
|
||||
|
@ -189,7 +191,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
isDisabled={props.properties.isFlex} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-maxWidth`}
|
||||
labelText='Maximum Width'
|
||||
labelText={Text({ textId: '@ContainerMaxWidth' })}
|
||||
inputKey='maxWidth'
|
||||
labelClassName='col-span-2'
|
||||
inputClassName='col-span-3'
|
||||
|
@ -200,7 +202,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
<div className='col-span-5 p-3'></div>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-minHeight`}
|
||||
labelText='Minimum Height'
|
||||
labelText={Text({ textId: '@ContainerMinHeight' })}
|
||||
inputKey='minHeight'
|
||||
labelClassName='col-span-2'
|
||||
inputClassName='col-span-3'
|
||||
|
@ -210,7 +212,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
onChange={(value) => props.onChange('minHeight', Number(value))} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-height`}
|
||||
labelText='Height'
|
||||
labelText={Text({ textId: '@ContainerHeight' })}
|
||||
inputKey='height'
|
||||
labelClassName='col-span-2'
|
||||
inputClassName='col-span-3'
|
||||
|
@ -223,7 +225,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
/>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-maxHeight`}
|
||||
labelText='Maximum Height'
|
||||
labelText={Text({ textId: '@ContainerMaxHeight' })}
|
||||
inputKey='maxHeight'
|
||||
labelClassName='col-span-2'
|
||||
inputClassName='col-span-3'
|
||||
|
@ -236,14 +238,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
|
||||
<Category category={{
|
||||
Type: 'Margins',
|
||||
DisplayedText: 'Margins'
|
||||
DisplayedText: Text({ textId: '@ContainerMargins' })
|
||||
}}
|
||||
heightClass={`${categoryHeight}`}
|
||||
>
|
||||
<div className='grid grid-cols-2 items-center gap-y-2 prop-category-body'>
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-ml`}
|
||||
labelText='Margin left'
|
||||
labelText={Text({ textId: '@ContainerMarginLeft' })}
|
||||
inputKey='left'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -253,7 +255,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
onChange={(value) => props.onChange('left', Number(value), PropertyType.Margin)} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-mb`}
|
||||
labelText='Margin bottom'
|
||||
labelText={Text({ textId: '@ContainerMarginBottom' })}
|
||||
inputKey='bottom'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -263,7 +265,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
onChange={(value) => props.onChange('bottom', Number(value), PropertyType.Margin)} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-mt`}
|
||||
labelText='Margin top'
|
||||
labelText={Text({ textId: '@ContainerMarginTop' })}
|
||||
inputKey='top'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -273,7 +275,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
onChange={(value) => props.onChange('top', Number(value), PropertyType.Margin)} />
|
||||
<TextInputGroup
|
||||
id={`${props.properties.id}-mr`}
|
||||
labelText='Margin right'
|
||||
labelText={Text({ textId: '@ContainerMarginRight' })}
|
||||
inputKey='right'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -286,13 +288,13 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
|
||||
<Category category={{
|
||||
Type: 'Behaviors',
|
||||
DisplayedText: 'Behaviors'
|
||||
DisplayedText: Text({ textId: '@ContainerBehaviors' })
|
||||
}}
|
||||
heightClass={`${categoryHeight}`}
|
||||
>
|
||||
<div className='grid grid-cols-2 items-center gap-y-2 prop-category-body'>
|
||||
<ToggleButton
|
||||
labelText='Flex'
|
||||
labelText={Text({ textId: '@ContainerFlex' })}
|
||||
inputKey='isFlex'
|
||||
labelClassName=''
|
||||
inputClassName='ml-auto mr-auto block'
|
||||
|
@ -301,7 +303,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
onChange={(event) => props.onChange('isFlex', event.target.checked)}
|
||||
/>
|
||||
<ToggleButton
|
||||
labelText='Anchor'
|
||||
labelText={Text({ textId: '@ContainerAnchor' })}
|
||||
inputKey='isAnchor'
|
||||
labelClassName=''
|
||||
inputClassName='ml-auto mr-auto block'
|
||||
|
@ -313,7 +315,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
|
||||
<Category category={{
|
||||
Type: 'Alignment',
|
||||
DisplayedText: 'Alignment'
|
||||
DisplayedText: Text({ textId: '@ContainerAlignment' })
|
||||
}}
|
||||
heightClass={`${categoryHeight}`}
|
||||
>
|
||||
|
@ -321,14 +323,14 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
<PositionReferenceSelector
|
||||
id='positionReference'
|
||||
name='PositionReference'
|
||||
labelText='Alignment'
|
||||
labelText={Text({ textId: '@ContainerAlignmentInput' })}
|
||||
value={props.properties.positionReference}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<div className='p-3'></div>
|
||||
<Select
|
||||
inputKey='linkedSymbolId'
|
||||
labelText='Align with symbol'
|
||||
labelText={Text({ textId: '@ContainerAlignWithSymbol' })}
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
inputs={[...props.symbols.values()].map(symbol => ({
|
||||
|
@ -343,7 +345,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
|
||||
<Category category={{
|
||||
Type: 'Dimensions',
|
||||
DisplayedText: 'Dimensions'
|
||||
DisplayedText: Text({ textId: '@ContainerDimensions' })
|
||||
}}
|
||||
heightClass={`${categoryHeight}`}
|
||||
>
|
||||
|
@ -354,7 +356,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
<PositionCheckboxes
|
||||
id='showSelfDimensions'
|
||||
name='ShowSelfDimensions'
|
||||
labelText='Show dimension'
|
||||
labelText={Text({ textId: '@ContainerShowDimension' })}
|
||||
value={props.properties.showSelfDimensions}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
|
@ -366,7 +368,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
<PositionCheckboxes
|
||||
id='showChildrenDimensions'
|
||||
name='ShowChildrenDimensions'
|
||||
labelText='Show overall dimension of its children'
|
||||
labelText={Text({ textId: '@ContainerShowChildrenDimension' })}
|
||||
value={props.properties.showChildrenDimensions}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
|
@ -374,33 +376,33 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
|
|||
}
|
||||
{
|
||||
SHOW_BORROWER_DIMENSIONS &&
|
||||
<>
|
||||
<div className='grid grid-cols-1 gap-2'>
|
||||
<OrientationCheckboxes
|
||||
id='markPosition'
|
||||
name='MarkPosition'
|
||||
value={props.properties.markPosition}
|
||||
labelText='Mark the position'
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='grid grid-cols-1 gap-2'>
|
||||
<PositionCheckboxes
|
||||
id='showDimensionWithMarks'
|
||||
name='ShowDimensionWithMarks'
|
||||
labelText='Show dimension with marked children'
|
||||
value={props.properties.showDimensionWithMarks}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<>
|
||||
<div className='grid grid-cols-1 gap-2'>
|
||||
<OrientationCheckboxes
|
||||
id='markPosition'
|
||||
name='MarkPosition'
|
||||
value={props.properties.markPosition}
|
||||
labelText={Text({ textId: '@ContainerMarkPosition' })}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
</div>
|
||||
<div className='grid grid-cols-1 gap-2'>
|
||||
<PositionCheckboxes
|
||||
id='showDimensionWithMarks'
|
||||
name='ShowDimensionWithMarks'
|
||||
labelText={Text({ textId: '@ContainerShowDimensionWithMarks' })}
|
||||
value={props.properties.showDimensionWithMarks}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</Category>
|
||||
|
||||
<Category category={{
|
||||
Type: 'Style',
|
||||
DisplayedText: 'Style'
|
||||
DisplayedText: Text({ textId: '@ContainerStyle' })
|
||||
}}
|
||||
heightClass={`${categoryHeight}`}
|
||||
>
|
||||
|
|
|
@ -15,6 +15,7 @@ import { GetCurrentHistoryState } from '../Editor';
|
|||
import { AddContainers } from './AddContainer';
|
||||
import { DeleteContainer } from './ContainerOperations';
|
||||
import { DeleteSymbol } from './SymbolOperations';
|
||||
import { Text } from '../../Text/Text';
|
||||
|
||||
export function InitActions(
|
||||
menuActions: Map<string, IMenuAction[]>,
|
||||
|
@ -28,8 +29,8 @@ export function InitActions(
|
|||
'',
|
||||
[
|
||||
{
|
||||
text: 'Undo',
|
||||
title: 'Undo last action',
|
||||
text: Text({ textId: '@Undo' }),
|
||||
title: Text({ textId: '@UndoTitle' }),
|
||||
shortcut: '<kbd>Ctrl</kbd>+<kbd>Z</kbd>',
|
||||
action: () => {
|
||||
if (historyCurrentStep <= 0) {
|
||||
|
@ -39,8 +40,8 @@ export function InitActions(
|
|||
}
|
||||
},
|
||||
{
|
||||
text: 'Redo',
|
||||
title: 'Redo last action',
|
||||
text: Text({ textId: '@Redo' }),
|
||||
title: Text({ textId: '@RedoTitle' }),
|
||||
shortcut: '<kbd>Ctrl</kbd>+<kbd>Y</kbd>',
|
||||
action: () => {
|
||||
if (historyCurrentStep >= history.length - 1) {
|
||||
|
@ -55,8 +56,8 @@ export function InitActions(
|
|||
menuActions.set(
|
||||
'elements-sidebar-row',
|
||||
[{
|
||||
text: 'Delete',
|
||||
title: 'Delete the container',
|
||||
text: Text({ textId: '@DeleteContainer' }),
|
||||
title: Text({ textId: '@DeleteContainerTitle' }),
|
||||
shortcut: '<kbd>Suppr</kbd>',
|
||||
action: (target: HTMLElement) => {
|
||||
const id = target.id;
|
||||
|
@ -73,8 +74,8 @@ export function InitActions(
|
|||
menuActions.set(
|
||||
'symbols-sidebar-row',
|
||||
[{
|
||||
text: 'Delete',
|
||||
title: 'Delete the container',
|
||||
text: Text({ textId: '@DeleteSymbol' }),
|
||||
title: Text({ textId: '@DeleteSymbolTitle' }),
|
||||
shortcut: '<kbd>Suppr</kbd>',
|
||||
action: (target: HTMLElement) => {
|
||||
const id = target.id;
|
||||
|
|
|
@ -135,7 +135,6 @@ export function ElementsList(props: IElementsListProps): JSX.Element {
|
|||
}): JSX.Element {
|
||||
const { container, depth } = containers[index];
|
||||
const key = container.properties.id.toString();
|
||||
const tabs = '|\t'.repeat(depth);
|
||||
const text = container.properties.displayedText === key
|
||||
? `${key}`
|
||||
: `${container.properties.displayedText}`;
|
||||
|
@ -144,29 +143,19 @@ export function ElementsList(props: IElementsListProps): JSX.Element {
|
|||
props.selectedContainer !== null &&
|
||||
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 (
|
||||
<button type="button"
|
||||
className={`transition-all w-full border-blue-500 hover:shadow-lg elements-sidebar-row whitespace-pre
|
||||
text-left text-sm font-medium inline-flex ${container.properties.type} ${selectedClass}`}
|
||||
id={key}
|
||||
key={key}
|
||||
style={style}
|
||||
title={container.properties.warning}
|
||||
onClick={() => props.selectContainer(container.properties.id)}
|
||||
onDrop={(event) => HandleOnDrop(event, props.containers, props.mainContainer, props.addContainer)}
|
||||
onDragOver={(event) => HandleDragOver(event, props.mainContainer)}
|
||||
onDragLeave={(event) => HandleDragLeave(event)}
|
||||
>
|
||||
{tabs}
|
||||
{text}
|
||||
{container.properties.warning.length > 0 &&
|
||||
<ExclamationTriangleIcon className='w-8'/>
|
||||
}
|
||||
</button>
|
||||
ElementsListRow(
|
||||
key,
|
||||
container,
|
||||
depth,
|
||||
isSelected,
|
||||
style,
|
||||
text,
|
||||
props.containers,
|
||||
props.mainContainer,
|
||||
props.addContainer,
|
||||
props.selectContainer
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -193,3 +182,52 @@ export function ElementsList(props: IElementsListProps): JSX.Element {
|
|||
</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
|
||||
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 inline-flex 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}
|
||||
<div className='pt-2 pb-2 inline-flex'>
|
||||
{text}
|
||||
{container.properties.warning.length > 0 &&
|
||||
<ExclamationTriangleIcon className='pl-2 w-7' />}
|
||||
</div>
|
||||
</button>;
|
||||
}
|
||||
|
|
31
src/Components/LanguageProvider/LanguageProvider.tsx
Normal file
31
src/Components/LanguageProvider/LanguageProvider.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import { FAST_BOOT } from '../../utils/default';
|
||||
import { Loader } from '../Loader/Loader';
|
||||
import { Text } from '../Text/Text';
|
||||
|
||||
interface IMainMenuProps {
|
||||
newEditor: () => void
|
||||
|
@ -82,8 +83,16 @@ export function MainMenu(props: IMainMenuProps): JSX.Element {
|
|||
<button type="button" className='mainmenu-btn' onClick={() => {
|
||||
setWindowState(WindowState.Loading);
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { IMessage } from '../../Interfaces/IMessage';
|
|||
import { DISABLE_API } from '../../utils/default';
|
||||
import { GetCircularReplacer } from '../../utils/saveload';
|
||||
import { TITLE_BAR_HEIGHT } from '../Sidebar/Sidebar';
|
||||
import { Text } from '../Text/Text';
|
||||
|
||||
interface IMessagesProps {
|
||||
historyState: IHistoryState
|
||||
|
@ -49,8 +50,8 @@ export function Messages(props: IMessagesProps): JSX.Element {
|
|||
<button
|
||||
onClick={() => { props.clearMessage(); }}
|
||||
className='h-full hover:bg-slate-400 rounded-lg p-1'
|
||||
aria-label='Clear all messages'
|
||||
title='Clear all messages'
|
||||
aria-label={Text({ textId: '@ClearAllMessages' })}
|
||||
title={Text({ textId: '@ClearAllMessages' })}
|
||||
>
|
||||
<TrashIcon className='heroicon'></TrashIcon>
|
||||
</button>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ArrowUpOnSquareIcon, CameraIcon } from '@heroicons/react/24/outline';
|
||||
import React from 'react';
|
||||
import { Text } from '../Text/Text';
|
||||
|
||||
interface ISettingsProps {
|
||||
saveEditorAsJSON: () => void
|
||||
|
@ -12,19 +13,19 @@ export function Settings(props: ISettingsProps): JSX.Element {
|
|||
m-2 md:text-xs font-bold'>
|
||||
<button type="button"
|
||||
className={'w-full transition-all flex sidebar-component'}
|
||||
title='Export as JSON'
|
||||
title={Text({ textId: '@ExportAsJSON' })}
|
||||
onClick={props.saveEditorAsJSON}
|
||||
>
|
||||
<ArrowUpOnSquareIcon className="heroicon w-16 h-7" />
|
||||
Export as JSON
|
||||
{Text({ textId: '@ExportAsJSON' })}
|
||||
</button>
|
||||
<button type="button"
|
||||
className={'w-full transition-all flex sidebar-component'}
|
||||
title='Export as SVG'
|
||||
title={Text({ textId: '@ExportAsSVG' })}
|
||||
onClick={props.saveEditorAsSVG}
|
||||
>
|
||||
<CameraIcon className="heroicon w-16 h-7" />
|
||||
Export as SVG
|
||||
{Text({ textId: '@ExportAsSVG' })}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@ import { RestoreX, TransformX } from '../../utils/svg';
|
|||
import { InputGroup } from '../InputGroup/InputGroup';
|
||||
import { TextInputGroup } from '../InputGroup/TextInputGroup';
|
||||
import { PositionReferenceSelector } from '../RadioGroupButtons/PositionReferenceSelector';
|
||||
import { Text } from '../Text/Text';
|
||||
|
||||
interface ISymbolFormProps {
|
||||
symbol: ISymbolModel
|
||||
|
@ -15,7 +16,7 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
|||
return (
|
||||
<div className='grid grid-cols-2 gap-y-4'>
|
||||
<InputGroup
|
||||
labelText='Name'
|
||||
labelText={Text({ textId: '@SymbolName' })}
|
||||
inputKey='id'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -24,7 +25,7 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
|||
isDisabled={true} />
|
||||
<TextInputGroup
|
||||
id='x'
|
||||
labelText='x'
|
||||
labelText={Text({ textId: '@SymbolX' })}
|
||||
inputKey='x'
|
||||
labelClassName=''
|
||||
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))} />
|
||||
<TextInputGroup
|
||||
id='height'
|
||||
labelText='Height'
|
||||
labelText={Text({ textId: '@SymbolHeight' })}
|
||||
inputKey='height'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
@ -43,7 +44,7 @@ export function SymbolForm(props: ISymbolFormProps): JSX.Element {
|
|||
onChange={(value) => props.onChange('height', Number(value))} />
|
||||
<TextInputGroup
|
||||
id='width'
|
||||
labelText='Width'
|
||||
labelText={Text({ textId: '@SymbolWidth' })}
|
||||
inputKey='width'
|
||||
labelClassName=''
|
||||
inputClassName=''
|
||||
|
|
11
src/Components/Text/Text.tsx
Normal file
11
src/Components/Text/Text.tsx
Normal 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;
|
||||
};
|
|
@ -16,6 +16,7 @@ import { UseWorker, UseAsync } from './UseWorker';
|
|||
import { FindContainerById } from '../../utils/itertools';
|
||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||
import { GetCurrentHistoryState } from '../Editor/Editor';
|
||||
import { Text } from '../Text/Text';
|
||||
|
||||
export interface IUIProps {
|
||||
editorState: IEditorState
|
||||
|
@ -95,14 +96,14 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
|
|||
|
||||
switch (selectedSidebar) {
|
||||
case SidebarType.Components:
|
||||
leftSidebarTitle = 'Components';
|
||||
leftSidebarTitle = Text({ textId: '@Components' });
|
||||
leftChildren = <Components
|
||||
selectedContainer={selectedContainer}
|
||||
componentOptions={configuration.AvailableContainers}
|
||||
categories={configuration.Categories}
|
||||
buttonOnClick={methods.addContainer}
|
||||
/>;
|
||||
rightSidebarTitle = 'Elements';
|
||||
rightSidebarTitle = Text({ textId: '@Elements' });
|
||||
rightChildren = <ElementsList
|
||||
containers={current.containers}
|
||||
mainContainer={mainContainer}
|
||||
|
@ -115,12 +116,12 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
|
|||
break;
|
||||
|
||||
case SidebarType.Symbols:
|
||||
leftSidebarTitle = 'Symbols';
|
||||
leftSidebarTitle = Text({ textId: '@SymbolsLeft' });
|
||||
leftChildren = <Symbols
|
||||
componentOptions={configuration.AvailableSymbols}
|
||||
buttonOnClick={methods.addSymbol}
|
||||
/>;
|
||||
rightSidebarTitle = 'Symbols';
|
||||
rightSidebarTitle = Text({ textId: '@SymbolsRight' });
|
||||
rightChildren = <SymbolsSidebar
|
||||
selectedSymbolId={current.selectedSymbolId}
|
||||
symbols={current.symbols}
|
||||
|
@ -130,7 +131,7 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
|
|||
break;
|
||||
|
||||
case SidebarType.History:
|
||||
leftSidebarTitle = 'Timeline';
|
||||
leftSidebarTitle = Text({ textId: '@Timeline' });
|
||||
leftChildren = <History
|
||||
history={editorState.history}
|
||||
historyCurrentStep={editorState.historyCurrentStep}
|
||||
|
@ -139,7 +140,7 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
|
|||
break;
|
||||
|
||||
case SidebarType.Messages:
|
||||
leftSidebarTitle = 'Messages';
|
||||
leftSidebarTitle = Text({ textId: '@Messages' });
|
||||
leftChildren = <Messages
|
||||
historyState={current}
|
||||
messages={messages}
|
||||
|
@ -148,7 +149,7 @@ export function UI({ editorState, ...methods }: IUIProps): JSX.Element {
|
|||
break;
|
||||
|
||||
case SidebarType.Settings:
|
||||
leftSidebarTitle = 'Settings';
|
||||
leftSidebarTitle = Text({ textId: '@Settings' });
|
||||
leftChildren = <Settings
|
||||
saveEditorAsJSON={methods.saveEditorAsJSON}
|
||||
saveEditorAsSVG={methods.saveEditorAsSVG}
|
||||
|
|
|
@ -2,17 +2,22 @@ import { useEffect } from 'react';
|
|||
import { IConfiguration } from '../Interfaces/IConfiguration';
|
||||
import { IEditorState } from '../Interfaces/IEditorState';
|
||||
import { IHistoryState } from '../Interfaces/IHistoryState';
|
||||
import { ILanguage } from '../Interfaces/ILanguage';
|
||||
import { languageOptions, translations } from '../Translations/Translations';
|
||||
import { GetDefaultEditorState as GetDefaultEditorStateAction } from '../utils/default';
|
||||
import { Revive, ReviveHistory as ReviveHistoryAction } from '../utils/saveload';
|
||||
|
||||
interface IAppEventParams {
|
||||
root: Element | Document
|
||||
languageContext: ILanguage
|
||||
setEditor: (newState: IEditorState) => void
|
||||
setLoaded: (loaded: boolean) => void
|
||||
eventInitDict?: CustomEventInit
|
||||
}
|
||||
|
||||
export interface IAppEvent {
|
||||
name: string
|
||||
func: (
|
||||
root: Element | Document,
|
||||
setEditor: (newState: IEditorState) => void,
|
||||
setLoaded: (loaded: boolean) => void,
|
||||
eventInitDict?: CustomEventInit
|
||||
) => void
|
||||
func: (params: IAppEventParams) => void
|
||||
}
|
||||
|
||||
export const events: IAppEvent[] = [
|
||||
|
@ -20,12 +25,16 @@ export const events: IAppEvent[] = [
|
|||
{ name: 'setLoaded', func: SetLoaded },
|
||||
{ name: 'reviveEditorState', func: ReviveEditorState },
|
||||
{ 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(
|
||||
root: Element | Document,
|
||||
appRef: React.RefObject<HTMLDivElement>,
|
||||
languageContext: ILanguage,
|
||||
setEditor: (newState: IEditorState) => void,
|
||||
setLoaded: (loaded: boolean) => void
|
||||
): void {
|
||||
|
@ -33,12 +42,13 @@ export function UseCustomEvents(
|
|||
const funcs = new Map<string, () => void>();
|
||||
for (const event of events) {
|
||||
function Func(eventInitDict?: CustomEventInit): void {
|
||||
return event.func(
|
||||
return event.func({
|
||||
root,
|
||||
languageContext,
|
||||
setEditor,
|
||||
setLoaded,
|
||||
eventInitDict
|
||||
);
|
||||
});
|
||||
}
|
||||
appRef.current?.addEventListener(event.name, Func);
|
||||
funcs.set(event.name, Func);
|
||||
|
@ -55,57 +65,85 @@ export function UseCustomEvents(
|
|||
});
|
||||
}
|
||||
|
||||
function SetEditor(
|
||||
root: Element | Document,
|
||||
setEditor: (newState: IEditorState) => void,
|
||||
setLoaded: (loaded: boolean) => void,
|
||||
eventInitDict?: CustomEventInit
|
||||
): void {
|
||||
function SetEditor({
|
||||
root,
|
||||
setEditor,
|
||||
eventInitDict
|
||||
}: IAppEventParams): void {
|
||||
const editor: IEditorState = eventInitDict?.detail;
|
||||
setEditor(editor);
|
||||
const customEvent = new CustomEvent<IEditorState>('setEditor', { detail: editor });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function SetLoaded(
|
||||
root: Element | Document,
|
||||
setEditor: (newState: IEditorState) => void,
|
||||
setLoaded: (loaded: boolean) => void,
|
||||
eventInitDict?: CustomEventInit
|
||||
): void {
|
||||
function SetLoaded({
|
||||
setLoaded,
|
||||
eventInitDict
|
||||
}: IAppEventParams): void {
|
||||
const isLoaded: boolean = eventInitDict?.detail;
|
||||
setLoaded(isLoaded);
|
||||
}
|
||||
|
||||
function ReviveEditorState(
|
||||
root: Element | Document,
|
||||
setEditor: (newState: IEditorState) => void,
|
||||
setLoaded: (loaded: boolean) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
function ReviveEditorState({
|
||||
root,
|
||||
eventInitDict
|
||||
}: IAppEventParams): void {
|
||||
const anEditorState: IEditorState = eventInitDict?.detail;
|
||||
Revive(anEditorState);
|
||||
const customEvent = new CustomEvent<IEditorState>('reviveEditorState', { detail: anEditorState });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function ReviveHistory(
|
||||
root: Element | Document,
|
||||
setEditor: (newState: IEditorState) => void,
|
||||
setLoaded: (loaded: boolean) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
function ReviveHistory({
|
||||
root,
|
||||
eventInitDict
|
||||
}: IAppEventParams): void {
|
||||
const history: IHistoryState[] = eventInitDict?.detail;
|
||||
ReviveHistoryAction(history);
|
||||
const customEvent = new CustomEvent<IHistoryState[]>('reviveHistory', { detail: history });
|
||||
root.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function GetDefaultEditorState(
|
||||
root: Element | Document,
|
||||
setEditor: (newState: IEditorState) => void,
|
||||
setLoaded: (loaded: boolean) => void,
|
||||
eventInitDict?: CustomEventInit): void {
|
||||
function GetDefaultEditorState({
|
||||
root,
|
||||
eventInitDict
|
||||
}: IAppEventParams): void {
|
||||
const configuration: IConfiguration = eventInitDict?.detail;
|
||||
const editorState = GetDefaultEditorStateAction(configuration);
|
||||
const customEvent = new CustomEvent<IEditorState>('getDefaultEditorState', { detail: editorState });
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { IHistoryState } from '../Interfaces/IHistoryState';
|
|||
import { FindContainerById } from '../utils/itertools';
|
||||
import { GetCircularReplacer } from '../utils/saveload';
|
||||
|
||||
// TODO: set the params of func in an interface
|
||||
export interface IEditorEvent {
|
||||
name: string
|
||||
func: (
|
||||
|
|
5
src/Interfaces/ILanguage.ts
Normal file
5
src/Interfaces/ILanguage.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export interface ILanguage {
|
||||
language: string
|
||||
dictionary: Record<string, string>
|
||||
languageChange?: (selected: string) => void
|
||||
}
|
14
src/Translations/Translations.ts
Normal file
14
src/Translations/Translations.ts
Normal 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'
|
||||
};
|
70
src/Translations/translation.en.json
Normal file
70
src/Translations/translation.en.json
Normal file
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"@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",
|
||||
|
||||
"@SymbolName": "Name",
|
||||
"@SymbolX": "x",
|
||||
"@SymbolHeight": "Height",
|
||||
"@SymbolWidth": "Width"
|
||||
}
|
70
src/Translations/translation.fr.json
Normal file
70
src/Translations/translation.fr.json
Normal file
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"@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",
|
||||
|
||||
"@SymbolName": "Nom",
|
||||
"@SymbolX": "x",
|
||||
"@SymbolHeight": "Hauteur",
|
||||
"@SymbolWidth": "Largeur"
|
||||
}
|
21
src/dts/svgld.d.ts
vendored
21
src/dts/svgld.d.ts
vendored
|
@ -88,6 +88,7 @@ export interface IAPIConfiguration {
|
|||
|
||||
|
||||
|
||||
|
||||
/** Model of available container used in application configuration */
|
||||
export interface IAvailableContainer {
|
||||
/** type */
|
||||
|
@ -212,7 +213,7 @@ export interface IAvailableContainer {
|
|||
* (optional)
|
||||
* User data that can be used for data storage or custom SVG
|
||||
*/
|
||||
UserData?: object;
|
||||
UserData?: IKeyValue[];
|
||||
}
|
||||
|
||||
|
||||
|
@ -250,7 +251,6 @@ export interface IConfiguration {
|
|||
|
||||
export interface IContainerModel {
|
||||
children: string[];
|
||||
parent: IContainerModel | null;
|
||||
properties: IContainerProperties;
|
||||
userData: Record<string, string | number>;
|
||||
}
|
||||
|
@ -260,10 +260,9 @@ export interface IContainerModel {
|
|||
*/
|
||||
export class ContainerModel implements IContainerModel {
|
||||
children: string[];
|
||||
parent: IContainerModel | null;
|
||||
properties: IContainerProperties;
|
||||
userData: Record<string, string | number>;
|
||||
constructor(parent: IContainerModel | null, properties: IContainerProperties, children?: string[], userData?: {});
|
||||
constructor(properties: IContainerProperties, children?: string[], userData?: {});
|
||||
}
|
||||
|
||||
|
||||
|
@ -271,6 +270,7 @@ export class ContainerModel implements IContainerModel {
|
|||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Properties of a container
|
||||
*/
|
||||
|
@ -363,7 +363,7 @@ export interface IContainerProperties {
|
|||
* (optional)
|
||||
* User data that can be used for data storage or custom SVG
|
||||
*/
|
||||
userData?: object;
|
||||
userData?: IKeyValue[];
|
||||
}
|
||||
|
||||
|
||||
|
@ -428,6 +428,17 @@ export interface IInputGroup {
|
|||
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 {
|
||||
left?: number;
|
||||
bottom?: number;
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
}
|
||||
|
||||
.elements-sidebar-row {
|
||||
@apply pl-6 pr-6 pt-2 pb-2 w-full
|
||||
@apply pl-6 pr-6 w-full
|
||||
}
|
||||
.symbols-sidebar-row {
|
||||
@apply elements-sidebar-row
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { App } from './Components/App/App';
|
||||
import { LanguageProvider } from './Components/LanguageProvider/LanguageProvider';
|
||||
import './index.scss';
|
||||
|
||||
function RenderRoot(root: Element | Document): void {
|
||||
ReactDOM.createRoot(root.querySelector('#root') as HTMLDivElement).render(
|
||||
<React.StrictMode>
|
||||
<App root={root}/>
|
||||
<LanguageProvider>
|
||||
<App root={root}/>
|
||||
</LanguageProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ export const FAST_BOOT = true;
|
|||
/** Disable any call to the API (default = false) */
|
||||
export const DISABLE_API = false;
|
||||
|
||||
export const DEFAULT_LANGUAGE = 'fr';
|
||||
|
||||
/**
|
||||
* Replace the SVG viewer by a canvas
|
||||
* Better compatibility with Gecko and WebKit engines like Firefox and Safari.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue