diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index accb088a3..63b6e890c 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -76,7 +76,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 75ecc09e9..102da0edb 100644 --- a/src/main/Save.tsx +++ b/src/main/Save.tsx @@ -14,6 +14,8 @@ import { selectSegments, selectTracks, setHasChanges as videoSetHasChanges, + selectValidSegments, + validateSegments, } from "../redux/videoSlice"; import { postVideoInformation, selectStatus, selectError } from "../redux/workflowPostSlice"; @@ -50,6 +52,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({ display: "flex", flexDirection: "column", @@ -74,11 +80,11 @@ const Save: React.FC = () => { return ( <> - {t("save.info-text")} + {validSegments ? t("save.info-text") : t("save.invalid-text")}
- + {validSegments && }
); @@ -87,7 +93,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 48fb01a92..11488bfb0 100644 --- a/src/main/WorkflowSelection.tsx +++ b/src/main/WorkflowSelection.tsx @@ -8,13 +8,13 @@ import { selectWorkflows, setSelectedWorkflowIndex } from "../redux/videoSlice"; import { PageButton } from "./Finish"; import { LuChevronLeft, LuDatabase } from "react-icons/lu"; +import { selectValidSegments, validateSegments } from "../redux/videoSlice"; 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"; @@ -39,6 +39,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: "flex", @@ -109,7 +112,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 b7ff287be..eb580085d 100644 --- a/src/redux/videoSlice.ts +++ b/src/redux/videoSlice.ts @@ -19,6 +19,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 @@ -54,6 +55,7 @@ export const initialState: video & httpRequestState = { tracks: [], subtitlesFromOpencast: [], activeSegmentIndex: 0, + validSegments: true, selectedWorkflowId: "", previewTriggered: false, clickTriggered: false, @@ -184,6 +186,15 @@ const videoSlice = createSlice({ updateCurrentlyAt(state, jumpTarget); 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); }, @@ -363,6 +374,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, @@ -545,6 +557,7 @@ export const { setJumpTriggered, jumpToPreviousSegment, jumpToNextSegment, + validateSegments, } = videoSlice.actions; export const selectVideos = createSelector( @@ -565,6 +578,7 @@ export const { selectCurrentlyAtInSeconds, selectSegments, selectActiveSegmentIndex, + selectValidSegments, selectIsCurrentSegmentAlive, selectSelectedWorkflowId, selectHasChanges,