Merged PR 392: Events: Fix needing to use setTimeout for callbacks

This commit is contained in:
Eric Nguyen 2023-03-01 09:53:06 +00:00
parent 6792d5e105
commit 460669987d
8 changed files with 182 additions and 88 deletions

View file

@ -36,6 +36,7 @@ module.exports = {
'max-depth': ['error', 4], 'max-depth': ['error', 4],
'function-paren-newline': ['error', 'multiline-arguments'], 'function-paren-newline': ['error', 'multiline-arguments'],
'multiline-ternary': ['error', 'always'], 'multiline-ternary': ['error', 'always'],
'prefer-template': "error",
// Import/export // Import/export
'import/no-default-export': 'error', 'import/no-default-export': 'error',

View file

@ -137,14 +137,10 @@
*/ */
public LoadEditor(editorState: IEditorState) { public LoadEditor(editorState: IEditorState) {
this.ReviveEditorState(editorState, (state) => { this.ReviveEditorState(editorState, (state) => {
this.SetEditor(state, (currentState) => { this.SetEditor(state, () => {
setTimeout(() => { this.app.App.SetAppState(2, () => {
this.app.App.SetAppState(2, () => { this.app.Editor.SetHistory({ history: state.history, historyCurrentStep: state.historyCurrentStep });
setTimeout(() => { });
this.app.Editor.SetHistory({ history: state.history, historyCurrentStep: state.historyCurrentStep });
}, 200);
});
}, 200);
}); });
}); });
} }

View file

@ -15,7 +15,7 @@ import { FetchConfiguration } from './api';
const CSHARP_WEB_API_BASE_URL = 'http://localhost:5209/'; const CSHARP_WEB_API_BASE_URL = 'http://localhost:5209/';
const CHARP_WEB_API_RESOURCE_URL = 'SVGLD'; const CHARP_WEB_API_RESOURCE_URL = 'SVGLD';
const CSHARP_WEB_API_URL = CSHARP_WEB_API_BASE_URL + CHARP_WEB_API_RESOURCE_URL + '/'; const CSHARP_WEB_API_URL = `${CSHARP_WEB_API_BASE_URL + CHARP_WEB_API_RESOURCE_URL}/`;
// TODO: Migrate this test to SVGLDWebAPI rather than using test-server/ // TODO: Migrate this test to SVGLDWebAPI rather than using test-server/
describe.concurrent('Test server test', () => { describe.concurrent('Test server test', () => {

View file

@ -47,6 +47,7 @@ function UseHTTPGETStatePreloading(
export function App(props: IAppProps): JSX.Element { export function App(props: IAppProps): JSX.Element {
const [appState, setAppState] = useState<AppState>(FAST_BOOT); const [appState, setAppState] = useState<AppState>(FAST_BOOT);
const appRef = useRef<HTMLDivElement>(null); const appRef = useRef<HTMLDivElement>(null);
const eventCallbackQueue = useRef<CustomEvent[]>([]);
const languageContext = useContext(LanguageContext); const languageContext = useContext(LanguageContext);
const defaultMainContainer = new ContainerModel(DEFAULT_MAINCONTAINER_PROPS); const defaultMainContainer = new ContainerModel(DEFAULT_MAINCONTAINER_PROPS);
@ -66,6 +67,7 @@ export function App(props: IAppProps): JSX.Element {
}); });
UseCustomEvents( UseCustomEvents(
eventCallbackQueue,
props.root, props.root,
appRef, appRef,
languageContext, languageContext,

View file

@ -75,10 +75,10 @@ export function SaveEditorAsSVG(): void {
} }
// add xml declaration // add xml declaration
source = '<?xml version="1.0" standalone="no"?>\r\n' + source; source = `<?xml version="1.0" standalone="no"?>\r\n${source}`;
// convert svg source to URI data scheme. // convert svg source to URI data scheme.
const url = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source); const url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`;
CreateDownloadNode('state.svg', url); CreateDownloadNode('state.svg', url);
} }

View file

@ -83,6 +83,7 @@ export function Editor(props: IEditorProps): JSX.Element {
const [selectedSymbolId, setSelectedSymbolId] = React.useState(''); const [selectedSymbolId, setSelectedSymbolId] = React.useState('');
const editorRef = useRef<HTMLDivElement>(null); const editorRef = useRef<HTMLDivElement>(null);
const eventCallbackQueue = useRef<CustomEvent[]>([]);
const setNewHistory = UseNewHistoryState(setHistory, setHistoryCurrentStep); const setNewHistory = UseNewHistoryState(setHistory, setHistoryCurrentStep);
function ResetState(): void { function ResetState(): void {
@ -100,6 +101,7 @@ export function Editor(props: IEditorProps): JSX.Element {
ResetState ResetState
); );
UseCustomEvents( UseCustomEvents(
eventCallbackQueue,
props.root, props.root,
history, history,
historyCurrentStep, historyCurrentStep,

View file

@ -9,16 +9,20 @@ import { GetDefaultEditorState as GetDefaultEditorStateAction } from '../utils/d
import { Revive, ReviveHistory as ReviveHistoryAction } from '../utils/saveload'; import { Revive, ReviveHistory as ReviveHistoryAction } from '../utils/saveload';
interface IAppEventParams { interface IAppEventParams {
root: Element | Document
languageContext: ILanguage languageContext: ILanguage
setEditor: (newState: IEditorState) => void setEditor: (newState: IEditorState) => void
setAppState: (appState: AppState) => void setAppState: (appState: AppState) => void
eventInitDict?: CustomEventInit eventInitDict?: CustomEventInit
} }
export interface WrappedCustomEvent {
event: CustomEvent
runAfterRender: boolean
}
export interface IAppEvent { export interface IAppEvent {
name: string name: string
func: (params: IAppEventParams) => void func: (params: IAppEventParams) => WrappedCustomEvent
} }
export const events: IAppEvent[] = [ export const events: IAppEvent[] = [
@ -33,6 +37,7 @@ export const events: IAppEvent[] = [
]; ];
export function UseCustomEvents( export function UseCustomEvents(
callbackQueue: React.RefObject<CustomEvent[]>,
root: Element | Document, root: Element | Document,
appRef: React.RefObject<HTMLDivElement>, appRef: React.RefObject<HTMLDivElement>,
languageContext: ILanguage, languageContext: ILanguage,
@ -49,13 +54,21 @@ export function UseCustomEvents(
const funcs = new Map<string, () => void>(); const funcs = new Map<string, () => void>();
for (const event of events) { for (const event of events) {
function Func(eventInitDict?: CustomEventInit): void { function Func(eventInitDict?: CustomEventInit): void {
event.func({ console.debug(`RUN INITIAL FUNCTION: ${event.name}`);
root, const customEvent = event.func({
languageContext, languageContext,
setEditor, setEditor,
setAppState, setAppState,
eventInitDict eventInitDict
}); });
if (customEvent.runAfterRender) {
console.debug(`ADDING CALLBACK TO QUEUE: ${customEvent.event.type}`);
callbackQueue.current?.push(customEvent.event);
} else {
console.debug(`EXECUTING CALLBACK IMMEDIATELY: ${customEvent.event.type}`);
root.dispatchEvent(customEvent.event);
}
} }
current.addEventListener(event.name, Func); current.addEventListener(event.name, Func);
funcs.set(event.name, Func); funcs.set(event.name, Func);
@ -70,89 +83,127 @@ export function UseCustomEvents(
} }
}; };
}); });
useEffect(() => {
if (callbackQueue.current === null) {
return;
}
if (callbackQueue.current.length > 0) {
const callback = callbackQueue.current.shift();
if (callback === undefined) {
return;
}
console.debug(`RUNNING CALLBACK FROM QUEUE: ${callback.type}`);
root.dispatchEvent(callback);
}
});
} }
function SetEditor({ function SetEditor({
root,
setEditor, setEditor,
setAppState, setAppState,
eventInitDict eventInitDict
}: IAppEventParams): void { }: IAppEventParams): WrappedCustomEvent {
const editor: IEditorState = eventInitDict?.detail; const editor: IEditorState = eventInitDict?.detail;
setEditor(editor); setEditor(editor);
setAppState(AppState.Loading); setAppState(AppState.Loading);
const customEvent = new CustomEvent<IEditorState>('setEditor', { detail: editor }); const event = new CustomEvent<IEditorState>('setEditor', { detail: editor });
root.dispatchEvent(customEvent); return {
event,
runAfterRender: true
};
} }
function SetAppState({ function SetAppState({
setAppState, setAppState,
eventInitDict eventInitDict
}: IAppEventParams): void { }: IAppEventParams): WrappedCustomEvent {
const appState: AppState = eventInitDict?.detail; const appState: AppState = eventInitDict?.detail;
setAppState(appState); setAppState(appState);
return {
event: new CustomEvent('setAppState'),
runAfterRender: true
};
} }
function ReviveEditorState({ function ReviveEditorState({
root,
eventInitDict eventInitDict
}: IAppEventParams): void { }: IAppEventParams): WrappedCustomEvent {
const anEditorState: IEditorState = eventInitDict?.detail; const anEditorState: IEditorState = eventInitDict?.detail;
Revive(anEditorState); Revive(anEditorState);
const customEvent = new CustomEvent<IEditorState>('reviveEditorState', { detail: anEditorState }); const event = new CustomEvent<IEditorState>('reviveEditorState', { detail: anEditorState });
root.dispatchEvent(customEvent); return {
event,
runAfterRender: false
};
} }
function ReviveHistory({ function ReviveHistory({
root,
eventInitDict eventInitDict
}: IAppEventParams): void { }: IAppEventParams): WrappedCustomEvent {
const history: IHistoryState[] = eventInitDict?.detail; const history: IHistoryState[] = eventInitDict?.detail;
ReviveHistoryAction(history); ReviveHistoryAction(history);
const customEvent = new CustomEvent<IHistoryState[]>('reviveHistory', { detail: history }); const event = new CustomEvent<IHistoryState[]>('reviveHistory', { detail: history });
root.dispatchEvent(customEvent); return {
event,
runAfterRender: false
};
} }
function GetDefaultEditorState({ function GetDefaultEditorState({
root,
eventInitDict eventInitDict
}: IAppEventParams): void { }: IAppEventParams): WrappedCustomEvent {
const configuration: IConfiguration = eventInitDict?.detail; const configuration: IConfiguration = eventInitDict?.detail;
const editorState = GetDefaultEditorStateAction(configuration); const editorState = GetDefaultEditorStateAction(configuration);
const customEvent = new CustomEvent<IEditorState>('getDefaultEditorState', { detail: editorState }); const event = new CustomEvent<IEditorState>('getDefaultEditorState', { detail: editorState });
root.dispatchEvent(customEvent); return {
event,
runAfterRender: false
};
} }
function AddLanguage({ function AddLanguage({
root,
eventInitDict eventInitDict
}: IAppEventParams): void { }: IAppEventParams): WrappedCustomEvent {
const language: ILanguage = eventInitDict?.detail.language; const language: ILanguage = eventInitDict?.detail.language;
const option: string = eventInitDict?.detail.option; const option: string = eventInitDict?.detail.option;
languageOptions[language.language] = option; languageOptions[language.language] = option;
translations[language.language] = language.dictionary; translations[language.language] = language.dictionary;
const customEvent = new CustomEvent('addLanguage'); const event = new CustomEvent('addLanguage');
root.dispatchEvent(customEvent);
return {
event,
runAfterRender: true
};
} }
function SetLanguage({ function SetLanguage({
root,
languageContext, languageContext,
eventInitDict eventInitDict
}: IAppEventParams): void { }: IAppEventParams): WrappedCustomEvent {
const language: string = eventInitDict?.detail; const language: string = eventInitDict?.detail;
let success = false; let success = false;
if (languageContext.languageChange !== undefined) { if (languageContext.languageChange !== undefined) {
languageContext.languageChange(language); languageContext.languageChange(language);
success = true; success = true;
} }
const customEvent = new CustomEvent<boolean>('setLanguage', { detail: success }); const event = new CustomEvent<boolean>('setLanguage', { detail: success });
root.dispatchEvent(customEvent);
return {
event,
runAfterRender: true
};
} }
function GetLanguages({ function GetLanguages(): WrappedCustomEvent {
root
}: IAppEventParams): void {
const customEvent = new CustomEvent<Record<string, Record<string, string>>>('getLanguages', { detail: translations }); const customEvent = new CustomEvent<Record<string, Record<string, string>>>('getLanguages', { detail: translations });
root.dispatchEvent(customEvent); return {
event: customEvent,
runAfterRender: false
};
} }

View file

@ -15,16 +15,16 @@ import { type IEditorState } from '../Interfaces/IEditorState';
import { type IHistoryState } from '../Interfaces/IHistoryState'; import { type IHistoryState } from '../Interfaces/IHistoryState';
import { FindContainerById } from '../utils/itertools'; import { FindContainerById } from '../utils/itertools';
import { GetCircularReplacer } from '../utils/saveload'; import { GetCircularReplacer } from '../utils/saveload';
import { type WrappedCustomEvent } from './AppEvents';
interface IEditorEventParams { interface IEditorEventParams {
root: Element | Document
editorState: IEditorState editorState: IEditorState
setNewHistory: (newHistory: IHistoryState[], historyCurrentStep?: number) => void setNewHistory: (newHistory: IHistoryState[], historyCurrentStep?: number) => void
eventInitDict?: CustomEventInit eventInitDict?: CustomEventInit
} }
export interface IEditorEvent { export interface IEditorEvent {
name: string name: string
func: (params: IEditorEventParams) => void func: (params: IEditorEventParams) => WrappedCustomEvent
} }
export const events: IEditorEvent[] = [ export const events: IEditorEvent[] = [
@ -41,6 +41,7 @@ export const events: IEditorEvent[] = [
]; ];
export function UseCustomEvents( export function UseCustomEvents(
callbackQueue: React.RefObject<CustomEvent[]>,
root: Element | Document, root: Element | Document,
history: IHistoryState[], history: IHistoryState[],
historyCurrentStep: number, historyCurrentStep: number,
@ -64,12 +65,17 @@ export function UseCustomEvents(
for (const event of events) { for (const event of events) {
function Func(eventInitDict?: CustomEventInit): void { function Func(eventInitDict?: CustomEventInit): void {
event.func({ const customEvent = event.func({
root,
editorState, editorState,
setNewHistory, setNewHistory,
eventInitDict eventInitDict
}); });
if (customEvent.runAfterRender) {
callbackQueue.current?.push(customEvent.event);
} else {
root.dispatchEvent(customEvent.event);
}
} }
current?.addEventListener(event.name, Func); current?.addEventListener(event.name, Func);
funcs.set(event.name, Func); funcs.set(event.name, Func);
@ -84,6 +90,22 @@ export function UseCustomEvents(
} }
}; };
}); });
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( export function UseEditorListener(
@ -104,74 +126,83 @@ export function UseEditorListener(
} }
function GetEditorState({ function GetEditorState({
root,
editorState editorState
}: IEditorEventParams): void { }: IEditorEventParams): WrappedCustomEvent {
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: structuredClone(editorState) }); const event = new CustomEvent<IEditorState>('getEditorState', { detail: structuredClone(editorState) });
root.dispatchEvent(customEvent); return {
event,
runAfterRender: false
};
} }
function GetEditorStateAsString({ function GetEditorStateAsString({
root,
editorState editorState
}: IEditorEventParams): void { }: IEditorEventParams): WrappedCustomEvent {
const spaces = import.meta.env.DEV const spaces = import.meta.env.DEV
? 4 ? 4
: 0; : 0;
const data = JSON.stringify(editorState, GetCircularReplacer(), spaces); const data = JSON.stringify(editorState, GetCircularReplacer(), spaces);
const customEvent = new CustomEvent<string>('getEditorStateAsString', { detail: data }); const event = new CustomEvent<string>('getEditorStateAsString', { detail: data });
root.dispatchEvent(customEvent); return {
event,
runAfterRender: false
};
} }
function SetHistory({ function SetHistory({
root,
editorState, editorState,
setNewHistory, setNewHistory,
eventInitDict eventInitDict
}: IEditorEventParams): void { }: IEditorEventParams): WrappedCustomEvent {
const history: IHistoryState[] = eventInitDict?.detail.history; const history: IHistoryState[] = eventInitDict?.detail.history;
const historyCurrentStep: number | undefined = eventInitDict?.detail.historyCurrentStep; const historyCurrentStep: number | undefined = eventInitDict?.detail.historyCurrentStep;
setNewHistory(history, historyCurrentStep); setNewHistory(history, historyCurrentStep);
const customEvent = new CustomEvent<IEditorState>('setHistory', { detail: editorState }); const event = new CustomEvent<IEditorState>('setHistory', { detail: editorState });
root.dispatchEvent(customEvent); return {
event,
runAfterRender: true
};
} }
function GetCurrentHistoryState({ function GetCurrentHistoryState({
root,
editorState editorState
}: IEditorEventParams): void { }: IEditorEventParams): WrappedCustomEvent {
const customEvent = new CustomEvent<IHistoryState>( const event = new CustomEvent<IHistoryState>(
'getCurrentHistoryState', 'getCurrentHistoryState',
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) }
); );
root.dispatchEvent(customEvent); return {
event,
runAfterRender: true
};
} }
function AppendNewState({ function AppendNewState({
root,
editorState, editorState,
setNewHistory, setNewHistory,
eventInitDict eventInitDict
}: IEditorEventParams): void { }: IEditorEventParams): WrappedCustomEvent {
const state: IHistoryState = eventInitDict?.detail.state; const state: IHistoryState = eventInitDict?.detail.state;
const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep); const history = GetCurrentHistory(editorState.history, editorState.historyCurrentStep);
history.push(state); history.push(state);
setNewHistory(history); setNewHistory(history);
const customEvent = new CustomEvent<IHistoryState>( const event = new CustomEvent<IHistoryState>(
'appendNewState', 'appendNewState',
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) }
); );
root.dispatchEvent(customEvent); return {
event,
runAfterRender: true
};
} }
function AddContainer({ function AddContainer({
root,
editorState, editorState,
setNewHistory, setNewHistory,
eventInitDict eventInitDict
}: IEditorEventParams): void { }: IEditorEventParams): WrappedCustomEvent {
const { const {
index, index,
type, type,
@ -189,19 +220,21 @@ function AddContainer({
); );
setNewHistory(newHistory); setNewHistory(newHistory);
const customEvent = new CustomEvent<IHistoryState>( const event = new CustomEvent<IHistoryState>(
'addContainer', 'addContainer',
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) }
); );
root.dispatchEvent(customEvent); return {
event,
runAfterRender: true
};
} }
function AppendContainer({ function AppendContainer({
root,
editorState, editorState,
setNewHistory, setNewHistory,
eventInitDict eventInitDict
}: IEditorEventParams): void { }: IEditorEventParams): WrappedCustomEvent {
const { const {
type, type,
parentId parentId
@ -222,19 +255,21 @@ function AppendContainer({
); );
setNewHistory(newHistory); setNewHistory(newHistory);
const customEvent = new CustomEvent<IHistoryState>( const event = new CustomEvent<IHistoryState>(
'appendContainerToSelectedContainer', 'appendContainerToSelectedContainer',
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) }
); );
root.dispatchEvent(customEvent); return {
event,
runAfterRender: true
};
} }
function DeleteContainer({ function DeleteContainer({
root,
editorState, editorState,
setNewHistory, setNewHistory,
eventInitDict eventInitDict
}: IEditorEventParams): void { }: IEditorEventParams): WrappedCustomEvent {
const { const {
containerId containerId
} = eventInitDict?.detail; } = eventInitDict?.detail;
@ -248,19 +283,21 @@ function DeleteContainer({
); );
setNewHistory(newHistory); setNewHistory(newHistory);
const customEvent = new CustomEvent<IHistoryState>( const event = new CustomEvent<IHistoryState>(
'deleteContainer', 'deleteContainer',
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) }
); );
root.dispatchEvent(customEvent); return {
event,
runAfterRender: true
};
} }
function AddSymbol({ function AddSymbol({
root,
editorState, editorState,
setNewHistory, setNewHistory,
eventInitDict eventInitDict
}: IEditorEventParams): void { }: IEditorEventParams): WrappedCustomEvent {
const { const {
name name
} = eventInitDict?.detail; } = eventInitDict?.detail;
@ -275,19 +312,21 @@ function AddSymbol({
); );
setNewHistory(newHistory); setNewHistory(newHistory);
const customEvent = new CustomEvent<IHistoryState>( const event = new CustomEvent<IHistoryState>(
'AddSymbol', 'AddSymbol',
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) }
); );
root.dispatchEvent(customEvent); return {
event,
runAfterRender: true
};
} }
function DeleteSymbol({ function DeleteSymbol({
root,
editorState, editorState,
setNewHistory, setNewHistory,
eventInitDict eventInitDict
}: IEditorEventParams): void { }: IEditorEventParams): WrappedCustomEvent {
const { const {
symbolId symbolId
} = eventInitDict?.detail; } = eventInitDict?.detail;
@ -300,9 +339,12 @@ function DeleteSymbol({
); );
setNewHistory(newHistory); setNewHistory(newHistory);
const customEvent = new CustomEvent<IHistoryState>( const event = new CustomEvent<IHistoryState>(
'DeleteSymbol', 'DeleteSymbol',
{ detail: structuredClone(editorState.history[editorState.historyCurrentStep]) } { detail: structuredClone(editorState.history[editorState.historyCurrentStep]) }
); );
root.dispatchEvent(customEvent); return {
event,
runAfterRender: true
};
} }