svg-layout-designer-react/src/Components/Editor/Actions/ContextMenuActions.ts
2023-02-23 13:54:38 +01:00

318 lines
9.6 KiB
TypeScript

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<string, IMenuAction[]>,
configuration: IConfiguration,
history: IHistoryState[],
historyCurrentStep: number,
setNewHistory: (newHistory: IHistoryState[]) => void,
setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
setIsReplacingContainer: Dispatch<SetStateAction<IReplaceContainer>>
): void {
menuActions.set(
'',
[
{
text: Text({ textId: '@Undo' }),
title: Text({ textId: '@UndoTitle' }),
shortcut: '<kbd>Ctrl</kbd>+<kbd>Z</kbd>',
action: () => {
if (historyCurrentStep <= 0) {
return;
}
setHistoryCurrentStep(historyCurrentStep - 1);
}
},
{
text: Text({ textId: '@Redo' }),
title: Text({ textId: '@RedoTitle' }),
shortcut: '<kbd>Ctrl</kbd>+<kbd>Y</kbd>',
action: () => {
if (historyCurrentStep >= history.length - 1) {
return;
}
setHistoryCurrentStep(historyCurrentStep + 1);
}
}
]
);
menuActions.set(
'elements-sidebar-row',
[{
text: Text({ textId: '@ReplaceByContainer' }),
title: Text({ textId: '@ReplaceByContainerTitle' }),
shortcut: '<kbd>R</kbd>',
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: '<kbd>Suppr</kbd>',
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: '<kbd>Suppr</kbd>',
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<string, IContainerModel>,
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<string, IContainerModel>,
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;
}