import Swal from 'sweetalert2'; import { type Dispatch, type SetStateAction } from 'react'; import { AddMethod } from '../../../Enums/AddMethod'; import { type IAction } from '../../../Interfaces/IAction'; import { type IConfiguration } from '../../../Interfaces/IConfiguration'; import { type IContainerModel } from '../../../Interfaces/IContainerModel'; import { type IHistoryState } from '../../../Interfaces/IHistoryState'; import { type ISetContainerListRequest } from '../../../Interfaces/ISetContainerListRequest'; import { type ISetContainerListResponse } from '../../../Interfaces/ISetContainerListResponse'; import { DISABLE_API } from '../../../utils/default'; import { FindContainerById } from '../../../utils/itertools'; import { SetContainerList } from '../../API/api'; import { type IMenuAction } from '../../Menu/Menu'; import { GetCurrentHistoryState } from '../Editor'; import { Text } from '../../Text/Text'; import { type IReplaceContainer } from '../../../Interfaces/IReplaceContainer'; import { AddContainers } from './AddContainer'; import { DeleteContainer } from './ContainerOperations'; import { DeleteSymbol } from './SymbolOperations'; export function InitActions( menuActions: Map, configuration: IConfiguration, history: IHistoryState[], historyCurrentStep: number, setNewHistory: (newHistory: IHistoryState[]) => void, setHistoryCurrentStep: Dispatch>, setIsReplacingContainer: Dispatch> ): void { menuActions.set( '', [ { text: Text({ textId: '@Undo' }), title: Text({ textId: '@UndoTitle' }), shortcut: 'Ctrl+Z', action: () => { if (historyCurrentStep <= 0) { return; } setHistoryCurrentStep(historyCurrentStep - 1); } }, { text: Text({ textId: '@Redo' }), title: Text({ textId: '@RedoTitle' }), shortcut: 'Ctrl+Y', action: () => { if (historyCurrentStep >= history.length - 1) { return; } setHistoryCurrentStep(historyCurrentStep + 1); } } ] ); menuActions.set( 'elements-sidebar-row', [{ text: Text({ textId: '@ReplaceByContainer' }), title: Text({ textId: '@ReplaceByContainerTitle' }), shortcut: 'R', action: (target: HTMLElement) => { const targetContainer = FindContainerById(history[historyCurrentStep].containers, target.id); const targetAvailableContainer = configuration.AvailableContainers.find( (availableContainer) => availableContainer.Type === targetContainer?.properties.type ); if (targetAvailableContainer === undefined) { return; } setIsReplacingContainer({ isReplacing: true, id: target.id, category: targetAvailableContainer.Category }); } }, { text: Text({ textId: '@DeleteContainer' }), title: Text({ textId: '@DeleteContainerTitle' }), shortcut: 'Suppr', action: (target: HTMLElement) => { const id = target.id; const newHistory = DeleteContainer( id, history, historyCurrentStep ); setNewHistory(newHistory); } }] ); menuActions.set( 'symbols-sidebar-row', [{ text: Text({ textId: '@DeleteSymbol' }), title: Text({ textId: '@DeleteSymbolTitle' }), shortcut: 'Suppr', action: (target: HTMLElement) => { const id = target.id; const newHistory = DeleteSymbol( id, history, historyCurrentStep ); setNewHistory(newHistory); } }] ); // API Actions if (DISABLE_API) { return; } for (const availableContainer of configuration.AvailableContainers) { if (availableContainer.Actions === undefined || availableContainer.Actions === null) { continue; } for (const action of availableContainer.Actions) { if (menuActions.get(availableContainer.Type) === undefined) { menuActions.set(availableContainer.Type, []); } const currentState = GetCurrentHistoryState(history, historyCurrentStep); const newAction: IMenuAction = { text: action.Label, title: action.Description, action: GetAction( action, currentState, configuration, history, historyCurrentStep, setNewHistory ) }; menuActions.get(availableContainer.Type)?.push(newAction); } } } function GetAction( action: IAction, currentState: IHistoryState, configuration: IConfiguration, history: IHistoryState[], historyCurrentStep: number, setNewHistory: (newHistory: IHistoryState[]) => void ): (target: HTMLElement) => void { return (target: HTMLElement) => { const id = target.id; const container = FindContainerById(currentState.containers, id); if (container === undefined) { Swal.fire({ title: 'Error', text: 'No container was selected on right click', icon: 'error' }); throw new Error(`[API:${action.Action}] No container was selected`); } /* eslint-disable @typescript-eslint/naming-convention */ const { prev, next } = GetPreviousAndNextSiblings(currentState.containers, container); const request: ISetContainerListRequest = { Container: container, PreviousContainer: prev, NextContainer: next, Action: action, ApplicationState: currentState }; /* eslint-enable */ SetContainerList(request, configuration.APIConfiguration?.apiSetContainerListUrl) .then((response: ISetContainerListResponse) => { HandleSetContainerList( action, container, response, configuration, history, historyCurrentStep, setNewHistory ); }); }; } function GetPreviousAndNextSiblings( containers: Map, container: IContainerModel ): { prev: IContainerModel | undefined, next: IContainerModel | undefined } { let prev; let next; const parent = FindContainerById(containers, container.properties.parentId); if (parent !== undefined && parent !== null && parent.children.length > 1) { const index = parent.children.indexOf(container.properties.id); if (index > 0) { prev = FindContainerById(containers, parent.children[index - 1]); } if (index < parent.children.length - 1) { next = FindContainerById(containers, parent.children[index + 1]); } } return { prev, next }; } function HandleSetContainerList( action: IAction, selectedContainer: IContainerModel, response: ISetContainerListResponse, configuration: IConfiguration, history: IHistoryState[], historyCurrentStep: number, setNewHistory: (newHistory: IHistoryState[]) => void ): void { const addingBehavior = response.AddingBehavior ?? action.AddingBehavior; const current = GetCurrentHistoryState(history, historyCurrentStep); const containers = current.containers; switch (addingBehavior) { case AddMethod.Insert: case AddMethod.Append: { response.Containers.forEach(config => { config.AddMethod = config.AddMethod ?? addingBehavior; }); const { history: newHistory } = AddContainers( selectedContainer.children.length, response.Containers, selectedContainer.properties.id, configuration, history, historyCurrentStep ); setNewHistory(newHistory); break; } case AddMethod.Replace: setNewHistory(HandleReplace( containers, selectedContainer, response, configuration, history, historyCurrentStep )); break; case AddMethod.ReplaceParent: { const parent = FindContainerById(containers, selectedContainer.properties.parentId); if (parent === undefined || parent === null) { Swal.fire({ title: 'Error', text: 'The selected container has not parent to replace', icon: 'error' }); return; } setNewHistory(HandleReplace( containers, parent, response, configuration, history, historyCurrentStep )); break; } } } function HandleReplace( containers: Map, selectedContainer: IContainerModel, response: ISetContainerListResponse, configuration: IConfiguration, history: IHistoryState[], historyCurrentStep: number ): IHistoryState[] { const parent = FindContainerById(containers, selectedContainer.properties.parentId); if (parent === undefined || parent === null) { throw new Error('[ReplaceContainer] Cannot replace a container that does not exists'); } const index = parent.children.indexOf(selectedContainer.properties.id); const newHistoryAfterDelete = DeleteContainer( selectedContainer.properties.id, history, historyCurrentStep ); const { history: newHistoryBeforeDelete } = AddContainers( index, response.Containers, selectedContainer.properties.parentId, configuration, newHistoryAfterDelete, newHistoryAfterDelete.length - 1 ); // Remove AddContainers from history if (import.meta.env.PROD) { newHistoryBeforeDelete.splice(newHistoryBeforeDelete.length - 2, 1); } // Rename the last action by Replace const types = response.Containers.map(container => container.Type); newHistoryBeforeDelete[newHistoryBeforeDelete.length - 1].lastAction = `Replace ${selectedContainer.properties.id} by [${types.join(', ')}]`; return newHistoryBeforeDelete; }