import React, { Dispatch, SetStateAction, useEffect, useRef } from 'react'; import './Editor.scss'; import { IConfiguration } from '../../Interfaces/IConfiguration'; import { IHistoryState } from '../../Interfaces/IHistoryState'; import { UI } from '../UI/UI'; import { SelectContainer, DeleteContainer, OnPropertyChange, ReplaceByContainer } from './Actions/ContainerOperations'; import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save'; import { OnKey } from './Actions/Shortcuts'; import { UseCustomEvents, UseEditorListener } from '../../Events/EditorEvents'; import { MAX_HISTORY } from '../../utils/default'; import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, SelectSymbol } from './Actions/SymbolOperations'; import { FindContainerById } from '../../utils/itertools'; import { Menu } from '../Menu/Menu'; import { InitActions } from './Actions/ContextMenuActions'; import { AddContainerToSelectedContainer, AddContainer } from './Actions/AddContainer'; import { IReplaceContainer } from '../../Interfaces/IReplaceContainer'; interface IEditorProps { root: Element | Document configuration: IConfiguration history: IHistoryState[] historyCurrentStep: number } function UseShortcuts( history: IHistoryState[], historyCurrentStep: number, setHistoryCurrentStep: Dispatch>, deleteAction: () => void ): void { useEffect(() => { function OnKeyUp(event: KeyboardEvent): void { return OnKey( event, history, historyCurrentStep, setHistoryCurrentStep, deleteAction ); } window.addEventListener('keyup', OnKeyUp); return () => { window.removeEventListener('keyup', OnKeyUp); }; }); } /** * Return a macro function to use both setHistory * and setHistoryCurrentStep at the same time * @param setHistory * @param setHistoryCurrentStep * @returns */ function UseNewHistoryState( setHistory: Dispatch>, setHistoryCurrentStep: Dispatch> ): (newHistory: IHistoryState[], historyCurrentStep?: number) => void { return (newHistory, historyCurrentStep?: number) => { setHistory(newHistory); setHistoryCurrentStep(historyCurrentStep !== undefined && historyCurrentStep !== null ? historyCurrentStep : newHistory.length - 1); }; } export function Editor(props: IEditorProps): JSX.Element { // States const [history, setHistory] = React.useState(structuredClone(props.history)); const [historyCurrentStep, setHistoryCurrentStep] = React.useState(props.historyCurrentStep); const [replaceContainer, setReplaceContainer] = React.useState({ isReplacing: false, id: undefined, category: undefined }); const editorRef = useRef(null); const setNewHistory = UseNewHistoryState(setHistory, setHistoryCurrentStep); // Events UseShortcuts( history, historyCurrentStep, setHistoryCurrentStep, () => { const current = GetCurrentHistoryState(history, historyCurrentStep); setNewHistory( DeleteContainer(current.selectedContainerId, history, historyCurrentStep) ); } ); UseCustomEvents( props.root, history, historyCurrentStep, props.configuration, editorRef, setNewHistory ); UseEditorListener( props.root, history, historyCurrentStep, props.configuration ); // Context Menu const menuActions = new Map(); InitActions( menuActions, props.configuration, history, historyCurrentStep, setNewHistory, setHistoryCurrentStep, setReplaceContainer ); // Render const configuration = props.configuration; const current = GetCurrentHistoryState(history, historyCurrentStep); const selected = FindContainerById(current.containers, current.selectedContainerId); return (
setNewHistory( SelectContainer( container, history, historyCurrentStep ))} deleteContainer={(containerId: string) => setNewHistory( DeleteContainer( containerId, history, historyCurrentStep ))} onPropertyChange={(key, value, type) => setNewHistory( OnPropertyChange( key, value, type, selected, history, historyCurrentStep ))} addOrReplaceContainer={(type) => { if (selected === null || selected === undefined) { return; } if (replaceContainer.isReplacing && replaceContainer.id !== undefined) { const newHistory = ReplaceByContainer( replaceContainer.id, type, configuration, history, historyCurrentStep ); setReplaceContainer({ isReplacing: false, id: undefined, category: undefined }); setNewHistory(newHistory); } else { setNewHistory(AddContainerToSelectedContainer( type, selected, configuration, history, historyCurrentStep )); } }} addContainerAt={(index, type, parent) => setNewHistory( AddContainer( index, type, parent, configuration, history, historyCurrentStep ) )} addSymbol={(type) => setNewHistory( AddSymbol( type, configuration, history, historyCurrentStep ))} onSymbolPropertyChange={(key, value) => setNewHistory( OnSymbolPropertyChange( key, value, history, historyCurrentStep ))} selectSymbol={(symbolId) => setNewHistory( SelectSymbol( symbolId, history, historyCurrentStep ))} deleteSymbol={(symbolId) => setNewHistory( DeleteSymbol( symbolId, history, historyCurrentStep ))} saveEditorAsJSON={() => SaveEditorAsJSON( history, historyCurrentStep, configuration )} saveEditorAsSVG={() => SaveEditorAsSVG()} loadState={(move) => setHistoryCurrentStep(move)} setReplaceContainer={setReplaceContainer} /> editorRef.current} configuration={configuration} actions={menuActions} className="z-30 transition-opacity rounded bg-slate-200 drop-shadow-xl" />
); } export function UpdateCounters(counters: Record, type: string): void { if (counters[type] === null || counters[type] === undefined) { counters[type] = 0; } else { counters[type]++; } } export function GetCurrentHistory(history: IHistoryState[], historyCurrentStep: number): IHistoryState[] { return history.slice( Math.max(0, history.length - MAX_HISTORY), historyCurrentStep + 1 ); } export function GetCurrentHistoryState(history: IHistoryState[], historyCurrentStep: number): IHistoryState { return history[historyCurrentStep]; }