From eb595f8fb5ee3f8abf791632706db31f00c04dd0 Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 26 Sep 2023 10:04:19 +0200 Subject: [PATCH 1/9] Avoid cutting actions row overflowing Have it wrap instead, or just drop the text altogether --- src/main/CuttingActions.tsx | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/CuttingActions.tsx b/src/main/CuttingActions.tsx index 2a6f92ad8..f350ab04f 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 { BREAKPOINT_MEDIUM, basicButtonStyle, customIconStyle } from '../cssStyles' import { IconType } from "react-icons"; import { LuScissors, LuChevronLeft, LuChevronRight, LuTrash, LuMoveHorizontal} from "react-icons/lu"; @@ -56,6 +56,8 @@ const CuttingActions: React.FC = () => { flexDirection: 'row' as const, justifyContent: 'center', alignItems: 'center', + + flexWrap: 'wrap', }) const verticalLineStyle = css({ @@ -126,7 +128,14 @@ interface cuttingActionsButtonInterface { * A button representing a single action a user can take while cutting * @param param0 */ -const CuttingActionsButton: React.FC = ({Icon, actionName, actionHandler, action, tooltip, ariaLabelText}) => { +const CuttingActionsButton: React.FC = ({ + Icon, + actionName, + actionHandler, + action, + tooltip, + ariaLabelText, +}) => { const ref = React.useRef(null) const theme = useTheme(); @@ -141,7 +150,11 @@ const CuttingActionsButton: React.FC = ({Icon, ac } }} > - {actionName} + {actionName} ); @@ -175,7 +188,11 @@ const MarkAsDeletedButton : React.FC = ({actionHand } }} > {isCurrentSegmentAlive ? : } -
{isCurrentSegmentAlive ? t('cuttingActions.delete-button') : t("cuttingActions.restore-button")}
+ {isCurrentSegmentAlive ? t('cuttingActions.delete-button') : t("cuttingActions.restore-button")} ); From f1539093900316595f1187da2d7ca0cefa3a8297 Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 26 Sep 2023 14:28:00 +0200 Subject: [PATCH 2/9] Reduce video controls row minwidth If we start lacking in available maxWidth, dont show some stuff to avoid overflow. --- src/cssStyles.tsx | 6 ++++++ src/main/CuttingActions.tsx | 14 +++----------- src/main/Header.tsx | 8 ++------ src/main/VideoControls.tsx | 10 +++++----- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/cssStyles.tsx b/src/cssStyles.tsx index e0c0e0782..2a8602881 100644 --- a/src/cssStyles.tsx +++ b/src/cssStyles.tsx @@ -464,3 +464,9 @@ export const backgroundBoxStyle = (theme: Theme) => css(({ padding: '20px', ...(flexGapReplacementStyle(25, false)), })) + +export const undisplay = (maxWidth: number) => css({ + [`@media (max-width: ${maxWidth}px)`]: { + display: "none", + } +}) diff --git a/src/main/CuttingActions.tsx b/src/main/CuttingActions.tsx index f350ab04f..133d095b7 100644 --- a/src/main/CuttingActions.tsx +++ b/src/main/CuttingActions.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { BREAKPOINT_MEDIUM, basicButtonStyle, customIconStyle } from '../cssStyles' +import { BREAKPOINT_MEDIUM, basicButtonStyle, customIconStyle, undisplay } from '../cssStyles' import { IconType } from "react-icons"; import { LuScissors, LuChevronLeft, LuChevronRight, LuTrash, LuMoveHorizontal} from "react-icons/lu"; @@ -150,11 +150,7 @@ const CuttingActionsButton: React.FC = ({ } }} > - {actionName} + {actionName} ); @@ -188,11 +184,7 @@ const MarkAsDeletedButton : React.FC = ({actionHand } }} > {isCurrentSegmentAlive ? : } - {isCurrentSegmentAlive ? t('cuttingActions.delete-button') : t("cuttingActions.restore-button")} + {isCurrentSegmentAlive ? t('cuttingActions.delete-button') : t("cuttingActions.restore-button")} ); diff --git a/src/main/Header.tsx b/src/main/Header.tsx index cf04681ee..418e7d3b7 100644 --- a/src/main/Header.tsx +++ b/src/main/Header.tsx @@ -9,7 +9,7 @@ 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, flexGapReplacementStyle } from '../cssStyles' +import { basicButtonStyle, BREAKPOINT_MEDIUM, BREAKPOINT_SMALL, flexGapReplacementStyle, undisplay } from '../cssStyles' import { ReactComponent as LogoSvg } from '../img/opencast-editor.svg'; import { selectIsEnd } from "../redux/endSlice"; @@ -251,11 +251,7 @@ const HeaderButton = React.forwardRef( css={[basicButtonStyle(theme), themeSelectorButtonStyle]} > - {label} + {label} ) }) diff --git a/src/main/VideoControls.tsx b/src/main/VideoControls.tsx index 3ebcca8ae..8e32004b5 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, flexGapReplacementStyle } from "../cssStyles"; +import { BREAKPOINT_MEDIUM, basicButtonStyle, flexGapReplacementStyle, undisplay } from "../cssStyles"; import { KEYMAP, rewriteKeys } from "../globalKeys"; import { useTranslation } from 'react-i18next'; @@ -138,7 +138,7 @@ const PreviewMode: React.FC<{ const previewModeTextStyle = (theme: Theme) => css({ display: 'inline-block', flexWrap: 'nowrap', - color: `${theme.text}` + color: `${theme.text}`, }) return ( @@ -154,7 +154,7 @@ const PreviewMode: React.FC<{ onKeyDown={(event: React.KeyboardEvent) => { if (event.key === " ") { switchPlayPreview(undefined) } }}> -
+
{t("video.previewButton")}
{isPlayPreview ? @@ -253,9 +253,9 @@ const TimeDisplay: React.FC<{ {new Date((currentlyAt ? currentlyAt : 0)).toISOString().substr(11, 10)} - {" / "} +
{" / "}
-
{new Date((duration ? duration : 0)).toISOString().substr(11, 10)}
From 7d12e82cccc311e44d2cbe7bf2af5127b3043001 Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 26 Sep 2023 15:57:14 +0200 Subject: [PATCH 3/9] Improve Header for small screen widths The header can now get even narrower, like in Studio. --- src/img/opencast-editor-narrow.svg | 55 ++++++++++++++++++++++++++++++ src/main/Header.tsx | 11 ++++-- src/util/utilityFunctions.ts | 30 ++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/img/opencast-editor-narrow.svg 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/Header.tsx b/src/main/Header.tsx index 418e7d3b7..f832467c4 100644 --- a/src/main/Header.tsx +++ b/src/main/Header.tsx @@ -12,10 +12,12 @@ import { MainMenuStateNames } from "../types"; import { basicButtonStyle, BREAKPOINT_MEDIUM, BREAKPOINT_SMALL, flexGapReplacementStyle, undisplay } from '../cssStyles' import { ReactComponent as LogoSvg } from '../img/opencast-editor.svg'; +import { ReactComponent as LogoSvgNarrow } from '../img/opencast-editor-narrow.svg'; 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 useWindowDimensions from "../util/utilityFunctions"; function Header() { const theme = useTheme() @@ -67,6 +69,10 @@ function Header() { backgroundColor: theme.header_button_hover_bg, color: `${theme.header_text}` }, + + [screenWidthAtMost(BREAKPOINT_MEDIUM)]: { + fontSize: 0, + }, }) return ( @@ -94,6 +100,7 @@ const Logo: React.FC = () => { const { t } = useTranslation() const { scheme } = useColorScheme(); + const { width } = useWindowDimensions() const logo = css({ paddingLeft: '8px', @@ -116,7 +123,7 @@ const Logo: React.FC = () => { return ( 920 ? LogoSvg : LogoSvgNarrow} stateName={MainMenuStateNames.cutting} bottomText={""} ariaLabelText={t("mainMenu.cutting-button")} diff --git a/src/util/utilityFunctions.ts b/src/util/utilityFunctions.ts index 340f014be..7164d8654 100644 --- a/src/util/utilityFunctions.ts +++ b/src/util/utilityFunctions.ts @@ -1,6 +1,7 @@ import { nanoid } from '@reduxjs/toolkit'; import { WebVTTParser, WebVTTSerializer } from 'webvtt-parser'; import { ExtendedSubtitleCue, SubtitleCue } from '../types'; +import { useEffect, useState } from 'react'; export const roundToDecimalPlace = (num: number, decimalPlace: number) => { const decimalFactor = Math.pow(10, decimalPlace) @@ -180,3 +181,32 @@ export function languageCodeToName(lang: string | undefined): string | undefined return 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; +} From 60939d9eb5c82ba8ca2b8fdac54ca2356c3ffb47 Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 26 Sep 2023 16:32:02 +0200 Subject: [PATCH 4/9] Reduce main menu min width On narrow resolutions, the main menu takes up *a lot* of space. Now it takes less than half of what it took before. --- src/cssStyles.tsx | 7 +++++-- src/main/CuttingActions.tsx | 6 +++--- src/main/Header.tsx | 10 +++++----- src/main/MainMenu.tsx | 20 ++++++++++++++++++-- src/main/VideoControls.tsx | 8 ++++---- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/cssStyles.tsx b/src/cssStyles.tsx index 2a8602881..5bfdf9d5d 100644 --- a/src/cssStyles.tsx +++ b/src/cssStyles.tsx @@ -7,6 +7,9 @@ import emotionNormalize from 'emotion-normalize'; import { checkFlexGapSupport } from './util/utilityFunctions'; import { createTheme } from '@mui/material/styles'; import { Theme, useTheme } from './themes'; +import { + DEFAULT_CONFIG as APPKIT_CONFIG, +} from "@opencast/appkit"; /** * An emotion component that inserts styles globally @@ -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; /** diff --git a/src/main/CuttingActions.tsx b/src/main/CuttingActions.tsx index 133d095b7..522f6d4f1 100644 --- a/src/main/CuttingActions.tsx +++ b/src/main/CuttingActions.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { BREAKPOINT_MEDIUM, basicButtonStyle, customIconStyle, undisplay } from '../cssStyles' +import { BREAKPOINTS, basicButtonStyle, customIconStyle, undisplay } from '../cssStyles' import { IconType } from "react-icons"; import { LuScissors, LuChevronLeft, LuChevronRight, LuTrash, LuMoveHorizontal} from "react-icons/lu"; @@ -150,7 +150,7 @@ const CuttingActionsButton: React.FC = ({ } }} > - {actionName} + {actionName}
); @@ -184,7 +184,7 @@ const MarkAsDeletedButton : React.FC = ({actionHand } }} > {isCurrentSegmentAlive ? : } - {isCurrentSegmentAlive ? t('cuttingActions.delete-button') : t("cuttingActions.restore-button")} + {isCurrentSegmentAlive ? t('cuttingActions.delete-button') : t("cuttingActions.restore-button")} ); diff --git a/src/main/Header.tsx b/src/main/Header.tsx index f832467c4..600738f18 100644 --- a/src/main/Header.tsx +++ b/src/main/Header.tsx @@ -9,7 +9,7 @@ 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, flexGapReplacementStyle, undisplay } from '../cssStyles' +import { basicButtonStyle, BREAKPOINTS, flexGapReplacementStyle, undisplay } from '../cssStyles' import { ReactComponent as LogoSvg } from '../img/opencast-editor.svg'; import { ReactComponent as LogoSvgNarrow } from '../img/opencast-editor-narrow.svg'; @@ -70,7 +70,7 @@ function Header() { color: `${theme.header_text}` }, - [screenWidthAtMost(BREAKPOINT_MEDIUM)]: { + [screenWidthAtMost(BREAKPOINTS.medium)]: { fontSize: 0, }, }) @@ -174,7 +174,7 @@ const LanguageButton: React.FC = () => { menu={{ label, items: menuItems, - breakpoint: BREAKPOINT_SMALL, + breakpoint: BREAKPOINTS.small, }} > @@ -200,7 +200,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 2e97d1a13..35a7baa1c 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, flexGapReplacementStyle } from '../cssStyles' +import { basicButtonStyle, BREAKPOINTS, flexGapReplacementStyle } from '../cssStyles' import { setIsPlaying } from "../redux/videoSlice"; import { useTranslation } from 'react-i18next'; @@ -22,6 +22,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 @@ -43,6 +44,10 @@ const MainMenu: React.FC = () => { overflowY: 'auto', background: `${theme.menu_background}`, ...(flexGapReplacementStyle(30, false)), + [screenWidthAtMost(BREAKPOINTS.large)]: { + minWidth: '60px', + padding: '20px 10px', + }, }); return ( @@ -144,6 +149,10 @@ export const MainMenuButton: React.FC = ({ boxShadow: `${theme.boxShadow}`, }, flexDirection: 'column', + [screenWidthAtMost(BREAKPOINTS.large)]: { + height: '60px', + minHeight: '40px', + }, }); return ( @@ -160,7 +169,14 @@ export const MainMenuButton: React.FC = ({ width: '36px', height: 'auto' }}/> - {bottomText &&
{bottomText}
} + {bottomText && +
+ {bottomText} +
} ); }; diff --git a/src/main/VideoControls.tsx b/src/main/VideoControls.tsx index 8e32004b5..a31377acc 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 { BREAKPOINT_MEDIUM, basicButtonStyle, flexGapReplacementStyle, undisplay } from "../cssStyles"; +import { BREAKPOINTS, basicButtonStyle, flexGapReplacementStyle, undisplay } from "../cssStyles"; import { KEYMAP, rewriteKeys } from "../globalKeys"; import { useTranslation } from 'react-i18next'; @@ -154,7 +154,7 @@ const PreviewMode: React.FC<{ onKeyDown={(event: React.KeyboardEvent) => { if (event.key === " ") { switchPlayPreview(undefined) } }}> -
+
{t("video.previewButton")}
{isPlayPreview ? @@ -253,9 +253,9 @@ const TimeDisplay: React.FC<{ {new Date((currentlyAt ? currentlyAt : 0)).toISOString().substr(11, 10)} -
{" / "}
+
{" / "}
-
{new Date((duration ? duration : 0)).toISOString().substr(11, 10)}
From 07127a39762954cb95dd4bd1d89f79e345254aa6 Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 26 Sep 2023 16:50:17 +0200 Subject: [PATCH 5/9] More space for metadata on narrow screens There's quite a lot of empty space in the metadata view. It feels especially bad when you don't have a lot of screen real estate in the first place, so let's use a little more of it. --- src/main/Metadata.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/Metadata.tsx b/src/main/Metadata.tsx index 302d9ddfe..7a53b03a2 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 { useSelector, useDispatch } from 'react-redux'; import { @@ -22,6 +22,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 @@ -92,6 +93,12 @@ const Metadata: React.FC = () => { marginRight: 'auto', minWidth: '50%', display: 'grid', + [screenWidthAtMost(1550)]: { + minWidth: '70%', + }, + [screenWidthAtMost(BREAKPOINTS.medium)]: { + minWidth: '90%', + }, }) const catalogStyle = css({ From 26ac0c55a9421181b7c8742d2836beffd44e83b6 Mon Sep 17 00:00:00 2001 From: Arnei Date: Tue, 9 Jul 2024 16:37:55 +0200 Subject: [PATCH 6/9] Remove duplicate save code This removes duplicate code regarding saving. Functionally, saving and starting workflows should still work the same. Also slightly improves on how we render the finish view, cleaning up our dom tree a bit. --- src/main/Discard.tsx | 7 +- src/main/Finish.tsx | 53 ++++----- src/main/Save.tsx | 32 ++++-- src/main/WorkflowConfiguration.tsx | 130 ++--------------------- src/main/WorkflowSelection.tsx | 33 +++--- src/redux/store.ts | 2 - src/redux/workflowPostAndProcessSlice.ts | 64 ----------- src/redux/workflowPostSlice.ts | 1 + src/types.ts | 5 +- 9 files changed, 81 insertions(+), 246 deletions(-) delete mode 100644 src/redux/workflowPostAndProcessSlice.ts 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/Save.tsx b/src/main/Save.tsx index cc78e929b..3ea590c77 100644 --- a/src/main/Save.tsx +++ b/src/main/Save.tsx @@ -9,10 +9,10 @@ import { import { LuLoader, LuCheckCircle, LuAlertCircle, LuChevronLeft, LuSave, LuCheck } from "react-icons/lu"; import { useAppDispatch, useAppSelector } from "../redux/store"; -import { selectFinishState } from "../redux/finishSlice"; import { selectHasChanges, selectSegments, + selectSelectedWorkflowId, selectTracks, setHasChanges as videoSetHasChanges, } from "../redux/videoSlice"; @@ -32,6 +32,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. @@ -41,8 +43,6 @@ const Save: React.FC = () => { const { t } = useTranslation(); - const finishState = useAppSelector(selectFinishState); - const postWorkflowStatus = useAppSelector(selectStatus); const postError = useAppSelector(selectError); const postMetadataStatus = useAppSelector(selectPostStatus); @@ -53,9 +53,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", }); @@ -108,8 +108,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 @@ -120,11 +127,12 @@ export const SaveButton: React.FC = () => { const subtitles = useAppSelector(selectSubtitles); const workflowStatus = useAppSelector(selectStatus); const metadataStatus = useAppSelector(selectPostStatus); + const selectedWorkflowId = useAppSelector(selectSelectedWorkflowId); const theme = useTheme(); const [metadataSaveStarted, setMetadataSaveStarted] = useState(false); // Update based on current fetching status - let Icon = LuSave; + let Icon = basicIcon; let spin = false; let tooltip = null; if (workflowStatus === "failed" || metadataStatus === "failed") { @@ -176,6 +184,7 @@ export const SaveButton: React.FC = () => { segments: segments, tracks: tracks, subtitles: prepareSubtitles(), + workflow: selectedWorkflowId ? [{ id: selectedWorkflowId }] : undefined, })); } @@ -185,6 +194,9 @@ export const SaveButton: React.FC = () => { // Let users leave the page without warning after a successful save useEffect(() => { if (workflowStatus === "success" && metadataStatus === "success") { + if (isTransitionToEnd) { + dispatch(setEnd({ hasEnded: true, value: "success" })); + } dispatch(videoSetHasChanges(false)); dispatch(metadataSetHasChanges(false)); dispatch(subtitleSetHasChanges(false)); @@ -202,7 +214,7 @@ export const SaveButton: React.FC = () => { } }}> - {t("save.confirm-button")} + {text ?? t("save.confirm-button")}
{ariaSaveUpdate()}
diff --git a/src/main/WorkflowConfiguration.tsx b/src/main/WorkflowConfiguration.tsx index fabdbd4bd..bd3edc786 100644 --- a/src/main/WorkflowConfiguration.tsx +++ b/src/main/WorkflowConfiguration.tsx @@ -1,40 +1,25 @@ -import React, { useEffect, useState } 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 { LuChevronLeft, LuDatabase, LuMoreHorizontal } from "react-icons/lu"; -import { useAppDispatch, useAppSelector } from "../redux/store"; -import { - selectSegments, - selectTracks, - setHasChanges as videoSetHasChanges, - selectSelectedWorkflowId, -} from "../redux/videoSlice"; -import { postVideoInformationWithWorkflow, selectStatus, selectError } from "../redux/workflowPostAndProcessSlice"; +import { useAppSelector } from "../redux/store"; import { PageButton } from "./Finish"; -import { setEnd } from "../redux/endSlice"; import { useTranslation } from "react-i18next"; import { - postMetadata, selectPostError, selectPostStatus, - setHasChanges as metadataSetHasChanges, } 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 @@ -65,7 +50,11 @@ const WorkflowConfiguration: React.FC = () => {
{t("workflowConfig.satisfied-text")}
- +
{t("various.error-text")}
@@ -83,103 +72,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 subtitles = useAppSelector(selectSubtitles); - const workflowStatus = useAppSelector(selectStatus); - const metadataStatus = useAppSelector(selectPostStatus); - const [metadataSaveStarted, setMetadataSaveStarted] = useState(false); - const theme = useTheme(); - - // Let users leave the page without warning after a successful save - useEffect(() => { - if (workflowStatus === "success" && metadataStatus === "success") { - dispatch(setEnd({ hasEnded: true, value: "success" })); - dispatch(videoSetHasChanges(false)); - dispatch(metadataSetHasChanges(false)); - dispatch(subtitleSetHasChanges(false)); - } - }, [dispatch, metadataStatus, 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; - }; - - // Dispatches first save request - // Subsequent save requests should be wrapped in useEffect hooks, - // so they are only sent after the previous one has finished - const saveAndProcess = () => { - setMetadataSaveStarted(true); - dispatch(postMetadata()); - }; - - // Subsequent save request - useEffect(() => { - if (metadataStatus === "success" && metadataSaveStarted) { - setMetadataSaveStarted(false); - dispatch(postVideoInformationWithWorkflow({ - segments: segments, - tracks: tracks, - workflow: [{ id: selectedWorkflowId }], - subtitles: prepareSubtitles(), - })); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [metadataStatus]); - - // Update based on current fetching status - let Icon = LuDatabase; - let spin = false; - if (workflowStatus === "failed" || metadataStatus === "failed") { - Icon = LuAlertCircle; - spin = false; - } else if (workflowStatus === "success" && metadataStatus === "success") { - Icon = LuCheck; - spin = false; - } else if (workflowStatus === "loading" || metadataStatus === "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/workflowPostAndProcessSlice.ts b/src/redux/workflowPostAndProcessSlice.ts deleted file mode 100644 index 92f352c60..000000000 --- a/src/redux/workflowPostAndProcessSlice.ts +++ /dev/null @@ -1,64 +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, - subtitles: argument.subtitles, - 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 a25f0ddcb..034fbe10a 100644 --- a/src/redux/workflowPostSlice.ts +++ b/src/redux/workflowPostSlice.ts @@ -20,6 +20,7 @@ export const postVideoInformation = segments: convertSegments(argument.segments), tracks: argument.tracks, subtitles: argument.subtitles, + workflows: argument.workflow, } ); return response; diff --git a/src/types.ts b/src/types.ts index 06232b9f3..4b6eb3949 100644 --- a/src/types.ts +++ b/src/types.ts @@ -68,10 +68,7 @@ export interface PostEditArgument { segments: Segment[] tracks: Track[] subtitles: SubtitlesFromOpencast[] -} - -export interface PostAndProcessEditArgument extends PostEditArgument{ - workflow: [{id: string}] + workflow?: [{id: string}] } // Use respective i18n keys as values From d204ae4e6e6ef71e15bb26a09709f238e86d75c5 Mon Sep 17 00:00:00 2001 From: Julian Kniephoff Date: Mon, 25 Nov 2024 12:50:10 +0100 Subject: [PATCH 7/9] Make behavior of forward button more intuitive This patch makes the forward button jump to the end of the video if you are currently in the last segment. The previous behavior, jumping to the **beginning** of the last segment, made it so that the forward button sometimes made you go backwards in the video. This is especially confusing if there is only one segment, in which case the forward button put you at the beginning of the video. --- src/redux/videoSlice.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/redux/videoSlice.ts b/src/redux/videoSlice.ts index e8884c8eb..cb693f07c 100644 --- a/src/redux/videoSlice.ts +++ b/src/redux/videoSlice.ts @@ -171,14 +171,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) => { From ce3d379223cc9ca6b84bb0840be537101a332a0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:20:21 +0000 Subject: [PATCH 8/9] Bump i18next from 23.16.0 to 24.0.5 Bumps [i18next](https://github.com/i18next/i18next) from 23.16.0 to 24.0.5. - [Release notes](https://github.com/i18next/i18next/releases) - [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next/compare/v23.16.0...v24.0.5) --- updated-dependencies: - dependency-name: i18next dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 17 +++++++++++++---- package.json | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8f2b96a9..89238ee37 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", @@ -3667,9 +3667,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", @@ -3684,8 +3684,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 901a22df1..c558784c0 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", From 4482b91d91a1318ff1c331de0314f27872a89cf1 Mon Sep 17 00:00:00 2001 From: Arnei Date: Fri, 6 Dec 2024 09:39:00 +0100 Subject: [PATCH 9/9] Remove unused value --- src/main/Header.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/Header.tsx b/src/main/Header.tsx index 8441a1f2c..b8660e565 100644 --- a/src/main/Header.tsx +++ b/src/main/Header.tsx @@ -23,7 +23,6 @@ import { } from "@opencast/appkit"; import { IconType } from "react-icons"; import i18next from "i18next"; -import useWindowDimensions from "../util/utilityFunctions"; import { languages as lngs } from "../i18n/lngs-generated"; function Header() { @@ -124,7 +123,6 @@ const Logo: React.FC = () => { const { t } = useTranslation(); const { scheme } = useColorScheme(); - const { width } = useWindowDimensions(); const logo = css({ paddingLeft: "8px",