INTO-CPS-Association/DTaaS

View on GitHub
client/src/preview/route/digitaltwins/manage/ReconfigureDialog.tsx

Summary

Maintainability
B
4 hrs
Test Coverage
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-await-in-loop */

import * as React from 'react';
import { useState, Dispatch, SetStateAction } from 'react';
import {
  Dialog,
  DialogTitle,
  DialogContent,
  DialogActions,
  Button,
  AlertColor,
} from '@mui/material';
import { useDispatch, useSelector } from 'react-redux';
import {
  LibraryConfigFile,
  removeAllModifiedLibraryFiles,
  selectModifiedLibraryFiles,
} from 'preview/store/libraryConfigFiles.slice';
import {
  FileState,
  removeAllModifiedFiles,
  selectModifiedFiles,
} from '../../../store/file.slice';
import {
  selectDigitalTwinByName,
  updateDescription,
} from '../../../store/digitalTwin.slice';
import { showSnackbar } from '../../../store/snackbar.slice';
import DigitalTwin, { formatName } from '../../../util/digitalTwin';
import Editor from '../editor/Editor';

interface ReconfigureDialogProps {
  showDialog: boolean;
  setShowDialog: Dispatch<SetStateAction<boolean>>;
  name: string;
}

export const handleCloseReconfigureDialog = (
  setShowDialog: Dispatch<SetStateAction<boolean>>,
) => {
  setShowDialog(false);
};

function ReconfigureDialog({
  showDialog,
  setShowDialog,
  name,
}: ReconfigureDialogProps) {
  const [fileName, setFileName] = useState<string>('');
  const [fileContent, setFileContent] = useState<string>('');
  const [fileType, setFileType] = useState<string>('');
  const [filePrivacy, setFilePrivacy] = useState<string>('');
  const [isLibraryFile, setIsLibraryFile] = useState<boolean>(false);
  const [libraryAssetPath, setLibraryAssetPath] = useState<string>('');
  const [openSaveDialog, setOpenSaveDialog] = useState(false);
  const [openCancelDialog, setOpenCancelDialog] = useState(false);
  const digitalTwin = useSelector(selectDigitalTwinByName(name));
  const modifiedFiles = useSelector(selectModifiedFiles);
  const modifiedLibraryFiles = useSelector(selectModifiedLibraryFiles);
  const dispatch = useDispatch();

  const handleSave = () => setOpenSaveDialog(true);
  const handleCancel = () => setOpenCancelDialog(true);
  const handleCloseSaveDialog = () => setOpenSaveDialog(false);
  const handleCloseCancelDialog = () => setOpenCancelDialog(false);

  const handleConfirmSave = async () => {
    await saveChanges(
      modifiedFiles,
      modifiedLibraryFiles,
      digitalTwin,
      dispatch,
      name,
    );
    setOpenSaveDialog(false);
    setShowDialog(false);
  };

  const handleConfirmCancel = () => {
    dispatch(removeAllModifiedFiles());
    dispatch(removeAllModifiedLibraryFiles());
    setOpenCancelDialog(false);
    setShowDialog(false);
  };

  return (
    <>
      <ReconfigureMainDialog
        showDialog={showDialog}
        setShowDialog={setShowDialog}
        name={name}
        handleCancel={handleCancel}
        handleSave={handleSave}
        fileName={fileName}
        setFileName={setFileName}
        fileContent={fileContent}
        setFileContent={setFileContent}
        fileType={fileType}
        setFileType={setFileType}
        filePrivacy={filePrivacy}
        setFilePrivacy={setFilePrivacy}
        isLibraryFile={isLibraryFile}
        setIsLibraryFile={setIsLibraryFile}
        libraryAssetPath={libraryAssetPath}
        setLibraryAssetPath={setLibraryAssetPath}
      />

      <ConfirmationDialog
        open={openSaveDialog}
        onClose={handleCloseSaveDialog}
        onConfirm={handleConfirmSave}
        content="Are you sure you want to apply the changes?"
      />

      <ConfirmationDialog
        open={openCancelDialog}
        onClose={handleCloseCancelDialog}
        onConfirm={handleConfirmCancel}
        content="Are you sure you want to cancel? Changes will not be applied."
      />
    </>
  );
}

export const saveChanges = async (
  modifiedFiles: FileState[],
  modifiedLibraryFiles: LibraryConfigFile[],
  digitalTwin: DigitalTwin,
  dispatch: ReturnType<typeof useDispatch>,
  name: string,
) => {
  for (const file of modifiedFiles) {
    await handleFileUpdate(file, digitalTwin, dispatch);
  }

  for (const file of modifiedLibraryFiles) {
    await handleFileUpdate(file, digitalTwin, dispatch);
  }

  showSuccessSnackbar(dispatch, name);
  dispatch(removeAllModifiedFiles());
  dispatch(removeAllModifiedLibraryFiles());
};

export const handleFileUpdate = async (
  file: FileState | LibraryConfigFile,
  digitalTwin: DigitalTwin,
  dispatch: ReturnType<typeof useDispatch>,
) => {
  try {
    if ('assetPath' in file) {
      await digitalTwin.DTAssets.updateLibraryFileContent(
        file.fileName,
        file.fileContent,
        file.assetPath,
      );
    } else {
      await digitalTwin.DTAssets.updateFileContent(file.name, file.content);

      if (file.name === 'description.md') {
        dispatch(
          updateDescription({
            assetName: digitalTwin.DTName,
            description: file.content,
          }),
        );
      }
    }
  } catch (error) {
    const fileName = 'assetPath' in file ? file.fileName : file.name;
    dispatch(
      showSnackbar({
        message: `Error updating file ${fileName}: ${error}`,
        severity: 'error',
      }),
    );
  }
};

const showSuccessSnackbar = (
  dispatch: ReturnType<typeof useDispatch>,
  name: string,
) => {
  dispatch(
    showSnackbar({
      message: `${formatName(name)} reconfigured successfully`,
      severity: 'success' as AlertColor,
    }),
  );
};

const ReconfigureMainDialog = ({
  showDialog,
  setShowDialog,
  name,
  handleCancel,
  handleSave,
  fileName,
  setFileName,
  fileContent,
  setFileContent,
  fileType,
  setFileType,
  filePrivacy,
  setFilePrivacy,
  isLibraryFile,
  setIsLibraryFile,
  libraryAssetPath,
  setLibraryAssetPath,
}: {
  showDialog: boolean;
  setShowDialog: Dispatch<SetStateAction<boolean>>;
  name: string;
  handleCancel: () => void;
  handleSave: () => void;
  fileName: string;
  setFileName: Dispatch<SetStateAction<string>>;
  fileContent: string;
  setFileContent: Dispatch<SetStateAction<string>>;
  fileType: string;
  setFileType: Dispatch<SetStateAction<string>>;
  filePrivacy: string;
  setFilePrivacy: Dispatch<SetStateAction<string>>;
  isLibraryFile: boolean;
  setIsLibraryFile: Dispatch<SetStateAction<boolean>>;
  libraryAssetPath: string;
  setLibraryAssetPath: Dispatch<SetStateAction<string>>;
}) => (
  <Dialog
    open={showDialog}
    onClose={() => handleCloseReconfigureDialog(setShowDialog)}
    fullWidth={true}
    maxWidth="lg"
    sx={{
      '& .MuiDialog-paper': {
        maxHeight: '65vh',
      },
    }}
  >
    <DialogTitle>
      Reconfigure <strong>{formatName(name)}</strong>
    </DialogTitle>
    <DialogContent>
      <Editor
        DTName={name}
        tab={'reconfigure'}
        fileName={fileName}
        setFileName={setFileName}
        fileContent={fileContent}
        setFileContent={setFileContent}
        fileType={fileType}
        setFileType={setFileType}
        filePrivacy={filePrivacy}
        setFilePrivacy={setFilePrivacy}
        isLibraryFile={isLibraryFile}
        setIsLibraryFile={setIsLibraryFile}
        libraryAssetPath={libraryAssetPath}
        setLibraryAssetPath={setLibraryAssetPath}
      />
    </DialogContent>
    <DialogActions>
      <Button color="primary" onClick={handleCancel}>
        Cancel
      </Button>
      <Button color="primary" onClick={handleSave}>
        Save
      </Button>
    </DialogActions>
  </Dialog>
);

const ConfirmationDialog = ({
  open,
  onClose,
  onConfirm,
  content,
}: {
  open: boolean;
  onClose: () => void;
  onConfirm: () => void;
  content: string;
}) => (
  <Dialog open={open} onClose={onClose}>
    <DialogContent>{content}</DialogContent>
    <DialogActions>
      <Button onClick={onClose}>No</Button>
      <Button color="primary" onClick={onConfirm}>
        Yes
      </Button>
    </DialogActions>
  </Dialog>
);

export default ReconfigureDialog;