Skip to content
Snippets Groups Projects
Commit 7dfa8398 authored by Matthias Feyll's avatar Matthias Feyll
Browse files

(ui): reduced complexity of rehydration process

parent 26b78691
No related branches found
No related tags found
4 merge requests!1196[renovate] Update module golang.org/x/net to v0.32.0,!1195UI: implement add device functionality,!1167Ui refactor style,!1161Ui refactor style
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
} from '@api/api' } from '@api/api'
import { DeviceViewTabValues } from '@component/devices/view/device.view.tabs' import { DeviceViewTabValues } from '@component/devices/view/device.view.tabs'
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { REHYDRATE } from 'redux-persist'
import { RootState } from 'src/stores' import { RootState } from 'src/stores'
import '../routines/index' import '../routines/index'
import { startListening } from '/src/stores/middleware/listener.middleware' import { startListening } from '/src/stores/middleware/listener.middleware'
...@@ -32,6 +33,13 @@ const initialState: DeviceSliceState = { ...@@ -32,6 +33,13 @@ const initialState: DeviceSliceState = {
selected: null, selected: null,
} }
interface SetSelectedDeviceType {
device: Device | null,
options?: {
bypassCheck: boolean
}
}
const deviceSlice = createSlice({ const deviceSlice = createSlice({
name: 'device', name: 'device',
initialState, initialState,
...@@ -46,24 +54,33 @@ const deviceSlice = createSlice({ ...@@ -46,24 +54,33 @@ const deviceSlice = createSlice({
state.activeTab = action.payload state.activeTab = action.payload
}, },
setSelectedDevice: { setSelectedDevice: {
reducer: (state, action: PayloadAction<Device | null, string, { skipListener?: boolean }>) => { reducer: (state, { payload, meta }: PayloadAction<SetSelectedDeviceType, string, { skipListener?: boolean }>) => {
// do thing if desired device is already selected /**
if (state.selected?.device.id === action.payload?.id) { * Do nothing if desired device is already selected
action.meta.skipListener = true * Bypass the check if the flag is set to true. We
* use this bypass to trigger the listener functions
* accordingly
*/
if (!payload?.options?.bypassCheck && state.selected?.device.id === payload.device?.id) {
meta.skipListener = true
return return
} }
let selectedObject = null; if (!payload.device) {
if (action.payload) { throw Error('Passed null device as parameter while bypassing the safety check')
selectedObject = { device: action.payload, mne: null, json: null } }
let selectedObject: SelectedObject | null = null;
if (payload) {
selectedObject = { device: payload.device, mne: null, json: null }
} }
state.selected = selectedObject state.selected = selectedObject
}, },
prepare: (device: Device | null) => { prepare: (payload) => {
return { return {
payload: device, payload,
meta: { skipListener: false } // set to true when needed meta: { skipListener: false }
} }
} }
}, },
...@@ -107,7 +124,24 @@ startListening({ ...@@ -107,7 +124,24 @@ startListening({
} }
// if there are no devices available do set null // if there are no devices available do set null
const newDevices = action.payload?.[0] || null const device = action.payload?.[0] || null
listenerApi.dispatch(setSelectedDevice(newDevices)) listenerApi.dispatch(setSelectedDevice({ device } as SetSelectedDeviceType))
}, },
}) })
/**
* On startup reset the selected device
*/
startListening({
predicate: ({ type }: any) => type === REHYDRATE,
effect: async (_, listenerApi) => {
const { device: state } = listenerApi.getState() as RootState
const device = state.selected?.device
if (!device) {
return
}
listenerApi.dispatch(setSelectedDevice({ device, options: { bypassCheck: true } } as SetSelectedDeviceType))
},
})
\ No newline at end of file
...@@ -20,7 +20,7 @@ export const fetchDevicesThunk = createAsyncThunk(FETCH_DEVICE_ACTION, (_, thunk ...@@ -20,7 +20,7 @@ export const fetchDevicesThunk = createAsyncThunk(FETCH_DEVICE_ACTION, (_, thunk
const { user } = thunkApi.getState() as RootState const { user } = thunkApi.getState() as RootState
if (!user.user?.roles) { if (!user.user?.roles) {
throw new Error('Background MNE fetching failed! User data is missing. Reload page or logout and login again') throw new Error('Background device fetching failed! User data is missing. Reload page or logout and login again')
// TODO // TODO
} }
......
...@@ -8,12 +8,12 @@ import { ...@@ -8,12 +8,12 @@ import {
import { createAsyncThunk } from '@reduxjs/toolkit' import { createAsyncThunk } from '@reduxjs/toolkit'
import { addRoutine } from '@shared/reducer/routine.reducer' import { addRoutine } from '@shared/reducer/routine.reducer'
import { Category, CategoryType } 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 { RootState } from 'src/stores'
import { startListening } from '../../../stores/middleware/listener.middleware' import { startListening } from '../../../stores/middleware/listener.middleware'
export const FETCH_MNE_ACTION = 'subscription/device/fetchSelectedMNE' export const FETCH_MNE_ACTION = 'subscription/device/fetchSelectedMNE'
/** /**
* #0 * #0
* Trigger fetch MNE (#1) * Trigger fetch MNE (#1)
...@@ -21,14 +21,16 @@ export const FETCH_MNE_ACTION = 'subscription/device/fetchSelectedMNE' ...@@ -21,14 +21,16 @@ export const FETCH_MNE_ACTION = 'subscription/device/fetchSelectedMNE'
* Triggered by a selectedDevice * Triggered by a selectedDevice
*/ */
startListening({ startListening({
predicate: (action) => setSelectedDevice.match(action) && !!action.payload && !action.meta?.skipListener, predicate: (action) => setSelectedDevice.match(action) && !!action.payload.device && !action.meta?.skipListener,
effect: async (action, listenerApi) => { effect: async (action, listenerApi) => {
const factory = RoutineHolderSingleton.getInstance();
const device = action.payload.device
listenerApi.dispatch( listenerApi.dispatch(
addRoutine({ addRoutine({
thunk: factory.getRoutineByName("fetchSelectedMneThunk"), thunk: fetchSelectedMneThunk,
category: Category.TAB as CategoryType, category: Category.TAB as CategoryType,
payload: action.payload as Object, payload: device,
}) })
) )
}, },
......
...@@ -26,7 +26,7 @@ export const useDeviceTableViewModel = (searchRef) => { ...@@ -26,7 +26,7 @@ export const useDeviceTableViewModel = (searchRef) => {
}, []); }, []);
const trClickHandler = (device: Device) => { const trClickHandler = (device: Device) => {
dispatch(setSelectedDevice(device)); dispatch(setSelectedDevice({ device }));
} }
......
import { fetchSelectedMneThunk } from '@component/devices/routines/mne.routine'
import { UtilsProvider } from '@provider/utils.provider' import { UtilsProvider } from '@provider/utils.provider'
import { RoutineHolderSingleton } from '@utils/routine-holder.singleton'
import i18next from 'i18next' import i18next from 'i18next'
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
...@@ -20,12 +18,6 @@ import { persistor, store } from './stores' ...@@ -20,12 +18,6 @@ import { persistor, store } from './stores'
window.env = window.location.hostname === 'localhost' ? 'development' : 'production'; 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( ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode> <React.StrictMode>
<ErrorBoundary fallback={<div>Something went wrong</div>}> <ErrorBoundary fallback={<div>Something went wrong</div>}>
......
import { PayloadAction, createSlice } from '@reduxjs/toolkit' import { PayloadAction, createSlice } from '@reduxjs/toolkit'
import { CategoryType } from '@shared/types/category.type' import { CategoryType } from '@shared/types/category.type'
import { ThunkDTO, ThunkPersist } 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 { RoutineManager } from '@utils/routine.manager'
import { REHYDRATE } from 'redux-persist'
import { RootState } from '../../stores' import { RootState } from '../../stores'
import { startListening } from '../../stores/middleware/listener.middleware' import { startListening } from '../../stores/middleware/listener.middleware'
import { setToken } from './user.reducer' import { setToken } from './user.reducer'
...@@ -52,30 +50,6 @@ startListening({ ...@@ -52,30 +50,6 @@ 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
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 * Add new routine
* *
...@@ -87,12 +61,17 @@ startListening({ ...@@ -87,12 +61,17 @@ startListening({
predicate: (action) => addRoutine.match(action), predicate: (action) => addRoutine.match(action),
effect: async (action, listenerApi) => { effect: async (action, listenerApi) => {
const { thunk } = action.payload as ThunkDTO const { thunk } = action.payload as ThunkDTO
const subscription = await listenerApi.dispatch(thunk.func(action.payload.payload)) const subscription = await listenerApi.dispatch(thunk(action.payload.payload))
if (subscription.error) {
throw new Error('Error during routine execution: ' + subscription.error.message)
}
RoutineManager.add(subscription.payload, action.payload.category) RoutineManager.add(subscription.payload, action.payload.category)
}, },
}) })
// unsubscribe old routine // unsubscribe old routine that is in the same category
startListening({ startListening({
predicate: (action) => addRoutine.match(action), predicate: (action) => addRoutine.match(action),
effect: async (action, listenerApi) => { effect: async (action, listenerApi) => {
......
import { CategoryType } from "./category.type" import { CategoryType } from "./category.type"
/** // The actual Thunk type is hard to determine because is very generic
* Contains the thunk function combined with a unique id export type ThunkFunc = any
* 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 { export interface ThunkDTO {
thunk: ThunkContainer thunk: ThunkFunc
payload: Object payload: Object
/** /**
......
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment