diff --git a/package-lock.json b/package-lock.json index 077c1f8ab..574f2ed77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "deepmerge": "^4.3.1", "emotion-normalize": "^11.0.1", "final-form": "^4.20.10", - "i18next": "^23.16.0", + "i18next": "^24.0.5", "i18next-browser-languagedetector": "^8.0.0", "i18next-chained-backend": "^4.6.2", "i18next-resources-to-backend": "^1.2.1", @@ -3736,9 +3736,9 @@ } }, "node_modules/i18next": { - "version": "23.16.0", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.0.tgz", - "integrity": "sha512-Ni3CG6c14teOogY19YNRl+kYaE/Rb59khy0VyHVn4uOZ97E2E/Yziyi6r3C3s9+wacjdLZiq/LLYyx+Cgd+FCw==", + "version": "24.0.5", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.0.5.tgz", + "integrity": "sha512-1jSdEzgFPGLZRsQwydoMFCBBaV+PmrVEO5WhANllZPX4y2JSGTxUjJ+xVklHIsiS95uR8gYc/y0hYZWevucNjg==", "funding": [ { "type": "individual", @@ -3753,8 +3753,17 @@ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" } ], + "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/i18next-browser-languagedetector": { diff --git a/package.json b/package.json index f23fc461d..73edfc071 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "deepmerge": "^4.3.1", "emotion-normalize": "^11.0.1", "final-form": "^4.20.10", - "i18next": "^23.16.0", + "i18next": "^24.0.5", "i18next-browser-languagedetector": "^8.0.0", "i18next-chained-backend": "^4.6.2", "i18next-resources-to-backend": "^1.2.1", diff --git a/src/cssStyles.tsx b/src/cssStyles.tsx index b64b16b10..120ac9897 100644 --- a/src/cssStyles.tsx +++ b/src/cssStyles.tsx @@ -6,6 +6,9 @@ import React from "react"; import emotionNormalize from "emotion-normalize"; import { createTheme } from "@mui/material/styles"; import { Theme, useTheme } from "./themes"; +import { + DEFAULT_CONFIG as APPKIT_CONFIG, +} from "@opencast/appkit"; import { StylesConfig } from "react-select"; /** @@ -35,8 +38,8 @@ export const globalStyle = (theme: Theme) => css({ // When to switch behaviour based on screen width -export const BREAKPOINT_SMALL = 450; -export const BREAKPOINT_MEDIUM = 650; +/** Breakpoint values */ +export const BREAKPOINTS = APPKIT_CONFIG.breakpoints; /** * CSS for buttons @@ -438,3 +441,9 @@ export const checkboxStyle = (theme: Theme) => css({ color: theme.text, "&.Mui-disabled": { color: theme.disabled }, }); + +export const undisplay = (maxWidth: number) => css({ + [`@media (max-width: ${maxWidth}px)`]: { + display: "none", + }, +}); diff --git a/src/img/opencast-editor-narrow.svg b/src/img/opencast-editor-narrow.svg new file mode 100644 index 000000000..c4a3c598e --- /dev/null +++ b/src/img/opencast-editor-narrow.svg @@ -0,0 +1,55 @@ + + + diff --git a/src/main/CuttingActions.tsx b/src/main/CuttingActions.tsx index b2612d70b..f65977aa5 100644 --- a/src/main/CuttingActions.tsx +++ b/src/main/CuttingActions.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { basicButtonStyle, customIconStyle } from "../cssStyles"; +import { BREAKPOINTS, basicButtonStyle, customIconStyle, undisplay } from "../cssStyles"; import { IconType } from "react-icons"; import { LuScissors, LuChevronLeft, LuChevronRight, LuTrash, LuMoveHorizontal } from "react-icons/lu"; @@ -110,6 +110,8 @@ const CuttingActions: React.FC = () => { flexDirection: "row" as const, justifyContent: "center", alignItems: "center", + + flexWrap: "wrap", }); const verticalLineStyle = css({ @@ -246,7 +248,7 @@ const CuttingActionsButton: React.FC = ({ }} > - {actionName} + {actionName} ); @@ -291,8 +293,10 @@ const MarkAsDeletedButton: React.FC = ({ } }} > - {isCurrentSegmentAlive ? : } -
{isCurrentSegmentAlive ? t("cuttingActions.delete-button") : t("cuttingActions.restore-button")}
+ {isCurrentSegmentAlive ? : } + + {isCurrentSegmentAlive ? t("cuttingActions.delete-button") : t("cuttingActions.restore-button")} + ); diff --git a/src/main/Discard.tsx b/src/main/Discard.tsx index 168f59796..93650348d 100644 --- a/src/main/Discard.tsx +++ b/src/main/Discard.tsx @@ -5,8 +5,7 @@ import { basicButtonStyle, backOrContinueStyle, navigationButtonStyle } from ".. import { LuChevronLeft, LuXCircle } from "react-icons/lu"; -import { useAppDispatch, useAppSelector } from "../redux/store"; -import { selectFinishState } from "../redux/finishSlice"; +import { useAppDispatch } from "../redux/store"; import { setEnd } from "../redux/endSlice"; import { PageButton } from "./Finish"; @@ -22,10 +21,8 @@ const Discard: React.FC = () => { const { t } = useTranslation(); - const finishState = useAppSelector(selectFinishState); - const cancelStyle = css({ - display: finishState !== "Discard changes" ? "none" : "flex", + display: "flex", flexDirection: "column" as const, alignItems: "center", gap: "30px", diff --git a/src/main/Finish.tsx b/src/main/Finish.tsx index 2905ad76a..0f93dd44d 100644 --- a/src/main/Finish.tsx +++ b/src/main/Finish.tsx @@ -14,7 +14,7 @@ import { basicButtonStyle, navigationButtonStyle } from "../cssStyles"; import { IconType } from "react-icons"; import { useAppDispatch, useAppSelector } from "../redux/store"; -import { selectPageNumber, setPageNumber } from "../redux/finishSlice"; +import { selectFinishState, selectPageNumber, setPageNumber } from "../redux/finishSlice"; import { useTheme } from "../themes"; import { settings } from "../config"; import { useTranslation } from "react-i18next"; @@ -25,33 +25,36 @@ import { useTranslation } from "react-i18next"; const Finish: React.FC = () => { const pageNumber = useAppSelector(selectPageNumber); + const finishState = useAppSelector(selectFinishState); - const pageZeroStyle = css({ - display: pageNumber !== 0 ? "none" : "block", - }); - - const pageOneStyle = css({ - display: pageNumber !== 1 ? "none" : "block", - }); - - const pageTwoStyle = css({ - display: pageNumber !== 2 ? "none" : "block", - }); - - return ( -
-
+ const render = () => { + if (pageNumber === 0) { + return ( -
-
- - - -
-
+ ); + } else if (pageNumber === 1) { + if (finishState === "Save changes") { + return ( + + ); + } else if (finishState === "Start processing") { + return ( + + ); + } else if (finishState === "Discard changes") { + return ( + + ); + } + } else if (pageNumber === 2) { + return ( -
-
+ ); + } + }; + + return ( + <>{render()} ); }; diff --git a/src/main/Header.tsx b/src/main/Header.tsx index f717fc034..0035c07be 100644 --- a/src/main/Header.tsx +++ b/src/main/Header.tsx @@ -10,10 +10,17 @@ import { LuMoon, LuSun } from "react-icons/lu"; import { HiOutlineTranslate } from "react-icons/hi"; import { LuKeyboard } from "react-icons/lu"; import { MainMenuStateNames } from "../types"; -import { basicButtonStyle, BREAKPOINT_MEDIUM, BREAKPOINT_SMALL } from "../cssStyles"; +import { basicButtonStyle, BREAKPOINTS, undisplay } from "../cssStyles"; import { selectIsEnd } from "../redux/endSlice"; -import { checkboxMenuItem, HeaderMenuItemDef, ProtoButton, useColorScheme, WithHeaderMenu } from "@opencast/appkit"; +import { + checkboxMenuItem, + HeaderMenuItemDef, + ProtoButton, + screenWidthAtMost, + useColorScheme, + WithHeaderMenu, +} from "@opencast/appkit"; import { IconType } from "react-icons"; import i18next from "i18next"; import { languages as lngs } from "../i18n/lngs-generated"; @@ -68,6 +75,10 @@ function Header() { backgroundColor: theme.header_button_hover_bg, color: `${theme.header_text}`, }, + + [screenWidthAtMost(BREAKPOINTS.medium)]: { + fontSize: 0, + }, }); return ( @@ -184,7 +195,7 @@ const LanguageButton: React.FC = () => { menu={{ label, items: menuItems, - breakpoint: BREAKPOINT_SMALL, + breakpoint: BREAKPOINTS.small, }} > @@ -210,7 +221,7 @@ const ThemeButton: React.FC = () => { menu={{ label: t("theme.appearance"), items: menuItems, - breakpoint: BREAKPOINT_MEDIUM, + breakpoint: BREAKPOINTS.medium, }}> ( css={[basicButtonStyle(theme), themeSelectorButtonStyle]} > - {label} + {label} ); }); diff --git a/src/main/MainMenu.tsx b/src/main/MainMenu.tsx index f2c5cc99f..1082ad160 100644 --- a/src/main/MainMenu.tsx +++ b/src/main/MainMenu.tsx @@ -13,7 +13,7 @@ import { setPageNumber } from "../redux/finishSlice"; import { MainMenuStateNames } from "../types"; import { settings } from "../config"; -import { basicButtonStyle } from "../cssStyles"; +import { basicButtonStyle, BREAKPOINTS } from "../cssStyles"; import { setIsPlaying } from "../redux/videoSlice"; import { useTranslation } from "react-i18next"; @@ -21,6 +21,7 @@ import { resetPostRequestState } from "../redux/workflowPostSlice"; import { setIsDisplayEditView } from "../redux/subtitleSlice"; import { useTheme } from "../themes"; +import { screenWidthAtMost } from "@opencast/appkit"; /** * A container for selecting the functionality shown in the main part of the app @@ -42,6 +43,10 @@ const MainMenu: React.FC = () => { overflowY: "auto", background: `${theme.menu_background}`, gap: "30px", + [screenWidthAtMost(BREAKPOINTS.large)]: { + minWidth: "60px", + padding: "20px 10px", + }, }); return ( @@ -142,6 +147,10 @@ export const MainMenuButton: React.FC = ({ boxShadow: `${theme.boxShadow}`, }, flexDirection: "column", + [screenWidthAtMost(BREAKPOINTS.large)]: { + height: "60px", + minHeight: "40px", + }, }); return ( @@ -159,8 +168,15 @@ export const MainMenuButton: React.FC = ({ fontSize: 36, width: "36px", height: "auto", - }} /> - {bottomText &&
{bottomText}
} + }}/> + {bottomText && +
+ {bottomText} +
} ); }; diff --git a/src/main/Metadata.tsx b/src/main/Metadata.tsx index e1ced778d..d9811ab6a 100644 --- a/src/main/Metadata.tsx +++ b/src/main/Metadata.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; import { css } from "@emotion/react"; -import { calendarStyle, errorBoxStyle, selectFieldStyle, titleStyle, titleStyleBold } from "../cssStyles"; +import { BREAKPOINTS, calendarStyle, errorBoxStyle, selectFieldStyle, titleStyle, titleStyleBold } from "../cssStyles"; import { useAppDispatch, useAppSelector } from "../redux/store"; import { @@ -27,6 +27,7 @@ import { useTheme } from "../themes"; import { ThemeProvider } from "@mui/material/styles"; import { cloneDeep } from "lodash"; import { ParseKeys } from "i18next"; +import { screenWidthAtMost } from "@opencast/appkit"; /** * Creates a Metadata form @@ -96,6 +97,12 @@ const Metadata: React.FC = () => { marginRight: "auto", minWidth: "50%", display: "grid", + [screenWidthAtMost(1550)]: { + minWidth: "70%", + }, + [screenWidthAtMost(BREAKPOINTS.medium)]: { + minWidth: "90%", + }, }); const catalogStyle = css({ diff --git a/src/main/Save.tsx b/src/main/Save.tsx index 3c7dac2fb..aeeb10181 100644 --- a/src/main/Save.tsx +++ b/src/main/Save.tsx @@ -9,7 +9,6 @@ import { import { LuLoader, LuCheckCircle, LuAlertCircle, LuChevronLeft, LuSave, LuCheck } from "react-icons/lu"; import { useAppDispatch, useAppSelector } from "../redux/store"; -import { selectFinishState } from "../redux/finishSlice"; import { selectCustomizedTrackSelection, selectHasChanges, @@ -34,6 +33,8 @@ import { import { serializeSubtitle } from "../util/utilityFunctions"; import { useTheme } from "../themes"; import { ThemedTooltip } from "./Tooltip"; +import { IconType } from "react-icons"; +import { setEnd } from "../redux/endSlice"; /** * Shown if the user wishes to save. @@ -43,8 +44,6 @@ const Save: React.FC = () => { const { t } = useTranslation(); - const finishState = useAppSelector(selectFinishState); - const postWorkflowStatus = useAppSelector(selectStatus); const postError = useAppSelector(selectError); const theme = useTheme(); @@ -53,9 +52,9 @@ const Save: React.FC = () => { const subtitleHasChanges = useAppSelector(selectSubtitleHasChanges); const saveStyle = css({ - height: "100%", - display: finishState !== "Save changes" ? "none" : "flex", - flexDirection: "column" as const, + display: "flex", + flexDirection: "column", + justifyContent: "center", alignItems: "center", gap: "30px", }); @@ -102,8 +101,15 @@ const Save: React.FC = () => { /** * Button that sends a post request to save current changes */ -export const SaveButton: React.FC = () => { - +export const SaveButton: React.FC<{ + basicIcon?: IconType + text?: string + isTransitionToEnd?: boolean +}> = ({ + basicIcon = LuSave, + text, + isTransitionToEnd = false, +}) => { const { t } = useTranslation(); // Initialize redux variables @@ -118,7 +124,7 @@ export const SaveButton: React.FC = () => { const theme = useTheme(); // Update based on current fetching status - let Icon = LuSave; + let Icon = basicIcon; let spin = false; let tooltip = null; if (workflowStatus === "failed") { @@ -167,6 +173,9 @@ export const SaveButton: React.FC = () => { // Let users leave the page without warning after a successful save useEffect(() => { if (workflowStatus === "success") { + if (isTransitionToEnd) { + dispatch(setEnd({ hasEnded: true, value: "success" })); + } dispatch(videoSetHasChanges(false)); dispatch(metadataSetHasChanges(false)); dispatch(subtitleSetHasChanges(false)); @@ -184,7 +193,7 @@ export const SaveButton: React.FC = () => { } }}> - {t("save.confirm-button")} + {text ?? t("save.confirm-button")}
{ariaSaveUpdate()}
diff --git a/src/main/VideoControls.tsx b/src/main/VideoControls.tsx index c5c2c70ec..a66d75f2f 100644 --- a/src/main/VideoControls.tsx +++ b/src/main/VideoControls.tsx @@ -11,7 +11,7 @@ import { } from "../redux/videoSlice"; import { convertMsToReadableString } from "../util/utilityFunctions"; -import { basicButtonStyle } from "../cssStyles"; +import { BREAKPOINTS, basicButtonStyle, undisplay } from "../cssStyles"; import { KEYMAP, rewriteKeys } from "../globalKeys"; import { useTranslation } from "react-i18next"; @@ -175,8 +175,9 @@ const PreviewMode: React.FC<{ if (event.key === " ") { switchPlayPreview(undefined); } - }}> -
+ }} + > +
{t("video.previewButton")}
{isPlayPreview ? @@ -360,9 +361,9 @@ const TimeDisplay: React.FC<{ {new Date((currentlyAt ? currentlyAt : 0)).toISOString().substr(11, 10)} - {" / "} +
{" / "}
-
{new Date((duration ? duration : 0)).toISOString().substr(11, 10)}
diff --git a/src/main/WorkflowConfiguration.tsx b/src/main/WorkflowConfiguration.tsx index 521f18305..1f67c33de 100644 --- a/src/main/WorkflowConfiguration.tsx +++ b/src/main/WorkflowConfiguration.tsx @@ -1,39 +1,17 @@ -import React, { useEffect } from "react"; +import React from "react"; import { css } from "@emotion/react"; import { - basicButtonStyle, backOrContinueStyle, errorBoxStyle, - spinningStyle, } from "../cssStyles"; - -import { LuLoader, LuCheck, LuAlertCircle, LuChevronLeft, LuDatabase, LuMoreHorizontal } from "react-icons/lu"; - -import { useAppDispatch, useAppSelector } from "../redux/store"; -import { - selectCustomizedTrackSelection, - selectSegments, - selectTracks, - setHasChanges as videoSetHasChanges, - selectSelectedWorkflowId, -} from "../redux/videoSlice"; -import { postVideoInformationWithWorkflow, selectStatus, selectError } from "../redux/workflowPostAndProcessSlice"; - +import { LuChevronLeft, LuDatabase, LuMoreHorizontal } from "react-icons/lu"; +import { useAppSelector } from "../redux/store"; import { PageButton } from "./Finish"; -import { setEnd } from "../redux/endSlice"; - import { useTranslation } from "react-i18next"; -import { - setHasChanges as metadataSetHasChanges, - selectCatalogs, -} from "../redux/metadataSlice"; -import { - selectSubtitles, - setHasChanges as subtitleSetHasChanges, -} from "../redux/subtitleSlice"; -import { serializeSubtitle } from "../util/utilityFunctions"; import { useTheme } from "../themes"; +import { selectError, selectStatus } from "../redux/workflowPostSlice"; +import { SaveButton } from "./Save"; /** * Will eventually display settings based on the selected workflow index @@ -62,7 +40,11 @@ const WorkflowConfiguration: React.FC = () => {
{t("workflowConfig.satisfied-text")}
- +
{t("various.error-text")}
@@ -74,92 +56,4 @@ const WorkflowConfiguration: React.FC = () => { ); }; -/** - * Button that sends a post request to save current changes - * and starts the selected workflow - */ -export const SaveAndProcessButton: React.FC<{ text: string; }> = ({ text }) => { - - // Initialize redux variables - const dispatch = useAppDispatch(); - - const selectedWorkflowId = useAppSelector(selectSelectedWorkflowId); - const segments = useAppSelector(selectSegments); - const tracks = useAppSelector(selectTracks); - const customizedTrackSelection = useAppSelector(selectCustomizedTrackSelection); - const subtitles = useAppSelector(selectSubtitles); - const metadata = useAppSelector(selectCatalogs); - const workflowStatus = useAppSelector(selectStatus); - const theme = useTheme(); - - // Let users leave the page without warning after a successful save - useEffect(() => { - if (workflowStatus === "success") { - dispatch(setEnd({ hasEnded: true, value: "success" })); - dispatch(videoSetHasChanges(false)); - dispatch(metadataSetHasChanges(false)); - dispatch(subtitleSetHasChanges(false)); - } - }, [dispatch, workflowStatus]); - - const prepareSubtitles = () => { - const subtitlesForPosting = []; - - for (const identifier in subtitles) { - subtitlesForPosting.push({ - id: identifier, - subtitle: serializeSubtitle(subtitles[identifier].cues), - tags: subtitles[identifier].tags, - }); - } - return subtitlesForPosting; - }; - - const saveAndProcess = () => { - dispatch(postVideoInformationWithWorkflow({ - segments: segments, - tracks: tracks, - customizedTrackSelection, - workflow: [{ id: selectedWorkflowId }], - subtitles: prepareSubtitles(), - metadata: metadata, - })); - }; - - // Update based on current fetching status - let Icon = LuDatabase; - let spin = false; - if (workflowStatus === "failed") { - Icon = LuAlertCircle; - spin = false; - } else if (workflowStatus === "success") { - Icon = LuCheck; - spin = false; - } else if (workflowStatus === "loading") { - Icon = LuLoader; - spin = true; - - } - - const saveButtonStyle = css({ - padding: "16px", - boxShadow: `${theme.boxShadow}`, - background: `${theme.element_bg}`, - }); - - return ( -
) => { - if (event.key === " " || event.key === "Enter") { - saveAndProcess(); - } - }}> - - {text} -
- ); -}; - export default WorkflowConfiguration; diff --git a/src/main/WorkflowSelection.tsx b/src/main/WorkflowSelection.tsx index 19b773358..48fb01a92 100644 --- a/src/main/WorkflowSelection.tsx +++ b/src/main/WorkflowSelection.tsx @@ -5,12 +5,9 @@ import { backOrContinueStyle, errorBoxStyle } from "../cssStyles"; import { useAppDispatch, useAppSelector } from "../redux/store"; import { selectWorkflows, setSelectedWorkflowIndex } from "../redux/videoSlice"; -import { selectFinishState, selectPageNumber } from "../redux/finishSlice"; import { PageButton } from "./Finish"; -import { LuChevronLeft } from "react-icons/lu"; -import { SaveAndProcessButton } from "./WorkflowConfiguration"; -import { selectStatus, selectError } from "../redux/workflowPostAndProcessSlice"; +import { LuChevronLeft, LuDatabase } from "react-icons/lu"; import { selectStatus as saveSelectStatus, selectError as saveSelectError } from "../redux/workflowPostSlice"; import { httpRequestState, Workflow } from "../types"; import { SaveButton } from "./Save"; @@ -37,18 +34,14 @@ const WorkflowSelection: React.FC = () => { return (b.displayOrder - a.displayOrder); }); - const finishState = useAppSelector(selectFinishState); - const pageNumber = useAppSelector(selectPageNumber); const theme = useTheme(); - const postAndProcessWorkflowStatus = useAppSelector(selectStatus); - const postAndProcessError = useAppSelector(selectError); const saveStatus = useAppSelector(saveSelectStatus); const saveError = useAppSelector(saveSelectError); const workflowSelectionStyle = css({ padding: "20px", - display: (finishState === "Start processing" && pageNumber === 1) ? "flex" : "none", + display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", @@ -107,7 +100,7 @@ const WorkflowSelection: React.FC = () => {
{t("various.error-text")}
{errorMessage ? - t("various.error-details-text", { errorMessage: postAndProcessError }) : + t("various.error-details-text", { errorMessage: saveError }) : t("various.error-text")}
@@ -140,9 +133,13 @@ const WorkflowSelection: React.FC = () => { This will take some time. , false, - , - postAndProcessWorkflowStatus, - postAndProcessError + , + saveStatus, + saveError ) ); } else { @@ -153,9 +150,13 @@ const WorkflowSelection: React.FC = () => { {t("workflowSelection.manyWorkflows-text")}
, true, - , - postAndProcessWorkflowStatus, - postAndProcessError + , + saveStatus, + saveError ) ); } diff --git a/src/redux/store.ts b/src/redux/store.ts index bfedf0db1..b7dd64bbb 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -3,7 +3,6 @@ import mainMenuStateReducer from "./mainMenuSlice"; import finishStateReducer from "./finishSlice"; import videoReducer from "./videoSlice"; import workflowPostReducer from "./workflowPostSlice"; -import workflowPostAndProcessReducer from "./workflowPostAndProcessSlice"; import endReducer from "./endSlice"; import metadataReducer from "./metadataSlice"; import subtitleReducer from "./subtitleSlice"; @@ -16,7 +15,6 @@ export const store = configureStore({ finishState: finishStateReducer, videoState: videoReducer, workflowPostState: workflowPostReducer, - workflowPostAndProcessState: workflowPostAndProcessReducer, endState: endReducer, metadataState: metadataReducer, subtitleState: subtitleReducer, diff --git a/src/redux/videoSlice.ts b/src/redux/videoSlice.ts index 6e2130de5..f8684359c 100644 --- a/src/redux/videoSlice.ts +++ b/src/redux/videoSlice.ts @@ -180,14 +180,17 @@ const videoSlice = createSlice({ state.jumpTriggered = true; }, jumpToNextSegment: state => { - let nextSegmentIndex = state.activeSegmentIndex + 1; + const nextSegmentIndex = state.activeSegmentIndex + 1; + let jumpTarget = 0; if (state.activeSegmentIndex + 1 >= state.segments.length) { - // Jump to start of last segment - nextSegmentIndex = state.activeSegmentIndex; + // Jump to end of last segment + jumpTarget = state.segments[state.activeSegmentIndex].end; + } else { + jumpTarget = state.segments[nextSegmentIndex].start; } - updateCurrentlyAt(state, state.segments[nextSegmentIndex].start); + updateCurrentlyAt(state, jumpTarget); state.jumpTriggered = true; }, addSegment: (state, action: PayloadAction) => { diff --git a/src/redux/workflowPostAndProcessSlice.ts b/src/redux/workflowPostAndProcessSlice.ts deleted file mode 100644 index 98d146e48..000000000 --- a/src/redux/workflowPostAndProcessSlice.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; -import { client } from "../util/client"; -import { PostAndProcessEditArgument, httpRequestState } from "../types"; - -import { convertSegments } from "./workflowPostSlice"; -import { settings } from "../config"; - -const initialState: httpRequestState = { - status: "idle", - error: undefined, - errorReason: "unknown", -}; - -export const postVideoInformationWithWorkflow = - createAsyncThunk("video/postVideoInformationWithWorkflow", async (argument: PostAndProcessEditArgument) => { - if (!settings.id) { - throw new Error("Missing media package identifier"); - } - - const response = await client.post(`${settings.opencast.url}/editor/${settings.id}/edit.json`, - { - segments: convertSegments(argument.segments), - tracks: argument.tracks, - customizedTrackSelection: argument.customizedTrackSelection, - subtitles: argument.subtitles, - metadataJSON: JSON.stringify(argument.metadata), - workflows: argument.workflow, - } - ); - return response; - }); - -/** - * Slice for managing a post request for saving current changes and starting a workflow - * TODO: Create a wrapper for this and workflowPostAndProcessSlice - */ -const workflowPostAndProcessSlice = createSlice({ - name: "workflowPostAndProcessState", - initialState, - reducers: { - }, - extraReducers: builder => { - builder.addCase( - postVideoInformationWithWorkflow.pending, (state, _action) => { - state.status = "loading"; - }); - builder.addCase( - postVideoInformationWithWorkflow.fulfilled, (state, _action) => { - state.status = "success"; - }); - builder.addCase( - postVideoInformationWithWorkflow.rejected, (state, action) => { - state.status = "failed"; - state.error = action.error.message; - }); - }, - selectors: { - selectStatus: state => state.status, - selectError: state => state.error, - }, -}); - -export const { selectStatus, selectError } = workflowPostAndProcessSlice.selectors; - - -export default workflowPostAndProcessSlice.reducer; diff --git a/src/redux/workflowPostSlice.ts b/src/redux/workflowPostSlice.ts index f2792a0da..ba7f6ec01 100644 --- a/src/redux/workflowPostSlice.ts +++ b/src/redux/workflowPostSlice.ts @@ -21,6 +21,7 @@ export const postVideoInformation = tracks: argument.tracks, customizedTrackSelection: argument.customizedTrackSelection, subtitles: argument.subtitles, + workflows: argument.workflow, metadataJSON: JSON.stringify(argument.metadata), } ); diff --git a/src/types.ts b/src/types.ts index cd2055831..dece68808 100644 --- a/src/types.ts +++ b/src/types.ts @@ -71,13 +71,10 @@ export interface PostEditArgument { tracks: Track[] customizedTrackSelection: boolean subtitles: SubtitlesFromOpencast[] + workflow?: [{id: string}] metadata: Catalog[] } -export interface PostAndProcessEditArgument extends PostEditArgument{ - workflow: [{id: string}] -} - // Use respective i18n keys as values export enum MainMenuStateNames { cutting = "mainMenu.cutting-button", diff --git a/src/util/utilityFunctions.ts b/src/util/utilityFunctions.ts index b6209b30e..347744390 100644 --- a/src/util/utilityFunctions.ts +++ b/src/util/utilityFunctions.ts @@ -1,7 +1,7 @@ import { nanoid } from "@reduxjs/toolkit"; import { WebVTTParser, WebVTTSerializer } from "webvtt-parser"; import { ExtendedSubtitleCue, SubtitleCue } from "../types"; -import { useEffect, useRef } from "react"; +import { useEffect, useState, useRef } from "react"; export const roundToDecimalPlace = (num: number, decimalPlace: number) => { const decimalFactor = Math.pow(10, decimalPlace); @@ -149,6 +149,36 @@ export function languageCodeToName(lang: string | undefined): string | undefined } } +/** + * @returns the current window width and height + */ +function getWindowDimensions() { + const { innerWidth: width, innerHeight: height } = window; + return { + width, + height, + }; +} + +/** + * A hook for window dimensions + */ +export default function useWindowDimensions() { + const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); + + useEffect(() => { + function handleResize() { + setWindowDimensions(getWindowDimensions()); + } + + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + return windowDimensions; + +} + // Runs a callback every delay milliseconds // Pass delay = null to stop // Based off: https://overreacted.io/making-setinterval-declarative-with-react-hooks/