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
This commit is part of merge request !1161. Comments created here will be created in the context of that merge request.
......@@ -5,6 +5,7 @@ import {
} from '@api/api'
import { DeviceViewTabValues } from '@component/devices/view/device.view.tabs'
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { REHYDRATE } from 'redux-persist'
import { RootState } from 'src/stores'
import '../routines/index'
import { startListening } from '/src/stores/middleware/listener.middleware'
......@@ -32,6 +33,13 @@ const initialState: DeviceSliceState = {
selected: null,
}
interface SetSelectedDeviceType {
device: Device | null,
options?: {
bypassCheck: boolean
}
}
const deviceSlice = createSlice({
name: 'device',
initialState,
......@@ -46,24 +54,33 @@ const deviceSlice = createSlice({
state.activeTab = action.payload
},
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
reducer: (state, { payload, meta }: PayloadAction<SetSelectedDeviceType, string, { skipListener?: boolean }>) => {
/**
* Do nothing if desired device is already selected
* 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
}
let selectedObject = null;
if (action.payload) {
selectedObject = { device: action.payload, mne: null, json: null }
if (!payload.device) {
throw Error('Passed null device as parameter while bypassing the safety check')
}
let selectedObject: SelectedObject | null = null;
if (payload) {
selectedObject = { device: payload.device, mne: null, json: null }
}
state.selected = selectedObject
},
prepare: (device: Device | null) => {
prepare: (payload) => {
return {
payload: device,
meta: { skipListener: false } // set to true when needed
payload,
meta: { skipListener: false }
}
}
},
......@@ -107,7 +124,24 @@ startListening({
}
// if there are no devices available do set null
const newDevices = action.payload?.[0] || null
listenerApi.dispatch(setSelectedDevice(newDevices))
const device = action.payload?.[0] || null
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
const { user } = thunkApi.getState() as RootState
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
}
......
......@@ -8,12 +8,12 @@ import {
import { createAsyncThunk } from '@reduxjs/toolkit'
import { addRoutine } from '@shared/reducer/routine.reducer'
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'
export const FETCH_MNE_ACTION = 'subscription/device/fetchSelectedMNE'
/**
* #0
* Trigger fetch MNE (#1)
......@@ -21,14 +21,16 @@ export const FETCH_MNE_ACTION = 'subscription/device/fetchSelectedMNE'
* Triggered by a selectedDevice
*/
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) => {
const factory = RoutineHolderSingleton.getInstance();
const device = action.payload.device
listenerApi.dispatch(
addRoutine({
thunk: factory.getRoutineByName("fetchSelectedMneThunk"),
thunk: fetchSelectedMneThunk,
category: Category.TAB as CategoryType,
payload: action.payload as Object,
payload: device,
})
)
},
......
......@@ -26,7 +26,7 @@ export const useDeviceTableViewModel = (searchRef) => {
}, []);
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 { RoutineHolderSingleton } from '@utils/routine-holder.singleton'
import i18next from 'i18next'
import React from 'react'
import ReactDOM from 'react-dom/client'
......@@ -20,12 +18,6 @@ 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>}>
......
import { PayloadAction, createSlice } from '@reduxjs/toolkit'
import { CategoryType } from '@shared/types/category.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'
......@@ -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
*
......@@ -87,12 +61,17 @@ startListening({
predicate: (action) => addRoutine.match(action),
effect: async (action, listenerApi) => {
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)
},
})
// unsubscribe old routine
// unsubscribe old routine that is in the same category
startListening({
predicate: (action) => addRoutine.match(action),
effect: async (action, listenerApi) => {
......
import { CategoryType } from "./category.type"
/**
* 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,
}
// The actual Thunk type is hard to determine because is very generic
export type ThunkFunc = any
export interface ThunkDTO {
thunk: ThunkContainer
thunk: ThunkFunc
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