diff --git a/react-ui/src/components/devices/reducer/device.reducer.ts b/react-ui/src/components/devices/reducer/device.reducer.ts index 9d91700c4dcd32c886001628cc794e820b0f9cc1..4afcba7886f6f251b029d4eade2cddaf4599545c 100755 --- a/react-ui/src/components/devices/reducer/device.reducer.ts +++ b/react-ui/src/components/devices/reducer/device.reducer.ts @@ -45,13 +45,27 @@ const deviceSlice = createSlice({ setActiveTab: (state, action: PayloadAction<DeviceViewTabValues>) => { state.activeTab = action.payload }, - setSelectedDevice: (state, action: PayloadAction<Device | null>) => { - let selectedObject = null; - if (action.payload) { - selectedObject = { device: action.payload, mne: null, json: null } - } + setSelectedDevice: { + reducer: (state, action: PayloadAction<Device | null, string, { skipListener?: boolean }>) => { + // do thing if desired device is already selected + if (state.selected?.device.id === action.payload?.id) { + action.meta.skipListener = true + return + } + + let selectedObject = null; + if (action.payload) { + selectedObject = { device: action.payload, mne: null, json: null } + } - state.selected = selectedObject + state.selected = selectedObject + }, + prepare: (device: Device | null) => { + return { + payload: device, + meta: { skipListener: false } // set to true when needed + } + } }, setSelectedMne: (state, action: PayloadAction<NetworkelementManagedNetworkElement>) => { if (!state.selected) { diff --git a/react-ui/src/components/devices/routines/mne.routine.ts b/react-ui/src/components/devices/routines/mne.routine.ts index a3ea43d2feb14b5267349bc09241af0df1c1dd01..876317dd2e56f0da069ee454fc138376682a168c 100755 --- a/react-ui/src/components/devices/routines/mne.routine.ts +++ b/react-ui/src/components/devices/routines/mne.routine.ts @@ -7,7 +7,8 @@ import { } from '@component/devices/reducer/device.reducer' import { createAsyncThunk } from '@reduxjs/toolkit' import { addRoutine } from '@shared/reducer/routine.reducer' -import { Category } from '@shared/types/category.type' +import { Category, CategoryType } from '@shared/types/category.type' +import { RoutineHolderSingleton } from '@utils/routine-holder.singleton' import { RootState } from 'src/stores' import { startListening } from '../../../stores/middleware/listener.middleware' @@ -20,13 +21,14 @@ export const FETCH_MNE_ACTION = 'subscription/device/fetchSelectedMNE' * Triggered by a selectedDevice */ startListening({ - predicate: (action) => setSelectedDevice.match(action) && !!action.payload, + predicate: (action) => setSelectedDevice.match(action) && !!action.payload && !action.meta?.skipListener, effect: async (action, listenerApi) => { + const factory = RoutineHolderSingleton.getInstance(); listenerApi.dispatch( addRoutine({ - thunk: fetchSelectedMneThunk, - category: Category.TAB, - payload: action.payload, + thunk: factory.getRoutineByName("fetchSelectedMneThunk"), + category: Category.TAB as CategoryType, + payload: action.payload as Object, }) ) }, diff --git a/react-ui/src/index.tsx b/react-ui/src/index.tsx index 3697efd07379da0126d042b824b9c3d857bb6fed..559e1bc54369e597163c25c111fea8cd4d5bd8a7 100755 --- a/react-ui/src/index.tsx +++ b/react-ui/src/index.tsx @@ -1,4 +1,6 @@ +import { fetchSelectedMneThunk } from '@component/devices/routines/mne.routine' import { UtilsProvider } from '@provider/utils.provider' +import { RoutineHolderSingleton } from '@utils/routine-holder.singleton' import i18next from 'i18next' import React from 'react' import ReactDOM from 'react-dom/client' @@ -18,6 +20,12 @@ import { persistor, store } from './stores' window.env = window.location.hostname === 'localhost' ? 'development' : 'production'; +const factory = RoutineHolderSingleton.getInstance(); +factory.registerRoutine("fetchSelectedMneThunk", { + func: fetchSelectedMneThunk, + id: 0 +}); + ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> <ErrorBoundary fallback={<div>Something went wrong</div>}> diff --git a/react-ui/src/shared/api/user.fetch.ts b/react-ui/src/shared/api/user.fetch.ts index 1b6dd344ed79136fc2bd22fa168a6e2befe199a6..08806783b867714aae8f7804081dc302c184f238 100644 --- a/react-ui/src/shared/api/user.fetch.ts +++ b/react-ui/src/shared/api/user.fetch.ts @@ -16,7 +16,7 @@ export const fetchUser = createAsyncThunk('user/fetchUser', (_, thunkAPI) => { const matchedUser = response.data.user.find((_user) => _user.name === user.username) if (!matchedUser) { - // TODO proper error handling + // TODO proper error handling => logout throw new Error('No user found with the provided username') } diff --git a/react-ui/src/shared/reducer/routine.reducer.ts b/react-ui/src/shared/reducer/routine.reducer.ts index 873d7d6a21500aed1bdd9c8abbd3eeb2992bed5d..f8c8c31ebca79c89056a787c428998cb4e21bcd1 100755 --- a/react-ui/src/shared/reducer/routine.reducer.ts +++ b/react-ui/src/shared/reducer/routine.reducer.ts @@ -1,13 +1,15 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit' import { CategoryType } from '@shared/types/category.type' -import { ThunkEntityDTO } from '@shared/types/thunk.type' +import { ThunkDTO, ThunkPersist } from '@shared/types/thunk.type' +import { RoutineHolderSingleton } from '@utils/routine-holder.singleton' import { RoutineManager } from '@utils/routine.manager' +import { REHYDRATE } from 'redux-persist' import { RootState } from '../../stores' import { startListening } from '../../stores/middleware/listener.middleware' import { setToken } from './user.reducer' export interface ReducerState { - thunks: Record<CategoryType, ThunkEntityDTO | null> + thunks: Record<CategoryType, ThunkPersist | null> } const initialState: ReducerState = { @@ -23,9 +25,14 @@ const RoutineSlice = createSlice({ name: 'routine', initialState, reducers: { - addRoutine: (state: any, { payload }: PayloadAction<ThunkEntityDTO>) => { - const newThunk: ThunkEntityDTO = payload - state.thunks[payload.category] = newThunk + addRoutine: (state: any, { payload }: PayloadAction<ThunkDTO>) => { + const thunk: ThunkPersist = { + category: payload.category, + payload: payload.payload, + thunkId: payload.thunk.id + } + + state.thunks[payload.category] = thunk }, removeAll: (state) => { @@ -48,19 +55,26 @@ startListening({ // on rehydrate add all persistet routines // TODO -> thunk does not have the thunk function object due to its coming from the store that ignores the value. // at this point we have to figure out how to get the thunk function out of the "string" name -// startListening({ -// predicate: ({ type }) => type === REHYDRATE, -// effect: async (_, listenerApi) => { -// const { routine } = listenerApi.getState() as RootState -// for (const [_, thunk] of Object.entries<ThunkEntity>(routine.thunks)) { -// if (!thunk) { -// continue -// } -// const dto: ThunkEntityDTO = thunk -// listenerApi.dispatch(addRoutine(dto)) -// } -// }, -// }) +startListening({ + predicate: ({ type }) => type === REHYDRATE, + effect: async (_, listenerApi) => { + const { routine } = listenerApi.getState() as RootState + const routines = RoutineHolderSingleton.getInstance() + + Object.values(routine.thunks) + .filter(thunk => !!thunk) + .forEach(thunk => { + const container = routines.getRoutineById(thunk.thunkId) + const dto: ThunkDTO = { + category: thunk.category, + payload: thunk.payload, + thunk: container + } + + listenerApi.dispatch(addRoutine(dto)) + }) + }, +}) /** * Add new routine @@ -72,8 +86,8 @@ startListening({ startListening({ predicate: (action) => addRoutine.match(action), effect: async (action, listenerApi) => { - const { thunk } = action.payload as ThunkEntityDTO - const subscription = await listenerApi.dispatch(thunk(action.payload.payload)) + const { thunk } = action.payload as ThunkDTO + const subscription = await listenerApi.dispatch(thunk.func(action.payload.payload)) RoutineManager.add(subscription.payload, action.payload.category) }, }) diff --git a/react-ui/src/shared/types/thunk.type.ts b/react-ui/src/shared/types/thunk.type.ts index ef0a92b8774f79fab8fd3a33937d50fa07954ee1..90b8460393f308fe99177a97b6333f9f0d3e6cf6 100644 --- a/react-ui/src/shared/types/thunk.type.ts +++ b/react-ui/src/shared/types/thunk.type.ts @@ -1,7 +1,21 @@ import { CategoryType } from "./category.type" -export interface ThunkEntityDTO { - thunk: any + +/** + * Contains the thunk function combined with a unique id + * Giving a explicit id (and not the index of the object) + * prevents missmatching the function if a update changes + * the RoutineList object length + */ +export interface ThunkContainer { + id: number + func: any, +} + + +export interface ThunkDTO { + thunk: ThunkContainer + payload: Object /** @@ -9,4 +23,10 @@ export interface ThunkEntityDTO { * New subscription will unsubscribe and overwrite the old one */ category: CategoryType -} \ No newline at end of file +} + +export interface ThunkPersist { + thunkId: number, + payload: Object + category: CategoryType +} diff --git a/react-ui/src/shared/utils/routine-holder.singleton.ts b/react-ui/src/shared/utils/routine-holder.singleton.ts new file mode 100644 index 0000000000000000000000000000000000000000..47332ab0bce45ea7660298095f541c4f40c34043 --- /dev/null +++ b/react-ui/src/shared/utils/routine-holder.singleton.ts @@ -0,0 +1,47 @@ +import { ThunkContainer } from "@shared/types/thunk.type"; + +interface LocalThunkContainer { + container: ThunkContainer, + name: string +} + +export class RoutineHolderSingleton { + private static instance: RoutineHolderSingleton; + private routineList: Array<LocalThunkContainer> = [] + + private constructor() { } + + static getInstance(): RoutineHolderSingleton { + if (!RoutineHolderSingleton.instance) { + RoutineHolderSingleton.instance = new RoutineHolderSingleton(); + } + return RoutineHolderSingleton.instance; + } + + registerRoutine(name: string, thunk: ThunkContainer) { + this.routineList = [...this.routineList, { container: thunk, name }]; + } + + getRoutineById(id: number): ThunkContainer { + const routine = this.routineList.find((thunk) => thunk.container.id === id) + + if (!routine) { + throw new Error('') + // TODO + } + + return routine.container; + } + + + getRoutineByName(name: string): ThunkContainer { + const routine = this.routineList.find((thunk) => thunk.name === name) + + if (!routine) { + throw new Error('') + // TODO + } + + return routine.container; + } +} \ No newline at end of file diff --git a/react-ui/src/shared/utils/routine.manager.ts b/react-ui/src/shared/utils/routine.manager.ts index 427043e24671fa32935ccf4d4728cc4859132752..1fee6723e7f36531f330160aadfc77d60ebfce82 100755 --- a/react-ui/src/shared/utils/routine.manager.ts +++ b/react-ui/src/shared/utils/routine.manager.ts @@ -38,6 +38,8 @@ export const RoutineManager = (() => { ...state.routines, [category]: entity } + infoMessage("Routine subscribed to category " + category) + return true }