From 20735165287599a0af26319278ad9f69f62c9a65 Mon Sep 17 00:00:00 2001 From: Ruben Thoms Date: Tue, 3 Oct 2023 11:44:21 +0200 Subject: [PATCH] Renaming, refactoring, and using `useSyncExternalStore` hook from React --- frontend/src/framework/GuiMessageBroker.ts | 68 ++++++++++--------- .../ViewWrapper/viewWrapper.tsx | 4 +- .../Content/private-components/layout.tsx | 25 ++++--- .../private-components/modulesList.tsx | 2 +- 4 files changed, 55 insertions(+), 44 deletions(-) diff --git a/frontend/src/framework/GuiMessageBroker.ts b/frontend/src/framework/GuiMessageBroker.ts index cae841af3..2f232952a 100644 --- a/frontend/src/framework/GuiMessageBroker.ts +++ b/frontend/src/framework/GuiMessageBroker.ts @@ -84,40 +84,36 @@ export class GuiMessageBroker { } } - addEventListener(event: K, listener: (event: GuiEventPayloads[K]) => void) { + subscribeToEvent(event: K, callback: (event: GuiEventPayloads[K]) => void) { const eventListeners = this._eventListeners.get(event) || new Set(); - eventListeners.add(listener); + eventListeners.add(callback); this._eventListeners.set(event, eventListeners); - } - removeEventListener(event: K, listener: (event: GuiEventPayloads[K]) => void) { - const eventListeners = this._eventListeners.get(event); - if (eventListeners) { - eventListeners.delete(listener); - } + return () => { + eventListeners.delete(callback); + }; } - dispatchEvent(event: K, details: GuiEventPayloads[K]) { - console.debug("dispatching event", event, details); + publishEvent(event: K, details: GuiEventPayloads[K]) { const eventListeners = this._eventListeners.get(event); if (eventListeners) { - eventListeners.forEach((listener) => listener({ ...details })); + eventListeners.forEach((callback) => callback({ ...details })); } } - addStateSubscriber(state: K, subscriber: (state: GuiStateTypes[K]) => void): () => void { - const stateSubscribers = this._stateSubscribers.get(state) || new Set(); - stateSubscribers.add(subscriber); - - this._stateSubscribers.set(state, stateSubscribers); + makeStateSubscriberFunction(state: K): (onStoreChangeCallback: () => void) => () => void { + // Using arrow function in order to keep "this" in context + const stateSubscriber = (onStoreChangeCallback: () => void): (() => void) => { + const stateSubscribers = this._stateSubscribers.get(state) || new Set(); + stateSubscribers.add(onStoreChangeCallback); + this._stateSubscribers.set(state, stateSubscribers); - if (this._storedValues.has(state)) { - subscriber(this._storedValues.get(state)); - } - - return () => { - stateSubscribers.delete(subscriber); + return () => { + stateSubscribers.delete(onStoreChangeCallback); + }; }; + + return stateSubscriber; } setState(state: K, value: GuiStateTypes[K]) { @@ -133,22 +129,30 @@ export class GuiMessageBroker { getState(state: K): GuiStateTypes[K] { return this._storedValues.get(state); } + + /* + It is really important that the snapshot returned by "stateSnapshotGetter" + returns the same value as long as the state has not been changed. + + */ + makeStateSnapshotGetter(state: K): () => GuiStateTypes[K] { + // Using arrow function in order to keep "this" in context + const stateSnapshotGetter = (): GuiStateTypes[K] => { + return this._storedValues.get(state); + }; + + return stateSnapshotGetter; + } } export function useGuiState( guiMessageBroker: GuiMessageBroker, key: T ): [GuiStateTypes[T], (value: GuiStateTypes[T] | ((prev: GuiStateTypes[T]) => GuiStateTypes[T])) => void] { - const [state, setState] = React.useState(guiMessageBroker.getState(key)); - - React.useEffect(() => { - const handleStateChange = (value: GuiStateTypes[T]) => { - setState(value); - }; - - const unsubscribeFunc = guiMessageBroker.addStateSubscriber(key, handleStateChange); - return unsubscribeFunc; - }, [key, guiMessageBroker]); + const state = React.useSyncExternalStore( + guiMessageBroker.makeStateSubscriberFunction(key), + guiMessageBroker.makeStateSnapshotGetter(key) + ); function setter(valueOrFunc: GuiStateTypes[T] | ((prev: GuiStateTypes[T]) => GuiStateTypes[T])): void { if (valueOrFunc instanceof Function) { diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx index 5fbb4947a..e1e1e145f 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/viewWrapper.tsx @@ -47,7 +47,7 @@ export const ViewWrapper: React.FC = (props) => { if (ref.current) { const point = pointerEventToPoint(e.nativeEvent); const rect = ref.current.getBoundingClientRect(); - guiMessageBroker.dispatchEvent(GuiEvent.ModuleHeaderPointerDown, { + guiMessageBroker.publishEvent(GuiEvent.ModuleHeaderPointerDown, { moduleInstanceId: props.moduleInstance.getId(), elementPosition: pointDifference(point, pointRelativeToDomRect(point, rect)), pointerPosition: point, @@ -59,7 +59,7 @@ export const ViewWrapper: React.FC = (props) => { const handleRemoveClick = React.useCallback( function handleRemoveClick(e: React.PointerEvent) { - guiMessageBroker.dispatchEvent(GuiEvent.RemoveModuleInstanceRequest, { + guiMessageBroker.publishEvent(GuiEvent.RemoveModuleInstanceRequest, { moduleInstanceId: props.moduleInstance.getId(), }); e.preventDefault(); diff --git a/frontend/src/framework/internal/components/Content/private-components/layout.tsx b/frontend/src/framework/internal/components/Content/private-components/layout.tsx index a6c65ddb0..7cbb85dc8 100644 --- a/frontend/src/framework/internal/components/Content/private-components/layout.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/layout.tsx @@ -237,21 +237,28 @@ export const Layout: React.FC = (props) => { props.workbench.setLayout(currentLayout); }; - guiMessageBroker.addEventListener(GuiEvent.ModuleHeaderPointerDown, handleModuleHeaderPointerDown); - guiMessageBroker.addEventListener(GuiEvent.NewModulePointerDown, handleNewModulePointerDown); - guiMessageBroker.addEventListener(GuiEvent.RemoveModuleInstanceRequest, handleRemoveModuleInstanceRequest); + const removeModuleHeaderPointerDownSubscriber = guiMessageBroker.subscribeToEvent( + GuiEvent.ModuleHeaderPointerDown, + handleModuleHeaderPointerDown + ); + const removeNewModulePointerDownSubscriber = guiMessageBroker.subscribeToEvent( + GuiEvent.NewModulePointerDown, + handleNewModulePointerDown + ); + const removeRemoveModuleInstanceRequestSubscriber = guiMessageBroker.subscribeToEvent( + GuiEvent.RemoveModuleInstanceRequest, + handleRemoveModuleInstanceRequest + ); document.addEventListener("pointerup", handlePointerUp); document.addEventListener("pointermove", handlePointerMove); document.addEventListener("keydown", handleButtonClick); return () => { - guiMessageBroker.removeEventListener(GuiEvent.ModuleHeaderPointerDown, handleModuleHeaderPointerDown); - guiMessageBroker.removeEventListener(GuiEvent.NewModulePointerDown, handleNewModulePointerDown); - guiMessageBroker.removeEventListener( - GuiEvent.RemoveModuleInstanceRequest, - handleRemoveModuleInstanceRequest - ); + removeModuleHeaderPointerDownSubscriber(); + removeNewModulePointerDownSubscriber(); + removeRemoveModuleInstanceRequestSubscriber(); + document.removeEventListener("pointerup", handlePointerUp); document.removeEventListener("pointermove", handlePointerMove); document.removeEventListener("keydown", handleButtonClick); diff --git a/frontend/src/framework/internal/components/Settings/private-components/modulesList.tsx b/frontend/src/framework/internal/components/Settings/private-components/modulesList.tsx index 5dc2f0b9f..7fd4d48fd 100644 --- a/frontend/src/framework/internal/components/Settings/private-components/modulesList.tsx +++ b/frontend/src/framework/internal/components/Settings/private-components/modulesList.tsx @@ -64,7 +64,7 @@ const ModulesListItem: React.FC = (props) => { const point = pointerEventToPoint(e); const rect = ref.current.getBoundingClientRect(); pointerDownElementPosition = pointDifference(point, pointRelativeToDomRect(point, rect)); - props.guiMessageBroker.dispatchEvent(GuiEvent.NewModulePointerDown, { + props.guiMessageBroker.publishEvent(GuiEvent.NewModulePointerDown, { moduleName: props.name, elementPosition: pointDifference(point, pointRelativeToDomRect(point, rect)), pointerPosition: point,