From 82890114d1c705bb0c67303181fff3578ae4116a Mon Sep 17 00:00:00 2001 From: Vanessa Scherma Date: Mon, 28 Oct 2024 17:37:44 +0100 Subject: [PATCH] Refactor CreatePage --- .../digitaltwins/DigitalTwinsPreview.tsx | 52 ++-- .../create/ChangeFileNameDialog.tsx | 73 +++++ .../create/ConfirmDeleteDialog.tsx | 68 +++++ .../digitaltwins/create/CreateDTDialog.tsx | 125 +++++++++ .../digitaltwins/create/CreateDialogs.tsx | 259 ++++-------------- .../route/digitaltwins/create/CreatePage.tsx | 170 ++++++++---- .../digitaltwins/create/DeleteFileDialog.tsx | 48 ++++ 7 files changed, 502 insertions(+), 293 deletions(-) create mode 100644 client/src/preview/route/digitaltwins/create/ChangeFileNameDialog.tsx create mode 100644 client/src/preview/route/digitaltwins/create/ConfirmDeleteDialog.tsx create mode 100644 client/src/preview/route/digitaltwins/create/CreateDTDialog.tsx create mode 100644 client/src/preview/route/digitaltwins/create/DeleteFileDialog.tsx diff --git a/client/src/preview/route/digitaltwins/DigitalTwinsPreview.tsx b/client/src/preview/route/digitaltwins/DigitalTwinsPreview.tsx index 514c32b00..ac5cea63c 100644 --- a/client/src/preview/route/digitaltwins/DigitalTwinsPreview.tsx +++ b/client/src/preview/route/digitaltwins/DigitalTwinsPreview.tsx @@ -10,7 +10,15 @@ import { addNewFile } from 'preview/store/file.slice'; import tabs from './DigitalTwinTabDataPreview'; import CreatePage from './create/CreatePage'; -export const createDTTab = (): TabData[] => +interface DTTabProps { + newDigitalTwinName: string; + setNewDigitalTwinName: React.Dispatch>; +} + +export const createDTTab = ({ + newDigitalTwinName, + setNewDigitalTwinName, +}: DTTabProps): TabData[] => tabs .filter( (tab) => @@ -20,24 +28,25 @@ export const createDTTab = (): TabData[] => ) .map((tab) => ({ label: tab.label, - body: ( - <> - {tab.label === 'Create' ? ( - <> - {tab.body} - - - ) : ( - <> - {tab.body} - - - )} - - ), + body: + tab.label === 'Create' ? ( + <> + {tab.body} + + + ) : ( + <> + {tab.body} + + + ), })); export const DTContent = () => { + const [newDigitalTwinName, setNewDigitalTwinName] = React.useState(''); const dispatch = useDispatch(); useEffect(() => { @@ -53,12 +62,15 @@ export const DTContent = () => { return ( - - This page demonstrates integration of DTaaS with gitlab CI/CD workflows. - The feature is experimental and requires certain gitlab setup in order + + This page demonstrates integration of DTaaS with GitLab CI/CD workflows. + The feature is experimental and requires certain GitLab setup in order for it to work. - + ); }; diff --git a/client/src/preview/route/digitaltwins/create/ChangeFileNameDialog.tsx b/client/src/preview/route/digitaltwins/create/ChangeFileNameDialog.tsx new file mode 100644 index 000000000..690abc635 --- /dev/null +++ b/client/src/preview/route/digitaltwins/create/ChangeFileNameDialog.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Button, +} from '@mui/material'; +import { renameFile } from 'preview/store/file.slice'; +import { useDispatch } from 'react-redux'; +import { useEffect, useState } from 'react'; + +interface ChangeFileNameDialogProps { + open: boolean; + onClose: () => void; + fileName: string; + setFileName: (name: string) => void; + setFileType: (type: string) => void; +} + +const ChangeFileNameDialog: React.FC = ({ + open, + onClose, + fileName, + setFileName, + setFileType, +}) => { + const [modifiedFileName, setModifiedFileName] = useState(fileName); + + const dispatch = useDispatch(); + + useEffect(() => { + setModifiedFileName(fileName); + }, [fileName]); + + const handleChangeFileName = () => { + dispatch(renameFile({ oldName: fileName, newName: modifiedFileName })); + setFileName(modifiedFileName); + + const extension = modifiedFileName.split('.').pop(); + setFileType(extension || ''); + + onClose(); + }; + + return ( + + Change the file name + + setModifiedFileName(e.target.value)} + /> + + + + + + + ); +}; + +export default ChangeFileNameDialog; diff --git a/client/src/preview/route/digitaltwins/create/ConfirmDeleteDialog.tsx b/client/src/preview/route/digitaltwins/create/ConfirmDeleteDialog.tsx new file mode 100644 index 000000000..f104a2a4b --- /dev/null +++ b/client/src/preview/route/digitaltwins/create/ConfirmDeleteDialog.tsx @@ -0,0 +1,68 @@ +import * as React from 'react'; +import { Dialog, DialogActions, DialogContent, Button } from '@mui/material'; +import { removeAllCreationFiles, addNewFile } from 'preview/store/file.slice'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from 'store/store'; + +interface ConfirmDeleteDialogProps { + open: boolean; + onClose: () => void; + setFileName: (name: string) => void; + setFileContent: (content: string) => void; + setFileType: (type: string) => void; + setNewDigitalTwinName: (name: string) => void; +} + +const ConfirmDeleteDialog: React.FC = ({ + open, + onClose, + setFileName, + setFileContent, + setFileType, + setNewDigitalTwinName, +}) => { + const dispatch = useDispatch(); + + const files = useSelector((state: RootState) => state.files); + + console.log(files); + + const handleConfirmCancel = () => { + setFileName(''); + setFileContent(''); + setFileType(''); + setNewDigitalTwinName(''); + dispatch(removeAllCreationFiles()); + + const defaultFiles = [ + { name: 'description.md', type: 'description' }, + { name: 'README.md', type: 'description' }, + { name: '.gitlab-ci.yml', type: 'config' }, + ]; + + defaultFiles.forEach((file) => { + const fileExists = files.some( + (f) => f.name === file.name && f.isNew === true, + ); + if (!fileExists) { + dispatch(addNewFile(file)); + } + }); + + onClose(); + }; + + return ( + + + Are you sure you want to delete the inserted files and their content? + + + + + + + ); +}; + +export default ConfirmDeleteDialog; diff --git a/client/src/preview/route/digitaltwins/create/CreateDTDialog.tsx b/client/src/preview/route/digitaltwins/create/CreateDTDialog.tsx new file mode 100644 index 000000000..361411fbd --- /dev/null +++ b/client/src/preview/route/digitaltwins/create/CreateDTDialog.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; +import { + Dialog, + DialogActions, + DialogContent, + Typography, + Button, +} from '@mui/material'; +import { + addNewFile, + FileState, + removeAllCreationFiles, +} from 'preview/store/file.slice'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from 'store/store'; +import GitlabInstance from 'preview/util/gitlab'; +import { getAuthority } from 'util/envUtil'; +import DigitalTwin from 'preview/util/gitlabDigitalTwin'; +import { showSnackbar } from 'preview/store/snackbar.slice'; +import { setDigitalTwin } from 'preview/store/digitalTwin.slice'; + +interface CreateDTDialogProps { + open: boolean; + onClose: () => void; + newDigitalTwinName: string; + setNewDigitalTwinName: (name: string) => void; + errorMessage: string; + setErrorMessage: (message: string) => void; + setFileName: (name: string) => void; + setFileContent: (content: string) => void; + setFileType: (type: string) => void; + setOpenInputDialog: (open: boolean) => void; +} + +const defaultFiles = [ + { name: 'description.md', type: 'description' }, + { name: 'README.md', type: 'description' }, + { name: '.gitlab-ci.yml', type: 'config' }, +]; + +const InputDialog: React.FC = ({ + open, + onClose, + newDigitalTwinName, + setNewDigitalTwinName, + errorMessage, + setErrorMessage, + setFileName, + setFileContent, + setFileType, + setOpenInputDialog, +}) => { + const files: FileState[] = useSelector((state: RootState) => state.files); + const dispatch = useDispatch(); + + const handleInputDialogClose = () => { + setOpenInputDialog(false); + }; + + const handleInputDialogConfirm = async () => { + const emptyNewFiles = files + .filter((file) => file.isNew && file.content === '') + .map((file) => file.name); + + if (emptyNewFiles.length > 0) { + setErrorMessage( + `The following files have empty content: ${emptyNewFiles.join(', ')}. Edit them in order to create the new digital twin.`, + ); + return; + } + + const gitlabInstance = new GitlabInstance( + sessionStorage.getItem('username') || '', + getAuthority(), + sessionStorage.getItem('access_token') || '', + ); + await gitlabInstance.init(); + const digitalTwin = new DigitalTwin(newDigitalTwinName, gitlabInstance); + const result = await digitalTwin.createDT(files); + if (result.startsWith('Error')) { + dispatch(showSnackbar({ message: result, severity: 'error' })); + } else { + dispatch( + showSnackbar({ + message: `Digital twin ${newDigitalTwinName} created successfully`, + severity: 'success', + }), + ); + dispatch(setDigitalTwin({ assetName: newDigitalTwinName, digitalTwin })); + dispatch(removeAllCreationFiles()); + + defaultFiles.forEach((file) => { + const fileExists = files.some( + (existingFile) => existingFile.name === file.name, + ); + if (!fileExists) { + dispatch(addNewFile(file)); + } + }); + } + handleInputDialogClose(); + setFileName(''); + setFileContent(''); + setFileType(''); + setNewDigitalTwinName(''); + }; + + return ( + + + + Are you sure you want to create the{' '} + {newDigitalTwinName} digital twin? + + {errorMessage} + + + + + + + ); +}; + +export default InputDialog; diff --git a/client/src/preview/route/digitaltwins/create/CreateDialogs.tsx b/client/src/preview/route/digitaltwins/create/CreateDialogs.tsx index 06243941a..ab5871a3b 100644 --- a/client/src/preview/route/digitaltwins/create/CreateDialogs.tsx +++ b/client/src/preview/route/digitaltwins/create/CreateDialogs.tsx @@ -1,28 +1,8 @@ import * as React from 'react'; -import { - Dialog, - DialogActions, - DialogContent, - DialogTitle, - TextField, - Button, - Typography, -} from '@mui/material'; -import { useDispatch, useSelector } from 'react-redux'; -import { - addNewFile, - deleteFile, - FileState, - removeAllCreationFiles, - renameFile, -} from 'preview/store/file.slice'; -import { useEffect } from 'react'; -import { RootState } from 'store/store'; -import GitlabInstance from 'preview/util/gitlab'; -import { getAuthority } from 'util/envUtil'; -import DigitalTwin from 'preview/util/gitlabDigitalTwin'; -import { showSnackbar } from 'preview/store/snackbar.slice'; -import { setDigitalTwin } from 'preview/store/digitalTwin.slice'; +import ChangeFileNameDialog from './ChangeFileNameDialog'; +import DeleteFileDialog from './DeleteFileDialog'; +import ConfirmDeleteDialog from './ConfirmDeleteDialog'; +import CreateDTDialog from './CreateDTDialog'; interface CreateDialogsProps { openChangeFileNameDialog: boolean; @@ -31,205 +11,58 @@ interface CreateDialogsProps { setFileName: (name: string) => void; setFileContent: (content: string) => void; setFileType: (type: string) => void; - openDeleteFileDialog: boolean; onCloseDeleteFileDialog: () => void; - openConfirmDeleteDialog: boolean; setOpenConfirmDeleteDialog: (open: boolean) => void; - openInputDialog: boolean; setOpenInputDialog: (open: boolean) => void; - newDigitalTwinName: string; - + setNewDigitalTwinName: (name: string) => void; errorMessage: string; setErrorMessage: (message: string) => void; } -const CreateDialogs: React.FC = ({ - openChangeFileNameDialog, - onCloseChangeFileNameDialog, - fileName, - setFileName, - setFileContent, - setFileType, - - openDeleteFileDialog, - onCloseDeleteFileDialog, - - openConfirmDeleteDialog, - setOpenConfirmDeleteDialog, - - openInputDialog, - setOpenInputDialog, - - newDigitalTwinName, - - errorMessage, - setErrorMessage, -}) => { - const [modifiedFileName, setModifiedFileName] = React.useState(fileName); - - const files: FileState[] = useSelector((state: RootState) => state.files); - const dispatch = useDispatch(); - - const defaultFiles = [ - { name: 'description.md', type: 'description' }, - { name: 'README.md', type: 'description' }, - { name: '.gitlab-ci.yml', type: 'config' }, - ]; - - useEffect(() => { - setModifiedFileName(fileName); - }, [fileName]); - - const handleChangeFileName = () => { - dispatch(renameFile({ oldName: fileName, newName: modifiedFileName })); - - setFileName(modifiedFileName); - - const extension = modifiedFileName.split('.').pop(); - setFileType(extension || ''); - - onCloseChangeFileNameDialog(); - }; - - const handleDeleteFile = () => { - dispatch(deleteFile(fileName)); - setFileName(''); - setFileContent(''); - onCloseDeleteFileDialog(); - }; - - const handleCancelConfirmation = () => { - setOpenConfirmDeleteDialog(false); - }; - - const handleConfirmCancel = () => { - setFileName(''); - setFileContent(''); - setFileType(''); - dispatch(removeAllCreationFiles()); - setOpenConfirmDeleteDialog(false); - }; - - const handleInputDialogClose = () => { - setOpenInputDialog(false); - }; - - const handleInputDialogConfirm = async () => { - const emptyNewFiles = files - .filter((file) => file.isNew && file.content === '') - .map((file) => file.name); - - if (emptyNewFiles.length > 0) { - setErrorMessage( - `The following files have empty content: ${emptyNewFiles.join(', ')}. Edit them in order to create the new digital twin.`, - ); - return; - } - - const gitlabInstance = new GitlabInstance( - sessionStorage.getItem('username') || '', - getAuthority(), - sessionStorage.getItem('access_token') || '', - ); - await gitlabInstance.init(); - const digitalTwin = new DigitalTwin(newDigitalTwinName, gitlabInstance); - const result = await digitalTwin.createDT(files); - if (result.startsWith('Error')) { - dispatch(showSnackbar({ message: result, severity: 'error' })); - } else { - dispatch( - showSnackbar({ - message: `Digital twin ${newDigitalTwinName} created successfully`, - severity: 'success', - }), - ); - dispatch(setDigitalTwin({ assetName: newDigitalTwinName, digitalTwin })); - dispatch(removeAllCreationFiles()); - - defaultFiles.forEach((file) => { - const fileExists = files.some( - (existingFile) => existingFile.name === file.name, - ); - if (!fileExists) { - dispatch(addNewFile(file)); - } - }); - } - handleInputDialogClose(); - setFileName(''); - setFileContent(''); - setFileType(''); - }; - - return ( - <> - - Change the file name - - setModifiedFileName(e.target.value)} - /> - - - - - - - - - - Are you sure you want to delete the {fileName} file - and its content? - - - - - - - - - - - Are you sure you want to create the{' '} - {newDigitalTwinName} digital twin? - - {errorMessage} - - - - - - - - - - Are you sure you want to delete the inserted files and their content? - - - - - - - - ); -}; +const CreateDialogs: React.FC = (props) => ( + <> + + + + + props.setOpenInputDialog(false)} + newDigitalTwinName={props.newDigitalTwinName} + setNewDigitalTwinName={props.setNewDigitalTwinName} + errorMessage={props.errorMessage} + setErrorMessage={props.setErrorMessage} + setFileName={props.setFileName} + setFileContent={props.setFileContent} + setFileType={props.setFileType} + setOpenInputDialog={props.setOpenInputDialog} + /> + + props.setOpenConfirmDeleteDialog(false)} + setFileName={props.setFileName} + setFileContent={props.setFileContent} + setFileType={props.setFileType} + setNewDigitalTwinName={props.setNewDigitalTwinName} + /> + +); export default CreateDialogs; diff --git a/client/src/preview/route/digitaltwins/create/CreatePage.tsx b/client/src/preview/route/digitaltwins/create/CreatePage.tsx index f40ab726e..85824ddce 100644 --- a/client/src/preview/route/digitaltwins/create/CreatePage.tsx +++ b/client/src/preview/route/digitaltwins/create/CreatePage.tsx @@ -1,12 +1,102 @@ import * as React from 'react'; -import { useState } from 'react'; +import { Dispatch, SetStateAction, useState } from 'react'; import { Box, Button, TextField } from '@mui/material'; import Editor from 'preview/route/digitaltwins/editor/Editor'; import CreateDialogs from './CreateDialogs'; import CustomSnackbar from '../Snackbar'; -function CreatePage() { - const [newDigitalTwinName, setNewDigitalTwinName] = useState(''); +interface CreatePageProps { + newDigitalTwinName: string; + setNewDigitalTwinName: Dispatch>; +} + +function FileActionButtons({ + fileName, + onDeleteClick, + onChangeFileNameClick, +}: { + fileName: string; + onDeleteClick: () => void; + onChangeFileNameClick: () => void; +}) { + const isFileModifiable = () => + !['README.md', 'description.md', '.gitlab-ci.yml'].includes(fileName); + const isFileDeletable = () => !['.gitlab-ci.yml'].includes(fileName); + + return ( + + {isFileDeletable() && fileName && ( + + )} + {isFileModifiable() && fileName && ( + + )} + + ); +} + +function DigitalTwinNameInput({ + value, + onChange, +}: { + value: string; + onChange: (e: React.ChangeEvent) => void; +}) { + return ( + + + + ); +} + +function ActionButtons({ + onCancel, + onSave, + isSaveDisabled, +}: { + onCancel: () => void; + onSave: () => void; + isSaveDisabled: boolean; +}) { + return ( + + + + + ); +} + +function CreatePage({ + newDigitalTwinName, + setNewDigitalTwinName, +}: CreatePageProps) { const [fileName, setFileName] = useState(''); const [fileContent, setFileContent] = useState(''); const [fileType, setFileType] = useState(''); @@ -17,11 +107,6 @@ function CreatePage() { const [openInputDialog, setOpenInputDialog] = useState(false); const [errorMessage, setErrorMessage] = useState(''); - const isFileModifiable = () => - !['README.md', 'description.md', '.gitlab-ci.yml'].includes(fileName); - - const isFileDeletable = () => !['.gitlab-ci.yml'].includes(fileName); - const confirmCancel = () => { setOpenConfirmDeleteDialog(true); }; @@ -39,40 +124,21 @@ function CreatePage() { justifyContent: 'space-between', alignItems: 'center', width: '100%', - marginTop: 5, + margintTop: 5, }} > - - {isFileDeletable() && fileName && ( - - )} - {isFileModifiable() && fileName && ( - - )} - - - - setNewDigitalTwinName(e.target.value)} - /> - + setOpenDeleteFileDialog(true)} + onChangeFileNameClick={() => setOpenChangeFileNameDialog(true)} + /> + setNewDigitalTwinName(e.target.value)} + /> - + - - - - + diff --git a/client/src/preview/route/digitaltwins/create/DeleteFileDialog.tsx b/client/src/preview/route/digitaltwins/create/DeleteFileDialog.tsx new file mode 100644 index 000000000..815267862 --- /dev/null +++ b/client/src/preview/route/digitaltwins/create/DeleteFileDialog.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import { Dialog, DialogActions, DialogContent, Button } from '@mui/material'; +import { deleteFile } from 'preview/store/file.slice'; +import { useDispatch } from 'react-redux'; + +interface DeleteFileDialogProps { + open: boolean; + onClose: () => void; + fileName: string; + setFileName: (name: string) => void; + setFileContent: (content: string) => void; +} + +const DeleteFileDialog: React.FC = ({ + open, + onClose, + fileName, + setFileName, + setFileContent, +}) => { + const dispatch = useDispatch(); + + const handleDeleteFile = () => { + dispatch(deleteFile(fileName)); + setFileName(''); + setFileContent(''); + onClose(); + }; + + return ( + + + Are you sure you want to delete the {fileName} file and + its content? + + + + + + + ); +}; + +export default DeleteFileDialog;