From 1839064efc24feec2450489021c3bd9aa0766e9a Mon Sep 17 00:00:00 2001 From: sumn2u Date: Mon, 8 Jul 2024 16:45:36 -0500 Subject: [PATCH] add confirmation box on exit button click --- client/src/AlertDialog/AlertDialog.test.js | 43 +++++++++ client/src/AlertDialog/index.jsx | 38 ++++++++ client/src/Annotation/index.jsx | 92 +++++++++++++++----- client/src/Annotator/index.jsx | 1 + client/src/Localization/translation-de-DE.js | 6 +- client/src/Localization/translation-en-EN.js | 4 + client/src/MainLayout/index.jsx | 19 +--- 7 files changed, 161 insertions(+), 42 deletions(-) create mode 100644 client/src/AlertDialog/AlertDialog.test.js create mode 100644 client/src/AlertDialog/index.jsx diff --git a/client/src/AlertDialog/AlertDialog.test.js b/client/src/AlertDialog/AlertDialog.test.js new file mode 100644 index 0000000..356b52f --- /dev/null +++ b/client/src/AlertDialog/AlertDialog.test.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import AlertDialog from './index'; + +describe('AlertDialog Component', () => { + const handleClose = jest.fn(); + const handleExit = jest.fn(); + const title = "Are you sure you want to exit?"; + const description = "Do you really want to exit? This action will clear the storage and all data will be lost."; + const exitConfirm = "Agree"; + const exitCancel = "Cancel"; + + beforeEach(() => { + render( + + ); + }); + + test('renders the alert dialog with correct title and description', () => { + expect(screen.getByTestId('alert-dialog')).toBeInTheDocument(); + expect(screen.getByTestId('alert-dialog-title')).toHaveTextContent(title); + expect(screen.getByTestId('alert-dialog-description')).toHaveTextContent(description); + }); + + test('calls handleClose when cancel button is clicked', () => { + fireEvent.click(screen.getByTestId('disagree-button')); + expect(handleClose).toHaveBeenCalledTimes(1); + }); + + test('calls handleExit when agree button is clicked', () => { + fireEvent.click(screen.getByTestId('agree-button')); + expect(handleExit).toHaveBeenCalledTimes(1); + }); +}); diff --git a/client/src/AlertDialog/index.jsx b/client/src/AlertDialog/index.jsx new file mode 100644 index 0000000..c7f783c --- /dev/null +++ b/client/src/AlertDialog/index.jsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +export const AlertDialog = (props) => { + const { open, handleClose, handleExit, title, description, exitConfirm, exitCancel } = props; + + return ( + + + {title} + + + + {description} + + + + + + + + ); +} + +export default AlertDialog; \ No newline at end of file diff --git a/client/src/Annotation/index.jsx b/client/src/Annotation/index.jsx index 28f9f16..3690bc0 100644 --- a/client/src/Annotation/index.jsx +++ b/client/src/Annotation/index.jsx @@ -8,7 +8,10 @@ import { useSnackbar } from '../SnackbarContext' import { getImagesAnnotation } from "../utils/send-data-to-server" import CircularProgress from '@mui/material/CircularProgress'; import Box from '@mui/material/Box'; +import AlertDialog from "../AlertDialog"; +import { clear_db } from "../utils/get-data-from-server" import colors from "../colors.js"; +import {useTranslation} from "react-i18next" const extractRelevantProps = (region) => ({ cls: region.cls, @@ -52,6 +55,8 @@ const userReducer = (state, action) => { export default () => { const [selectedImageIndex, changeSelectedImageIndex] = useState(0) + const [open, setOpen] = useState(false); + const {t} = useTranslation(); const [showLabel, setShowLabel] = useState(false) const [isSettingsOpen, setIsSettingsOpen] = useState(false) const [imageNames, setImageNames] = useState([]) @@ -71,7 +76,18 @@ export default () => { } }) - + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + const handleExit = () => { + logout() + handleClose() + } + const [loading, setLoading] = useState(true); // Add loading state const onSelectJumpHandle = (selectedImageName) => { @@ -181,6 +197,23 @@ export default () => { const regions = [ {name: "Polygon", value: "create-polygon"}, {name: "Bounding Box", value: "create-box"}, {name: "Point", value: "create-point"}] return regions.filter(region => region.name === toolName)[0]?.value || "create-polygon" } + + const reloadApp = () => { + settingsConfig.changeSetting('settings', null); + window.location.reload(); + } + + const logout = async () => { + try { + const response = await clear_db(); + showSnackbar(response.message, 'success'); + await new Promise(resolve => setTimeout(resolve, 500)); // Wait for 500 milliseconds + } catch (error) { + showSnackbar(error.message, 'error'); + } + reloadApp() + }; + const preloadConfiguration = () => { // get last saved configuration const savedConfiguration = settingsConfig.settings|| {}; @@ -221,29 +254,40 @@ export default () => { ) : ( - label.id) || []} - selectedImage={selectedImageIndex} - enabledRegionProps={["class", "comment"]} - userReducer={userReducer} - onExit={(output) => { - console.log("Exiting!"); - }} - settings={settings} - onSelectJump={onSelectJumpHandle} - showTags={true} - selectedTool={getToolSelectionType(settings.configuration.regions)} - openDocs={() => window.open(config.DOCS_URL, '_blank')} - hideSettings={false} - onShowSettings={() => { - setIsSettingsOpen(!isSettingsOpen); - setShowLabel(false); - }} - selectedImageIndex={selectedImageIndex} - /> + <> + + label.id) || []} + selectedImage={selectedImageIndex} + enabledRegionProps={["class", "comment"]} + userReducer={userReducer} + onExit={(output) => { + handleClickOpen() + }} + settings={settings} + onSelectJump={onSelectJumpHandle} + showTags={true} + selectedTool={getToolSelectionType(settings.configuration.regions)} + openDocs={() => window.open(config.DOCS_URL, '_blank')} + hideSettings={false} + onShowSettings={() => { + setIsSettingsOpen(!isSettingsOpen); + setShowLabel(false); + }} + selectedImageIndex={selectedImageIndex} + /> + )} )} diff --git a/client/src/Annotator/index.jsx b/client/src/Annotator/index.jsx index dc5669b..936ece0 100644 --- a/client/src/Annotator/index.jsx +++ b/client/src/Annotator/index.jsx @@ -207,6 +207,7 @@ export const Annotator = ({ hideSettings={hideSettings} hideSave={hideSave} allImages= {allImages} + onExit={onExit} enabledRegionProps={enabledRegionProps} onSelectJump={onSelectJump} saveActiveImage = {saveCurrentData} diff --git a/client/src/Localization/translation-de-DE.js b/client/src/Localization/translation-de-DE.js index 7ab1486..0193a4d 100644 --- a/client/src/Localization/translation-de-DE.js +++ b/client/src/Localization/translation-de-DE.js @@ -82,7 +82,11 @@ const translationDeDE = { "expand_selection": "Auswahl erweitern", "collapse_selection": "Auswahl verkleinern", "error.image_not_found": "Bild nicht gefunden", - "info": "Die Info" + "info": "Die Info", + "exit_alert_title": "Sind Sie sicher, dass Sie beenden möchten?", + "exit_alert_description": "Möchten Sie wirklich beenden? Diese Aktion wird den Speicher löschen und alle Daten werden verloren gehen.", + "exit_alert_cancel": "Abbrechen", + "exit_alert_confirm": "Zustimmen", }; export default translationDeDE; diff --git a/client/src/Localization/translation-en-EN.js b/client/src/Localization/translation-en-EN.js index d2a8f60..158396c 100644 --- a/client/src/Localization/translation-en-EN.js +++ b/client/src/Localization/translation-en-EN.js @@ -84,6 +84,10 @@ const translationEnEN = { "collapse_selection": "Collapse selection", "error.image_not_found": "Image not found", "info": "Info", + "exit_alert_title": "Are you sure you want to exit?", + "exit_alert_description": "Do you really want to exit? This action will clear the storage and all data will be lost.", + "exit_alert_cancel": "Cancel", + "exit_alert_confirm": "Exit", }; export default translationEnEN; diff --git a/client/src/MainLayout/index.jsx b/client/src/MainLayout/index.jsx index ba2f014..16eb86f 100644 --- a/client/src/MainLayout/index.jsx +++ b/client/src/MainLayout/index.jsx @@ -25,7 +25,6 @@ import {withHotKeys} from "react-hotkeys" import {Save, ExitToApp} from "@mui/icons-material" import capitalize from "lodash/capitalize" import { useTranslation } from "react-i18next" -import { clear_db } from "../utils/get-data-from-server" import { useSnackbar} from "../SnackbarContext" import ClassDistributionSidebarBox from "../ClassDistributionSidebarBox" @@ -45,6 +44,7 @@ export const MainLayout = ({ onRegionClassAdded, hideHeader, hideHeaderText, + onExit, hideClone = true, hideSettings = false, hideSave = false, @@ -97,21 +97,6 @@ export const MainLayout = ({ } }, []) - const reloadApp = () => { - settings.changeSetting('settings', null); - window.location.reload(); - } - - const logout = async () => { - try { - const response = await clear_db(); - showSnackbar(response.message, 'success'); - await new Promise(resolve => setTimeout(resolve, 500)); // Wait for 500 milliseconds - } catch (error) { - showSnackbar(error.message, 'error'); - } - reloadApp() - }; const canvas = ( { if(item.name === "Exit"){ - logout() + onExit() } else { dispatch({type: "HEADER_BUTTON_CLICKED", buttonName: item.name}) }