diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index b775856ac..2d0e53fc2 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -71,7 +71,9 @@ "success-tooltip-aria": "Saved successfully", "saveArea-tooltip": "Save Area", "confirm-success": "Okay", - "cancel-save": "Don't save" + "cancel-save": "Don't save", + "invalid-headline-text": "Invalid Edits", + "invalid-text": "The segments do not create a valid video. Either change or discard your edits if you wish to proceed. If you wanted to delete this video use the Opencast Admin UI. Contact an adminstrator for further help." }, "discard": { diff --git a/src/main/Save.tsx b/src/main/Save.tsx index fdefd4736..23b217a35 100644 --- a/src/main/Save.tsx +++ b/src/main/Save.tsx @@ -15,6 +15,8 @@ import { selectSegments, selectTracks, setHasChanges as videoSetHasChanges, + selectValidSegments, + validateSegments, } from "../redux/videoSlice"; import { postVideoInformation, selectStatus, selectError } from "../redux/workflowPostSlice"; @@ -52,6 +54,10 @@ const Save: React.FC = () => { const hasChanges = useAppSelector(selectHasChanges); const subtitleHasChanges = useAppSelector(selectSubtitleHasChanges); + const dispatch = useAppDispatch(); + dispatch(validateSegments()); + const validSegments = useAppSelector(selectValidSegments); + const saveStyle = css({ height: "100%", display: finishState !== "Save changes" ? "none" : "flex", @@ -76,11 +82,11 @@ const Save: React.FC = () => { return ( <> - {t("save.info-text")} + {validSegments ? t("save.info-text") : t("save.invalid-text")}
- + {validSegments && }
); @@ -89,7 +95,7 @@ const Save: React.FC = () => { return (
-

{t("save.headline-text")}

+

{validSegments ? t("save.headline-text") : t("save.invalid-headline-text")}

{render()}
{t("various.error-text")}
diff --git a/src/main/WorkflowSelection.tsx b/src/main/WorkflowSelection.tsx index 229e528bf..8791e8ae4 100644 --- a/src/main/WorkflowSelection.tsx +++ b/src/main/WorkflowSelection.tsx @@ -10,14 +10,14 @@ import { selectFinishState, selectPageNumber } from "../redux/finishSlice"; import { PageButton } from "./Finish"; import { LuChevronLeft } from "react-icons/lu"; import { SaveAndProcessButton } from "./WorkflowConfiguration"; +import { selectValidSegments, validateSegments } from "../redux/videoSlice"; import { selectStatus, selectError } from "../redux/workflowPostAndProcessSlice"; import { selectStatus as saveSelectStatus, selectError as saveSelectError } from "../redux/workflowPostSlice"; import { httpRequestState, Workflow } from "../types"; import { SaveButton } from "./Save"; import { EmotionJSX } from "@emotion/react/types/jsx-namespace"; -import { useTranslation } from "react-i18next"; -import { Trans } from "react-i18next"; +import { useTranslation, Trans } from "react-i18next"; import { FormControlLabel, Radio, RadioGroup } from "@mui/material"; import { useTheme } from "../themes"; @@ -46,6 +46,9 @@ const WorkflowSelection: React.FC = () => { const saveStatus = useAppSelector(saveSelectStatus); const saveError = useAppSelector(saveSelectError); + dispatch(validateSegments()); + const validSegments = useAppSelector(selectValidSegments); + const workflowSelectionStyle = css({ padding: "20px", display: (finishState === "Start processing" && pageNumber === 1) ? "flex" : "none", @@ -116,7 +119,18 @@ const WorkflowSelection: React.FC = () => { // Fills the layout template with values based on how many workflows are available const renderSelection = () => { - if (workflows.length <= 0) { + if (!validSegments) { + return ( + render( + t("save.invalid-headline-text"), + {t("save.invalid-text")}, + false, +
, + saveStatus, + saveError + ) + ); + } else if (workflows.length <= 0) { return ( render( t("workflowSelection.saveAndProcess-text"), diff --git a/src/redux/videoSlice.ts b/src/redux/videoSlice.ts index e170bb657..32c536bbe 100644 --- a/src/redux/videoSlice.ts +++ b/src/redux/videoSlice.ts @@ -18,6 +18,7 @@ export interface video { tracks: Track[], subtitlesFromOpencast: SubtitlesFromOpencast[], activeSegmentIndex: number, // Index of the segment that is currenlty hovered + validSegments: boolean, // Whether the segment will result in a valid video edit selectedWorkflowId: string, // Id of the currently selected workflow aspectRatios: { width: number, height: number; }[], // Aspect ratios of every video hasChanges: boolean, // Did user make changes in cutting view since last save @@ -52,6 +53,7 @@ export const initialState: video & httpRequestState = { tracks: [], subtitlesFromOpencast: [], activeSegmentIndex: 0, + validSegments: true, selectedWorkflowId: "", previewTriggered: false, clickTriggered: false, @@ -178,6 +180,15 @@ const videoSlice = createSlice({ updateCurrentlyAt(state, state.segments[nextSegmentIndex].start); state.jumpTriggered = true; }, + validateSegments: state => { + // Test if whole video has been deleted + if (state.segments.length === 1 && state.segments[0].deleted && state.segments[0].start === 0 && + state.segments[0].end === state.duration) { + state.validSegments = false; + } else { + state.validSegments = true; + } + }, addSegment: (state, action: PayloadAction) => { state.segments.push(action.payload); }, @@ -348,6 +359,7 @@ const videoSlice = createSlice({ selectCurrentlyAtInSeconds: state => state.currentlyAt / 1000, selectSegments: state => state.segments, selectActiveSegmentIndex: state => state.activeSegmentIndex, + selectValidSegments: state => state.validSegments, selectIsCurrentSegmentAlive: state => !state.segments[state.activeSegmentIndex].deleted, selectSelectedWorkflowId: state => state.selectedWorkflowId, selectHasChanges: state => state.hasChanges, @@ -517,6 +529,7 @@ export const { setJumpTriggered, jumpToPreviousSegment, jumpToNextSegment, + validateSegments, } = videoSlice.actions; export const selectVideos = createSelector( @@ -537,6 +550,7 @@ export const { selectCurrentlyAtInSeconds, selectSegments, selectActiveSegmentIndex, + selectValidSegments, selectIsCurrentSegmentAlive, selectSelectedWorkflowId, selectHasChanges,