diff --git a/changelog.d/20240313_014112_umangapatel123_refactor_into_usereducer.md b/changelog.d/20240313_014112_umangapatel123_refactor_into_usereducer.md new file mode 100644 index 000000000000..ac7ca1a92b5e --- /dev/null +++ b/changelog.d/20240313_014112_umangapatel123_refactor_into_usereducer.md @@ -0,0 +1,4 @@ +### Fixed + +- Incorrect file name usage when importing annotations from a cloud storage + () diff --git a/cvat-ui/src/components/import-dataset/import-dataset-modal.tsx b/cvat-ui/src/components/import-dataset/import-dataset-modal.tsx index 674768cecd09..5051b4a96542 100644 --- a/cvat-ui/src/components/import-dataset/import-dataset-modal.tsx +++ b/cvat-ui/src/components/import-dataset/import-dataset-modal.tsx @@ -1,10 +1,10 @@ // Copyright (C) 2021-2022 Intel Corporation -// Copyright (C) 2022-2023 CVAT.ai Corporation +// Copyright (C) 2022-2024 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import './styles.scss'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useReducer } from 'react'; import { connect, useDispatch } from 'react-redux'; import Modal from 'antd/lib/modal'; import Form, { RuleObject } from 'antd/lib/form'; @@ -24,6 +24,7 @@ import Space from 'antd/lib/space'; import Switch from 'antd/lib/switch'; import { getCore, Storage, StorageData } from 'cvat-core-wrapper'; import StorageField from 'components/storage/storage-field'; +import { createAction, ActionUnion } from 'utils/redux'; import ImportDatasetStatusModal from './import-dataset-status-modal'; const { confirm } = Modal; @@ -48,7 +49,7 @@ const initialValues: FormValues = { }; interface UploadParams { - resource: 'annotation' | 'dataset'; + resource: 'annotation' | 'dataset' | null; convMaskToPoly: boolean; useDefaultSettings: boolean; sourceStorage: Storage; @@ -57,6 +58,218 @@ interface UploadParams { fileName: string | null; } +interface State { + instanceType: string; + file: File | null; + selectedLoader: any; + useDefaultSettings: boolean; + defaultStorageLocation: string; + defaultStorageCloudId?: number; + helpMessage: string; + selectedSourceStorageLocation: StorageLocation; + uploadParams: UploadParams; + resource: string; +} + +enum ReducerActionType { + SET_INSTANCE_TYPE = 'SET_INSTANCE_TYPE', + SET_FILE = 'SET_FILE', + SET_SELECTED_LOADER = 'SET_SELECTED_LOADER', + SET_USE_DEFAULT_SETTINGS = 'SET_USE_DEFAULT_SETTINGS', + SET_DEFAULT_STORAGE_LOCATION = 'SET_DEFAULT_STORAGE_LOCATION', + SET_DEFAULT_STORAGE_CLOUD_ID = 'SET_DEFAULT_STORAGE_CLOUD_ID', + SET_HELP_MESSAGE = 'SET_HELP_MESSAGE', + SET_SELECTED_SOURCE_STORAGE_LOCATION = 'SET_SELECTED_SOURCE_STORAGE_LOCATION', + SET_FILE_NAME = 'SET_FILE_NAME', + SET_SELECTED_FORMAT = 'SET_SELECTED_FORMAT', + SET_CONV_MASK_TO_POLY = 'SET_CONV_MASK_TO_POLY', + SET_SOURCE_STORAGE = 'SET_SOURCE_STORAGE', + SET_RESOURCE = 'SET_RESOURCE', +} + +export const reducerActions = { + setInstanceType: (instanceType: string) => ( + createAction(ReducerActionType.SET_INSTANCE_TYPE, { instanceType }) + ), + setFile: (file: File | null) => ( + createAction(ReducerActionType.SET_FILE, { file }) + ), + setSelectedLoader: (selectedLoader: any) => ( + createAction(ReducerActionType.SET_SELECTED_LOADER, { selectedLoader }) + ), + setUseDefaultSettings: (useDefaultSettings: boolean) => ( + createAction(ReducerActionType.SET_USE_DEFAULT_SETTINGS, { useDefaultSettings }) + ), + setDefaultStorageLocation: (defaultStorageLocation: string) => ( + createAction(ReducerActionType.SET_DEFAULT_STORAGE_LOCATION, { defaultStorageLocation }) + ), + setDefaultStorageCloudId: (defaultStorageCloudId?: number) => ( + createAction(ReducerActionType.SET_DEFAULT_STORAGE_CLOUD_ID, { defaultStorageCloudId }) + ), + setHelpMessage: (helpMessage: string) => ( + createAction(ReducerActionType.SET_HELP_MESSAGE, { helpMessage }) + ), + setSelectedSourceStorageLocation: (selectedSourceStorageLocation: StorageLocation) => ( + createAction(ReducerActionType.SET_SELECTED_SOURCE_STORAGE_LOCATION, { selectedSourceStorageLocation }) + ), + setFileName: (fileName: string) => ( + createAction(ReducerActionType.SET_FILE_NAME, { fileName }) + ), + setSelectedFormat: (selectedFormat: string) => ( + createAction(ReducerActionType.SET_SELECTED_FORMAT, { selectedFormat }) + ), + setConvMaskToPoly: (convMaskToPoly: boolean) => ( + createAction(ReducerActionType.SET_CONV_MASK_TO_POLY, { convMaskToPoly }) + ), + setSourceStorage: (sourceStorage: Storage) => ( + createAction(ReducerActionType.SET_SOURCE_STORAGE, { sourceStorage }) + ), + setResource: (resource: string) => ( + createAction(ReducerActionType.SET_RESOURCE, { resource }) + ), +}; + +const reducer = (state: State, action: ActionUnion): State => { + if (action.type === ReducerActionType.SET_INSTANCE_TYPE) { + return { + ...state, + instanceType: action.payload.instanceType, + }; + } + + if (action.type === ReducerActionType.SET_FILE) { + return { + ...state, + file: action.payload.file, + uploadParams: { + ...state.uploadParams, + file: action.payload.file, + }, + }; + } + + if (action.type === ReducerActionType.SET_SELECTED_LOADER) { + return { + ...state, + selectedLoader: action.payload.selectedLoader, + }; + } + + if (action.type === ReducerActionType.SET_USE_DEFAULT_SETTINGS) { + const isDefaultSettings = action.payload.useDefaultSettings; + return { + ...state, + useDefaultSettings: action.payload.useDefaultSettings, + uploadParams: { + ...state.uploadParams, + useDefaultSettings: action.payload.useDefaultSettings, + sourceStorage: isDefaultSettings ? new Storage({ + location: state.defaultStorageLocation === StorageLocation.LOCAL ? + StorageLocation.LOCAL : StorageLocation.CLOUD_STORAGE, + cloudStorageId: state.defaultStorageCloudId, + }) : state.uploadParams.sourceStorage, + }, + }; + } + + if (action.type === ReducerActionType.SET_DEFAULT_STORAGE_LOCATION) { + return { + ...state, + defaultStorageLocation: action.payload.defaultStorageLocation, + uploadParams: { + ...state.uploadParams, + sourceStorage: new Storage({ + location: action.payload.defaultStorageLocation === StorageLocation.LOCAL ? + StorageLocation.LOCAL : StorageLocation.CLOUD_STORAGE, + cloudStorageId: state.defaultStorageCloudId, + }), + }, + }; + } + + if (action.type === ReducerActionType.SET_DEFAULT_STORAGE_CLOUD_ID) { + return { + ...state, + defaultStorageCloudId: action.payload.defaultStorageCloudId, + uploadParams: { + ...state.uploadParams, + sourceStorage: new Storage({ + location: state.defaultStorageLocation === StorageLocation.LOCAL ? + StorageLocation.LOCAL : StorageLocation.CLOUD_STORAGE, + cloudStorageId: action.payload.defaultStorageCloudId, + }), + }, + }; + } + + if (action.type === ReducerActionType.SET_HELP_MESSAGE) { + return { + ...state, + helpMessage: action.payload.helpMessage, + }; + } + + if (action.type === ReducerActionType.SET_SELECTED_SOURCE_STORAGE_LOCATION) { + return { + ...state, + selectedSourceStorageLocation: action.payload.selectedSourceStorageLocation, + }; + } + + if (action.type === ReducerActionType.SET_FILE_NAME) { + return { + ...state, + uploadParams: { + ...state.uploadParams, + fileName: action.payload.fileName, + }, + }; + } + + if (action.type === ReducerActionType.SET_SELECTED_FORMAT) { + return { + ...state, + uploadParams: { + ...state.uploadParams, + selectedFormat: action.payload.selectedFormat, + }, + }; + } + + if (action.type === ReducerActionType.SET_CONV_MASK_TO_POLY) { + return { + ...state, + uploadParams: { + ...state.uploadParams, + convMaskToPoly: action.payload.convMaskToPoly, + }, + }; + } + + if (action.type === ReducerActionType.SET_SOURCE_STORAGE) { + return { + ...state, + uploadParams: { + ...state.uploadParams, + sourceStorage: action.payload.sourceStorage, + }, + }; + } + + if (action.type === ReducerActionType.SET_RESOURCE) { + return { + ...state, + resource: action.payload.resource, + uploadParams: { + ...state.uploadParams, + resource: action.payload.resource === 'dataset' ? 'dataset' : 'annotation', + }, + }; + } + + return state; +}; + function ImportDatasetModal(props: StateToProps): JSX.Element { const { importers, @@ -65,51 +278,63 @@ function ImportDatasetModal(props: StateToProps): JSX.Element { current, } = props; const [form] = Form.useForm(); - const dispatch = useDispatch(); - // TODO useState -> useReducer - const [instanceType, setInstanceType] = useState(''); - const [file, setFile] = useState(null); - const [selectedLoader, setSelectedLoader] = useState(null); - const [useDefaultSettings, setUseDefaultSettings] = useState(true); - const [defaultStorageLocation, setDefaultStorageLocation] = useState(StorageLocation.LOCAL); - const [defaultStorageCloudId, setDefaultStorageCloudId] = useState(undefined); - const [helpMessage, setHelpMessage] = useState(''); - const [selectedSourceStorageLocation, setSelectedSourceStorageLocation] = useState(StorageLocation.LOCAL); - const [uploadParams, setUploadParams] = useState({ - convMaskToPoly: true, + const appDispatch = useDispatch(); + + const [state, dispatch] = useReducer(reducer, { + instanceType: '', + file: null, + selectedLoader: null, useDefaultSettings: true, - } as UploadParams); - const [resource, setResource] = useState(''); + defaultStorageLocation: StorageLocation.LOCAL, + defaultStorageCloudId: undefined, + helpMessage: '', + selectedSourceStorageLocation: StorageLocation.LOCAL, + uploadParams: { + resource: null, + convMaskToPoly: true, + useDefaultSettings: true, + sourceStorage: new Storage({ + location: StorageLocation.LOCAL, + cloudStorageId: undefined, + }), + selectedFormat: null, + file: null, + fileName: null, + }, + resource: '', + }); + + const { + instanceType, + file, + selectedLoader, + useDefaultSettings, + defaultStorageLocation, + defaultStorageCloudId, + helpMessage, + selectedSourceStorageLocation, + uploadParams, + resource, + } = state; useEffect(() => { if (instanceT === 'project') { - setResource('dataset'); + dispatch(reducerActions.setResource('dataset')); } else if (instanceT === 'task' || instanceT === 'job') { - setResource('annotation'); + dispatch(reducerActions.setResource('annotation')); } }, [instanceT]); const isDataset = useCallback((): boolean => resource === 'dataset', [resource]); const isAnnotation = useCallback((): boolean => resource === 'annotation', [resource]); - useEffect(() => { - setUploadParams({ - ...uploadParams, - resource, - sourceStorage: { - location: defaultStorageLocation, - cloudStorageId: defaultStorageCloudId, - } as Storage, - } as UploadParams); - }, [resource, defaultStorageLocation, defaultStorageCloudId]); - const isProject = useCallback((): boolean => instance instanceof core.classes.Project, [instance]); const isTask = useCallback((): boolean => instance instanceof core.classes.Task, [instance]); useEffect(() => { if (instance) { - setDefaultStorageLocation(instance.sourceStorage.location); - setDefaultStorageCloudId(instance.sourceStorage.cloudStorageId); + dispatch(reducerActions.setDefaultStorageLocation(instance.sourceStorage.location)); + dispatch(reducerActions.setDefaultStorageCloudId(instance.sourceStorage.cloudStorageId)); let type: 'project' | 'task' | 'job' = 'job'; if (isProject()) { @@ -117,16 +342,15 @@ function ImportDatasetModal(props: StateToProps): JSX.Element { } else if (isTask()) { type = 'task'; } - setInstanceType(`${type} #${instance.id}`); + dispatch(reducerActions.setInstanceType(`${type} #${instance.id}`)); } }, [instance, resource]); useEffect(() => { - setHelpMessage( - // eslint-disable-next-line prefer-template + dispatch(reducerActions.setHelpMessage( `Import from ${(defaultStorageLocation) ? defaultStorageLocation.split('_')[0] : 'local'} ` + `storage ${(defaultStorageCloudId) ? `№${defaultStorageCloudId}` : ''}`, - ); + )); }, [defaultStorageLocation, defaultStorageCloudId]); const uploadLocalFile = (): JSX.Element => ( @@ -156,16 +380,12 @@ function ImportDatasetModal(props: StateToProps): JSX.Element { `${selectedLoader.format.toLowerCase()} extension can be used`, ); } else { - setFile(_file); - setUploadParams({ - ...uploadParams, - file: _file, - } as UploadParams); + dispatch(reducerActions.setFile(_file)); } return false; }} onRemove={() => { - setFile(null); + dispatch(reducerActions.setFile(null)); }} >

@@ -215,26 +435,24 @@ function ImportDatasetModal(props: StateToProps): JSX.Element { placeholder='Dataset file name' className='cvat-modal-import-filename-input' onChange={(e: React.ChangeEvent) => { - setUploadParams({ - ...uploadParams, - fileName: e.target.value || '', - } as UploadParams); + dispatch(reducerActions.setFileName(e.target.value || '')); }} /> ); const closeModal = useCallback((): void => { - setUseDefaultSettings(true); - setSelectedSourceStorageLocation(StorageLocation.LOCAL); + dispatch(reducerActions.setUseDefaultSettings(true)); + dispatch(reducerActions.setSelectedSourceStorageLocation(StorageLocation.LOCAL)); form.resetFields(); - setFile(null); - dispatch(importActions.closeImportDatasetModal(instance)); + dispatch(reducerActions.setFile(null)); + dispatch(reducerActions.setFileName('')); + appDispatch(importActions.closeImportDatasetModal(instance)); }, [form, instance]); const onUpload = (): void => { if (uploadParams && uploadParams.resource) { - dispatch( + appDispatch( importDatasetAsync( instance, uploadParams.selectedFormat as string, @@ -338,11 +556,8 @@ function ImportDatasetModal(props: StateToProps): JSX.Element { const [loader] = importers.filter( (importer: any): boolean => importer.name === format, ); - setSelectedLoader(loader); - setUploadParams({ - ...uploadParams, - selectedFormat: format, - } as UploadParams); + dispatch(reducerActions.setSelectedLoader(loader)); + dispatch(reducerActions.setSelectedFormat(format)); }} > {importers @@ -381,10 +596,7 @@ function ImportDatasetModal(props: StateToProps): JSX.Element { > { - setUploadParams({ - ...uploadParams, - convMaskToPoly: value, - } as UploadParams); + dispatch(reducerActions.setConvMaskToPoly(value)); }} /> @@ -401,11 +613,7 @@ function ImportDatasetModal(props: StateToProps): JSX.Element { > { - setUseDefaultSettings(value); - setUploadParams({ - ...uploadParams, - useDefaultSettings: value, - } as UploadParams); + dispatch(reducerActions.setUseDefaultSettings(value)); }} /> @@ -419,16 +627,15 @@ function ImportDatasetModal(props: StateToProps): JSX.Element { locationName={['sourceStorage', 'location']} selectCloudStorageName={['sourceStorage', 'cloudStorageId']} onChangeStorage={(value: StorageData) => { - setUploadParams({ - ...uploadParams, - sourceStorage: new Storage({ - location: value?.location || defaultStorageLocation, - cloudStorageId: (value.location) ? value.cloudStorageId : defaultStorageCloudId, - }), - } as UploadParams); + dispatch(reducerActions.setSourceStorage(new Storage({ + location: value?.location || defaultStorageLocation, + cloudStorageId: (value.location) ? value.cloudStorageId : defaultStorageCloudId, + }))); }} locationValue={selectedSourceStorageLocation} - onChangeLocationValue={(value: StorageLocation) => setSelectedSourceStorageLocation(value)} + onChangeLocationValue={(value: StorageLocation) => { + dispatch(reducerActions.setSelectedSourceStorageLocation(value)); + }} /> )} { !loadFromLocal && renderCustomName() }