Implement symbols - Add, Remove, Select Container - Form - Link with container - Symbol behavior application to container (move to x with xpositionreference) Important changes - Remove SelectedContainer from HistoryState, meaning that it will be slower for each load but will be faster for each operations* (SetHistory, SelectContainer, DeleteContainer, SymbolOperations) - ElementsSidebar now opens with isSidebarOpen meaning that both sidebar will open on toggle - Moved camelize, transformX, restoreX to different modules (stringtools.ts, svg.ts)
223 lines
7.2 KiB
TypeScript
223 lines
7.2 KiB
TypeScript
import { Dispatch, SetStateAction } from 'react';
|
|
import { IContainerModel, ContainerModel } from '../../Interfaces/IContainerModel';
|
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
|
import { findContainerById } from '../../utils/itertools';
|
|
import { getCurrentHistory } from './Editor';
|
|
import { ApplyBehaviors } from './Behaviors/Behaviors';
|
|
import { restoreX } from '../../utils/svg';
|
|
import { ISymbolModel } from '../../Interfaces/ISymbolModel';
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Handled the property change event in the properties form
|
|
* @param key Property name
|
|
* @param properties Properties of the selected container
|
|
* @returns void
|
|
*/
|
|
export function OnPropertiesSubmit(
|
|
event: React.SyntheticEvent<HTMLFormElement>,
|
|
selected: IContainerModel | undefined,
|
|
fullHistory: IHistoryState[],
|
|
historyCurrentStep: number,
|
|
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
|
setHistoryCurrentStep: Dispatch<SetStateAction<number>>
|
|
): void {
|
|
event.preventDefault();
|
|
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!');
|
|
}
|
|
|
|
// Assign container properties
|
|
const form: HTMLFormElement = event.target as HTMLFormElement;
|
|
for (const property in container.properties) {
|
|
const input: HTMLInputElement | HTMLDivElement | null = form.querySelector(`#${property}`);
|
|
|
|
if (input === null) {
|
|
continue;
|
|
}
|
|
|
|
if (input instanceof HTMLInputElement) {
|
|
submitHTMLInput(input, container, property, form);
|
|
continue;
|
|
}
|
|
|
|
if (input instanceof HTMLDivElement) {
|
|
submitRadioButtons(input, container, property);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Assign cssproperties
|
|
for (const styleProperty in container.properties.style) {
|
|
submitCSSForm(form, styleProperty, container);
|
|
}
|
|
|
|
// Apply the behaviors
|
|
ApplyBehaviors(container, current.Symbols);
|
|
|
|
history.push({
|
|
LastAction: `Change properties 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);
|
|
}
|
|
|
|
const submitHTMLInput = (
|
|
input: HTMLInputElement,
|
|
container: IContainerModel,
|
|
property: string,
|
|
form: HTMLFormElement
|
|
): void => {
|
|
if (input.type !== 'number') {
|
|
(container.properties as any)[property] = input.value;
|
|
return;
|
|
}
|
|
|
|
if (property === 'x') {
|
|
// Hardcoded fix for XPositionReference
|
|
const x = RestoreX(form, input);
|
|
(container.properties as any)[property] = x;
|
|
return;
|
|
}
|
|
|
|
(container.properties as any)[property] = Number(input.value);
|
|
};
|
|
|
|
const submitCSSForm = (form: HTMLFormElement, styleProperty: string, container: ContainerModel): void => {
|
|
const input: HTMLInputElement | null = form.querySelector(`#${styleProperty}`);
|
|
if (input === null) {
|
|
return;
|
|
}
|
|
(container.properties.style as any)[styleProperty] = input.value;
|
|
};
|
|
|
|
const RestoreX = (
|
|
form: HTMLFormElement,
|
|
input: HTMLInputElement
|
|
): number => {
|
|
const inputWidth: HTMLInputElement | null = form.querySelector('#width');
|
|
const inputRadio: HTMLDivElement | null = form.querySelector('#XPositionReference');
|
|
if (inputWidth === null || inputRadio === null) {
|
|
throw new Error('[OnPropertiesSubmit] Missing inputs for width or XPositionReference');
|
|
}
|
|
|
|
const radiobutton: HTMLInputElement | null = inputRadio.querySelector('input[name="XPositionReference"]:checked');
|
|
if (radiobutton === null) {
|
|
throw new Error('[OnPropertiesSubmit] Missing inputs for XPositionReference');
|
|
}
|
|
|
|
return restoreX(Number(input.value), Number(inputWidth.value), Number(radiobutton.value));
|
|
};
|
|
|
|
const submitRadioButtons = (
|
|
div: HTMLDivElement,
|
|
container: IContainerModel,
|
|
property: string
|
|
): void => {
|
|
const radiobutton: HTMLInputElement | null = div.querySelector(`input[name="${property}"]:checked`);
|
|
if (radiobutton === null) {
|
|
return;
|
|
}
|
|
|
|
if (radiobutton.type === 'radio') {
|
|
(container.properties as any)[property] = Number(radiobutton.value);
|
|
return;
|
|
}
|
|
|
|
(container.properties as any)[property] = radiobutton.value;
|
|
};
|
|
|