From dd64b24022d275e8cf5ab4ae9f78b24f61539bad Mon Sep 17 00:00:00 2001 From: sumn2u Date: Wed, 23 Oct 2024 12:02:10 -0500 Subject: [PATCH] added shortcuts info --- client/src/Annotation/index.jsx | 12 ++++ client/src/Annotator/index.jsx | 6 +- client/src/Localization/translation-de-DE.js | 3 + client/src/Localization/translation-en-EN.js | 3 + client/src/MainLayout/index.jsx | 6 +- .../ShortcutsDialog/ShortcutsDialog.test.js | 51 +++++++++++++ client/src/ShortcutsDialog/index.jsx | 72 +++++++++++++++++++ 7 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 client/src/ShortcutsDialog/ShortcutsDialog.test.js create mode 100644 client/src/ShortcutsDialog/index.jsx diff --git a/client/src/Annotation/index.jsx b/client/src/Annotation/index.jsx index 12e1039..1b6642c 100644 --- a/client/src/Annotation/index.jsx +++ b/client/src/Annotation/index.jsx @@ -10,6 +10,7 @@ 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 ShortcutsDialog from "../ShortcutsDialog" import { clear_db, getSettings } from "../utils/get-data-from-server" import colors from "../colors.js" import { useTranslation } from "react-i18next" @@ -58,6 +59,7 @@ const userReducer = (state, action) => { export default () => { const [selectedImageIndex, changeSelectedImageIndex] = useState(0) + const [openDialog, setOpenDialog] = useState(false) const [open, setOpen] = useState(false) const { t } = useTranslation() const [showLabel, setShowLabel] = useState(true) @@ -93,6 +95,14 @@ export default () => { handleClose() } + const handleOpenDialog = () => { + setOpenDialog(true) + } + + const handleCloseDialog = () => { + setOpenDialog(false) + } + const [loading, setLoading] = useState(true) // Add loading state const onSelectJumpHandle = (selectedImageName) => { let selectedImage = imageNames.filter((image) => { @@ -348,6 +358,7 @@ export default () => { exitCancel={t("exit_alert_cancel")} handleExit={handleExit} /> + { onExit={(output) => { handleClickOpen() }} + onShortcutClick={() => handleOpenDialog()} settings={settings} onSelectJump={onSelectJumpHandle} showTags={true} diff --git a/client/src/Annotator/index.jsx b/client/src/Annotator/index.jsx index e572fe2..94f571a 100644 --- a/client/src/Annotator/index.jsx +++ b/client/src/Annotator/index.jsx @@ -51,6 +51,7 @@ export const Annotator = ({ videoTime = 0, videoName, onExit, + onShortcutClick, settings, keypointDefinitions, onSelectJump, @@ -148,7 +149,7 @@ export const Annotator = ({ const dispatch = useEventCallback(async (action) => { if (action.type === "HEADER_BUTTON_CLICKED") { - if (["Exit", "Done", "Save", "Complete"].includes(action.buttonName)) { + if (["Exit", "Done", "Save", "Complete", "Shortcuts"].includes(action.buttonName)) { // save the current data if (action.buttonName === "Save") { const result = await preprocessDataBeforeSend( @@ -163,6 +164,8 @@ export const Annotator = ({ payload: result, }) return null + }else if(action.buttonName === "Shortcuts") { + return onShortcutClick() } else { return onExit(without(state, "history")) } @@ -219,6 +222,7 @@ export const Annotator = ({ hideSave={hideSave} allImages={allImages} onExit={onExit} + onShortcutClick={onShortcutClick} 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 88fb0b2..b8b91f7 100644 --- a/client/src/Localization/translation-de-DE.js +++ b/client/src/Localization/translation-de-DE.js @@ -21,6 +21,7 @@ const translationDeDE = { "btn.previous": "Zurück", "btn.save": "Speichern", "btn.settings": "Einstellungen", + "btn.shortcuts": "Verknüpfungen", "btn.exit": "Beenden", "btn.clone": "Klonen", "btn.play": "Abspielen", @@ -97,6 +98,8 @@ const translationDeDE = { "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", + short_key_up: "Nach oben navigieren", + short_key_down: "Nach unten navigieren", } export default translationDeDE diff --git a/client/src/Localization/translation-en-EN.js b/client/src/Localization/translation-en-EN.js index e6c802c..23d412a 100644 --- a/client/src/Localization/translation-en-EN.js +++ b/client/src/Localization/translation-en-EN.js @@ -20,6 +20,7 @@ const translationEnEN = { "btn.previous": "Prev", "btn.save": "Save", "btn.settings": "Settings", + "btn.shortcuts": "Shortcuts", "btn.exit": "Exit", "btn.clone": "Clone", "btn.play": "Play", @@ -96,6 +97,8 @@ const translationEnEN = { "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", + short_key_up: "Navigate Up", + short_key_down: "Navigate Down", } export default translationEnEN diff --git a/client/src/MainLayout/index.jsx b/client/src/MainLayout/index.jsx index 6b23e2d..220f798 100644 --- a/client/src/MainLayout/index.jsx +++ b/client/src/MainLayout/index.jsx @@ -22,7 +22,7 @@ import getHotkeyHelpText from "../utils/get-hotkey-help-text" import { useKey } from "react-use" import { useSettings } from "../SettingsProvider" import { withHotKeys } from "react-hotkeys" -import { Save, ExitToApp } from "@mui/icons-material" +import { Save, ExitToApp, QuestionMark } from "@mui/icons-material" import capitalize from "lodash/capitalize" import { useTranslation } from "react-i18next" import { useSnackbar } from "../SnackbarContext" @@ -47,6 +47,7 @@ export const MainLayout = ({ hideHeader, hideHeaderText, onExit, + onShortcutClick, hideClone = true, hideSettings = false, hideSave = false, @@ -215,6 +216,8 @@ export const MainLayout = ({ const onClickHeaderItem = useEventCallback((item) => { if (item.name === "Exit") { onExit() + } else if (item.name === "Shortcuts") { + onShortcutClick() } else { dispatch({ type: "HEADER_BUTTON_CLICKED", buttonName: item.name }) } @@ -286,6 +289,7 @@ export const MainLayout = ({ }, { name: "Docs", label: t("btn.docs") }, !hideSettings && { name: "Settings", label: t("btn.settings") }, + { name: "Shortcuts", label: t("btn.shortcuts"), icon: }, { name: "Exit", label: t("btn.exit"), icon: }, ].filter(Boolean)} onClickHeaderItem={onClickHeaderItem} diff --git a/client/src/ShortcutsDialog/ShortcutsDialog.test.js b/client/src/ShortcutsDialog/ShortcutsDialog.test.js new file mode 100644 index 0000000..ac2d3e2 --- /dev/null +++ b/client/src/ShortcutsDialog/ShortcutsDialog.test.js @@ -0,0 +1,51 @@ +import React from "react" +import { render, screen, fireEvent } from "@testing-library/react" +import "@testing-library/jest-dom" +import ShortcutsDialog from "./index" // Adjust the import based on your file structure +import { useTranslation } from "react-i18next" + +// Mock the useTranslation hook +jest.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key) => key, // Simply return the key as the translation + }), +})) + +describe("ShortcutsDialog", () => { + const mockHandleClose = jest.fn() + + const defaultProps = { + open: true, + handleClose: mockHandleClose, + } + + beforeEach(() => { + render() + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + test("renders all shortcuts with corresponding actions", () => { + const shortcuts = [ + { key: "Ctrl + Shift + B", action: "helptext_boundingbox" }, + { key: "Ctrl + Shift + Z", action: "helptext_zoom" }, + { key: "Ctrl + Shift + P", action: "helptext_polypolygon" }, + { key: "Ctrl + Shift + C", action: "helptext_circle" }, + { key: "↑", action: "short_key_up" }, + { key: "↓", action: "short_key_down" }, + ] + shortcuts.forEach((shortcut, index) => { + expect(screen.getByTestId(`shortcut-key-${index}`)).toBeInTheDocument() + }) + }) + + test("calls handleClose when close button is clicked", () => { + const closeButton = screen.getByTestId("close-button") + fireEvent.click(closeButton) + expect(mockHandleClose).toHaveBeenCalledTimes(1) + }) + + +}) diff --git a/client/src/ShortcutsDialog/index.jsx b/client/src/ShortcutsDialog/index.jsx new file mode 100644 index 0000000..5b93d8c --- /dev/null +++ b/client/src/ShortcutsDialog/index.jsx @@ -0,0 +1,72 @@ +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 DialogTitle from "@mui/material/DialogTitle" +import Box from "@mui/material/Box" +import Typography from "@mui/material/Typography" +import { useTranslation } from "react-i18next" + +export const ShortcutsDialog = ({ open, handleClose }) => { + const { t } = useTranslation() + + const shortcuts = [ + { key: "Ctrl + Shift + B", action: t("helptext_boundingbox") }, + { key: "Ctrl + Shift + Z", action: t("helptext_zoom") }, + { key: "Ctrl + Shift + P", action: t("helptext_polypolygon") }, + { key: "Ctrl + Shift + C", action: t("helptext_circle") }, + { key: "↑", action: t("short_key_up") }, // Up arrow key for navigation + { key: "↓", action: t("short_key_down") }, // Down arrow key for navigation + ] + + return ( + + + {t("Keyboard Shortcuts")} + + + + {shortcuts.map((shortcut, index) => ( + + + {shortcut.key} + + + {shortcut.action} + + + ))} + + + + + + + ) +} + +export default ShortcutsDialog