Merge branch 'dev' of https://techformsa.visualstudio.com/DefaultCollection/SmartConfigurator/_git/SVGLayoutDesignerReact into dev
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
commit
7e3ccdee99
46 changed files with 1068 additions and 181 deletions
|
@ -21,7 +21,7 @@ steps:
|
||||||
path: $(pnpm_config_cache)
|
path: $(pnpm_config_cache)
|
||||||
displayName: Cache pnpm
|
displayName: Cache pnpm
|
||||||
|
|
||||||
- script: |
|
- bash: |
|
||||||
curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
|
curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7
|
||||||
pnpm config set store-dir $(pnpm_config_cache)
|
pnpm config set store-dir $(pnpm_config_cache)
|
||||||
displayName: "Setup pnpm"
|
displayName: "Setup pnpm"
|
||||||
|
@ -31,7 +31,8 @@ steps:
|
||||||
versionSpec: '16.x'
|
versionSpec: '16.x'
|
||||||
displayName: 'Install Node.js 16.x LTS'
|
displayName: 'Install Node.js 16.x LTS'
|
||||||
|
|
||||||
- script: |
|
- bash: |
|
||||||
|
set -euo pipefail
|
||||||
node --version
|
node --version
|
||||||
node ./test-server/node-http.js &
|
node ./test-server/node-http.js &
|
||||||
jobs
|
jobs
|
||||||
|
@ -46,7 +47,8 @@ steps:
|
||||||
versionSpec: '>=18.7.0'
|
versionSpec: '>=18.7.0'
|
||||||
displayName: 'Install Node.js Latest'
|
displayName: 'Install Node.js Latest'
|
||||||
|
|
||||||
- script: |
|
- bash: |
|
||||||
|
set -euo pipefail
|
||||||
node --version
|
node --version
|
||||||
node ./test-server/node-http.js &
|
node ./test-server/node-http.js &
|
||||||
jobs
|
jobs
|
||||||
|
|
|
@ -4,22 +4,19 @@ onmessage = (e) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCircularReplacer = () => {
|
const getCircularReplacer = () => {
|
||||||
const seen = new WeakSet();
|
|
||||||
return (key, value) => {
|
return (key, value) => {
|
||||||
if (key === 'parent') {
|
if (key === 'parent') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'SelectedContainer') {
|
if (key === 'Symbols') {
|
||||||
return;
|
return Array.from(value.entries());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'object' && value !== null) {
|
if (key === 'linkedContainers') {
|
||||||
if (seen.has(value)) {
|
return Array.from(value);
|
||||||
return;
|
|
||||||
}
|
|
||||||
seen.add(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,9 +26,10 @@ export const App: React.FunctionComponent<IAppProps> = (props) => {
|
||||||
history: [{
|
history: [{
|
||||||
LastAction: '',
|
LastAction: '',
|
||||||
MainContainer: defaultMainContainer,
|
MainContainer: defaultMainContainer,
|
||||||
SelectedContainer: defaultMainContainer,
|
|
||||||
SelectedContainerId: defaultMainContainer.properties.id,
|
SelectedContainerId: defaultMainContainer.properties.id,
|
||||||
TypeCounters: {}
|
TypeCounters: {},
|
||||||
|
Symbols: new Map(),
|
||||||
|
SelectedSymbolId: ''
|
||||||
}],
|
}],
|
||||||
historyCurrentStep: 0
|
historyCurrentStep: 0
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { ContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
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 { XPositionReference } from '../../Enums/XPositionReference';
|
|
||||||
import { DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default';
|
import { DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default';
|
||||||
|
|
||||||
export function NewEditor(
|
export function NewEditor(
|
||||||
|
@ -26,6 +25,7 @@ export function NewEditor(
|
||||||
|
|
||||||
// Save the configuration and the new MainContainer
|
// Save the configuration and the new MainContainer
|
||||||
// and default the selected container to it
|
// and default the selected container to it
|
||||||
|
// TODO: Put this in default.ts
|
||||||
const editorState: IEditorState = {
|
const editorState: IEditorState = {
|
||||||
configuration,
|
configuration,
|
||||||
history:
|
history:
|
||||||
|
@ -33,9 +33,10 @@ export function NewEditor(
|
||||||
{
|
{
|
||||||
LastAction: '',
|
LastAction: '',
|
||||||
MainContainer,
|
MainContainer,
|
||||||
SelectedContainer: MainContainer,
|
|
||||||
SelectedContainerId: MainContainer.properties.id,
|
SelectedContainerId: MainContainer.properties.id,
|
||||||
TypeCounters: {}
|
TypeCounters: {},
|
||||||
|
Symbols: new Map(),
|
||||||
|
SelectedSymbolId: ''
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
historyCurrentStep: 0
|
historyCurrentStep: 0
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { ClockIcon, CubeIcon, MapIcon } from '@heroicons/react/outline';
|
import { ClockIcon, CubeIcon, LinkIcon, MapIcon } from '@heroicons/react/outline';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { BarIcon } from './BarIcon';
|
import { BarIcon } from './BarIcon';
|
||||||
|
|
||||||
interface IBarProps {
|
interface IBarProps {
|
||||||
isSidebarOpen: boolean
|
isSidebarOpen: boolean
|
||||||
|
isSymbolsOpen: boolean
|
||||||
isElementsSidebarOpen: boolean
|
isElementsSidebarOpen: boolean
|
||||||
isHistoryOpen: boolean
|
isHistoryOpen: boolean
|
||||||
ToggleSidebar: () => void
|
ToggleSidebar: () => void
|
||||||
ToggleElementsSidebar: () => void
|
ToggleSymbols: () => void
|
||||||
ToggleTimeline: () => void
|
ToggleTimeline: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,10 +24,10 @@ export const Bar: React.FC<IBarProps> = (props) => {
|
||||||
<CubeIcon className='heroicon'/>
|
<CubeIcon className='heroicon'/>
|
||||||
</BarIcon>
|
</BarIcon>
|
||||||
<BarIcon
|
<BarIcon
|
||||||
isActive={props.isElementsSidebarOpen}
|
isActive={props.isSymbolsOpen}
|
||||||
title='Map'
|
title='Symbols'
|
||||||
onClick={() => props.ToggleElementsSidebar()}>
|
onClick={() => props.ToggleSymbols()}>
|
||||||
<MapIcon className='heroicon'/>
|
<LinkIcon className='heroicon'/>
|
||||||
</BarIcon>
|
</BarIcon>
|
||||||
<BarIcon
|
<BarIcon
|
||||||
isActive={props.isHistoryOpen}
|
isActive={props.isHistoryOpen}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { constraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
|
||||||
* Apply the following modification to the overlapping rigid body container :
|
* Apply the following modification to the overlapping rigid body container :
|
||||||
* @param container Container to impose its position
|
* @param container Container to impose its position
|
||||||
*/
|
*/
|
||||||
export function ImposePosition(container: IContainerModel): IContainerModel {
|
export function ApplyAnchor(container: IContainerModel): IContainerModel {
|
||||||
if (container.parent === undefined ||
|
if (container.parent === undefined ||
|
||||||
container.parent === null) {
|
container.parent === null) {
|
||||||
return container;
|
return container;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
import { APPLY_BEHAVIORS_ON_CHILDREN } from '../../../utils/default';
|
import { APPLY_BEHAVIORS_ON_CHILDREN } from '../../../utils/default';
|
||||||
import { ImposePosition } from './AnchorBehaviors';
|
import { ApplyAnchor } from './AnchorBehaviors';
|
||||||
import { RecalculatePhysics } from './RigidBodyBehaviors';
|
import { ApplyRigidBody } from './RigidBodyBehaviors';
|
||||||
|
import { ApplySymbol } from './SymbolBehaviors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recalculate the position of the container and its neighbors
|
* Recalculate the position of the container and its neighbors
|
||||||
|
@ -9,19 +11,24 @@ import { RecalculatePhysics } from './RigidBodyBehaviors';
|
||||||
* @param container Container to recalculate its positions
|
* @param container Container to recalculate its positions
|
||||||
* @returns Updated container
|
* @returns Updated container
|
||||||
*/
|
*/
|
||||||
export function ApplyBehaviors(container: IContainerModel): IContainerModel {
|
export function ApplyBehaviors(container: IContainerModel, symbols: Map<string, ISymbolModel>): IContainerModel {
|
||||||
if (container.properties.isAnchor) {
|
if (container.properties.isAnchor) {
|
||||||
ImposePosition(container);
|
ApplyAnchor(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.properties.isRigidBody) {
|
if (container.properties.isRigidBody) {
|
||||||
RecalculatePhysics(container);
|
ApplyRigidBody(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
const symbol = symbols.get(container.properties.linkedSymbolId);
|
||||||
|
if (container.properties.linkedSymbolId !== '' && symbol !== undefined) {
|
||||||
|
ApplySymbol(container, symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (APPLY_BEHAVIORS_ON_CHILDREN) {
|
if (APPLY_BEHAVIORS_ON_CHILDREN) {
|
||||||
// Apply DFS by recursion
|
// Apply DFS by recursion
|
||||||
for (const child of container.children) {
|
for (const child of container.children) {
|
||||||
ApplyBehaviors(child);
|
ApplyBehaviors(child, symbols);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
||||||
* @param container Container to apply its rigid body properties
|
* @param container Container to apply its rigid body properties
|
||||||
* @returns A rigid body container
|
* @returns A rigid body container
|
||||||
*/
|
*/
|
||||||
export function RecalculatePhysics(
|
export function ApplyRigidBody(
|
||||||
container: IContainerModel
|
container: IContainerModel
|
||||||
): IContainerModel {
|
): IContainerModel {
|
||||||
container = constraintBodyInsideParent(container);
|
container = constraintBodyInsideParent(container);
|
||||||
|
|
9
src/Components/Editor/Behaviors/SymbolBehaviors.ts
Normal file
9
src/Components/Editor/Behaviors/SymbolBehaviors.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
|
import { restoreX, transformX } from '../../../utils/svg';
|
||||||
|
|
||||||
|
export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel {
|
||||||
|
container.properties.x = transformX(symbol.x, symbol.width, symbol.config.XPositionReference);
|
||||||
|
container.properties.x = restoreX(container.properties.x, container.properties.width, container.properties.XPositionReference);
|
||||||
|
return container;
|
||||||
|
}
|
|
@ -2,19 +2,20 @@ import { Dispatch, SetStateAction } from 'react';
|
||||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||||
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
|
import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { findContainerById } from '../../utils/itertools';
|
import { findContainerById, MakeIterator } from '../../utils/itertools';
|
||||||
import { getCurrentHistory } from './Editor';
|
import { getCurrentHistory, UpdateCounters } from './Editor';
|
||||||
import { AddMethod } from '../../Enums/AddMethod';
|
import { AddMethod } from '../../Enums/AddMethod';
|
||||||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
||||||
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../utils/default';
|
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../utils/default';
|
||||||
import { ApplyBehaviors } from './Behaviors/Behaviors';
|
import { ApplyBehaviors } from './Behaviors/Behaviors';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a container
|
* Select a container
|
||||||
* @param container Selected container
|
* @param container Selected container
|
||||||
*/
|
*/
|
||||||
export function SelectContainer(
|
export function SelectContainer(
|
||||||
container: ContainerModel,
|
containerId: string,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
@ -23,19 +24,13 @@ export function SelectContainer(
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
const mainContainerClone = structuredClone(current.MainContainer);
|
|
||||||
const selectedContainer = findContainerById(mainContainerClone, container.properties.id);
|
|
||||||
|
|
||||||
if (selectedContainer === undefined) {
|
|
||||||
throw new Error('[SelectContainer] Cannot find container among children of main container!');
|
|
||||||
}
|
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Select ${selectedContainer.properties.id}`,
|
LastAction: `Select ${containerId}`,
|
||||||
MainContainer: mainContainerClone,
|
MainContainer: structuredClone(current.MainContainer),
|
||||||
SelectedContainer: selectedContainer,
|
SelectedContainerId: containerId,
|
||||||
SelectedContainerId: selectedContainer.properties.id,
|
TypeCounters: Object.assign({}, current.TypeCounters),
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
Symbols: structuredClone(current.Symbols),
|
||||||
|
SelectedSymbolId: current.SelectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
@ -76,6 +71,8 @@ export function DeleteContainer(
|
||||||
if (container === null || container === undefined) {
|
if (container === null || container === undefined) {
|
||||||
throw new Error('[DeleteContainer] Container model was not found among children of the main container!');
|
throw new Error('[DeleteContainer] Container model was not found among children of the main container!');
|
||||||
}
|
}
|
||||||
|
const newSymbols = structuredClone(current.Symbols)
|
||||||
|
UnlinkSymbol(newSymbols, container);
|
||||||
|
|
||||||
const index = container.parent.children.indexOf(container);
|
const index = container.parent.children.indexOf(container);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
|
@ -94,14 +91,26 @@ export function DeleteContainer(
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Delete ${containerId}`,
|
LastAction: `Delete ${containerId}`,
|
||||||
MainContainer: mainContainerClone,
|
MainContainer: mainContainerClone,
|
||||||
SelectedContainer,
|
|
||||||
SelectedContainerId,
|
SelectedContainerId,
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
TypeCounters: Object.assign({}, current.TypeCounters),
|
||||||
|
Symbols: newSymbols,
|
||||||
|
SelectedSymbolId: current.SelectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function UnlinkSymbol(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
|
||||||
|
const it = MakeIterator(container);
|
||||||
|
for (const child of it) {
|
||||||
|
const symbol = symbols.get(child.properties.linkedSymbolId);
|
||||||
|
if (symbol === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
symbol.linkedContainers.delete(child.properties.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new container to a selected container
|
* Add a new container to a selected container
|
||||||
* @param type The type of container
|
* @param type The type of container
|
||||||
|
@ -114,21 +123,19 @@ export function DeleteContainer(
|
||||||
*/
|
*/
|
||||||
export function AddContainerToSelectedContainer(
|
export function AddContainerToSelectedContainer(
|
||||||
type: string,
|
type: string,
|
||||||
|
selected: IContainerModel | undefined,
|
||||||
configuration: IConfiguration,
|
configuration: IConfiguration,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
): void {
|
): void {
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
if (selected === null ||
|
||||||
const current = history[history.length - 1];
|
selected === undefined) {
|
||||||
|
|
||||||
if (current.SelectedContainer === null ||
|
|
||||||
current.SelectedContainer === undefined) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parent = current.SelectedContainer;
|
const parent = selected;
|
||||||
AddContainer(
|
AddContainer(
|
||||||
parent.children.length,
|
parent.children.length,
|
||||||
type,
|
type,
|
||||||
|
@ -166,11 +173,6 @@ export function AddContainer(
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
if (current.MainContainer === null ||
|
|
||||||
current.MainContainer === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the preset properties from the API
|
// Get the preset properties from the API
|
||||||
const containerConfig = configuration.AvailableContainers
|
const containerConfig = configuration.AvailableContainers
|
||||||
.find(option => option.Type === type);
|
.find(option => option.Type === type);
|
||||||
|
@ -220,7 +222,7 @@ export function AddContainer(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
ApplyBehaviors(newContainer);
|
ApplyBehaviors(newContainer, current.Symbols);
|
||||||
|
|
||||||
// And push it the the parent children
|
// And push it the the parent children
|
||||||
if (index === parentClone.children.length) {
|
if (index === parentClone.children.length) {
|
||||||
|
@ -235,23 +237,15 @@ export function AddContainer(
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
LastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
||||||
MainContainer: clone,
|
MainContainer: clone,
|
||||||
SelectedContainer: parentClone,
|
|
||||||
SelectedContainerId: parentClone.properties.id,
|
SelectedContainerId: parentClone.properties.id,
|
||||||
TypeCounters: newCounters
|
TypeCounters: newCounters,
|
||||||
|
Symbols: structuredClone(current.Symbols),
|
||||||
|
SelectedSymbolId: current.SelectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function UpdateCounters(counters: Record<string, number>, type: string): void {
|
|
||||||
if (counters[type] === null ||
|
|
||||||
counters[type] === undefined) {
|
|
||||||
counters[type] = 0;
|
|
||||||
} else {
|
|
||||||
counters[type]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function InitializeDefaultChild(
|
function InitializeDefaultChild(
|
||||||
configuration: IConfiguration,
|
configuration: IConfiguration,
|
||||||
containerConfig: IAvailableContainer,
|
containerConfig: IAvailableContainer,
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { OnPropertyChange, OnPropertiesSubmit } from './PropertiesOperations';
|
||||||
import EditorEvents from '../../Events/EditorEvents';
|
import EditorEvents from '../../Events/EditorEvents';
|
||||||
import { IEditorState } from '../../Interfaces/IEditorState';
|
import { IEditorState } from '../../Interfaces/IEditorState';
|
||||||
import { MAX_HISTORY } from '../../utils/default';
|
import { MAX_HISTORY } from '../../utils/default';
|
||||||
|
import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './SymbolOperations';
|
||||||
|
import { findContainerById } from '../../utils/itertools';
|
||||||
|
|
||||||
interface IEditorProps {
|
interface IEditorProps {
|
||||||
configuration: IConfiguration
|
configuration: IConfiguration
|
||||||
|
@ -18,6 +20,15 @@ interface IEditorProps {
|
||||||
historyCurrentStep: number
|
historyCurrentStep: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function UpdateCounters(counters: Record<string, number>, type: string): void {
|
||||||
|
if (counters[type] === null ||
|
||||||
|
counters[type] === undefined) {
|
||||||
|
counters[type] = 0;
|
||||||
|
} else {
|
||||||
|
counters[type]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const getCurrentHistory = (history: IHistoryState[], historyCurrentStep: number): IHistoryState[] =>
|
export const getCurrentHistory = (history: IHistoryState[], historyCurrentStep: number): IHistoryState[] =>
|
||||||
history.slice(
|
history.slice(
|
||||||
Math.max(0, history.length - MAX_HISTORY), // change this to 0 for unlimited (not recommanded because of overflow)
|
Math.max(0, history.length - MAX_HISTORY), // change this to 0 for unlimited (not recommanded because of overflow)
|
||||||
|
@ -70,13 +81,16 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
|
|
||||||
const configuration = props.configuration;
|
const configuration = props.configuration;
|
||||||
const current = getCurrentHistoryState(history, historyCurrentStep);
|
const current = getCurrentHistoryState(history, historyCurrentStep);
|
||||||
|
const selected = findContainerById(current.MainContainer, current.SelectedContainerId);
|
||||||
return (
|
return (
|
||||||
<div ref={editorRef} className="Editor font-sans h-full">
|
<div ref={editorRef} className="Editor font-sans h-full">
|
||||||
<UI
|
<UI
|
||||||
|
SelectedContainer={selected}
|
||||||
current={current}
|
current={current}
|
||||||
history={history}
|
history={history}
|
||||||
historyCurrentStep={historyCurrentStep}
|
historyCurrentStep={historyCurrentStep}
|
||||||
AvailableContainers={configuration.AvailableContainers}
|
AvailableContainers={configuration.AvailableContainers}
|
||||||
|
AvailableSymbols={configuration.AvailableSymbols}
|
||||||
SelectContainer={(container) => SelectContainer(
|
SelectContainer={(container) => SelectContainer(
|
||||||
container,
|
container,
|
||||||
history,
|
history,
|
||||||
|
@ -93,6 +107,7 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
)}
|
)}
|
||||||
OnPropertyChange={(key, value, isStyle) => OnPropertyChange(
|
OnPropertyChange={(key, value, isStyle) => OnPropertyChange(
|
||||||
key, value, isStyle,
|
key, value, isStyle,
|
||||||
|
selected,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
setHistory,
|
setHistory,
|
||||||
|
@ -100,6 +115,7 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
)}
|
)}
|
||||||
OnPropertiesSubmit={(event) => OnPropertiesSubmit(
|
OnPropertiesSubmit={(event) => OnPropertiesSubmit(
|
||||||
event,
|
event,
|
||||||
|
selected,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
setHistory,
|
setHistory,
|
||||||
|
@ -107,6 +123,7 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
)}
|
)}
|
||||||
AddContainerToSelectedContainer={(type) => AddContainerToSelectedContainer(
|
AddContainerToSelectedContainer={(type) => AddContainerToSelectedContainer(
|
||||||
type,
|
type,
|
||||||
|
selected,
|
||||||
configuration,
|
configuration,
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
|
@ -123,6 +140,35 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
setHistory,
|
setHistory,
|
||||||
setHistoryCurrentStep
|
setHistoryCurrentStep
|
||||||
)}
|
)}
|
||||||
|
AddSymbol={(type) => AddSymbol(
|
||||||
|
type,
|
||||||
|
configuration,
|
||||||
|
history,
|
||||||
|
historyCurrentStep,
|
||||||
|
setHistory,
|
||||||
|
setHistoryCurrentStep
|
||||||
|
)}
|
||||||
|
OnSymbolPropertyChange={(key, value) => OnSymbolPropertyChange(
|
||||||
|
key, value,
|
||||||
|
history,
|
||||||
|
historyCurrentStep,
|
||||||
|
setHistory,
|
||||||
|
setHistoryCurrentStep
|
||||||
|
)}
|
||||||
|
SelectSymbol={(symbolId) => SelectSymbol(
|
||||||
|
symbolId,
|
||||||
|
history,
|
||||||
|
historyCurrentStep,
|
||||||
|
setHistory,
|
||||||
|
setHistoryCurrentStep
|
||||||
|
)}
|
||||||
|
DeleteSymbol={(symbolId) => DeleteSymbol(
|
||||||
|
symbolId,
|
||||||
|
history,
|
||||||
|
historyCurrentStep,
|
||||||
|
setHistory,
|
||||||
|
setHistoryCurrentStep
|
||||||
|
)}
|
||||||
SaveEditorAsJSON={() => SaveEditorAsJSON(
|
SaveEditorAsJSON={() => SaveEditorAsJSON(
|
||||||
history,
|
history,
|
||||||
historyCurrentStep,
|
historyCurrentStep,
|
||||||
|
@ -134,7 +180,8 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
||||||
<SVG
|
<SVG
|
||||||
width={current.MainContainer?.properties.width}
|
width={current.MainContainer?.properties.width}
|
||||||
height={current.MainContainer?.properties.height}
|
height={current.MainContainer?.properties.height}
|
||||||
selected={current.SelectedContainer}
|
selected={selected}
|
||||||
|
symbols={current.Symbols}
|
||||||
>
|
>
|
||||||
{ current.MainContainer }
|
{ current.MainContainer }
|
||||||
</SVG>
|
</SVG>
|
||||||
|
|
|
@ -3,8 +3,9 @@ import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerMode
|
||||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
import { findContainerById } from '../../utils/itertools';
|
import { findContainerById } from '../../utils/itertools';
|
||||||
import { getCurrentHistory } from './Editor';
|
import { getCurrentHistory } from './Editor';
|
||||||
import { restoreX } from '../SVG/Elements/Container';
|
|
||||||
import { ApplyBehaviors } from './Behaviors/Behaviors';
|
import { ApplyBehaviors } from './Behaviors/Behaviors';
|
||||||
|
import { restoreX } from '../../utils/svg';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handled the property change event in the properties form
|
* Handled the property change event in the properties form
|
||||||
|
@ -16,6 +17,7 @@ export function OnPropertyChange(
|
||||||
key: string,
|
key: string,
|
||||||
value: string | number | boolean,
|
value: string | number | boolean,
|
||||||
isStyle: boolean = false,
|
isStyle: boolean = false,
|
||||||
|
selected: IContainerModel | undefined,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
@ -24,37 +26,66 @@ export function OnPropertyChange(
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
if (current.SelectedContainer === null ||
|
if (selected === null ||
|
||||||
current.SelectedContainer === undefined) {
|
selected === undefined) {
|
||||||
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
||||||
const container: ContainerModel | undefined = findContainerById(mainContainerClone, current.SelectedContainer.properties.id);
|
const container: ContainerModel | undefined = findContainerById(mainContainerClone, selected.properties.id);
|
||||||
|
|
||||||
if (container === null || container === undefined) {
|
if (container === null || container === undefined) {
|
||||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldSymbolId = container.properties.linkedSymbolId;
|
||||||
|
|
||||||
if (isStyle) {
|
if (isStyle) {
|
||||||
(container.properties.style as any)[key] = value;
|
(container.properties.style as any)[key] = value;
|
||||||
} else {
|
} else {
|
||||||
(container.properties as any)[key] = value;
|
(container.properties as any)[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyBehaviors(container);
|
LinkSymbol(
|
||||||
|
container.properties.id,
|
||||||
|
oldSymbolId,
|
||||||
|
container.properties.linkedSymbolId,
|
||||||
|
current.Symbols
|
||||||
|
);
|
||||||
|
|
||||||
|
ApplyBehaviors(container, current.Symbols);
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Change ${key} of ${container.properties.id}`,
|
LastAction: `Change ${key} of ${container.properties.id}`,
|
||||||
MainContainer: mainContainerClone,
|
MainContainer: mainContainerClone,
|
||||||
SelectedContainer: container,
|
|
||||||
SelectedContainerId: container.properties.id,
|
SelectedContainerId: container.properties.id,
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
TypeCounters: Object.assign({}, current.TypeCounters),
|
||||||
|
Symbols: structuredClone(current.Symbols),
|
||||||
|
SelectedSymbolId: current.SelectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LinkSymbol(
|
||||||
|
containerId: string,
|
||||||
|
oldSymbolId: string,
|
||||||
|
newSymbolId: string,
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
|
): void {
|
||||||
|
const oldSymbol = symbols.get(oldSymbolId);
|
||||||
|
const newSymbol = symbols.get(newSymbolId);
|
||||||
|
|
||||||
|
if (newSymbol === undefined) {
|
||||||
|
if (oldSymbol !== undefined) {
|
||||||
|
oldSymbol.linkedContainers.delete(containerId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSymbol.linkedContainers.add(containerId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handled the property change event in the properties form
|
* Handled the property change event in the properties form
|
||||||
* @param key Property name
|
* @param key Property name
|
||||||
|
@ -63,6 +94,7 @@ export function OnPropertyChange(
|
||||||
*/
|
*/
|
||||||
export function OnPropertiesSubmit(
|
export function OnPropertiesSubmit(
|
||||||
event: React.SyntheticEvent<HTMLFormElement>,
|
event: React.SyntheticEvent<HTMLFormElement>,
|
||||||
|
selected: IContainerModel | undefined,
|
||||||
fullHistory: IHistoryState[],
|
fullHistory: IHistoryState[],
|
||||||
historyCurrentStep: number,
|
historyCurrentStep: number,
|
||||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
@ -72,13 +104,13 @@ export function OnPropertiesSubmit(
|
||||||
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
const current = history[history.length - 1];
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
if (current.SelectedContainer === null ||
|
if (selected === null ||
|
||||||
current.SelectedContainer === undefined) {
|
selected === undefined) {
|
||||||
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
||||||
const container: ContainerModel | undefined = findContainerById(mainContainerClone, current.SelectedContainer.properties.id);
|
const container: ContainerModel | undefined = findContainerById(mainContainerClone, selected.properties.id);
|
||||||
|
|
||||||
if (container === null || container === undefined) {
|
if (container === null || container === undefined) {
|
||||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
||||||
|
@ -110,14 +142,15 @@ export function OnPropertiesSubmit(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the behaviors
|
// Apply the behaviors
|
||||||
ApplyBehaviors(container);
|
ApplyBehaviors(container, current.Symbols);
|
||||||
|
|
||||||
history.push({
|
history.push({
|
||||||
LastAction: `Change properties of ${container.properties.id}`,
|
LastAction: `Change properties of ${container.properties.id}`,
|
||||||
MainContainer: mainContainerClone,
|
MainContainer: mainContainerClone,
|
||||||
SelectedContainer: container,
|
|
||||||
SelectedContainerId: container.properties.id,
|
SelectedContainerId: container.properties.id,
|
||||||
TypeCounters: Object.assign({}, current.TypeCounters)
|
TypeCounters: Object.assign({}, current.TypeCounters),
|
||||||
|
Symbols: structuredClone(current.Symbols),
|
||||||
|
SelectedSymbolId: current.SelectedSymbolId
|
||||||
});
|
});
|
||||||
setHistory(history);
|
setHistory(history);
|
||||||
setHistoryCurrentStep(history.length - 1);
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
@ -187,3 +220,4 @@ const submitRadioButtons = (
|
||||||
|
|
||||||
(container.properties as any)[property] = radiobutton.value;
|
(container.properties as any)[property] = radiobutton.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
180
src/Components/Editor/SymbolOperations.ts
Normal file
180
src/Components/Editor/SymbolOperations.ts
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
|
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||||
|
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
|
import { DEFAULT_SYMBOL_HEIGHT, DEFAULT_SYMBOL_WIDTH } from '../../utils/default';
|
||||||
|
import { findContainerById } from '../../utils/itertools';
|
||||||
|
import { restoreX } from '../../utils/svg';
|
||||||
|
import { ApplyBehaviors } from './Behaviors/Behaviors';
|
||||||
|
import { getCurrentHistory, UpdateCounters } from './Editor';
|
||||||
|
|
||||||
|
export function AddSymbol(
|
||||||
|
name: string,
|
||||||
|
configuration: IConfiguration,
|
||||||
|
fullHistory: IHistoryState[],
|
||||||
|
historyCurrentStep: number,
|
||||||
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
|
): void {
|
||||||
|
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
const symbolConfig = configuration.AvailableSymbols
|
||||||
|
.find(option => option.Name === name);
|
||||||
|
|
||||||
|
if (symbolConfig === undefined) {
|
||||||
|
throw new Error('[AddSymbol] Symbol could not be found in the config');
|
||||||
|
}
|
||||||
|
const type = `symbol-${name}`;
|
||||||
|
const newCounters = structuredClone(current.TypeCounters);
|
||||||
|
UpdateCounters(newCounters, type);
|
||||||
|
|
||||||
|
const newSymbols = structuredClone(current.Symbols);
|
||||||
|
// TODO: Put this in default.ts as GetDefaultConfig
|
||||||
|
const newSymbol: ISymbolModel = {
|
||||||
|
id: `${name}-${newCounters[type]}`,
|
||||||
|
type: name,
|
||||||
|
config: structuredClone(symbolConfig),
|
||||||
|
x: 0,
|
||||||
|
width: symbolConfig.Width ?? DEFAULT_SYMBOL_WIDTH,
|
||||||
|
height: symbolConfig.Height ?? DEFAULT_SYMBOL_HEIGHT,
|
||||||
|
linkedContainers: new Set()
|
||||||
|
};
|
||||||
|
newSymbol.x = restoreX(newSymbol.x, newSymbol.width, newSymbol.config.XPositionReference);
|
||||||
|
|
||||||
|
newSymbols.set(newSymbol.id, newSymbol);
|
||||||
|
|
||||||
|
history.push({
|
||||||
|
LastAction: `Add ${name}`,
|
||||||
|
MainContainer: structuredClone(current.MainContainer),
|
||||||
|
SelectedContainerId: current.SelectedContainerId,
|
||||||
|
TypeCounters: newCounters,
|
||||||
|
Symbols: newSymbols,
|
||||||
|
SelectedSymbolId: newSymbol.id
|
||||||
|
});
|
||||||
|
setHistory(history);
|
||||||
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SelectSymbol(
|
||||||
|
symbolId: string,
|
||||||
|
fullHistory: IHistoryState[],
|
||||||
|
historyCurrentStep: number,
|
||||||
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
|
): void {
|
||||||
|
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
history.push({
|
||||||
|
LastAction: `Select ${symbolId}`,
|
||||||
|
MainContainer: structuredClone(current.MainContainer),
|
||||||
|
SelectedContainerId: current.SelectedContainerId,
|
||||||
|
TypeCounters: structuredClone(current.TypeCounters),
|
||||||
|
Symbols: structuredClone(current.Symbols),
|
||||||
|
SelectedSymbolId: symbolId
|
||||||
|
});
|
||||||
|
setHistory(history);
|
||||||
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeleteSymbol(
|
||||||
|
symbolId: string,
|
||||||
|
fullHistory: IHistoryState[],
|
||||||
|
historyCurrentStep: number,
|
||||||
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
|
): void {
|
||||||
|
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
const newSymbols = structuredClone(current.Symbols);
|
||||||
|
const symbol = newSymbols.get(symbolId);
|
||||||
|
|
||||||
|
if (symbol === undefined) {
|
||||||
|
throw new Error(`[DeleteSymbol] Could not find symbol in the current state!: ${symbolId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMainContainer = structuredClone(current.MainContainer);
|
||||||
|
|
||||||
|
UnlinkContainers(symbol, newMainContainer);
|
||||||
|
|
||||||
|
newSymbols.delete(symbolId);
|
||||||
|
|
||||||
|
history.push({
|
||||||
|
LastAction: `Select ${symbolId}`,
|
||||||
|
MainContainer: newMainContainer,
|
||||||
|
SelectedContainerId: current.SelectedContainerId,
|
||||||
|
TypeCounters: structuredClone(current.TypeCounters),
|
||||||
|
Symbols: newSymbols,
|
||||||
|
SelectedSymbolId: symbolId
|
||||||
|
});
|
||||||
|
setHistory(history);
|
||||||
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UnlinkContainers(symbol: ISymbolModel, newMainContainer: IContainerModel) {
|
||||||
|
symbol.linkedContainers.forEach((containerId) => {
|
||||||
|
const container = findContainerById(newMainContainer, containerId);
|
||||||
|
|
||||||
|
if (container === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.properties.linkedSymbolId = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handled the property change event in the properties form
|
||||||
|
* @param key Property name
|
||||||
|
* @param value New value of the property
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function OnPropertyChange(
|
||||||
|
key: string,
|
||||||
|
value: string | number | boolean,
|
||||||
|
fullHistory: IHistoryState[],
|
||||||
|
historyCurrentStep: number,
|
||||||
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||||
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||||
|
): void {
|
||||||
|
const history = getCurrentHistory(fullHistory, historyCurrentStep);
|
||||||
|
const current = history[history.length - 1];
|
||||||
|
|
||||||
|
if (current.SelectedSymbolId === '') {
|
||||||
|
throw new Error('[OnSymbolPropertyChange] Property was changed before selecting a symbol');
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSymbols: Map<string, ISymbolModel> = structuredClone(current.Symbols);
|
||||||
|
const symbol = newSymbols.get(current.SelectedSymbolId);
|
||||||
|
|
||||||
|
if (symbol === null || symbol === undefined) {
|
||||||
|
throw new Error('[OnSymbolPropertyChange] Symbol model was not found in state!');
|
||||||
|
}
|
||||||
|
|
||||||
|
(symbol as any)[key] = value;
|
||||||
|
|
||||||
|
const newMainContainer = structuredClone(current.MainContainer);
|
||||||
|
symbol.linkedContainers.forEach((containerId) => {
|
||||||
|
const container = findContainerById(newMainContainer, containerId);
|
||||||
|
|
||||||
|
if (container === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyBehaviors(container, newSymbols);
|
||||||
|
});
|
||||||
|
|
||||||
|
history.push({
|
||||||
|
LastAction: `Change ${key} of ${symbol.id}`,
|
||||||
|
MainContainer: newMainContainer,
|
||||||
|
SelectedContainerId: current.SelectedContainerId,
|
||||||
|
TypeCounters: Object.assign({}, current.TypeCounters),
|
||||||
|
Symbols: newSymbols,
|
||||||
|
SelectedSymbolId: symbol.id
|
||||||
|
});
|
||||||
|
setHistory(history);
|
||||||
|
setHistoryCurrentStep(history.length - 1);
|
||||||
|
}
|
|
@ -4,16 +4,19 @@ import { fireEvent, render, screen } from '../../utils/test-utils';
|
||||||
import { ElementsSidebar } from './ElementsSidebar';
|
import { ElementsSidebar } from './ElementsSidebar';
|
||||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||||
|
import { findContainerById } from '../../utils/itertools';
|
||||||
|
|
||||||
describe.concurrent('Elements sidebar', () => {
|
describe.concurrent('Elements sidebar', () => {
|
||||||
it('With a MainContainer', () => {
|
it('With a MainContainer', () => {
|
||||||
render(<ElementsSidebar
|
render(<ElementsSidebar
|
||||||
|
symbols={new Map()}
|
||||||
MainContainer={{
|
MainContainer={{
|
||||||
children: [],
|
children: [],
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
linkedSymbolId: '',
|
||||||
displayedText: 'main',
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -28,7 +31,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
}}
|
}}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
SelectedContainer={null}
|
SelectedContainer={undefined}
|
||||||
OnPropertyChange={() => {}}
|
OnPropertyChange={() => {}}
|
||||||
OnPropertiesSubmit={() => {}}
|
OnPropertiesSubmit={() => {}}
|
||||||
SelectContainer={() => {}}
|
SelectContainer={() => {}}
|
||||||
|
@ -42,12 +45,13 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('With a selected MainContainer', () => {
|
it('With a selected MainContainer', () => {
|
||||||
const MainContainer = {
|
const MainContainer: IContainerModel = {
|
||||||
children: [],
|
children: [],
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
parentId: '',
|
parentId: '',
|
||||||
|
linkedSymbolId: '',
|
||||||
displayedText: 'main',
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -62,6 +66,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const { container } = render(<ElementsSidebar
|
const { container } = render(<ElementsSidebar
|
||||||
|
symbols={new Map()}
|
||||||
MainContainer={MainContainer}
|
MainContainer={MainContainer}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
|
@ -102,12 +107,13 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
|
|
||||||
it('With multiple containers', () => {
|
it('With multiple containers', () => {
|
||||||
const children: IContainerModel[] = [];
|
const children: IContainerModel[] = [];
|
||||||
const MainContainer = {
|
const MainContainer: IContainerModel = {
|
||||||
children,
|
children,
|
||||||
parent: null,
|
parent: null,
|
||||||
properties: {
|
properties: {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
parentId: '',
|
parentId: '',
|
||||||
|
linkedSymbolId: '',
|
||||||
displayedText: 'main',
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -128,6 +134,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'child-1',
|
id: 'child-1',
|
||||||
parentId: 'main',
|
parentId: 'main',
|
||||||
|
linkedSymbolId: '',
|
||||||
displayedText: 'child-1',
|
displayedText: 'child-1',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -149,6 +156,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'child-2',
|
id: 'child-2',
|
||||||
parentId: 'main',
|
parentId: 'main',
|
||||||
|
linkedSymbolId: '',
|
||||||
displayedText: 'child-2',
|
displayedText: 'child-2',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -164,6 +172,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
render(<ElementsSidebar
|
render(<ElementsSidebar
|
||||||
|
symbols={new Map()}
|
||||||
MainContainer={MainContainer}
|
MainContainer={MainContainer}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
|
@ -190,6 +199,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
parentId: '',
|
parentId: '',
|
||||||
|
linkedSymbolId: '',
|
||||||
displayedText: 'main',
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -209,6 +219,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
properties: {
|
properties: {
|
||||||
id: 'child-1',
|
id: 'child-1',
|
||||||
parentId: 'main',
|
parentId: 'main',
|
||||||
|
linkedSymbolId: '',
|
||||||
displayedText: 'child-1',
|
displayedText: 'child-1',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -223,12 +234,13 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
};
|
};
|
||||||
children.push(child1Model);
|
children.push(child1Model);
|
||||||
|
|
||||||
let SelectedContainer = MainContainer;
|
let SelectedContainer: IContainerModel | undefined = MainContainer;
|
||||||
const selectContainer = vi.fn((container: IContainerModel) => {
|
const selectContainer = vi.fn((containerId: string) => {
|
||||||
SelectedContainer = container;
|
SelectedContainer = findContainerById(MainContainer, containerId);
|
||||||
});
|
});
|
||||||
|
|
||||||
const { container, rerender } = render(<ElementsSidebar
|
const { container, rerender } = render(<ElementsSidebar
|
||||||
|
symbols={new Map()}
|
||||||
MainContainer={MainContainer}
|
MainContainer={MainContainer}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
|
@ -253,6 +265,7 @@ describe.concurrent('Elements sidebar', () => {
|
||||||
fireEvent.click(child1);
|
fireEvent.click(child1);
|
||||||
|
|
||||||
rerender(<ElementsSidebar
|
rerender(<ElementsSidebar
|
||||||
|
symbols={new Map()}
|
||||||
MainContainer={MainContainer}
|
MainContainer={MainContainer}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
isHistoryOpen={false}
|
isHistoryOpen={false}
|
||||||
|
|
|
@ -7,15 +7,17 @@ import { Menu } from '../Menu/Menu';
|
||||||
import { MenuItem } from '../Menu/MenuItem';
|
import { MenuItem } from '../Menu/MenuItem';
|
||||||
import { handleDragLeave, handleDragOver, handleLeftClick, handleOnDrop, handleRightClick } from './MouseEventHandlers';
|
import { handleDragLeave, handleDragOver, handleLeftClick, handleOnDrop, handleRightClick } from './MouseEventHandlers';
|
||||||
import { IPoint } from '../../Interfaces/IPoint';
|
import { IPoint } from '../../Interfaces/IPoint';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
|
|
||||||
interface IElementsSidebarProps {
|
interface IElementsSidebarProps {
|
||||||
MainContainer: IContainerModel
|
MainContainer: IContainerModel
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
isHistoryOpen: boolean
|
isHistoryOpen: boolean
|
||||||
SelectedContainer: IContainerModel | null
|
SelectedContainer: IContainerModel | undefined
|
||||||
OnPropertyChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
OnPropertyChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
||||||
OnPropertiesSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
OnPropertiesSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
||||||
SelectContainer: (container: IContainerModel) => void
|
SelectContainer: (containerId: string) => void
|
||||||
DeleteContainer: (containerid: string) => void
|
DeleteContainer: (containerid: string) => void
|
||||||
AddContainer: (index: number, type: string, parent: string) => void
|
AddContainer: (index: number, type: string, parent: string) => void
|
||||||
}
|
}
|
||||||
|
@ -104,7 +106,7 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
||||||
onDrop={(event) => handleOnDrop(event, props.MainContainer, props.AddContainer)}
|
onDrop={(event) => handleOnDrop(event, props.MainContainer, props.AddContainer)}
|
||||||
onDragOver={(event) => handleDragOver(event, props.MainContainer)}
|
onDragOver={(event) => handleDragOver(event, props.MainContainer)}
|
||||||
onDragLeave={(event) => handleDragLeave(event)}
|
onDragLeave={(event) => handleDragLeave(event)}
|
||||||
onClick={() => props.SelectContainer(container)}
|
onClick={() => props.SelectContainer(container.properties.id)}
|
||||||
>
|
>
|
||||||
{ text }
|
{ text }
|
||||||
</button>
|
</button>
|
||||||
|
@ -140,6 +142,7 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (props: IElement
|
||||||
</Menu>
|
</Menu>
|
||||||
<Properties
|
<Properties
|
||||||
properties={props.SelectedContainer?.properties}
|
properties={props.SelectedContainer?.properties}
|
||||||
|
symbols={props.symbols}
|
||||||
onChange={props.OnPropertyChange}
|
onChange={props.OnPropertyChange}
|
||||||
onSubmit={props.OnPropertiesSubmit}
|
onSubmit={props.OnPropertiesSubmit}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
import { MenuAlt2Icon, MenuAlt3Icon, MenuIcon } from '@heroicons/react/outline';
|
import { MenuAlt2Icon, MenuAlt3Icon, MenuIcon } from '@heroicons/react/outline';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||||
import IProperties from '../../Interfaces/IProperties';
|
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
|
import { restoreX, transformX } from '../../utils/svg';
|
||||||
import { InputGroup } from '../InputGroup/InputGroup';
|
import { InputGroup } from '../InputGroup/InputGroup';
|
||||||
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
|
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
|
||||||
import { restoreX, transformX } from '../SVG/Elements/Container';
|
import { Select } from '../Select/Select';
|
||||||
|
|
||||||
interface IDynamicFormProps {
|
interface IDynamicFormProps {
|
||||||
properties: IProperties
|
properties: IContainerProperties
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCSSInputs = (
|
const getCSSInputs = (
|
||||||
properties: IProperties,
|
properties: IContainerProperties,
|
||||||
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
||||||
): JSX.Element[] => {
|
): JSX.Element[] => {
|
||||||
const groupInput: JSX.Element[] = [];
|
const groupInput: JSX.Element[] = [];
|
||||||
|
@ -67,6 +70,7 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
|
||||||
labelClassName=''
|
labelClassName=''
|
||||||
inputClassName=''
|
inputClassName=''
|
||||||
type='number'
|
type='number'
|
||||||
|
isDisabled={props.properties.linkedSymbolId !== ''}
|
||||||
value={transformX(props.properties.x, props.properties.width, props.properties.XPositionReference).toString()}
|
value={transformX(props.properties.x, props.properties.width, props.properties.XPositionReference).toString()}
|
||||||
onChange={(event) => props.onChange('x', restoreX(Number(event.target.value), props.properties.width, props.properties.XPositionReference))}
|
onChange={(event) => props.onChange('x', restoreX(Number(event.target.value), props.properties.width, props.properties.XPositionReference))}
|
||||||
/>
|
/>
|
||||||
|
@ -160,9 +164,22 @@ const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
|
||||||
]}
|
]}
|
||||||
onChange={(event) => props.onChange('XPositionReference', Number(event.target.value))}
|
onChange={(event) => props.onChange('XPositionReference', Number(event.target.value))}
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
inputKey='linkedSymbolId'
|
||||||
|
labelText='Align with symbol'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
inputs={[...props.symbols.values()].map(symbol => ({
|
||||||
|
text: symbol.id,
|
||||||
|
value: symbol.id
|
||||||
|
}))}
|
||||||
|
value={props.properties.linkedSymbolId ?? ''}
|
||||||
|
onChange={(event) => props.onChange('linkedSymbolId', event.target.value)}
|
||||||
|
/>
|
||||||
{ getCSSInputs(props.properties, props.onChange) }
|
{ getCSSInputs(props.properties, props.onChange) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default DynamicForm;
|
export default DynamicForm;
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import IProperties from '../../Interfaces/IProperties';
|
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import DynamicForm from './DynamicForm';
|
import DynamicForm from './DynamicForm';
|
||||||
import StaticForm from './StaticForm';
|
import StaticForm from './StaticForm';
|
||||||
|
|
||||||
interface IFormProps {
|
interface IFormProps {
|
||||||
properties: IProperties
|
properties: IContainerProperties
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
isDynamicInput: boolean
|
isDynamicInput: boolean
|
||||||
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
||||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
||||||
|
@ -14,6 +16,7 @@ export const Form: React.FunctionComponent<IFormProps> = (props) => {
|
||||||
if (props.isDynamicInput) {
|
if (props.isDynamicInput) {
|
||||||
return <DynamicForm
|
return <DynamicForm
|
||||||
properties={props.properties}
|
properties={props.properties}
|
||||||
|
symbols={props.symbols}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { expect, describe, it, vi } from 'vitest';
|
import { expect, describe, it, vi } from 'vitest';
|
||||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||||
import IProperties from '../../Interfaces/IProperties';
|
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
||||||
import { Properties } from './Properties';
|
import { Properties } from './Properties';
|
||||||
|
|
||||||
describe.concurrent('Properties', () => {
|
describe.concurrent('Properties', () => {
|
||||||
|
@ -11,6 +11,7 @@ describe.concurrent('Properties', () => {
|
||||||
properties={undefined}
|
properties={undefined}
|
||||||
onChange={() => {}}
|
onChange={() => {}}
|
||||||
onSubmit={() => {}}
|
onSubmit={() => {}}
|
||||||
|
symbols={new Map()}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.queryByText('id')).toBeNull();
|
expect(screen.queryByText('id')).toBeNull();
|
||||||
|
@ -20,9 +21,10 @@ describe.concurrent('Properties', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Some properties, change values with dynamic input', () => {
|
it('Some properties, change values with dynamic input', () => {
|
||||||
const prop: IProperties = {
|
const prop: IContainerProperties = {
|
||||||
id: 'stuff',
|
id: 'stuff',
|
||||||
parentId: 'parentId',
|
parentId: 'parentId',
|
||||||
|
linkedSymbolId: '',
|
||||||
displayedText: 'stuff',
|
displayedText: 'stuff',
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1,
|
y: 1,
|
||||||
|
@ -42,6 +44,7 @@ describe.concurrent('Properties', () => {
|
||||||
properties={prop}
|
properties={prop}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onSubmit={() => {}}
|
onSubmit={() => {}}
|
||||||
|
symbols={new Map()}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
expect(screen.queryByText('id')).toBeDefined();
|
expect(screen.queryByText('id')).toBeDefined();
|
||||||
|
@ -76,6 +79,7 @@ describe.concurrent('Properties', () => {
|
||||||
properties={Object.assign({}, prop)}
|
properties={Object.assign({}, prop)}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onSubmit={() => {}}
|
onSubmit={() => {}}
|
||||||
|
symbols={new Map()}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
propertyId = container.querySelector('#id');
|
propertyId = container.querySelector('#id');
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import IProperties from '../../Interfaces/IProperties';
|
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
import { ToggleButton } from '../ToggleButton/ToggleButton';
|
import { ToggleButton } from '../ToggleButton/ToggleButton';
|
||||||
import { Form } from './Form';
|
import { Form } from './Form';
|
||||||
|
|
||||||
interface IPropertiesProps {
|
interface IPropertiesProps {
|
||||||
properties?: IProperties
|
properties?: IContainerProperties
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
||||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
||||||
}
|
}
|
||||||
|
@ -27,6 +29,7 @@ export const Properties: React.FC<IPropertiesProps> = (props: IPropertiesProps)
|
||||||
/>
|
/>
|
||||||
<Form
|
<Form
|
||||||
properties={props.properties}
|
properties={props.properties}
|
||||||
|
symbols={props.symbols}
|
||||||
isDynamicInput={isDynamicInput}
|
isDynamicInput={isDynamicInput}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
onSubmit={props.onSubmit}
|
onSubmit={props.onSubmit}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { MenuAlt2Icon, MenuIcon, MenuAlt3Icon } from '@heroicons/react/outline';
|
import { MenuAlt2Icon, MenuIcon, MenuAlt3Icon } from '@heroicons/react/outline';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { XPositionReference } from '../../Enums/XPositionReference';
|
import { XPositionReference } from '../../Enums/XPositionReference';
|
||||||
import IProperties from '../../Interfaces/IProperties';
|
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
||||||
|
import { transformX } from '../../utils/svg';
|
||||||
import { InputGroup } from '../InputGroup/InputGroup';
|
import { InputGroup } from '../InputGroup/InputGroup';
|
||||||
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
|
import { RadioGroupButtons } from '../RadioGroupButtons/RadioGroupButtons';
|
||||||
import { transformX } from '../SVG/Elements/Container';
|
|
||||||
|
|
||||||
interface IStaticFormProps {
|
interface IStaticFormProps {
|
||||||
properties: IProperties
|
properties: IContainerProperties
|
||||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCSSInputs = (properties: IProperties): JSX.Element[] => {
|
const getCSSInputs = (properties: IContainerProperties): JSX.Element[] => {
|
||||||
const groupInput: JSX.Element[] = [];
|
const groupInput: JSX.Element[] = [];
|
||||||
for (const key in properties.style) {
|
for (const key in properties.style) {
|
||||||
groupInput.push(<InputGroup
|
groupInput.push(<InputGroup
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Interweave, Node } from 'interweave';
|
import { Interweave, Node } from 'interweave';
|
||||||
import { XPositionReference } from '../../../Enums/XPositionReference';
|
|
||||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { DIMENSION_MARGIN, SHOW_CHILDREN_DIMENSIONS, SHOW_PARENT_DIMENSION, SHOW_TEXT } from '../../../utils/default';
|
import { DIMENSION_MARGIN, SHOW_CHILDREN_DIMENSIONS, SHOW_PARENT_DIMENSION, SHOW_TEXT } from '../../../utils/default';
|
||||||
import { getDepth } from '../../../utils/itertools';
|
import { getDepth } from '../../../utils/itertools';
|
||||||
import { Dimension } from './Dimension';
|
import { Dimension } from './Dimension';
|
||||||
import IProperties from '../../../Interfaces/IProperties';
|
import IContainerProperties from '../../../Interfaces/IContainerProperties';
|
||||||
|
import { transformX } from '../../../utils/svg';
|
||||||
|
import { camelize } from '../../../utils/stringtools';
|
||||||
|
|
||||||
interface IContainerProps {
|
interface IContainerProps {
|
||||||
model: IContainerModel
|
model: IContainerModel
|
||||||
|
@ -45,7 +46,7 @@ export const Container: React.FC<IContainerProps> = (props: IContainerProps) =>
|
||||||
</rect>);
|
</rect>);
|
||||||
// Dimension props
|
// Dimension props
|
||||||
const depth = getDepth(props.model);
|
const depth = getDepth(props.model);
|
||||||
const dimensionMargin = DIMENSION_MARGIN * (depth + 1);
|
const dimensionMargin = DIMENSION_MARGIN * depth;
|
||||||
const id = `dim-${props.model.properties.id}`;
|
const id = `dim-${props.model.properties.id}`;
|
||||||
const xStart: number = 0;
|
const xStart: number = 0;
|
||||||
const xEnd = props.model.properties.width;
|
const xEnd = props.model.properties.width;
|
||||||
|
@ -132,27 +133,7 @@ function GetChildrenDimensionProps(props: IContainerProps, dimensionMargin: numb
|
||||||
return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren };
|
return { childrenId, xChildrenStart, xChildrenEnd, yChildren, textChildren };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
|
function CreateReactCustomSVG(customSVG: string, props: IContainerProperties): React.ReactNode {
|
||||||
let transformedX = x;
|
|
||||||
if (xPositionReference === XPositionReference.Center) {
|
|
||||||
transformedX += width / 2;
|
|
||||||
} else if (xPositionReference === XPositionReference.Right) {
|
|
||||||
transformedX += width;
|
|
||||||
}
|
|
||||||
return transformedX;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function restoreX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
|
|
||||||
let transformedX = x;
|
|
||||||
if (xPositionReference === XPositionReference.Center) {
|
|
||||||
transformedX -= width / 2;
|
|
||||||
} else if (xPositionReference === XPositionReference.Right) {
|
|
||||||
transformedX -= width;
|
|
||||||
}
|
|
||||||
return transformedX;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CreateReactCustomSVG(customSVG: string, props: IProperties): React.ReactNode {
|
|
||||||
return <Interweave
|
return <Interweave
|
||||||
tagName='g'
|
tagName='g'
|
||||||
disableLineBreaks={true}
|
disableLineBreaks={true}
|
||||||
|
@ -162,7 +143,7 @@ function CreateReactCustomSVG(customSVG: string, props: IProperties): React.Reac
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function transform(node: HTMLElement, children: Node[], props: IProperties): React.ReactNode {
|
function transform(node: HTMLElement, children: Node[], props: IContainerProperties): React.ReactNode {
|
||||||
const supportedTags = ['line', 'path', 'rect'];
|
const supportedTags = ['line', 'path', 'rect'];
|
||||||
if (supportedTags.includes(node.tagName.toLowerCase())) {
|
if (supportedTags.includes(node.tagName.toLowerCase())) {
|
||||||
const attributes: {[att: string]: string | object | null} = {};
|
const attributes: {[att: string]: string | object | null} = {};
|
||||||
|
@ -207,7 +188,3 @@ function transform(node: HTMLElement, children: Node[], props: IProperties): Rea
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function camelize(str: string): any {
|
|
||||||
return str.split('-').map((word, index) => index > 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word).join('');
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
||||||
import { ContainerModel } from '../../../Interfaces/IContainerModel';
|
import { ContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { DIMENSION_MARGIN } from '../../../utils/default';
|
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||||
import { getAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
import { getAbsolutePosition, MakeBFSIterator } from '../../../utils/itertools';
|
||||||
import { transformX } from './Container';
|
import { transformX } from '../../../utils/svg';
|
||||||
import { Dimension } from './Dimension';
|
import { Dimension } from './Dimension';
|
||||||
|
|
||||||
interface IDimensionLayerProps {
|
interface IDimensionLayerProps {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||||
import { getAbsolutePosition } from '../../../utils/itertools';
|
import { getAbsolutePosition } from '../../../utils/itertools';
|
||||||
|
|
||||||
interface ISelectorProps {
|
interface ISelectorProps {
|
||||||
selected: IContainerModel | null
|
selected?: IContainerModel
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Selector: React.FC<ISelectorProps> = (props) => {
|
export const Selector: React.FC<ISelectorProps> = (props) => {
|
||||||
|
|
39
src/Components/SVG/Elements/Symbol.tsx
Normal file
39
src/Components/SVG/Elements/Symbol.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { Interweave } from 'interweave';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
|
import { DIMENSION_MARGIN } from '../../../utils/default';
|
||||||
|
|
||||||
|
interface ISymbolProps {
|
||||||
|
model: ISymbolModel
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Symbol: React.FC<ISymbolProps> = (props) => {
|
||||||
|
const href = props.model.config.Image.Base64Image ?? props.model.config.Image.Url;
|
||||||
|
const hasSVG = props.model.config.Image.Svg !== undefined &&
|
||||||
|
props.model.config.Image.Svg !== null;
|
||||||
|
if (hasSVG) {
|
||||||
|
return (
|
||||||
|
<g
|
||||||
|
x={props.model.x}
|
||||||
|
y={-DIMENSION_MARGIN}
|
||||||
|
>
|
||||||
|
<Interweave
|
||||||
|
noWrap={true}
|
||||||
|
disableLineBreaks={true}
|
||||||
|
content={props.model.config.Image.Svg}
|
||||||
|
allowElements={true}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<image
|
||||||
|
href={href}
|
||||||
|
x={props.model.x}
|
||||||
|
y={-DIMENSION_MARGIN}
|
||||||
|
height={props.model.height}
|
||||||
|
width={props.model.width}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
23
src/Components/SVG/Elements/SymbolLayer.tsx
Normal file
23
src/Components/SVG/Elements/SymbolLayer.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||||
|
import { Symbol } from './Symbol';
|
||||||
|
|
||||||
|
interface ISymbolLayerProps {
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SymbolLayer: React.FC<ISymbolLayerProps> = (props) => {
|
||||||
|
const symbols: JSX.Element[] = [];
|
||||||
|
props.symbols.forEach((symbol) => {
|
||||||
|
symbols.push(
|
||||||
|
<Symbol key={`symbol-${symbol.id}`} model={symbol} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
{
|
||||||
|
symbols
|
||||||
|
}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
|
@ -4,15 +4,17 @@ import { Container } from './Elements/Container';
|
||||||
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { Selector } from './Elements/Selector';
|
import { Selector } from './Elements/Selector';
|
||||||
import { BAR_WIDTH } from '../Bar/Bar';
|
import { BAR_WIDTH } from '../Bar/Bar';
|
||||||
import { DimensionLayer } from './Elements/DimensionLayer';
|
|
||||||
import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
|
import { DepthDimensionLayer } from './Elements/DepthDimensionLayer';
|
||||||
import { SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
|
import { SHOW_DIMENSIONS_PER_DEPTH } from '../../utils/default';
|
||||||
|
import { SymbolLayer } from './Elements/SymbolLayer';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
|
|
||||||
interface ISVGProps {
|
interface ISVGProps {
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
children: ContainerModel | ContainerModel[] | null
|
children: ContainerModel | ContainerModel[] | null
|
||||||
selected: ContainerModel | null
|
selected?: ContainerModel
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Viewer {
|
interface Viewer {
|
||||||
|
@ -81,6 +83,7 @@ export const SVG: React.FC<ISVGProps> = (props: ISVGProps) => {
|
||||||
? <DepthDimensionLayer roots={props.children}/>
|
? <DepthDimensionLayer roots={props.children}/>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
<SymbolLayer symbols={props.symbols} />
|
||||||
<Selector selected={props.selected} /> {/* leave this at the end so it can be removed during the svg export */}
|
<Selector selected={props.selected} /> {/* leave this at the end so it can be removed during the svg export */}
|
||||||
</svg>
|
</svg>
|
||||||
</UncontrolledReactSVGPanZoom>
|
</UncontrolledReactSVGPanZoom>
|
||||||
|
|
55
src/Components/Select/Select.tsx
Normal file
55
src/Components/Select/Select.tsx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { IInputGroup } from '../../Interfaces/IInputGroup';
|
||||||
|
|
||||||
|
interface ISelectProps {
|
||||||
|
labelKey?: string
|
||||||
|
labelText: string
|
||||||
|
inputKey: string
|
||||||
|
labelClassName: string
|
||||||
|
inputClassName: string
|
||||||
|
inputs: IInputGroup[]
|
||||||
|
value?: string
|
||||||
|
onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const className = `
|
||||||
|
w-full
|
||||||
|
text-xs font-medium transition-all text-gray-800 mt-1 px-3 py-2
|
||||||
|
bg-white border-2 border-white rounded-lg placeholder-gray-800
|
||||||
|
focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500
|
||||||
|
disabled:bg-slate-300 disabled:text-gray-500 disabled:border-slate-300 disabled:shadow-none`;
|
||||||
|
|
||||||
|
export const Select: React.FC<ISelectProps> = (props) => {
|
||||||
|
const options = [(
|
||||||
|
<option key='symbol-none' value=''>None</option>
|
||||||
|
)];
|
||||||
|
|
||||||
|
props.inputs.forEach(input => {
|
||||||
|
options.push(<option
|
||||||
|
key={input.value}
|
||||||
|
value={input.value}
|
||||||
|
>
|
||||||
|
{input.text}
|
||||||
|
</option>);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<label
|
||||||
|
key={props.labelKey}
|
||||||
|
className={`mt-4 text-xs font-medium text-gray-800 ${props.labelClassName}`}
|
||||||
|
htmlFor={props.inputKey}
|
||||||
|
>
|
||||||
|
{ props.labelText }
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id={props.inputKey}
|
||||||
|
value={props.value}
|
||||||
|
onChange={props.onChange}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{ options }
|
||||||
|
</select>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
56
src/Components/SymbolProperties/DynamicForm.tsx
Normal file
56
src/Components/SymbolProperties/DynamicForm.tsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
|
import { restoreX, transformX } from '../../utils/svg';
|
||||||
|
import { InputGroup } from '../InputGroup/InputGroup';
|
||||||
|
|
||||||
|
interface IDynamicFormProps {
|
||||||
|
symbol: ISymbolModel
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
|
onChange: (key: string, value: string | number | boolean) => void
|
||||||
|
}
|
||||||
|
const DynamicForm: React.FunctionComponent<IDynamicFormProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<div className='grid grid-cols-2 gap-y-4'>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Name'
|
||||||
|
inputKey='id'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='string'
|
||||||
|
value={props.symbol.id.toString()}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='x'
|
||||||
|
inputKey='x'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='number'
|
||||||
|
value={transformX(props.symbol.x, props.symbol.width, props.symbol.config.XPositionReference).toString()}
|
||||||
|
onChange={(event) => props.onChange('x', restoreX(Number(event.target.value), props.symbol.width, props.symbol.config.XPositionReference))}
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Height'
|
||||||
|
inputKey='height'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='number'
|
||||||
|
min={0}
|
||||||
|
value={props.symbol.height.toString()}
|
||||||
|
onChange={(event) => props.onChange('height', Number(event.target.value))}
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
labelText='Width'
|
||||||
|
inputKey='width'
|
||||||
|
labelClassName=''
|
||||||
|
inputClassName=''
|
||||||
|
type='number'
|
||||||
|
min={0}
|
||||||
|
value={props.symbol.width.toString()}
|
||||||
|
onChange={(event) => props.onChange('width', Number(event.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DynamicForm;
|
17
src/Components/SymbolProperties/Form.tsx
Normal file
17
src/Components/SymbolProperties/Form.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
|
import DynamicForm from './DynamicForm';
|
||||||
|
|
||||||
|
interface IFormProps {
|
||||||
|
symbol: ISymbolModel
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
|
onChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Form: React.FunctionComponent<IFormProps> = (props) => {
|
||||||
|
return <DynamicForm
|
||||||
|
symbol={props.symbol}
|
||||||
|
symbols={props.symbols}
|
||||||
|
onChange={props.onChange}
|
||||||
|
/>;
|
||||||
|
};
|
27
src/Components/SymbolProperties/SymbolProperties.tsx
Normal file
27
src/Components/SymbolProperties/SymbolProperties.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import IContainerProperties from '../../Interfaces/IContainerProperties';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
|
import { ToggleButton } from '../ToggleButton/ToggleButton';
|
||||||
|
import { Form } from './Form';
|
||||||
|
|
||||||
|
interface ISymbolPropertiesProps {
|
||||||
|
symbol?: ISymbolModel
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
|
onChange: (key: string, value: string | number | boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SymbolProperties: React.FC<ISymbolPropertiesProps> = (props: ISymbolPropertiesProps) => {
|
||||||
|
if (props.symbol === undefined) {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='h-3/5 p-3 bg-slate-200 overflow-y-auto'>
|
||||||
|
<Form
|
||||||
|
symbol={props.symbol}
|
||||||
|
symbols={props.symbols}
|
||||||
|
onChange={props.onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
68
src/Components/Symbols/Symbols.tsx
Normal file
68
src/Components/Symbols/Symbols.tsx
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
|
||||||
|
import { truncateString } from '../../utils/stringtools';
|
||||||
|
|
||||||
|
interface ISymbolsProps {
|
||||||
|
componentOptions: IAvailableSymbol[]
|
||||||
|
isOpen: boolean
|
||||||
|
buttonOnClick: (type: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragStart(event: React.DragEvent<HTMLButtonElement>): void {
|
||||||
|
event.dataTransfer.setData('type', (event.target as HTMLButtonElement).id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Symbols: React.FC<ISymbolsProps> = (props: ISymbolsProps) => {
|
||||||
|
const listElements = props.componentOptions.map(componentOption => {
|
||||||
|
if (componentOption.Image.Url !== undefined || componentOption.Image.Base64Image !== undefined) {
|
||||||
|
const url = componentOption.Image.Base64Image ?? componentOption.Image.Url;
|
||||||
|
return (<button
|
||||||
|
className='justify-center sidebar-component-card hover:h-full'
|
||||||
|
key={componentOption.Name}
|
||||||
|
id={componentOption.Name}
|
||||||
|
title={componentOption.Name}
|
||||||
|
onClick={() => props.buttonOnClick(componentOption.Name)}
|
||||||
|
draggable={true}
|
||||||
|
onDragStart={(event) => handleDragStart(event)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
className='transition-all h-12 w-full object-cover'
|
||||||
|
src={url}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{truncateString(componentOption.Name, 5)}
|
||||||
|
</div>
|
||||||
|
</button>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<button
|
||||||
|
className='group justify-center sidebar-component hover:h-full'
|
||||||
|
key={componentOption.Name}
|
||||||
|
id={componentOption.Name}
|
||||||
|
title={componentOption.Name}
|
||||||
|
onClick={() => props.buttonOnClick(componentOption.Name)}
|
||||||
|
draggable={true}
|
||||||
|
onDragStart={(event) => handleDragStart(event)}
|
||||||
|
>
|
||||||
|
|
||||||
|
{truncateString(componentOption.Name, 5)}
|
||||||
|
</button>);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isOpenClasses = props.isOpen ? 'left-16' : '-left-64';
|
||||||
|
return (
|
||||||
|
<div className={`fixed z-10 bg-slate-200
|
||||||
|
text-gray-700 transition-all h-full w-64
|
||||||
|
overflow-y-auto ${isOpenClasses}`}>
|
||||||
|
<div className='bg-slate-100 sidebar-title'>
|
||||||
|
Symbols
|
||||||
|
</div>
|
||||||
|
<div className='grid grid-cols-1 md:grid-cols-3 gap-2
|
||||||
|
m-2 md:text-xs font-bold'>
|
||||||
|
{listElements}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
34
src/Components/SymbolsSidebar/MouseEventHandlers.ts
Normal file
34
src/Components/SymbolsSidebar/MouseEventHandlers.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { IPoint } from '../../Interfaces/IPoint';
|
||||||
|
|
||||||
|
export function handleRightClick(
|
||||||
|
event: MouseEvent,
|
||||||
|
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||||
|
setOnClickSymbolId: React.Dispatch<React.SetStateAction<string>>,
|
||||||
|
setContextMenuPosition: React.Dispatch<React.SetStateAction<IPoint>>
|
||||||
|
): void {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!(event.target instanceof HTMLButtonElement)) {
|
||||||
|
setIsContextMenuOpen(false);
|
||||||
|
setOnClickSymbolId('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextMenuPosition: IPoint = { x: event.pageX, y: event.pageY };
|
||||||
|
setIsContextMenuOpen(true);
|
||||||
|
setOnClickSymbolId(event.target.id);
|
||||||
|
setContextMenuPosition(contextMenuPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleLeftClick(
|
||||||
|
isContextMenuOpen: boolean,
|
||||||
|
setIsContextMenuOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||||
|
setOnClickContainerId: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
): void {
|
||||||
|
if (!isContextMenuOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsContextMenuOpen(false);
|
||||||
|
setOnClickContainerId('');
|
||||||
|
}
|
137
src/Components/SymbolsSidebar/SymbolsSidebar.tsx
Normal file
137
src/Components/SymbolsSidebar/SymbolsSidebar.tsx
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { FixedSizeList as List } from 'react-window';
|
||||||
|
import { Menu } from '../Menu/Menu';
|
||||||
|
import { MenuItem } from '../Menu/MenuItem';
|
||||||
|
import { handleLeftClick, handleRightClick } from './MouseEventHandlers';
|
||||||
|
import { IPoint } from '../../Interfaces/IPoint';
|
||||||
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
||||||
|
import { SymbolProperties } from '../SymbolProperties/SymbolProperties';
|
||||||
|
|
||||||
|
interface ISymbolsSidebarProps {
|
||||||
|
SelectedSymbolId: string
|
||||||
|
symbols: Map<string, ISymbolModel>
|
||||||
|
isOpen: boolean
|
||||||
|
isHistoryOpen: boolean
|
||||||
|
OnPropertyChange: (key: string, value: string | number | boolean) => void
|
||||||
|
SelectSymbol: (symbolId: string) => void
|
||||||
|
DeleteSymbol: (containerid: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSidebarProps): JSX.Element => {
|
||||||
|
// States
|
||||||
|
const [isContextMenuOpen, setIsContextMenuOpen] = React.useState<boolean>(false);
|
||||||
|
const [onClickSymbolId, setOnClickSymbolId] = React.useState<string>('');
|
||||||
|
const [contextMenuPosition, setContextMenuPosition] = React.useState<IPoint>({
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const elementRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
React.useEffect(() => {
|
||||||
|
const onContextMenu = (event: MouseEvent): void => handleRightClick(
|
||||||
|
event,
|
||||||
|
setIsContextMenuOpen,
|
||||||
|
setOnClickSymbolId,
|
||||||
|
setContextMenuPosition
|
||||||
|
);
|
||||||
|
|
||||||
|
const onLeftClick = (): void => handleLeftClick(
|
||||||
|
isContextMenuOpen,
|
||||||
|
setIsContextMenuOpen,
|
||||||
|
setOnClickSymbolId
|
||||||
|
);
|
||||||
|
|
||||||
|
elementRef.current?.addEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
onContextMenu
|
||||||
|
);
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
'click',
|
||||||
|
onLeftClick
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
elementRef.current?.removeEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
onContextMenu
|
||||||
|
);
|
||||||
|
|
||||||
|
window.removeEventListener(
|
||||||
|
'click',
|
||||||
|
onLeftClick
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render
|
||||||
|
let isOpenClasses = '-right-64';
|
||||||
|
if (props.isOpen) {
|
||||||
|
isOpenClasses = props.isHistoryOpen
|
||||||
|
? 'right-64'
|
||||||
|
: 'right-0';
|
||||||
|
}
|
||||||
|
|
||||||
|
const containers = [...props.symbols.values()];
|
||||||
|
const Row = ({ index, style }: {index: number, style: React.CSSProperties}): JSX.Element => {
|
||||||
|
const container = containers[index];
|
||||||
|
const key = container.id.toString();
|
||||||
|
const text = key;
|
||||||
|
const selectedClass: string = props.SelectedSymbolId !== '' &&
|
||||||
|
props.SelectedSymbolId === container.id
|
||||||
|
? 'border-l-4 bg-slate-400/60 hover:bg-slate-400'
|
||||||
|
: 'bg-slate-300/60 hover:bg-slate-300';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
||||||
|
text-left text-sm font-medium transition-all ${selectedClass}`
|
||||||
|
}
|
||||||
|
id={key}
|
||||||
|
key={key}
|
||||||
|
style={style}
|
||||||
|
onClick={() => props.SelectSymbol(key)}
|
||||||
|
>
|
||||||
|
{ text }
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`fixed flex flex-col bg-slate-100 text-gray-800 transition-all h-full w-64 overflow-y-auto z-20 ${isOpenClasses}`}>
|
||||||
|
<div className='bg-slate-100 font-bold sidebar-title'>
|
||||||
|
Elements
|
||||||
|
</div>
|
||||||
|
<div ref={elementRef} className='h-96 text-gray-800'>
|
||||||
|
<List
|
||||||
|
className='List divide-y divide-black'
|
||||||
|
itemCount={containers.length}
|
||||||
|
itemSize={35}
|
||||||
|
height={384}
|
||||||
|
width={256}
|
||||||
|
>
|
||||||
|
{ Row }
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
|
<Menu
|
||||||
|
className='transition-opacity rounded bg-slate-200 py-1 drop-shadow-xl'
|
||||||
|
x={contextMenuPosition.x}
|
||||||
|
y={contextMenuPosition.y}
|
||||||
|
isOpen={isContextMenuOpen}
|
||||||
|
>
|
||||||
|
<MenuItem className='contextmenu-item' text='Delete' onClick={() => {
|
||||||
|
setIsContextMenuOpen(false);
|
||||||
|
props.DeleteSymbol(onClickSymbolId);
|
||||||
|
}} />
|
||||||
|
</Menu>
|
||||||
|
<SymbolProperties
|
||||||
|
symbol={props.symbols.get(props.SelectedSymbolId)}
|
||||||
|
symbols={props.symbols}
|
||||||
|
onChange={props.OnPropertyChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -3,38 +3,55 @@ import { ElementsSidebar } from '../ElementsSidebar/ElementsSidebar';
|
||||||
import { Sidebar } from '../Sidebar/Sidebar';
|
import { Sidebar } from '../Sidebar/Sidebar';
|
||||||
import { History } from '../History/History';
|
import { History } from '../History/History';
|
||||||
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
import { IAvailableContainer } from '../../Interfaces/IAvailableContainer';
|
||||||
import { ContainerModel } from '../../Interfaces/IContainerModel';
|
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
import { PhotographIcon, UploadIcon } from '@heroicons/react/outline';
|
import { PhotographIcon, UploadIcon } from '@heroicons/react/outline';
|
||||||
import { FloatingButton } from '../FloatingButton/FloatingButton';
|
import { FloatingButton } from '../FloatingButton/FloatingButton';
|
||||||
import { Bar } from '../Bar/Bar';
|
import { Bar } from '../Bar/Bar';
|
||||||
|
import { IAvailableSymbol } from '../../Interfaces/IAvailableSymbol';
|
||||||
|
import { Symbols } from '../Symbols/Symbols';
|
||||||
|
import { SymbolsSidebar } from '../SymbolsSidebar/SymbolsSidebar';
|
||||||
|
|
||||||
interface IUIProps {
|
interface IUIProps {
|
||||||
|
SelectedContainer: IContainerModel | undefined
|
||||||
current: IHistoryState
|
current: IHistoryState
|
||||||
history: IHistoryState[]
|
history: IHistoryState[]
|
||||||
historyCurrentStep: number
|
historyCurrentStep: number
|
||||||
AvailableContainers: IAvailableContainer[]
|
AvailableContainers: IAvailableContainer[]
|
||||||
SelectContainer: (container: ContainerModel) => void
|
AvailableSymbols: IAvailableSymbol[]
|
||||||
|
SelectContainer: (containerId: string) => void
|
||||||
DeleteContainer: (containerId: string) => void
|
DeleteContainer: (containerId: string) => void
|
||||||
OnPropertyChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
OnPropertyChange: (key: string, value: string | number | boolean, isStyle?: boolean) => void
|
||||||
OnPropertiesSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
OnPropertiesSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
||||||
AddContainerToSelectedContainer: (type: string) => void
|
AddContainerToSelectedContainer: (type: string) => void
|
||||||
AddContainer: (index: number, type: string, parentId: string) => void
|
AddContainer: (index: number, type: string, parentId: string) => void
|
||||||
|
AddSymbol: (type: string) => void
|
||||||
|
OnSymbolPropertyChange: (key: string, value: string | number | boolean) => void
|
||||||
|
SelectSymbol: (symbolId: string) => void
|
||||||
|
DeleteSymbol: (symbolId: string) => void
|
||||||
SaveEditorAsJSON: () => void
|
SaveEditorAsJSON: () => void
|
||||||
SaveEditorAsSVG: () => void
|
SaveEditorAsSVG: () => void
|
||||||
LoadState: (move: number) => void
|
LoadState: (move: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CloseOtherSidebars(
|
||||||
|
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||||
|
setIsSymbolsOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
|
): void {
|
||||||
|
setIsSidebarOpen(false);
|
||||||
|
setIsSymbolsOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
|
const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
|
||||||
const [isElementsSidebarOpen, setIsElementsSidebarOpen] = React.useState(false);
|
const [isSymbolsOpen, setIsSymbolsOpen] = React.useState(false);
|
||||||
const [isHistoryOpen, setIsHistoryOpen] = React.useState(false);
|
const [isHistoryOpen, setIsHistoryOpen] = React.useState(false);
|
||||||
|
|
||||||
let buttonRightOffsetClasses = 'right-12';
|
let buttonRightOffsetClasses = 'right-12';
|
||||||
if (isElementsSidebarOpen || isHistoryOpen) {
|
if (isSidebarOpen || isHistoryOpen) {
|
||||||
buttonRightOffsetClasses = 'right-72';
|
buttonRightOffsetClasses = 'right-72';
|
||||||
}
|
}
|
||||||
if (isHistoryOpen && isElementsSidebarOpen) {
|
if (isHistoryOpen && isSidebarOpen) {
|
||||||
buttonRightOffsetClasses = 'right-[544px]';
|
buttonRightOffsetClasses = 'right-[544px]';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,22 +59,35 @@ export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
||||||
<>
|
<>
|
||||||
<Bar
|
<Bar
|
||||||
isSidebarOpen={isSidebarOpen}
|
isSidebarOpen={isSidebarOpen}
|
||||||
isElementsSidebarOpen={isElementsSidebarOpen}
|
isSymbolsOpen={isSymbolsOpen}
|
||||||
|
isElementsSidebarOpen={isSidebarOpen}
|
||||||
isHistoryOpen={isHistoryOpen}
|
isHistoryOpen={isHistoryOpen}
|
||||||
ToggleElementsSidebar={() => setIsElementsSidebarOpen(!isElementsSidebarOpen)}
|
ToggleSidebar={() => {
|
||||||
ToggleSidebar={() => setIsSidebarOpen(!isSidebarOpen)}
|
CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen);
|
||||||
|
setIsSidebarOpen(!isSidebarOpen);
|
||||||
|
}}
|
||||||
|
ToggleSymbols={() => {
|
||||||
|
CloseOtherSidebars(setIsSidebarOpen, setIsSymbolsOpen);
|
||||||
|
setIsSymbolsOpen(!isSymbolsOpen);
|
||||||
|
}}
|
||||||
ToggleTimeline={() => setIsHistoryOpen(!isHistoryOpen)}
|
ToggleTimeline={() => setIsHistoryOpen(!isHistoryOpen)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Sidebar
|
<Sidebar
|
||||||
componentOptions={props.AvailableContainers}
|
componentOptions={props.AvailableContainers}
|
||||||
isOpen={isSidebarOpen}
|
isOpen={isSidebarOpen}
|
||||||
buttonOnClick={(type: string) => props.AddContainerToSelectedContainer(type)}
|
buttonOnClick={props.AddContainerToSelectedContainer}
|
||||||
|
/>
|
||||||
|
<Symbols
|
||||||
|
componentOptions={props.AvailableSymbols}
|
||||||
|
isOpen={isSymbolsOpen}
|
||||||
|
buttonOnClick={props.AddSymbol}
|
||||||
/>
|
/>
|
||||||
<ElementsSidebar
|
<ElementsSidebar
|
||||||
MainContainer={props.current.MainContainer}
|
MainContainer={props.current.MainContainer}
|
||||||
SelectedContainer={props.current.SelectedContainer}
|
symbols={props.current.Symbols}
|
||||||
isOpen={isElementsSidebarOpen}
|
SelectedContainer={props.SelectedContainer}
|
||||||
|
isOpen={isSidebarOpen}
|
||||||
isHistoryOpen={isHistoryOpen}
|
isHistoryOpen={isHistoryOpen}
|
||||||
OnPropertyChange={props.OnPropertyChange}
|
OnPropertyChange={props.OnPropertyChange}
|
||||||
OnPropertiesSubmit={props.OnPropertiesSubmit}
|
OnPropertiesSubmit={props.OnPropertiesSubmit}
|
||||||
|
@ -65,6 +95,15 @@ export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
||||||
DeleteContainer={props.DeleteContainer}
|
DeleteContainer={props.DeleteContainer}
|
||||||
AddContainer={props.AddContainer}
|
AddContainer={props.AddContainer}
|
||||||
/>
|
/>
|
||||||
|
<SymbolsSidebar
|
||||||
|
SelectedSymbolId={props.current.SelectedSymbolId}
|
||||||
|
symbols={props.current.Symbols}
|
||||||
|
isOpen={isSymbolsOpen}
|
||||||
|
isHistoryOpen={isHistoryOpen}
|
||||||
|
OnPropertyChange={props.OnSymbolPropertyChange}
|
||||||
|
SelectSymbol={props.SelectSymbol}
|
||||||
|
DeleteSymbol={props.DeleteSymbol}
|
||||||
|
/>
|
||||||
<History
|
<History
|
||||||
history={props.history}
|
history={props.history}
|
||||||
historyCurrentStep={props.historyCurrentStep}
|
historyCurrentStep={props.historyCurrentStep}
|
||||||
|
|
|
@ -5,8 +5,8 @@ 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
|
||||||
XPositionReference: XPositionReference
|
|
||||||
Image: IImage
|
Image: IImage
|
||||||
Width: number
|
Width?: number
|
||||||
Height: number
|
Height?: number
|
||||||
|
XPositionReference?: XPositionReference
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,25 @@
|
||||||
import IProperties from './IProperties';
|
import IContainerProperties from './IContainerProperties';
|
||||||
|
|
||||||
export interface IContainerModel {
|
export interface IContainerModel {
|
||||||
children: IContainerModel[]
|
children: IContainerModel[]
|
||||||
parent: IContainerModel | null
|
parent: IContainerModel | null
|
||||||
properties: IProperties
|
properties: IContainerProperties
|
||||||
userData: Record<string, string | number>
|
userData: Record<string, string | number>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Macro for creating the interface
|
||||||
|
* Do not add methods since they will be lost during serialization
|
||||||
|
*/
|
||||||
export class ContainerModel implements IContainerModel {
|
export class ContainerModel implements IContainerModel {
|
||||||
public children: IContainerModel[];
|
public children: IContainerModel[];
|
||||||
public parent: IContainerModel | null;
|
public parent: IContainerModel | null;
|
||||||
public properties: IProperties;
|
public properties: IContainerProperties;
|
||||||
public userData: Record<string, string | number>;
|
public userData: Record<string, string | number>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
parent: IContainerModel | null,
|
parent: IContainerModel | null,
|
||||||
properties: IProperties,
|
properties: IContainerProperties,
|
||||||
children: IContainerModel[] = [],
|
children: IContainerModel[] = [],
|
||||||
userData = {}) {
|
userData = {}) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
|
|
|
@ -4,13 +4,17 @@ import { XPositionReference } from '../Enums/XPositionReference';
|
||||||
/**
|
/**
|
||||||
* Properties of a container
|
* Properties of a container
|
||||||
*/
|
*/
|
||||||
export default interface IProperties {
|
export default interface IContainerProperties {
|
||||||
/** id of the container */
|
/** id of the container */
|
||||||
id: string
|
id: string
|
||||||
|
|
||||||
|
// TODO: replace null by empty string
|
||||||
/** id of the parent container (null when there is no parent) */
|
/** id of the parent container (null when there is no parent) */
|
||||||
parentId: string | null
|
parentId: string | null
|
||||||
|
|
||||||
|
/** id of the linked symbol ('' when there is no parent) */
|
||||||
|
linkedSymbolId: string
|
||||||
|
|
||||||
/** Text displayed in the container */
|
/** Text displayed in the container */
|
||||||
displayedText: string
|
displayedText: string
|
||||||
|
|
|
@ -1,9 +1,22 @@
|
||||||
import { IContainerModel } from './IContainerModel';
|
import { IContainerModel } from './IContainerModel';
|
||||||
|
import { ISymbolModel } from './ISymbolModel';
|
||||||
|
|
||||||
export interface IHistoryState {
|
export interface IHistoryState {
|
||||||
|
/** Last editor action */
|
||||||
LastAction: string
|
LastAction: string
|
||||||
|
|
||||||
|
/** Reference to the main container */
|
||||||
MainContainer: IContainerModel
|
MainContainer: IContainerModel
|
||||||
SelectedContainer: IContainerModel | null
|
|
||||||
|
/** Id of the selected container */
|
||||||
SelectedContainerId: string
|
SelectedContainerId: string
|
||||||
|
|
||||||
|
/** Counter of type of container. Used for ids. */
|
||||||
TypeCounters: Record<string, number>
|
TypeCounters: Record<string, number>
|
||||||
|
|
||||||
|
/** List of symbols */
|
||||||
|
Symbols: Map<string, ISymbolModel>
|
||||||
|
|
||||||
|
/** Selected symbols id */
|
||||||
|
SelectedSymbolId: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,20 @@
|
||||||
/** Model of an image with multiple source */
|
/**
|
||||||
|
* Model of an image with multiple source
|
||||||
|
* It must at least have one source.
|
||||||
|
*
|
||||||
|
* If Url/Base64Image and Svg are set,
|
||||||
|
* Url/Base64Image will be shown in the menu while SVG will be drawn
|
||||||
|
*/
|
||||||
export interface IImage {
|
export interface IImage {
|
||||||
|
/** Name of the image */
|
||||||
Name: string
|
Name: string
|
||||||
Url: string
|
|
||||||
Base64Image: string
|
/** (optional) Url of the image */
|
||||||
Svg: string
|
Url?: string
|
||||||
|
|
||||||
|
/** (optional) base64 data of the image */
|
||||||
|
Base64Image?: string
|
||||||
|
|
||||||
|
/** (optional) SVG string */
|
||||||
|
Svg?: string
|
||||||
}
|
}
|
||||||
|
|
24
src/Interfaces/ISymbolModel.ts
Normal file
24
src/Interfaces/ISymbolModel.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { IAvailableSymbol } from './IAvailableSymbol';
|
||||||
|
|
||||||
|
export interface ISymbolModel {
|
||||||
|
/** Identifier */
|
||||||
|
id: string
|
||||||
|
|
||||||
|
/** Type */
|
||||||
|
type: string
|
||||||
|
|
||||||
|
/** Configuration of the symbol */
|
||||||
|
config: IAvailableSymbol
|
||||||
|
|
||||||
|
/** Horizontal offset */
|
||||||
|
x: number
|
||||||
|
|
||||||
|
/** Width */
|
||||||
|
width: number
|
||||||
|
|
||||||
|
/** Height */
|
||||||
|
height: number
|
||||||
|
|
||||||
|
/** List of linked container id */
|
||||||
|
linkedContainers: Set<string>
|
||||||
|
}
|
|
@ -11,6 +11,10 @@
|
||||||
@apply transition-all px-2 py-6 text-sm rounded-lg bg-slate-300/60 hover:bg-slate-300
|
@apply transition-all px-2 py-6 text-sm rounded-lg bg-slate-300/60 hover:bg-slate-300
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-component-card {
|
||||||
|
@apply transition-all overflow-hidden text-sm rounded-lg bg-slate-300/60 hover:bg-slate-300
|
||||||
|
}
|
||||||
|
|
||||||
.elements-sidebar-row {
|
.elements-sidebar-row {
|
||||||
@apply pl-6 pr-6 pt-2 pb-2 w-full
|
@apply pl-6 pr-6 pt-2 pb-2 w-full
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { XPositionReference } from '../Enums/XPositionReference';
|
||||||
import { IAvailableContainer } from '../Interfaces/IAvailableContainer';
|
import { IAvailableContainer } from '../Interfaces/IAvailableContainer';
|
||||||
import { IConfiguration } from '../Interfaces/IConfiguration';
|
import { IConfiguration } from '../Interfaces/IConfiguration';
|
||||||
import { IContainerModel } from '../Interfaces/IContainerModel';
|
import { IContainerModel } from '../Interfaces/IContainerModel';
|
||||||
import IProperties from '../Interfaces/IProperties';
|
import IContainerProperties from '../Interfaces/IContainerProperties';
|
||||||
|
|
||||||
/// CONTAINER DEFAULTS ///
|
/// CONTAINER DEFAULTS ///
|
||||||
|
|
||||||
|
@ -18,6 +18,11 @@ export const SHOW_DIMENSIONS_PER_DEPTH = true;
|
||||||
export const DIMENSION_MARGIN = 50;
|
export const DIMENSION_MARGIN = 50;
|
||||||
export const NOTCHES_LENGTH = 4;
|
export const NOTCHES_LENGTH = 4;
|
||||||
|
|
||||||
|
/// SYMBOL DEFAULTS ///
|
||||||
|
|
||||||
|
export const DEFAULT_SYMBOL_WIDTH = 32;
|
||||||
|
export const DEFAULT_SYMBOL_HEIGHT = 32;
|
||||||
|
|
||||||
/// EDITOR DEFAULTS ///
|
/// EDITOR DEFAULTS ///
|
||||||
|
|
||||||
export const ENABLE_SHORTCUTS = true;
|
export const ENABLE_SHORTCUTS = true;
|
||||||
|
@ -48,9 +53,10 @@ export const DEFAULT_CONFIG: IConfiguration = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_MAINCONTAINER_PROPS: IProperties = {
|
export const DEFAULT_MAINCONTAINER_PROPS: IContainerProperties = {
|
||||||
id: 'main',
|
id: 'main',
|
||||||
parentId: 'null',
|
parentId: 'null',
|
||||||
|
linkedSymbolId: '',
|
||||||
displayedText: 'main',
|
displayedText: 'main',
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -73,9 +79,10 @@ export const GetDefaultContainerProps = (
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
containerConfig: IAvailableContainer
|
containerConfig: IAvailableContainer
|
||||||
): IProperties => ({
|
): IContainerProperties => ({
|
||||||
id: `${type}-${typeCount}`,
|
id: `${type}-${typeCount}`,
|
||||||
parentId: parent.properties.id,
|
parentId: parent.properties.id,
|
||||||
|
linkedSymbolId: '',
|
||||||
displayedText: `${type}-${typeCount}`,
|
displayedText: `${type}-${typeCount}`,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
|
|
@ -18,6 +18,11 @@ export function Revive(editorState: IEditorState): void {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.Symbols = new Map(state.Symbols);
|
||||||
|
for (const symbol of state.Symbols.values()) {
|
||||||
|
symbol.linkedContainers = new Set(symbol.linkedContainers);
|
||||||
|
}
|
||||||
|
|
||||||
const it = MakeIterator(state.MainContainer);
|
const it = MakeIterator(state.MainContainer);
|
||||||
for (const container of it) {
|
for (const container of it) {
|
||||||
const parentId = container.properties.parentId;
|
const parentId = container.properties.parentId;
|
||||||
|
@ -31,24 +36,21 @@ export function Revive(editorState: IEditorState): void {
|
||||||
}
|
}
|
||||||
container.parent = parent;
|
container.parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selected = findContainerById(state.MainContainer, state.SelectedContainerId);
|
|
||||||
if (selected === undefined) {
|
|
||||||
state.SelectedContainer = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
state.SelectedContainer = selected;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCircularReplacer = (): (key: any, value: object | null) => object | null | undefined => {
|
export const getCircularReplacer = (): (key: any, value: object | Map<string, any> | null) => object | null | undefined => {
|
||||||
return (key: any, value: object | null) => {
|
return (key: any, value: object | null) => {
|
||||||
if (key === 'parent') {
|
if (key === 'parent') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'SelectedContainer') {
|
if (key === 'Symbols') {
|
||||||
return;
|
return Array.from((value as Map<string, any>).entries());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'linkedContainers') {
|
||||||
|
return Array.from(value as Set<string>);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|
|
@ -4,3 +4,7 @@ export function truncateString(str: string, num: number): string {
|
||||||
}
|
}
|
||||||
return `${str.slice(0, num)}...`;
|
return `${str.slice(0, num)}...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function camelize(str: string): any {
|
||||||
|
return str.split('-').map((word, index) => index > 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word).join('');
|
||||||
|
}
|
||||||
|
|
21
src/utils/svg.ts
Normal file
21
src/utils/svg.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { XPositionReference } from '../Enums/XPositionReference';
|
||||||
|
|
||||||
|
export function transformX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
|
||||||
|
let transformedX = x;
|
||||||
|
if (xPositionReference === XPositionReference.Center) {
|
||||||
|
transformedX += width / 2;
|
||||||
|
} else if (xPositionReference === XPositionReference.Right) {
|
||||||
|
transformedX += width;
|
||||||
|
}
|
||||||
|
return transformedX;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function restoreX(x: number, width: number, xPositionReference = XPositionReference.Left): number {
|
||||||
|
let transformedX = x;
|
||||||
|
if (xPositionReference === XPositionReference.Center) {
|
||||||
|
transformedX -= width / 2;
|
||||||
|
} else if (xPositionReference === XPositionReference.Right) {
|
||||||
|
transformedX -= width;
|
||||||
|
}
|
||||||
|
return transformedX;
|
||||||
|
}
|
|
@ -112,7 +112,8 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
],
|
],
|
||||||
AvailableSymbols: [
|
AvailableSymbols: [
|
||||||
{
|
{
|
||||||
Height: 0,
|
Width: 32,
|
||||||
|
Height: 32,
|
||||||
Image: {
|
Image: {
|
||||||
Base64Image: null,
|
Base64Image: null,
|
||||||
Name: null,
|
Name: null,
|
||||||
|
@ -120,11 +121,11 @@ 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',
|
||||||
Width: 0,
|
|
||||||
XPositionReference: 1
|
XPositionReference: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Height: 0,
|
Width: 32,
|
||||||
|
Height: 32,
|
||||||
Image: {
|
Image: {
|
||||||
Base64Image: null,
|
Base64Image: null,
|
||||||
Name: null,
|
Name: null,
|
||||||
|
@ -132,7 +133,6 @@ const GetSVGLayoutConfiguration = () => {
|
||||||
Url: 'https://e7.pngegg.com/pngimages/647/127/png-clipart-svg-working-group-information-world-wide-web-internet-structure.png'
|
Url: 'https://e7.pngegg.com/pngimages/647/127/png-clipart-svg-working-group-information-world-wide-web-internet-structure.png'
|
||||||
},
|
},
|
||||||
Name: 'Joint de structure',
|
Name: 'Joint de structure',
|
||||||
Width: 0,
|
|
||||||
XPositionReference: 0
|
XPositionReference: 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue