diff --git a/src/components/app-bars/ImageViewerAppBar/ImageViewerAppBar.tsx b/src/components/app-bars/ImageViewerAppBar/ImageViewerAppBar.tsx index 4c7a5c49..0eddc8f5 100644 --- a/src/components/app-bars/ImageViewerAppBar/ImageViewerAppBar.tsx +++ b/src/components/app-bars/ImageViewerAppBar/ImageViewerAppBar.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { ArrowBack } from "@mui/icons-material"; @@ -10,26 +10,32 @@ import { Typography, } from "@mui/material"; -import { useDialog } from "hooks"; +import { useDialogHotkey } from "hooks"; import { ExitAnnotatorDialog } from "components/dialogs"; import { LogoLoader } from "components/styled-components"; +import { HotkeyContext } from "utils/common/enums"; export const ImageViewerAppBar = () => { + const [returnToProject, setReturnToProject] = useState(false); const { onClose: onCloseExitAnnotatorDialog, onOpen: onOpenExitAnnotatorDialog, - open: openExitAnnotatorDialog, - } = useDialog(); + open: ExitAnnotatorDialogOpen, + } = useDialogHotkey(HotkeyContext.ConfirmationDialog); const navigate = useNavigate(); const onReturnToMainProject = () => { - onCloseExitAnnotatorDialog(); - navigate("/"); + setReturnToProject(true); }; + useEffect(() => { + //NOTE: Wait until ExitAnnotatorDialogOpen is finished updating. Otherwise unmounted component access warning + if (returnToProject && !ExitAnnotatorDialogOpen) navigate("/"); + }, [returnToProject, ExitAnnotatorDialogOpen, navigate]); + return ( <> { ); diff --git a/src/components/app-bars/ProjectToolbar/ProjectToolbar.tsx b/src/components/app-bars/ProjectToolbar/ProjectToolbar.tsx index aa7f1709..eae96f53 100644 --- a/src/components/app-bars/ProjectToolbar/ProjectToolbar.tsx +++ b/src/components/app-bars/ProjectToolbar/ProjectToolbar.tsx @@ -45,12 +45,12 @@ import { useThingSelection, } from "hooks"; import { TooltipTitle } from "components/tooltips"; -import { HotkeyView } from "utils/common/enums"; +import { HotkeyContext } from "utils/common/enums"; import { useNavigate } from "react-router-dom"; import { selectActiveCategories } from "store/project/reselectors"; import { imageViewerSlice } from "store/imageViewer"; import { TooltipButton } from "components/styled-components/TooltipButton/TooltipButton"; -import { DialogWithAction } from "components/dialogs"; +import { ConfirmationDialog } from "components/dialogs"; import { ImageCategoryMenu } from "components/menus"; import { Partition } from "utils/models/enums"; import { dataSlice } from "store/data"; @@ -78,7 +78,7 @@ export const ProjectToolbar = () => { onClose: handleCloseDeleteImagesDialog, onOpen: onOpenDeleteImagesDialog, open: deleteImagesDialogisOpen, - } = useDialogHotkey(HotkeyView.DialogWithAction); + } = useDialogHotkey(HotkeyContext.ConfirmationDialog); const handleDelete = () => { dispatch( @@ -96,20 +96,11 @@ export const ProjectToolbar = () => { selectedThingIds: allSelectedThingIds, }) ); - dispatch( - applicationSettingsSlice.actions.unregisterHotkeyView({ - hotkeyView: HotkeyView.MainImageGridAppBar, - }) - ); + navigate("/imageviewer"); }; const handleNavigateMeasurements = () => { - dispatch( - applicationSettingsSlice.actions.unregisterHotkeyView({ - hotkeyView: HotkeyView.MainImageGridAppBar, - }) - ); navigate("/measurements"); }; @@ -211,7 +202,7 @@ export const ProjectToolbar = () => { )} - { inputRef={inputRef} size="small" sx={{ ml: 5 }} + variant="standard" + inputProps={{ min: 0, style: { textAlign: "center" } }} /> )} diff --git a/src/components/cards/ExampleProjectCard/ExampleProjectCard.tsx b/src/components/cards/ExampleProjectCard/ExampleProjectCard.tsx index ea5331bc..29795987 100644 --- a/src/components/cards/ExampleProjectCard/ExampleProjectCard.tsx +++ b/src/components/cards/ExampleProjectCard/ExampleProjectCard.tsx @@ -125,10 +125,7 @@ export const ExampleProjectCard = ({ ); if (!deserializedProject) return; - const project = deserializedProject.project; - const data = deserializedProject.data; - - const classifier = deserializedProject.classifier; + const { project, data, classifier } = deserializedProject; batch(() => { // loadPercent will be set to 1 here diff --git a/src/components/dialogs/BaseExampleDialog/index.ts b/src/components/dialogs/BaseExampleDialog/index.ts deleted file mode 100644 index 598dcbf1..00000000 --- a/src/components/dialogs/BaseExampleDialog/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { BaseExampleDialog } from "./BaseExampleDialog"; diff --git a/src/components/dialogs/CategoryDialog/CategoryDialog.tsx b/src/components/dialogs/CategoryDialog/CategoryDialog.tsx index 9a766da3..5c1b0f64 100644 --- a/src/components/dialogs/CategoryDialog/CategoryDialog.tsx +++ b/src/components/dialogs/CategoryDialog/CategoryDialog.tsx @@ -1,6 +1,6 @@ import { TextField, Box } from "@mui/material"; -import { DialogWithAction } from "../DialogWithAction"; +import { ConfirmationDialog } from "../ConfirmationDialog"; import { useCategoryValidation } from "hooks/useCategoryValidation/useCategoryValidation"; import { ColorIcon } from "components/controls"; @@ -37,7 +37,9 @@ export const CategoryDialog = ({ } = useCategoryValidation({ kind, initName, initColor }); const handleConfirm = () => { - onConfirm(name, color, kind); + if (!isInvalidName) { + onConfirm(name, color, kind); + } }; const handleClose = () => { @@ -46,7 +48,7 @@ export const CategoryDialog = ({ }; return ( - void; diff --git a/src/components/dialogs/UpdateCategoryDialog/UpdateCategoryDialog.tsx b/src/components/dialogs/CategoryDialog/UpdateCategoryDialog.tsx similarity index 94% rename from src/components/dialogs/UpdateCategoryDialog/UpdateCategoryDialog.tsx rename to src/components/dialogs/CategoryDialog/UpdateCategoryDialog.tsx index 23ee5d23..1f1bb45b 100644 --- a/src/components/dialogs/UpdateCategoryDialog/UpdateCategoryDialog.tsx +++ b/src/components/dialogs/CategoryDialog/UpdateCategoryDialog.tsx @@ -1,6 +1,6 @@ import { useDispatch } from "react-redux"; import { dataSlice } from "store/data/dataSlice"; -import { CategoryDialog } from "../CategoryDialog"; +import { CategoryDialog } from "./CategoryDialog"; import { Category } from "store/data/types"; type UpdateCategoriesDialogProps = { diff --git a/src/components/dialogs/CategoryDialog/index.ts b/src/components/dialogs/CategoryDialog/index.ts index 3b8d775a..4677acd5 100644 --- a/src/components/dialogs/CategoryDialog/index.ts +++ b/src/components/dialogs/CategoryDialog/index.ts @@ -1 +1,2 @@ -export { CategoryDialog } from "./CategoryDialog"; +export { CreateCategoryDialog } from "./CreateCategoryDialog"; +export { UpdateCategoryDialog } from "./UpdateCategoryDialog"; diff --git a/src/components/dialogs/DialogWithAction/DialogWithAction.stories.tsx b/src/components/dialogs/ConfirmationDialog/ConfirmationDialog.stories.tsx similarity index 72% rename from src/components/dialogs/DialogWithAction/DialogWithAction.stories.tsx rename to src/components/dialogs/ConfirmationDialog/ConfirmationDialog.stories.tsx index db0e1adc..25cf943a 100644 --- a/src/components/dialogs/DialogWithAction/DialogWithAction.stories.tsx +++ b/src/components/dialogs/ConfirmationDialog/ConfirmationDialog.stories.tsx @@ -1,9 +1,10 @@ +// ignore-no-logs import type { Meta, StoryObj } from "@storybook/react"; -import { DialogWithActionSHK } from "./DialogWithActionSHK"; +import { ConfirmationDialogSHK } from "./ConfirmationDialogSHK"; -const meta: Meta = { - title: "Common/DialogWithAction", - component: DialogWithActionSHK, +const meta: Meta = { + title: "Common/ConfirmationDialog", + component: ConfirmationDialogSHK, tags: ["autodocs"], parameters: { docs: { @@ -16,11 +17,11 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = { args: { - title: "Dialog with Action", + title: "Confirmation Dialog", content: "Dialog content", onConfirm: () => { console.log("I'm confirmed"); @@ -34,7 +35,7 @@ export const Default: Story = { export const AltText: Story = { args: { - title: "Dialog with Action", + title: "Confirmation Dialog", content: "Dialog content", onConfirm: () => { console.log("I'm confirmed"); @@ -49,7 +50,7 @@ export const AltText: Story = { export const WithReject: Story = { args: { - title: "Dialog with Action", + title: "Confirmation Dialog", content: "Dialog content", onConfirm: () => { console.log("I'm confirmed"); diff --git a/src/components/dialogs/DialogWithAction/DialogWithAction.tsx b/src/components/dialogs/ConfirmationDialog/ConfirmationDialog.tsx similarity index 73% rename from src/components/dialogs/DialogWithAction/DialogWithAction.tsx rename to src/components/dialogs/ConfirmationDialog/ConfirmationDialog.tsx index 0672808c..89d0a0b8 100644 --- a/src/components/dialogs/DialogWithAction/DialogWithAction.tsx +++ b/src/components/dialogs/ConfirmationDialog/ConfirmationDialog.tsx @@ -10,10 +10,10 @@ import { } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; import { useHotkeys } from "hooks"; -import { HotkeyView } from "utils/common/enums"; +import { HotkeyContext } from "utils/common/enums"; import { ReactElement } from "react"; -type DialogWithActionProps = Omit< +type ConfirmationDialogProps = Omit< DialogProps, "children" | "open" | "content" > & { @@ -26,34 +26,40 @@ type DialogWithActionProps = Omit< confirmText?: string; rejectText?: string; confirmDisabled?: boolean; + disableHotkeyOnInput?: boolean; }; -export const DialogWithAction = ({ +export const ConfirmationDialog = ({ title, content, onConfirm, onClose: handleClose, - onReject: handleReject, + onReject, confirmText = "Confirm", rejectText = "Reject", isOpen, confirmDisabled, + disableHotkeyOnInput, ...rest -}: DialogWithActionProps) => { +}: ConfirmationDialogProps) => { const handleConfirm = () => { + handleClose(); onConfirm(); + }; + const handleReject = () => { + onReject && onReject(); handleClose(); }; useHotkeys( "enter", () => { - handleConfirm(); + !confirmDisabled && handleConfirm(); }, - HotkeyView.DialogWithAction, - { enableOnTags: ["INPUT"], enabled: isOpen }, - [handleConfirm] + HotkeyContext.ConfirmationDialog, + { enableOnTags: disableHotkeyOnInput ? [] : ["INPUT"], enabled: isOpen }, + [handleConfirm, confirmDisabled] ); return ( @@ -77,26 +83,26 @@ export const DialogWithAction = ({ - {content && {content}} + {content && {content}} + + + {onReject && ( + + )} - {handleReject ? ( - - ) : ( - <> - )} - ); diff --git a/src/components/dialogs/DialogWithAction/DialogWithActionSHK.tsx b/src/components/dialogs/ConfirmationDialog/ConfirmationDialogSHK.tsx similarity index 75% rename from src/components/dialogs/DialogWithAction/DialogWithActionSHK.tsx rename to src/components/dialogs/ConfirmationDialog/ConfirmationDialogSHK.tsx index e556113e..627ba631 100644 --- a/src/components/dialogs/DialogWithAction/DialogWithActionSHK.tsx +++ b/src/components/dialogs/ConfirmationDialog/ConfirmationDialogSHK.tsx @@ -1,9 +1,9 @@ import { useHotkeys } from "hooks"; -import { HotkeyView } from "utils/common/enums"; -import { DialogWithAction } from "./DialogWithAction"; +import { HotkeyContext } from "utils/common/enums"; +import { ConfirmationDialog } from "./ConfirmationDialog"; -type DialogWithActionHKProps = { +type ConfirmationDialogProps = { title: string; content: string; onConfirm: () => void; @@ -16,7 +16,7 @@ type DialogWithActionHKProps = { //NOTE: SHK : Sans Hotkey for storybook -export const DialogWithActionSHK = ({ +export const ConfirmationDialogSHK = ({ title, content, onConfirm, @@ -25,7 +25,7 @@ export const DialogWithActionSHK = ({ confirmText = "Confirm", rejectText = "Reject", isOpen, -}: DialogWithActionHKProps) => { +}: ConfirmationDialogProps) => { const handleConfirm = () => { onConfirm(); @@ -37,13 +37,13 @@ export const DialogWithActionSHK = ({ () => { handleConfirm(); }, - HotkeyView.DeleteCategoryDialog, + HotkeyContext.ConfirmationDialog, { enableOnTags: ["INPUT"] }, [handleConfirm] ); return ( - void; + returnToProject: () => void; onClose: () => void; open: boolean; }; export const ExitAnnotatorDialog = ({ - onReturnToProject, + returnToProject, onClose, open, }: ExitAnnotatorDialogProps) => { @@ -19,7 +19,7 @@ export const ExitAnnotatorDialog = ({ const activeImageId = useSelector(selectActiveImageId); - const onSaveChanges = () => { + const handleSaveChanges = () => { batch(() => { dispatch( imageViewerSlice.actions.setActiveImageId({ @@ -30,11 +30,10 @@ export const ExitAnnotatorDialog = ({ dispatch(dataSlice.actions.reconcile({ keepChanges: true })); dispatch(imageViewerSlice.actions.setImageStack({ imageIds: [] })); }); - - onReturnToProject(); + returnToProject(); }; - const onDiscardChanges = () => { + const handleDiscardChanges = () => { batch(() => { dispatch( imageViewerSlice.actions.setActiveImageId({ @@ -44,19 +43,22 @@ export const ExitAnnotatorDialog = ({ ); dispatch(dataSlice.actions.reconcile({ keepChanges: false })); }); + returnToProject(); + }; - onReturnToProject(); + const handleClose = () => { + onClose(); }; return ( - ); diff --git a/src/components/dialogs/ExportAnnotationsDialog/ExportAnnotationsDialog.tsx b/src/components/dialogs/ExportAnnotationsDialog/ExportAnnotationsDialog.tsx index 904275e3..83edf73f 100644 --- a/src/components/dialogs/ExportAnnotationsDialog/ExportAnnotationsDialog.tsx +++ b/src/components/dialogs/ExportAnnotationsDialog/ExportAnnotationsDialog.tsx @@ -1,11 +1,7 @@ import React, { ChangeEvent, useState } from "react"; import { Grid, TextField } from "@mui/material"; - -import { useHotkeys } from "hooks"; - -import { HotkeyView } from "utils/common/enums"; -import { DialogWithAction } from "../DialogWithAction"; +import { ConfirmationDialog } from "../ConfirmationDialog"; type ExportAnnotationsDialogProps = { onClose: () => void; @@ -22,28 +18,17 @@ export const ExportAnnotationsDialog = ({ }: ExportAnnotationsDialogProps) => { const [projectName, setProjectName] = useState(defaultName); - const onCancel = () => { + const handleCancel = () => { setProjectName(defaultName); onClose(); }; - const onNameChange = (event: ChangeEvent) => { + const handleNameChange = (event: ChangeEvent) => { setProjectName(event.target.value); }; - useHotkeys( - "enter", - () => { - handleSave(projectName); - onClose(); - }, - HotkeyView.ExportAnnotationsDialog, - { enableOnTags: ["INPUT"] }, - [handleSave] - ); - return ( - @@ -54,13 +39,14 @@ export const ExportAnnotationsDialog = ({ id="name" label="Project file name" margin="dense" + variant="standard" defaultValue={projectName} - onChange={onNameChange} + onChange={handleNameChange} /> } - onClose={onCancel} + onClose={handleCancel} onConfirm={() => handleSave(projectName)} confirmText="Export Annotations" isOpen={open} diff --git a/src/components/dialogs/FallbackDialog/FallBackDialog.tsx b/src/components/dialogs/FallbackDialog/FallBackDialog.tsx index 85d51e03..dce28829 100644 --- a/src/components/dialogs/FallbackDialog/FallBackDialog.tsx +++ b/src/components/dialogs/FallbackDialog/FallBackDialog.tsx @@ -27,7 +27,7 @@ import { useDialogHotkey } from "hooks"; import { SaveFittedModelDialog, SaveProjectDialog } from "components/dialogs"; -import { HotkeyView } from "utils/common/enums"; +import { HotkeyContext } from "utils/common/enums"; import { ModelStatus } from "utils/models/enums"; import { APPLICATION_COLORS } from "utils/common/constants"; @@ -74,7 +74,7 @@ export const FallBackDialog = (props: any) => { onClose: onSaveProjectDialogClose, onOpen: onSaveProjectDialogOpen, open: openSaveProjectDialog, - } = useDialogHotkey(HotkeyView.SaveProjectDialog); + } = useDialogHotkey(HotkeyContext.ConfirmationDialog); // const routePath = useLocation().pathname; // const inAnnotator = routePath === "/annotator"; @@ -83,13 +83,13 @@ export const FallBackDialog = (props: any) => { onClose: onSaveClassifierDialogClose, onOpen: onSaveClassifierDialogOpen, open: openSaveClassifierDialog, - } = useDialogHotkey(HotkeyView.SaveFittedModelDialog); + } = useDialogHotkey(HotkeyContext.ConfirmationDialog); const { onClose: onSaveSegmenterDialogClose, onOpen: onSaveSegmenterDialogOpen, open: openSaveSegmenterDialog, - } = useDialogHotkey(HotkeyView.SaveFittedModelDialog); + } = useDialogHotkey(HotkeyContext.ConfirmationDialog); const selectedClassifierModel = useSelector(selectClassifierSelectedModel); const classifierModelStatus = useSelector(selectClassifierModelStatus); diff --git a/src/components/dialogs/ImportTensorflowModelDialog/CloudUpload.tsx b/src/components/dialogs/ImportTensorflowModelDialog/CloudUpload.tsx index 8ce85a26..593d6718 100644 --- a/src/components/dialogs/ImportTensorflowModelDialog/CloudUpload.tsx +++ b/src/components/dialogs/ImportTensorflowModelDialog/CloudUpload.tsx @@ -3,11 +3,9 @@ import React, { useState } from "react"; import { Button, Checkbox, - DialogContent, FormControl, FormControlLabel, InputAdornment, - MenuItem, TextField, Typography, } from "@mui/material"; @@ -103,63 +101,62 @@ export const CloudUpload = ({ return ( <> - - {"Upload a model from the internet."} - - - - - - - ), - }} - size={"small"} - value={modelUrl} - onChange={handleModelUrlChange} - error={errMessage.length > 0} - /> - - {errMessage} - - - {successMessage} - - - } - label="From TF Hub?" - /> - - - + {errMessage} + + + {successMessage} + + + } + label="From TF Hub?" + /> + + ); }; diff --git a/src/components/dialogs/ImportTensorflowModelDialog/ImportTensorflowModelDialog.tsx b/src/components/dialogs/ImportTensorflowModelDialog/ImportTensorflowModelDialog.tsx index 11852928..e3597bc3 100644 --- a/src/components/dialogs/ImportTensorflowModelDialog/ImportTensorflowModelDialog.tsx +++ b/src/components/dialogs/ImportTensorflowModelDialog/ImportTensorflowModelDialog.tsx @@ -7,6 +7,7 @@ import { Collapse, Dialog, DialogActions, + DialogContent, DialogTitle, IconButton, Tab, @@ -30,7 +31,7 @@ import { Cellpose } from "utils/models/Cellpose"; import { ModelTask } from "utils/models/enums"; import { availableClassifierModels } from "utils/models/availableClassificationModels"; import { availableSegmenterModels } from "utils/models/availableSegmentationModels"; -import { HotkeyView } from "utils/common/enums"; +import { HotkeyContext } from "utils/common/enums"; import { Shape } from "store/data/types"; import { selectProjectImageChannels } from "store/project/selectors"; import { useSelector } from "react-redux"; @@ -76,7 +77,7 @@ type ImportTensorflowModelDialogProps = { loadedModel?: Model; open: boolean; modelTask: ModelTask; - dispatchFunction: (model: Model, inputShape: Shape) => void; + dispatchFunction: (model: Model, inputShape: Shape) => Promise; }; export const ImportTensorflowModelDialog = ({ @@ -117,14 +118,14 @@ export const ImportTensorflowModelDialog = ({ } }, []); - const dispatchModelToStore = () => { + const dispatchModelToStore = async () => { if (!selectedModel) { process.env.NODE_ENV !== "production" && console.warn("Attempting to dispatch undefined model"); return; } - dispatchFunction(selectedModel, inputShape); + await dispatchFunction(selectedModel, inputShape); setCloudWarning(false); setInvalidModel(false); @@ -144,10 +145,12 @@ export const ImportTensorflowModelDialog = ({ useHotkeys( "enter", - () => dispatchModelToStore(), - HotkeyView.ImportTensorflowModelDialog, - { enableOnTags: ["INPUT"] }, - [dispatchModelToStore] + (event) => { + selectedModel && !invalidModel && dispatchModelToStore(); + }, + HotkeyContext.ConfirmationDialog, + + [dispatchModelToStore, selectedModel, invalidModel] ); useEffect(() => { @@ -232,51 +235,51 @@ export const ImportTensorflowModelDialog = ({ disabled={modelTask === ModelTask.Segmentation} /> - - - - - - - - - + + + + + + + + +