Merged PR 163: Remove the static form + rename some components for clarity
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
The static form is hard to maintain so I am removing it + rename some components for clarity + moved some utils files
This commit is contained in:
parent
7e3ccdee99
commit
66ea3b1b64
21 changed files with 150 additions and 523 deletions
419
src/Components/Editor/Actions/ContainerOperations.ts
Normal file
419
src/Components/Editor/Actions/ContainerOperations.ts
Normal file
|
@ -0,0 +1,419 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
||||
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { findContainerById, MakeIterator } from '../../../utils/itertools';
|
||||
import { getCurrentHistory, UpdateCounters } from '../Editor';
|
||||
import { AddMethod } from '../../../Enums/AddMethod';
|
||||
import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer';
|
||||
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../../utils/default';
|
||||
import { ApplyBehaviors } from '../Behaviors/Behaviors';
|
||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
|
||||
/**
|
||||
* Select a container
|
||||
* @param container Selected container
|
||||
*/
|
||||
export function SelectContainer(
|
||||
containerId: 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 ${containerId}`,
|
||||
MainContainer: structuredClone(current.MainContainer),
|
||||
SelectedContainerId: containerId,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters),
|
||||
Symbols: structuredClone(current.Symbols),
|
||||
SelectedSymbolId: current.SelectedSymbolId
|
||||
});
|
||||
setHistory(history);
|
||||
setHistoryCurrentStep(history.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a container
|
||||
* @param containerId containerId of the container to delete
|
||||
* @param fullHistory History of the editor
|
||||
* @param historyCurrentStep Current step
|
||||
* @param setHistory State setter for History
|
||||
* @param setHistoryCurrentStep State setter for current step
|
||||
*/
|
||||
export function DeleteContainer(
|
||||
containerId: 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 mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
||||
const container = findContainerById(mainContainerClone, containerId);
|
||||
|
||||
if (container === undefined) {
|
||||
throw new Error(`[DeleteContainer] Tried to delete a container that is not present in the main container: ${containerId}`);
|
||||
}
|
||||
|
||||
if (container === mainContainerClone ||
|
||||
container.parent === undefined ||
|
||||
container.parent === null) {
|
||||
// TODO: Implement alert
|
||||
throw new Error('[DeleteContainer] Tried to delete the main container! Deleting the main container is not allowed!');
|
||||
}
|
||||
|
||||
if (container === null || container === undefined) {
|
||||
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);
|
||||
if (index > -1) {
|
||||
container.parent.children.splice(index, 1);
|
||||
} else {
|
||||
throw new Error('[DeleteContainer] Could not find container among parent\'s children');
|
||||
}
|
||||
|
||||
// Select the previous container
|
||||
// or select the one above
|
||||
const SelectedContainerId = GetSelectedContainerOnDelete(
|
||||
mainContainerClone,
|
||||
current.SelectedContainerId,
|
||||
container.parent,
|
||||
index
|
||||
);
|
||||
|
||||
history.push({
|
||||
LastAction: `Delete ${containerId}`,
|
||||
MainContainer: mainContainerClone,
|
||||
SelectedContainerId,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters),
|
||||
Symbols: newSymbols,
|
||||
SelectedSymbolId: current.SelectedSymbolId
|
||||
});
|
||||
setHistory(history);
|
||||
setHistoryCurrentStep(history.length - 1);
|
||||
}
|
||||
|
||||
function GetSelectedContainerOnDelete(mainContainerClone: IContainerModel, selectedContainerId: string, parent: IContainerModel, index: number): string {
|
||||
const SelectedContainer = findContainerById(mainContainerClone, selectedContainerId) ??
|
||||
parent.children.at(index - 1) ??
|
||||
parent;
|
||||
const SelectedContainerId = SelectedContainer.properties.id;
|
||||
return SelectedContainerId;
|
||||
}
|
||||
|
||||
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
|
||||
* @param type The type of container
|
||||
* @param configuration Configuration of the App
|
||||
* @param fullHistory History of the editor
|
||||
* @param historyCurrentStep Current step
|
||||
* @param setHistory State setter for History
|
||||
* @param setHistoryCurrentStep State setter for current step
|
||||
* @returns void
|
||||
*/
|
||||
export function AddContainerToSelectedContainer(
|
||||
type: string,
|
||||
selected: IContainerModel | undefined,
|
||||
configuration: IConfiguration,
|
||||
fullHistory: IHistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
if (selected === null ||
|
||||
selected === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = selected;
|
||||
AddContainer(
|
||||
parent.children.length,
|
||||
type,
|
||||
parent.properties.id,
|
||||
configuration,
|
||||
fullHistory,
|
||||
historyCurrentStep,
|
||||
setHistory,
|
||||
setHistoryCurrentStep
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and add a new container at `index` in children of parent of `parentId`
|
||||
* @param index Index where to insert to the new container
|
||||
* @param type Type of container
|
||||
* @param parentId Parent in which to insert the new container
|
||||
* @param configuration Configuration of the app
|
||||
* @param fullHistory History of the editor
|
||||
* @param historyCurrentStep Current step
|
||||
* @param setHistory State setter of History
|
||||
* @param setHistoryCurrentStep State setter of the current step
|
||||
* @returns void
|
||||
*/
|
||||
export function AddContainer(
|
||||
index: number,
|
||||
type: string,
|
||||
parentId: 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];
|
||||
|
||||
// Get the preset properties from the API
|
||||
const containerConfig = configuration.AvailableContainers
|
||||
.find(option => option.Type === type);
|
||||
|
||||
if (containerConfig === undefined) {
|
||||
throw new Error(`[AddContainer] Object type not found. Found: ${type}`);
|
||||
}
|
||||
|
||||
// Set the counter of the object type in order to assign an unique id
|
||||
const newCounters = Object.assign({}, current.TypeCounters);
|
||||
UpdateCounters(newCounters, type);
|
||||
const count = newCounters[type];
|
||||
|
||||
// Create maincontainer model
|
||||
const clone: IContainerModel = structuredClone(current.MainContainer);
|
||||
|
||||
// Find the parent
|
||||
const parentClone: IContainerModel | undefined = findContainerById(
|
||||
clone, parentId
|
||||
);
|
||||
|
||||
if (parentClone === null || parentClone === undefined) {
|
||||
throw new Error('[AddContainer] Container model was not found among children of the main container!');
|
||||
}
|
||||
|
||||
let x = containerConfig.DefaultX ?? 0;
|
||||
const y = containerConfig.DefaultY ?? 0;
|
||||
|
||||
x = ApplyAddMethod(index, containerConfig, parentClone, x);
|
||||
|
||||
const defaultProperties = GetDefaultContainerProps(
|
||||
type,
|
||||
count,
|
||||
parentClone,
|
||||
x,
|
||||
y,
|
||||
containerConfig
|
||||
);
|
||||
|
||||
// Create the container
|
||||
const newContainer = new ContainerModel(
|
||||
parentClone,
|
||||
defaultProperties,
|
||||
[],
|
||||
{
|
||||
type
|
||||
}
|
||||
);
|
||||
|
||||
ApplyBehaviors(newContainer, current.Symbols);
|
||||
|
||||
// And push it the the parent children
|
||||
if (index === parentClone.children.length) {
|
||||
parentClone.children.push(newContainer);
|
||||
} else {
|
||||
parentClone.children.splice(index, 0, newContainer);
|
||||
}
|
||||
|
||||
InitializeDefaultChild(configuration, containerConfig, newContainer, newCounters);
|
||||
|
||||
// Update the state
|
||||
history.push({
|
||||
LastAction: `Add ${newContainer.properties.id} in ${parentClone.properties.id}`,
|
||||
MainContainer: clone,
|
||||
SelectedContainerId: parentClone.properties.id,
|
||||
TypeCounters: newCounters,
|
||||
Symbols: structuredClone(current.Symbols),
|
||||
SelectedSymbolId: current.SelectedSymbolId
|
||||
});
|
||||
setHistory(history);
|
||||
setHistoryCurrentStep(history.length - 1);
|
||||
}
|
||||
|
||||
function InitializeDefaultChild(
|
||||
configuration: IConfiguration,
|
||||
containerConfig: IAvailableContainer,
|
||||
newContainer: ContainerModel,
|
||||
newCounters: Record<string, number>
|
||||
): void {
|
||||
if (containerConfig.DefaultChildType === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentConfig = configuration.AvailableContainers
|
||||
.find(option => option.Type === containerConfig.DefaultChildType);
|
||||
let parent = newContainer;
|
||||
let depth = 0;
|
||||
const seen = new Set<string>([containerConfig.Type]);
|
||||
|
||||
while (currentConfig !== undefined &&
|
||||
depth <= DEFAULTCHILDTYPE_MAX_DEPTH
|
||||
) {
|
||||
if (!DEFAULTCHILDTYPE_ALLOW_CYCLIC && seen.has(currentConfig.Type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
seen.add(currentConfig.Type);
|
||||
const x = currentConfig.DefaultX ?? 0;
|
||||
const y = currentConfig.DefaultY ?? 0;
|
||||
|
||||
UpdateCounters(newCounters, currentConfig.Type);
|
||||
const count = newCounters[currentConfig.Type];
|
||||
const defaultChildProperties = GetDefaultContainerProps(
|
||||
currentConfig.Type,
|
||||
count,
|
||||
parent,
|
||||
x,
|
||||
y,
|
||||
currentConfig
|
||||
);
|
||||
|
||||
// Create the container
|
||||
const newChildContainer = new ContainerModel(
|
||||
parent,
|
||||
defaultChildProperties,
|
||||
[],
|
||||
{
|
||||
type: currentConfig.Type
|
||||
}
|
||||
);
|
||||
|
||||
// And push it the the parent children
|
||||
parent.children.push(newChildContainer);
|
||||
|
||||
// iterate
|
||||
depth++;
|
||||
parent = newChildContainer;
|
||||
currentConfig = configuration.AvailableContainers
|
||||
.find(option => option.Type === (currentConfig as IAvailableContainer).DefaultChildType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new offset by applying an Add method (append, insert etc.)
|
||||
* See AddMethod
|
||||
* @param index Index of the container
|
||||
* @param containerConfig Configuration of a container
|
||||
* @param parent Parent container
|
||||
* @param x Additionnal offset
|
||||
* @returns New offset
|
||||
*/
|
||||
function ApplyAddMethod(index: number, containerConfig: IAvailableContainer, parent: IContainerModel, x: number): number {
|
||||
if (index > 0 && (
|
||||
containerConfig.AddMethod === undefined ||
|
||||
containerConfig.AddMethod === AddMethod.Append)) {
|
||||
// Append method (default)
|
||||
const lastChild: IContainerModel | undefined = parent.children.at(index - 1);
|
||||
|
||||
if (lastChild !== undefined) {
|
||||
x += (lastChild.properties.x + lastChild.properties.width);
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
isStyle: boolean = false,
|
||||
selected: IContainerModel | undefined,
|
||||
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 (selected === null ||
|
||||
selected === undefined) {
|
||||
throw new Error('[OnPropertyChange] Property was changed before selecting a Container');
|
||||
}
|
||||
|
||||
const mainContainerClone: IContainerModel = structuredClone(current.MainContainer);
|
||||
const container: ContainerModel | undefined = findContainerById(mainContainerClone, selected.properties.id);
|
||||
|
||||
if (container === null || container === undefined) {
|
||||
throw new Error('[OnPropertyChange] Container model was not found among children of the main container!');
|
||||
}
|
||||
|
||||
const oldSymbolId = container.properties.linkedSymbolId;
|
||||
|
||||
if (isStyle) {
|
||||
(container.properties.style as any)[key] = value;
|
||||
} else {
|
||||
(container.properties as any)[key] = value;
|
||||
}
|
||||
|
||||
LinkSymbol(
|
||||
container.properties.id,
|
||||
oldSymbolId,
|
||||
container.properties.linkedSymbolId,
|
||||
current.Symbols
|
||||
);
|
||||
|
||||
ApplyBehaviors(container, current.Symbols);
|
||||
|
||||
history.push({
|
||||
LastAction: `Change ${key} of ${container.properties.id}`,
|
||||
MainContainer: mainContainerClone,
|
||||
SelectedContainerId: container.properties.id,
|
||||
TypeCounters: Object.assign({}, current.TypeCounters),
|
||||
Symbols: structuredClone(current.Symbols),
|
||||
SelectedSymbolId: current.SelectedSymbolId
|
||||
});
|
||||
setHistory(history);
|
||||
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);
|
||||
}
|
85
src/Components/Editor/Actions/Save.ts
Normal file
85
src/Components/Editor/Actions/Save.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||
import { IConfiguration } from '../../../Interfaces/IConfiguration';
|
||||
import { getCircularReplacer } from '../../../utils/saveload';
|
||||
import { ID } from '../../SVG/SVG';
|
||||
import { IEditorState } from '../../../Interfaces/IEditorState';
|
||||
|
||||
export function SaveEditorAsJSON(
|
||||
history: IHistoryState[],
|
||||
historyCurrentStep: number,
|
||||
configuration: IConfiguration
|
||||
): void {
|
||||
const exportName = 'state.json';
|
||||
const spaces = import.meta.env.DEV ? 4 : 0;
|
||||
const editorState: IEditorState = {
|
||||
history,
|
||||
historyCurrentStep,
|
||||
configuration
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (window.Worker) {
|
||||
// use webworker for the stringify to avoid freezing
|
||||
const myWorker = new Worker('workers/worker.js');
|
||||
myWorker.postMessage({ editorState, spaces });
|
||||
myWorker.onmessage = (event) => {
|
||||
const data = event.data;
|
||||
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
|
||||
createDownloadNode(exportName, dataStr);
|
||||
myWorker.terminate();
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const data = JSON.stringify(editorState, getCircularReplacer(), spaces);
|
||||
const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(data)}`;
|
||||
createDownloadNode(exportName, dataStr);
|
||||
}
|
||||
|
||||
export function SaveEditorAsSVG(): void {
|
||||
const svgWrapper = document.getElementById(ID) as HTMLElement;
|
||||
let svg = svgWrapper.querySelector('svg') as SVGSVGElement;
|
||||
|
||||
if (svg === undefined) {
|
||||
throw new Error('[SaveEditorAsSVG] Missing <svg> element');
|
||||
}
|
||||
|
||||
// Recover svg from SVG Viewer
|
||||
svg = svg.cloneNode(true) as SVGSVGElement;
|
||||
svg.removeAttribute('height');
|
||||
svg.removeAttribute('width');
|
||||
const mainSvg = svg.children[1].children;
|
||||
svg.replaceChildren(...mainSvg);
|
||||
|
||||
// remove the selector
|
||||
const group = svg.children[svg.children.length - 1];
|
||||
group.removeChild(group.children[group.children.length - 1]);
|
||||
|
||||
// get svg source.
|
||||
const serializer = new XMLSerializer();
|
||||
let source = serializer.serializeToString(svg);
|
||||
|
||||
// add name spaces.
|
||||
if (source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/) == null) {
|
||||
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
|
||||
}
|
||||
if (source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/) == null) {
|
||||
source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
|
||||
}
|
||||
|
||||
// add xml declaration
|
||||
source = '<?xml version="1.0" standalone="no"?>\r\n' + source;
|
||||
|
||||
// convert svg source to URI data scheme.
|
||||
const url = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source);
|
||||
createDownloadNode('state.svg', url);
|
||||
}
|
||||
|
||||
function createDownloadNode(filename: string, datastring: string): void {
|
||||
const downloadAnchorNode = document.createElement('a');
|
||||
downloadAnchorNode.href = datastring;
|
||||
downloadAnchorNode.download = filename;
|
||||
document.body.appendChild(downloadAnchorNode); // required for firefox
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
}
|
28
src/Components/Editor/Actions/Shortcuts.ts
Normal file
28
src/Components/Editor/Actions/Shortcuts.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||
import { ENABLE_SHORTCUTS } from '../../../utils/default';
|
||||
|
||||
export function onKeyDown(
|
||||
event: KeyboardEvent,
|
||||
history: IHistoryState[],
|
||||
historyCurrentStep: number,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
||||
): void {
|
||||
if (!ENABLE_SHORTCUTS) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
if (event.isComposing || event.keyCode === 229) {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'z' &&
|
||||
event.ctrlKey &&
|
||||
historyCurrentStep > 0) {
|
||||
setHistoryCurrentStep(historyCurrentStep - 1);
|
||||
} else if (event.key === 'y' &&
|
||||
event.ctrlKey &&
|
||||
historyCurrentStep < history.length - 1) {
|
||||
setHistoryCurrentStep(historyCurrentStep + 1);
|
||||
}
|
||||
}
|
180
src/Components/Editor/Actions/SymbolOperations.ts
Normal file
180
src/Components/Editor/Actions/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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue