diff --git a/frontend/src/hooks/useManagedViewSet.ts b/frontend/src/hooks/useManagedViewSet.ts index 8462a8f..e935bcd 100644 --- a/frontend/src/hooks/useManagedViewSet.ts +++ b/frontend/src/hooks/useManagedViewSet.ts @@ -1,18 +1,24 @@ import * as React from "react" import {useEffect, useMemo, useReducer} from "react" -import {useViewSet} from "./useViewSet"; -import {DjangoResource} from "../types/DjangoTypes"; -import {arrayExclude, arrayExtension} from "../utils/ArrayExtension"; +import {DjangoResource} from "../types/DjangoTypes" +import {arrayExclude, arrayExtension} from "../utils/ArrayExtension" +import {useViewSet} from "./useViewSet" + +// Function types + +type ManagedRefresh = () => Promise +type ManagedCreate = (data: Partial) => Promise +type ManagedCommand = (command: string, data: any) => Promise +type ManagedUpdate = (index: number, data: Partial) => Promise +type ManagedDestroy = (index: number) => Promise +type ManagedAction = (index: number, act: string, data: any) => Promise + +type ManagedUpdateDetails = (data: Partial) => Promise +type ManagedDestroyDetails = () => Promise +type ManagedActionDetails = (act: string, data: any) => Promise -export type ManagedRefresh = () => Promise -export type ManagedCreate = (data: Partial) => Promise -export type ManagedUpdate = (index: number, data: Partial) => Promise -export type ManagedDestroy = (index: number) => Promise - -export type ManagedUpdateDetails = (data: Partial) => Promise -export type ManagedDestroyDetails = () => Promise - +// Public interfaces export interface ManagedViewSet { busy: boolean, @@ -23,6 +29,7 @@ export interface ManagedViewSet { refresh: ManagedRefresh, create: ManagedCreate, + command: ManagedCommand, } @@ -30,17 +37,21 @@ export interface ManagedResource { value: Resource, busy: boolean, error: Error | null, + update: ManagedUpdateDetails destroy: ManagedDestroyDetails + action: ManagedActionDetails, } -export interface ManagedState { +// Reducer state + +export interface ManagedReducerState { firstRun: boolean, busy: boolean, error: Error | null, - + operationError: Error | null, resources: Resource[] | null, @@ -49,13 +60,14 @@ export interface ManagedState { } -export interface ManagedAction { - type: `${"refresh" | "create" | "update" | "destroy"}.${"start" | "success" | "error"}`, +export interface ManagedReducerAction { + type: `${"refresh" | "create" | "command" | "update" | "destroy" | "action"}.${"start" | "success" | "error"}`, value?: any, index?: number, } -function reducerManagedViewSet(state: ManagedState, action: ManagedAction): ManagedState { + +function reducerManagedViewSet(state: ManagedReducerState, action: ManagedReducerAction): ManagedReducerState { switch(action.type) { case "refresh.start": @@ -87,6 +99,30 @@ function reducerManagedViewSet(state: ManagedState, action: error: action.value, } + case "command.start": + return { + ...state, + busy: true, + } + + case "command.success": + return { + ...state, + busy: false, + error: null, + operationError: null, + resources: action.value, + resourceBusy: action.value.map(() => false), + resourceError: action.value.map(() => null), + } + + case "command.error": + return { + ...state, + busy: false, + operationError: action.value, + } + case "create.start": return { ...state, @@ -155,6 +191,31 @@ function reducerManagedViewSet(state: ManagedState, action: busy: false, operationError: action.value, } + + case "action.start": + return { + ...state, + operationError: null, + resourceBusy: arrayExtension(state.resourceBusy!, action.index!, true), + } + + case "action.success": + return { + ...state, + busy: false, + resources: arrayExtension(state.resources!, action.index!, action.value), + resourceBusy: arrayExtension(state.resourceBusy!, action.index!, false), + resourceError: arrayExtension(state.resourceError!, action.index!, null), + } + + case "action.error": + return { + ...state, + busy: true, + error: null, + resourceBusy: arrayExtension(state.resourceBusy!, action.index!, false), + resourceError: arrayExtension(state.resourceError!, action.index!, action.value), + } } } @@ -164,7 +225,7 @@ export function useManagedViewSet(baseRoute: st useViewSet(baseRoute) const [state, dispatch] = - useReducer, ManagedAction>>(reducerManagedViewSet, { + useReducer, ManagedReducerAction>>(reducerManagedViewSet, { firstRun: true, busy: false, error: null, @@ -174,7 +235,6 @@ export function useManagedViewSet(baseRoute: st resourceError: null, }) - const refresh: ManagedRefresh = React.useCallback( async () => { @@ -240,12 +300,51 @@ export function useManagedViewSet(baseRoute: st dispatch({ type: "create.success", - value: response + value: response, }) }, - [viewset, state, dispatch] + [viewset, state, dispatch], ) + const command: ManagedCommand = + React.useCallback( + async (command, data) => { + if(state.busy) { + console.error("Cannot run a command while the viewset is busy, ignoring...") + return + } + if(state.error) { + console.error("Cannot run a command while the viewset has an error, ignoring...") + return + } + + dispatch({ + type: "command.start", + }) + + let response: Resource[] + + try { + response = await viewset.command({url: `${baseRoute}${command}/`, data}) + } + + catch(err) { + dispatch({ + type: "command.error", + value: err, + }) + return + } + + dispatch({ + type: "command.success", + value: response, + }) + }, + [viewset, state, dispatch], + ) + + const update: ManagedUpdate = React.useCallback( async (index, data) => { @@ -337,7 +436,56 @@ export function useManagedViewSet(baseRoute: st index: index, }) }, - [viewset, state, dispatch, pkKey] + [viewset, state, dispatch, pkKey], + ) + + const action: ManagedAction = + React.useCallback( + async (index, act, data) => { + if(state.busy) { + console.error("Cannot run an action while the viewset is busy, ignoring...") + return + } + if(state.error) { + console.error("Cannot run an action while the viewset has an error, ignoring...") + return + } + + const request: Resource | undefined = state.resources![index] + if(request === undefined) { + console.error(`No resource with index ${index}, ignoring...`) + return + } + + const pk = request[pkKey] + + dispatch({ + type: "action.start", + index: index, + }) + + let response: Resource + + try { + response = await viewset.action({url: `${baseRoute}${pk}/${action}/`, data}) + } + + catch(err) { + dispatch({ + type: "action.error", + index: index, + value: err, + }) + return + } + + dispatch({ + type: "action.success", + index: index, + value: response, + }) + }, + [viewset, state, dispatch, pkKey], ) const resources: ManagedResource[] | null = @@ -355,6 +503,7 @@ export function useManagedViewSet(baseRoute: st error: state.resourceError![index], update: (data) => update(index, data), destroy: () => destroy(index), + action: (act, data) => action(index, act, data), } } ) @@ -383,5 +532,6 @@ export function useManagedViewSet(baseRoute: st resources, refresh, create, + command, } }