import React, { Dispatch, SetStateAction } from 'react'; import { IHistoryState } from '../../Interfaces/IHistoryState'; import { IConfiguration } from '../../Interfaces/IConfiguration'; import { ContainerModel, IContainerModel } from '../../Interfaces/IContainerModel'; import { findContainerById } from '../../utils/itertools'; import { getCurrentHistory } from './Editor'; import IProperties from '../../Interfaces/IProperties'; import { AddMethod } from '../../Enums/AddMethod'; import { IAvailableContainer } from '../../Interfaces/IAvailableContainer'; import { transformPosition } from '../SVG/Elements/Container'; /** * Select a container * @param container Selected container */ export function SelectContainer( container: ContainerModel, fullHistory: IHistoryState[], historyCurrentStep: number, setHistory: Dispatch>, setHistoryCurrentStep: Dispatch> ): void { const history = getCurrentHistory(fullHistory, historyCurrentStep); 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({ LastAction: `Select ${selectedContainer.properties.id}`, MainContainer: mainContainerClone, SelectedContainer: selectedContainer, SelectedContainerId: selectedContainer.properties.id, TypeCounters: Object.assign({}, current.TypeCounters) }); 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>, setHistoryCurrentStep: Dispatch> ): void { const history = getCurrentHistory(fullHistory, historyCurrentStep); const current = history[historyCurrentStep]; 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 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 SelectedContainer = findContainerById(mainContainerClone, current.SelectedContainerId) ?? container.parent.children.at(index - 1) ?? container.parent; const SelectedContainerId = SelectedContainer.properties.id; history.push({ LastAction: `Delete ${containerId}`, MainContainer: mainContainerClone, SelectedContainer, SelectedContainerId, TypeCounters: Object.assign({}, current.TypeCounters) }); setHistory(history); setHistoryCurrentStep(history.length - 1); } /** * 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, configuration: IConfiguration, fullHistory: IHistoryState[], historyCurrentStep: number, setHistory: Dispatch>, setHistoryCurrentStep: Dispatch> ): void { const history = getCurrentHistory(fullHistory, historyCurrentStep); const current = history[history.length - 1]; if (current.SelectedContainer === null || current.SelectedContainer === undefined) { return; } const parent = current.SelectedContainer; 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>, setHistoryCurrentStep: Dispatch> ): void { const history = getCurrentHistory(fullHistory, historyCurrentStep); const current = history[history.length - 1]; if (current.MainContainer === null || current.MainContainer === undefined) { return; } // 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); if (newCounters[type] === null || newCounters[type] === undefined) { newCounters[type] = 0; } else { 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; const width = containerConfig.Width ?? parentClone.properties.width; const height = containerConfig.Height ?? parentClone.properties.height; x = ApplyAddMethod(index, containerConfig, parentClone, x); const defaultProperties: IProperties = { ...containerConfig.Style, id: `${type}-${count}`, parentId: parentClone.properties.id, x, y, width, height, isRigidBody: false, isAnchor: false, XPositionReference: containerConfig.XPositionReference }; // Create the container const newContainer = new ContainerModel( parentClone, defaultProperties, [], { type } ); // And push it the the parent children if (index === parentClone.children.length) { parentClone.children.push(newContainer); } else { parentClone.children.splice(index, 0, newContainer); } // Update the state history.push({ LastAction: 'Add container', MainContainer: clone, SelectedContainer: parentClone, SelectedContainerId: parentClone.properties.id, TypeCounters: newCounters }); setHistory(history); setHistoryCurrentStep(history.length - 1); } /** * 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)) { const lastChild: IContainerModel | undefined = parent.children.at(index - 1); if (lastChild !== undefined) { const [transformedX] = transformPosition( lastChild.properties.x, lastChild.properties.y, lastChild.properties.width, lastChild.properties.XPositionReference ); x += transformedX + lastChild.properties.width; } } return x; }