Skip to content

Commit

Permalink
Add unsaved changes warning in event details
Browse files Browse the repository at this point in the history
Adds a confirmation modal that warns about unsaved changes
to the following tabs of the event details:
Metadata,
Extended Metadata,
Scheduling (for scheduled events),
Workflows (for scheduled events).
Thereby this fixes opencast#982.

Includes opencast#1046, because I was expecting this to be tightly related to
modals and did not want to write it twice. The general coding
approach would be the same though.

Speaking of approach, I would like feedback on if this makes
for a good approach, or if there are better alternatives I've
missed. If people agree with this approach it could then be
extended to other modals (e.g. series) as well.
  • Loading branch information
Arnei committed Jan 21, 2025
1 parent 0e44e3c commit cd6b4e3
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import { useAppDispatch, useAppSelector } from "../../../../store";
import { MetadataCatalog } from "../../../../slices/eventSlice";
import { AsyncThunk } from "@reduxjs/toolkit";

type InitialValues = {
[key: string]: any;
}

/**
* This component renders metadata details of a certain event or series
*/
Expand All @@ -25,6 +29,7 @@ const DetailsExtendedMetadataTab = ({
editAccessRole,
metadata,
updateResource,
formikRef,
}: {
resourceId: string,
editAccessRole: string,
Expand All @@ -34,6 +39,7 @@ const DetailsExtendedMetadataTab = ({
values: { [key: string]: any; };
catalog: MetadataCatalog;
}, any> //(id: string, values: { [key: string]: any }, catalog: MetadataCatalog) => void,
formikRef?: React.RefObject<FormikProps<InitialValues>>
}) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -81,10 +87,11 @@ const DetailsExtendedMetadataTab = ({
metadata.length > 0 &&
metadata.map((catalog, key) => (
// initialize form
<Formik
<Formik<InitialValues>
enableReinitialize
initialValues={getInitialValues(catalog)}
onSubmit={(values) => handleSubmit(values, catalog)}
innerRef={formikRef}
>
{(formik) => (
/* Render table for each metadata catalog */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import { useAppDispatch, useAppSelector } from "../../../../store";
import { MetadataCatalog } from "../../../../slices/eventSlice";
import { AsyncThunk } from "@reduxjs/toolkit";

type InitialValues = {
[key: string]: string | string[];
}

/**
* This component renders metadata details of a certain event or series
*/
Expand All @@ -24,12 +28,14 @@ const DetailsMetadataTab = ({
resourceId,
header,
editAccessRole,
formikRef,
}: {
metadataFields: MetadataCatalog,
updateResource: AsyncThunk<void, { id: string; values: { [key: string]: any; }; }, any>
resourceId: string,
header: string,
editAccessRole: string,
formikRef?: React.RefObject<FormikProps<InitialValues>>
}) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -69,10 +75,11 @@ const DetailsMetadataTab = ({

return (
// initialize form
<Formik
<Formik<InitialValues>
enableReinitialize
initialValues={getInitialValues()}
onSubmit={(values) => handleSubmit(values)}
innerRef={formikRef}
>
{(formik) => (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,28 @@ import {
import { Recording } from "../../../../slices/recordingSlice";
import { useTranslation } from "react-i18next";

export type InitialValues = {
scheduleStartDate: string;
scheduleStartHour: string;
scheduleStartMinute: string;
scheduleDurationHours: string;
scheduleDurationMinutes: string;
scheduleEndDate: string;
scheduleEndHour: string;
scheduleEndMinute: string;
captureAgent: string;
inputs: string[];
}

/**
* This component manages the main assets tab of event details modal
*/
const EventDetailsSchedulingTab = ({
eventId,
formikRef
}: {
eventId: string,
formikRef?: React.RefObject<FormikProps<InitialValues>>
}) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -247,10 +262,11 @@ const EventDetailsSchedulingTab = ({
/* Scheduling configuration */
hasSchedulingProperties && (
/* Initialize form */
<Formik
<Formik<InitialValues>
enableReinitialize
initialValues={getInitialValues()}
onSubmit={(values) => submitForm(values).then((r) => {})}
innerRef={formikRef}
>
{(formik) => (
<div className="obj tbl-details">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import { Formik } from "formik";
import { Formik, FormikProps } from "formik";
import {
deletingWorkflow as getDeletingWorkflow,
getBaseWorkflow,
Expand Down Expand Up @@ -30,13 +30,22 @@ import { Tooltip } from "../../../shared/Tooltip";
import { WorkflowTabHierarchy } from "../modals/EventDetails";
import { useTranslation } from "react-i18next";

type InitialValues = {
workflowDefinition: string;
configuration: {
[key: string]: any;
} | undefined;
}

/**
* This component manages the workflows tab of the event details modal
*/
const EventDetailsWorkflowTab = ({
eventId,
formikRef,
}: {
eventId: string,
formikRef?: React.RefObject<FormikProps<InitialValues>>
}) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -286,10 +295,11 @@ const EventDetailsWorkflowTab = ({

{workflows.scheduling &&
(isLoading || (
<Formik
<Formik<InitialValues>
initialValues={setInitialValues()}
enableReinitialize
onSubmit={(values) => handleSubmit(values)}
innerRef={formikRef}
>
{(formik) => (
<div className="obj list-obj">
Expand Down
11 changes: 10 additions & 1 deletion src/components/events/partials/modals/EventDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
} from "../../../../slices/eventDetailsSlice";
import { removeNotificationWizardForm } from "../../../../slices/notificationSlice";
import DetailsTobiraTab from "../ModalTabsAndPages/DetailsTobiraTab";
import { FormikProps } from "formik";

export enum EventDetailsPage {
Metadata,
Expand All @@ -70,11 +71,13 @@ const EventDetails = ({
close,
policyChanged,
setPolicyChanged,
formikRef,
}: {
eventId: string,
close?: () => void,
policyChanged: boolean,
setPolicyChanged: (value: boolean) => void,
formikRef: React.RefObject<FormikProps<any>>
}) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -206,6 +209,7 @@ const EventDetails = ({
header={tabs[page].bodyHeaderTranslation ?? ""}
updateResource={updateMetadata}
editAccessRole="ROLE_UI_EVENTS_DETAILS_METADATA_EDIT"
formikRef={formikRef}
/>
)}
{page === EventDetailsPage.ExtendedMetadata && !isLoadingMetadata && (
Expand All @@ -214,6 +218,7 @@ const EventDetails = ({
metadata={extendedMetadata}
updateResource={updateExtendedMetadata}
editAccessRole="ROLE_UI_EVENTS_DETAILS_METADATA_EDIT"
formikRef={formikRef}
/>
)}
{page === 2 && <EventDetailsPublicationTab eventId={eventId} />}
Expand All @@ -223,12 +228,16 @@ const EventDetails = ({
/>
)}
{page === 4 && !isLoadingScheduling && (
<EventDetailsSchedulingTab eventId={eventId} />
<EventDetailsSchedulingTab
eventId={eventId}
formikRef={formikRef}
/>
)}
{page === EventDetailsPage.Workflow &&
((workflowTabHierarchy === "entry" && (
<EventDetailsWorkflowTab
eventId={eventId}
formikRef={formikRef}
/>
)) ||
(workflowTabHierarchy === "workflow-details" && (
Expand Down
11 changes: 10 additions & 1 deletion src/components/events/partials/modals/EventDetailsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { removeNotificationWizardForm } from "../../../../slices/notificationSli
import { getModalEvent } from "../../../../selectors/eventDetailsSelectors";
import { setModalEvent, setShowModal } from "../../../../slices/eventDetailsSlice";
import { Modal, ModalHandle } from "../../../shared/modals/Modal";
import { FormikProps } from "formik";

/**
* This component renders the modal for displaying event details
Expand All @@ -17,6 +18,7 @@ const EventDetailsModal = () => {

// tracks, whether the policies are different to the initial value
const [policyChanged, setPolicyChanged] = useState(false);
const formikRef = useRef<FormikProps<any>>(null);

const event = useAppSelector(state => getModalEvent(state))!;

Expand All @@ -30,7 +32,13 @@ const EventDetailsModal = () => {
};

const close = () => {
if (!policyChanged || confirmUnsaved()) {
let isUnsavedChanges = false
isUnsavedChanges = policyChanged
if (formikRef.current && formikRef.current.dirty !== undefined && formikRef.current.dirty) {
isUnsavedChanges = true
}

if (!isUnsavedChanges || confirmUnsaved()) {
setPolicyChanged(false);
dispatch(removeNotificationWizardForm());
hideModal();
Expand All @@ -51,6 +59,7 @@ const EventDetailsModal = () => {
eventId={event.id}
policyChanged={policyChanged}
setPolicyChanged={(value) => setPolicyChanged(value)}
formikRef={formikRef}
/>
</Modal>
);
Expand Down

0 comments on commit cd6b4e3

Please sign in to comment.