import { useEffect } from 'react'; import { AddContainer as AddContainerAction } from '../Components/Editor/Actions/AddContainer'; import { DeleteContainer as DeleteContainerAction } from '../Components/Editor/Actions/ContainerOperations'; import { AddSymbol as AddSymbolAction, DeleteSymbol as DeleteSymbolAction } from '../Components/Editor/Actions/SymbolOperations'; import { GetCurrentHistory } from '../Components/Editor/Editor'; import { type IConfiguration } from '../Interfaces/IConfiguration'; import { type IEditorState } from '../Interfaces/IEditorState'; import { type IHistoryState } from '../Interfaces/IHistoryState'; import { FindContainerById } from '../utils/itertools'; import { GetCircularReplacer } from '../utils/saveload'; import { type WrappedCustomEvent } from './AppEvents'; interface IEditorEventParams { editorState: IEditorState setNewHistory: (newHistory: IHistoryState[], historyCurrentStep?: number) => void eventInitDict?: CustomEventInit } export interface IEditorEvent { name: string func: (params: IEditorEventParams) => WrappedCustomEvent } export const events: IEditorEvent[] = [ { name: 'getEditorState', func: GetEditorState }, { name: 'getEditorStateAsString', func: GetEditorStateAsString }, { name: 'setHistory', func: SetHistory }, { name: 'getCurrentHistoryState', func: GetCurrentHistoryState }, { name: 'appendNewState', func: AppendNewState }, { name: 'addContainer', func: AddContainer }, { name: 'appendContainer', func: AppendContainer }, { name: 'deleteContainer', func: DeleteContainer }, { name: 'addSymbol', func: AddSymbol }, { name: 'deleteSymbol', func: DeleteSymbol } ]; export function UseCustomEvents( callbackQueue: React.RefObject, root: Element | Document, editorState: IEditorState, editorRef: React.RefObject, setNewHistory: (newHistory: IHistoryState[], historyCurrentStep?: number) => void ): void { useEffect(() => { const current = editorRef.current; if (current === null) { return; } const funcs = new Map void>(); for (const event of events) { function Func(eventInitDict?: CustomEventInit): void { const customEvent = event.func({ editorState, setNewHistory, eventInitDict }); if (customEvent.runAfterRender) { callbackQueue.current?.push(customEvent.event); } else { root.dispatchEvent(customEvent.event); } } current?.addEventListener(event.name, Func); funcs.set(event.name, Func); } return () => { for (const event of events) { const func = funcs.get(event.name); if (func === undefined) { continue; } current?.removeEventListener(event.name, func); } }; }); useEffect(() => { if (callbackQueue.current === null) { return; } while (callbackQueue.current.length > 0) { const callback = callbackQueue.current.shift(); if (callback === undefined) { continue; } root.dispatchEvent(callback); } }); } export function UseEditorListener( root: Element | Document, editorState: IEditorState ): void { useEffect(() => { const event = new CustomEvent('editorListener', { detail: editorState }); root.dispatchEvent(event); }); } function GetEditorState({ editorState }: IEditorEventParams): WrappedCustomEvent { const event = new CustomEvent('getEditorState', { detail: structuredClone(editorState) }); return { event, runAfterRender: false }; } function GetEditorStateAsString({ editorState }: IEditorEventParams): WrappedCustomEvent { const spaces = import.meta.env.DEV ? 4 : 0; const data = JSON.stringify(editorState, GetCircularReplacer(), spaces); const event = new CustomEvent('getEditorStateAsString', { detail: data }); return { event, runAfterRender: false }; } function SetHistory({ editorState, setNewHistory, eventInitDict }: IEditorEventParams): WrappedCustomEvent { const history: IHistoryState[] = eventInitDict?.detail.history; const historyCurrentStep: number | undefined = eventInitDict?.detail.historyCurrentStep; setNewHistory(history, historyCurrentStep); const event = new CustomEvent('setHistory', { detail: editorState }); return { event, runAfterRender: true }; } function GetCurrentHistoryState({ editorState }: IEditorEventParams): WrappedCustomEvent { const event = new CustomEvent( 'getCurrentHistoryState', { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } ); return { event, runAfterRender: true }; } function AppendNewState({ editorState, setNewHistory, eventInitDict }: IEditorEventParams): WrappedCustomEvent { const state: IHistoryState = eventInitDict?.detail.state; const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep); history.push(state); setNewHistory(history); const event = new CustomEvent( 'appendNewState', { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } ); return { event, runAfterRender: true }; } function AddContainer({ editorState, setNewHistory, eventInitDict }: IEditorEventParams): WrappedCustomEvent { const { index, type, parentId } = eventInitDict?.detail; const newHistory = AddContainerAction( index, type, parentId, editorState ); setNewHistory(newHistory); const event = new CustomEvent( 'addContainer', { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } ); return { event, runAfterRender: true }; } function AppendContainer({ editorState, setNewHistory, eventInitDict }: IEditorEventParams): WrappedCustomEvent { const { type, parentId } = eventInitDict?.detail; const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep); const currentState = history[editorState.historyCurrentStep]; const parent = FindContainerById(currentState.containers, parentId); const newHistory = AddContainerAction( parent?.children.length ?? 0, type, parentId, editorState ); setNewHistory(newHistory); const event = new CustomEvent( 'appendContainerToSelectedContainer', { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } ); return { event, runAfterRender: true }; } function DeleteContainer({ editorState, setNewHistory, eventInitDict }: IEditorEventParams): WrappedCustomEvent { const { containerId } = eventInitDict?.detail; const newHistory = DeleteContainerAction( containerId, editorState ); setNewHistory(newHistory); const event = new CustomEvent( 'deleteContainer', { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } ); return { event, runAfterRender: true }; } function AddSymbol({ editorState, setNewHistory, eventInitDict }: IEditorEventParams): WrappedCustomEvent { const { name } = eventInitDict?.detail; const newHistory = AddSymbolAction( name, editorState ); setNewHistory(newHistory); const event = new CustomEvent( 'AddSymbol', { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } ); return { event, runAfterRender: true }; } function DeleteSymbol({ editorState, setNewHistory, eventInitDict }: IEditorEventParams): WrappedCustomEvent { const { symbolId } = eventInitDict?.detail; const newHistory = DeleteSymbolAction( symbolId, editorState ); setNewHistory(newHistory); const event = new CustomEvent( 'DeleteSymbol', { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } ); return { event, runAfterRender: true }; }