-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented
StatusController
and StatusWriter
for setting/updatin…
…g the status of modules (#416)
- Loading branch information
1 parent
1fb813b
commit 120053c
Showing
13 changed files
with
532 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export enum StatusMessageType { | ||
Warning = "warning", | ||
Error = "error", | ||
} | ||
|
||
export enum StatusSource { | ||
View = "view", | ||
Settings = "settings", | ||
} | ||
|
||
export interface ModuleInstanceStatusController { | ||
addMessage(source: StatusSource, message: string, type: StatusMessageType): void; | ||
clearMessages(source: StatusSource): void; | ||
setLoading(isLoading: boolean): void; | ||
|
||
setDebugMessage(source: StatusSource, message: string): void; | ||
incrementReportedComponentRenderCount(source: StatusSource): void; | ||
|
||
reviseAndPublishState(): void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import React from "react"; | ||
|
||
import { ModuleContext } from "./ModuleContext"; | ||
import { ModuleInstanceStatusController, StatusMessageType, StatusSource } from "./ModuleInstanceStatusController"; | ||
|
||
export class ViewStatusWriter { | ||
private _statusController: ModuleInstanceStatusController; | ||
|
||
constructor(statusController: ModuleInstanceStatusController) { | ||
this._statusController = statusController; | ||
} | ||
|
||
setLoading(isLoading: boolean): void { | ||
this._statusController.setLoading(isLoading); | ||
} | ||
|
||
addError(message: string): void { | ||
this._statusController.addMessage(StatusSource.View, message, StatusMessageType.Error); | ||
} | ||
|
||
addWarning(message: string): void { | ||
this._statusController.addMessage(StatusSource.View, message, StatusMessageType.Warning); | ||
} | ||
|
||
setDebugMessage(message: string): void { | ||
this._statusController.setDebugMessage(StatusSource.View, message); | ||
} | ||
} | ||
|
||
export class SettingsStatusWriter { | ||
private _statusController: ModuleInstanceStatusController; | ||
|
||
constructor(statusController: ModuleInstanceStatusController) { | ||
this._statusController = statusController; | ||
} | ||
|
||
addError(message: string): void { | ||
this._statusController.addMessage(StatusSource.Settings, message, StatusMessageType.Error); | ||
} | ||
|
||
addWarning(message: string): void { | ||
this._statusController.addMessage(StatusSource.Settings, message, StatusMessageType.Warning); | ||
} | ||
|
||
setDebugMessage(message: string): void { | ||
this._statusController.setDebugMessage(StatusSource.Settings, message); | ||
} | ||
} | ||
|
||
export function useViewStatusWriter(moduleContext: ModuleContext<any>): ViewStatusWriter { | ||
const statusController = moduleContext.getStatusController(); | ||
|
||
const statusWriter = React.useRef<ViewStatusWriter>(new ViewStatusWriter(statusController)); | ||
|
||
statusController.clearMessages(StatusSource.View); | ||
statusController.incrementReportedComponentRenderCount(StatusSource.View); | ||
|
||
React.useEffect(function handleRender() { | ||
statusController.reviseAndPublishState(); | ||
}); | ||
|
||
return statusWriter.current; | ||
} | ||
|
||
export function useSettingsStatusWriter(moduleContext: ModuleContext<any>): ViewStatusWriter { | ||
const statusController = moduleContext.getStatusController(); | ||
|
||
const statusWriter = React.useRef<ViewStatusWriter>(new ViewStatusWriter(statusController)); | ||
|
||
statusController.clearMessages(StatusSource.Settings); | ||
statusController.incrementReportedComponentRenderCount(StatusSource.Settings); | ||
|
||
React.useEffect(function handleRender() { | ||
statusController.reviseAndPublishState(); | ||
}); | ||
|
||
return statusWriter.current; | ||
} |
139 changes: 139 additions & 0 deletions
139
frontend/src/framework/internal/ModuleInstanceStatusControllerInternal.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import React from "react"; | ||
|
||
import { | ||
ModuleInstanceStatusController, | ||
StatusMessageType, | ||
StatusSource, | ||
} from "@framework/ModuleInstanceStatusController"; | ||
|
||
import { cloneDeep, filter, isEqual, keys } from "lodash"; | ||
|
||
type StatusMessage = { | ||
source: StatusSource; | ||
message: string; | ||
type: StatusMessageType; | ||
}; | ||
|
||
type StatusControllerState = { | ||
messages: StatusMessage[]; | ||
loading: boolean; | ||
viewDebugMessage: string; | ||
settingsDebugMessage: string; | ||
viewRenderCount: number | null; | ||
settingsRenderCount: number | null; | ||
}; | ||
|
||
export class ModuleInstanceStatusControllerInternal implements ModuleInstanceStatusController { | ||
protected _stateCandidates: StatusControllerState; | ||
protected _state: StatusControllerState = { | ||
messages: [], | ||
loading: false, | ||
viewDebugMessage: "", | ||
settingsDebugMessage: "", | ||
viewRenderCount: null, | ||
settingsRenderCount: null, | ||
}; | ||
private _subscribers: Map<keyof StatusControllerState, Set<() => void>> = new Map(); | ||
|
||
constructor() { | ||
this._stateCandidates = cloneDeep(this._state); | ||
} | ||
|
||
addMessage(source: StatusSource, message: string, type: StatusMessageType): void { | ||
this._stateCandidates.messages.push({ | ||
source, | ||
message, | ||
type, | ||
}); | ||
} | ||
|
||
clearMessages(source: StatusSource): void { | ||
this._stateCandidates.messages = this._stateCandidates.messages.filter((msg) => msg.source !== source); | ||
} | ||
|
||
setLoading(isLoading: boolean): void { | ||
this._stateCandidates.loading = isLoading; | ||
} | ||
|
||
setDebugMessage(source: StatusSource, message: string): void { | ||
if (source === StatusSource.View) { | ||
this._stateCandidates.viewDebugMessage = message; | ||
} | ||
if (source === StatusSource.Settings) { | ||
this._stateCandidates.settingsDebugMessage = message; | ||
} | ||
} | ||
|
||
incrementReportedComponentRenderCount(source: StatusSource): void { | ||
if (source === StatusSource.View) { | ||
if (this._stateCandidates.viewRenderCount === null) { | ||
this._stateCandidates.viewRenderCount = 0; | ||
} | ||
this._stateCandidates.viewRenderCount++; | ||
} | ||
if (source === StatusSource.Settings) { | ||
if (this._stateCandidates.settingsRenderCount === null) { | ||
this._stateCandidates.settingsRenderCount = 0; | ||
} | ||
this._stateCandidates.settingsRenderCount++; | ||
} | ||
} | ||
|
||
reviseAndPublishState(): void { | ||
const differentStateKeys = filter(keys(this._stateCandidates), (key: keyof StatusControllerState) => { | ||
return !isEqual(this._state[key], this._stateCandidates[key]); | ||
}) as (keyof StatusControllerState)[]; | ||
|
||
this._state = cloneDeep(this._stateCandidates); | ||
|
||
differentStateKeys.forEach((stateKey) => { | ||
this.notifySubscribers(stateKey); | ||
}); | ||
} | ||
|
||
private notifySubscribers(stateKey: keyof StatusControllerState): void { | ||
const subscribers = this._subscribers.get(stateKey); | ||
if (subscribers) { | ||
subscribers.forEach((subscriber) => { | ||
subscriber(); | ||
}); | ||
} | ||
} | ||
|
||
makeSnapshotGetter<T extends keyof StatusControllerState>(stateKey: T): () => StatusControllerState[T] { | ||
const snapshotGetter = (): any => { | ||
return this._state[stateKey]; | ||
}; | ||
|
||
return snapshotGetter; | ||
} | ||
|
||
makeSubscriberFunction<T extends keyof StatusControllerState>( | ||
stateKey: T | ||
): (onStoreChangeCallback: () => void) => () => void { | ||
// Using arrow function in order to keep "this" in context | ||
const subscriber = (onStoreChangeCallback: () => void): (() => void) => { | ||
const subscribers = this._subscribers.get(stateKey) || new Set(); | ||
subscribers.add(onStoreChangeCallback); | ||
this._subscribers.set(stateKey, subscribers); | ||
|
||
return () => { | ||
subscribers.delete(onStoreChangeCallback); | ||
}; | ||
}; | ||
|
||
return subscriber; | ||
} | ||
} | ||
|
||
export function useStatusControllerStateValue<T extends keyof StatusControllerState>( | ||
statusController: ModuleInstanceStatusControllerInternal, | ||
stateKey: T | ||
): StatusControllerState[T] { | ||
const value = React.useSyncExternalStore<StatusControllerState[T]>( | ||
statusController.makeSubscriberFunction(stateKey), | ||
statusController.makeSnapshotGetter(stateKey) | ||
); | ||
|
||
return value; | ||
} |
Oops, something went wrong.