From 996af564faa8d657a11605ff22581fc29d8d2baf Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 9 Jul 2024 16:58:10 +0200 Subject: [PATCH] Add typing to async thunks Adds typescript to createAsyncThunk from redux toolkit. This properly types our state in those methods, should we need it. --- src/redux/createAsyncThunkWithTypes.ts | 14 ++++++++++++++ src/redux/metadataSlice.ts | 10 +++++----- src/redux/subtitleSlice.ts | 9 ++++----- src/redux/videoSlice.ts | 5 +++-- src/redux/workflowPostAndProcessSlice.ts | 5 +++-- src/redux/workflowPostSlice.ts | 5 +++-- 6 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 src/redux/createAsyncThunkWithTypes.ts diff --git a/src/redux/createAsyncThunkWithTypes.ts b/src/redux/createAsyncThunkWithTypes.ts new file mode 100644 index 000000000..cecaa5091 --- /dev/null +++ b/src/redux/createAsyncThunkWithTypes.ts @@ -0,0 +1,14 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { AppDispatch, RootState } from "./store"; + +/** + * Use instead of createAsyncThunk to provide basic typing to all async thunks + * + * Thematically this belongs in `store.ts`. However, this causes + * "Cannot access 'createAsyncThunk' before initialization", + * so this has to be moved into it's own file. + */ +export const createAppAsyncThunk = createAsyncThunk.withTypes<{ + state: RootState + dispatch: AppDispatch +}>(); diff --git a/src/redux/metadataSlice.ts b/src/redux/metadataSlice.ts index 4d7d27678..21b2e21f8 100644 --- a/src/redux/metadataSlice.ts +++ b/src/redux/metadataSlice.ts @@ -1,8 +1,9 @@ -import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { client } from "../util/client"; import { httpRequestState } from "../types"; import { settings } from "../config"; +import { createAppAsyncThunk } from "./createAsyncThunkWithTypes"; export interface Catalog { fields: MetadataField[], @@ -69,7 +70,7 @@ const initialState: metadata & httpRequestState & postRequestState = { postErrorReason: "unknown", }; -export const fetchMetadata = createAsyncThunk("metadata/fetchMetadata", async () => { +export const fetchMetadata = createAppAsyncThunk("metadata/fetchMetadata", async () => { if (!settings.id) { throw new Error("Missing media package identifier"); } @@ -78,13 +79,12 @@ export const fetchMetadata = createAsyncThunk("metadata/fetchMetadata", async () return JSON.parse(response); }); -export const postMetadata = createAsyncThunk("metadata/postMetadata", async (_, { getState }) => { +export const postMetadata = createAppAsyncThunk("metadata/postMetadata", async (_, { getState }) => { if (!settings.id) { throw new Error("Missing media package identifier"); } - // TODO: Get only metadataState instead of all states - const allStates = getState() as { metadataState: { catalogs: metadata["catalogs"]; }; }; + const allStates = getState(); await client.post(`${settings.opencast.url}/editor/${settings.id}/metadata.json`, allStates.metadataState.catalogs diff --git a/src/redux/subtitleSlice.ts b/src/redux/subtitleSlice.ts index 3fff70fc6..6c7bf580f 100644 --- a/src/redux/subtitleSlice.ts +++ b/src/redux/subtitleSlice.ts @@ -1,8 +1,7 @@ import { Segment, SubtitleCue, SubtitlesInEditor } from "./../types"; -import { createAsyncThunk, createSlice, nanoid, PayloadAction } from "@reduxjs/toolkit"; +import { createSlice, nanoid, PayloadAction } from "@reduxjs/toolkit"; import { roundToDecimalPlace } from "../util/utilityFunctions"; -import type { RootState } from "../redux/store"; -import { video } from "./videoSlice"; +import { createAppAsyncThunk } from "./createAsyncThunkWithTypes"; export interface subtitle { isDisplayEditView: boolean; // Should the edit view be displayed @@ -250,7 +249,7 @@ export const { * Will grab the state from videoState to skip past deleted segment if preview * mode is active. */ -export const setCurrentlyAtAndTriggerPreview = createAsyncThunk("subtitleState/setCurrentlyAtAndTriggerPreview", +export const setCurrentlyAtAndTriggerPreview = createAppAsyncThunk("subtitleState/setCurrentlyAtAndTriggerPreview", async (milliseconds: number, { getState, dispatch }) => { milliseconds = roundToDecimalPlace(milliseconds, 0); @@ -258,7 +257,7 @@ export const setCurrentlyAtAndTriggerPreview = createAsyncThunk("subtitleState/s milliseconds = 0; } - const allStates = getState() as { videoState: video, subtitleState: subtitle; }; + const allStates = getState(); const segments: Segment[] = allStates.videoState.segments; let triggered = false; diff --git a/src/redux/videoSlice.ts b/src/redux/videoSlice.ts index e8884c8eb..0f0a06954 100644 --- a/src/redux/videoSlice.ts +++ b/src/redux/videoSlice.ts @@ -1,10 +1,11 @@ import { clamp } from "lodash"; -import { createSlice, nanoid, createAsyncThunk, PayloadAction, createSelector } from "@reduxjs/toolkit"; +import { createSlice, nanoid, PayloadAction, createSelector } from "@reduxjs/toolkit"; import { client } from "../util/client"; import { Segment, httpRequestState, Track, Workflow, SubtitlesFromOpencast } from "../types"; import { roundToDecimalPlace } from "../util/utilityFunctions"; import { settings } from "../config"; +import { createAppAsyncThunk } from "./createAsyncThunkWithTypes"; export interface video { isPlaying: boolean, // Are videos currently playing? @@ -81,7 +82,7 @@ export const initialState: video & httpRequestState = { errorReason: "unknown", }; -export const fetchVideoInformation = createAsyncThunk("video/fetchVideoInformation", async () => { +export const fetchVideoInformation = createAppAsyncThunk("video/fetchVideoInformation", async () => { if (!settings.id) { throw new Error("Missing media package identifier"); } diff --git a/src/redux/workflowPostAndProcessSlice.ts b/src/redux/workflowPostAndProcessSlice.ts index 92f352c60..8c6274f34 100644 --- a/src/redux/workflowPostAndProcessSlice.ts +++ b/src/redux/workflowPostAndProcessSlice.ts @@ -1,9 +1,10 @@ -import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; +import { createSlice } from "@reduxjs/toolkit"; import { client } from "../util/client"; import { PostAndProcessEditArgument, httpRequestState } from "../types"; import { convertSegments } from "./workflowPostSlice"; import { settings } from "../config"; +import { createAppAsyncThunk } from "./createAsyncThunkWithTypes"; const initialState: httpRequestState = { status: "idle", @@ -12,7 +13,7 @@ const initialState: httpRequestState = { }; export const postVideoInformationWithWorkflow = - createAsyncThunk("video/postVideoInformationWithWorkflow", async (argument: PostAndProcessEditArgument) => { + createAppAsyncThunk("video/postVideoInformationWithWorkflow", async (argument: PostAndProcessEditArgument) => { if (!settings.id) { throw new Error("Missing media package identifier"); } diff --git a/src/redux/workflowPostSlice.ts b/src/redux/workflowPostSlice.ts index a25f0ddcb..08120186c 100644 --- a/src/redux/workflowPostSlice.ts +++ b/src/redux/workflowPostSlice.ts @@ -1,7 +1,8 @@ -import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; +import { createSlice } from "@reduxjs/toolkit"; import { client } from "../util/client"; import { Segment, PostEditArgument, httpRequestState } from "../types"; import { settings } from "../config"; +import { createAppAsyncThunk } from "./createAsyncThunkWithTypes"; const initialState: httpRequestState = { status: "idle", @@ -10,7 +11,7 @@ const initialState: httpRequestState = { }; export const postVideoInformation = - createAsyncThunk("video/postVideoInformation", async (argument: PostEditArgument) => { + createAppAsyncThunk("video/postVideoInformation", async (argument: PostEditArgument) => { if (!settings.id) { throw new Error("Missing media package id"); }