diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0a76039d9..244e72068 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,7 @@ "culori": "^3.2.0", "geojson": "^0.5.0", "jotai": "^2.6.2", + "jotai-effect": "^1.0.0", "jotai-scope": "^0.5.1", "jotai-tanstack-query": "^0.8.2", "lodash": "^4.17.21", @@ -9594,6 +9595,14 @@ } } }, + "node_modules/jotai-effect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jotai-effect/-/jotai-effect-1.0.0.tgz", + "integrity": "sha512-eCgKKG4BACDzuJGYTu0xZRk1C1MEOvbAhC3L8w7YufQ2lSLORwNX/WFnCuZxLFX0sDLkTUeoUzOYaw8wnXY+UQ==", + "peerDependencies": { + "jotai": ">=2.5.0" + } + }, "node_modules/jotai-scope": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/jotai-scope/-/jotai-scope-0.5.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index ad1e91e2a..36cee091e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,6 +35,7 @@ "culori": "^3.2.0", "geojson": "^0.5.0", "jotai": "^2.6.2", + "jotai-effect": "^1.0.0", "jotai-scope": "^0.5.1", "jotai-tanstack-query": "^0.8.2", "lodash": "^4.17.21", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2f4c8e0b1..1d03a1c5b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -44,45 +44,50 @@ enum InitAppState { const layout: LayoutElement[] = []; -const WORKBENCH = new Workbench(); - function App() { + // Workbench must be kept as a state in order to keep it when any framework code is changed in dev mode. + // Otherwise, the workbench will be reset on every code change. This would cause it to loose its state and will + // cause the app to crash. + const [workbench] = React.useState(new Workbench()); + const [isMounted, setIsMounted] = React.useState(false); const [initAppState, setInitAppState] = React.useState(InitAppState.CheckingIfUserIsSignedIn); const queryClient = useQueryClient(); const { authState } = useAuthProvider(); - function initApp() { - if (!WORKBENCH.loadLayoutFromLocalStorage()) { - WORKBENCH.makeLayout(layout); - } - - if (WORKBENCH.getLayout().length === 0) { - WORKBENCH.getGuiMessageBroker().setState(GuiState.LeftDrawerContent, LeftDrawerContent.ModulesList); - } else { - WORKBENCH.getGuiMessageBroker().setState(GuiState.LeftDrawerContent, LeftDrawerContent.ModuleSettings); - } - setInitAppState(InitAppState.InitCompleted); - WORKBENCH.getGuiMessageBroker().setState(GuiState.AppInitialized, true); - } - function signIn() { window.location.href = `/api/login?redirect_url_after_login=${btoa("/")}`; } React.useEffect( function handleMountWhenSignedIn() { + function initApp() { + if (!workbench.loadLayoutFromLocalStorage()) { + workbench.makeLayout(layout); + } + + if (workbench.getLayout().length === 0) { + workbench.getGuiMessageBroker().setState(GuiState.LeftDrawerContent, LeftDrawerContent.ModulesList); + } else { + workbench + .getGuiMessageBroker() + .setState(GuiState.LeftDrawerContent, LeftDrawerContent.ModuleSettings); + } + setInitAppState(InitAppState.InitCompleted); + workbench.getGuiMessageBroker().setState(GuiState.AppInitialized, true); + } + if (authState !== AuthState.LoggedIn || isMounted) { return; } setIsMounted(true); - const storedEnsembleIdents = WORKBENCH.maybeLoadEnsembleSettingsFromLocalStorage(); + const storedEnsembleIdents = workbench.maybeLoadEnsembleSettingsFromLocalStorage(); if (storedEnsembleIdents) { setInitAppState(InitAppState.LoadingEnsembles); - WORKBENCH.loadAndSetupEnsembleSetInSession(queryClient, storedEnsembleIdents).finally(() => { + workbench.loadAndSetupEnsembleSetInSession(queryClient, storedEnsembleIdents).finally(() => { initApp(); }); } else { @@ -90,11 +95,11 @@ function App() { } return function handleUnmount() { - WORKBENCH.clearLayout(); - WORKBENCH.resetModuleInstanceNumbers(); + workbench.clearLayout(); + workbench.resetModuleInstanceNumbers(); }; }, - [authState, isMounted, queryClient] + [authState, isMounted, queryClient, workbench] ); function makeStateMessages() { @@ -160,12 +165,12 @@ function App() { })} > <> - - - + + + - + ); } diff --git a/frontend/src/framework/Module.tsx b/frontend/src/framework/Module.tsx index 6247bb0ed..dc81aaee0 100644 --- a/frontend/src/framework/Module.tsx +++ b/frontend/src/framework/Module.tsx @@ -1,7 +1,6 @@ import React from "react"; -import { Atom, PrimitiveAtom, WritableAtom } from "jotai"; -import { cloneDeep } from "lodash"; +import { Getter, Setter } from "jotai"; import { ChannelDefinition, ChannelReceiverDefinition } from "./DataChannelTypes"; import { InitialSettings } from "./InitialSettings"; @@ -9,13 +8,8 @@ import { SettingsContext, ViewContext } from "./ModuleContext"; import { ModuleDataTagId } from "./ModuleDataTags"; import { ModuleInstance, ModuleInstanceTopic } from "./ModuleInstance"; import { DrawPreviewFunc } from "./Preview"; -import { StateBaseType, StateOptions } from "./StateStore"; import { SyncSettingKey } from "./SyncSettings"; -import { - InterfaceBaseType, - InterfaceInitialization, - UniDirectionalModuleComponentsInterface, -} from "./UniDirectionalModuleComponentsInterface"; +import { InterfaceBaseType, InterfaceInitialization } from "./UniDirectionalModuleComponentsInterface"; import { Workbench } from "./Workbench"; import { WorkbenchServices } from "./WorkbenchServices"; import { WorkbenchSession } from "./WorkbenchSession"; @@ -33,13 +27,27 @@ export enum ModuleDevState { DEPRECATED = "deprecated", } +export type ModuleInterfaceTypes = { + settingsToView?: InterfaceBaseType; + viewToSettings?: InterfaceBaseType; +}; + +export type ModuleInterfacesInitializations = { + settingsToView: TInterfaceTypes["settingsToView"] extends undefined + ? undefined + : InterfaceInitialization>; + viewToSettings: TInterfaceTypes["viewToSettings"] extends undefined + ? undefined + : InterfaceInitialization>; +}; + export type ModuleSettingsProps< - TTStateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType = Record, - TSettingsAtomsType extends Record = Record, - TViewAtomsType extends Record = Record + TInterfaceTypes extends ModuleInterfaceTypes = { + settingsToView: Record; + viewToSettings: Record; + } > = { - settingsContext: SettingsContext; + settingsContext: SettingsContext; workbenchSession: WorkbenchSession; workbenchServices: WorkbenchServices; workbenchSettings: WorkbenchSettings; @@ -47,39 +55,37 @@ export type ModuleSettingsProps< }; export type ModuleViewProps< - TTStateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType = Record, - TSettingsAtomsType extends Record = Record, - TViewAtomsType extends Record = Record + TInterfaceTypes extends ModuleInterfaceTypes = { + settingsToView: Record; + viewToSettings: Record; + } > = { - viewContext: ViewContext; + viewContext: ViewContext; workbenchSession: WorkbenchSession; workbenchServices: WorkbenchServices; workbenchSettings: WorkbenchSettings; initialSettings?: InitialSettings; }; -export type ModuleAtoms> = { - [K in keyof TAtoms]: Atom | WritableAtom | PrimitiveAtom; -}; - -export type AtomsInitialization, TInterfaceType extends InterfaceBaseType> = ( - settingsToViewInterface: UniDirectionalModuleComponentsInterface -) => ModuleAtoms; +export type InterfaceEffects = (( + getInterfaceValue: (key: TKey) => TInterfaceType[TKey], + setAtomValue: Setter, + getAtomValue: Getter +) => void)[]; export type ModuleSettings< - TTStateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType = Record, - TSettingsAtomsType extends Record = Record, - TViewAtomsType extends Record = Record -> = React.FC>; + TInterfaceTypes extends ModuleInterfaceTypes = { + settingsToView: Record; + viewToSettings: Record; + } +> = React.FC>; export type ModuleView< - TTStateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType = Record, - TSettingsAtomsType extends Record = Record, - TViewAtomsType extends Record = Record -> = React.FC>; + TInterfaceTypes extends ModuleInterfaceTypes = { + settingsToView: Record; + viewToSettings: Record; + } +> = React.FC>; export enum ImportState { NotImported = "NotImported", @@ -101,24 +107,24 @@ export interface ModuleOptions { channelReceiverDefinitions?: ChannelReceiverDefinition[]; } -export class Module< - TStateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType, - TSettingsAtomsType extends Record, - TViewAtomsType extends Record -> { +export class Module { private _name: string; private _defaultTitle: string; - public viewFC: ModuleView; - public settingsFC: ModuleSettings; - protected _importState: ImportState; - private _moduleInstances: ModuleInstance[]; - private _defaultState: TStateType | null; - private _settingsToViewInterfaceInitialization: InterfaceInitialization | null; - private _settingsAtomsInitialization: AtomsInitialization | null; - private _viewAtomsInitialization: AtomsInitialization | null; - private _stateOptions: StateOptions | undefined; - private _workbench: Workbench | null; + public viewFC: ModuleView; + public settingsFC: ModuleSettings; + protected _importState: ImportState = ImportState.NotImported; + private _moduleInstances: ModuleInstance[] = []; + private _settingsToViewInterfaceInitialization: InterfaceInitialization< + Exclude + > | null = null; + private _viewToSettingsInterfaceInitialization: InterfaceInitialization< + Exclude + > | null = null; + private _viewToSettingsInterfaceEffects: InterfaceEffects> = + []; + private _settingsToViewInterfaceEffects: InterfaceEffects> = + []; + private _workbench: Workbench | null = null; private _syncableSettingKeys: SyncSettingKey[]; private _drawPreviewFunc: DrawPreviewFunc | null; private _description: string | null; @@ -135,13 +141,6 @@ export class Module< this._devState = options.devState; this.viewFC = () =>
Not defined
; this.settingsFC = () =>
Not defined
; - this._importState = ImportState.NotImported; - this._moduleInstances = []; - this._defaultState = null; - this._settingsToViewInterfaceInitialization = null; - this._settingsAtomsInitialization = null; - this._viewAtomsInitialization = null; - this._workbench = null; this._syncableSettingKeys = options.syncableSettingKeys ?? []; this._drawPreviewFunc = options.drawPreviewFunc ?? null; this._description = options.description ?? null; @@ -186,26 +185,36 @@ export class Module< this._workbench = workbench; } - setDefaultState(defaultState: TStateType, options?: StateOptions): void { - this._defaultState = defaultState; - this._stateOptions = options; - this._moduleInstances.forEach((instance) => { - if (this._defaultState && !instance.isInitialized()) { - instance.setDefaultState(cloneDeep(this._defaultState), cloneDeep(this._stateOptions)); - } - }); + setSettingsToViewInterfaceInitialization( + interfaceInitialization: InterfaceInitialization> + ): void { + this._settingsToViewInterfaceInitialization = interfaceInitialization; } - setSettingsToViewInterfaceInitialization(interfaceInitialization: InterfaceInitialization): void { - this._settingsToViewInterfaceInitialization = interfaceInitialization; + setViewToSettingsInterfaceInitialization( + interfaceInitialization: InterfaceInitialization> + ): void { + this._viewToSettingsInterfaceInitialization = interfaceInitialization; + } + + setViewToSettingsInterfaceEffects( + atomsInitialization: InterfaceEffects> + ): void { + this._viewToSettingsInterfaceEffects = atomsInitialization; } - setSettingsAtomsInitialization(atomsInitialization: AtomsInitialization): void { - this._settingsAtomsInitialization = atomsInitialization; + setSettingsToViewInterfaceEffects( + atomsInitialization: InterfaceEffects> + ): void { + this._settingsToViewInterfaceEffects = atomsInitialization; } - setViewAtomsInitialization(atomsInitialization: AtomsInitialization): void { - this._viewAtomsInitialization = atomsInitialization; + getViewToSettingsInterfaceEffects(): InterfaceEffects> { + return this._viewToSettingsInterfaceEffects; + } + + getSettingsToViewInterfaceEffects(): InterfaceEffects> { + return this._settingsToViewInterfaceEffects; } getSyncableSettingKeys(): SyncSettingKey[] { @@ -216,14 +225,12 @@ export class Module< return this._syncableSettingKeys.includes(key); } - makeInstance( - instanceNumber: number - ): ModuleInstance { + makeInstance(instanceNumber: number): ModuleInstance { if (!this._workbench) { throw new Error("Module must be added to a workbench before making an instance"); } - const instance = new ModuleInstance({ + const instance = new ModuleInstance({ module: this, workbench: this._workbench, instanceNumber, @@ -246,25 +253,26 @@ export class Module< } } + private initializeModuleInstance(instance: ModuleInstance): void { + instance.initialize(); + if (this._settingsToViewInterfaceInitialization) { + instance.makeSettingsToViewInterface(this._settingsToViewInterfaceInitialization); + } + instance.makeSettingsToViewInterfaceEffectsAtom(); + if (this._viewToSettingsInterfaceInitialization) { + instance.makeViewToSettingsInterface(this._viewToSettingsInterfaceInitialization); + } + instance.makeViewToSettingsInterfaceEffectsAtom(); + } + private maybeImportSelf(): void { if (this._importState !== ImportState.NotImported) { - if (this._defaultState && this._importState === ImportState.Imported) { + if (this._importState === ImportState.Imported) { this._moduleInstances.forEach((instance) => { if (instance.isInitialized()) { return; } - if (this._defaultState) { - instance.setDefaultState(cloneDeep(this._defaultState), cloneDeep(this._stateOptions)); - } - if (this._settingsToViewInterfaceInitialization) { - instance.makeSettingsToViewInterface(this._settingsToViewInterfaceInitialization); - if (this._settingsAtomsInitialization) { - instance.makeSettingsAtoms(this._settingsAtomsInitialization); - } - if (this._viewAtomsInitialization) { - instance.makeViewAtoms(this._viewAtomsInitialization); - } - } + this.initializeModuleInstance(instance); }); } return; @@ -276,18 +284,7 @@ export class Module< .then(() => { this.setImportState(ImportState.Imported); this._moduleInstances.forEach((instance) => { - if (this._defaultState) { - instance.setDefaultState(cloneDeep(this._defaultState), cloneDeep(this._stateOptions)); - } - if (this._settingsToViewInterfaceInitialization) { - instance.makeSettingsToViewInterface(this._settingsToViewInterfaceInitialization); - if (this._settingsAtomsInitialization) { - instance.makeSettingsAtoms(this._settingsAtomsInitialization); - } - if (this._viewAtomsInitialization) { - instance.makeViewAtoms(this._viewAtomsInitialization); - } - } + this.initializeModuleInstance(instance); }); }) .catch((e) => { diff --git a/frontend/src/framework/ModuleContext.ts b/frontend/src/framework/ModuleContext.ts index 38ac9383d..7bb1a9b89 100644 --- a/frontend/src/framework/ModuleContext.ts +++ b/frontend/src/framework/ModuleContext.ts @@ -10,9 +10,8 @@ */ /* eslint-disable react-hooks/rules-of-hooks */ -import { WritableAtom, useAtom, useAtomValue, useSetAtom } from "jotai"; - import { ChannelContentDefinition, KeyKind } from "./DataChannelTypes"; +import { ModuleInterfaceTypes } from "./Module"; import { ModuleInstance, ModuleInstanceTopic, @@ -20,53 +19,22 @@ import { useModuleInstanceTopicValue, } from "./ModuleInstance"; import { ModuleInstanceStatusController } from "./ModuleInstanceStatusController"; -import { StateBaseType, StateStore, useSetStoreValue, useStoreState, useStoreValue } from "./StateStore"; import { SyncSettingKey } from "./SyncSettings"; -import { InterfaceBaseType, useSettingsToViewInterfaceValue } from "./UniDirectionalModuleComponentsInterface"; +import { InterfaceBaseType, useInterfaceValue } from "./UniDirectionalModuleComponentsInterface"; import { useChannelReceiver } from "./internal/DataChannels/hooks/useChannelReceiver"; import { usePublishChannelContents } from "./internal/DataChannels/hooks/usePublishChannelContents"; -export class ModuleContext< - TStateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType, - TSettingsAtomsType extends Record, - TViewAtomsType extends Record -> { - protected _moduleInstance: ModuleInstance; - private _stateStore: StateStore; - - constructor( - moduleInstance: ModuleInstance, - stateStore: StateStore - ) { +export class ModuleContext { + protected _moduleInstance: ModuleInstance; + + constructor(moduleInstance: ModuleInstance) { this._moduleInstance = moduleInstance; - this._stateStore = stateStore; } getInstanceIdString(): string { return this._moduleInstance.getId(); } - getStateStore(): StateStore { - return this._stateStore; - } - - useStoreState( - key: K - ): [TStateType[K], (value: TStateType[K] | ((prev: TStateType[K]) => TStateType[K])) => void] { - return useStoreState(this._stateStore, key); - } - - useStoreValue(key: K): TStateType[K] { - return useStoreValue(this._stateStore, key); - } - - useSetStoreValue( - key: K - ): (newValue: TStateType[K] | ((prev: TStateType[K]) => TStateType[K])) => void { - return useSetStoreValue(this._stateStore, key); - } - useModuleInstanceTopic(topic: T): ModuleInstanceTopicValueTypes[T] { return useModuleInstanceTopicValue(this._moduleInstance, topic); } @@ -114,86 +82,25 @@ export class ModuleContext< }); } - useSettingsToViewInterfaceValue(key: TKey): TInterfaceType[TKey]; - useSettingsToViewInterfaceValue(key: TKey): TInterfaceType[TKey] { - return useSettingsToViewInterfaceValue(this._moduleInstance.getUniDirectionalSettingsToViewInterface(), key); - } - - useViewAtom( + useSettingsToViewInterfaceValue( key: TKey - ): [Awaited, (value: TViewAtomsType[TKey]) => void] { - const atom = this._moduleInstance.getViewAtom(key); - - return useAtom(atom); - } - - useViewAtomValue(key: TKey): TViewAtomsType[TKey] { - const atom = this._moduleInstance.getViewAtom(key); - - return useAtomValue(atom); - } - - useSetViewAtom< - TKey extends keyof Pick< - TViewAtomsType, - keyof { - [key in keyof TViewAtomsType]: TViewAtomsType[key] extends WritableAtom ? key : never; - } - > - >(key: TKey): (...args: [TViewAtomsType[TKey]]) => void { - const atom = this._moduleInstance.getViewAtom(key) as WritableAtom; - return useSetAtom(atom); + ): TInterfaceTypes["settingsToView"][TKey] { + return useInterfaceValue(this._moduleInstance.getUniDirectionalSettingsToViewInterface(), key); } - useSettingsAtom( + useViewToSettingsInterfaceValue( key: TKey - ): [Awaited, (value: TSettingsAtomsType[TKey]) => void] { - const atom = this._moduleInstance.getSettingsAtom(key); - - return useAtom(atom); - } - - useSettingsAtomValue(key: TKey): TSettingsAtomsType[TKey] { - const atom = this._moduleInstance.getSettingsAtom(key); - - return useAtomValue(atom); - } - - useSetSettingsAtom< - TKey extends keyof Pick< - TSettingsAtomsType, - keyof { - [key in keyof TSettingsAtomsType]: TSettingsAtomsType[key] extends WritableAtom - ? key - : never; - } - > - >(key: TKey): (...args: [TSettingsAtomsType[TKey]]) => void { - const atom = this._moduleInstance.getSettingsAtom(key) as WritableAtom; - return useSetAtom(atom); + ): TInterfaceTypes["viewToSettings"][TKey] { + return useInterfaceValue(this._moduleInstance.getUniDirectionalViewToSettingsInterface(), key); } } -export type ViewContext< - StateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType, - TSettingsAtomsType extends Record, - TViewAtomsType extends Record -> = Omit< - ModuleContext, - | "useSettingsToViewInterfaceState" - | "useSetSettingsToViewInterfaceValue" - | "useSettingsAtom" - | "useSetSettingsAtom" - | "useSettingsAtomValue" +export type ViewContext = Omit< + ModuleContext, + "useViewToSettingsInterfaceValue" | "useSettingsAtom" | "useSetSettingsAtom" | "useSettingsAtomValue" >; -export type SettingsContext< - StateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType, - TSettingsAtomsType extends Record, - TViewAtomsType extends Record -> = Omit< - ModuleContext, - "useViewAtom" | "useViewAtomValue" | "useSetViewAtom" +export type SettingsContext = Omit< + ModuleContext, + "useSettingsToViewInterfaceValue" | "useViewAtom" | "useViewAtomValue" | "useSetViewAtom" >; diff --git a/frontend/src/framework/ModuleInstance.ts b/frontend/src/framework/ModuleInstance.ts index 12767d24b..968e56d2f 100644 --- a/frontend/src/framework/ModuleInstance.ts +++ b/frontend/src/framework/ModuleInstance.ts @@ -1,16 +1,14 @@ import React, { ErrorInfo } from "react"; -import { cloneDeep } from "lodash"; +import { Atom, atom } from "jotai"; +import { atomEffect } from "jotai-effect"; -import { AtomStore } from "./AtomStoreMaster"; import { ChannelDefinition, ChannelReceiverDefinition } from "./DataChannelTypes"; import { InitialSettings } from "./InitialSettings"; -import { AtomsInitialization, ImportState, Module, ModuleAtoms, ModuleSettings, ModuleView } from "./Module"; +import { ImportState, Module, ModuleInterfaceTypes, ModuleSettings, ModuleView } from "./Module"; import { ModuleContext } from "./ModuleContext"; -import { StateBaseType, StateOptions, StateStore } from "./StateStore"; import { SyncSettingKey } from "./SyncSettings"; import { - InterfaceBaseType, InterfaceInitialization, UniDirectionalModuleComponentsInterface, } from "./UniDirectionalModuleComponentsInterface"; @@ -39,62 +37,40 @@ export type ModuleInstanceTopicValueTypes = { [ModuleInstanceTopic.IMPORT_STATE]: ImportState; }; -export interface ModuleInstanceOptions< - TStateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType, - TSettingsAtomsType extends Record, - TViewAtomsType extends Record -> { - module: Module; +export interface ModuleInstanceOptions { + module: Module; workbench: Workbench; instanceNumber: number; channelDefinitions: ChannelDefinition[] | null; channelReceiverDefinitions: ChannelReceiverDefinition[] | null; } -export class ModuleInstance< - TStateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType, - TSettingsAtomsType extends Record, - TViewAtomsType extends Record -> { +export class ModuleInstance { private _id: string; private _title: string; - private _initialised: boolean; - private _moduleInstanceState: ModuleInstanceState; - private _fatalError: { err: Error; errInfo: ErrorInfo } | null; - private _syncedSettingKeys: SyncSettingKey[]; - private _stateStore: StateStore | null; - private _module: Module; - private _context: ModuleContext | null; + private _initialized: boolean = false; + private _moduleInstanceState: ModuleInstanceState = ModuleInstanceState.INITIALIZING; + private _fatalError: { err: Error; errInfo: ErrorInfo } | null = null; + private _syncedSettingKeys: SyncSettingKey[] = []; + private _module: Module; + private _context: ModuleContext | null = null; private _subscribers: Map void>> = new Map(); - private _cachedDefaultState: TStateType | null; - private _cachedStateStoreOptions?: StateOptions; - private _initialSettings: InitialSettings | null; - private _statusController: ModuleInstanceStatusControllerInternal; + private _initialSettings: InitialSettings | null = null; + private _statusController: ModuleInstanceStatusControllerInternal = new ModuleInstanceStatusControllerInternal(); private _channelManager: ChannelManager; - private _workbench: Workbench; - private _settingsViewInterface: UniDirectionalModuleComponentsInterface | null; - private _settingsAtoms: ModuleAtoms | null; - private _viewAtoms: ModuleAtoms | null; - - constructor(options: ModuleInstanceOptions) { + private _settingsToViewInterface: UniDirectionalModuleComponentsInterface< + Exclude + > | null = null; + private _viewToSettingsInterface: UniDirectionalModuleComponentsInterface< + Exclude + > | null = null; + private _settingsToViewInterfaceEffectsAtom: Atom | null = null; + private _viewToSettingsInterfaceEffectsAtom: Atom | null = null; + + constructor(options: ModuleInstanceOptions) { this._id = `${options.module.getName()}-${options.instanceNumber}`; this._title = options.module.getDefaultTitle(); - this._stateStore = null; this._module = options.module; - this._context = null; - this._initialised = false; - this._syncedSettingKeys = []; - this._moduleInstanceState = ModuleInstanceState.INITIALIZING; - this._fatalError = null; - this._cachedDefaultState = null; - this._initialSettings = null; - this._statusController = new ModuleInstanceStatusControllerInternal(); - this._workbench = options.workbench; - this._settingsViewInterface = null; - this._settingsAtoms = null; - this._viewAtoms = null; this._channelManager = new ChannelManager(this._id); @@ -112,66 +88,114 @@ export class ModuleInstance< } } - getAtomStore(): AtomStore { - return this._workbench.getAtomStoreMaster().getAtomStoreForModuleInstance(this._id); - } - - getUniDirectionalSettingsToViewInterface(): UniDirectionalModuleComponentsInterface { - if (!this._settingsViewInterface) { + getUniDirectionalSettingsToViewInterface(): UniDirectionalModuleComponentsInterface< + Exclude + > { + if (!this._settingsToViewInterface) { throw `Module instance '${this._title}' does not have an interface yet. Did you forget to init the module?`; } - return this._settingsViewInterface; - } - - getSettingsAtom(key: TKey): ModuleAtoms[TKey] { - if (!this._settingsAtoms) { - throw `Module instance '${this._title}' does not have initialized settings atoms yet. Did you forget to add an atom initialization when registering the module?`; - } - return this._settingsAtoms[key]; + return this._settingsToViewInterface; } - getViewAtom(key: TKey): ModuleAtoms[TKey] { - if (!this._viewAtoms) { - throw `Module instance '${this._title}' does not have initialized view atoms yet. Did you forget to add an atom initialization when registering the module?`; + getUniDirectionalViewToSettingsInterface(): UniDirectionalModuleComponentsInterface< + Exclude + > { + if (!this._viewToSettingsInterface) { + throw `Module instance '${this._title}' does not have an interface yet. Did you forget to init the module?`; } - return this._viewAtoms[key]; + return this._viewToSettingsInterface; } getChannelManager(): ChannelManager { return this._channelManager; } - setDefaultState(defaultState: TStateType, options?: StateOptions): void { - if (this._cachedDefaultState === null) { - this._cachedDefaultState = defaultState; - this._cachedStateStoreOptions = options; + initialize(): void { + this._context = new ModuleContext(this); + this._initialized = true; + this.setModuleInstanceState(ModuleInstanceState.OK); + } + + makeSettingsToViewInterface( + interfaceInitialization: InterfaceInitialization> + ) { + if (!interfaceInitialization) { + return; } + this._settingsToViewInterface = new UniDirectionalModuleComponentsInterface(interfaceInitialization); + } - this._stateStore = new StateStore(cloneDeep(defaultState), options); - this._context = new ModuleContext( - this, - this._stateStore - ); - this._initialised = true; - this.setModuleInstanceState(ModuleInstanceState.OK); + makeViewToSettingsInterface( + interfaceInitialization: InterfaceInitialization> + ) { + if (!interfaceInitialization) { + return; + } + this._viewToSettingsInterface = new UniDirectionalModuleComponentsInterface(interfaceInitialization); + } + + makeSettingsToViewInterfaceEffectsAtom(): void { + const effectFuncs = this.getModule().getSettingsToViewInterfaceEffects(); + const getUniDirectionalSettingsToViewInterface: () => UniDirectionalModuleComponentsInterface< + Exclude + > = () => this.getUniDirectionalSettingsToViewInterface(); + + const newEffects: Atom[] = []; + for (const effectFunc of effectFuncs) { + const effect = atomEffect((get, set) => { + function getAtomFromInterface>( + key: TKey + ): Exclude[TKey] { + return get(getUniDirectionalSettingsToViewInterface().getAtom(key)); + } + effectFunc(getAtomFromInterface, set, get); + }); + newEffects.push(effect); + } + this._settingsToViewInterfaceEffectsAtom = atom((get) => { + for (const effect of newEffects) { + get(effect); + } + }); } - makeSettingsToViewInterface(interfaceInitialization: InterfaceInitialization) { - this._settingsViewInterface = new UniDirectionalModuleComponentsInterface(interfaceInitialization); + makeViewToSettingsInterfaceEffectsAtom(): void { + const effectFuncs = this.getModule().getViewToSettingsInterfaceEffects(); + const getUniDirectionalViewToSettingsInterface: () => UniDirectionalModuleComponentsInterface< + Exclude + > = () => this.getUniDirectionalViewToSettingsInterface(); + + const newEffects: Atom[] = []; + for (const effectFunc of effectFuncs) { + const effect = atomEffect((get, set) => { + function getAtomFromInterface>( + key: TKey + ): Exclude[TKey] { + return get(getUniDirectionalViewToSettingsInterface().getAtom(key)); + } + effectFunc(getAtomFromInterface, set, get); + }); + newEffects.push(effect); + } + this._viewToSettingsInterfaceEffectsAtom = atom((get) => { + for (const effect of newEffects) { + get(effect); + } + }); } - makeSettingsAtoms(initFunc: AtomsInitialization) { - if (!this._settingsViewInterface) { - throw `Module instance '${this._title}' does not have an interface yet. Did you forget to init the module?`; + getSettingsToViewInterfaceEffectsAtom(): Atom { + if (!this._settingsToViewInterfaceEffectsAtom) { + throw `Module instance '${this._title}' does not have settings to view interface effects yet. Did you forget to init the module?`; } - this._settingsAtoms = initFunc(this._settingsViewInterface); + return this._settingsToViewInterfaceEffectsAtom; } - makeViewAtoms(initFunc: AtomsInitialization) { - if (!this._settingsViewInterface) { - throw `Module instance '${this._title}' does not have an interface yet. Did you forget to init the module?`; + getViewToSettingsInterfaceEffectsAtom(): Atom { + if (!this._viewToSettingsInterfaceEffectsAtom) { + throw `Module instance '${this._title}' does not have view to settings interface effects yet. Did you forget to init the module?`; } - this._viewAtoms = initFunc(this._settingsViewInterface); + return this._viewToSettingsInterfaceEffectsAtom; } addSyncedSetting(settingKey: SyncSettingKey): void { @@ -193,14 +217,14 @@ export class ModuleInstance< } isInitialized(): boolean { - return this._initialised; + return this._initialized; } - getViewFC(): ModuleView { + getViewFC(): ModuleView { return this._module.viewFC; } - getSettingsFC(): ModuleSettings { + getSettingsFC(): ModuleSettings { return this._module.settingsFC; } @@ -208,7 +232,7 @@ export class ModuleInstance< return this._module.getImportState(); } - getContext(): ModuleContext { + getContext(): ModuleContext { if (!this._context) { throw `Module context is not available yet. Did you forget to init the module '${this._title}.'?`; } @@ -275,7 +299,7 @@ export class ModuleInstance< return snapshotGetter; } - getModule(): Module { + getModule(): Module { return this._module; } @@ -311,7 +335,7 @@ export class ModuleInstance< this.setModuleInstanceState(ModuleInstanceState.RESETTING); return new Promise((resolve) => { - this.setDefaultState(this._cachedDefaultState as TStateType, this._cachedStateStoreOptions); + this.initialize(); resolve(); }); } @@ -326,7 +350,7 @@ export class ModuleInstance< } export function useModuleInstanceTopicValue( - moduleInstance: ModuleInstance, + moduleInstance: ModuleInstance, topic: T ): ModuleInstanceTopicValueTypes[T] { const value = React.useSyncExternalStore( diff --git a/frontend/src/framework/ModuleRegistry.ts b/frontend/src/framework/ModuleRegistry.ts index 898cce124..c6fdc7766 100644 --- a/frontend/src/framework/ModuleRegistry.ts +++ b/frontend/src/framework/ModuleRegistry.ts @@ -1,10 +1,9 @@ import { ChannelDefinition, ChannelReceiverDefinition } from "./DataChannelTypes"; -import { AtomsInitialization, Module, ModuleCategory, ModuleDevState } from "./Module"; +import { InterfaceEffects, Module, ModuleCategory, ModuleDevState, ModuleInterfaceTypes } from "./Module"; import { ModuleDataTagId } from "./ModuleDataTags"; import { DrawPreviewFunc } from "./Preview"; -import { StateBaseType, StateOptions } from "./StateStore"; import { SyncSettingKey } from "./SyncSettings"; -import { InterfaceBaseType, InterfaceInitialization } from "./UniDirectionalModuleComponentsInterface"; +import { InterfaceInitialization } from "./UniDirectionalModuleComponentsInterface"; import { ModuleNotFoundPlaceholder } from "./internal/ModuleNotFoundPlaceholder"; export type RegisterModuleOptions = { @@ -31,19 +30,16 @@ export class ModuleNotFoundError extends Error { } export class ModuleRegistry { - private static _registeredModules: Record> = {}; - private static _moduleNotFoundPlaceholders: Record> = {}; + private static _registeredModules: Record> = {}; + private static _moduleNotFoundPlaceholders: Record> = {}; /* eslint-disable-next-line @typescript-eslint/no-empty-function */ private constructor() {} - static registerModule< - TStateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType = Record, - TSettingsAtomsType extends Record = Record, - TViewAtomsType extends Record = Record - >(options: RegisterModuleOptions): Module { - const module = new Module({ + static registerModule( + options: RegisterModuleOptions + ): Module { + const module = new Module({ name: options.moduleName, defaultTitle: options.defaultTitle, category: options.category, @@ -59,50 +55,52 @@ export class ModuleRegistry { return module; } - static initModule< - TStateType extends StateBaseType, - TInterfaceType extends InterfaceBaseType = Record, - TSettingsAtomsType extends Record = Record, - TViewAtomsType extends Record = Record - >( + static initModule( moduleName: string, - defaultState: TStateType, - options?: StateOptions, - interfaceInitialization?: InterfaceInitialization, - settingsAtomsInitialization?: AtomsInitialization, - viewAtomsInitialization?: AtomsInitialization - ): Module { + options: { + settingsToViewInterfaceInitialization?: TInterfaceTypes["settingsToView"] extends undefined + ? undefined + : InterfaceInitialization>; + viewToSettingsInterfaceInitialization?: TInterfaceTypes["viewToSettings"] extends undefined + ? undefined + : InterfaceInitialization>; + viewToSettingsInterfaceEffects?: InterfaceEffects>; + settingsToViewInterfaceEffects?: InterfaceEffects>; + } + ): Module { const module = this._registeredModules[moduleName]; if (module) { - module.setDefaultState(defaultState, options); - if (interfaceInitialization) { - module.setSettingsToViewInterfaceInitialization(interfaceInitialization); + if (options.settingsToViewInterfaceInitialization) { + module.setSettingsToViewInterfaceInitialization(options.settingsToViewInterfaceInitialization); + } + if (options.viewToSettingsInterfaceInitialization) { + module.setViewToSettingsInterfaceInitialization(options.viewToSettingsInterfaceInitialization); } - if (settingsAtomsInitialization) { - module.setSettingsAtomsInitialization(settingsAtomsInitialization); + if (options.viewToSettingsInterfaceEffects) { + module.setViewToSettingsInterfaceEffects(options.viewToSettingsInterfaceEffects); } - if (viewAtomsInitialization) { - module.setViewAtomsInitialization(viewAtomsInitialization); + if (options.settingsToViewInterfaceEffects) { + module.setSettingsToViewInterfaceEffects(options.settingsToViewInterfaceEffects); } - return module as Module; + return module as Module; } throw new ModuleNotFoundError(moduleName); } - static getModule(moduleName: string): Module { + static getModule(moduleName: string): Module { const module = this._registeredModules[moduleName]; if (module) { - return module as Module; + return module as Module; } const placeholder = this._moduleNotFoundPlaceholders[moduleName]; if (placeholder) { - return placeholder as Module; + return placeholder as Module; } this._moduleNotFoundPlaceholders[moduleName] = new ModuleNotFoundPlaceholder(moduleName); - return this._moduleNotFoundPlaceholders[moduleName] as Module; + return this._moduleNotFoundPlaceholders[moduleName] as Module; } - static getRegisteredModules(): Record> { + static getRegisteredModules(): Record> { return this._registeredModules; } } diff --git a/frontend/src/framework/StateStore.ts b/frontend/src/framework/StateStore.ts deleted file mode 100644 index f6ebfce30..000000000 --- a/frontend/src/framework/StateStore.ts +++ /dev/null @@ -1,104 +0,0 @@ -import React from "react"; - -import { isEqual } from "lodash"; - -export type StateBaseType = object; -export type StateOptions = { - [K in keyof T]?: { - deepCompare?: boolean; - }; -}; - -export class StateStore { - private _state: Record; - private _options?: StateOptions; - private _subscribersMap: Partial>>; - - constructor(defaultState: StateType, options?: StateOptions) { - this._state = defaultState; - this._subscribersMap = {}; - this._options = options; - } - - hasKey(key: keyof StateType): boolean { - return key in this._state; - } - - getValue(key: K): StateType[K] { - return this._state[key]; - } - - setValue(key: K, value: StateType[K]) { - if (this._state[key] === value) { - return; - } - - if (this._options && this._options[key]?.deepCompare) { - if (isEqual(value, this._state[key])) { - return; - } - } - - this._state[key] = value; - const subscribersSet = this._subscribersMap[key] || new Set(); - for (const cb of subscribersSet) { - cb(value); - } - } - - subscribe(key: K, cb: (value: StateType[K]) => void) { - const subscribersSet = this._subscribersMap[key] || new Set(); - subscribersSet.add(cb); - this._subscribersMap[key] = subscribersSet; - - // Trigger the callback immediately in case we have some late subscribers - cb(this._state[key]); - - return () => { - subscribersSet.delete(cb); - }; - } -} - -export function useStoreState( - stateStore: StateStore, - key: T -): [S[T], (value: S[T] | ((prev: S[T]) => S[T])) => void] { - const [state, setState] = React.useState(stateStore.getValue(key)); - - React.useEffect(() => { - const handleStateChange = (value: S[T]) => { - setState(value); - }; - - const unsubscribeFunc = stateStore.subscribe(key, handleStateChange); - return unsubscribeFunc; - }, [key, stateStore]); - - const setter = React.useCallback( - function setter(valueOrFunc: S[T] | ((prev: S[T]) => S[T])): void { - if (valueOrFunc instanceof Function) { - const value = stateStore.getValue(key); - stateStore.setValue(key, valueOrFunc(value)); - return; - } - stateStore.setValue(key, valueOrFunc); - }, - [key, stateStore] - ); - - return [state, setter]; -} - -export function useStoreValue(stateStore: StateStore, key: T): S[T] { - const [state] = useStoreState(stateStore, key); - return state; -} - -export function useSetStoreValue( - stateStore: StateStore, - key: T -): (value: S[T] | ((prev: S[T]) => S[T])) => void { - const [, setter] = useStoreState(stateStore, key); - return setter; -} diff --git a/frontend/src/framework/StatusWriter.ts b/frontend/src/framework/StatusWriter.ts index ef95c2fa6..201fa8715 100644 --- a/frontend/src/framework/StatusWriter.ts +++ b/frontend/src/framework/StatusWriter.ts @@ -65,7 +65,7 @@ export class SettingsStatusWriter { } } -export function useViewStatusWriter(viewContext: ViewContext): ViewStatusWriter { +export function useViewStatusWriter(viewContext: ViewContext): ViewStatusWriter { const statusController = viewContext.getStatusController(); const statusWriter = React.useRef(new ViewStatusWriter(statusController)); @@ -80,7 +80,7 @@ export function useViewStatusWriter(viewContext: ViewContext return statusWriter.current; } -export function useSettingsStatusWriter(settingsContext: SettingsContext): SettingsStatusWriter { +export function useSettingsStatusWriter(settingsContext: SettingsContext): SettingsStatusWriter { const statusController = settingsContext.getStatusController(); const statusWriter = React.useRef(new SettingsStatusWriter(statusController)); diff --git a/frontend/src/framework/SyncSettings.ts b/frontend/src/framework/SyncSettings.ts index 5565494ca..bc2e192eb 100644 --- a/frontend/src/framework/SyncSettings.ts +++ b/frontend/src/framework/SyncSettings.ts @@ -45,13 +45,13 @@ export const SyncSettingsMeta = { export class SyncSettingsHelper { private _workbenchServices: WorkbenchServices; - private _moduleContext: SettingsContext | ViewContext | null; + private _moduleContext: SettingsContext | ViewContext | null; private _activeSyncedKeys: SyncSettingKey[]; constructor( activeSyncedKeys: SyncSettingKey[], workbenchServices: WorkbenchServices, - moduleContext?: SettingsContext | ViewContext + moduleContext?: SettingsContext | ViewContext ) { this._activeSyncedKeys = activeSyncedKeys; this._workbenchServices = workbenchServices; diff --git a/frontend/src/framework/UniDirectionalModuleComponentsInterface.ts b/frontend/src/framework/UniDirectionalModuleComponentsInterface.ts index ce1c3f410..b770f7b9a 100644 --- a/frontend/src/framework/UniDirectionalModuleComponentsInterface.ts +++ b/frontend/src/framework/UniDirectionalModuleComponentsInterface.ts @@ -19,16 +19,16 @@ export class UniDirectionalModuleComponentsInterface(key: T): Atom { - const derivedAtom = this._atoms.get(key); - if (derivedAtom) { - return derivedAtom as Atom; + const atom = this._atoms.get(key); + if (atom) { + return atom as Atom; } throw new Error(`Atom for key '${String(key)}' not found`); } } -export function useSettingsToViewInterfaceValue( +export function useInterfaceValue( interfaceInstance: UniDirectionalModuleComponentsInterface, key: K ): InterfaceType[K] { diff --git a/frontend/src/framework/Workbench.ts b/frontend/src/framework/Workbench.ts index 0bd6e13b7..71cbfe105 100644 --- a/frontend/src/framework/Workbench.ts +++ b/frontend/src/framework/Workbench.ts @@ -40,7 +40,7 @@ export type StoredUserEnsembleSetting = { }; export class Workbench { - private _moduleInstances: ModuleInstance[]; + private _moduleInstances: ModuleInstance[]; private _workbenchSession: WorkbenchSessionPrivate; private _workbenchServices: PrivateWorkbenchServices; private _workbenchSettings: PrivateWorkbenchSettings; @@ -113,11 +113,11 @@ export class Workbench { }; } - getModuleInstances(): ModuleInstance[] { + getModuleInstances(): ModuleInstance[] { return this._moduleInstances; } - getModuleInstance(id: string): ModuleInstance | undefined { + getModuleInstance(id: string): ModuleInstance | undefined { return this._moduleInstances.find((moduleInstance) => moduleInstance.getId() === id); } @@ -163,7 +163,7 @@ export class Workbench { this.notifySubscribers(WorkbenchEvents.ModuleInstancesChanged); } - makeAndAddModuleInstance(moduleName: string, layout: LayoutElement): ModuleInstance { + makeAndAddModuleInstance(moduleName: string, layout: LayoutElement): ModuleInstance { const module = ModuleRegistry.getModule(moduleName); if (!module) { throw new Error(`Module ${moduleName} not found`); diff --git a/frontend/src/framework/internal/ModuleNotFoundPlaceholder.tsx b/frontend/src/framework/internal/ModuleNotFoundPlaceholder.tsx index 349488bad..679fb9db8 100644 --- a/frontend/src/framework/internal/ModuleNotFoundPlaceholder.tsx +++ b/frontend/src/framework/internal/ModuleNotFoundPlaceholder.tsx @@ -4,12 +4,7 @@ import { Button } from "@lib/components/Button"; import { Tag } from "@lib/components/Tag"; import { BugReport, Forum, WebAssetOff } from "@mui/icons-material"; -export class ModuleNotFoundPlaceholder extends Module< - Record, - Record, - Record, - Record -> { +export class ModuleNotFoundPlaceholder extends Module { constructor(moduleName: string) { super({ name: moduleName, @@ -20,11 +15,8 @@ export class ModuleNotFoundPlaceholder extends Module< this._importState = ImportState.Imported; } - makeInstance( - instanceNumber: number - ): ModuleInstance, Record, Record, Record> { + makeInstance(instanceNumber: number): ModuleInstance { const instance = super.makeInstance(instanceNumber); - instance.setDefaultState({}); return instance; } diff --git a/frontend/src/framework/internal/components/ApplyInterfaceEffects/applyInterfaceEffects.tsx b/frontend/src/framework/internal/components/ApplyInterfaceEffects/applyInterfaceEffects.tsx new file mode 100644 index 000000000..e96822f62 --- /dev/null +++ b/frontend/src/framework/internal/components/ApplyInterfaceEffects/applyInterfaceEffects.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +import { ModuleInterfaceTypes } from "@framework/Module"; +import { ModuleInstance } from "@framework/ModuleInstance"; + +import { useAtom } from "jotai"; + +export type ApplyInterfaceEffectsProps = { + moduleInstance: ModuleInstance; + children?: React.ReactNode; +}; + +export function ApplyInterfaceEffectsToView( + props: ApplyInterfaceEffectsProps +) { + useAtom(props.moduleInstance.getSettingsToViewInterfaceEffectsAtom()); + return <>{props.children}; +} + +export function ApplyInterfaceEffectsToSettings( + props: ApplyInterfaceEffectsProps +) { + useAtom(props.moduleInstance.getViewToSettingsInterfaceEffectsAtom()); + return <>{props.children}; +} diff --git a/frontend/src/framework/internal/components/ApplyInterfaceEffects/index.ts b/frontend/src/framework/internal/components/ApplyInterfaceEffects/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/channelReceiverNodesWrapper.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/channelReceiverNodesWrapper.tsx index ad202abe6..7c450b3c5 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/channelReceiverNodesWrapper.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/channelReceiverNodesWrapper.tsx @@ -14,7 +14,7 @@ import { ChannelReceiverNode } from "./channelReceiverNode"; export type ChannelReceiverNodesWrapperProps = { forwardedRef: React.RefObject; - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; workbench: Workbench; }; diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/header.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/header.tsx index 90c74a77b..3266bbb50 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/header.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/header.tsx @@ -22,7 +22,7 @@ import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { Close, Error, History, Input, Output, Warning } from "@mui/icons-material"; export type HeaderProps = { - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; isDragged: boolean; onPointerDown: (event: React.PointerEvent) => void; onRemoveClick: (event: React.PointerEvent) => void; diff --git a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/viewContent.tsx b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/viewContent.tsx index c0b959645..d1f0b2f6c 100644 --- a/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/viewContent.tsx +++ b/frontend/src/framework/internal/components/Content/private-components/ViewWrapper/private-components/viewContent.tsx @@ -9,6 +9,7 @@ import { } from "@framework/ModuleInstance"; import { StatusSource } from "@framework/ModuleInstanceStatusController"; import { Workbench } from "@framework/Workbench"; +import { ApplyInterfaceEffectsToView } from "@framework/internal/components/ApplyInterfaceEffects/applyInterfaceEffects"; import { DebugProfiler } from "@framework/internal/components/DebugProfiler"; import { ErrorBoundary } from "@framework/internal/components/ErrorBoundary"; import { HydrateQueryClientAtom } from "@framework/internal/components/HydrateQueryClientAtom"; @@ -19,7 +20,7 @@ import { Provider } from "jotai"; import { CrashView } from "./crashView"; type ViewContentProps = { - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; workbench: Workbench; }; @@ -27,7 +28,7 @@ export const ViewContent = React.memo((props: ViewContentProps) => { const importState = useModuleInstanceTopicValue(props.moduleInstance, ModuleInstanceTopic.IMPORT_STATE); const moduleInstanceState = useModuleInstanceTopicValue(props.moduleInstance, ModuleInstanceTopic.STATE); - const atomStore = props.moduleInstance.getAtomStore(); + const atomStore = props.workbench.getAtomStoreMaster().getAtomStoreForModuleInstance(props.moduleInstance.getId()); const handleModuleInstanceReload = React.useCallback( function handleModuleInstanceReload() { @@ -105,13 +106,15 @@ export const ViewContent = React.memo((props: ViewContentProps) => { > - + + + 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 b7b4440f5..597e045be 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 @@ -15,7 +15,7 @@ import { ViewWrapperPlaceholder } from "../viewWrapperPlaceholder"; type ViewWrapperProps = { isActive: boolean; - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; workbench: Workbench; width: number; height: number; diff --git a/frontend/src/framework/internal/components/ErrorBoundary/errorBoundary.tsx b/frontend/src/framework/internal/components/ErrorBoundary/errorBoundary.tsx index 813bff763..f9497bf5b 100644 --- a/frontend/src/framework/internal/components/ErrorBoundary/errorBoundary.tsx +++ b/frontend/src/framework/internal/components/ErrorBoundary/errorBoundary.tsx @@ -3,7 +3,7 @@ import React from "react"; import { ModuleInstance } from "@framework/ModuleInstance"; export type Props = { - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; children?: React.ReactNode; }; diff --git a/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/moduleSettings.tsx b/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/moduleSettings.tsx index c393ebaf6..65bdef672 100644 --- a/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/moduleSettings.tsx +++ b/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/moduleSettings.tsx @@ -16,11 +16,12 @@ import { Settings as SettingsIcon } from "@mui/icons-material"; import { Provider } from "jotai"; +import { ApplyInterfaceEffectsToSettings } from "../../ApplyInterfaceEffects/applyInterfaceEffects"; import { DebugProfiler } from "../../DebugProfiler"; import { HydrateQueryClientAtom } from "../../HydrateQueryClientAtom"; type ModuleSettingsProps = { - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; activeModuleInstanceId: string; workbench: Workbench; }; @@ -28,7 +29,7 @@ type ModuleSettingsProps = { export const ModuleSettings: React.FC = (props) => { const importState = useModuleInstanceTopicValue(props.moduleInstance, ModuleInstanceTopic.IMPORT_STATE); const moduleInstanceState = useModuleInstanceTopicValue(props.moduleInstance, ModuleInstanceTopic.STATE); - const atomStore = props.moduleInstance.getAtomStore(); + const atomStore = props.workbench.getAtomStoreMaster().getAtomStoreForModuleInstance(props.moduleInstance.getId()); if (importState !== ImportState.Imported || !props.moduleInstance.isInitialized()) { return null; @@ -93,13 +94,15 @@ export const ModuleSettings: React.FC = (props) => { > - + + + diff --git a/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/modulesList.tsx b/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/modulesList.tsx index 82d5ca257..7c2a13a49 100644 --- a/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/modulesList.tsx +++ b/frontend/src/framework/internal/components/LeftSettingsPanel/private-components/modulesList.tsx @@ -350,7 +350,7 @@ function makeDevStateIcon(devState: ModuleDevState): React.ReactNode { } type DetailsPopupProps = { - module: Module; + module: Module; left: number; top: number; onClose: () => void; diff --git a/frontend/src/framework/internal/components/RightSettingsPanel/private-components/ModuleInstanceLog/moduleInstanceLog.tsx b/frontend/src/framework/internal/components/RightSettingsPanel/private-components/ModuleInstanceLog/moduleInstanceLog.tsx index 4c7944240..cc4ce2077 100644 --- a/frontend/src/framework/internal/components/RightSettingsPanel/private-components/ModuleInstanceLog/moduleInstanceLog.tsx +++ b/frontend/src/framework/internal/components/RightSettingsPanel/private-components/ModuleInstanceLog/moduleInstanceLog.tsx @@ -162,7 +162,7 @@ export function ModuleInstanceLog(props: ModuleInstanceLogProps): React.ReactNod type LogListProps = { onShowDetails: (details: Record, yPos: number) => void; onHideDetails: () => void; - moduleInstance: ModuleInstance; + moduleInstance: ModuleInstance; }; function LogList(props: LogListProps): React.ReactNode { diff --git a/frontend/src/framework/internal/hooks/workbenchHooks.ts b/frontend/src/framework/internal/hooks/workbenchHooks.ts index 978b1c11f..3fbba8d1c 100644 --- a/frontend/src/framework/internal/hooks/workbenchHooks.ts +++ b/frontend/src/framework/internal/hooks/workbenchHooks.ts @@ -3,8 +3,8 @@ import React from "react"; import { ModuleInstance } from "@framework/ModuleInstance"; import { Workbench, WorkbenchEvents } from "@framework/Workbench"; -export function useModuleInstances(workbench: Workbench): ModuleInstance[] { - const [moduleInstances, setModuleInstances] = React.useState[]>([]); +export function useModuleInstances(workbench: Workbench): ModuleInstance[] { + const [moduleInstances, setModuleInstances] = React.useState[]>([]); React.useEffect(() => { function handleModuleInstancesChange() { diff --git a/frontend/src/modules/3DViewer/settingsToViewInterface.ts b/frontend/src/modules/3DViewer/interfaces.ts similarity index 52% rename from frontend/src/modules/3DViewer/settingsToViewInterface.ts rename to frontend/src/modules/3DViewer/interfaces.ts index bde016138..b74c6fb9f 100644 --- a/frontend/src/modules/3DViewer/settingsToViewInterface.ts +++ b/frontend/src/modules/3DViewer/interfaces.ts @@ -1,27 +1,45 @@ import { BoundingBox3d_api } from "@api"; +import { EnsembleIdent } from "@framework/EnsembleIdent"; import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; +import { IntersectionType } from "@framework/types/intersection"; import { ColorScale } from "@lib/utils/ColorScale"; import { + addCustomIntersectionPolylineEditModeActiveAtom, colorScaleAtom, + editCustomIntersectionPolylineEditModeActiveAtom, gridLayerAtom, intersectionExtensionLengthAtom, + intersectionTypeAtom, showGridlinesAtom, showIntersectionAtom, useCustomBoundsAtom, } from "./settings/atoms/baseAtoms"; import { + selectedCustomIntersectionPolylineIdAtom, + selectedEnsembleIdentAtom, selectedGridCellIndexRangesAtom, selectedGridModelBoundingBox3dAtom, selectedGridModelNameAtom, selectedGridModelParameterDateOrIntervalAtom, selectedGridModelParameterNameAtom, + selectedHighlightedWellboreUuidAtom, selectedRealizationAtom, selectedWellboreUuidsAtom, } from "./settings/atoms/derivedAtoms"; import { GridCellIndexRanges } from "./typesAndEnums"; +import { + editCustomIntersectionPolylineEditModeActiveAtom as viewEditCustomIntersectionPolylineEditModeActiveAtom, + intersectionTypeAtom as viewIntersectionTypeAtom, +} from "./view/atoms/baseAtoms"; export type SettingsToViewInterface = { + ensembleIdent: EnsembleIdent | null; + highlightedWellboreUuid: string | null; + customIntersectionPolylineId: string | null; + intersectionType: IntersectionType; + addCustomIntersectionPolylineEditModeActive: boolean; + editCustomIntersectionPolylineEditModeActive: boolean; showGridlines: boolean; showIntersection: boolean; gridLayer: number; @@ -37,7 +55,35 @@ export type SettingsToViewInterface = { gridCellIndexRanges: GridCellIndexRanges; }; -export const interfaceInitialization: InterfaceInitialization = { +export type ViewToSettingsInterface = { + editCustomIntersectionPolylineEditModeActive: boolean; + intersectionType: IntersectionType; +}; + +export type Interfaces = { + settingsToView: SettingsToViewInterface; + viewToSettings: ViewToSettingsInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + ensembleIdent: (get) => { + return get(selectedEnsembleIdentAtom); + }, + highlightedWellboreUuid: (get) => { + return get(selectedHighlightedWellboreUuidAtom); + }, + customIntersectionPolylineId: (get) => { + return get(selectedCustomIntersectionPolylineIdAtom); + }, + intersectionType: (get) => { + return get(intersectionTypeAtom); + }, + addCustomIntersectionPolylineEditModeActive: (get) => { + return get(addCustomIntersectionPolylineEditModeActiveAtom); + }, + editCustomIntersectionPolylineEditModeActive: (get) => { + return get(editCustomIntersectionPolylineEditModeActiveAtom); + }, showGridlines: (get) => { return get(showGridlinesAtom); }, @@ -78,3 +124,12 @@ export const interfaceInitialization: InterfaceInitialization = { + editCustomIntersectionPolylineEditModeActive: (get) => { + return get(viewEditCustomIntersectionPolylineEditModeActiveAtom); + }, + intersectionType: (get) => { + return get(viewIntersectionTypeAtom); + }, +}; diff --git a/frontend/src/modules/3DViewer/loadModule.tsx b/frontend/src/modules/3DViewer/loadModule.tsx index 5853960e2..36deb122a 100644 --- a/frontend/src/modules/3DViewer/loadModule.tsx +++ b/frontend/src/modules/3DViewer/loadModule.tsx @@ -1,12 +1,18 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; +import { Interfaces, settingsToViewInterfaceInitialization, viewToSettingsInterfaceInitialization } from "./interfaces"; import { MODULE_NAME } from "./registerModule"; +import { viewToSettingsInterfaceEffects } from "./settings/atoms/interfaceEffects"; import { Settings } from "./settings/settings"; -import { SettingsToViewInterface, interfaceInitialization } from "./settingsToViewInterface"; -import { State } from "./state"; +import { settingsToViewInterfaceEffects } from "./view/atoms/interfaceEffects"; import { View } from "./view/view"; -const module = ModuleRegistry.initModule(MODULE_NAME, {}, {}, interfaceInitialization); +const module = ModuleRegistry.initModule(MODULE_NAME, { + settingsToViewInterfaceInitialization, + viewToSettingsInterfaceInitialization, + viewToSettingsInterfaceEffects, + settingsToViewInterfaceEffects, +}); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/3DViewer/registerModule.ts b/frontend/src/modules/3DViewer/registerModule.ts index 2c9f9a3f3..86b8ec946 100644 --- a/frontend/src/modules/3DViewer/registerModule.ts +++ b/frontend/src/modules/3DViewer/registerModule.ts @@ -3,15 +3,14 @@ import { ModuleDataTagId } from "@framework/ModuleDataTags"; import { ModuleRegistry } from "@framework/ModuleRegistry"; import { SyncSettingKey } from "@framework/SyncSettings"; +import { Interfaces } from "./interfaces"; import { preview } from "./preview"; -import { SettingsToViewInterface } from "./settingsToViewInterface"; -import { State } from "./state"; export const MODULE_NAME = "3DViewer"; const description = "Generic 3D viewer for grid, surfaces, and wells."; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: MODULE_NAME, defaultTitle: "3D Viewer", category: ModuleCategory.MAIN, diff --git a/frontend/src/modules/3DViewer/settings/atoms/baseAtoms.ts b/frontend/src/modules/3DViewer/settings/atoms/baseAtoms.ts index 21a6d836e..72c62537b 100644 --- a/frontend/src/modules/3DViewer/settings/atoms/baseAtoms.ts +++ b/frontend/src/modules/3DViewer/settings/atoms/baseAtoms.ts @@ -1,4 +1,5 @@ import { EnsembleIdent } from "@framework/EnsembleIdent"; +import { IntersectionType } from "@framework/types/intersection"; import { ColorScale } from "@lib/utils/ColorScale"; import { GridCellIndexRanges } from "@modules/3DViewer/typesAndEnums"; @@ -10,6 +11,10 @@ export const gridLayerAtom = atom(1); export const intersectionExtensionLengthAtom = atom(1000); export const colorScaleAtom = atom(null); export const useCustomBoundsAtom = atom(false); +export const intersectionTypeAtom = atom(IntersectionType.WELLBORE); +export const addCustomIntersectionPolylineEditModeActiveAtom = atom(false); +export const editCustomIntersectionPolylineEditModeActiveAtom = atom(false); +export const currentCustomIntersectionPolylineAtom = atom([]); export const userSelectedEnsembleIdentAtom = atom(null); export const userSelectedRealizationAtom = atom(null); diff --git a/frontend/src/modules/3DViewer/settings/atoms/derivedAtoms.ts b/frontend/src/modules/3DViewer/settings/atoms/derivedAtoms.ts index af39bf4f3..a646a9785 100644 --- a/frontend/src/modules/3DViewer/settings/atoms/derivedAtoms.ts +++ b/frontend/src/modules/3DViewer/settings/atoms/derivedAtoms.ts @@ -2,21 +2,70 @@ import { Grid3dDimensions_api } from "@api"; import { EnsembleIdent } from "@framework/EnsembleIdent"; import { EnsembleRealizationFilterFunctionAtom, EnsembleSetAtom } from "@framework/GlobalAtoms"; import { IntersectionPolylinesAtom } from "@framework/userCreatedItems/IntersectionPolylines"; -import { selectedEnsembleIdentAtom } from "@modules/3DViewer/sharedAtoms/sharedAtoms"; import { GridCellIndexRanges } from "@modules/3DViewer/typesAndEnums"; import { atom } from "jotai"; import { + userSelectedCustomIntersectionPolylineIdAtom, + userSelectedEnsembleIdentAtom, userSelectedGridCellIndexRangesAtom, userSelectedGridModelNameAtom, userSelectedGridModelParameterDateOrIntervalAtom, userSelectedGridModelParameterNameAtom, + userSelectedHighlightedWellboreUuidAtom, userSelectedRealizationAtom, userSelectedWellboreUuidsAtom, } from "./baseAtoms"; import { drilledWellboreHeadersQueryAtom, gridModelInfosQueryAtom } from "./queryAtoms"; +export const selectedEnsembleIdentAtom = atom((get) => { + const ensembleSet = get(EnsembleSetAtom); + const userSelectedEnsembleIdent = get(userSelectedEnsembleIdentAtom); + + if (userSelectedEnsembleIdent === null || !ensembleSet.hasEnsemble(userSelectedEnsembleIdent)) { + return ensembleSet.getEnsembleArr()[0]?.getIdent() || null; + } + + return userSelectedEnsembleIdent; +}); + +export const selectedHighlightedWellboreUuidAtom = atom((get) => { + const userSelectedHighlightedWellboreUuid = get(userSelectedHighlightedWellboreUuidAtom); + const wellboreHeaders = get(drilledWellboreHeadersQueryAtom); + + if (!wellboreHeaders.data) { + return null; + } + + if ( + !userSelectedHighlightedWellboreUuid || + !wellboreHeaders.data.some((el) => el.wellboreUuid === userSelectedHighlightedWellboreUuid) + ) { + return wellboreHeaders.data[0]?.wellboreUuid ?? null; + } + + return userSelectedHighlightedWellboreUuid; +}); + +export const selectedCustomIntersectionPolylineIdAtom = atom((get) => { + const userSelectedCustomIntersectionPolylineId = get(userSelectedCustomIntersectionPolylineIdAtom); + const customIntersectionPolylines = get(IntersectionPolylinesAtom); + + if (!customIntersectionPolylines.length) { + return null; + } + + if ( + !userSelectedCustomIntersectionPolylineId || + !customIntersectionPolylines.some((el) => el.id === userSelectedCustomIntersectionPolylineId) + ) { + return customIntersectionPolylines[0].id; + } + + return userSelectedCustomIntersectionPolylineId; +}); + export const availableRealizationsAtom = atom((get) => { const ensembleSet = get(EnsembleSetAtom); const selectedEnsembleIdent = get(selectedEnsembleIdentAtom); diff --git a/frontend/src/modules/3DViewer/settings/atoms/interfaceEffects.ts b/frontend/src/modules/3DViewer/settings/atoms/interfaceEffects.ts new file mode 100644 index 000000000..c0a351aff --- /dev/null +++ b/frontend/src/modules/3DViewer/settings/atoms/interfaceEffects.ts @@ -0,0 +1,17 @@ +import { InterfaceEffects } from "@framework/Module"; +import { ViewToSettingsInterface } from "@modules/3DViewer/interfaces"; + +import { editCustomIntersectionPolylineEditModeActiveAtom, intersectionTypeAtom } from "./baseAtoms"; + +export const viewToSettingsInterfaceEffects: InterfaceEffects = [ + (getInterfaceValue, setAtomValue) => { + const editCustomIntersectionPolylineEditModeActive = getInterfaceValue( + "editCustomIntersectionPolylineEditModeActive" + ); + setAtomValue(editCustomIntersectionPolylineEditModeActiveAtom, editCustomIntersectionPolylineEditModeActive); + }, + (getInterfaceValue, setAtomValue) => { + const viewIntersectionType = getInterfaceValue("intersectionType"); + setAtomValue(intersectionTypeAtom, viewIntersectionType); + }, +]; diff --git a/frontend/src/modules/3DViewer/settings/atoms/queryAtoms.ts b/frontend/src/modules/3DViewer/settings/atoms/queryAtoms.ts index 8b7c2b8df..9c95115a2 100644 --- a/frontend/src/modules/3DViewer/settings/atoms/queryAtoms.ts +++ b/frontend/src/modules/3DViewer/settings/atoms/queryAtoms.ts @@ -1,10 +1,9 @@ import { apiService } from "@framework/ApiService"; import { EnsembleSetAtom } from "@framework/GlobalAtoms"; -import { selectedEnsembleIdentAtom } from "@modules/3DViewer/sharedAtoms/sharedAtoms"; import { atomWithQuery } from "jotai-tanstack-query"; -import { selectedRealizationAtom } from "./derivedAtoms"; +import { selectedEnsembleIdentAtom, selectedRealizationAtom } from "./derivedAtoms"; const STALE_TIME = 60 * 1000; const CACHE_TIME = 60 * 1000; diff --git a/frontend/src/modules/3DViewer/settings/settings.tsx b/frontend/src/modules/3DViewer/settings/settings.tsx index d6173c455..f02cdb7d1 100644 --- a/frontend/src/modules/3DViewer/settings/settings.tsx +++ b/frontend/src/modules/3DViewer/settings/settings.tsx @@ -30,8 +30,11 @@ import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { isEqual } from "lodash"; import { + addCustomIntersectionPolylineEditModeActiveAtom, colorScaleAtom, + editCustomIntersectionPolylineEditModeActiveAtom, intersectionExtensionLengthAtom, + intersectionTypeAtom, showGridlinesAtom, showIntersectionAtom, useCustomBoundsAtom, @@ -48,10 +51,12 @@ import { import { availableRealizationsAtom, gridModelDimensionsAtom, + selectedEnsembleIdentAtom, selectedGridCellIndexRangesAtom, selectedGridModelNameAtom, selectedGridModelParameterDateOrIntervalAtom, selectedGridModelParameterNameAtom, + selectedHighlightedWellboreUuidAtom, selectedRealizationAtom, selectedWellboreUuidsAtom, } from "./atoms/derivedAtoms"; @@ -59,18 +64,10 @@ import { drilledWellboreHeadersQueryAtom, gridModelInfosQueryAtom } from "./atom import { GridCellIndexFilter } from "./components/gridCellIndexFilter"; import { WellboreSelector } from "./components/wellboreSelector"; -import { SettingsToViewInterface } from "../settingsToViewInterface"; -import { - addCustomIntersectionPolylineEditModeActiveAtom, - editCustomIntersectionPolylineEditModeActiveAtom, - intersectionTypeAtom, - selectedEnsembleIdentAtom, - selectedHighlightedWellboreUuidAtom, -} from "../sharedAtoms/sharedAtoms"; -import { State } from "../state"; +import { Interfaces } from "../interfaces"; import { GridCellIndexRanges } from "../typesAndEnums"; -export function Settings(props: ModuleSettingsProps): JSX.Element { +export function Settings(props: ModuleSettingsProps): JSX.Element { const ensembleSet = useEnsembleSet(props.workbenchSession); const statusWriter = useSettingsStatusWriter(props.settingsContext); diff --git a/frontend/src/modules/3DViewer/sharedAtoms/sharedAtoms.ts b/frontend/src/modules/3DViewer/sharedAtoms/sharedAtoms.ts deleted file mode 100644 index 97b851aef..000000000 --- a/frontend/src/modules/3DViewer/sharedAtoms/sharedAtoms.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* -Note that shared atoms is just a temporary solution to a use case that does not have a clear solution yet. -This is not how it should be done properly, communication between settings and view components should be done -through the use of interfaces. -*/ -import { EnsembleIdent } from "@framework/EnsembleIdent"; -import { EnsembleSetAtom } from "@framework/GlobalAtoms"; -import { IntersectionType } from "@framework/types/intersection"; -import { IntersectionPolylinesAtom } from "@framework/userCreatedItems/IntersectionPolylines"; - -import { atom } from "jotai"; - -import { - userSelectedCustomIntersectionPolylineIdAtom, - userSelectedEnsembleIdentAtom, - userSelectedHighlightedWellboreUuidAtom, -} from "../settings/atoms/baseAtoms"; -import { drilledWellboreHeadersQueryAtom } from "../settings/atoms/queryAtoms"; - -export const selectedEnsembleIdentAtom = atom((get) => { - const ensembleSet = get(EnsembleSetAtom); - const userSelectedEnsembleIdent = get(userSelectedEnsembleIdentAtom); - - if (userSelectedEnsembleIdent === null || !ensembleSet.hasEnsemble(userSelectedEnsembleIdent)) { - return ensembleSet.getEnsembleArr()[0]?.getIdent() || null; - } - - return userSelectedEnsembleIdent; -}); - -export const selectedHighlightedWellboreUuidAtom = atom((get) => { - const userSelectedHighlightedWellboreUuid = get(userSelectedHighlightedWellboreUuidAtom); - const wellboreHeaders = get(drilledWellboreHeadersQueryAtom); - - if (!wellboreHeaders.data) { - return null; - } - - if ( - !userSelectedHighlightedWellboreUuid || - !wellboreHeaders.data.some((el) => el.wellboreUuid === userSelectedHighlightedWellboreUuid) - ) { - return wellboreHeaders.data[0]?.wellboreUuid ?? null; - } - - return userSelectedHighlightedWellboreUuid; -}); - -export const intersectionTypeAtom = atom(IntersectionType.WELLBORE); -export const addCustomIntersectionPolylineEditModeActiveAtom = atom(false); -export const editCustomIntersectionPolylineEditModeActiveAtom = atom(false); - -export const currentCustomIntersectionPolylineAtom = atom([]); - -export const selectedCustomIntersectionPolylineIdAtom = atom((get) => { - const userSelectedCustomIntersectionPolylineId = get(userSelectedCustomIntersectionPolylineIdAtom); - const customIntersectionPolylines = get(IntersectionPolylinesAtom); - - if (!customIntersectionPolylines.length) { - return null; - } - - if ( - !userSelectedCustomIntersectionPolylineId || - !customIntersectionPolylines.some((el) => el.id === userSelectedCustomIntersectionPolylineId) - ) { - return customIntersectionPolylines[0].id; - } - - return userSelectedCustomIntersectionPolylineId; -}); diff --git a/frontend/src/modules/3DViewer/state.ts b/frontend/src/modules/3DViewer/state.ts deleted file mode 100644 index 90ff7f709..000000000 --- a/frontend/src/modules/3DViewer/state.ts +++ /dev/null @@ -1 +0,0 @@ -export type State = Record; diff --git a/frontend/src/modules/3DViewer/typesAndEnums.ts b/frontend/src/modules/3DViewer/typesAndEnums.ts index 209be6c6b..de3bd1dc5 100644 --- a/frontend/src/modules/3DViewer/typesAndEnums.ts +++ b/frontend/src/modules/3DViewer/typesAndEnums.ts @@ -1,14 +1,3 @@ -export enum IntersectionType { - CUSTOM_POLYLINE = "custom-polyline", - WELLBORE = "wellbore", -} - -export type CustomIntersectionPolyline = { - id: string; - name: string; - polyline: number[][]; -}; - export type GridCellIndexRanges = { i: [number, number]; j: [number, number]; diff --git a/frontend/src/modules/3DViewer/view/atoms/baseAtoms.ts b/frontend/src/modules/3DViewer/view/atoms/baseAtoms.ts new file mode 100644 index 000000000..938939ea9 --- /dev/null +++ b/frontend/src/modules/3DViewer/view/atoms/baseAtoms.ts @@ -0,0 +1,11 @@ +import { EnsembleIdent } from "@framework/EnsembleIdent"; +import { IntersectionType } from "@framework/types/intersection"; + +import { atom } from "jotai"; + +export const intersectionTypeAtom = atom(IntersectionType.WELLBORE); +export const editCustomIntersectionPolylineEditModeActiveAtom = atom(false); + +export const ensembleIdentAtom = atom(null); +export const highlightedWellboreUuidAtom = atom(null); +export const customIntersectionPolylineIdAtom = atom(null); diff --git a/frontend/src/modules/3DViewer/view/atoms/derivedAtoms.ts b/frontend/src/modules/3DViewer/view/atoms/derivedAtoms.ts index ba6b98cb7..5bf0c16c5 100644 --- a/frontend/src/modules/3DViewer/view/atoms/derivedAtoms.ts +++ b/frontend/src/modules/3DViewer/view/atoms/derivedAtoms.ts @@ -1,28 +1,17 @@ import { IntersectionReferenceSystem } from "@equinor/esv-intersection"; import { IntersectionType } from "@framework/types/intersection"; import { IntersectionPolylinesAtom } from "@framework/userCreatedItems/IntersectionPolylines"; -import { - intersectionTypeAtom, - selectedCustomIntersectionPolylineIdAtom, - selectedHighlightedWellboreUuidAtom, -} from "@modules/3DViewer/sharedAtoms/sharedAtoms"; import { atom } from "jotai"; +import { customIntersectionPolylineIdAtom, highlightedWellboreUuidAtom, intersectionTypeAtom } from "./baseAtoms"; import { fieldWellboreTrajectoriesQueryAtom } from "./queryAtoms"; -export const selectedCustomIntersectionPolylineAtom = atom((get) => { - const customIntersectionPolylineId = get(selectedCustomIntersectionPolylineIdAtom); - const customIntersectionPolylines = get(IntersectionPolylinesAtom); - - return customIntersectionPolylines.find((el) => el.id === customIntersectionPolylineId); -}); - export const intersectionReferenceSystemAtom = atom((get) => { const fieldWellboreTrajectories = get(fieldWellboreTrajectoriesQueryAtom); - const wellboreUuid = get(selectedHighlightedWellboreUuidAtom); + const wellboreUuid = get(highlightedWellboreUuidAtom); const customIntersectionPolylines = get(IntersectionPolylinesAtom); - const customIntersectionPolylineId = get(selectedCustomIntersectionPolylineIdAtom); + const customIntersectionPolylineId = get(customIntersectionPolylineIdAtom); const customIntersectionPolyline = customIntersectionPolylines.find((el) => el.id === customIntersectionPolylineId); @@ -66,3 +55,10 @@ export const intersectionReferenceSystemAtom = atom((get) => { return null; }); + +export const selectedCustomIntersectionPolylineAtom = atom((get) => { + const customIntersectionPolylineId = get(customIntersectionPolylineIdAtom); + const customIntersectionPolylines = get(IntersectionPolylinesAtom); + + return customIntersectionPolylines.find((el) => el.id === customIntersectionPolylineId) ?? null; +}); diff --git a/frontend/src/modules/3DViewer/view/atoms/interfaceEffects.ts b/frontend/src/modules/3DViewer/view/atoms/interfaceEffects.ts new file mode 100644 index 000000000..d18ff31f7 --- /dev/null +++ b/frontend/src/modules/3DViewer/view/atoms/interfaceEffects.ts @@ -0,0 +1,28 @@ +import { InterfaceEffects } from "@framework/Module"; +import { SettingsToViewInterface } from "@modules/3DViewer/interfaces"; + +import { + customIntersectionPolylineIdAtom, + ensembleIdentAtom, + highlightedWellboreUuidAtom, + intersectionTypeAtom, +} from "./baseAtoms"; + +export const settingsToViewInterfaceEffects: InterfaceEffects = [ + (getInterfaceValue, setAtomValue) => { + const ensembleIdent = getInterfaceValue("ensembleIdent"); + setAtomValue(ensembleIdentAtom, ensembleIdent); + }, + (getInterfaceValue, setAtomValue) => { + const highlightedWellboreUuid = getInterfaceValue("highlightedWellboreUuid"); + setAtomValue(highlightedWellboreUuidAtom, highlightedWellboreUuid); + }, + (getInterfaceValue, setAtomValue) => { + const customIntersectionPolylineId = getInterfaceValue("customIntersectionPolylineId"); + setAtomValue(customIntersectionPolylineIdAtom, customIntersectionPolylineId); + }, + (getInterfaceValue, setAtomValue) => { + const intersectionType = getInterfaceValue("intersectionType"); + setAtomValue(intersectionTypeAtom, intersectionType); + }, +]; diff --git a/frontend/src/modules/3DViewer/view/atoms/queryAtoms.ts b/frontend/src/modules/3DViewer/view/atoms/queryAtoms.ts index 5a1e62160..f7e220120 100644 --- a/frontend/src/modules/3DViewer/view/atoms/queryAtoms.ts +++ b/frontend/src/modules/3DViewer/view/atoms/queryAtoms.ts @@ -1,14 +1,15 @@ import { apiService } from "@framework/ApiService"; import { EnsembleSetAtom } from "@framework/GlobalAtoms"; -import { selectedEnsembleIdentAtom } from "@modules/3DViewer/sharedAtoms/sharedAtoms"; import { atomWithQuery } from "jotai-tanstack-query"; +import { ensembleIdentAtom } from "./baseAtoms"; + const STALE_TIME = 60 * 1000; const CACHE_TIME = 60 * 1000; export const fieldWellboreTrajectoriesQueryAtom = atomWithQuery((get) => { - const ensembleIdent = get(selectedEnsembleIdentAtom); + const ensembleIdent = get(ensembleIdentAtom); const ensembleSet = get(EnsembleSetAtom); let fieldIdentifier: string | null = null; diff --git a/frontend/src/modules/3DViewer/view/components/HoverUpdateWrapper.tsx b/frontend/src/modules/3DViewer/view/components/HoverUpdateWrapper.tsx index f8a1ee8af..381799b0a 100644 --- a/frontend/src/modules/3DViewer/view/components/HoverUpdateWrapper.tsx +++ b/frontend/src/modules/3DViewer/view/components/HoverUpdateWrapper.tsx @@ -13,7 +13,7 @@ export type HoverUpdateWrapperProps = { wellboreUuid: string | null; intersectionReferenceSystem?: IntersectionReferenceSystem; workbenchServices: WorkbenchServices; - viewContext: ViewContext; + viewContext: ViewContext; } & SubsurfaceViewerWrapperProps; export function HoverUpdateWrapper(props: HoverUpdateWrapperProps): React.ReactNode { diff --git a/frontend/src/modules/3DViewer/view/components/SyncedSettingsUpdateWrapper.tsx b/frontend/src/modules/3DViewer/view/components/SyncedSettingsUpdateWrapper.tsx index dd88870c3..41d669c47 100644 --- a/frontend/src/modules/3DViewer/view/components/SyncedSettingsUpdateWrapper.tsx +++ b/frontend/src/modules/3DViewer/view/components/SyncedSettingsUpdateWrapper.tsx @@ -8,7 +8,7 @@ import { HoverUpdateWrapper, HoverUpdateWrapperProps } from "./HoverUpdateWrappe export type SyncedSettingsUpdateWrapperProps = { workbenchServices: WorkbenchServices; - viewContext: ViewContext; + viewContext: ViewContext; } & HoverUpdateWrapperProps; export function SyncedSettingsUpdateWrapper(props: SyncedSettingsUpdateWrapperProps): React.ReactNode { diff --git a/frontend/src/modules/3DViewer/view/view.tsx b/frontend/src/modules/3DViewer/view/view.tsx index 6c106b539..248d16b71 100644 --- a/frontend/src/modules/3DViewer/view/view.tsx +++ b/frontend/src/modules/3DViewer/view/view.tsx @@ -16,25 +16,19 @@ import { ColorScaleWithName } from "@modules/_shared/utils/ColorScaleWithName"; import { calcExtendedSimplifiedWellboreTrajectoryInXYPlane } from "@modules/_shared/utils/wellbore"; import { NorthArrow3DLayer } from "@webviz/subsurface-viewer/dist/layers"; -import { useAtom, useAtomValue } from "jotai"; +import { useAtom, useSetAtom } from "jotai"; +import { editCustomIntersectionPolylineEditModeActiveAtom, intersectionTypeAtom } from "./atoms/baseAtoms"; import { SyncedSettingsUpdateWrapper } from "./components/SyncedSettingsUpdateWrapper"; import { useGridParameterQuery, useGridSurfaceQuery } from "./queries/gridQueries"; import { useGridPolylineIntersection as useGridPolylineIntersectionQuery } from "./queries/polylineIntersection"; import { useWellboreCasingsQuery } from "./queries/wellboreSchematicsQueries"; import { makeAxesLayer, makeGrid3DLayer, makeIntersectionLayer, makeWellsLayer } from "./utils/layers"; +import { Interfaces } from "../interfaces"; import { userSelectedCustomIntersectionPolylineIdAtom } from "../settings/atoms/baseAtoms"; -import { SettingsToViewInterface } from "../settingsToViewInterface"; -import { - editCustomIntersectionPolylineEditModeActiveAtom, - intersectionTypeAtom, - selectedEnsembleIdentAtom, - selectedHighlightedWellboreUuidAtom, -} from "../sharedAtoms/sharedAtoms"; -import { State } from "../state"; - -export function View(props: ModuleViewProps): React.ReactNode { + +export function View(props: ModuleViewProps): React.ReactNode { const statusWriter = useViewStatusWriter(props.viewContext); const syncedSettingKeys = props.viewContext.useSyncedSettingKeys(); const syncHelper = new SyncSettingsHelper(syncedSettingKeys, props.workbenchServices); @@ -49,11 +43,10 @@ export function View(props: ModuleViewProps): Re const useCustomBounds = props.viewContext.useSettingsToViewInterfaceValue("useCustomBounds"); - const highlightedWellboreUuid = useAtomValue(selectedHighlightedWellboreUuidAtom); - - const ensembleIdent = useAtomValue(selectedEnsembleIdentAtom); const intersectionPolylines = useIntersectionPolylines(props.workbenchSession); + const ensembleIdent = props.viewContext.useSettingsToViewInterfaceValue("ensembleIdent"); + const highlightedWellboreUuid = props.viewContext.useSettingsToViewInterfaceValue("highlightedWellboreUuid"); const realization = props.viewContext.useSettingsToViewInterfaceValue("realization"); const wellboreUuids = props.viewContext.useSettingsToViewInterfaceValue("wellboreUuids"); const gridModelName = props.viewContext.useSettingsToViewInterfaceValue("gridModelName"); @@ -62,6 +55,15 @@ export function View(props: ModuleViewProps): Re const gridModelParameterDateOrInterval = props.viewContext.useSettingsToViewInterfaceValue( "gridModelParameterDateOrInterval" ); + + const editPolylineModeActive = props.viewContext.useSettingsToViewInterfaceValue( + "editCustomIntersectionPolylineEditModeActive" + ); + const setEditPolylineModeActive = useSetAtom(editCustomIntersectionPolylineEditModeActiveAtom); + + const intersectionType = props.viewContext.useSettingsToViewInterfaceValue("intersectionType"); + const setIntersectionType = useSetAtom(intersectionTypeAtom); + const ensembleSet = useEnsembleSet(props.workbenchSession); React.useEffect( @@ -85,10 +87,6 @@ export function View(props: ModuleViewProps): Re const intersectionExtensionLength = props.viewContext.useSettingsToViewInterfaceValue("intersectionExtensionLength"); - const [editPolylineModeActive, setEditPolylineModeActive] = useAtom( - editCustomIntersectionPolylineEditModeActiveAtom - ); - const [intersectionType, setIntersectionType] = useAtom(intersectionTypeAtom); const [selectedCustomIntersectionPolylineId, setSelectedCustomIntersectionPolylineId] = useAtom( userSelectedCustomIntersectionPolylineIdAtom diff --git a/frontend/src/modules/DbgWorkbenchSpy/implementation.tsx b/frontend/src/modules/DbgWorkbenchSpy/implementation.tsx index b7b0b5b7a..301600556 100644 --- a/frontend/src/modules/DbgWorkbenchSpy/implementation.tsx +++ b/frontend/src/modules/DbgWorkbenchSpy/implementation.tsx @@ -1,19 +1,21 @@ import React from "react"; import { EnsembleSet } from "@framework/EnsembleSet"; -import { ModuleSettingsProps, ModuleViewProps } from "@framework/Module"; +import { ModuleViewProps } from "@framework/Module"; import { AllTopicDefinitions, WorkbenchServices } from "@framework/WorkbenchServices"; import { useEnsembleSet } from "@framework/WorkbenchSession"; import { timestampUtcMsToIsoString } from "@framework/utils/timestampUtils"; import { Button } from "@lib/components/Button"; -export type SharedState = { - triggeredRefreshCounter: number; -}; +import { atom, useSetAtom } from "jotai"; + +import { Interfaces } from "./interfaces"; + +export const triggeredRefreshCounterAtom = atom(0); //----------------------------------------------------------------------------------------------------------- -export function WorkbenchSpySettings(props: ModuleSettingsProps) { - const setRefreshCounter = props.settingsContext.useSetStoreValue("triggeredRefreshCounter"); +export function WorkbenchSpySettings() { + const setRefreshCounter = useSetAtom(triggeredRefreshCounterAtom); return (
@@ -22,14 +24,14 @@ export function WorkbenchSpySettings(props: ModuleSettingsProps) { } //----------------------------------------------------------------------------------------------------------- -export function WorkbenchSpyView(props: ModuleViewProps) { +export function WorkbenchSpyView(props: ModuleViewProps) { const ensembleSet = useEnsembleSet(props.workbenchSession); const [hoverRealization, hoverRealization_TS] = useServiceValueWithTS( "global.hoverRealization", props.workbenchServices ); const [hoverTimestamp, hoverTimestamp_TS] = useServiceValueWithTS("global.hoverTimestamp", props.workbenchServices); - const triggeredRefreshCounter = props.viewContext.useStoreValue("triggeredRefreshCounter"); + const triggeredRefreshCounter = props.viewContext.useSettingsToViewInterfaceValue("triggeredRefreshCounter"); const componentRenderCount = React.useRef(0); React.useEffect(function incrementComponentRenderCount() { diff --git a/frontend/src/modules/DbgWorkbenchSpy/interfaces.ts b/frontend/src/modules/DbgWorkbenchSpy/interfaces.ts new file mode 100644 index 000000000..e7765eecb --- /dev/null +++ b/frontend/src/modules/DbgWorkbenchSpy/interfaces.ts @@ -0,0 +1,15 @@ +import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; + +import { triggeredRefreshCounterAtom } from "./implementation"; + +type SettingsToViewInterface = { + triggeredRefreshCounter: number; +}; + +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + triggeredRefreshCounter: (get) => get(triggeredRefreshCounterAtom), +}; diff --git a/frontend/src/modules/DbgWorkbenchSpy/loadModule.tsx b/frontend/src/modules/DbgWorkbenchSpy/loadModule.tsx index a0e7767d7..60898b4d6 100644 --- a/frontend/src/modules/DbgWorkbenchSpy/loadModule.tsx +++ b/frontend/src/modules/DbgWorkbenchSpy/loadModule.tsx @@ -1,12 +1,11 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; -import { SharedState, WorkbenchSpySettings, WorkbenchSpyView } from "./implementation"; +import { WorkbenchSpySettings, WorkbenchSpyView } from "./implementation"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; -const defaultState: SharedState = { - triggeredRefreshCounter: 0, -}; - -const module = ModuleRegistry.initModule("DbgWorkbenchSpy", defaultState); +const module = ModuleRegistry.initModule("DbgWorkbenchSpy", { + settingsToViewInterfaceInitialization, +}); module.viewFC = WorkbenchSpyView; module.settingsFC = WorkbenchSpySettings; diff --git a/frontend/src/modules/DbgWorkbenchSpy/registerModule.ts b/frontend/src/modules/DbgWorkbenchSpy/registerModule.ts index 274b3ddeb..2f9b87d95 100644 --- a/frontend/src/modules/DbgWorkbenchSpy/registerModule.ts +++ b/frontend/src/modules/DbgWorkbenchSpy/registerModule.ts @@ -1,9 +1,9 @@ import { ModuleCategory, ModuleDevState } from "@framework/Module"; import { ModuleRegistry } from "@framework/ModuleRegistry"; -import { SharedState } from "./implementation"; +import { Interfaces } from "./interfaces"; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: "DbgWorkbenchSpy", defaultTitle: "Debug Workbench Spy", category: ModuleCategory.DEBUG, diff --git a/frontend/src/modules/DistributionPlot/interfaces.ts b/frontend/src/modules/DistributionPlot/interfaces.ts new file mode 100644 index 000000000..a554388ef --- /dev/null +++ b/frontend/src/modules/DistributionPlot/interfaces.ts @@ -0,0 +1,20 @@ +import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; + +import { numBinsAtom, orientationAtom, plotTypeAtom } from "./settings/atoms/baseAtoms"; +import { PlotType } from "./typesAndEnums"; + +type SettingsToViewInterface = { + plotType: PlotType | null; + numBins: number; + orientation: "h" | "v"; +}; + +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + plotType: (get) => get(plotTypeAtom), + numBins: (get) => get(numBinsAtom), + orientation: (get) => get(orientationAtom), +}; diff --git a/frontend/src/modules/DistributionPlot/loadModule.tsx b/frontend/src/modules/DistributionPlot/loadModule.tsx index 11882b050..6ec0fe391 100644 --- a/frontend/src/modules/DistributionPlot/loadModule.tsx +++ b/frontend/src/modules/DistributionPlot/loadModule.tsx @@ -1,16 +1,12 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; -import { Settings } from "./settings"; -import { PlotType, State } from "./state"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; +import { Settings } from "./settings/settings"; import { View } from "./view"; -const defaultState: State = { - plotType: PlotType.Histogram, - numBins: 10, - orientation: "h", -}; - -const module = ModuleRegistry.initModule("DistributionPlot", defaultState); +const module = ModuleRegistry.initModule("DistributionPlot", { + settingsToViewInterfaceInitialization, +}); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/DistributionPlot/registerModule.ts b/frontend/src/modules/DistributionPlot/registerModule.ts index 5fea31fa8..6563734ce 100644 --- a/frontend/src/modules/DistributionPlot/registerModule.ts +++ b/frontend/src/modules/DistributionPlot/registerModule.ts @@ -2,14 +2,14 @@ import { ModuleCategory, ModuleDevState } from "@framework/Module"; import { ModuleRegistry } from "@framework/ModuleRegistry"; import { SyncSettingKey } from "@framework/SyncSettings"; +import { Interfaces } from "./interfaces"; import { preview } from "./preview"; import { receiverDefs } from "./receiverDefs"; -import { State } from "./state"; const description = "Sub-module that can be connected to other modules via data channels for visualization of distribution data."; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: "DistributionPlot", defaultTitle: "Distribution plot", category: ModuleCategory.SUB, diff --git a/frontend/src/modules/DistributionPlot/settings/atoms/baseAtoms.ts b/frontend/src/modules/DistributionPlot/settings/atoms/baseAtoms.ts new file mode 100644 index 000000000..eb2d7c3f1 --- /dev/null +++ b/frontend/src/modules/DistributionPlot/settings/atoms/baseAtoms.ts @@ -0,0 +1,7 @@ +import { PlotType } from "@modules/DistributionPlot/typesAndEnums"; + +import { atom } from "jotai"; + +export const plotTypeAtom = atom(PlotType.Histogram); +export const numBinsAtom = atom(10); +export const orientationAtom = atom<"h" | "v">("h"); diff --git a/frontend/src/modules/DistributionPlot/settings.tsx b/frontend/src/modules/DistributionPlot/settings/settings.tsx similarity index 88% rename from frontend/src/modules/DistributionPlot/settings.tsx rename to frontend/src/modules/DistributionPlot/settings/settings.tsx index 1109082d2..821b657ad 100644 --- a/frontend/src/modules/DistributionPlot/settings.tsx +++ b/frontend/src/modules/DistributionPlot/settings/settings.tsx @@ -8,7 +8,12 @@ import { Label } from "@lib/components/Label"; import { RadioGroup } from "@lib/components/RadioGroup"; import { Slider } from "@lib/components/Slider"; -import { PlotType, State } from "./state"; +import { useAtom } from "jotai"; + +import { numBinsAtom, orientationAtom, plotTypeAtom } from "./atoms/baseAtoms"; + +import { Interfaces } from "../interfaces"; +import { PlotType } from "../typesAndEnums"; const plotTypes = [ { @@ -30,10 +35,10 @@ const plotTypes = [ ]; //----------------------------------------------------------------------------------------------------------- -export function Settings({ settingsContext, initialSettings }: ModuleSettingsProps) { - const [plotType, setPlotType] = settingsContext.useStoreState("plotType"); - const [numBins, setNumBins] = settingsContext.useStoreState("numBins"); - const [orientation, setOrientation] = settingsContext.useStoreState("orientation"); +export function Settings({ initialSettings }: ModuleSettingsProps) { + const [plotType, setPlotType] = useAtom(plotTypeAtom); + const [numBins, setNumBins] = useAtom(numBinsAtom); + const [orientation, setOrientation] = useAtom(orientationAtom); useApplyInitialSettingsToState(initialSettings, "plotType", "string", setPlotType); useApplyInitialSettingsToState(initialSettings, "numBins", "number", setNumBins); diff --git a/frontend/src/modules/DistributionPlot/state.ts b/frontend/src/modules/DistributionPlot/typesAndEnums.ts similarity index 59% rename from frontend/src/modules/DistributionPlot/state.ts rename to frontend/src/modules/DistributionPlot/typesAndEnums.ts index 5f108da5d..ed220cbd4 100644 --- a/frontend/src/modules/DistributionPlot/state.ts +++ b/frontend/src/modules/DistributionPlot/typesAndEnums.ts @@ -4,9 +4,3 @@ export enum PlotType { Scatter = "scatter", ScatterWithColorMapping = "scatterWithColor", } - -export interface State { - plotType: PlotType | null; - numBins: number; - orientation: "v" | "h"; -} diff --git a/frontend/src/modules/DistributionPlot/view.tsx b/frontend/src/modules/DistributionPlot/view.tsx index 7bfc7b1c5..ae359eea5 100644 --- a/frontend/src/modules/DistributionPlot/view.tsx +++ b/frontend/src/modules/DistributionPlot/view.tsx @@ -14,7 +14,8 @@ import { Warning } from "@mui/icons-material"; import { Layout, PlotData } from "plotly.js"; -import { PlotType, State } from "./state"; +import { Interfaces } from "./interfaces"; +import { PlotType } from "./typesAndEnums"; import { makeHistogramTrace } from "./utils/histogram"; import { makeHoverText, makeHoverTextWithColor, makeTitleFromChannelContent } from "./utils/stringUtils"; import { calcTextSize } from "./utils/textSize"; @@ -33,7 +34,7 @@ const MaxNumberPlotsExceededMessage: React.FC = () => { MaxNumberPlotsExceededMessage.displayName = "MaxNumberPlotsExceededMessage"; -export const View = ({ viewContext, workbenchSettings }: ModuleViewProps) => { +export const View = ({ viewContext, workbenchSettings }: ModuleViewProps) => { const [isPending, startTransition] = React.useTransition(); const [content, setContent] = React.useState(null); const [revNumberX, setRevNumberX] = React.useState(0); @@ -44,9 +45,9 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps) const [prevOrientation, setPrevOrientation] = React.useState<"v" | "h" | null>(null); const [prevSize, setPrevSize] = React.useState(null); - const plotType = viewContext.useStoreValue("plotType"); - const numBins = viewContext.useStoreValue("numBins"); - const orientation = viewContext.useStoreValue("orientation"); + const plotType = viewContext.useSettingsToViewInterfaceValue("plotType"); + const numBins = viewContext.useSettingsToViewInterfaceValue("numBins"); + const orientation = viewContext.useSettingsToViewInterfaceValue("orientation"); const statusWriter = useViewStatusWriter(viewContext); diff --git a/frontend/src/modules/FlowNetwork/settingsToViewInterface.ts b/frontend/src/modules/FlowNetwork/interfaces.ts similarity index 85% rename from frontend/src/modules/FlowNetwork/settingsToViewInterface.ts rename to frontend/src/modules/FlowNetwork/interfaces.ts index 5c3c77d44..cf0193239 100644 --- a/frontend/src/modules/FlowNetwork/settingsToViewInterface.ts +++ b/frontend/src/modules/FlowNetwork/interfaces.ts @@ -12,9 +12,7 @@ import { } from "./settings/atoms/derivedAtoms"; import { QueryStatus } from "./types"; -export type State = Record; - -export type Interface = { +type SettingsToViewInterface = { edgeMetadataList: EdgeMetadata[]; nodeMetadataList: NodeMetadata[]; datedTrees: DatedTree[]; @@ -24,7 +22,11 @@ export type Interface = { queryStatus: QueryStatus; }; -export const interfaceInitialization: InterfaceInitialization = { +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { edgeMetadataList: (get) => { return get(edgeMetadataListAtom); }, diff --git a/frontend/src/modules/FlowNetwork/loadModule.tsx b/frontend/src/modules/FlowNetwork/loadModule.tsx index f9f54611c..28c1dbd3c 100644 --- a/frontend/src/modules/FlowNetwork/loadModule.tsx +++ b/frontend/src/modules/FlowNetwork/loadModule.tsx @@ -1,13 +1,11 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; import { MODULE_NAME } from "./registerModule"; import { Settings } from "./settings/settings"; -import { Interface, State, interfaceInitialization } from "./settingsToViewInterface"; import { View } from "./view"; -const defaultState: State = {}; - -const module = ModuleRegistry.initModule(MODULE_NAME, defaultState, {}, interfaceInitialization); +const module = ModuleRegistry.initModule(MODULE_NAME, { settingsToViewInterfaceInitialization }); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/FlowNetwork/registerModule.ts b/frontend/src/modules/FlowNetwork/registerModule.ts index 3611a74db..05eabcffc 100644 --- a/frontend/src/modules/FlowNetwork/registerModule.ts +++ b/frontend/src/modules/FlowNetwork/registerModule.ts @@ -2,14 +2,14 @@ import { ModuleCategory, ModuleDevState } from "@framework/Module"; import { ModuleDataTagId } from "@framework/ModuleDataTags"; import { ModuleRegistry } from "@framework/ModuleRegistry"; +import { Interfaces } from "./interfaces"; import { preview } from "./preview"; -import { Interface, State } from "./settingsToViewInterface"; export const MODULE_NAME = "FlowNetwork"; const description = "Visualizes dated group trees over time."; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: MODULE_NAME, defaultTitle: "Flow Network", category: ModuleCategory.MAIN, diff --git a/frontend/src/modules/FlowNetwork/settings/settings.tsx b/frontend/src/modules/FlowNetwork/settings/settings.tsx index 63159bd82..056f2c959 100644 --- a/frontend/src/modules/FlowNetwork/settings/settings.tsx +++ b/frontend/src/modules/FlowNetwork/settings/settings.tsx @@ -39,10 +39,10 @@ import { selectedRealizationNumberAtom, } from "./atoms/derivedAtoms"; -import { Interface, State } from "../settingsToViewInterface"; +import { Interfaces } from "../interfaces"; import { FrequencyEnumToStringMapping, NodeTypeEnumToStringMapping } from "../types"; -export function Settings({ workbenchSession, settingsContext }: ModuleSettingsProps) { +export function Settings({ workbenchSession, settingsContext }: ModuleSettingsProps) { const ensembleSet = useEnsembleSet(workbenchSession); const statusWriter = useSettingsStatusWriter(settingsContext); diff --git a/frontend/src/modules/FlowNetwork/view.tsx b/frontend/src/modules/FlowNetwork/view.tsx index f24b46ee0..5f8fa8e1b 100644 --- a/frontend/src/modules/FlowNetwork/view.tsx +++ b/frontend/src/modules/FlowNetwork/view.tsx @@ -4,10 +4,10 @@ import { CircularProgress } from "@lib/components/CircularProgress"; import { ContentError, ContentInfo } from "@modules/_shared/components/ContentMessage"; import { GroupTreePlot } from "@webviz/group-tree-plot"; -import { Interface, State } from "./settingsToViewInterface"; +import { Interfaces } from "./interfaces"; import { QueryStatus } from "./types"; -export function View({ viewContext }: ModuleViewProps) { +export function View({ viewContext }: ModuleViewProps) { const edgeMetadataList = viewContext.useSettingsToViewInterfaceValue("edgeMetadataList"); const nodeMetadataList = viewContext.useSettingsToViewInterfaceValue("nodeMetadataList"); const datedTrees = viewContext.useSettingsToViewInterfaceValue("datedTrees"); diff --git a/frontend/src/modules/InplaceVolumetrics/interfaces.ts b/frontend/src/modules/InplaceVolumetrics/interfaces.ts new file mode 100644 index 000000000..b886daa1b --- /dev/null +++ b/frontend/src/modules/InplaceVolumetrics/interfaces.ts @@ -0,0 +1,34 @@ +import { InplaceVolumetricsCategoricalMetaData_api } from "@api"; +import { EnsembleIdent } from "@framework/EnsembleIdent"; +import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; + +import { + categoricalFilterAtom, + categoricalOptionsAtom, + ensembleIdentAtom, + realizationsToIncludeAtom, + responseNameAtom, + tableNameAtom, +} from "./settings/atoms/baseAtoms"; + +type SettingsToViewInterface = { + ensembleIdent: EnsembleIdent | null; + tableName: string | null; + responseName: string | null; + categoricalOptions: InplaceVolumetricsCategoricalMetaData_api[] | null; + categoricalFilter: InplaceVolumetricsCategoricalMetaData_api[] | null; + realizationsToInclude: number[] | null; +}; + +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + ensembleIdent: (get) => get(ensembleIdentAtom), + tableName: (get) => get(tableNameAtom), + responseName: (get) => get(responseNameAtom), + categoricalOptions: (get) => get(categoricalOptionsAtom), + categoricalFilter: (get) => get(categoricalFilterAtom), + realizationsToInclude: (get) => get(realizationsToIncludeAtom), +}; diff --git a/frontend/src/modules/InplaceVolumetrics/loadModule.tsx b/frontend/src/modules/InplaceVolumetrics/loadModule.tsx index 703c8fbe3..8cdb4582d 100644 --- a/frontend/src/modules/InplaceVolumetrics/loadModule.tsx +++ b/frontend/src/modules/InplaceVolumetrics/loadModule.tsx @@ -1,19 +1,10 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; -import { Settings } from "./settings"; -import { State } from "./state"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; +import { Settings } from "./settings/settings"; import { View } from "./view"; -const defaultState: State = { - ensembleIdent: null, - tableName: null, - categoricalOptions: null, - categoricalFilter: null, - responseName: null, - realizationsToInclude: null, -}; - -const module = ModuleRegistry.initModule("InplaceVolumetrics", defaultState); +const module = ModuleRegistry.initModule("InplaceVolumetrics", { settingsToViewInterfaceInitialization }); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/InplaceVolumetrics/registerModule.ts b/frontend/src/modules/InplaceVolumetrics/registerModule.ts index ba03da06a..3ba0a070a 100644 --- a/frontend/src/modules/InplaceVolumetrics/registerModule.ts +++ b/frontend/src/modules/InplaceVolumetrics/registerModule.ts @@ -3,11 +3,11 @@ import { ModuleDataTagId } from "@framework/ModuleDataTags"; import { ModuleRegistry } from "@framework/ModuleRegistry"; import { channelDefs } from "./channelDefs"; -import { State } from "./state"; +import { Interfaces } from "./interfaces"; const description = "Plotting of in-place volumetric distributions."; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: "InplaceVolumetrics", defaultTitle: "Inplace volumetrics", channelDefinitions: channelDefs, diff --git a/frontend/src/modules/InplaceVolumetrics/settings/atoms/baseAtoms.ts b/frontend/src/modules/InplaceVolumetrics/settings/atoms/baseAtoms.ts new file mode 100644 index 000000000..deefc0d39 --- /dev/null +++ b/frontend/src/modules/InplaceVolumetrics/settings/atoms/baseAtoms.ts @@ -0,0 +1,11 @@ +import { InplaceVolumetricsCategoricalMetaData_api } from "@api"; +import { EnsembleIdent } from "@framework/EnsembleIdent"; + +import { atom } from "jotai"; + +export const ensembleIdentAtom = atom(null); +export const tableNameAtom = atom(null); +export const responseNameAtom = atom(null); +export const categoricalOptionsAtom = atom(null); +export const categoricalFilterAtom = atom(null); +export const realizationsToIncludeAtom = atom(null); diff --git a/frontend/src/modules/InplaceVolumetrics/settings.tsx b/frontend/src/modules/InplaceVolumetrics/settings/settings.tsx similarity index 93% rename from frontend/src/modules/InplaceVolumetrics/settings.tsx rename to frontend/src/modules/InplaceVolumetrics/settings/settings.tsx index a115b5ed6..53fdfe45c 100644 --- a/frontend/src/modules/InplaceVolumetrics/settings.tsx +++ b/frontend/src/modules/InplaceVolumetrics/settings/settings.tsx @@ -13,8 +13,12 @@ import { QueryStateWrapper } from "@lib/components/QueryStateWrapper"; import { Select } from "@lib/components/Select"; import { UseQueryResult } from "@tanstack/react-query"; -import { useTableDescriptionsQuery } from "./queryHooks"; -import { State } from "./state"; +import { useAtom } from "jotai"; + +import { categoricalFilterAtom, ensembleIdentAtom, responseNameAtom, tableNameAtom } from "./atoms/baseAtoms"; + +import { Interfaces } from "../interfaces"; +import { useTableDescriptionsQuery } from "../queryHooks"; //----------------------------------------------------------------------------------------------------------- @@ -96,12 +100,12 @@ function getTableResponseOptions( return responsesToSelectOptions(responses); } -export function Settings({ settingsContext, workbenchSession }: ModuleSettingsProps) { +export function Settings({ workbenchSession }: ModuleSettingsProps) { const ensembleSet = useEnsembleSet(workbenchSession); - const [ensembleIdent, setEnsembleIdent] = settingsContext.useStoreState("ensembleIdent"); - const [tableName, setTableName] = settingsContext.useStoreState("tableName"); - const [categoricalFilter, setCategoricalFilter] = settingsContext.useStoreState("categoricalFilter"); - const [responseName, setResponseName] = settingsContext.useStoreState("responseName"); + const [ensembleIdent, setEnsembleIdent] = useAtom(ensembleIdentAtom); + const [tableName, setTableName] = useAtom(tableNameAtom); + const [categoricalFilter, setCategoricalFilter] = useAtom(categoricalFilterAtom); + const [responseName, setResponseName] = useAtom(responseNameAtom); const tableDescriptionsQuery = useTableDescriptionsQuery(ensembleIdent, true); diff --git a/frontend/src/modules/InplaceVolumetrics/state.ts b/frontend/src/modules/InplaceVolumetrics/state.ts deleted file mode 100644 index 7fee42129..000000000 --- a/frontend/src/modules/InplaceVolumetrics/state.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { InplaceVolumetricsCategoricalMetaData_api } from "@api"; -import { EnsembleIdent } from "@framework/EnsembleIdent"; - -export interface State { - ensembleIdent: EnsembleIdent | null; - tableName: string | null; - responseName: string | null; - categoricalOptions: InplaceVolumetricsCategoricalMetaData_api[] | null; - categoricalFilter: InplaceVolumetricsCategoricalMetaData_api[] | null; - realizationsToInclude: number[] | null; -} diff --git a/frontend/src/modules/InplaceVolumetrics/view.tsx b/frontend/src/modules/InplaceVolumetrics/view.tsx index 292ffd8a0..4db6b7a12 100644 --- a/frontend/src/modules/InplaceVolumetrics/view.tsx +++ b/frontend/src/modules/InplaceVolumetrics/view.tsx @@ -12,17 +12,17 @@ import { useElementSize } from "@lib/hooks/useElementSize"; import { Layout, PlotData, PlotHoverEvent } from "plotly.js"; import { ChannelIds } from "./channelDefs"; +import { Interfaces } from "./interfaces"; import { useRealizationsResponseQuery } from "./queryHooks"; -import { VolumetricResponseAbbreviations } from "./settings"; -import { State } from "./state"; +import { VolumetricResponseAbbreviations } from "./settings/settings"; -export const View = (props: ModuleViewProps) => { +export const View = (props: ModuleViewProps) => { const wrapperDivRef = React.useRef(null); const wrapperDivSize = useElementSize(wrapperDivRef); - const ensembleIdent = props.viewContext.useStoreValue("ensembleIdent"); - const tableName = props.viewContext.useStoreValue("tableName"); - const responseName = props.viewContext.useStoreValue("responseName"); - const categoryFilter = props.viewContext.useStoreValue("categoricalFilter"); + const ensembleIdent = props.viewContext.useSettingsToViewInterfaceValue("ensembleIdent"); + const tableName = props.viewContext.useSettingsToViewInterfaceValue("tableName"); + const responseName = props.viewContext.useSettingsToViewInterfaceValue("responseName"); + const categoryFilter = props.viewContext.useSettingsToViewInterfaceValue("categoricalFilter"); const responseBody: Body_get_realizations_response_api = { categorical_filter: categoryFilter || undefined }; const realizationsResponseQuery = useRealizationsResponseQuery( ensembleIdent?.getCaseUuid() ?? "", @@ -95,7 +95,7 @@ export const View = (props: ModuleViewProps) => { dependencies: [realizationsResponseQuery.data, ensemble, tableName, responseName], contents: [{ contentIdString: responseName || "", displayName: responseName || "", dataGenerator }], }); - + const layout: Partial = { width: wrapperDivSize.width, height: wrapperDivSize.height, diff --git a/frontend/src/modules/Intersection/settingsToViewInterface.ts b/frontend/src/modules/Intersection/interfaces.ts similarity index 88% rename from frontend/src/modules/Intersection/settingsToViewInterface.ts rename to frontend/src/modules/Intersection/interfaces.ts index 47b4de98e..ccac90da8 100644 --- a/frontend/src/modules/Intersection/settingsToViewInterface.ts +++ b/frontend/src/modules/Intersection/interfaces.ts @@ -18,6 +18,7 @@ import { selectedEnsembleIdentAtom, selectedWellboreAtom, } from "./settings/atoms/derivedAtoms"; +import { WellboreHeader } from "./typesAndEnums"; import { LayerManager } from "./utils/layers/LayerManager"; export type SettingsToViewInterface = { @@ -31,15 +32,14 @@ export type SettingsToViewInterface = { ensembleIdent: EnsembleIdent | null; selectedCustomIntersectionPolylineId: string | null; layerManager: LayerManager; - wellboreHeader: { - uuid: string; - identifier: string; - depthReferencePoint: string; - depthReferenceElevation: number; - } | null; + wellboreHeader: WellboreHeader | null; }; -export const interfaceInitialization: InterfaceInitialization = { +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { showGridlines: (get) => { return get(showGridlinesAtom); }, diff --git a/frontend/src/modules/Intersection/loadModule.tsx b/frontend/src/modules/Intersection/loadModule.tsx index ad87fc7f9..914eb2dd8 100644 --- a/frontend/src/modules/Intersection/loadModule.tsx +++ b/frontend/src/modules/Intersection/loadModule.tsx @@ -1,20 +1,15 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; import { MODULE_NAME } from "./registerModule"; import { Settings } from "./settings/settings"; -import { SettingsToViewInterface, interfaceInitialization } from "./settingsToViewInterface"; -import { State } from "./state"; -import { ViewAtoms, viewAtomsInitialization } from "./view/atoms/atomDefinitions"; +import { settingsToViewInterfaceEffects } from "./view/atoms/interfaceEffects"; import { View } from "./view/view"; -const module = ModuleRegistry.initModule, ViewAtoms>( - MODULE_NAME, - {}, - {}, - interfaceInitialization, - undefined, - viewAtomsInitialization -); +const module = ModuleRegistry.initModule(MODULE_NAME, { + settingsToViewInterfaceInitialization, + settingsToViewInterfaceEffects, +}); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/Intersection/registerModule.ts b/frontend/src/modules/Intersection/registerModule.ts index e6093f70b..6e08a5446 100644 --- a/frontend/src/modules/Intersection/registerModule.ts +++ b/frontend/src/modules/Intersection/registerModule.ts @@ -2,14 +2,12 @@ import { ModuleCategory, ModuleDevState } from "@framework/Module"; import { ModuleRegistry } from "@framework/ModuleRegistry"; import { SyncSettingKey } from "@framework/SyncSettings"; +import { Interfaces } from "./interfaces"; import { preview } from "./preview"; -import { SettingsToViewInterface } from "./settingsToViewInterface"; -import { State } from "./state"; -import { ViewAtoms } from "./view/atoms/atomDefinitions"; export const MODULE_NAME = "Intersection"; -ModuleRegistry.registerModule, ViewAtoms>({ +ModuleRegistry.registerModule({ moduleName: MODULE_NAME, defaultTitle: "Intersection", category: ModuleCategory.MAIN, diff --git a/frontend/src/modules/Intersection/settings/settings.tsx b/frontend/src/modules/Intersection/settings/settings.tsx index 65d1a6f57..b72f8b249 100644 --- a/frontend/src/modules/Intersection/settings/settings.tsx +++ b/frontend/src/modules/Intersection/settings/settings.tsx @@ -38,13 +38,9 @@ import { import { drilledWellboreHeadersQueryAtom } from "./atoms/queryAtoms"; import { Layers } from "./components/layers"; -import { SettingsToViewInterface } from "../settingsToViewInterface"; -import { State } from "../state"; -import { ViewAtoms } from "../view/atoms/atomDefinitions"; +import { Interfaces } from "../interfaces"; -export function Settings( - props: ModuleSettingsProps, ViewAtoms> -): JSX.Element { +export function Settings(props: ModuleSettingsProps): JSX.Element { const ensembleSet = useEnsembleSet(props.workbenchSession); const filteredEnsembleSet = useAtomValue(filteredEnsembleSetAtom); const statusWriter = useSettingsStatusWriter(props.settingsContext); diff --git a/frontend/src/modules/Intersection/state.ts b/frontend/src/modules/Intersection/state.ts deleted file mode 100644 index 90ff7f709..000000000 --- a/frontend/src/modules/Intersection/state.ts +++ /dev/null @@ -1 +0,0 @@ -export type State = Record; diff --git a/frontend/src/modules/Intersection/typesAndEnums.ts b/frontend/src/modules/Intersection/typesAndEnums.ts index b08010c66..f383bd50f 100644 --- a/frontend/src/modules/Intersection/typesAndEnums.ts +++ b/frontend/src/modules/Intersection/typesAndEnums.ts @@ -1 +1,8 @@ export const CURVE_FITTING_EPSILON = 5; // meter + +export type WellboreHeader = { + uuid: string; + identifier: string; + depthReferencePoint: string; + depthReferenceElevation: number; +}; diff --git a/frontend/src/modules/Intersection/view/atoms/atomDefinitions.ts b/frontend/src/modules/Intersection/view/atoms/atomDefinitions.ts deleted file mode 100644 index 68fd1145b..000000000 --- a/frontend/src/modules/Intersection/view/atoms/atomDefinitions.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { WellboreTrajectory_api } from "@api"; -import { IntersectionReferenceSystem } from "@equinor/esv-intersection"; -import { apiService } from "@framework/ApiService"; -import { ModuleAtoms } from "@framework/Module"; -import { UniDirectionalModuleComponentsInterface } from "@framework/UniDirectionalModuleComponentsInterface"; -import { IntersectionType } from "@framework/types/intersection"; -import { IntersectionPolylinesAtom } from "@framework/userCreatedItems/IntersectionPolylines"; -import { point2Distance, vec2FromArray } from "@lib/utils/vec2"; -import { SettingsToViewInterface } from "@modules/Intersection/settingsToViewInterface"; -import { CURVE_FITTING_EPSILON } from "@modules/Intersection/typesAndEnums"; -import { calcExtendedSimplifiedWellboreTrajectoryInXYPlane } from "@modules/_shared/utils/wellbore"; -import { QueryObserverResult } from "@tanstack/react-query"; - -import { atom } from "jotai"; -import { atomWithQuery } from "jotai-tanstack-query"; - -const STALE_TIME = 60 * 1000; -const CACHE_TIME = 60 * 1000; - -export type ViewAtoms = { - intersectionReferenceSystemAtom: IntersectionReferenceSystem | null; - polylineAtom: { - polylineUtmXy: number[]; - actualSectionLengths: number[]; - }; - wellboreTrajectoryQueryAtom: QueryObserverResult; -}; - -export function viewAtomsInitialization( - settingsToViewInterface: UniDirectionalModuleComponentsInterface -): ModuleAtoms { - const selectedCustomIntersectionPolylineAtom = atom((get) => { - const customIntersectionPolylineId = get( - settingsToViewInterface.getAtom("selectedCustomIntersectionPolylineId") - ); - const customIntersectionPolylines = get(IntersectionPolylinesAtom); - - return customIntersectionPolylines.find((el) => el.id === customIntersectionPolylineId); - }); - - const intersectionReferenceSystemAtom = atom((get) => { - const wellboreTrajectoryQuery = get(wellboreTrajectoryQueryAtom); - const customIntersectionPolyline = get(selectedCustomIntersectionPolylineAtom); - const intersectionType = get(settingsToViewInterface.getAtom("intersectionType")); - - if (intersectionType === IntersectionType.WELLBORE) { - if (!wellboreTrajectoryQuery.data) { - return null; - } - - const wellboreTrajectory = wellboreTrajectoryQuery.data; - - if (wellboreTrajectoryQuery) { - const path: number[][] = []; - for (const [index, northing] of wellboreTrajectory.northingArr.entries()) { - const easting = wellboreTrajectory.eastingArr[index]; - const tvd_msl = wellboreTrajectory.tvdMslArr[index]; - - path.push([easting, northing, tvd_msl]); - } - const offset = wellboreTrajectory.mdArr[0]; - - const referenceSystem = new IntersectionReferenceSystem(path); - referenceSystem.offset = offset; - - return referenceSystem; - } - } else if (intersectionType === IntersectionType.CUSTOM_POLYLINE && customIntersectionPolyline) { - if (customIntersectionPolyline.points.length < 2) { - return null; - } - const referenceSystem = new IntersectionReferenceSystem( - customIntersectionPolyline.points.map((point) => [point[0], point[1], 0]) - ); - referenceSystem.offset = 0; - - return referenceSystem; - } - - return null; - }); - - const polylineAtom = atom((get) => { - const intersectionType = get(settingsToViewInterface.getAtom("intersectionType")); - const intersectionExtensionLength = get(settingsToViewInterface.getAtom("intersectionExtensionLength")); - const selectedCustomIntersectionPolyline = get(selectedCustomIntersectionPolylineAtom); - const intersectionReferenceSystem = get(intersectionReferenceSystemAtom); - - const polylineUtmXy: number[] = []; - const actualSectionLengths: number[] = []; - - if (intersectionReferenceSystem) { - if (intersectionType === IntersectionType.WELLBORE) { - const path = intersectionReferenceSystem.path; - const simplifiedCurveResult = calcExtendedSimplifiedWellboreTrajectoryInXYPlane( - path, - intersectionExtensionLength, - CURVE_FITTING_EPSILON - ); - polylineUtmXy.push(...simplifiedCurveResult.simplifiedWellboreTrajectoryXy.flat()); - actualSectionLengths.push(...simplifiedCurveResult.actualSectionLengths); - } else if (intersectionType === IntersectionType.CUSTOM_POLYLINE && selectedCustomIntersectionPolyline) { - for (const [index, point] of selectedCustomIntersectionPolyline.points.entries()) { - polylineUtmXy.push(point[0], point[1]); - if (index > 0) { - const previousPoint = selectedCustomIntersectionPolyline.points[index - 1]; - actualSectionLengths.push(point2Distance(vec2FromArray(point), vec2FromArray(previousPoint))); - } - } - } - } - - return { - polylineUtmXy, - actualSectionLengths, - }; - }); - - const wellboreTrajectoryQueryAtom = atomWithQuery((get) => { - const wellbore = get(settingsToViewInterface.getAtom("wellboreHeader")); - - return { - queryKey: ["getWellboreTrajectory", wellbore?.uuid ?? ""], - queryFn: () => apiService.well.getWellTrajectories(wellbore?.uuid ? [wellbore.uuid] : []), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - select: (data: WellboreTrajectory_api[]) => data[0], - enabled: wellbore?.uuid ? true : false, - }; - }); - - return { - intersectionReferenceSystemAtom, - polylineAtom, - wellboreTrajectoryQueryAtom, - }; -} diff --git a/frontend/src/modules/Intersection/view/atoms/baseAtoms.ts b/frontend/src/modules/Intersection/view/atoms/baseAtoms.ts new file mode 100644 index 000000000..0e9b3f426 --- /dev/null +++ b/frontend/src/modules/Intersection/view/atoms/baseAtoms.ts @@ -0,0 +1,9 @@ +import { IntersectionType } from "@framework/types/intersection"; +import { WellboreHeader } from "@modules/Intersection/typesAndEnums"; + +import { atom } from "jotai"; + +export const selectedCustomIntersectionPolylineIdAtom = atom(null); +export const intersectionTypeAtom = atom(IntersectionType.WELLBORE); +export const wellboreHeaderAtom = atom(null); +export const intersectionExtensionLengthAtom = atom(1000); diff --git a/frontend/src/modules/Intersection/view/atoms/derivedAtoms.ts b/frontend/src/modules/Intersection/view/atoms/derivedAtoms.ts new file mode 100644 index 000000000..2e19c5dd6 --- /dev/null +++ b/frontend/src/modules/Intersection/view/atoms/derivedAtoms.ts @@ -0,0 +1,100 @@ +import { IntersectionReferenceSystem } from "@equinor/esv-intersection"; +import { IntersectionType } from "@framework/types/intersection"; +import { IntersectionPolylinesAtom } from "@framework/userCreatedItems/IntersectionPolylines"; +import { point2Distance, vec2FromArray } from "@lib/utils/vec2"; +import { CURVE_FITTING_EPSILON } from "@modules/Intersection/typesAndEnums"; +import { calcExtendedSimplifiedWellboreTrajectoryInXYPlane } from "@modules/_shared/utils/wellbore"; + +import { atom } from "jotai"; + +import { + intersectionExtensionLengthAtom, + intersectionTypeAtom, + selectedCustomIntersectionPolylineIdAtom, +} from "./baseAtoms"; +import { wellboreTrajectoryQueryAtom } from "./queryAtoms"; + +export const selectedCustomIntersectionPolylineAtom = atom((get) => { + const customIntersectionPolylineId = get(selectedCustomIntersectionPolylineIdAtom); + const customIntersectionPolylines = get(IntersectionPolylinesAtom); + + return customIntersectionPolylines.find((el) => el.id === customIntersectionPolylineId); +}); + +export const intersectionReferenceSystemAtom = atom((get) => { + const wellboreTrajectoryQuery = get(wellboreTrajectoryQueryAtom); + const customIntersectionPolyline = get(selectedCustomIntersectionPolylineAtom); + const intersectionType = get(intersectionTypeAtom); + + if (intersectionType === IntersectionType.WELLBORE) { + if (!wellboreTrajectoryQuery.data) { + return null; + } + + const wellboreTrajectory = wellboreTrajectoryQuery.data; + + if (wellboreTrajectoryQuery) { + const path: number[][] = []; + for (const [index, northing] of wellboreTrajectory.northingArr.entries()) { + const easting = wellboreTrajectory.eastingArr[index]; + const tvd_msl = wellboreTrajectory.tvdMslArr[index]; + + path.push([easting, northing, tvd_msl]); + } + const offset = wellboreTrajectory.mdArr[0]; + + const referenceSystem = new IntersectionReferenceSystem(path); + referenceSystem.offset = offset; + + return referenceSystem; + } + } else if (intersectionType === IntersectionType.CUSTOM_POLYLINE && customIntersectionPolyline) { + if (customIntersectionPolyline.points.length < 2) { + return null; + } + const referenceSystem = new IntersectionReferenceSystem( + customIntersectionPolyline.points.map((point) => [point[0], point[1], 0]) + ); + referenceSystem.offset = 0; + + return referenceSystem; + } + + return null; +}); + +export const polylineAtom = atom((get) => { + const intersectionType = get(intersectionTypeAtom); + const intersectionExtensionLength = get(intersectionExtensionLengthAtom); + const selectedCustomIntersectionPolyline = get(selectedCustomIntersectionPolylineAtom); + const intersectionReferenceSystem = get(intersectionReferenceSystemAtom); + + const polylineUtmXy: number[] = []; + const actualSectionLengths: number[] = []; + + if (intersectionReferenceSystem) { + if (intersectionType === IntersectionType.WELLBORE) { + const path = intersectionReferenceSystem.path; + const simplifiedCurveResult = calcExtendedSimplifiedWellboreTrajectoryInXYPlane( + path, + intersectionExtensionLength, + CURVE_FITTING_EPSILON + ); + polylineUtmXy.push(...simplifiedCurveResult.simplifiedWellboreTrajectoryXy.flat()); + actualSectionLengths.push(...simplifiedCurveResult.actualSectionLengths); + } else if (intersectionType === IntersectionType.CUSTOM_POLYLINE && selectedCustomIntersectionPolyline) { + for (const [index, point] of selectedCustomIntersectionPolyline.points.entries()) { + polylineUtmXy.push(point[0], point[1]); + if (index > 0) { + const previousPoint = selectedCustomIntersectionPolyline.points[index - 1]; + actualSectionLengths.push(point2Distance(vec2FromArray(point), vec2FromArray(previousPoint))); + } + } + } + } + + return { + polylineUtmXy, + actualSectionLengths, + }; +}); diff --git a/frontend/src/modules/Intersection/view/atoms/interfaceEffects.ts b/frontend/src/modules/Intersection/view/atoms/interfaceEffects.ts new file mode 100644 index 000000000..575de6efa --- /dev/null +++ b/frontend/src/modules/Intersection/view/atoms/interfaceEffects.ts @@ -0,0 +1,28 @@ +import { InterfaceEffects } from "@framework/Module"; +import { SettingsToViewInterface } from "@modules/Intersection/interfaces"; + +import { + intersectionExtensionLengthAtom, + intersectionTypeAtom, + selectedCustomIntersectionPolylineIdAtom, + wellboreHeaderAtom, +} from "./baseAtoms"; + +export const settingsToViewInterfaceEffects: InterfaceEffects = [ + (getInterfaceValue, setAtomValue) => { + const selectedCustomIntersectionPolylineId = getInterfaceValue("selectedCustomIntersectionPolylineId"); + setAtomValue(selectedCustomIntersectionPolylineIdAtom, selectedCustomIntersectionPolylineId); + }, + (getInterfaceValue, setAtomValue) => { + const intersectionType = getInterfaceValue("intersectionType"); + setAtomValue(intersectionTypeAtom, intersectionType); + }, + (getInterfaceValue, setAtomValue) => { + const wellboreHeader = getInterfaceValue("wellboreHeader"); + setAtomValue(wellboreHeaderAtom, wellboreHeader); + }, + (getInterfaceValue, setAtomValue) => { + const intersectionExtensionLength = getInterfaceValue("intersectionExtensionLength"); + setAtomValue(intersectionExtensionLengthAtom, intersectionExtensionLength); + }, +]; diff --git a/frontend/src/modules/Intersection/view/atoms/queryAtoms.ts b/frontend/src/modules/Intersection/view/atoms/queryAtoms.ts new file mode 100644 index 000000000..ca98144d2 --- /dev/null +++ b/frontend/src/modules/Intersection/view/atoms/queryAtoms.ts @@ -0,0 +1,22 @@ +import { WellboreTrajectory_api } from "@api"; +import { apiService } from "@framework/ApiService"; + +import { atomWithQuery } from "jotai-tanstack-query"; + +import { wellboreHeaderAtom } from "./baseAtoms"; + +const STALE_TIME = 60 * 1000; +const CACHE_TIME = 60 * 1000; + +export const wellboreTrajectoryQueryAtom = atomWithQuery((get) => { + const wellbore = get(wellboreHeaderAtom); + + return { + queryKey: ["getWellboreTrajectory", wellbore?.uuid ?? ""], + queryFn: () => apiService.well.getWellTrajectories(wellbore?.uuid ? [wellbore.uuid] : []), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + select: (data: WellboreTrajectory_api[]) => data[0], + enabled: wellbore?.uuid ? true : false, + }; +}); diff --git a/frontend/src/modules/Intersection/view/components/layersWrapper.tsx b/frontend/src/modules/Intersection/view/components/layersWrapper.tsx index d14f3117b..7bc341c54 100644 --- a/frontend/src/modules/Intersection/view/components/layersWrapper.tsx +++ b/frontend/src/modules/Intersection/view/components/layersWrapper.tsx @@ -18,8 +18,7 @@ import { SurfaceStatisticalFanchart } from "@framework/components/EsvIntersectio import { makeSurfaceStatisticalFanchartFromRealizationSurface } from "@framework/components/EsvIntersection/utils/surfaceStatisticalFancharts"; import { IntersectionType } from "@framework/types/intersection"; import { useElementBoundingRect } from "@lib/hooks/useElementBoundingRect"; -import { SettingsToViewInterface } from "@modules/Intersection/settingsToViewInterface"; -import { State } from "@modules/Intersection/state"; +import { Interfaces } from "@modules/Intersection/interfaces"; import { BaseLayer, LayerStatus, useLayers } from "@modules/Intersection/utils/layers/BaseLayer"; import { GridLayer, isGridLayer } from "@modules/Intersection/utils/layers/GridLayer"; import { SeismicLayer, isSeismicLayer } from "@modules/Intersection/utils/layers/SeismicLayer"; @@ -33,7 +32,6 @@ import { isEqual } from "lodash"; import { ViewportWrapper } from "./viewportWrapper"; import { ColorScaleWithName } from "../../../_shared/utils/ColorScaleWithName"; -import { ViewAtoms } from "../atoms/atomDefinitions"; export type LayersWrapperProps = { referenceSystem: IntersectionReferenceSystem | null; @@ -42,7 +40,7 @@ export type LayersWrapperProps = { intersectionExtensionLength: number; intersectionType: IntersectionType; workbenchServices: WorkbenchServices; - viewContext: ViewContext, ViewAtoms>; + viewContext: ViewContext; wellboreHeaderUuid: string | null; wellboreHeaderDepthReferencePoint: string | null; wellboreHeaderDepthReferenceElevation: number | null; diff --git a/frontend/src/modules/Intersection/view/components/readoutWrapper.tsx b/frontend/src/modules/Intersection/view/components/readoutWrapper.tsx index a6a525329..750c826f8 100644 --- a/frontend/src/modules/Intersection/view/components/readoutWrapper.tsx +++ b/frontend/src/modules/Intersection/view/components/readoutWrapper.tsx @@ -12,13 +12,10 @@ import { import { HighlightItem, HighlightItemShape, ReadoutItem } from "@framework/components/EsvIntersection/types"; import { ReadoutBox } from "@framework/components/EsvIntersection/utilityComponents/ReadoutBox"; import { isWellborepathLayer } from "@framework/components/EsvIntersection/utils/layers"; -import { SettingsToViewInterface } from "@modules/Intersection/settingsToViewInterface"; -import { State } from "@modules/Intersection/state"; +import { Interfaces } from "@modules/Intersection/interfaces"; import { isEqual } from "lodash"; -import { ViewAtoms } from "../atoms/atomDefinitions"; - export type ReadoutWrapperProps = { wellboreHeaderUuid: string | null; showGrid: boolean; @@ -33,7 +30,7 @@ export type ReadoutWrapperProps = { }; verticalScale: number; workbenchServices: WorkbenchServices; - viewContext: ViewContext, ViewAtoms>; + viewContext: ViewContext; }; export function ReadoutWrapper(props: ReadoutWrapperProps): React.ReactNode { diff --git a/frontend/src/modules/Intersection/view/components/viewportWrapper.tsx b/frontend/src/modules/Intersection/view/components/viewportWrapper.tsx index 85b7bf7f4..95e13736b 100644 --- a/frontend/src/modules/Intersection/view/components/viewportWrapper.tsx +++ b/frontend/src/modules/Intersection/view/components/viewportWrapper.tsx @@ -6,15 +6,12 @@ import { SyncSettingKey, SyncSettingsHelper } from "@framework/SyncSettings"; import { WorkbenchServices } from "@framework/WorkbenchServices"; import { LayerItem, Viewport } from "@framework/components/EsvIntersection"; import { Toolbar } from "@framework/components/EsvIntersection/utilityComponents/Toolbar"; -import { SettingsToViewInterface } from "@modules/Intersection/settingsToViewInterface"; -import { State } from "@modules/Intersection/state"; +import { Interfaces } from "@modules/Intersection/interfaces"; import { cloneDeep, isEqual } from "lodash"; import { ReadoutWrapper } from "./readoutWrapper"; -import { ViewAtoms } from "../atoms/atomDefinitions"; - export type ViewportWrapperProps = { wellboreHeaderUuid: string | null; referenceSystem?: IntersectionReferenceSystem; @@ -26,7 +23,7 @@ export type ViewportWrapperProps = { }; viewport: Viewport | null; workbenchServices: WorkbenchServices; - viewContext: ViewContext, ViewAtoms>; + viewContext: ViewContext; }; export function ViewportWrapper(props: ViewportWrapperProps): React.ReactNode { diff --git a/frontend/src/modules/Intersection/view/view.tsx b/frontend/src/modules/Intersection/view/view.tsx index bdd9583c6..86161141d 100644 --- a/frontend/src/modules/Intersection/view/view.tsx +++ b/frontend/src/modules/Intersection/view/view.tsx @@ -7,12 +7,14 @@ import { IntersectionType } from "@framework/types/intersection"; import { CircularProgress } from "@lib/components/CircularProgress"; import { resolveClassNames } from "@lib/utils/resolveClassNames"; -import { ViewAtoms } from "./atoms/atomDefinitions"; +import { useAtomValue } from "jotai"; + +import { intersectionReferenceSystemAtom, polylineAtom } from "./atoms/derivedAtoms"; +import { wellboreTrajectoryQueryAtom } from "./atoms/queryAtoms"; import { LayersWrapper } from "./components/layersWrapper"; import { useWellboreCasingsQuery } from "./queries/wellboreSchematicsQueries"; -import { SettingsToViewInterface } from "../settingsToViewInterface"; -import { State } from "../state"; +import { Interfaces } from "../interfaces"; import { LayerStatus, useLayersStatuses } from "../utils/layers/BaseLayer"; import { isGridLayer } from "../utils/layers/GridLayer"; import { LayerManagerTopic, useLayerManagerTopicValue } from "../utils/layers/LayerManager"; @@ -21,17 +23,15 @@ import { isSurfaceLayer } from "../utils/layers/SurfaceLayer"; import { isSurfacesUncertaintyLayer } from "../utils/layers/SurfacesUncertaintyLayer"; import { isWellpicksLayer } from "../utils/layers/WellpicksLayer"; -export function View( - props: ModuleViewProps, ViewAtoms> -): JSX.Element { +export function View(props: ModuleViewProps): React.ReactNode { const statusWriter = useViewStatusWriter(props.viewContext); const ensembleSet = useEnsembleSet(props.workbenchSession); const ensembleIdent = props.viewContext.useSettingsToViewInterfaceValue("ensembleIdent"); - const intersectionReferenceSystem = props.viewContext.useViewAtomValue("intersectionReferenceSystemAtom"); + const intersectionReferenceSystem = useAtomValue(intersectionReferenceSystemAtom); const wellboreHeader = props.viewContext.useSettingsToViewInterfaceValue("wellboreHeader"); - const wellboreTrajectoryQuery = props.viewContext.useViewAtomValue("wellboreTrajectoryQueryAtom"); - const polyline = props.viewContext.useViewAtomValue("polylineAtom"); + const wellboreTrajectoryQuery = useAtomValue(wellboreTrajectoryQueryAtom); + const polyline = useAtomValue(polylineAtom); const extensionLength = props.viewContext.useSettingsToViewInterfaceValue("intersectionExtensionLength"); const wellbore = props.viewContext.useSettingsToViewInterfaceValue("wellboreHeader"); diff --git a/frontend/src/modules/Map/MapState.ts b/frontend/src/modules/Map/MapState.ts deleted file mode 100644 index 630749b84..000000000 --- a/frontend/src/modules/Map/MapState.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { FullSurfaceAddress } from "@modules/_shared/Surface"; - -export interface MapState { - surfaceAddress: FullSurfaceAddress | null; -} diff --git a/frontend/src/modules/Map/interfaces.ts b/frontend/src/modules/Map/interfaces.ts new file mode 100644 index 000000000..c951678dc --- /dev/null +++ b/frontend/src/modules/Map/interfaces.ts @@ -0,0 +1,16 @@ +import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; +import { FullSurfaceAddress } from "@modules/_shared/Surface/surfaceAddress"; + +import { surfaceAddressAtom } from "./settings/atoms/baseAtoms"; + +type SettingsToViewInterface = { + surfaceAddress: FullSurfaceAddress | null; +}; + +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + surfaceAddress: (get) => get(surfaceAddressAtom), +}; diff --git a/frontend/src/modules/Map/loadModule.tsx b/frontend/src/modules/Map/loadModule.tsx index 71946f1bf..e9296f6c4 100644 --- a/frontend/src/modules/Map/loadModule.tsx +++ b/frontend/src/modules/Map/loadModule.tsx @@ -1,16 +1,10 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; -import { MapSettings } from "./MapSettings"; -import { MapState } from "./MapState"; -import { MapView } from "./MapView"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; +import { MapSettings } from "./settings/settings"; +import { MapView } from "./view"; -const defaultState: MapState = { - surfaceAddress: null, -}; - -const module = ModuleRegistry.initModule("Map", defaultState, { - surfaceAddress: { deepCompare: true }, -}); +const module = ModuleRegistry.initModule("Map", { settingsToViewInterfaceInitialization }); module.viewFC = MapView; module.settingsFC = MapSettings; diff --git a/frontend/src/modules/Map/registerModule.ts b/frontend/src/modules/Map/registerModule.ts index 612894c96..00cc0ce42 100644 --- a/frontend/src/modules/Map/registerModule.ts +++ b/frontend/src/modules/Map/registerModule.ts @@ -3,12 +3,12 @@ import { ModuleDataTagId } from "@framework/ModuleDataTags"; import { ModuleRegistry } from "@framework/ModuleRegistry"; import { SyncSettingKey } from "@framework/SyncSettings"; -import { MapState } from "./MapState"; +import { Interfaces } from "./interfaces"; import { preview } from "./preview"; const description = "Plotting of surfaces in a 2D top view."; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: "Map", defaultTitle: "Map", category: ModuleCategory.MAIN, diff --git a/frontend/src/modules/Map/settings/atoms/baseAtoms.ts b/frontend/src/modules/Map/settings/atoms/baseAtoms.ts new file mode 100644 index 000000000..2c4afe992 --- /dev/null +++ b/frontend/src/modules/Map/settings/atoms/baseAtoms.ts @@ -0,0 +1,5 @@ +import { FullSurfaceAddress } from "@modules/_shared/Surface"; + +import { atom } from "jotai"; + +export const surfaceAddressAtom = atom(null); diff --git a/frontend/src/modules/Map/MapSettings.tsx b/frontend/src/modules/Map/settings/settings.tsx similarity index 97% rename from frontend/src/modules/Map/MapSettings.tsx rename to frontend/src/modules/Map/settings/settings.tsx index 3e1a2cd3e..3008e7e61 100644 --- a/frontend/src/modules/Map/MapSettings.tsx +++ b/frontend/src/modules/Map/settings/settings.tsx @@ -19,8 +19,12 @@ import { FullSurfaceAddress, SurfaceAddressBuilder, SurfaceDirectory, SurfaceTim import { useObservedSurfacesMetadataQuery, useRealizationSurfacesMetadataQuery } from "@modules/_shared/Surface"; import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePropagateApiErrorToStatusWriter"; -import { MapState } from "./MapState"; -import { AggregationDropdown } from "./UiComponents"; +import { useSetAtom } from "jotai"; + +import { surfaceAddressAtom } from "./atoms/baseAtoms"; + +import { AggregationDropdown } from "../UiComponents"; +import { Interfaces } from "../interfaces"; const SurfaceTimeTypeEnumToStringMapping = { [SurfaceTimeType.None]: "Static", @@ -28,7 +32,7 @@ const SurfaceTimeTypeEnumToStringMapping = { [SurfaceTimeType.Interval]: "Time interval", }; //----------------------------------------------------------------------------------------------------------- -export function MapSettings(props: ModuleSettingsProps) { +export function MapSettings(props: ModuleSettingsProps) { const ensembleSet = useEnsembleSet(props.workbenchSession); const [selectedEnsembleIdent, setSelectedEnsembleIdent] = React.useState(null); const [timeType, setTimeType] = React.useState(SurfaceTimeType.None); @@ -41,6 +45,7 @@ export function MapSettings(props: ModuleSettingsProps) { const [selectedTimeOrInterval, setSelectedTimeOrInterval] = React.useState(null); const [aggregation, setAggregation] = React.useState(null); const [useObserved, toggleUseObserved] = React.useState(false); + const setSurfaceAddress = useSetAtom(surfaceAddressAtom); const syncedSettingKeys = props.settingsContext.useSyncedSettingKeys(); const syncHelper = new SyncSettingsHelper(syncedSettingKeys, props.workbenchServices); const syncedValueEnsembles = syncHelper.useValue(SyncSettingKey.ENSEMBLE, "global.syncValue.ensembles"); @@ -120,7 +125,7 @@ export function MapSettings(props: ModuleSettingsProps) { } console.debug(`propagateSurfaceSelectionToView() => ${surfaceAddress ? "valid surfAddr" : "NULL surfAddr"}`); - props.settingsContext.getStateStore().setValue("surfaceAddress", surfaceAddress); + setSurfaceAddress(surfaceAddress); }); function handleEnsembleSelectionChange(newEnsembleIdent: EnsembleIdent | null) { diff --git a/frontend/src/modules/Map/MapView.tsx b/frontend/src/modules/Map/view.tsx similarity index 93% rename from frontend/src/modules/Map/MapView.tsx rename to frontend/src/modules/Map/view.tsx index 4ed8b498b..e82842f58 100644 --- a/frontend/src/modules/Map/MapView.tsx +++ b/frontend/src/modules/Map/view.tsx @@ -9,11 +9,10 @@ import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePr import { useSurfaceDataQueryByAddress } from "@modules_shared/Surface"; import SubsurfaceViewer from "@webviz/subsurface-viewer"; -import { MapState } from "./MapState"; +import { Interfaces } from "./interfaces"; -//----------------------------------------------------------------------------------------------------------- -export function MapView(props: ModuleViewProps) { - const surfaceAddress = props.viewContext.useStoreValue("surfaceAddress"); +export function MapView(props: ModuleViewProps): React.ReactNode { + const surfaceAddress = props.viewContext.useSettingsToViewInterfaceValue("surfaceAddress"); const statusWriter = useViewStatusWriter(props.viewContext); diff --git a/frontend/src/modules/MyModule/interfaces.ts b/frontend/src/modules/MyModule/interfaces.ts new file mode 100644 index 000000000..c3d406a60 --- /dev/null +++ b/frontend/src/modules/MyModule/interfaces.ts @@ -0,0 +1,34 @@ +import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; +import { ColorScaleGradientType, ColorScaleType } from "@lib/utils/ColorScale"; + +import { divMidPointAtom, gradientTypeAtom, maxAtom, minAtom, typeAtom } from "./settings/atoms/baseAtoms"; + +type SettingsToViewInterface = { + type: ColorScaleType; + gradientType: ColorScaleGradientType; + min: number; + max: number; + divMidPoint: number; +}; + +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + type: (get) => { + return get(typeAtom); + }, + gradientType: (get) => { + return get(gradientTypeAtom); + }, + min: (get) => { + return get(minAtom); + }, + max: (get) => { + return get(maxAtom); + }, + divMidPoint: (get) => { + return get(divMidPointAtom); + }, +}; diff --git a/frontend/src/modules/MyModule/loadModule.tsx b/frontend/src/modules/MyModule/loadModule.tsx index 7e0a51ed1..6c34335d5 100644 --- a/frontend/src/modules/MyModule/loadModule.tsx +++ b/frontend/src/modules/MyModule/loadModule.tsx @@ -1,19 +1,10 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; -import { ColorScaleGradientType, ColorScaleType } from "@lib/utils/ColorScale"; -import { settings } from "./settings"; -import { State } from "./state"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; +import { Settings } from "./settings/settings"; import { View } from "./view"; -const defaultState: State = { - type: ColorScaleType.Discrete, - gradientType: ColorScaleGradientType.Sequential, - min: 0, - max: 18, - divMidPoint: 9, -}; - -const module = ModuleRegistry.initModule("MyModule", defaultState); +const module = ModuleRegistry.initModule("MyModule", { settingsToViewInterfaceInitialization }); module.viewFC = View; -module.settingsFC = settings; +module.settingsFC = Settings; diff --git a/frontend/src/modules/MyModule/registerModule.ts b/frontend/src/modules/MyModule/registerModule.ts index 14c1cda66..77da7eb97 100644 --- a/frontend/src/modules/MyModule/registerModule.ts +++ b/frontend/src/modules/MyModule/registerModule.ts @@ -1,9 +1,9 @@ import { ModuleCategory, ModuleDevState } from "@framework/Module"; import { ModuleRegistry } from "@framework/ModuleRegistry"; -import { State } from "./state"; +import { Interfaces } from "./interfaces"; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: "MyModule", defaultTitle: "My Module", category: ModuleCategory.DEBUG, diff --git a/frontend/src/modules/MyModule/settings/atoms/baseAtoms.ts b/frontend/src/modules/MyModule/settings/atoms/baseAtoms.ts new file mode 100644 index 000000000..59f519693 --- /dev/null +++ b/frontend/src/modules/MyModule/settings/atoms/baseAtoms.ts @@ -0,0 +1,9 @@ +import { ColorScaleGradientType, ColorScaleType } from "@lib/utils/ColorScale"; + +import { atom } from "jotai"; + +export const typeAtom = atom(ColorScaleType.Discrete); +export const gradientTypeAtom = atom(ColorScaleGradientType.Sequential); +export const minAtom = atom(0); +export const maxAtom = atom(18); +export const divMidPointAtom = atom(9); diff --git a/frontend/src/modules/MyModule/settings.tsx b/frontend/src/modules/MyModule/settings/settings.tsx similarity index 88% rename from frontend/src/modules/MyModule/settings.tsx rename to frontend/src/modules/MyModule/settings/settings.tsx index 4889a2b0e..e8adff2f2 100644 --- a/frontend/src/modules/MyModule/settings.tsx +++ b/frontend/src/modules/MyModule/settings/settings.tsx @@ -7,14 +7,18 @@ import { Label } from "@lib/components/Label"; import { RadioGroup } from "@lib/components/RadioGroup"; import { ColorScaleGradientType, ColorScaleType } from "@lib/utils/ColorScale"; -import { State } from "./state"; +import { useAtom } from "jotai"; -export const settings = (props: ModuleSettingsProps) => { - const [type, setType] = props.settingsContext.useStoreState("type"); - const [gradientType, setGradientType] = props.settingsContext.useStoreState("gradientType"); - const [min, setMin] = props.settingsContext.useStoreState("min"); - const [max, setMax] = props.settingsContext.useStoreState("max"); - const [divMidPoint, setDivMidPoint] = props.settingsContext.useStoreState("divMidPoint"); +import { divMidPointAtom, gradientTypeAtom, maxAtom, minAtom, typeAtom } from "./atoms/baseAtoms"; + +import { Interfaces } from "../interfaces"; + +export function Settings(props: ModuleSettingsProps): React.ReactNode { + const [type, setType] = useAtom(typeAtom); + const [gradientType, setGradientType] = useAtom(gradientTypeAtom); + const [min, setMin] = useAtom(minAtom); + const [max, setMax] = useAtom(maxAtom); + const [divMidPoint, setDivMidPoint] = useAtom(divMidPointAtom); function handleTypeChange(e: React.ChangeEvent) { setType(e.target.value as ColorScaleType); @@ -107,4 +111,4 @@ export const settings = (props: ModuleSettingsProps) => { )}
); -}; +} diff --git a/frontend/src/modules/MyModule/state.ts b/frontend/src/modules/MyModule/state.ts deleted file mode 100644 index 7681f121f..000000000 --- a/frontend/src/modules/MyModule/state.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ColorScaleGradientType, ColorScaleType } from "@lib/utils/ColorScale"; - -export type State = { - type: ColorScaleType; - gradientType: ColorScaleGradientType; - min: number; - max: number; - divMidPoint: number; -}; diff --git a/frontend/src/modules/MyModule/view.tsx b/frontend/src/modules/MyModule/view.tsx index e882845e5..f07a894df 100644 --- a/frontend/src/modules/MyModule/view.tsx +++ b/frontend/src/modules/MyModule/view.tsx @@ -7,7 +7,7 @@ import { ColorScaleType } from "@lib/utils/ColorScale"; import { PlotData } from "plotly.js"; -import { State } from "./state"; +import { Interfaces } from "./interfaces"; const countryData = [ "Belarus", @@ -402,12 +402,12 @@ for (let i = 0; i < countryData.length; i += 2) { alcConsumption.push(countryData[i + 1] as number); } -export const View = (props: ModuleViewProps) => { - const type = props.viewContext.useStoreValue("type"); - const gradientType = props.viewContext.useStoreValue("gradientType"); - const min = props.viewContext.useStoreValue("min"); - const max = props.viewContext.useStoreValue("max"); - const divMidPoint = props.viewContext.useStoreValue("divMidPoint"); +export function View(props: ModuleViewProps): React.ReactNode { + const type = props.viewContext.useSettingsToViewInterfaceValue("type"); + const gradientType = props.viewContext.useSettingsToViewInterfaceValue("gradientType"); + const min = props.viewContext.useSettingsToViewInterfaceValue("min"); + const max = props.viewContext.useSettingsToViewInterfaceValue("max"); + const divMidPoint = props.viewContext.useSettingsToViewInterfaceValue("divMidPoint"); const ref = React.useRef(null); @@ -444,4 +444,4 @@ export const View = (props: ModuleViewProps) => { ); -}; +} diff --git a/frontend/src/modules/MyModule2/interfaces.ts b/frontend/src/modules/MyModule2/interfaces.ts new file mode 100644 index 000000000..c74218dd6 --- /dev/null +++ b/frontend/src/modules/MyModule2/interfaces.ts @@ -0,0 +1,17 @@ +import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; + +import { textAtom } from "./atoms"; + +type SettingsToViewInterface = { + text: string; + derivedText: string; +}; + +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + text: (get) => get(textAtom), + derivedText: (get) => get(textAtom).toUpperCase(), +}; diff --git a/frontend/src/modules/MyModule2/loadModule.tsx b/frontend/src/modules/MyModule2/loadModule.tsx index cccd7c747..d83e138bd 100644 --- a/frontend/src/modules/MyModule2/loadModule.tsx +++ b/frontend/src/modules/MyModule2/loadModule.tsx @@ -1,16 +1,11 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; import "./atoms"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; import { Settings } from "./settings"; -import { SettingsToViewInterface, State, defaultState, interfaceDefinition } from "./state"; import { View } from "./view"; -const module = ModuleRegistry.initModule( - "MyModule2", - defaultState, - undefined, - interfaceDefinition -); +const module = ModuleRegistry.initModule("MyModule2", { settingsToViewInterfaceInitialization }); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/MyModule2/registerModule.ts b/frontend/src/modules/MyModule2/registerModule.ts index 1a992985d..0bfcc8169 100644 --- a/frontend/src/modules/MyModule2/registerModule.ts +++ b/frontend/src/modules/MyModule2/registerModule.ts @@ -1,9 +1,9 @@ import { ModuleCategory, ModuleDevState } from "@framework/Module"; import { ModuleRegistry } from "@framework/ModuleRegistry"; -import { SettingsToViewInterface, State } from "./state"; +import { Interfaces } from "./interfaces"; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: "MyModule2", defaultTitle: "My Module 2", category: ModuleCategory.DEBUG, diff --git a/frontend/src/modules/MyModule2/settings.tsx b/frontend/src/modules/MyModule2/settings.tsx index dbd90340a..0528c3590 100644 --- a/frontend/src/modules/MyModule2/settings.tsx +++ b/frontend/src/modules/MyModule2/settings.tsx @@ -7,7 +7,7 @@ import { useAtom } from "jotai"; import { textAtom } from "./atoms"; -export const Settings = () => { +export function Settings(): React.ReactNode { const [atomText, setAtomText] = useAtom(textAtom); function handleAtomTextChange(event: React.ChangeEvent) { @@ -21,6 +21,6 @@ export const Settings = () => { ); -}; +} Settings.displayName = "Settings"; diff --git a/frontend/src/modules/MyModule2/state.ts b/frontend/src/modules/MyModule2/state.ts deleted file mode 100644 index eb9f9a6d3..000000000 --- a/frontend/src/modules/MyModule2/state.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; - -import { textAtom } from "./atoms"; - -export type State = { - text: string; -}; - -export const defaultState: State = { - text: "Hello World", -}; - -// ------------------------------------------------ - -export type SettingsToViewInterface = { - text: string; - derivedText: string; -}; - -export const interfaceDefinition: InterfaceInitialization = { - text: (get) => get(textAtom), - derivedText: (get) => get(textAtom).toUpperCase(), -}; diff --git a/frontend/src/modules/MyModule2/view.tsx b/frontend/src/modules/MyModule2/view.tsx index 5d095cdff..24145ea06 100644 --- a/frontend/src/modules/MyModule2/view.tsx +++ b/frontend/src/modules/MyModule2/view.tsx @@ -1,11 +1,9 @@ -import React from "react"; - import { ModuleViewProps } from "@framework/Module"; import { Label } from "@lib/components/Label"; -import { SettingsToViewInterface, State } from "./state"; +import { Interfaces } from "./interfaces"; -export const View = (props: ModuleViewProps) => { +export const View = (props: ModuleViewProps) => { const text = props.viewContext.useSettingsToViewInterfaceValue("text"); const derivedText = props.viewContext.useSettingsToViewInterfaceValue("derivedText"); diff --git a/frontend/src/modules/ParameterDistributionMatrix/settingsToViewInterface.ts b/frontend/src/modules/ParameterDistributionMatrix/interfaces.ts similarity index 85% rename from frontend/src/modules/ParameterDistributionMatrix/settingsToViewInterface.ts rename to frontend/src/modules/ParameterDistributionMatrix/interfaces.ts index 53e9478d9..f6d9b3f63 100644 --- a/frontend/src/modules/ParameterDistributionMatrix/settingsToViewInterface.ts +++ b/frontend/src/modules/ParameterDistributionMatrix/interfaces.ts @@ -10,7 +10,7 @@ import { import { selectedEnsembleIdentsAtom, selectedParameterIdentsAtom } from "./settings/atoms/derivedAtoms"; import { ParameterDistributionPlotType } from "./typesAndEnums"; -export type Interface = { +type SettingsToViewInterface = { selectedVisualizationType: ParameterDistributionPlotType; showIndividualRealizationValues: boolean; showPercentilesAndMeanLines: boolean; @@ -18,7 +18,11 @@ export type Interface = { selectedParameterIdents: ParameterIdent[]; }; -export const interfaceInitialization: InterfaceInitialization = { +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { selectedVisualizationType: (get) => { return get(selectedVisualizationTypeAtom); }, diff --git a/frontend/src/modules/ParameterDistributionMatrix/loadModule.tsx b/frontend/src/modules/ParameterDistributionMatrix/loadModule.tsx index 3375f4f97..ed2add361 100644 --- a/frontend/src/modules/ParameterDistributionMatrix/loadModule.tsx +++ b/frontend/src/modules/ParameterDistributionMatrix/loadModule.tsx @@ -1,14 +1,11 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; import { MODULE_NAME } from "./registerModule"; import { Settings } from "./settings/settings"; -import { Interface, interfaceInitialization } from "./settingsToViewInterface"; -import { State } from "./state"; import { View } from "./view/view"; -const defaultState: State = {}; - -const module = ModuleRegistry.initModule(MODULE_NAME, defaultState, {}, interfaceInitialization); +const module = ModuleRegistry.initModule(MODULE_NAME, { settingsToViewInterfaceInitialization }); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/ParameterDistributionMatrix/registerModule.tsx b/frontend/src/modules/ParameterDistributionMatrix/registerModule.tsx index a9d2496f6..eb6c1921c 100644 --- a/frontend/src/modules/ParameterDistributionMatrix/registerModule.tsx +++ b/frontend/src/modules/ParameterDistributionMatrix/registerModule.tsx @@ -1,14 +1,13 @@ import { ModuleCategory, ModuleDevState } from "@framework/Module"; import { ModuleRegistry } from "@framework/ModuleRegistry"; -import { Interface } from "./settingsToViewInterface"; -import { State } from "./state"; +import { Interfaces } from "./interfaces"; export const MODULE_NAME = "ParameterDistributionMatrix"; const description = "Plotting of parameter distributions"; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: MODULE_NAME, defaultTitle: "Parameter Distribution Matrix", category: ModuleCategory.MAIN, diff --git a/frontend/src/modules/ParameterDistributionMatrix/settings/settings.tsx b/frontend/src/modules/ParameterDistributionMatrix/settings/settings.tsx index e42211a1c..417d4b8cf 100644 --- a/frontend/src/modules/ParameterDistributionMatrix/settings/settings.tsx +++ b/frontend/src/modules/ParameterDistributionMatrix/settings/settings.tsx @@ -24,12 +24,14 @@ import { selectedParameterIdentsAtom, } from "./atoms/derivedAtoms"; -import { Interface } from "../settingsToViewInterface"; -import { State } from "../state"; -import { MAX_PARAMETERS } from "../typesAndEnums"; -import { ParameterDistributionPlotType, ParameterDistributionPlotTypeEnumToStringMapping } from "../typesAndEnums"; +import { Interfaces } from "../interfaces"; +import { + MAX_PARAMETERS, + ParameterDistributionPlotType, + ParameterDistributionPlotTypeEnumToStringMapping, +} from "../typesAndEnums"; -export function Settings({ workbenchSession }: ModuleSettingsProps) { +export function Settings({ workbenchSession }: ModuleSettingsProps) { const ensembleSet = useEnsembleSet(workbenchSession); const selectedEnsembleIdents = useAtomValue(selectedEnsembleIdentsAtom); diff --git a/frontend/src/modules/ParameterDistributionMatrix/state.ts b/frontend/src/modules/ParameterDistributionMatrix/state.ts deleted file mode 100644 index 90ff7f709..000000000 --- a/frontend/src/modules/ParameterDistributionMatrix/state.ts +++ /dev/null @@ -1 +0,0 @@ -export type State = Record; diff --git a/frontend/src/modules/ParameterDistributionMatrix/typesAndEnums.ts b/frontend/src/modules/ParameterDistributionMatrix/typesAndEnums.ts index 2da7d740a..a5d9cb54c 100644 --- a/frontend/src/modules/ParameterDistributionMatrix/typesAndEnums.ts +++ b/frontend/src/modules/ParameterDistributionMatrix/typesAndEnums.ts @@ -2,6 +2,7 @@ import { ParameterIdent } from "@framework/EnsembleParameters"; export type EnsembleParameterRealizationsAndValues = { ensembleDisplayName: string; + ensembleColor: string; realizations: number[]; values: number[]; }; diff --git a/frontend/src/modules/ParameterDistributionMatrix/view/components/ParameterDistributionPlot.tsx b/frontend/src/modules/ParameterDistributionMatrix/view/components/ParameterDistributionPlot.tsx index 6448f8f8b..cb7e00bd2 100644 --- a/frontend/src/modules/ParameterDistributionMatrix/view/components/ParameterDistributionPlot.tsx +++ b/frontend/src/modules/ParameterDistributionMatrix/view/components/ParameterDistributionPlot.tsx @@ -9,7 +9,6 @@ import { ParameterDataArr, ParameterDistributionPlotType } from "../../typesAndE type ParameterDistributionPlotProps = { dataArr: ParameterDataArr[]; - ensembleColors: Map; plotType: ParameterDistributionPlotType; showIndividualRealizationValues: boolean; showPercentilesAndMeanLines: boolean; @@ -36,7 +35,6 @@ export const ParameterDistributionPlot: React.FC if (shouldShowLegend) { addedLegendNames.add(ensembleData.ensembleDisplayName); } - const ensembleColor = props.ensembleColors.get(ensembleData.ensembleDisplayName); const distributionTrace = { x: ensembleData.values, @@ -44,7 +42,7 @@ export const ParameterDistributionPlot: React.FC spanmode: "hard", name: ensembleData.ensembleDisplayName, legendgroup: ensembleData.ensembleDisplayName, - marker: { color: ensembleColor }, + marker: { color: ensembleData.ensembleColor }, xaxis: `x${subplotIndex}`, yaxis: `y${subplotIndex}`, showlegend: shouldShowLegend, @@ -65,7 +63,7 @@ export const ParameterDistributionPlot: React.FC ensembleData.values, yPosition, ensembleData.ensembleDisplayName, - ensembleColor, + ensembleData.ensembleColor, subplotIndex ) ); @@ -93,7 +91,7 @@ export const ParameterDistributionPlot: React.FC hoverinfo: "x+text+name", mode: "markers", marker: { - color: props.ensembleColors.get(ensembleData.ensembleDisplayName), + color: ensembleData.ensembleColor, symbol: "line-ns-open", }, showlegend: false, @@ -123,8 +121,6 @@ export const ParameterDistributionPlot: React.FC throw new Error("Realizations and values must have the same length"); } - const ensembleColor = props.ensembleColors.get(ensembleData.ensembleDisplayName); - const verticalPosition = index * (2 + 1); // 2 is the height of each box + 1 space const hoverText = ensembleData.values.map( (_, index) => `Realization: ${ensembleData.realizations[index]}` @@ -135,7 +131,7 @@ export const ParameterDistributionPlot: React.FC type: "box", name: ensembleData.ensembleDisplayName, legendgroup: ensembleData.ensembleDisplayName, - marker: { color: ensembleColor }, + marker: { color: ensembleData.ensembleColor }, xaxis: `x${subplotIndex}`, yaxis: `y${subplotIndex}`, showlegend: shouldShowLegend, @@ -157,7 +153,7 @@ export const ParameterDistributionPlot: React.FC ensembleData.values, verticalPosition, ensembleData.ensembleDisplayName, - ensembleColor, + ensembleData.ensembleColor, subplotIndex ) ); diff --git a/frontend/src/modules/ParameterDistributionMatrix/view/view.tsx b/frontend/src/modules/ParameterDistributionMatrix/view/view.tsx index 41e0f1c14..7b717a11d 100644 --- a/frontend/src/modules/ParameterDistributionMatrix/view/view.tsx +++ b/frontend/src/modules/ParameterDistributionMatrix/view/view.tsx @@ -9,11 +9,10 @@ import { useElementSize } from "@lib/hooks/useElementSize"; import { ParameterDistributionPlot } from "./components/ParameterDistributionPlot"; -import { Interface } from "../settingsToViewInterface"; -import { State } from "../state"; +import { Interfaces } from "../interfaces"; import { ParameterDataArr } from "../typesAndEnums"; -export function View(props: ModuleViewProps) { +export function View(props: ModuleViewProps) { const wrapperDivRef = React.useRef(null); const wrapperDivSize = useElementSize(wrapperDivRef); @@ -36,18 +35,10 @@ export function View(props: ModuleViewProps) { filterEnsembleRealizationsFunc ); - const colorSet = props.workbenchSettings.useColorSet(); - const ensembleColors = new Map(); - ensembleSet.getEnsembleArr().forEach((ensemble, index) => { - const color = index === 0 ? colorSet.getFirstColor() : colorSet.getNextColor(); - ensembleColors.set(ensemble.getDisplayName(), color); - }); - return (
= { + selectedPhase: (get) => { + return get(selectedPhaseAtom); + }, + selectedColorBy: (get) => { + return get(selectedColorByAtom); + }, + selectedDependentVariables: (get) => { + return get(selectedDependentVariablesAtom); + }, + selectedEnsembleIdents: (get) => { + return get(selectedEnsembleIdentsAtom); + }, + selectedPvtNums: (get) => { + return get(selectedPvtNumsAtom); + }, + pvtDataQueries: (get) => { + return get(pvtDataQueriesAtom); + }, +}; diff --git a/frontend/src/modules/Pvt/loadModule.tsx b/frontend/src/modules/Pvt/loadModule.tsx index 782c17c8d..28c1dbd3c 100644 --- a/frontend/src/modules/Pvt/loadModule.tsx +++ b/frontend/src/modules/Pvt/loadModule.tsx @@ -1,19 +1,11 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; import { MODULE_NAME } from "./registerModule"; import { Settings } from "./settings/settings"; -import { SettingsToViewInterface, interfaceInitialization } from "./settingsToViewInterface"; -import { State } from "./state"; import { View } from "./view"; -const defaultState: State = {}; - -const module = ModuleRegistry.initModule( - MODULE_NAME, - defaultState, - {}, - interfaceInitialization -); +const module = ModuleRegistry.initModule(MODULE_NAME, { settingsToViewInterfaceInitialization }); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/Pvt/registerModule.tsx b/frontend/src/modules/Pvt/registerModule.tsx index cc35d5f45..66895704a 100644 --- a/frontend/src/modules/Pvt/registerModule.tsx +++ b/frontend/src/modules/Pvt/registerModule.tsx @@ -2,16 +2,15 @@ import { ModuleCategory, ModuleDevState } from "@framework/Module"; import { ModuleDataTagId } from "@framework/ModuleDataTags"; import { ModuleRegistry } from "@framework/ModuleRegistry"; +import { Interfaces } from "./interfaces"; import { preview } from "./preview"; -import { SettingsToViewInterface } from "./settingsToViewInterface"; -import { State } from "./state"; export const MODULE_NAME = "Pvt"; const description = "Visualizes formation volume factor and viscosity data for oil, gas, and water from Eclipse init and include files."; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: MODULE_NAME, defaultTitle: "PVT", category: ModuleCategory.MAIN, diff --git a/frontend/src/modules/Pvt/settings/settings.tsx b/frontend/src/modules/Pvt/settings/settings.tsx index 9bf651910..d36e9ffd4 100644 --- a/frontend/src/modules/Pvt/settings/settings.tsx +++ b/frontend/src/modules/Pvt/settings/settings.tsx @@ -29,8 +29,7 @@ import { import { pvtDataQueriesAtom } from "./atoms/queryAtoms"; import { DependentVariableSelector } from "./components/DependentVariableSelector/dependentVariableSelector"; -import { SettingsToViewInterface } from "../settingsToViewInterface"; -import { State } from "../state"; +import { Interfaces } from "../interfaces"; import { ColorBy, PHASE_TO_DISPLAY_NAME, @@ -40,7 +39,7 @@ import { } from "../typesAndEnums"; import { computeRealizationsIntersection } from "../utils/realizationsIntersection"; -export function Settings({ workbenchSession }: ModuleSettingsProps) { +export function Settings({ workbenchSession }: ModuleSettingsProps) { const ensembleSet = useEnsembleSet(workbenchSession); const filterEnsembleRealizationsFunc = useEnsembleRealizationFilterFunc(workbenchSession); diff --git a/frontend/src/modules/Pvt/state.ts b/frontend/src/modules/Pvt/state.ts deleted file mode 100644 index 90ff7f709..000000000 --- a/frontend/src/modules/Pvt/state.ts +++ /dev/null @@ -1 +0,0 @@ -export type State = Record; diff --git a/frontend/src/modules/Pvt/view.tsx b/frontend/src/modules/Pvt/view.tsx index 726c5c8cd..f76bc234c 100644 --- a/frontend/src/modules/Pvt/view.tsx +++ b/frontend/src/modules/Pvt/view.tsx @@ -11,16 +11,11 @@ import { useElementSize } from "@lib/hooks/useElementSize"; import { ContentMessage, ContentMessageType } from "@modules/_shared/components/ContentMessage/contentMessage"; import { makeDistinguishableEnsembleDisplayName } from "@modules/_shared/ensembleNameUtils"; -import { SettingsToViewInterface } from "./settingsToViewInterface"; -import { State } from "./state"; +import { Interfaces } from "./interfaces"; import { PvtDataAccessor } from "./utils/PvtDataAccessor"; import { PvtPlotBuilder } from "./utils/PvtPlotBuilder"; -export function View({ - viewContext, - workbenchSettings, - workbenchSession, -}: ModuleViewProps) { +export function View({ viewContext, workbenchSettings, workbenchSession }: ModuleViewProps) { const colorSet = workbenchSettings.useColorSet(); const statusWriter = useViewStatusWriter(viewContext); const ensembleSet = useEnsembleSet(workbenchSession); diff --git a/frontend/src/modules/Rft/interfaces.ts b/frontend/src/modules/Rft/interfaces.ts new file mode 100644 index 000000000..7f0c0e210 --- /dev/null +++ b/frontend/src/modules/Rft/interfaces.ts @@ -0,0 +1,16 @@ +import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; + +import { rftWellAddressAtom } from "./settings/atoms/baseAtoms"; +import { RftWellAddress } from "./typesAndEnums"; + +type SettingsToViewInterface = { rftWellAddress: RftWellAddress | null }; + +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + rftWellAddress: (get) => { + return get(rftWellAddressAtom); + }, +}; diff --git a/frontend/src/modules/Rft/loadModule.tsx b/frontend/src/modules/Rft/loadModule.tsx index b4d79ae0d..3ee44a59f 100644 --- a/frontend/src/modules/Rft/loadModule.tsx +++ b/frontend/src/modules/Rft/loadModule.tsx @@ -1,14 +1,10 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; import { Settings } from "./settings"; -import State from "./state"; import { View } from "./view"; -const defaultState: State = { - rftWellAddress: null, -}; - -const module = ModuleRegistry.initModule("Rft", defaultState, {}); +const module = ModuleRegistry.initModule("Rft", { settingsToViewInterfaceInitialization }); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/Rft/registerModule.tsx b/frontend/src/modules/Rft/registerModule.tsx index 2c8a27e82..6fef6a40d 100644 --- a/frontend/src/modules/Rft/registerModule.tsx +++ b/frontend/src/modules/Rft/registerModule.tsx @@ -2,11 +2,11 @@ import { ModuleCategory, ModuleDevState } from "@framework/Module"; import { ModuleDataTagId } from "@framework/ModuleDataTags"; import { ModuleRegistry } from "@framework/ModuleRegistry"; -import State from "./state"; +import { Interfaces } from "./interfaces"; const description = "Plotting of simulated RFT results."; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: "Rft", defaultTitle: "RFT", category: ModuleCategory.MAIN, diff --git a/frontend/src/modules/Rft/settings.tsx b/frontend/src/modules/Rft/settings.tsx index c3fc1a29c..03e520292 100644 --- a/frontend/src/modules/Rft/settings.tsx +++ b/frontend/src/modules/Rft/settings.tsx @@ -10,10 +10,13 @@ import { timestampUtcMsToCompactIsoString } from "@framework/utils/timestampUtil import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; import { Select, SelectOption } from "@lib/components/Select"; +import { useAtom } from "jotai"; import { isEqual } from "lodash"; +import { Interfaces } from "./interfaces"; import { useRftWellList } from "./queryHooks"; -import state, { RftWellAddress } from "./state"; +import { rftWellAddressAtom } from "./settings/atoms/baseAtoms"; +import { RftWellAddress } from "./typesAndEnums"; //Helpers to populate dropdowns const stringToOptions = (strings: string[]): SelectOption[] => { @@ -26,9 +29,9 @@ const timepointOptions = (timePoints: number[]): SelectOption[] => { })); }; -export function Settings({ settingsContext, workbenchServices, workbenchSession }: ModuleSettingsProps) { +export function Settings({ settingsContext, workbenchServices, workbenchSession }: ModuleSettingsProps) { const ensembleSet = useEnsembleSet(workbenchSession); - const [rftWellAddress, setRftWellAddress] = settingsContext.useStoreState("rftWellAddress"); + const [rftWellAddress, setRftWellAddress] = useAtom(rftWellAddressAtom); const [selectedEnsembleIdent, setSelectedEnsembleIdent] = React.useState(null); const [selectedWellName, setSelectedWellName] = React.useState(null); const [selectedTimePoint, setSelectedTimePoint] = React.useState(null); diff --git a/frontend/src/modules/Rft/settings/atoms/baseAtoms.ts b/frontend/src/modules/Rft/settings/atoms/baseAtoms.ts new file mode 100644 index 000000000..2d4fa5667 --- /dev/null +++ b/frontend/src/modules/Rft/settings/atoms/baseAtoms.ts @@ -0,0 +1,5 @@ +import { RftWellAddress } from "@modules/Rft/typesAndEnums"; + +import { atom } from "jotai"; + +export const rftWellAddressAtom = atom(null); diff --git a/frontend/src/modules/Rft/state.ts b/frontend/src/modules/Rft/typesAndEnums.ts similarity index 50% rename from frontend/src/modules/Rft/state.ts rename to frontend/src/modules/Rft/typesAndEnums.ts index a51b1c264..07c7959f6 100644 --- a/frontend/src/modules/Rft/state.ts +++ b/frontend/src/modules/Rft/typesAndEnums.ts @@ -1,11 +1,9 @@ -export interface RftWellAddress { +export type RftWellAddress = { addressType: "realizations"; caseUuid: string; ensembleName: string; wellName: string; responseName: string; timePoint: number; - realizationNums: number[] | null - -} -export default interface State { rftWellAddress: RftWellAddress | null } + realizationNums: number[] | null; +}; diff --git a/frontend/src/modules/Rft/view.tsx b/frontend/src/modules/Rft/view.tsx index 70c9ca3b2..bb9e56d48 100644 --- a/frontend/src/modules/Rft/view.tsx +++ b/frontend/src/modules/Rft/view.tsx @@ -8,13 +8,13 @@ import { useElementSize } from "@lib/hooks/useElementSize"; import { PlotData } from "plotly.js"; +import { Interfaces } from "./interfaces"; import { useRftRealizationData } from "./queryHooks"; -import State from "./state"; -export const View = ({ viewContext }: ModuleViewProps) => { +export const View = ({ viewContext }: ModuleViewProps) => { const wrapperDivRef = React.useRef(null); const wrapperDivSize = useElementSize(wrapperDivRef); - const rftWellAddress = viewContext.useStoreValue("rftWellAddress"); + const rftWellAddress = viewContext.useSettingsToViewInterfaceValue("rftWellAddress"); const rftRealizationDataQuery = useRftRealizationData( rftWellAddress?.caseUuid, rftWellAddress?.ensembleName, diff --git a/frontend/src/modules/SeismicIntersection/interfaces.ts b/frontend/src/modules/SeismicIntersection/interfaces.ts new file mode 100644 index 000000000..aee98b07c --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/interfaces.ts @@ -0,0 +1,51 @@ +import { InterfaceInitialization } from "@framework/UniDirectionalModuleComponentsInterface"; +import { Wellbore } from "@framework/types/wellbore"; + +import { + extensionAtom, + seismicAddressAtom, + surfaceAddressAtom, + wellboreAddressAtom, + wellborePickCaseUuidAtom, + wellborePickSelectionAtom, + zScaleAtom, +} from "./settings/atoms/baseAtoms"; +import { SeismicAddress, SurfaceAddress, WellborePickSelectionType } from "./typesAndEnums"; + +type SettingsToViewInterface = { + wellboreAddress: Wellbore | null; + seismicAddress: SeismicAddress | null; + surfaceAddress: SurfaceAddress | null; + wellborePickCaseUuid: string | null; + wellborePickSelection: WellborePickSelectionType; + extension: number; + zScale: number; +}; + +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { + wellboreAddress: (get) => { + return get(wellboreAddressAtom); + }, + seismicAddress: (get) => { + return get(seismicAddressAtom); + }, + surfaceAddress: (get) => { + return get(surfaceAddressAtom); + }, + wellborePickCaseUuid: (get) => { + return get(wellborePickCaseUuidAtom); + }, + wellborePickSelection: (get) => { + return get(wellborePickSelectionAtom); + }, + extension: (get) => { + return get(extensionAtom); + }, + zScale: (get) => { + return get(zScaleAtom); + }, +}; diff --git a/frontend/src/modules/SeismicIntersection/loadModule.tsx b/frontend/src/modules/SeismicIntersection/loadModule.tsx index 91f0135ca..68b75e9d5 100644 --- a/frontend/src/modules/SeismicIntersection/loadModule.tsx +++ b/frontend/src/modules/SeismicIntersection/loadModule.tsx @@ -1,20 +1,10 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; -import { Settings } from "./settings"; -import { State, WellborePickSelectionType } from "./state"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; +import { Settings } from "./settings/settings"; import { View } from "./view"; -const defaultState: State = { - wellboreAddress: null, - seismicAddress: null, - surfaceAddress: null, - wellborePickCaseUuid: null, - wellborePickSelection: WellborePickSelectionType.NONE, - extension: 1000, - zScale: 5, -}; - -const module = ModuleRegistry.initModule("SeismicIntersection", defaultState); +const module = ModuleRegistry.initModule("SeismicIntersection", { settingsToViewInterfaceInitialization }); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/SeismicIntersection/queryHooks.tsx b/frontend/src/modules/SeismicIntersection/queryHooks.tsx index cee1676d0..5ece3012c 100644 --- a/frontend/src/modules/SeismicIntersection/queryHooks.tsx +++ b/frontend/src/modules/SeismicIntersection/queryHooks.tsx @@ -1,7 +1,6 @@ import { Body_post_get_seismic_fence_api, Body_post_get_surface_intersection_api, - SeismicCubeMeta_api, SeismicFencePolyline_api, SurfaceIntersectionCumulativeLengthPolyline_api, SurfaceIntersectionData_api, @@ -14,19 +13,6 @@ import { SeismicFenceData_trans, transformSeismicFenceData } from "./utils/query const STALE_TIME = 60 * 1000; const CACHE_TIME = 60 * 1000; -export function useSeismicCubeMetaListQuery( - caseUuid: string | undefined, - ensembleName: string | undefined -): UseQueryResult { - return useQuery({ - queryKey: ["getSeismicCubeMetaList", caseUuid, ensembleName], - queryFn: () => apiService.seismic.getSeismicCubeMetaList(caseUuid ?? "", ensembleName ?? ""), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: !!(caseUuid && ensembleName), - }); -} - export function useSeismicFenceDataQuery( caseUuid: string | null, ensembleName: string | null, diff --git a/frontend/src/modules/SeismicIntersection/registerModule.ts b/frontend/src/modules/SeismicIntersection/registerModule.ts index bd55fbd20..ddf8b5f8b 100644 --- a/frontend/src/modules/SeismicIntersection/registerModule.ts +++ b/frontend/src/modules/SeismicIntersection/registerModule.ts @@ -3,11 +3,11 @@ import { ModuleDataTagId } from "@framework/ModuleDataTags"; import { ModuleRegistry } from "@framework/ModuleRegistry"; import { SyncSettingKey } from "@framework/SyncSettings"; -import { State } from "./state"; +import { Interfaces } from "./interfaces"; const description = "Visualization of intersection data with a wellbore and seismic fence."; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: "SeismicIntersection", defaultTitle: "Seismic Intersection", category: ModuleCategory.MAIN, diff --git a/frontend/src/modules/SeismicIntersection/settings/atoms/baseAtoms.ts b/frontend/src/modules/SeismicIntersection/settings/atoms/baseAtoms.ts new file mode 100644 index 000000000..33d41c1ff --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/settings/atoms/baseAtoms.ts @@ -0,0 +1,12 @@ +import { Wellbore } from "@framework/types/wellbore"; +import { SeismicAddress, SurfaceAddress, WellborePickSelectionType } from "@modules/SeismicIntersection/typesAndEnums"; + +import { atom } from "jotai"; + +export const wellboreAddressAtom = atom(null); +export const seismicAddressAtom = atom(null); +export const surfaceAddressAtom = atom(null); +export const wellborePickCaseUuidAtom = atom(null); +export const wellborePickSelectionAtom = atom(WellborePickSelectionType.NONE); +export const extensionAtom = atom(1000); +export const zScaleAtom = atom(5); diff --git a/frontend/src/modules/SeismicIntersection/settings/hooks/queryHooks.tsx b/frontend/src/modules/SeismicIntersection/settings/hooks/queryHooks.tsx new file mode 100644 index 000000000..1f16594a0 --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/settings/hooks/queryHooks.tsx @@ -0,0 +1,19 @@ +import { SeismicCubeMeta_api } from "@api"; +import { apiService } from "@framework/ApiService"; +import { UseQueryResult, useQuery } from "@tanstack/react-query"; + +const STALE_TIME = 60 * 1000; +const CACHE_TIME = 60 * 1000; + +export function useSeismicCubeMetaListQuery( + caseUuid: string | undefined, + ensembleName: string | undefined +): UseQueryResult { + return useQuery({ + queryKey: ["getSeismicCubeMetaList", caseUuid, ensembleName], + queryFn: () => apiService.seismic.getSeismicCubeMetaList(caseUuid ?? "", ensembleName ?? ""), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + enabled: !!(caseUuid && ensembleName), + }); +} diff --git a/frontend/src/modules/SeismicIntersection/settings.tsx b/frontend/src/modules/SeismicIntersection/settings/settings.tsx similarity index 94% rename from frontend/src/modules/SeismicIntersection/settings.tsx rename to frontend/src/modules/SeismicIntersection/settings/settings.tsx index ca71cfc1c..52bfc0dce 100644 --- a/frontend/src/modules/SeismicIntersection/settings.tsx +++ b/frontend/src/modules/SeismicIntersection/settings/settings.tsx @@ -24,12 +24,28 @@ import { useRealizationSurfacesMetadataQuery } from "@modules/_shared/Surface"; import { useDrilledWellboreHeadersQuery } from "@modules/_shared/WellBore"; import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePropagateApiErrorToStatusWriter"; +import { useAtom, useSetAtom } from "jotai"; import { isEqual } from "lodash"; -import { useSeismicCubeMetaListQuery } from "./queryHooks"; -import { State, WellborePickSelectionType, WellborePickSelectionTypeEnumToStringMapping } from "./state"; -import { SeismicAddress, SurfaceAddress } from "./types"; -import { SeismicCubeMetaDirectory, SeismicTimeType } from "./utils/seismicCubeDirectory"; +import { + extensionAtom, + seismicAddressAtom, + surfaceAddressAtom, + wellboreAddressAtom, + wellborePickCaseUuidAtom, + wellborePickSelectionAtom, + zScaleAtom, +} from "./atoms/baseAtoms"; +import { useSeismicCubeMetaListQuery } from "./hooks/queryHooks"; + +import { Interfaces } from "../interfaces"; +import { + SeismicAddress, + SurfaceAddress, + WellborePickSelectionType, + WellborePickSelectionTypeEnumToStringMapping, +} from "../typesAndEnums"; +import { SeismicCubeMetaDirectory, SeismicTimeType } from "../utils/seismicCubeDirectory"; const SeismicTimeTypeEnumToSurveyTypeStringMapping = { [SeismicTimeType.TimePoint]: "3D", @@ -60,20 +76,24 @@ const Z_SCALE_LIMITS = { min: 1, max: 100 }; // Minimum z-scale factor // Hardcoded surface time type - no surface as function of time const SURFACE_TIME_TYPE = SurfaceTimeType.None; -export function Settings({ settingsContext, workbenchSession, workbenchServices }: ModuleSettingsProps) { +export function Settings({ + settingsContext, + workbenchSession, + workbenchServices, +}: ModuleSettingsProps): React.ReactNode { const syncedSettingKeys = settingsContext.useSyncedSettingKeys(); const syncHelper = new SyncSettingsHelper(syncedSettingKeys, workbenchServices); const syncedValueEnsembles = syncHelper.useValue(SyncSettingKey.ENSEMBLE, "global.syncValue.ensembles"); const ensembleSet = useEnsembleSet(workbenchSession); const statusWriter = useSettingsStatusWriter(settingsContext); - const setSeismicAddress = settingsContext.useSetStoreValue("seismicAddress"); - const setSurfaceAddress = settingsContext.useSetStoreValue("surfaceAddress"); - const setWellboreAddress = settingsContext.useSetStoreValue("wellboreAddress"); - const setWellborePickCaseUuid = settingsContext.useSetStoreValue("wellborePickCaseUuid"); - const setWellborePickSelection = settingsContext.useSetStoreValue("wellborePickSelection"); - const [extension, setExtension] = settingsContext.useStoreState("extension"); - const [zScale, setZScale] = settingsContext.useStoreState("zScale"); + const setSeismicAddress = useSetAtom(seismicAddressAtom); + const setSurfaceAddress = useSetAtom(surfaceAddressAtom); + const [wellboreAddress, setWellboreAddress] = useAtom(wellboreAddressAtom); + const setWellborePickCaseUuid = useSetAtom(wellborePickCaseUuidAtom); + const [wellborePickSelection, setWellborePickSelection] = useAtom(wellborePickSelectionAtom); + const [extension, setExtension] = useAtom(extensionAtom); + const [zScale, setZScale] = useAtom(zScaleAtom); const [fetchedSurfaceNames, setFetchedSurfaceNames] = React.useState([]); const [fetchedSurfaceAttributes, setFetchedSurfaceAttributes] = React.useState([]); @@ -83,12 +103,9 @@ export function Settings({ settingsContext, workbenchSession, workbenchServices const [isObserved, setIsObserved] = React.useState(false); const [seismicTimeType, setSeismicTimeType] = React.useState(SeismicTimeType.TimePoint); - const [selectedWellboreAddress, setSelectedWellboreAddress] = React.useState( - settingsContext.useStoreValue("wellboreAddress") - ); - const [selectedWellborePickSelection, setSelectedWellborePickSelection] = React.useState( - settingsContext.useStoreValue("wellborePickSelection") - ); + const [selectedWellboreAddress, setSelectedWellboreAddress] = React.useState(wellboreAddress); + const [selectedWellborePickSelection, setSelectedWellborePickSelection] = + React.useState(wellborePickSelection); const candidateEnsembleIdent = maybeAssignFirstSyncedEnsemble(selectedEnsembleIdent, syncedValueEnsembles); const computedEnsembleIdent = fixupEnsembleIdent(candidateEnsembleIdent, ensembleSet); diff --git a/frontend/src/modules/SeismicIntersection/state.ts b/frontend/src/modules/SeismicIntersection/state.ts deleted file mode 100644 index 341b59ed3..000000000 --- a/frontend/src/modules/SeismicIntersection/state.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Wellbore } from "@framework/types/wellbore"; - -import { SeismicAddress, SurfaceAddress } from "./types"; - -export enum WellborePickSelectionType { - NONE = "None", - ALL = "All", - SELECTED_SURFACES = "SelectedSurfaces", -} - -export const WellborePickSelectionTypeEnumToStringMapping = { - [WellborePickSelectionType.NONE]: "None", - [WellborePickSelectionType.ALL]: "All", - [WellborePickSelectionType.SELECTED_SURFACES]: "Selected Surfaces", -}; - -export interface State { - wellboreAddress: Wellbore | null; - seismicAddress: SeismicAddress | null; - surfaceAddress: SurfaceAddress | null; - wellborePickCaseUuid: string | null; - wellborePickSelection: WellborePickSelectionType; - extension: number; - zScale: number; -} diff --git a/frontend/src/modules/SeismicIntersection/types.ts b/frontend/src/modules/SeismicIntersection/types.ts deleted file mode 100644 index ebcec448d..000000000 --- a/frontend/src/modules/SeismicIntersection/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -export type SeismicAddress = { - caseUuid: string; - ensemble: string; - realizationNumber: number; - attribute: string; - observed: boolean; - timeString?: string; -}; - -export type SurfaceAddress = { - caseUuid: string; - ensemble: string; - realizationNumber: number; - surfaceNames: string[]; - attribute: string; -}; diff --git a/frontend/src/modules/SeismicIntersection/typesAndEnums.ts b/frontend/src/modules/SeismicIntersection/typesAndEnums.ts new file mode 100644 index 000000000..b528075a6 --- /dev/null +++ b/frontend/src/modules/SeismicIntersection/typesAndEnums.ts @@ -0,0 +1,28 @@ +export type SeismicAddress = { + caseUuid: string; + ensemble: string; + realizationNumber: number; + attribute: string; + observed: boolean; + timeString?: string; +}; + +export type SurfaceAddress = { + caseUuid: string; + ensemble: string; + realizationNumber: number; + surfaceNames: string[]; + attribute: string; +}; + +export enum WellborePickSelectionType { + NONE = "None", + ALL = "All", + SELECTED_SURFACES = "SelectedSurfaces", +} + +export const WellborePickSelectionTypeEnumToStringMapping = { + [WellborePickSelectionType.NONE]: "None", + [WellborePickSelectionType.ALL]: "All", + [WellborePickSelectionType.SELECTED_SURFACES]: "Selected Surfaces", +}; diff --git a/frontend/src/modules/SeismicIntersection/view.tsx b/frontend/src/modules/SeismicIntersection/view.tsx index 2f0c7473b..f07dbf651 100644 --- a/frontend/src/modules/SeismicIntersection/view.tsx +++ b/frontend/src/modules/SeismicIntersection/view.tsx @@ -20,8 +20,9 @@ import { usePropagateApiErrorToStatusWriter } from "@modules/_shared/hooks/usePr import { isEqual } from "lodash"; +import { Interfaces } from "./interfaces"; import { useSeismicFenceDataQuery, useSurfaceIntersectionQueries } from "./queryHooks"; -import { State, WellborePickSelectionType } from "./state"; +import { WellborePickSelectionType } from "./typesAndEnums"; import { addMDOverlay, addSeismicLayer, @@ -44,7 +45,7 @@ import { useGenerateSeismicSliceImageData, } from "./utils/esvIntersectionHooks"; -export const View = ({ viewContext, workbenchSettings }: ModuleViewProps) => { +export function View({ viewContext, workbenchSettings }: ModuleViewProps): React.ReactNode { const wrapperDivRef = React.useRef(null); const wrapperDivSize = useElementSize(wrapperDivRef); const esvIntersectionContainerRef = React.useRef(null); @@ -52,13 +53,13 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps) const statusWriter = useViewStatusWriter(viewContext); - const seismicAddress = viewContext.useStoreValue("seismicAddress"); - const surfaceAddress = viewContext.useStoreValue("surfaceAddress"); - const wellboreAddress = viewContext.useStoreValue("wellboreAddress"); - const wellborePickCaseUuid = viewContext.useStoreValue("wellborePickCaseUuid"); - const wellborePickSelection = viewContext.useStoreValue("wellborePickSelection"); - const extension = viewContext.useStoreValue("extension"); - const zScale = viewContext.useStoreValue("zScale"); + const seismicAddress = viewContext.useSettingsToViewInterfaceValue("seismicAddress"); + const surfaceAddress = viewContext.useSettingsToViewInterfaceValue("surfaceAddress"); + const wellboreAddress = viewContext.useSettingsToViewInterfaceValue("wellboreAddress"); + const wellborePickCaseUuid = viewContext.useSettingsToViewInterfaceValue("wellborePickCaseUuid"); + const wellborePickSelection = viewContext.useSettingsToViewInterfaceValue("wellborePickSelection"); + const extension = viewContext.useSettingsToViewInterfaceValue("extension"); + const zScale = viewContext.useSettingsToViewInterfaceValue("zScale"); const seismicColorScale = workbenchSettings.useDiscreteColorScale({ gradientType: ColorScaleGradientType.Diverging, @@ -327,4 +328,4 @@ export const View = ({ viewContext, workbenchSettings }: ModuleViewProps) )}
); -}; +} diff --git a/frontend/src/modules/SimulationTimeSeries/settingsToViewInterface.ts b/frontend/src/modules/SimulationTimeSeries/interfaces.ts similarity index 91% rename from frontend/src/modules/SimulationTimeSeries/settingsToViewInterface.ts rename to frontend/src/modules/SimulationTimeSeries/interfaces.ts index b7fe3c36f..b1c2d1652 100644 --- a/frontend/src/modules/SimulationTimeSeries/settingsToViewInterface.ts +++ b/frontend/src/modules/SimulationTimeSeries/interfaces.ts @@ -28,7 +28,11 @@ export type SettingsToViewInterface = { resampleFrequency: Frequency_api | null; }; -export const interfaceInitialization: InterfaceInitialization = { +export type Interfaces = { + settingsToView: SettingsToViewInterface; +}; + +export const settingsToViewInterfaceInitialization: InterfaceInitialization = { groupBy: (get) => { return get(groupByAtom); }, diff --git a/frontend/src/modules/SimulationTimeSeries/loadModule.tsx b/frontend/src/modules/SimulationTimeSeries/loadModule.tsx index 6697a50cd..914eb2dd8 100644 --- a/frontend/src/modules/SimulationTimeSeries/loadModule.tsx +++ b/frontend/src/modules/SimulationTimeSeries/loadModule.tsx @@ -1,21 +1,15 @@ import { ModuleRegistry } from "@framework/ModuleRegistry"; +import { Interfaces, settingsToViewInterfaceInitialization } from "./interfaces"; import { MODULE_NAME } from "./registerModule"; -import { SettingsAtoms, settingsAtomsInitialization } from "./settings/atoms/atomDefinitions"; import { Settings } from "./settings/settings"; -import { SettingsToViewInterface, interfaceInitialization } from "./settingsToViewInterface"; -import { State } from "./state"; -import { ViewAtoms, viewAtomsInitialization } from "./view/atoms/atomDefinitions"; +import { settingsToViewInterfaceEffects } from "./view/atoms/interfaceEffects"; import { View } from "./view/view"; -const module = ModuleRegistry.initModule( - MODULE_NAME, - {}, - undefined, - interfaceInitialization, - settingsAtomsInitialization, - viewAtomsInitialization -); +const module = ModuleRegistry.initModule(MODULE_NAME, { + settingsToViewInterfaceInitialization, + settingsToViewInterfaceEffects, +}); module.viewFC = View; module.settingsFC = Settings; diff --git a/frontend/src/modules/SimulationTimeSeries/registerModule.ts b/frontend/src/modules/SimulationTimeSeries/registerModule.ts index 2466ec8e1..679b53e4a 100644 --- a/frontend/src/modules/SimulationTimeSeries/registerModule.ts +++ b/frontend/src/modules/SimulationTimeSeries/registerModule.ts @@ -3,17 +3,14 @@ import { ModuleDataTagId } from "@framework/ModuleDataTags"; import { ModuleRegistry } from "@framework/ModuleRegistry"; import { channelDefs } from "./channelDefs"; +import { Interfaces } from "./interfaces"; import { preview } from "./preview"; -import { SettingsAtoms } from "./settings/atoms/atomDefinitions"; -import { SettingsToViewInterface } from "./settingsToViewInterface"; -import { State } from "./state"; -import { ViewAtoms } from "./view/atoms/atomDefinitions"; export const MODULE_NAME = "SimulationTimeSeries"; const description = "Plotting of simulation time series data."; -ModuleRegistry.registerModule({ +ModuleRegistry.registerModule({ moduleName: MODULE_NAME, defaultTitle: "Simulation Time Series", category: ModuleCategory.MAIN, diff --git a/frontend/src/modules/SimulationTimeSeries/settings/atoms/atomDefinitions.ts b/frontend/src/modules/SimulationTimeSeries/settings/atoms/atomDefinitions.ts deleted file mode 100644 index d699aec3a..000000000 --- a/frontend/src/modules/SimulationTimeSeries/settings/atoms/atomDefinitions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ModuleAtoms } from "@framework/Module"; - -export type SettingsAtoms = Record; - -export function settingsAtomsInitialization(): ModuleAtoms { - return {}; -} diff --git a/frontend/src/modules/SimulationTimeSeries/settings/settings.tsx b/frontend/src/modules/SimulationTimeSeries/settings/settings.tsx index 9ce8be25b..c59e99824 100644 --- a/frontend/src/modules/SimulationTimeSeries/settings/settings.tsx +++ b/frontend/src/modules/SimulationTimeSeries/settings/settings.tsx @@ -48,7 +48,7 @@ import { import { vectorListQueriesAtom } from "./atoms/queryAtoms"; import { useMakeSettingsStatusWriterMessages } from "./hooks/useMakeSettingsStatusWriterMessages"; -import { SettingsToViewInterface } from "../settingsToViewInterface"; +import { Interfaces } from "../interfaces"; import { FanchartStatisticOption, FanchartStatisticOptionEnumToStringMapping, @@ -61,10 +61,7 @@ import { VisualizationModeEnumToStringMapping, } from "../typesAndEnums"; -export function Settings({ - settingsContext, - workbenchSession, -}: ModuleSettingsProps, SettingsToViewInterface>) { +export function Settings({ settingsContext, workbenchSession }: ModuleSettingsProps) { const ensembleSet = useEnsembleSet(workbenchSession); const statusWriter = useSettingsStatusWriter(settingsContext); diff --git a/frontend/src/modules/SimulationTimeSeries/state.ts b/frontend/src/modules/SimulationTimeSeries/state.ts deleted file mode 100644 index 90ff7f709..000000000 --- a/frontend/src/modules/SimulationTimeSeries/state.ts +++ /dev/null @@ -1 +0,0 @@ -export type State = Record; diff --git a/frontend/src/modules/SimulationTimeSeries/view/atoms/atomDefinitions.ts b/frontend/src/modules/SimulationTimeSeries/view/atoms/atomDefinitions.ts deleted file mode 100644 index 970af41ac..000000000 --- a/frontend/src/modules/SimulationTimeSeries/view/atoms/atomDefinitions.ts +++ /dev/null @@ -1,372 +0,0 @@ -import { - Frequency_api, - Observations_api, - VectorHistoricalData_api, - VectorRealizationData_api, - VectorStatisticData_api, -} from "@api"; -import { apiService } from "@framework/ApiService"; -import { EnsembleIdent } from "@framework/EnsembleIdent"; -import { EnsembleRealizationFilterFunctionAtom, EnsembleSetAtom } from "@framework/GlobalAtoms"; -import { ModuleAtoms } from "@framework/Module"; -import { UniDirectionalModuleComponentsInterface } from "@framework/UniDirectionalModuleComponentsInterface"; -import { atomWithQueries } from "@framework/utils/atomUtils"; -import { SettingsToViewInterface } from "@modules/SimulationTimeSeries/settingsToViewInterface"; -import { - EnsembleVectorObservationDataMap, - VectorSpec, - VisualizationMode, -} from "@modules/SimulationTimeSeries/typesAndEnums"; -import { QueryObserverResult } from "@tanstack/react-query"; - -import { atom } from "jotai"; - -import { createLoadedVectorSpecificationAndDataArray } from "../utils/vectorSpecificationsAndQueriesUtils"; - -export type ViewAtoms = { - userSelectedActiveTimestampUtcMs: number | null; - activeTimestampUtcMs: number | null; - vectorDataQueries: QueryObserverResult[]; - vectorStatisticsQueries: QueryObserverResult[]; - historicalVectorDataQueries: QueryObserverResult[]; - vectorObservationsQueries: { - isFetching: boolean; - isError: boolean; - ensembleVectorObservationDataMap: EnsembleVectorObservationDataMap; - }; - queryIsFetching: boolean; - realizationsQueryHasError: boolean; - statisticsQueryHasError: boolean; - historicalDataQueryHasError: boolean; - loadedVectorSpecificationsAndRealizationData: { - vectorSpecification: VectorSpec; - data: VectorRealizationData_api[]; - }[]; - loadedVectorSpecificationsAndStatisticsData: { - vectorSpecification: VectorSpec; - data: VectorStatisticData_api; - }[]; - loadedVectorSpecificationsAndHistoricalData: { - vectorSpecification: VectorSpec; - data: VectorHistoricalData_api; - }[]; - colorByParameter: boolean; -}; - -const STALE_TIME = 60 * 1000; -const CACHE_TIME = 60 * 1000; - -export function viewAtomsInitialization( - settingsToViewInterface: UniDirectionalModuleComponentsInterface -): ModuleAtoms { - const userSelectedActiveTimestampUtcMsAtom = atom(null); - - const validEnsembleRealizationsFunctionAtom = atom((get) => { - const ensembleSet = get(EnsembleSetAtom); - let validEnsembleRealizationsFunction = get(EnsembleRealizationFilterFunctionAtom); - - if (validEnsembleRealizationsFunction === null) { - validEnsembleRealizationsFunction = (ensembleIdent: EnsembleIdent) => { - return ensembleSet.findEnsemble(ensembleIdent)?.getRealizations() ?? []; - }; - } - - return validEnsembleRealizationsFunction; - }); - - const vectorDataQueriesAtom = atomWithQueries((get) => { - const vectorSpecifications = get(settingsToViewInterface.getAtom("vectorSpecifications")); - const resampleFrequency = get(settingsToViewInterface.getAtom("resampleFrequency")); - const visualizationMode = get(settingsToViewInterface.getAtom("visualizationMode")); - const validEnsembleRealizationsFunction = get(validEnsembleRealizationsFunctionAtom); - - const enabled = - visualizationMode === VisualizationMode.INDIVIDUAL_REALIZATIONS || - visualizationMode === VisualizationMode.STATISTICS_AND_REALIZATIONS; - - const queries = vectorSpecifications.map((item) => { - const realizations = [...validEnsembleRealizationsFunction(item.ensembleIdent)]; - return () => ({ - queryKey: [ - "getRealizationsVectorData", - item.ensembleIdent.getCaseUuid(), - item.ensembleIdent.getEnsembleName(), - item.vectorName, - resampleFrequency, - realizations, - ], - queryFn: () => - apiService.timeseries.getRealizationsVectorData( - item.ensembleIdent.getCaseUuid() ?? "", - item.ensembleIdent.getEnsembleName() ?? "", - item.vectorName ?? "", - resampleFrequency, - realizations - ), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: !!( - enabled && - item.vectorName && - item.ensembleIdent.getCaseUuid() && - item.ensembleIdent.getEnsembleName() - ), - }); - }); - - return { - queries, - }; - }); - - const vectorStatisticsQueriesAtom = atomWithQueries((get) => { - const vectorSpecifications = get(settingsToViewInterface.getAtom("vectorSpecifications")); - const resampleFrequency = get(settingsToViewInterface.getAtom("resampleFrequency")); - const visualizationMode = get(settingsToViewInterface.getAtom("visualizationMode")); - const validEnsembleRealizationsFunction = get(validEnsembleRealizationsFunctionAtom); - - const enabled = - visualizationMode === VisualizationMode.STATISTICAL_FANCHART || - visualizationMode === VisualizationMode.STATISTICAL_LINES || - visualizationMode === VisualizationMode.STATISTICS_AND_REALIZATIONS; - - const queries = vectorSpecifications.map((item) => { - const realizations = [...validEnsembleRealizationsFunction(item.ensembleIdent)]; - return () => ({ - queryKey: [ - "getStatisticalVectorData", - item.ensembleIdent.getCaseUuid(), - item.ensembleIdent.getEnsembleName(), - item.vectorName, - resampleFrequency, - realizations, - ], - queryFn: () => - apiService.timeseries.getStatisticalVectorData( - item.ensembleIdent.getCaseUuid() ?? "", - item.ensembleIdent.getEnsembleName() ?? "", - item.vectorName ?? "", - resampleFrequency ?? Frequency_api.MONTHLY, - undefined, - realizations - ), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: !!( - enabled && - item.vectorName && - item.ensembleIdent.getCaseUuid() && - item.ensembleIdent.getEnsembleName() - ), - }); - }); - - return { - queries, - }; - }); - - const historicalVectorDataQueriesAtom = atomWithQueries((get) => { - const vectorSpecifications = get(settingsToViewInterface.getAtom("vectorSpecifications")); - const resampleFrequency = get(settingsToViewInterface.getAtom("resampleFrequency")); - - const vectorSpecificationsWithHistoricalData = vectorSpecifications?.filter((vec) => vec.hasHistoricalVector); - const enabled = vectorSpecificationsWithHistoricalData?.some((vec) => vec.hasHistoricalVector) ?? false; - - const queries = vectorSpecifications.map((item) => { - return () => ({ - queryKey: [ - "getHistoricalVectorData", - item.ensembleIdent.getCaseUuid(), - item.ensembleIdent.getEnsembleName(), - item.vectorName, - resampleFrequency, - ], - queryFn: () => - apiService.timeseries.getHistoricalVectorData( - item.ensembleIdent.getCaseUuid() ?? "", - item.ensembleIdent.getEnsembleName() ?? "", - item.vectorName ?? "", - resampleFrequency ?? Frequency_api.MONTHLY - ), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: !!( - enabled && - item.vectorName && - item.ensembleIdent.getCaseUuid() && - item.ensembleIdent.getEnsembleName() - ), - }); - }); - - return { - queries, - }; - }); - - const vectorObservationsQueriesAtom = atomWithQueries((get) => { - const showObservations = get(settingsToViewInterface.getAtom("showObservations")); - const vectorSpecifications = get(settingsToViewInterface.getAtom("vectorSpecifications")); - - const uniqueEnsembleIdents = [...new Set(vectorSpecifications?.map((item) => item.ensembleIdent) ?? [])]; - - const queries = uniqueEnsembleIdents.map((item) => { - return () => ({ - queryKey: ["getObservations", item.getCaseUuid()], - queryFn: () => apiService.observations.getObservations(item.getCaseUuid() ?? ""), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: !!(showObservations && item.getCaseUuid()), - }); - }); - - return { - queries, - combine: (results: QueryObserverResult[]) => { - const combinedResult: EnsembleVectorObservationDataMap = new Map(); - if (!vectorSpecifications) - return { isFetching: false, isError: false, ensembleVectorObservationDataMap: combinedResult }; - - results.forEach((result, index) => { - const ensembleIdent = uniqueEnsembleIdents.at(index); - if (!ensembleIdent) return; - - const ensembleVectorSpecifications = vectorSpecifications.filter( - (item) => item.ensembleIdent === ensembleIdent - ); - - const ensembleHasObservations = result.data?.summary.length !== 0; - combinedResult.set(ensembleIdent, { - hasSummaryObservations: ensembleHasObservations, - vectorsObservationData: [], - }); - for (const vectorSpec of ensembleVectorSpecifications) { - const vectorObservationsData = - result.data?.summary.find((elm) => elm.vector_name === vectorSpec.vectorName) ?? null; - if (!vectorObservationsData) continue; - - combinedResult.get(ensembleIdent)?.vectorsObservationData.push({ - vectorSpecification: vectorSpec, - data: vectorObservationsData, - }); - } - }); - - return { - isFetching: results.some((result) => result.isFetching), - isError: results.some((result) => result.isError), - ensembleVectorObservationDataMap: combinedResult, - }; - }, - }; - }); - - const queryIsFetchingAtom = atom((get) => { - const vectorDataQueries = get(vectorDataQueriesAtom); - const vectorStatisticsQueries = get(vectorStatisticsQueriesAtom); - const historicalVectorDataQueries = get(historicalVectorDataQueriesAtom); - const vectorObservationsQueries = get(vectorObservationsQueriesAtom); - - const vectorDataIsFetching = vectorDataQueries.some((query) => query.isFetching); - const vectorStatisticsIsFetching = vectorStatisticsQueries.some((query) => query.isFetching); - const historicalVectorDataIsFetching = historicalVectorDataQueries.some((query) => query.isFetching); - const vectorObservationsIsFetching = vectorObservationsQueries.isFetching; - - const isFetching = - vectorDataIsFetching || - vectorStatisticsIsFetching || - historicalVectorDataIsFetching || - vectorObservationsIsFetching; - - return isFetching; - }); - - const realizationsQueryHasErrorAtom = atom((get) => { - const vectorDataQueries = get(vectorDataQueriesAtom); - - return vectorDataQueries.some((query) => query.isError); - }); - - const statisticsQueryHasErrorAtom = atom((get) => { - const vectorStatisticsQueries = get(vectorStatisticsQueriesAtom); - - return vectorStatisticsQueries.some((query) => query.isError); - }); - - const historicalDataQueryHasErrorAtom = atom((get) => { - const historicalVectorDataQueries = get(historicalVectorDataQueriesAtom); - - return historicalVectorDataQueries.some((query) => query.isError); - }); - - const loadedVectorSpecificationsAndRealizationDataAtom = atom((get) => { - const vectorDataQueries = get(vectorDataQueriesAtom); - const vectorSpecifications = get(settingsToViewInterface.getAtom("vectorSpecifications")); - - return createLoadedVectorSpecificationAndDataArray(vectorSpecifications, vectorDataQueries); - }); - - const loadedVectorSpecificationsAndStatisticsDataAtom = atom((get) => { - const vectorStatisticsQueries = get(vectorStatisticsQueriesAtom); - const vectorSpecifications = get(settingsToViewInterface.getAtom("vectorSpecifications")); - - return createLoadedVectorSpecificationAndDataArray(vectorSpecifications, vectorStatisticsQueries); - }); - - const loadedVectorSpecificationsAndHistoricalDataAtom = atom((get) => { - const historicalVectorDataQueries = get(historicalVectorDataQueriesAtom); - const vectorSpecifications = get(settingsToViewInterface.getAtom("vectorSpecifications")); - - return createLoadedVectorSpecificationAndDataArray(vectorSpecifications, historicalVectorDataQueries); - }); - - const activeTimestampUtcMsAtom = atom((get) => { - const loadedVectorSpecificationsAndRealizationData = get(loadedVectorSpecificationsAndRealizationDataAtom); - const isQueryFetching = get(queryIsFetchingAtom); - const userSelectedActiveTimestampUtcMs = get(userSelectedActiveTimestampUtcMsAtom); - - if ( - !isQueryFetching && - userSelectedActiveTimestampUtcMs === null && - loadedVectorSpecificationsAndRealizationData.length > 0 - ) { - const firstTimeStamp = - loadedVectorSpecificationsAndRealizationData.at(0)?.data.at(0)?.timestamps_utc_ms[0] ?? null; - return firstTimeStamp; - } - - return userSelectedActiveTimestampUtcMs; - }); - - const colorByParameterAtom = atom((get) => { - const colorRealizationsByParameter = get(settingsToViewInterface.getAtom("colorByParameter")); - const visualizationMode = get(settingsToViewInterface.getAtom("visualizationMode")); - const parameterIdent = get(settingsToViewInterface.getAtom("parameterIdent")); - const selectedEnsembles = get(settingsToViewInterface.getAtom("selectedEnsembles")); - - return ( - colorRealizationsByParameter && - visualizationMode === VisualizationMode.INDIVIDUAL_REALIZATIONS && - parameterIdent !== null && - selectedEnsembles.some((ensemble) => ensemble.getParameters().hasParameter(parameterIdent)) - ); - }); - - return { - userSelectedActiveTimestampUtcMs: userSelectedActiveTimestampUtcMsAtom, - activeTimestampUtcMs: activeTimestampUtcMsAtom, - vectorDataQueries: vectorDataQueriesAtom, - vectorStatisticsQueries: vectorStatisticsQueriesAtom, - historicalVectorDataQueries: historicalVectorDataQueriesAtom, - vectorObservationsQueries: vectorObservationsQueriesAtom, - queryIsFetching: queryIsFetchingAtom, - realizationsQueryHasError: realizationsQueryHasErrorAtom, - statisticsQueryHasError: statisticsQueryHasErrorAtom, - historicalDataQueryHasError: historicalDataQueryHasErrorAtom, - loadedVectorSpecificationsAndRealizationData: loadedVectorSpecificationsAndRealizationDataAtom, - loadedVectorSpecificationsAndStatisticsData: loadedVectorSpecificationsAndStatisticsDataAtom, - loadedVectorSpecificationsAndHistoricalData: loadedVectorSpecificationsAndHistoricalDataAtom, - colorByParameter: colorByParameterAtom, - }; -} diff --git a/frontend/src/modules/SimulationTimeSeries/view/atoms/baseAtoms.ts b/frontend/src/modules/SimulationTimeSeries/view/atoms/baseAtoms.ts new file mode 100644 index 000000000..8c0d836d5 --- /dev/null +++ b/frontend/src/modules/SimulationTimeSeries/view/atoms/baseAtoms.ts @@ -0,0 +1,15 @@ +import { Frequency_api } from "@api"; +import { Ensemble } from "@framework/Ensemble"; +import { ParameterIdent } from "@framework/EnsembleParameters"; +import { VectorSpec, VisualizationMode } from "@modules/SimulationTimeSeries/typesAndEnums"; + +import { atom } from "jotai"; + +export const userSelectedActiveTimestampUtcMsAtom = atom(null); +export const vectorSpecificationsAtom = atom([]); +export const resampleFrequencyAtom = atom(null); +export const visualizationModeAtom = atom(VisualizationMode.STATISTICAL_FANCHART); +export const showObservationsAtom = atom(true); +export const interfaceColorByParameterAtom = atom(false); +export const parameterIdentAtom = atom(null); +export const selectedEnsemblesAtom = atom([]); diff --git a/frontend/src/modules/SimulationTimeSeries/view/atoms/derivedAtoms.ts b/frontend/src/modules/SimulationTimeSeries/view/atoms/derivedAtoms.ts new file mode 100644 index 000000000..6290fd739 --- /dev/null +++ b/frontend/src/modules/SimulationTimeSeries/view/atoms/derivedAtoms.ts @@ -0,0 +1,126 @@ +import { EnsembleIdent } from "@framework/EnsembleIdent"; +import { EnsembleRealizationFilterFunctionAtom, EnsembleSetAtom } from "@framework/GlobalAtoms"; +import { VisualizationMode } from "@modules/SimulationTimeSeries/typesAndEnums"; + +import { atom } from "jotai"; + +import { + interfaceColorByParameterAtom, + parameterIdentAtom, + selectedEnsemblesAtom, + userSelectedActiveTimestampUtcMsAtom, + vectorSpecificationsAtom, + visualizationModeAtom, +} from "./baseAtoms"; +import { + historicalVectorDataQueriesAtom, + vectorDataQueriesAtom, + vectorObservationsQueriesAtom, + vectorStatisticsQueriesAtom, +} from "./queryAtoms"; + +import { createLoadedVectorSpecificationAndDataArray } from "../utils/vectorSpecificationsAndQueriesUtils"; + +export const validEnsembleRealizationsFunctionAtom = atom((get) => { + const ensembleSet = get(EnsembleSetAtom); + let validEnsembleRealizationsFunction = get(EnsembleRealizationFilterFunctionAtom); + + if (validEnsembleRealizationsFunction === null) { + validEnsembleRealizationsFunction = (ensembleIdent: EnsembleIdent) => { + return ensembleSet.findEnsemble(ensembleIdent)?.getRealizations() ?? []; + }; + } + + return validEnsembleRealizationsFunction; +}); + +export const queryIsFetchingAtom = atom((get) => { + const vectorDataQueries = get(vectorDataQueriesAtom); + const vectorStatisticsQueries = get(vectorStatisticsQueriesAtom); + const historicalVectorDataQueries = get(historicalVectorDataQueriesAtom); + const vectorObservationsQueries = get(vectorObservationsQueriesAtom); + + const vectorDataIsFetching = vectorDataQueries.some((query) => query.isFetching); + const vectorStatisticsIsFetching = vectorStatisticsQueries.some((query) => query.isFetching); + const historicalVectorDataIsFetching = historicalVectorDataQueries.some((query) => query.isFetching); + const vectorObservationsIsFetching = vectorObservationsQueries.isFetching; + + const isFetching = + vectorDataIsFetching || + vectorStatisticsIsFetching || + historicalVectorDataIsFetching || + vectorObservationsIsFetching; + + return isFetching; +}); + +export const realizationsQueryHasErrorAtom = atom((get) => { + const vectorDataQueries = get(vectorDataQueriesAtom); + + return vectorDataQueries.some((query) => query.isError); +}); + +export const statisticsQueryHasErrorAtom = atom((get) => { + const vectorStatisticsQueries = get(vectorStatisticsQueriesAtom); + + return vectorStatisticsQueries.some((query) => query.isError); +}); + +export const historicalDataQueryHasErrorAtom = atom((get) => { + const historicalVectorDataQueries = get(historicalVectorDataQueriesAtom); + + return historicalVectorDataQueries.some((query) => query.isError); +}); + +export const loadedVectorSpecificationsAndRealizationDataAtom = atom((get) => { + const vectorDataQueries = get(vectorDataQueriesAtom); + const vectorSpecifications = get(vectorSpecificationsAtom); + + return createLoadedVectorSpecificationAndDataArray(vectorSpecifications, vectorDataQueries); +}); + +export const loadedVectorSpecificationsAndStatisticsDataAtom = atom((get) => { + const vectorStatisticsQueries = get(vectorStatisticsQueriesAtom); + const vectorSpecifications = get(vectorSpecificationsAtom); + + return createLoadedVectorSpecificationAndDataArray(vectorSpecifications, vectorStatisticsQueries); +}); + +export const loadedVectorSpecificationsAndHistoricalDataAtom = atom((get) => { + const historicalVectorDataQueries = get(historicalVectorDataQueriesAtom); + const vectorSpecifications = get(vectorSpecificationsAtom); + + return createLoadedVectorSpecificationAndDataArray(vectorSpecifications, historicalVectorDataQueries); +}); + +export const activeTimestampUtcMsAtom = atom((get) => { + const loadedVectorSpecificationsAndRealizationData = get(loadedVectorSpecificationsAndRealizationDataAtom); + const isQueryFetching = get(queryIsFetchingAtom); + const userSelectedActiveTimestampUtcMs = get(userSelectedActiveTimestampUtcMsAtom); + + if ( + !isQueryFetching && + userSelectedActiveTimestampUtcMs === null && + loadedVectorSpecificationsAndRealizationData.length > 0 + ) { + const firstTimeStamp = + loadedVectorSpecificationsAndRealizationData.at(0)?.data.at(0)?.timestamps_utc_ms[0] ?? null; + return firstTimeStamp; + } + + return userSelectedActiveTimestampUtcMs; +}); + +export const colorByParameterAtom = atom((get) => { + const colorRealizationsByParameter = get(interfaceColorByParameterAtom); + const visualizationMode = get(visualizationModeAtom); + const parameterIdent = get(parameterIdentAtom); + const selectedEnsembles = get(selectedEnsemblesAtom); + + return ( + colorRealizationsByParameter && + visualizationMode === VisualizationMode.INDIVIDUAL_REALIZATIONS && + parameterIdent !== null && + selectedEnsembles.some((ensemble) => ensemble.getParameters().hasParameter(parameterIdent)) + ); +}); diff --git a/frontend/src/modules/SimulationTimeSeries/view/atoms/interfaceEffects.ts b/frontend/src/modules/SimulationTimeSeries/view/atoms/interfaceEffects.ts new file mode 100644 index 000000000..da265b642 --- /dev/null +++ b/frontend/src/modules/SimulationTimeSeries/view/atoms/interfaceEffects.ts @@ -0,0 +1,43 @@ +import { InterfaceEffects } from "@framework/Module"; +import { SettingsToViewInterface } from "@modules/SimulationTimeSeries/interfaces"; + +import { + interfaceColorByParameterAtom, + parameterIdentAtom, + resampleFrequencyAtom, + selectedEnsemblesAtom, + showObservationsAtom, + vectorSpecificationsAtom, + visualizationModeAtom, +} from "./baseAtoms"; + +export const settingsToViewInterfaceEffects: InterfaceEffects = [ + (getInterfaceValue, setAtomValue) => { + const vectorSpecifications = getInterfaceValue("vectorSpecifications"); + setAtomValue(vectorSpecificationsAtom, vectorSpecifications); + }, + (getInterfaceValue, setAtomValue) => { + const resampleFrequency = getInterfaceValue("resampleFrequency"); + setAtomValue(resampleFrequencyAtom, resampleFrequency); + }, + (getInterfaceValue, setAtomValue) => { + const visualizationMode = getInterfaceValue("visualizationMode"); + setAtomValue(visualizationModeAtom, visualizationMode); + }, + (getInterfaceValue, setAtomValue) => { + const showObservations = getInterfaceValue("showObservations"); + setAtomValue(showObservationsAtom, showObservations); + }, + (getInterfaceValue, setAtomValue) => { + const interfaceColorByParameter = getInterfaceValue("colorByParameter"); + setAtomValue(interfaceColorByParameterAtom, interfaceColorByParameter); + }, + (getInterfaceValue, setAtomValue) => { + const parameterIdent = getInterfaceValue("parameterIdent"); + setAtomValue(parameterIdentAtom, parameterIdent); + }, + (getInterfaceValue, setAtomValue) => { + const selectedEnsembles = getInterfaceValue("selectedEnsembles"); + setAtomValue(selectedEnsemblesAtom, selectedEnsembles); + }, +]; diff --git a/frontend/src/modules/SimulationTimeSeries/view/atoms/queryAtoms.ts b/frontend/src/modules/SimulationTimeSeries/view/atoms/queryAtoms.ts new file mode 100644 index 000000000..13b4ebe29 --- /dev/null +++ b/frontend/src/modules/SimulationTimeSeries/view/atoms/queryAtoms.ts @@ -0,0 +1,204 @@ +import { Frequency_api, Observations_api } from "@api"; +import { apiService } from "@framework/ApiService"; +import { atomWithQueries } from "@framework/utils/atomUtils"; +import { EnsembleVectorObservationDataMap, VisualizationMode } from "@modules/SimulationTimeSeries/typesAndEnums"; +import { QueryObserverResult } from "@tanstack/react-query"; + +import { + resampleFrequencyAtom, + showObservationsAtom, + vectorSpecificationsAtom, + visualizationModeAtom, +} from "./baseAtoms"; +import { validEnsembleRealizationsFunctionAtom } from "./derivedAtoms"; + +const STALE_TIME = 60 * 1000; +const CACHE_TIME = 60 * 1000; + +export const vectorDataQueriesAtom = atomWithQueries((get) => { + const vectorSpecifications = get(vectorSpecificationsAtom); + const resampleFrequency = get(resampleFrequencyAtom); + const visualizationMode = get(visualizationModeAtom); + const validEnsembleRealizationsFunction = get(validEnsembleRealizationsFunctionAtom); + + const enabled = + visualizationMode === VisualizationMode.INDIVIDUAL_REALIZATIONS || + visualizationMode === VisualizationMode.STATISTICS_AND_REALIZATIONS; + + const queries = vectorSpecifications.map((item) => { + const realizations = [...validEnsembleRealizationsFunction(item.ensembleIdent)]; + return () => ({ + queryKey: [ + "getRealizationsVectorData", + item.ensembleIdent.getCaseUuid(), + item.ensembleIdent.getEnsembleName(), + item.vectorName, + resampleFrequency, + realizations, + ], + queryFn: () => + apiService.timeseries.getRealizationsVectorData( + item.ensembleIdent.getCaseUuid() ?? "", + item.ensembleIdent.getEnsembleName() ?? "", + item.vectorName ?? "", + resampleFrequency, + realizations + ), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + enabled: !!( + enabled && + item.vectorName && + item.ensembleIdent.getCaseUuid() && + item.ensembleIdent.getEnsembleName() + ), + }); + }); + + return { + queries, + }; +}); + +export const vectorStatisticsQueriesAtom = atomWithQueries((get) => { + const vectorSpecifications = get(vectorSpecificationsAtom); + const resampleFrequency = get(resampleFrequencyAtom); + const visualizationMode = get(visualizationModeAtom); + const validEnsembleRealizationsFunction = get(validEnsembleRealizationsFunctionAtom); + + const enabled = + visualizationMode === VisualizationMode.STATISTICAL_FANCHART || + visualizationMode === VisualizationMode.STATISTICAL_LINES || + visualizationMode === VisualizationMode.STATISTICS_AND_REALIZATIONS; + + const queries = vectorSpecifications.map((item) => { + const realizations = [...validEnsembleRealizationsFunction(item.ensembleIdent)]; + return () => ({ + queryKey: [ + "getStatisticalVectorData", + item.ensembleIdent.getCaseUuid(), + item.ensembleIdent.getEnsembleName(), + item.vectorName, + resampleFrequency, + realizations, + ], + queryFn: () => + apiService.timeseries.getStatisticalVectorData( + item.ensembleIdent.getCaseUuid() ?? "", + item.ensembleIdent.getEnsembleName() ?? "", + item.vectorName ?? "", + resampleFrequency ?? Frequency_api.MONTHLY, + undefined, + realizations + ), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + enabled: !!( + enabled && + item.vectorName && + item.ensembleIdent.getCaseUuid() && + item.ensembleIdent.getEnsembleName() + ), + }); + }); + + return { + queries, + }; +}); + +export const historicalVectorDataQueriesAtom = atomWithQueries((get) => { + const vectorSpecifications = get(vectorSpecificationsAtom); + const resampleFrequency = get(resampleFrequencyAtom); + + const vectorSpecificationsWithHistoricalData = vectorSpecifications?.filter((vec) => vec.hasHistoricalVector); + const enabled = vectorSpecificationsWithHistoricalData?.some((vec) => vec.hasHistoricalVector) ?? false; + + const queries = vectorSpecifications.map((item) => { + return () => ({ + queryKey: [ + "getHistoricalVectorData", + item.ensembleIdent.getCaseUuid(), + item.ensembleIdent.getEnsembleName(), + item.vectorName, + resampleFrequency, + ], + queryFn: () => + apiService.timeseries.getHistoricalVectorData( + item.ensembleIdent.getCaseUuid() ?? "", + item.ensembleIdent.getEnsembleName() ?? "", + item.vectorName ?? "", + resampleFrequency ?? Frequency_api.MONTHLY + ), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + enabled: !!( + enabled && + item.vectorName && + item.ensembleIdent.getCaseUuid() && + item.ensembleIdent.getEnsembleName() + ), + }); + }); + + return { + queries, + }; +}); + +export const vectorObservationsQueriesAtom = atomWithQueries((get) => { + const showObservations = get(showObservationsAtom); + const vectorSpecifications = get(vectorSpecificationsAtom); + + const uniqueEnsembleIdents = [...new Set(vectorSpecifications?.map((item) => item.ensembleIdent) ?? [])]; + + const queries = uniqueEnsembleIdents.map((item) => { + return () => ({ + queryKey: ["getObservations", item.getCaseUuid()], + queryFn: () => apiService.observations.getObservations(item.getCaseUuid() ?? ""), + staleTime: STALE_TIME, + gcTime: CACHE_TIME, + enabled: !!(showObservations && item.getCaseUuid()), + }); + }); + + return { + queries, + combine: (results: QueryObserverResult[]) => { + const combinedResult: EnsembleVectorObservationDataMap = new Map(); + if (!vectorSpecifications) + return { isFetching: false, isError: false, ensembleVectorObservationDataMap: combinedResult }; + + results.forEach((result, index) => { + const ensembleIdent = uniqueEnsembleIdents.at(index); + if (!ensembleIdent) return; + + const ensembleVectorSpecifications = vectorSpecifications.filter( + (item) => item.ensembleIdent === ensembleIdent + ); + + const ensembleHasObservations = result.data?.summary.length !== 0; + combinedResult.set(ensembleIdent, { + hasSummaryObservations: ensembleHasObservations, + vectorsObservationData: [], + }); + for (const vectorSpec of ensembleVectorSpecifications) { + const vectorObservationsData = + result.data?.summary.find((elm) => elm.vector_name === vectorSpec.vectorName) ?? null; + if (!vectorObservationsData) continue; + + combinedResult.get(ensembleIdent)?.vectorsObservationData.push({ + vectorSpecification: vectorSpec, + data: vectorObservationsData, + }); + } + }); + + return { + isFetching: results.some((result) => result.isFetching), + isError: results.some((result) => result.isError), + ensembleVectorObservationDataMap: combinedResult, + }; + }, + }; +}); diff --git a/frontend/src/modules/SimulationTimeSeries/view/hooks/useMakeEnsembleDisplayNameFunc.ts b/frontend/src/modules/SimulationTimeSeries/view/hooks/useMakeEnsembleDisplayNameFunc.ts index 7977bea07..7ea14fab8 100644 --- a/frontend/src/modules/SimulationTimeSeries/view/hooks/useMakeEnsembleDisplayNameFunc.ts +++ b/frontend/src/modules/SimulationTimeSeries/view/hooks/useMakeEnsembleDisplayNameFunc.ts @@ -1,14 +1,10 @@ import { EnsembleIdent } from "@framework/EnsembleIdent"; import { ViewContext } from "@framework/ModuleContext"; -import { SettingsAtoms } from "@modules/SimulationTimeSeries/settings/atoms/atomDefinitions"; -import { SettingsToViewInterface } from "@modules/SimulationTimeSeries/settingsToViewInterface"; -import { State } from "@modules/SimulationTimeSeries/state"; +import { Interfaces } from "@modules/SimulationTimeSeries/interfaces"; import { makeDistinguishableEnsembleDisplayName } from "@modules/_shared/ensembleNameUtils"; -import { ViewAtoms } from "../atoms/atomDefinitions"; - export function useMakeEnsembleDisplayNameFunc( - viewContext: ViewContext + viewContext: ViewContext ): (ensembleIdent: EnsembleIdent) => string { const selectedEnsembles = viewContext.useSettingsToViewInterfaceValue("selectedEnsembles"); diff --git a/frontend/src/modules/SimulationTimeSeries/view/hooks/useMakeViewStatusWriterMessages.ts b/frontend/src/modules/SimulationTimeSeries/view/hooks/useMakeViewStatusWriterMessages.ts index 053017b7d..a00568236 100644 --- a/frontend/src/modules/SimulationTimeSeries/view/hooks/useMakeViewStatusWriterMessages.ts +++ b/frontend/src/modules/SimulationTimeSeries/view/hooks/useMakeViewStatusWriterMessages.ts @@ -2,27 +2,31 @@ import { Ensemble } from "@framework/Ensemble"; import { EnsembleSetAtom } from "@framework/GlobalAtoms"; import { ViewContext } from "@framework/ModuleContext"; import { ViewStatusWriter } from "@framework/StatusWriter"; -import { SettingsAtoms } from "@modules/SimulationTimeSeries/settings/atoms/atomDefinitions"; -import { SettingsToViewInterface } from "@modules/SimulationTimeSeries/settingsToViewInterface"; -import { State } from "@modules/SimulationTimeSeries/state"; +import { Interfaces } from "@modules/SimulationTimeSeries/interfaces"; import { useAtomValue } from "jotai"; -import { ViewAtoms } from "../atoms/atomDefinitions"; +import { + historicalDataQueryHasErrorAtom, + queryIsFetchingAtom, + realizationsQueryHasErrorAtom, + statisticsQueryHasErrorAtom, +} from "../atoms/derivedAtoms"; +import { vectorObservationsQueriesAtom } from "../atoms/queryAtoms"; export function useMakeViewStatusWriterMessages( - viewContext: ViewContext, + viewContext: ViewContext, statusWriter: ViewStatusWriter, parameterDisplayName: string | null, ensemblesWithoutParameter: Ensemble[] ) { const ensembleSet = useAtomValue(EnsembleSetAtom); const showObservations = viewContext.useSettingsToViewInterfaceValue("showObservations"); - const vectorObservationsQueries = viewContext.useViewAtomValue("vectorObservationsQueries"); - const isQueryFetching = viewContext.useViewAtomValue("queryIsFetching"); - const hasHistoricalVectorQueryError = viewContext.useViewAtomValue("historicalDataQueryHasError"); - const hasRealizationsQueryError = viewContext.useViewAtomValue("realizationsQueryHasError"); - const hasStatisticsQueryError = viewContext.useViewAtomValue("statisticsQueryHasError"); + const vectorObservationsQueries = useAtomValue(vectorObservationsQueriesAtom); + const isQueryFetching = useAtomValue(queryIsFetchingAtom); + const hasHistoricalVectorQueryError = useAtomValue(historicalDataQueryHasErrorAtom); + const hasRealizationsQueryError = useAtomValue(realizationsQueryHasErrorAtom); + const hasStatisticsQueryError = useAtomValue(statisticsQueryHasErrorAtom); statusWriter.setLoading(isQueryFetching); if (hasRealizationsQueryError) { diff --git a/frontend/src/modules/SimulationTimeSeries/view/hooks/usePublishToDataChannels.ts b/frontend/src/modules/SimulationTimeSeries/view/hooks/usePublishToDataChannels.ts index aad13300e..122bf4b74 100644 --- a/frontend/src/modules/SimulationTimeSeries/view/hooks/usePublishToDataChannels.ts +++ b/frontend/src/modules/SimulationTimeSeries/view/hooks/usePublishToDataChannels.ts @@ -1,23 +1,23 @@ import { ChannelContentDefinition } from "@framework/DataChannelTypes"; import { ViewContext } from "@framework/ModuleContext"; -import { SettingsAtoms } from "@modules/SimulationTimeSeries/settings/atoms/atomDefinitions"; -import { SettingsToViewInterface } from "@modules/SimulationTimeSeries/settingsToViewInterface"; -import { State } from "@modules/SimulationTimeSeries/state"; +import { Interfaces } from "@modules/SimulationTimeSeries/interfaces"; + +import { useAtomValue } from "jotai"; import { useMakeEnsembleDisplayNameFunc } from "./useMakeEnsembleDisplayNameFunc"; import { ChannelIds } from "../../channelDefs"; import { makeVectorGroupDataGenerator } from "../../dataGenerators"; -import { ViewAtoms } from "../atoms/atomDefinitions"; +import { + activeTimestampUtcMsAtom, + loadedVectorSpecificationsAndRealizationDataAtom, + queryIsFetchingAtom, +} from "../atoms/derivedAtoms"; -export function usePublishToDataChannels( - viewContext: ViewContext -) { - const loadedVectorSpecificationsAndRealizationData = viewContext.useViewAtomValue( - "loadedVectorSpecificationsAndRealizationData" - ); - const activeTimestampUtcMs = viewContext.useViewAtomValue("activeTimestampUtcMs"); - const isQueryFetching = viewContext.useViewAtomValue("queryIsFetching"); +export function usePublishToDataChannels(viewContext: ViewContext) { + const loadedVectorSpecificationsAndRealizationData = useAtomValue(loadedVectorSpecificationsAndRealizationDataAtom); + const activeTimestampUtcMs = useAtomValue(activeTimestampUtcMsAtom); + const isQueryFetching = useAtomValue(queryIsFetchingAtom); const makeEnsembleDisplayName = useMakeEnsembleDisplayNameFunc(viewContext); diff --git a/frontend/src/modules/SimulationTimeSeries/view/hooks/useSubplotBuilder.ts b/frontend/src/modules/SimulationTimeSeries/view/hooks/useSubplotBuilder.ts index b9a21c0e7..26462e576 100644 --- a/frontend/src/modules/SimulationTimeSeries/view/hooks/useSubplotBuilder.ts +++ b/frontend/src/modules/SimulationTimeSeries/view/hooks/useSubplotBuilder.ts @@ -2,16 +2,21 @@ import { SummaryVectorObservations_api } from "@api"; import { ViewContext } from "@framework/ModuleContext"; import { ColorSet } from "@lib/utils/ColorSet"; import { Size2D } from "@lib/utils/geometry"; -import { SettingsAtoms } from "@modules/SimulationTimeSeries/settings/atoms/atomDefinitions"; -import { SettingsToViewInterface } from "@modules/SimulationTimeSeries/settingsToViewInterface"; -import { State } from "@modules/SimulationTimeSeries/state"; +import { Interfaces } from "@modules/SimulationTimeSeries/interfaces"; +import { useAtomValue } from "jotai"; import { Layout } from "plotly.js"; import { useMakeEnsembleDisplayNameFunc } from "./useMakeEnsembleDisplayNameFunc"; import { GroupBy, VectorSpec, VisualizationMode } from "../../typesAndEnums"; -import { ViewAtoms } from "../atoms/atomDefinitions"; +import { + activeTimestampUtcMsAtom, + loadedVectorSpecificationsAndHistoricalDataAtom, + loadedVectorSpecificationsAndRealizationDataAtom, + loadedVectorSpecificationsAndStatisticsDataAtom, +} from "../atoms/derivedAtoms"; +import { vectorObservationsQueriesAtom } from "../atoms/queryAtoms"; import { EnsemblesContinuousParameterColoring } from "../utils/ensemblesContinuousParameterColoring"; import { SubplotBuilder, SubplotOwner } from "../utils/subplotBuilder"; import { TimeSeriesPlotData } from "../utils/timeSeriesPlotData"; @@ -21,7 +26,7 @@ import { } from "../utils/vectorSpecificationsAndQueriesUtils"; export function useSubplotBuilder( - viewContext: ViewContext, + viewContext: ViewContext, wrapperDivSize: Size2D, colorSet: ColorSet, ensemblesParameterColoring: EnsemblesContinuousParameterColoring | null @@ -33,18 +38,12 @@ export function useSubplotBuilder( const showHistorical = viewContext.useSettingsToViewInterfaceValue("showHistorical"); const statisticsSelection = viewContext.useSettingsToViewInterfaceValue("statisticsSelection"); - const vectorObservationsQueries = viewContext.useViewAtomValue("vectorObservationsQueries"); - const loadedVectorSpecificationsAndRealizationData = viewContext.useViewAtomValue( - "loadedVectorSpecificationsAndRealizationData" - ); - const loadedVectorSpecificationsAndStatisticsData = viewContext.useViewAtomValue( - "loadedVectorSpecificationsAndStatisticsData" - ); - const loadedVectorSpecificationsAndHistoricalData = viewContext.useViewAtomValue( - "loadedVectorSpecificationsAndHistoricalData" - ); + const vectorObservationsQueries = useAtomValue(vectorObservationsQueriesAtom); + const loadedVectorSpecificationsAndRealizationData = useAtomValue(loadedVectorSpecificationsAndRealizationDataAtom); + const loadedVectorSpecificationsAndStatisticsData = useAtomValue(loadedVectorSpecificationsAndStatisticsDataAtom); + const loadedVectorSpecificationsAndHistoricalData = useAtomValue(loadedVectorSpecificationsAndHistoricalDataAtom); const colorByParameter = viewContext.useSettingsToViewInterfaceValue("colorByParameter"); - const activeTimestampUtcMs = viewContext.useViewAtomValue("activeTimestampUtcMs"); + const activeTimestampUtcMs = useAtomValue(activeTimestampUtcMsAtom); const makeEnsembleDisplayName = useMakeEnsembleDisplayNameFunc(viewContext); diff --git a/frontend/src/modules/SimulationTimeSeries/view/view.tsx b/frontend/src/modules/SimulationTimeSeries/view/view.tsx index 522b98a21..8db2987de 100644 --- a/frontend/src/modules/SimulationTimeSeries/view/view.tsx +++ b/frontend/src/modules/SimulationTimeSeries/view/view.tsx @@ -8,21 +8,19 @@ import { useElementSize } from "@lib/hooks/useElementSize"; import { ColorScaleGradientType } from "@lib/utils/ColorScale"; import { ContentError } from "@modules/_shared/components/ContentMessage"; +import { useAtomValue, useSetAtom } from "jotai"; import { PlotDatum, PlotMouseEvent } from "plotly.js"; -import { ViewAtoms } from "./atoms/atomDefinitions"; +import { userSelectedActiveTimestampUtcMsAtom } from "./atoms/baseAtoms"; +import { realizationsQueryHasErrorAtom, statisticsQueryHasErrorAtom } from "./atoms/derivedAtoms"; import { useMakeViewStatusWriterMessages } from "./hooks/useMakeViewStatusWriterMessages"; import { usePublishToDataChannels } from "./hooks/usePublishToDataChannels"; import { useSubplotBuilder } from "./hooks/useSubplotBuilder"; import { EnsemblesContinuousParameterColoring } from "./utils/ensemblesContinuousParameterColoring"; -import { SettingsAtoms } from "../settings/atoms/atomDefinitions"; -import { SettingsToViewInterface } from "../settingsToViewInterface"; +import { Interfaces } from "../interfaces"; -export const View = ({ - viewContext, - workbenchSettings, -}: ModuleViewProps, SettingsToViewInterface, SettingsAtoms, ViewAtoms>) => { +export const View = ({ viewContext, workbenchSettings }: ModuleViewProps) => { const wrapperDivRef = React.useRef(null); const wrapperDivSize = useElementSize(wrapperDivRef); @@ -31,10 +29,10 @@ export const View = ({ const colorByParameter = viewContext.useSettingsToViewInterfaceValue("colorByParameter"); const parameterIdent = viewContext.useSettingsToViewInterfaceValue("parameterIdent"); const selectedEnsembles = viewContext.useSettingsToViewInterfaceValue("selectedEnsembles"); - const hasRealizationsQueryError = viewContext.useViewAtomValue("realizationsQueryHasError"); - const hasStatisticsQueryError = viewContext.useViewAtomValue("statisticsQueryHasError"); + const hasRealizationsQueryError = useAtomValue(realizationsQueryHasErrorAtom); + const hasStatisticsQueryError = useAtomValue(statisticsQueryHasErrorAtom); - const setActiveTimestampUtcMs = viewContext.useSetViewAtom("userSelectedActiveTimestampUtcMs"); + const setActiveTimestampUtcMs = useSetAtom(userSelectedActiveTimestampUtcMsAtom); // Color palettes const colorSet = workbenchSettings.useColorSet(); diff --git a/frontend/src/modules/SimulationTimeSeriesMatrix/channelDefs.ts b/frontend/src/modules/SimulationTimeSeriesMatrix/channelDefs.ts deleted file mode 100644 index 84709f6b4..000000000 --- a/frontend/src/modules/SimulationTimeSeriesMatrix/channelDefs.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ChannelDefinition, KeyKind } from "@framework/DataChannelTypes"; - -export enum ChannelIds { - TIME_SERIES = "TimeSeries (with value per realization)", -} - -export const channelDefs: ChannelDefinition[] = [ - { - idString: ChannelIds.TIME_SERIES, - displayName: "Time series (with value per realization)", - kindOfKey: KeyKind.REALIZATION, - }, -]; diff --git a/frontend/src/modules/SimulationTimeSeriesMatrix/dataGenerators.ts b/frontend/src/modules/SimulationTimeSeriesMatrix/dataGenerators.ts deleted file mode 100644 index db8ebc259..000000000 --- a/frontend/src/modules/SimulationTimeSeriesMatrix/dataGenerators.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { VectorRealizationData_api } from "@api"; -import { ChannelContentMetaData, DataGenerator } from "@framework/DataChannelTypes"; -import { EnsembleIdent } from "@framework/EnsembleIdent"; -import { simulationUnitReformat, simulationVectorDescription } from "@modules/_shared/reservoirSimulationStringUtils"; - -import { VectorSpec } from "./state"; - -export function makeVectorGroupDataGenerator( - vectorSpecification: VectorSpec, - vectorSpecificationsAndRealizationData: { - vectorSpecification: VectorSpec; - data: VectorRealizationData_api[]; - }[], - activeTimestampUtcMs: number, - makeEnsembleDisplayName: (ensembleIdent: EnsembleIdent) => string -): DataGenerator { - return () => { - const data: { key: number; value: number }[] = []; - let metaData: ChannelContentMetaData = { - unit: "", - ensembleIdentString: "", - displayString: "", - }; - - const vector = vectorSpecificationsAndRealizationData.find( - (vec) => - vec.vectorSpecification.vectorName === vectorSpecification.vectorName && - vec.vectorSpecification.ensembleIdent.equals(vectorSpecification.ensembleIdent) - ); - if (vector) { - let unit = ""; - vector.data.forEach((el) => { - unit = simulationUnitReformat(el.unit); - const indexOfTimestamp = el.timestamps_utc_ms.indexOf(activeTimestampUtcMs); - data.push({ - key: el.realization, - value: indexOfTimestamp === -1 ? el.values[0] : el.values[indexOfTimestamp], - }); - }); - metaData = { - unit, - ensembleIdentString: vector.vectorSpecification.ensembleIdent.toString(), - displayString: `${simulationVectorDescription( - vector.vectorSpecification.vectorName - )} (${makeEnsembleDisplayName(vector.vectorSpecification.ensembleIdent)})`, - }; - } - return { - data, - metaData: metaData ?? undefined, - }; - }; -} diff --git a/frontend/src/modules/SimulationTimeSeriesMatrix/loadModule.tsx b/frontend/src/modules/SimulationTimeSeriesMatrix/loadModule.tsx deleted file mode 100644 index 26f47cd83..000000000 --- a/frontend/src/modules/SimulationTimeSeriesMatrix/loadModule.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Frequency_api, StatisticFunction_api } from "@api"; -import { ModuleRegistry } from "@framework/ModuleRegistry"; - -import { Settings } from "./settings"; -import { FanchartStatisticOption, GroupBy, State, VisualizationMode } from "./state"; -import { View } from "./view"; - -const defaultState: State = { - groupBy: GroupBy.TIME_SERIES, - colorRealizationsByParameter: false, - parameterIdent: null, - visualizationMode: VisualizationMode.STATISTICAL_FANCHART, - vectorSpecifications: [], - resamplingFrequency: Frequency_api.MONTHLY, - showObservations: true, - showHistorical: true, - statisticsSelection: { - IndividualStatisticsSelection: Object.values(StatisticFunction_api), - FanchartStatisticsSelection: Object.values(FanchartStatisticOption), - }, -}; - -const module = ModuleRegistry.initModule("SimulationTimeSeriesMatrix", defaultState); - -module.viewFC = View; -module.settingsFC = Settings; diff --git a/frontend/src/modules/SimulationTimeSeriesMatrix/preview.svg b/frontend/src/modules/SimulationTimeSeriesMatrix/preview.svg deleted file mode 100644 index f87d9840c..000000000 --- a/frontend/src/modules/SimulationTimeSeriesMatrix/preview.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/frontend/src/modules/SimulationTimeSeriesMatrix/preview.tsx b/frontend/src/modules/SimulationTimeSeriesMatrix/preview.tsx deleted file mode 100644 index eb2960245..000000000 --- a/frontend/src/modules/SimulationTimeSeriesMatrix/preview.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { DrawPreviewFunc } from "@framework/Preview"; -import previewImg from "./preview.svg"; - -export const preview: DrawPreviewFunc = function (width: number, height: number) { - return -}; diff --git a/frontend/src/modules/SimulationTimeSeriesMatrix/queryHooks.tsx b/frontend/src/modules/SimulationTimeSeriesMatrix/queryHooks.tsx deleted file mode 100644 index 5a414f713..000000000 --- a/frontend/src/modules/SimulationTimeSeriesMatrix/queryHooks.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import { Frequency_api, SummaryVectorObservations_api, VectorDescription_api } from "@api"; -import { VectorHistoricalData_api, VectorRealizationData_api, VectorStatisticData_api } from "@api"; -import { apiService } from "@framework/ApiService"; -import { EnsembleIdent } from "@framework/EnsembleIdent"; -import { UseQueryResult, useQueries } from "@tanstack/react-query"; - -import { VectorSpec } from "./state"; - -const STALE_TIME = 60 * 1000; -const CACHE_TIME = 60 * 1000; - -export function useVectorListQueries( - caseUuidsAndEnsembleNames: EnsembleIdent[] | null -): UseQueryResult[] { - return useQueries({ - queries: (caseUuidsAndEnsembleNames ?? []).map((item) => { - return { - queryKey: ["getVectorList", item.getCaseUuid(), item.getEnsembleName()], - queryFn: () => - apiService.timeseries.getVectorList(item.getCaseUuid() ?? "", item.getEnsembleName() ?? ""), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: item.getCaseUuid() && item.getEnsembleName() ? true : false, - }; - }), - }); -} - -export function useVectorDataQueries( - vectorSpecifications: VectorSpec[] | null, - resampleFrequency: Frequency_api | null, - allowEnable: boolean -): UseQueryResult[] { - return useQueries({ - queries: (vectorSpecifications ?? []).map((item) => { - return { - queryKey: [ - "getRealizationsVectorData", - item.ensembleIdent.getCaseUuid(), - item.ensembleIdent.getEnsembleName(), - item.vectorName, - resampleFrequency, - item.selectedIndividualRealizations, - ], - queryFn: () => - apiService.timeseries.getRealizationsVectorData( - item.ensembleIdent.getCaseUuid() ?? "", - item.ensembleIdent.getEnsembleName() ?? "", - item.vectorName ?? "", - resampleFrequency ?? undefined, - item.selectedIndividualRealizations - ), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: !!( - allowEnable && - item.vectorName && - item.ensembleIdent.getCaseUuid() && - item.ensembleIdent.getEnsembleName() - ), - }; - }), - }); -} - -export function useStatisticalVectorDataQueries( - vectorSpecifications: VectorSpec[] | null, - resampleFrequency: Frequency_api | null, - allowEnable: boolean -): UseQueryResult[] { - return useQueries({ - queries: (vectorSpecifications ?? []).map((item) => { - return { - queryKey: [ - "getStatisticalVectorData", - item.ensembleIdent.getCaseUuid(), - item.ensembleIdent.getEnsembleName(), - item.vectorName, - resampleFrequency, - item.selectedStatisticsRealizations, - ], - queryFn: () => - apiService.timeseries.getStatisticalVectorData( - item.ensembleIdent.getCaseUuid() ?? "", - item.ensembleIdent.getEnsembleName() ?? "", - item.vectorName ?? "", - resampleFrequency ?? Frequency_api.MONTHLY, - undefined, - item.selectedStatisticsRealizations - ), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: !!( - allowEnable && - item.vectorName && - item.ensembleIdent.getCaseUuid() && - item.ensembleIdent.getEnsembleName() && - resampleFrequency - ), - }; - }), - }); -} - -export function useHistoricalVectorDataQueries( - nonHistoricalVectorSpecifications: VectorSpec[] | null, - resampleFrequency: Frequency_api | null, - allowEnable: boolean -): UseQueryResult[] { - return useQueries({ - queries: (nonHistoricalVectorSpecifications ?? []).map((item) => { - return { - queryKey: [ - "getHistoricalVectorData", - item.ensembleIdent.getCaseUuid(), - item.ensembleIdent.getEnsembleName(), - item.vectorName, - resampleFrequency, - ], - queryFn: () => - apiService.timeseries.getHistoricalVectorData( - item.ensembleIdent.getCaseUuid() ?? "", - item.ensembleIdent.getEnsembleName() ?? "", - item.vectorName ?? "", - resampleFrequency ?? Frequency_api.MONTHLY - ), - staleTime: STALE_TIME, - gcTime: CACHE_TIME, - enabled: !!( - allowEnable && - item.vectorName && - item.ensembleIdent.getCaseUuid() && - item.ensembleIdent.getEnsembleName() && - resampleFrequency - ), - }; - }), - }); -} - -/** - * Definition of ensemble vector observation data - * - * hasSummaryObservations: true if the ensemble has observations, i.e the summary observations array is not empty - * vectorsObservationData: array of vector observation data for requested vector specifications - */ -export type EnsembleVectorObservationData = { - hasSummaryObservations: boolean; - vectorsObservationData: { vectorSpecification: VectorSpec; data: SummaryVectorObservations_api }[]; -}; - -/** - * Definition of map of ensemble ident and ensemble vector observation data - */ -export type EnsembleVectorObservationDataMap = Map; - -/** - * Definition of vector observations queries result for combined queries - */ -export type VectorObservationsQueriesResult = { - isFetching: boolean; - isError: boolean; - ensembleVectorObservationDataMap: EnsembleVectorObservationDataMap; -}; - -/** - * This function takes vectorSpecifications and returns a map of ensembleIdent and the respective observations data for - * the selected vectors. - * - * If the returned summary array from back-end is empty array, the ensemble does not have observations. - * If the selected vectors are not among the returned summary array, the vector does not have observations. - */ -export function useVectorObservationsQueries( - vectorSpecifications: VectorSpec[] | null, - allowEnable: boolean -): VectorObservationsQueriesResult { - const uniqueEnsembleIdents = [...new Set(vectorSpecifications?.map((item) => item.ensembleIdent) ?? [])]; - return useQueries({ - queries: (uniqueEnsembleIdents ?? []).map((item) => { - return { - queryKey: ["getObservations", item.getCaseUuid()], - queryFn: () => apiService.observations.getObservations(item.getCaseUuid() ?? ""), - staleTime: STALE_TIME, - cacheTime: CACHE_TIME, - enabled: !!(allowEnable && item.getCaseUuid()), - }; - }), - combine: (results) => { - const combinedResult: EnsembleVectorObservationDataMap = new Map(); - if (!vectorSpecifications) - return { isFetching: false, isError: false, ensembleVectorObservationDataMap: combinedResult }; - - results.forEach((result, index) => { - const ensembleIdent = uniqueEnsembleIdents.at(index); - if (!ensembleIdent) return; - - const ensembleVectorSpecifications = vectorSpecifications.filter( - (item) => item.ensembleIdent === ensembleIdent - ); - - const ensembleHasObservations = result.data?.summary.length !== 0; - combinedResult.set(ensembleIdent, { - hasSummaryObservations: ensembleHasObservations, - vectorsObservationData: [], - }); - for (const vectorSpec of ensembleVectorSpecifications) { - const vectorObservationsData = - result.data?.summary.find((elm) => elm.vector_name === vectorSpec.vectorName) ?? null; - if (!vectorObservationsData) continue; - - combinedResult.get(ensembleIdent)?.vectorsObservationData.push({ - vectorSpecification: vectorSpec, - data: vectorObservationsData, - }); - } - }); - - return { - isFetching: results.some((result) => result.isFetching), - isError: results.some((result) => result.isError), - ensembleVectorObservationDataMap: combinedResult, - }; - }, - }); -} diff --git a/frontend/src/modules/SimulationTimeSeriesMatrix/registerModule.ts b/frontend/src/modules/SimulationTimeSeriesMatrix/registerModule.ts deleted file mode 100644 index 354602cbc..000000000 --- a/frontend/src/modules/SimulationTimeSeriesMatrix/registerModule.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ModuleCategory, ModuleDevState } from "@framework/Module"; -import { ModuleDataTagId } from "@framework/ModuleDataTags"; -import { ModuleRegistry } from "@framework/ModuleRegistry"; - -import { channelDefs } from "./channelDefs"; -// import { SyncSettingKey } from "@framework/SyncSettings"; -// import { broadcastChannelsDef } from "./channelDefs"; -import { preview } from "./preview"; -import { State } from "./state"; - -const description = "Plotting of simulation time series data."; - -ModuleRegistry.registerModule({ - moduleName: "SimulationTimeSeriesMatrix", - defaultTitle: "Simulation Time Series Matrix", - category: ModuleCategory.MAIN, - devState: ModuleDevState.DEPRECATED, - dataTagIds: [ModuleDataTagId.SUMMARY, ModuleDataTagId.OBSERVATIONS], - // syncableSettingKeys: [SyncSettingKey.ENSEMBLE, SyncSettingKey.TIME_SERIES], - preview, - channelDefinitions: channelDefs, - description, -}); diff --git a/frontend/src/modules/SimulationTimeSeriesMatrix/settings.tsx b/frontend/src/modules/SimulationTimeSeriesMatrix/settings.tsx deleted file mode 100644 index 5ac23f1c8..000000000 --- a/frontend/src/modules/SimulationTimeSeriesMatrix/settings.tsx +++ /dev/null @@ -1,520 +0,0 @@ -import React from "react"; - -import { Frequency_api, StatisticFunction_api } from "@api"; -import { EnsembleIdent } from "@framework/EnsembleIdent"; -import { Parameter, ParameterIdent } from "@framework/EnsembleParameters"; -import { EnsembleSet } from "@framework/EnsembleSet"; -import { ModuleSettingsProps } from "@framework/Module"; -import { useSettingsStatusWriter } from "@framework/StatusWriter"; -import { useEnsembleSet } from "@framework/WorkbenchSession"; -import { EnsembleSelect } from "@framework/components/EnsembleSelect"; -import { ParameterListFilter } from "@framework/components/ParameterListFilter"; -import { VectorSelector, createVectorSelectorDataFromVectors } from "@framework/components/VectorSelector"; -import { fixupEnsembleIdents } from "@framework/utils/ensembleUiHelpers"; -import { Checkbox } from "@lib/components/Checkbox"; -import { CircularProgress } from "@lib/components/CircularProgress"; -import { CollapsibleGroup } from "@lib/components/CollapsibleGroup"; -import { Dropdown } from "@lib/components/Dropdown"; -import { Label } from "@lib/components/Label"; -import { PendingWrapper } from "@lib/components/PendingWrapper"; -import { QueriesErrorCriteria, QueryStateWrapper } from "@lib/components/QueryStateWrapper"; -import { RadioGroup } from "@lib/components/RadioGroup"; -import { Select } from "@lib/components/Select"; -import { SmartNodeSelectorSelection, TreeDataNode } from "@lib/components/SmartNodeSelector"; -import { useValidState } from "@lib/hooks/useValidState"; -import { resolveClassNames } from "@lib/utils/resolveClassNames"; -import { FilterAlt } from "@mui/icons-material"; - -import { isEqual } from "lodash"; -import { VectorDescription_api } from "src/api"; - -import { useVectorListQueries } from "./queryHooks"; -import { - FanchartStatisticOption, - FanchartStatisticOptionEnumToStringMapping, - FrequencyEnumToStringMapping, - GroupBy, - GroupByEnumToStringMapping, - State, - StatisticFunctionEnumToStringMapping, - VectorSpec, - VisualizationMode, - VisualizationModeEnumToStringMapping, -} from "./state"; -import { EnsembleVectorListsHelper } from "./utils/ensemblesVectorListHelper"; -import { getContinuousAndNonConstantParameters } from "./utils/getContinuousAndNonConstantParameters"; -import { joinStringArrayToHumanReadableString } from "./utils/stringUtils"; - -enum StatisticsType { - INDIVIDUAL = "Individual", - FANCHART = "Fanchart", -} - -export function Settings({ settingsContext, workbenchSession }: ModuleSettingsProps) { - const ensembleSet = useEnsembleSet(workbenchSession); - const statusWriter = useSettingsStatusWriter(settingsContext); - - // Store state/values - const [resampleFrequency, setResamplingFrequency] = settingsContext.useStoreState("resamplingFrequency"); - const [groupBy, setGroupBy] = settingsContext.useStoreState("groupBy"); - const [colorRealizationsByParameter, setColorRealizationsByParameter] = - settingsContext.useStoreState("colorRealizationsByParameter"); - const [visualizationMode, setVisualizationMode] = settingsContext.useStoreState("visualizationMode"); - const [showHistorical, setShowHistorical] = settingsContext.useStoreState("showHistorical"); - const [showObservations, setShowObservations] = settingsContext.useStoreState("showObservations"); - const [statisticsSelection, setStatisticsSelection] = settingsContext.useStoreState("statisticsSelection"); - const setParameterIdent = settingsContext.useSetStoreValue("parameterIdent"); - const setVectorSpecifications = settingsContext.useSetStoreValue("vectorSpecifications"); - - // Transitions - const [isPendingGetParameters, startGetParametersTransition] = React.useTransition(); - - // States - const [previousEnsembleSet, setPreviousEnsembleSet] = React.useState(ensembleSet); - const [selectedEnsembleIdents, setSelectedEnsembleIdents] = React.useState([]); - const [selectedVectorNames, setSelectedVectorNames] = React.useState([]); - const [selectedVectorTags, setSelectedVectorTags] = React.useState([]); - const [availableVectorNames, setAvailableVectorNames] = React.useState([]); - const [vectorSelectorData, setVectorSelectorData] = React.useState([]); - const [statisticsType, setStatisticsType] = React.useState(StatisticsType.INDIVIDUAL); - const [filteredParameterIdentList, setFilteredParameterIdentList] = React.useState([]); - const [prevVectorQueriesDataList, setPrevVectorQueriesDataList] = React.useState< - (VectorDescription_api[] | undefined)[] - >([]); - const [prevSelectedEnsembleIdents, setPrevSelectedEnsembleIdents] = React.useState([]); - // If the selectedEnsembleIdents state gets an initial value, this should also be set to an initial value - // NOTE: DO NOT call a function in order to set the initial value, as this will cause the function to be called - // on every render. Rather set it the same place as where the initial value of selectedEnsembleIdents is set. - const [continuousAndNonConstantParametersUnion, setContinuousAndNonConstantParametersUnion] = React.useState< - Parameter[] - >([]); - - const ensembleVectorListsHelper = React.useRef(new EnsembleVectorListsHelper([], [])); - - if (!isEqual(ensembleSet, previousEnsembleSet)) { - const newSelectedEnsembleIdents = selectedEnsembleIdents.filter((ensemble) => - ensembleSet.hasEnsemble(ensemble) - ); - const validatedEnsembleIdents = fixupEnsembleIdents(newSelectedEnsembleIdents, ensembleSet) ?? []; - if (!isEqual(selectedEnsembleIdents, validatedEnsembleIdents)) { - setSelectedEnsembleIdents(validatedEnsembleIdents); - } - - setPreviousEnsembleSet(ensembleSet); - } - - const vectorListQueries = useVectorListQueries(selectedEnsembleIdents); - - if ( - !isEqual( - vectorListQueries.map((el) => el.data), - prevVectorQueriesDataList - ) || - !isEqual(selectedEnsembleIdents, prevSelectedEnsembleIdents) - ) { - setPrevVectorQueriesDataList(vectorListQueries.map((el) => el.data)); - setPrevSelectedEnsembleIdents(selectedEnsembleIdents); - ensembleVectorListsHelper.current = new EnsembleVectorListsHelper(selectedEnsembleIdents, vectorListQueries); - } - - const isVectorListQueriesFetching = vectorListQueries.some((query) => query.isFetching); - - // Await update of vectorSelectorData until all vector lists are finished fetching - let computedVectorSelectorData = vectorSelectorData; - let computedAvailableVectorNames = availableVectorNames; - const vectorNamesUnion = ensembleVectorListsHelper.current.vectorsUnion(); - if (!isVectorListQueriesFetching && !isEqual(computedAvailableVectorNames, vectorNamesUnion)) { - computedAvailableVectorNames = vectorNamesUnion; - computedVectorSelectorData = createVectorSelectorDataFromVectors(vectorNamesUnion); - - setAvailableVectorNames(computedAvailableVectorNames); - setVectorSelectorData(computedVectorSelectorData); - } - - const selectedVectorNamesHasHistorical = - !isVectorListQueriesFetching && ensembleVectorListsHelper.current.hasAnyHistoricalVector(selectedVectorNames); - - const [selectedParameterIdentStr, setSelectedParameterIdentStr] = useValidState({ - initialState: null, - validStates: filteredParameterIdentList.map((item: ParameterIdent) => item.toString()), - }); - - // Set error if all vector list queries fail - const hasEveryVectorListQueryError = - vectorListQueries.length > 0 && vectorListQueries.every((query) => query.isError); - if (hasEveryVectorListQueryError) { - let errorMessage = "Could not load vectors for selected ensemble"; - if (vectorListQueries.length > 1) { - errorMessage += "s"; - } - statusWriter.addError(errorMessage); - } - - // Set warning for vector names not existing in a selected ensemble - const validateVectorNamesInEnsemble = (vectorNames: string[], ensembleIdent: EnsembleIdent) => { - const existingVectors = vectorNames.filter((vector) => - ensembleVectorListsHelper.current.isVectorInEnsemble(ensembleIdent, vector) - ); - if (existingVectors.length === vectorNames.length) { - return; - } - - const nonExistingVectors = vectorNames.filter((vector) => !existingVectors.includes(vector)); - const ensembleStr = ensembleSet.findEnsemble(ensembleIdent)?.getDisplayName() ?? ensembleIdent.toString(); - const vectorArrayStr = joinStringArrayToHumanReadableString(nonExistingVectors); - statusWriter.addWarning(`Vector ${vectorArrayStr} does not exist in ensemble ${ensembleStr}`); - }; - - // Note: selectedVectorNames is not updated until vectorSelectorData is updated and VectorSelector triggers onChange - if (selectedEnsembleIdents.length === 1) { - // If single ensemble is selected and no vectors exist, selectedVectorNames is empty as no vectors are valid - // in the VectorSelector. Then utilizing selectedVectorTags for status message - const vectorNames = selectedVectorNames.length > 0 ? selectedVectorNames : selectedVectorTags; - validateVectorNamesInEnsemble(vectorNames, selectedEnsembleIdents[0]); - } - for (const ensembleIdent of selectedEnsembleIdents) { - validateVectorNamesInEnsemble(selectedVectorNames, ensembleIdent); - } - - // Set statistics type for checkbox rendering - const computedStatisticsType = computeStatisticsType(statisticsType, visualizationMode); - if (statisticsType !== computedStatisticsType) { - setStatisticsType(computedStatisticsType); - } - - const numberOfQueriesWithData = ensembleVectorListsHelper.current.numberOfQueriesWithData(); - - React.useEffect( - function propagateVectorSpecsToView() { - const newVectorSpecifications: VectorSpec[] = []; - for (const ensembleIdent of selectedEnsembleIdents) { - for (const vector of selectedVectorNames) { - if (!ensembleVectorListsHelper.current.isVectorInEnsemble(ensembleIdent, vector)) { - continue; - } - - newVectorSpecifications.push({ - ensembleIdent: ensembleIdent, - color: ensembleSet.findEnsemble(ensembleIdent)?.getColor() ?? null, - vectorName: vector, - hasHistoricalVector: ensembleVectorListsHelper.current.hasHistoricalVector( - ensembleIdent, - vector - ), - }); - } - } - setVectorSpecifications(newVectorSpecifications); - }, - [selectedEnsembleIdents, selectedVectorNames, numberOfQueriesWithData, setVectorSpecifications, ensembleSet] - ); - - React.useEffect( - function propagateParameterIdentToView() { - if (selectedParameterIdentStr === null) { - setParameterIdent(null); - return; - } - - // Try/catch as ParameterIdent.fromString() can throw - try { - const newParameterIdent = ParameterIdent.fromString(selectedParameterIdentStr); - const isParameterAmongFiltered = filteredParameterIdentList.some((parameter) => - parameter.equals(newParameterIdent) - ); - if (isParameterAmongFiltered) { - setParameterIdent(newParameterIdent); - } else { - setParameterIdent(null); - } - } catch { - setParameterIdent(null); - } - }, - [selectedParameterIdentStr, filteredParameterIdentList, setParameterIdent] - ); - - function handleGroupByChange(event: React.ChangeEvent) { - setGroupBy(event.target.value as GroupBy); - } - - function handleColorByParameterChange(parameterIdentStrings: string[]) { - if (parameterIdentStrings.length !== 0) { - setSelectedParameterIdentStr(parameterIdentStrings[0]); - return; - } - setSelectedParameterIdentStr(null); - } - - function handleEnsembleSelectChange(ensembleIdentArr: EnsembleIdent[]) { - setSelectedEnsembleIdents(ensembleIdentArr); - - startGetParametersTransition(function transitionToGetContinuousAndNonConstantParameters() { - setContinuousAndNonConstantParametersUnion( - getContinuousAndNonConstantParameters(ensembleIdentArr, ensembleSet) - ); - }); - } - - function handleVectorSelectionChange(selection: SmartNodeSelectorSelection) { - setSelectedVectorNames(selection.selectedNodes); - setSelectedVectorTags(selection.selectedTags); - } - - function handleFrequencySelectionChange(newFrequencyStr: string) { - const newFreq = newFrequencyStr !== "RAW" ? (newFrequencyStr as Frequency_api) : null; - setResamplingFrequency(newFreq); - } - - function handleShowHistorical(event: React.ChangeEvent) { - setShowHistorical(event.target.checked); - } - - function handleShowObservations(event: React.ChangeEvent) { - setShowObservations(event.target.checked); - } - - function handleVisualizationModeChange(event: React.ChangeEvent) { - setVisualizationMode(event.target.value as VisualizationMode); - } - - function handleFanchartStatisticsSelectionChange( - event: React.ChangeEvent, - statistic: FanchartStatisticOption - ) { - setStatisticsSelection((prev) => { - if (event.target.checked) { - return { - IndividualStatisticsSelection: prev.IndividualStatisticsSelection, - FanchartStatisticsSelection: prev.FanchartStatisticsSelection - ? [...prev.FanchartStatisticsSelection, statistic] - : [statistic], - }; - } else { - return { - IndividualStatisticsSelection: prev.IndividualStatisticsSelection, - FanchartStatisticsSelection: prev.FanchartStatisticsSelection - ? prev.FanchartStatisticsSelection.filter((item) => item !== statistic) - : [], - }; - } - }); - } - - const handleParameterListFilterChange = React.useCallback( - function handleParameterListFilterChange(filteredParameters: Parameter[]) { - const filteredParamIdents = filteredParameters.map((elm) => - ParameterIdent.fromNameAndGroup(elm.name, elm.groupName) - ); - - setFilteredParameterIdentList(filteredParamIdents); - }, - [setFilteredParameterIdentList] - ); - - function handleIndividualStatisticsSelectionChange( - event: React.ChangeEvent, - statistic: StatisticFunction_api - ) { - setStatisticsSelection((prev) => { - if (event.target.checked) { - return { - IndividualStatisticsSelection: prev.IndividualStatisticsSelection - ? [...prev.IndividualStatisticsSelection, statistic] - : [statistic], - FanchartStatisticsSelection: prev.FanchartStatisticsSelection, - }; - } else { - return { - IndividualStatisticsSelection: prev.IndividualStatisticsSelection - ? prev.IndividualStatisticsSelection.filter((item) => item !== statistic) - : [], - FanchartStatisticsSelection: prev.FanchartStatisticsSelection, - }; - } - }); - } - - function makeStatisticCheckboxes() { - if (computedStatisticsType === StatisticsType.FANCHART) { - return Object.values(FanchartStatisticOption).map((value: FanchartStatisticOption) => { - return ( - { - handleFanchartStatisticsSelectionChange(event, value); - }} - /> - ); - }); - } - if (computedStatisticsType === StatisticsType.INDIVIDUAL) { - return Object.values(StatisticFunction_api).map((value: StatisticFunction_api) => { - return ( - { - handleIndividualStatisticsSelectionChange(event, value); - }} - /> - ); - }); - } - - return []; - } - - return ( -
- - { - return { value: val, label: GroupByEnumToStringMapping[val] }; - })} - onChange={handleGroupByChange} - /> - - - { - return { value: val, label: FrequencyEnumToStringMapping[val] }; - }), - ]} - value={resampleFrequency ?? Frequency_api.MONTHLY} - onChange={handleFrequencySelectionChange} - /> - - - - - - - -
query.isLoading), - })} - > - } - showErrorWhen={QueriesErrorCriteria.ALL_QUERIES_HAVE_ERROR} - errorComponent={"Could not load vectors for selected ensembles"} - > - - -
-
- - { - setColorRealizationsByParameter(event.target.checked); - }} - /> -
-
- } - > - - - - -
-