From: Michal Klobukowski Date: Mon, 27 Aug 2018 12:09:16 +0000 (+0200) Subject: Merge branch 'master' X-Git-Tag: 1.3.0~132^2~6 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/5627bf1a83323d2b0364cb069564998eb8c6ca7a?hp=4157d0e93444cd1d2a631e7826f8cd831863b58a Merge branch 'master' Feature #14102 Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski --- diff --git a/src/components/data-explorer/data-explorer.test.tsx b/src/components/data-explorer/data-explorer.test.tsx index a34ab1c8..3e447b40 100644 --- a/src/components/data-explorer/data-explorer.test.tsx +++ b/src/components/data-explorer/data-explorer.test.tsx @@ -13,6 +13,7 @@ import { SearchInput } from "../search-input/search-input"; import { TablePagination } from "@material-ui/core"; import { ProjectIcon } from '../icon/icon'; import { DefaultView } from '../default-view/default-view'; +import { SortDirection } from '../data-table/data-column'; configure({ adapter: new Adapter() }); @@ -20,11 +21,13 @@ describe("", () => { it("communicates with ", () => { const onSearch = jest.fn(); + const onSetColumns = jest.fn(); const dataExplorer = mount(); + onSearch={onSearch} + onSetColumns={onSetColumns} />); expect(dataExplorer.find(SearchInput).prop("value")).toEqual("search value"); dataExplorer.find(SearchInput).prop("onSearch")("new value"); expect(onSearch).toHaveBeenCalledWith("new value"); @@ -32,12 +35,14 @@ describe("", () => { it("communicates with ", () => { const onColumnToggle = jest.fn(); - const columns = [{ name: "Column 1", render: jest.fn(), selected: true, configurable: true }]; + const onSetColumns = jest.fn(); + const columns = [{ name: "Column 1", render: jest.fn(), selected: true, configurable: true, sortDirection: SortDirection.ASC, filters: [] }]; const dataExplorer = mount(); + items={[{ name: "item 1" }]} + onSetColumns={onSetColumns} />); expect(dataExplorer.find(ColumnSelector).prop("columns")).toBe(columns); dataExplorer.find(ColumnSelector).prop("onColumnToggle")("columns"); expect(onColumnToggle).toHaveBeenCalledWith("columns"); @@ -47,7 +52,8 @@ describe("", () => { const onFiltersChange = jest.fn(); const onSortToggle = jest.fn(); const onRowClick = jest.fn(); - const columns = [{ name: "Column 1", render: jest.fn(), selected: true, configurable: true }]; + const onSetColumns = jest.fn(); + const columns = [{ name: "Column 1", render: jest.fn(), selected: true, configurable: true, sortDirection: SortDirection.ASC, filters: [] }]; const items = [{ name: "item 1" }]; const dataExplorer = mount(", () => { items={items} onFiltersChange={onFiltersChange} onSortToggle={onSortToggle} - onRowClick={onRowClick} />); + onRowClick={onRowClick} + onSetColumns={onSetColumns} />); expect(dataExplorer.find(DataTable).prop("columns").slice(0, -1)).toEqual(columns); expect(dataExplorer.find(DataTable).prop("items")).toBe(items); dataExplorer.find(DataTable).prop("onRowClick")("event", "rowClick"); @@ -70,7 +77,7 @@ describe("", () => { const dataExplorer = mount(); + onSetColumns={jest.fn()} />); expect(dataExplorer.find(DataTable)).toHaveLength(0); expect(dataExplorer.find(DefaultView)).toHaveLength(1); }); @@ -78,6 +85,7 @@ describe("", () => { it("communicates with ", () => { const onChangePage = jest.fn(); const onChangeRowsPerPage = jest.fn(); + const onSetColumns = jest.fn(); const dataExplorer = mount(", () => { rowsPerPage={50} onChangePage={onChangePage} onChangeRowsPerPage={onChangeRowsPerPage} - />); + onSetColumns={onSetColumns} />); expect(dataExplorer.find(TablePagination).prop("page")).toEqual(10); expect(dataExplorer.find(TablePagination).prop("rowsPerPage")).toEqual(50); dataExplorer.find(TablePagination).prop("onChangePage")(undefined, 6); @@ -114,5 +122,6 @@ const mockDataExplorerProps = () => ({ onChangeRowsPerPage: jest.fn(), onContextMenu: jest.fn(), defaultIcon: ProjectIcon, + onSetColumns: jest.fn(), defaultMessages: ['testing'], }); diff --git a/src/components/form-dialog/form-dialog.tsx b/src/components/form-dialog/form-dialog.tsx index dee89249..150dc4b6 100644 --- a/src/components/form-dialog/form-dialog.tsx +++ b/src/components/form-dialog/form-dialog.tsx @@ -48,7 +48,8 @@ export const FormDialog = withStyles(styles)((props: DialogProjectProps & WithDi onClose={props.closeDialog} disableBackdropClick={props.submitting} disableEscapeKeyDown={props.submitting} - fullWidth> + fullWidth + maxWidth='sm'>
{props.dialogTitle} diff --git a/src/components/move-to-dialog/move-to-dialog.tsx b/src/components/move-to-dialog/move-to-dialog.tsx deleted file mode 100644 index 2bfc2c3d..00000000 --- a/src/components/move-to-dialog/move-to-dialog.tsx +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import * as React from "react"; -import { Field, InjectedFormProps, WrappedFieldProps } from "redux-form"; -import { Dialog, DialogTitle, DialogContent, DialogActions, Button, CircularProgress } from "@material-ui/core"; - -import { WithDialogProps } from "~/store/dialog/with-dialog"; -import { ProjectTreePicker } from "~/views-components/project-tree-picker/project-tree-picker"; -import { MOVE_TO_VALIDATION } from "~/validators/validators"; - -export const MoveToDialog = (props: WithDialogProps & InjectedFormProps<{ name: string }>) => - - - Move to - - - - - - - - - ; - -const Picker = (props: WrappedFieldProps) => -
- props.input.onChange(projectUuid)} /> -
; \ No newline at end of file diff --git a/src/components/project-copy/project-copy.tsx b/src/components/project-copy/project-copy.tsx deleted file mode 100644 index 34c8e6d1..00000000 --- a/src/components/project-copy/project-copy.tsx +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 -import * as React from "react"; -import { Field, InjectedFormProps, WrappedFieldProps } from "redux-form"; -import { Dialog, DialogTitle, DialogContent, DialogActions, Button, CircularProgress } from "@material-ui/core"; -import { WithDialogProps } from "~/store/dialog/with-dialog"; -import { ProjectTreePicker } from "~/views-components/project-tree-picker/project-tree-picker"; -import { MAKE_A_COPY_VALIDATION, COPY_NAME_VALIDATION } from "~/validators/validators"; -import { TextField } from '~/components/text-field/text-field'; - -export interface CopyFormData { - name: string; - projectUuid: string; - uuid: string; -} - -export const ProjectCopy = (props: WithDialogProps & InjectedFormProps) => -
- - Make a copy - - - - - - - - - -
; -const Picker = (props: WrappedFieldProps) => -
- props.input.onChange(projectUuid)} /> -
; \ No newline at end of file diff --git a/src/store/collections/collection-copy-actions.ts b/src/store/collections/collection-copy-actions.ts new file mode 100644 index 00000000..80f577e7 --- /dev/null +++ b/src/store/collections/collection-copy-actions.ts @@ -0,0 +1,51 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch } from "redux"; +import { dialogActions } from "~/store/dialog/dialog-actions"; +import { initialize, startSubmit, stopSubmit } from 'redux-form'; +import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions'; +import { RootState } from '~/store/store'; +import { ServiceRepository } from '~/services/services'; +import { getCommonResourceServiceError, CommonResourceServiceError } from '~/common/api/common-resource-service'; +import { snackbarActions } from '~/store/snackbar/snackbar-actions'; +import { projectPanelActions } from '~/store/project-panel/project-panel-action'; + +export const COLLECTION_COPY_FORM_NAME = 'collectionCopyFormName'; + +export interface CollectionCopyFormDialogData { + name: string; + ownerUuid: string; + uuid: string; +} + +export const openCollectionCopyDialog = (resource: { name: string, uuid: string }) => + (dispatch: Dispatch) => { + dispatch(resetPickerProjectTree()); + const initialData: CollectionCopyFormDialogData = { name: `Copy of: ${resource.name}`, ownerUuid: '', uuid: resource.uuid }; + dispatch(initialize(COLLECTION_COPY_FORM_NAME, initialData)); + dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_COPY_FORM_NAME, data: {} })); + }; + +export const copyCollection = (resource: CollectionCopyFormDialogData) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch(startSubmit(COLLECTION_COPY_FORM_NAME)); + try { + const collection = await services.collectionService.get(resource.uuid); + const uuidKey = 'uuid'; + delete collection[uuidKey]; + await services.collectionService.create({ ...collection, ownerUuid: resource.ownerUuid, name: resource.name }); + dispatch(projectPanelActions.REQUEST_ITEMS()); + dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_COPY_FORM_NAME })); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection has been copied', hideDuration: 2000 })); + } catch (e) { + const error = getCommonResourceServiceError(e); + if (error === CommonResourceServiceError.UNIQUE_VIOLATION) { + dispatch(stopSubmit(COLLECTION_COPY_FORM_NAME, { ownerUuid: 'A collection with the same name already exists in the target project.' })); + } else { + dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_COPY_FORM_NAME })); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not copy the collection', hideDuration: 2000 })); + } + } + }; \ No newline at end of file diff --git a/src/store/collections/collection-create-actions.ts b/src/store/collections/collection-create-actions.ts new file mode 100644 index 00000000..d8d292c0 --- /dev/null +++ b/src/store/collections/collection-create-actions.ts @@ -0,0 +1,55 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch } from "redux"; +import { reset, startSubmit, stopSubmit, initialize } from 'redux-form'; +import { RootState } from '~/store/store'; +import { uploadCollectionFiles } from '~/store/collections/uploader/collection-uploader-actions'; +import { projectPanelActions } from "~/store/project-panel/project-panel-action"; +import { snackbarActions } from "~/store/snackbar/snackbar-actions"; +import { dialogActions } from "~/store/dialog/dialog-actions"; +import { CollectionResource } from '~/models/collection'; +import { ServiceRepository } from '~/services/services'; +import { getCommonResourceServiceError, CommonResourceServiceError } from "~/common/api/common-resource-service"; + +export interface CollectionCreateFormDialogData { + ownerUuid: string; + name: string; + description: string; + files: File[]; +} + +export const COLLECTION_CREATE_FORM_NAME = "collectionCreateFormName"; + +export const openCollectionCreateDialog = (ownerUuid: string) => + (dispatch: Dispatch) => { + dispatch(initialize(COLLECTION_CREATE_FORM_NAME, { ownerUuid })); + dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_CREATE_FORM_NAME, data: { ownerUuid } })); + }; + +export const addCollection = (data: CollectionCreateFormDialogData) => + async (dispatch: Dispatch) => { + await dispatch(createCollection(data)); + dispatch(snackbarActions.OPEN_SNACKBAR({ + message: "Collection has been successfully created.", + hideDuration: 2000 + })); + }; + +export const createCollection = (collection: Partial) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch(startSubmit(COLLECTION_CREATE_FORM_NAME)); + try { + const newCollection = await services.collectionService.create(collection); + await dispatch(uploadCollectionFiles(newCollection.uuid)); + dispatch(projectPanelActions.REQUEST_ITEMS()); + dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_CREATE_FORM_NAME })); + dispatch(reset(COLLECTION_CREATE_FORM_NAME)); + } catch (e) { + const error = getCommonResourceServiceError(e); + if (error === CommonResourceServiceError.UNIQUE_VIOLATION) { + dispatch(stopSubmit(COLLECTION_CREATE_FORM_NAME, { name: 'Collection with the same name already exists.' })); + } + } + }; \ No newline at end of file diff --git a/src/store/move-collection-dialog/move-collection-dialog.ts b/src/store/collections/collection-move-actions.ts similarity index 72% rename from src/store/move-collection-dialog/move-collection-dialog.ts rename to src/store/collections/collection-move-actions.ts index 59506799..6fc836f8 100644 --- a/src/store/move-collection-dialog/move-collection-dialog.ts +++ b/src/store/collections/collection-move-actions.ts @@ -10,33 +10,33 @@ import { RootState } from '~/store/store'; import { getCommonResourceServiceError, CommonResourceServiceError } from "~/common/api/common-resource-service"; import { snackbarActions } from '~/store/snackbar/snackbar-actions'; import { projectPanelActions } from '~/store/project-panel/project-panel-action'; -import { MoveToFormDialogData } from '../move-to-dialog/move-to-dialog'; +import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog'; import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions'; -export const MOVE_COLLECTION_DIALOG = 'moveCollectionDialog'; +export const COLLECTION_MOVE_FORM_NAME = 'collectionMoveFormName'; export const openMoveCollectionDialog = (resource: { name: string, uuid: string }) => (dispatch: Dispatch) => { dispatch(resetPickerProjectTree()); - dispatch(initialize(MOVE_COLLECTION_DIALOG, resource)); - dispatch(dialogActions.OPEN_DIALOG({ id: MOVE_COLLECTION_DIALOG, data: {} })); + dispatch(initialize(COLLECTION_MOVE_FORM_NAME, resource)); + dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_MOVE_FORM_NAME, data: {} })); }; export const moveCollection = (resource: MoveToFormDialogData) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(startSubmit(MOVE_COLLECTION_DIALOG)); + dispatch(startSubmit(COLLECTION_MOVE_FORM_NAME)); try { const collection = await services.collectionService.get(resource.uuid); await services.collectionService.update(resource.uuid, { ...collection, ownerUuid: resource.ownerUuid }); dispatch(projectPanelActions.REQUEST_ITEMS()); - dispatch(dialogActions.CLOSE_DIALOG({ id: MOVE_COLLECTION_DIALOG })); + dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_MOVE_FORM_NAME })); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Collection has been moved', hideDuration: 2000 })); } catch (e) { const error = getCommonResourceServiceError(e); if (error === CommonResourceServiceError.UNIQUE_VIOLATION) { - dispatch(stopSubmit(MOVE_COLLECTION_DIALOG, { ownerUuid: 'A collection with the same name already exists in the target project.' })); + dispatch(stopSubmit(COLLECTION_MOVE_FORM_NAME, { ownerUuid: 'A collection with the same name already exists in the target project.' })); } else { - dispatch(dialogActions.CLOSE_DIALOG({ id: MOVE_COLLECTION_DIALOG })); + dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_MOVE_FORM_NAME })); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not move the collection.', hideDuration: 2000 })); } } diff --git a/src/store/collections/collection-update-actions.ts b/src/store/collections/collection-update-actions.ts new file mode 100644 index 00000000..27c3bfce --- /dev/null +++ b/src/store/collections/collection-update-actions.ts @@ -0,0 +1,58 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch } from "redux"; +import { initialize, startSubmit, stopSubmit } from 'redux-form'; +import { RootState } from "~/store/store"; +import { collectionPanelActions } from "~/store/collection-panel/collection-panel-action"; +import { updateDetails } from "~/store/details-panel/details-panel-action"; +import { dialogActions } from "~/store/dialog/dialog-actions"; +import { dataExplorerActions } from "~/store/data-explorer/data-explorer-action"; +import { snackbarActions } from "~/store/snackbar/snackbar-actions"; +import { ContextMenuResource } from '~/store/context-menu/context-menu-reducer'; +import { PROJECT_PANEL_ID } from "~/views/project-panel/project-panel"; +import { getCommonResourceServiceError, CommonResourceServiceError } from "~/common/api/common-resource-service"; +import { ServiceRepository } from "~/services/services"; +import { CollectionResource } from '~/models/collection'; + +export interface CollectionUpdateFormDialogData { + uuid: string; + name: string; + description: string; +} + +export const COLLECTION_UPDATE_FORM_NAME = 'collectionUpdateFormName'; + +export const openCollectionUpdateDialog = (resource: ContextMenuResource) => + (dispatch: Dispatch) => { + dispatch(initialize(COLLECTION_UPDATE_FORM_NAME, resource)); + dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_UPDATE_FORM_NAME, data: {} })); + }; + +export const editCollection = (data: CollectionUpdateFormDialogData) => + async (dispatch: Dispatch) => { + await dispatch(updateCollection(data)); + dispatch(snackbarActions.OPEN_SNACKBAR({ + message: "Collection has been successfully updated.", + hideDuration: 2000 + })); + }; + +export const updateCollection = (collection: Partial) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const uuid = collection.uuid || ''; + dispatch(startSubmit(COLLECTION_UPDATE_FORM_NAME)); + try { + const updatedCollection = await services.collectionService.update(uuid, collection); + dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: updatedCollection as CollectionResource })); + dispatch(updateDetails(updatedCollection)); + dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID })); + dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_UPDATE_FORM_NAME })); + } catch(e) { + const error = getCommonResourceServiceError(e); + if (error === CommonResourceServiceError.UNIQUE_VIOLATION) { + dispatch(stopSubmit(COLLECTION_UPDATE_FORM_NAME, { name: 'Collection with the same name already exists.' })); + } + } + }; \ No newline at end of file diff --git a/src/store/collections/collections-reducer.ts b/src/store/collections/collections-reducer.ts index b2ee4550..c7601505 100644 --- a/src/store/collections/collections-reducer.ts +++ b/src/store/collections/collections-reducer.ts @@ -3,18 +3,12 @@ // SPDX-License-Identifier: AGPL-3.0 import { combineReducers } from 'redux'; -import { collectionCreatorReducer, CollectionCreatorState } from "./creator/collection-creator-reducer"; -import { collectionUpdaterReducer, CollectionUpdaterState } from "./updater/collection-updater-reducer"; import { collectionUploaderReducer, CollectionUploaderState } from "./uploader/collection-uploader-reducer"; export type CollectionsState = { - creator: CollectionCreatorState; - updater: CollectionUpdaterState; uploader: CollectionUploaderState }; export const collectionsReducer = combineReducers({ - creator: collectionCreatorReducer, - updater: collectionUpdaterReducer, uploader: collectionUploaderReducer }); diff --git a/src/store/collections/creator/collection-creator-reducer.test.ts b/src/store/collections/creator/collection-creator-reducer.test.ts deleted file mode 100644 index 5aa9dcfe..00000000 --- a/src/store/collections/creator/collection-creator-reducer.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { collectionCreatorReducer } from "./collection-creator-reducer"; -import { collectionCreateActions } from "./collection-creator-action"; - -describe('collection-reducer', () => { - - it('should open collection creator dialog', () => { - const initialState = { opened: false, ownerUuid: "" }; - const collection = { opened: true, ownerUuid: "" }; - - const state = collectionCreatorReducer(initialState, collectionCreateActions.OPEN_COLLECTION_CREATOR(initialState)); - expect(state).toEqual(collection); - }); - - it('should close collection creator dialog', () => { - const initialState = { opened: true, ownerUuid: "" }; - const collection = { opened: false, ownerUuid: "" }; - - const state = collectionCreatorReducer(initialState, collectionCreateActions.CLOSE_COLLECTION_CREATOR()); - expect(state).toEqual(collection); - }); - - it('should reset collection creator dialog props', () => { - const initialState = { opened: true, ownerUuid: "test" }; - const collection = { opened: false, ownerUuid: "" }; - - const state = collectionCreatorReducer(initialState, collectionCreateActions.CREATE_COLLECTION_SUCCESS()); - expect(state).toEqual(collection); - }); -}); diff --git a/src/store/collections/creator/collection-creator-reducer.ts b/src/store/collections/creator/collection-creator-reducer.ts deleted file mode 100644 index 72c999d0..00000000 --- a/src/store/collections/creator/collection-creator-reducer.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { collectionCreateActions, CollectionCreateAction } from './collection-creator-action'; - -export interface CollectionCreatorState { - opened: boolean; - ownerUuid: string; -} - -const updateCreator = (state: CollectionCreatorState, creator?: Partial) => ({ - ...state, - ...creator -}); - -const initialState: CollectionCreatorState = { - opened: false, - ownerUuid: '' -}; - -export const collectionCreatorReducer = (state: CollectionCreatorState = initialState, action: CollectionCreateAction) => { - return collectionCreateActions.match(action, { - OPEN_COLLECTION_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true }), - CLOSE_COLLECTION_CREATOR: () => updateCreator(state, { opened: false }), - CREATE_COLLECTION: () => updateCreator(state), - CREATE_COLLECTION_SUCCESS: () => updateCreator(state, { opened: false, ownerUuid: "" }), - default: () => state - }); -}; diff --git a/src/store/collections/updater/collection-updater-reducer.ts b/src/store/collections/updater/collection-updater-reducer.ts deleted file mode 100644 index 97d010fc..00000000 --- a/src/store/collections/updater/collection-updater-reducer.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { collectionUpdaterActions, CollectionUpdaterAction } from './collection-updater-action'; - -export interface CollectionUpdaterState { - opened: boolean; - uuid: string; -} - -const updateCollection = (state: CollectionUpdaterState, updater?: Partial) => ({ - ...state, - ...updater -}); - -const initialState: CollectionUpdaterState = { - opened: false, - uuid: '' -}; - -export const collectionUpdaterReducer = (state: CollectionUpdaterState = initialState, action: CollectionUpdaterAction) => { - return collectionUpdaterActions.match(action, { - OPEN_COLLECTION_UPDATER: ({ uuid }) => updateCollection(state, { uuid, opened: true }), - CLOSE_COLLECTION_UPDATER: () => updateCollection(state, { opened: false, uuid: "" }), - UPDATE_COLLECTION_SUCCESS: () => updateCollection(state, { opened: false, uuid: "" }), - default: () => state - }); -}; diff --git a/src/store/project/project-action.ts b/src/store/project/project-action.ts index 53e09cc6..4f03ae1c 100644 --- a/src/store/project/project-action.ts +++ b/src/store/project/project-action.ts @@ -9,18 +9,9 @@ import { FilterBuilder } from "~/common/api/filter-builder"; import { RootState } from "../store"; import { updateFavorites } from "../favorites/favorites-actions"; import { ServiceRepository } from "~/services/services"; -import { projectPanelActions } from "~/store/project-panel/project-panel-action"; -import { resourcesActions } from "~/store/resources/resources-actions"; -import { reset } from 'redux-form'; +import { resourcesActions } from '~/store/resources/resources-actions'; export const projectActions = unionize({ - OPEN_PROJECT_CREATOR: ofType<{ ownerUuid: string }>(), - CLOSE_PROJECT_CREATOR: ofType<{}>(), - CREATE_PROJECT: ofType>(), - CREATE_PROJECT_SUCCESS: ofType(), - OPEN_PROJECT_UPDATER: ofType<{ uuid: string }>(), - CLOSE_PROJECT_UPDATER: ofType<{}>(), - UPDATE_PROJECT_SUCCESS: ofType(), REMOVE_PROJECT: ofType(), PROJECTS_REQUEST: ofType(), PROJECTS_SUCCESS: ofType<{ projects: ProjectResource[], parentItemId?: string }>(), @@ -29,9 +20,7 @@ export const projectActions = unionize({ RESET_PROJECT_TREE_ACTIVITY: ofType() }); -export const PROJECT_FORM_NAME = 'projectEditDialog'; - -export const getProjectList = (parentUuid: string = '') => +export const getProjectList = (parentUuid: string = '') => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { dispatch(projectActions.PROJECTS_REQUEST(parentUuid)); return services.projectService.list({ @@ -41,39 +30,9 @@ export const getProjectList = (parentUuid: string = '') => }).then(({ items: projects }) => { dispatch(projectActions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid })); dispatch(updateFavorites(projects.map(project => project.uuid))); + dispatch(resourcesActions.SET_RESOURCES(projects)); return projects; }); }; -export const createProject = (project: Partial) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const { ownerUuid } = getState().projects.creator; - const projectData = { ownerUuid, ...project }; - dispatch(projectActions.CREATE_PROJECT(projectData)); - return services.projectService - .create(projectData) - .then(project => dispatch(projectActions.CREATE_PROJECT_SUCCESS(project))); - }; - -export const updateProject = (project: Partial) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const { uuid } = getState().projects.updater; - return services.projectService - .update(uuid, project) - .then(project => { - dispatch(projectActions.UPDATE_PROJECT_SUCCESS(project)); - dispatch(projectPanelActions.REQUEST_ITEMS()); - dispatch(getProjectList(project.ownerUuid)); - dispatch(resourcesActions.SET_RESOURCES([project])); - }); - }; - -export const PROJECT_CREATE_DIALOG = "projectCreateDialog"; - -export const openProjectCreator = (ownerUuid: string) => - (dispatch: Dispatch) => { - dispatch(reset(PROJECT_CREATE_DIALOG)); - dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid })); - }; - export type ProjectAction = UnionOf; diff --git a/src/store/project/project-reducer.test.ts b/src/store/project/project-reducer.test.ts index 8cd3121e..56a62534 100644 --- a/src/store/project/project-reducer.test.ts +++ b/src/store/project/project-reducer.test.ts @@ -31,15 +31,7 @@ describe('project-reducer', () => { status: TreeItemStatus.INITIAL } ], - currentItemId: "", - creator: { - opened: false, - ownerUuid: "", - }, - updater: { - opened: false, - uuid: '' - } + currentItemId: "" }); }); @@ -52,9 +44,7 @@ describe('project-reducer', () => { active: true, status: TreeItemStatus.PENDING }], - currentItemId: "1", - creator: { opened: false, ownerUuid: "" }, - updater: { opened: false, uuid: '' } + currentItemId: "1" }; const project = { items: [{ @@ -64,9 +54,7 @@ describe('project-reducer', () => { active: false, status: TreeItemStatus.PENDING }], - currentItemId: "", - creator: { opened: false, ownerUuid: "" }, - updater: { opened: false, uuid: '' } + currentItemId: "" }; const state = projectsReducer(initialState, projectActions.RESET_PROJECT_TREE_ACTIVITY(initialState.items[0].id)); @@ -82,9 +70,7 @@ describe('project-reducer', () => { active: false, status: TreeItemStatus.PENDING }], - currentItemId: "1", - creator: { opened: false, ownerUuid: "" }, - updater: { opened: false, uuid: '' } + currentItemId: "1" }; const project = { items: [{ @@ -94,9 +80,7 @@ describe('project-reducer', () => { active: true, status: TreeItemStatus.PENDING, }], - currentItemId: "1", - creator: { opened: false, ownerUuid: "" }, - updater: { opened: false, uuid: '' } + currentItemId: "1" }; const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(initialState.items[0].id)); @@ -113,9 +97,7 @@ describe('project-reducer', () => { active: false, status: TreeItemStatus.PENDING, }], - currentItemId: "1", - creator: { opened: false, ownerUuid: "" }, - updater: { opened: false, uuid: '' } + currentItemId: "1" }; const project = { items: [{ @@ -125,10 +107,7 @@ describe('project-reducer', () => { active: false, status: TreeItemStatus.PENDING, }], - currentItemId: "1", - creator: { opened: false, ownerUuid: "" }, - updater: { opened: false, uuid: '' } - + currentItemId: "1" }; const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(initialState.items[0].id)); diff --git a/src/store/project/project-reducer.ts b/src/store/project/project-reducer.ts index bb074865..452f6be3 100644 --- a/src/store/project/project-reducer.ts +++ b/src/store/project/project-reducer.ts @@ -10,17 +10,9 @@ import { ProjectResource } from "~/models/project"; export type ProjectState = { items: Array>, - currentItemId: string, - creator: ProjectCreator, - updater: ProjectUpdater + currentItemId: string }; -interface ProjectCreator { - opened: boolean; - ownerUuid: string; - error?: string; -} - interface ProjectUpdater { opened: boolean; uuid: string; @@ -98,45 +90,14 @@ function updateProjectTree(tree: Array>, projects: Pro return items; } -const updateCreator = (state: ProjectState, creator: Partial) => ({ - ...state, - creator: { - ...state.creator, - ...creator - } -}); - -const updateProject = (state: ProjectState, updater?: Partial) => ({ - ...state, - updater: { - ...state.updater, - ...updater - } -}); - const initialState: ProjectState = { items: [], - currentItemId: "", - creator: { - opened: false, - ownerUuid: "" - }, - updater: { - opened: false, - uuid: '' - } + currentItemId: "" }; export const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => { return projectActions.match(action, { - OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true }), - CLOSE_PROJECT_CREATOR: () => updateCreator(state, { opened: false }), - CREATE_PROJECT: () => updateCreator(state, { error: undefined }), - CREATE_PROJECT_SUCCESS: () => updateCreator(state, { opened: false, ownerUuid: "" }), - OPEN_PROJECT_UPDATER: ({ uuid }) => updateProject(state, { uuid, opened: true }), - CLOSE_PROJECT_UPDATER: () => updateProject(state, { opened: false, uuid: "" }), - UPDATE_PROJECT_SUCCESS: () => updateProject(state, { opened: false, uuid: "" }), REMOVE_PROJECT: () => state, PROJECTS_REQUEST: itemId => { const items = _.cloneDeep(state.items); diff --git a/src/store/projects/project-create-actions.ts b/src/store/projects/project-create-actions.ts new file mode 100644 index 00000000..d1bc827f --- /dev/null +++ b/src/store/projects/project-create-actions.ts @@ -0,0 +1,56 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch } from "redux"; +import { reset, startSubmit, stopSubmit, initialize } from 'redux-form'; +import { RootState } from '~/store/store'; +import { snackbarActions } from '~/store/snackbar/snackbar-actions'; +import { dialogActions } from "~/store/dialog/dialog-actions"; +import { projectPanelActions } from '~/store/project-panel/project-panel-action'; +import { getProjectList } from '~/store/project/project-action'; +import { getCommonResourceServiceError, CommonResourceServiceError } from '~/common/api/common-resource-service'; +import { ProjectResource } from '~/models/project'; +import { ServiceRepository } from '~/services/services'; + + +export interface ProjectCreateFormDialogData { + ownerUuid: string; + name: string; + description: string; +} + +export const PROJECT_CREATE_FORM_NAME = 'projectCreateFormName'; + +export const openProjectCreateDialog = (ownerUuid: string) => + (dispatch: Dispatch) => { + dispatch(initialize(PROJECT_CREATE_FORM_NAME, { ownerUuid })); + dispatch(dialogActions.OPEN_DIALOG({ id: PROJECT_CREATE_FORM_NAME, data: {} })); + }; + +export const addProject = (data: ProjectCreateFormDialogData) => + async (dispatch: Dispatch) => { + await dispatch(createProject(data)); + dispatch(snackbarActions.OPEN_SNACKBAR({ + message: "Project has been successfully created.", + hideDuration: 2000 + })); + }; + + +const createProject = (project: Partial) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch(startSubmit(PROJECT_CREATE_FORM_NAME)); + try { + const newProject = await services.projectService.create(project); + dispatch(getProjectList(newProject.ownerUuid)); + dispatch(projectPanelActions.REQUEST_ITEMS()); + dispatch(dialogActions.CLOSE_DIALOG({ id: PROJECT_CREATE_FORM_NAME })); + dispatch(reset(PROJECT_CREATE_FORM_NAME)); + } catch (e) { + const error = getCommonResourceServiceError(e); + if (error === CommonResourceServiceError.UNIQUE_VIOLATION) { + dispatch(stopSubmit(PROJECT_CREATE_FORM_NAME, { name: 'Project with the same name already exists.' })); + } + } + }; \ No newline at end of file diff --git a/src/store/move-project-dialog/move-project-dialog.ts b/src/store/projects/project-move-actions.ts similarity index 71% rename from src/store/move-project-dialog/move-project-dialog.ts rename to src/store/projects/project-move-actions.ts index 9e8e6f9b..52d17ac8 100644 --- a/src/store/move-project-dialog/move-project-dialog.ts +++ b/src/store/projects/project-move-actions.ts @@ -11,22 +11,22 @@ import { getCommonResourceServiceError, CommonResourceServiceError } from "~/com import { snackbarActions } from '~/store/snackbar/snackbar-actions'; import { projectPanelActions } from '~/store/project-panel/project-panel-action'; import { getProjectList } from '~/store/project/project-action'; -import { MoveToFormDialogData } from '../move-to-dialog/move-to-dialog'; +import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog'; import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions'; -import { findTreeItem } from '../project/project-reducer'; +import { findTreeItem } from '~/store/project/project-reducer'; -export const MOVE_PROJECT_DIALOG = 'moveProjectDialog'; +export const PROJECT_MOVE_FORM_NAME = 'projectMoveFormName'; export const openMoveProjectDialog = (resource: { name: string, uuid: string }) => (dispatch: Dispatch) => { dispatch(resetPickerProjectTree()); - dispatch(initialize(MOVE_PROJECT_DIALOG, resource)); - dispatch(dialogActions.OPEN_DIALOG({ id: MOVE_PROJECT_DIALOG, data: {} })); + dispatch(initialize(PROJECT_MOVE_FORM_NAME, resource)); + dispatch(dialogActions.OPEN_DIALOG({ id: PROJECT_MOVE_FORM_NAME, data: {} })); }; export const moveProject = (resource: MoveToFormDialogData) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(startSubmit(MOVE_PROJECT_DIALOG)); + dispatch(startSubmit(PROJECT_MOVE_FORM_NAME)); try { const project = await services.projectService.get(resource.uuid); await services.projectService.update(resource.uuid, { ...project, ownerUuid: resource.ownerUuid }); @@ -36,16 +36,16 @@ export const moveProject = (resource: MoveToFormDialogData) => if (findTreeItem(projects.items, resource.ownerUuid)) { dispatch(getProjectList(resource.ownerUuid)); } - dispatch(dialogActions.CLOSE_DIALOG({ id: MOVE_PROJECT_DIALOG })); + dispatch(dialogActions.CLOSE_DIALOG({ id: PROJECT_MOVE_FORM_NAME })); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Project has been moved', hideDuration: 2000 })); } catch (e) { const error = getCommonResourceServiceError(e); if (error === CommonResourceServiceError.UNIQUE_VIOLATION) { - dispatch(stopSubmit(MOVE_PROJECT_DIALOG, { ownerUuid: 'A project with the same name already exists in the target project.' })); + dispatch(stopSubmit(PROJECT_MOVE_FORM_NAME, { ownerUuid: 'A project with the same name already exists in the target project.' })); } else if (error === CommonResourceServiceError.OWNERSHIP_CYCLE) { - dispatch(stopSubmit(MOVE_PROJECT_DIALOG, { ownerUuid: 'Cannot move a project into itself.' })); + dispatch(stopSubmit(PROJECT_MOVE_FORM_NAME, { ownerUuid: 'Cannot move a project into itself.' })); } else { - dispatch(dialogActions.CLOSE_DIALOG({ id: MOVE_PROJECT_DIALOG })); + dispatch(dialogActions.CLOSE_DIALOG({ id: PROJECT_MOVE_FORM_NAME })); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not move the project.', hideDuration: 2000 })); } } diff --git a/src/store/projects/project-update-actions.ts b/src/store/projects/project-update-actions.ts new file mode 100644 index 00000000..e674d296 --- /dev/null +++ b/src/store/projects/project-update-actions.ts @@ -0,0 +1,57 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch } from "redux"; +import { initialize, startSubmit, stopSubmit } from 'redux-form'; +import { RootState } from "~/store/store"; +import { updateDetails } from "~/store/details-panel/details-panel-action"; +import { dialogActions } from "~/store/dialog/dialog-actions"; +import { snackbarActions } from "~/store/snackbar/snackbar-actions"; +import { ContextMenuResource } from '~/store/context-menu/context-menu-reducer'; +import { getCommonResourceServiceError, CommonResourceServiceError } from "~/common/api/common-resource-service"; +import { ServiceRepository } from "~/services/services"; +import { ProjectResource } from '~/models/project'; +import { getProjectList } from '~/store/project/project-action'; +import { projectPanelActions } from '~/store/project-panel/project-panel-action'; + +export interface ProjectUpdateFormDialogData { + uuid: string; + name: string; + description: string; +} + +export const PROJECT_UPDATE_FORM_NAME = 'projectUpdateFormName'; + +export const openProjectUpdateDialog = (resource: ContextMenuResource) => + (dispatch: Dispatch) => { + dispatch(initialize(PROJECT_UPDATE_FORM_NAME, resource)); + dispatch(dialogActions.OPEN_DIALOG({ id: PROJECT_UPDATE_FORM_NAME, data: {} })); + }; + +export const editProject = (data: ProjectUpdateFormDialogData) => + async (dispatch: Dispatch) => { + await dispatch(updateProject(data)); + dispatch(snackbarActions.OPEN_SNACKBAR({ + message: "Project has been successfully updated.", + hideDuration: 2000 + })); + }; + +export const updateProject = (project: Partial) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const uuid = project.uuid || ''; + dispatch(startSubmit(PROJECT_UPDATE_FORM_NAME)); + try { + const updatedProject = await services.projectService.update(uuid, project); + dispatch(projectPanelActions.REQUEST_ITEMS()); + dispatch(getProjectList(updatedProject.ownerUuid)); + dispatch(updateDetails(updatedProject)); + dispatch(dialogActions.CLOSE_DIALOG({ id: PROJECT_UPDATE_FORM_NAME })); + } catch (e) { + const error = getCommonResourceServiceError(e); + if (error === CommonResourceServiceError.UNIQUE_VIOLATION) { + dispatch(stopSubmit(PROJECT_UPDATE_FORM_NAME, { name: 'Project with the same name already exists.' })); + } + } + }; \ No newline at end of file diff --git a/src/validators/validators.tsx b/src/validators/validators.tsx index 106c74da..95edadfd 100644 --- a/src/validators/validators.tsx +++ b/src/validators/validators.tsx @@ -16,6 +16,6 @@ export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)]; export const COLLECTION_PROJECT_VALIDATION = [require]; export const COPY_NAME_VALIDATION = [require, maxLength(255)]; -export const MAKE_A_COPY_VALIDATION = [require, maxLength(255)]; +export const COPY_FILE_VALIDATION = [require]; export const MOVE_TO_VALIDATION = [require]; diff --git a/src/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog.tsx b/src/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog.tsx index e230470b..86fc360e 100644 --- a/src/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog.tsx +++ b/src/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog.tsx @@ -6,9 +6,9 @@ import * as React from "react"; import { compose } from "redux"; import { reduxForm, InjectedFormProps } from 'redux-form'; import { withDialog, WithDialogProps } from '~/store/dialog/with-dialog'; -import { CollectionPartialCopyFields } from '../form-dialog/collection-form-dialog'; -import { FormDialog } from '~/components/form-dialog/form-dialog'; import { COLLECTION_PARTIAL_COPY, doCollectionPartialCopy, CollectionPartialCopyFormData } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions'; +import { CollectionPartialCopyFields } from '~/views-components/form-fields/collection-form-fields'; +import { FormDialog } from '~/components/form-dialog/form-dialog'; export const CollectionPartialCopyDialog = compose( withDialog(COLLECTION_PARTIAL_COPY), diff --git a/src/views-components/context-menu/action-sets/collection-action-set.ts b/src/views-components/context-menu/action-sets/collection-action-set.ts index 5fb4f2d1..b3fdc3fb 100644 --- a/src/views-components/context-menu/action-sets/collection-action-set.ts +++ b/src/views-components/context-menu/action-sets/collection-action-set.ts @@ -6,17 +6,17 @@ import { ContextMenuActionSet } from "../context-menu-action-set"; import { ToggleFavoriteAction } from "../actions/favorite-action"; import { toggleFavorite } from "~/store/favorites/favorites-actions"; import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, ProvenanceGraphIcon, AdvancedIcon, RemoveIcon } from "~/components/icon/icon"; -import { openUpdater } from "~/store/collections/updater/collection-updater-action"; +import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions"; import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; -import { openMoveCollectionDialog } from '~/store/move-collection-dialog/move-collection-dialog'; -import { openProjectCopyDialog } from "~/views-components/project-copy-dialog/project-copy-dialog"; +import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions'; +import { openCollectionCopyDialog } from "~/store/collections/collection-copy-actions"; export const collectionActionSet: ContextMenuActionSet = [[ { icon: RenameIcon, name: "Edit collection", execute: (dispatch, resource) => { - dispatch(openUpdater(resource)); + dispatch(openCollectionUpdateDialog(resource)); } }, { @@ -43,7 +43,7 @@ export const collectionActionSet: ContextMenuActionSet = [[ icon: CopyIcon, name: "Copy to project", execute: (dispatch, resource) => { - dispatch(openProjectCopyDialog({name: resource.name, projectUuid: resource.uuid})); + dispatch(openCollectionCopyDialog(resource)); } }, { diff --git a/src/views-components/context-menu/action-sets/collection-resource-action-set.ts b/src/views-components/context-menu/action-sets/collection-resource-action-set.ts index ab93f6bb..a299b937 100644 --- a/src/views-components/context-menu/action-sets/collection-resource-action-set.ts +++ b/src/views-components/context-menu/action-sets/collection-resource-action-set.ts @@ -6,17 +6,17 @@ import { ContextMenuActionSet } from "../context-menu-action-set"; import { ToggleFavoriteAction } from "../actions/favorite-action"; import { toggleFavorite } from "~/store/favorites/favorites-actions"; import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, RemoveIcon } from "~/components/icon/icon"; -import { openUpdater } from "~/store/collections/updater/collection-updater-action"; +import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions"; import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; -import { openMoveCollectionDialog } from '~/store/move-collection-dialog/move-collection-dialog'; -import { openProjectCopyDialog } from "~/views-components/project-copy-dialog/project-copy-dialog"; +import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions'; +import { openCollectionCopyDialog } from '~/store/collections/collection-copy-actions'; export const collectionResourceActionSet: ContextMenuActionSet = [[ { icon: RenameIcon, name: "Edit collection", execute: (dispatch, resource) => { - dispatch(openUpdater(resource)); + dispatch(openCollectionUpdateDialog(resource)); } }, { @@ -43,7 +43,7 @@ export const collectionResourceActionSet: ContextMenuActionSet = [[ icon: CopyIcon, name: "Copy to project", execute: (dispatch, resource) => { - dispatch(openProjectCopyDialog({name: resource.name, projectUuid: resource.uuid})); + dispatch(openCollectionCopyDialog(resource)); }, }, { diff --git a/src/views-components/context-menu/action-sets/project-action-set.ts b/src/views-components/context-menu/action-sets/project-action-set.ts index 7f83fb24..af10aedf 100644 --- a/src/views-components/context-menu/action-sets/project-action-set.ts +++ b/src/views-components/context-menu/action-sets/project-action-set.ts @@ -2,28 +2,28 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { initialize } from "redux-form"; import { ContextMenuActionSet } from "../context-menu-action-set"; -import { projectActions, PROJECT_FORM_NAME, openProjectCreator } from '~/store/project/project-action'; import { NewProjectIcon, RenameIcon, CopyIcon, MoveToIcon } from "~/components/icon/icon"; import { ToggleFavoriteAction } from "../actions/favorite-action"; import { toggleFavorite } from "~/store/favorites/favorites-actions"; import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; -import { openMoveProjectDialog } from '~/store/move-project-dialog/move-project-dialog'; -import { openProjectCopyDialog } from "~/views-components/project-copy-dialog/project-copy-dialog"; +import { openMoveProjectDialog } from '~/store/projects/project-move-actions'; +import { openProjectCreateDialog } from '~/store/projects/project-create-actions'; +import { openProjectUpdateDialog } from '~/store/projects/project-update-actions'; export const projectActionSet: ContextMenuActionSet = [[ { icon: NewProjectIcon, name: "New project", - execute: (dispatch, resource) => dispatch(openProjectCreator(resource.uuid)) + execute: (dispatch, resource) => { + dispatch(openProjectCreateDialog(resource.uuid)); + } }, { icon: RenameIcon, name: "Edit project", execute: (dispatch, resource) => { - dispatch(projectActions.OPEN_PROJECT_UPDATER({ uuid: resource.uuid })); - dispatch(initialize(PROJECT_FORM_NAME, { name: resource.name, description: resource.description })); + dispatch(openProjectUpdateDialog(resource)); } }, { @@ -43,7 +43,7 @@ export const projectActionSet: ContextMenuActionSet = [[ icon: CopyIcon, name: "Copy to project", execute: (dispatch, resource) => { - dispatch(openProjectCopyDialog({name: resource.name, projectUuid: resource.uuid})); + // add code } - } + }, ]]; diff --git a/src/views-components/context-menu/action-sets/root-project-action-set.ts b/src/views-components/context-menu/action-sets/root-project-action-set.ts index eb4a9a30..386c5162 100644 --- a/src/views-components/context-menu/action-sets/root-project-action-set.ts +++ b/src/views-components/context-menu/action-sets/root-project-action-set.ts @@ -2,26 +2,24 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { reset } from "redux-form"; - import { ContextMenuActionSet } from "../context-menu-action-set"; -import { openProjectCreator } from "~/store/project/project-action"; -import { collectionCreateActions } from "~/store/collections/creator/collection-creator-action"; -import { COLLECTION_CREATE_DIALOG } from "../../dialog-create/dialog-collection-create"; +import { openCollectionCreateDialog } from '~/store/collections/collection-create-actions'; import { NewProjectIcon, CollectionIcon } from "~/components/icon/icon"; +import { openProjectCreateDialog } from '~/store/projects/project-create-actions'; export const rootProjectActionSet: ContextMenuActionSet = [[ { icon: NewProjectIcon, name: "New project", - execute: (dispatch, resource) => dispatch(openProjectCreator(resource.uuid)) + execute: (dispatch, resource) => { + dispatch(openProjectCreateDialog(resource.uuid)); + } }, { icon: CollectionIcon, name: "New Collection", execute: (dispatch, resource) => { - dispatch(reset(COLLECTION_CREATE_DIALOG)); - dispatch(collectionCreateActions.OPEN_COLLECTION_CREATOR({ ownerUuid: resource.uuid })); + dispatch(openCollectionCreateDialog(resource.uuid)); } } ]]; diff --git a/src/views-components/create-collection-dialog/create-collection-dialog.tsx b/src/views-components/create-collection-dialog/create-collection-dialog.tsx deleted file mode 100644 index 94eb82f9..00000000 --- a/src/views-components/create-collection-dialog/create-collection-dialog.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { SubmissionError } from "redux-form"; - -import { RootState } from "~/store/store"; -import { DialogCollectionCreate } from "../dialog-create/dialog-collection-create"; -import { collectionCreateActions, createCollection } from "~/store/collections/creator/collection-creator-action"; -import { snackbarActions } from "~/store/snackbar/snackbar-actions"; -import { UploadFile } from "~/store/collections/uploader/collection-uploader-actions"; -import { projectPanelActions } from "~/store/project-panel/project-panel-action"; - -const mapStateToProps = (state: RootState) => ({ - open: state.collections.creator.opened -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - handleClose: () => { - dispatch(collectionCreateActions.CLOSE_COLLECTION_CREATOR()); - }, - onSubmit: (data: { name: string, description: string }, files: UploadFile[]) => { - return dispatch(addCollection(data, files.map(f => f.file))) - .catch((e: any) => { - throw new SubmissionError({ name: e.errors.join("").includes("UniqueViolation") ? "Collection with this name already exists." : "" }); - }); - } -}); - -const addCollection = (data: { name: string, description: string }, files: File[]) => - (dispatch: Dispatch) => { - return dispatch(createCollection(data, files)).then(() => { - dispatch(snackbarActions.OPEN_SNACKBAR({ - message: "Collection has been successfully created.", - hideDuration: 2000 - })); - dispatch(projectPanelActions.REQUEST_ITEMS()); - }); - }; - -export const CreateCollectionDialog = connect(mapStateToProps, mapDispatchToProps)(DialogCollectionCreate); - diff --git a/src/views-components/create-project-dialog/create-project-dialog.tsx b/src/views-components/create-project-dialog/create-project-dialog.tsx deleted file mode 100644 index 43f56ed8..00000000 --- a/src/views-components/create-project-dialog/create-project-dialog.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { SubmissionError } from "redux-form"; - -import { RootState } from "~/store/store"; -import { DialogProjectCreate } from "../dialog-create/dialog-project-create"; -import { projectActions, createProject, getProjectList } from "~/store/project/project-action"; -import { projectPanelActions } from "~/store/project-panel/project-panel-action"; -import { snackbarActions } from "~/store/snackbar/snackbar-actions"; - -const mapStateToProps = (state: RootState) => ({ - open: state.projects.creator.opened -}); - -const addProject = (data: { name: string, description: string }) => - (dispatch: Dispatch, getState: () => RootState) => { - const { ownerUuid } = getState().projects.creator; - return dispatch(createProject(data)).then(() => { - dispatch(snackbarActions.OPEN_SNACKBAR({ - message: "Project has been successfully created.", - hideDuration: 2000 - })); - dispatch(projectPanelActions.REQUEST_ITEMS()); - dispatch(getProjectList(ownerUuid)); - }); - }; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - handleClose: () => { - dispatch(projectActions.CLOSE_PROJECT_CREATOR()); - }, - onSubmit: (data: { name: string, description: string }) => { - return dispatch(addProject(data)) - .catch((e: any) => { - throw new SubmissionError({ name: e.errors.join("").includes("UniqueViolation") ? "Project with this name already exists." : "" }); - }); - } -}); - -export const CreateProjectDialog = connect(mapStateToProps, mapDispatchToProps)(DialogProjectCreate); diff --git a/src/views-components/dialog-copy/dialog-collection-copy.tsx b/src/views-components/dialog-copy/dialog-collection-copy.tsx new file mode 100644 index 00000000..029db578 --- /dev/null +++ b/src/views-components/dialog-copy/dialog-collection-copy.tsx @@ -0,0 +1,34 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from "react"; +import { InjectedFormProps, Field } from 'redux-form'; +import { WithDialogProps } from '~/store/dialog/with-dialog'; +import { FormDialog } from '~/components/form-dialog/form-dialog'; +import { ProjectTreePickerField } from '~/views-components/project-tree-picker/project-tree-picker'; +import { COPY_NAME_VALIDATION, COPY_FILE_VALIDATION } from '~/validators/validators'; +import { TextField } from "~/components/text-field/text-field"; +import { CollectionCopyFormDialogData } from "~/store/collections/collection-copy-actions"; + +type CopyFormDialogProps = WithDialogProps & InjectedFormProps; + +export const DialogCollectionCopy = (props: CopyFormDialogProps) => + ; + +const CollectionCopyFields = () => + + +; diff --git a/src/views-components/dialog-create/dialog-collection-create.tsx b/src/views-components/dialog-create/dialog-collection-create.tsx index af0e33f1..2fb007b8 100644 --- a/src/views-components/dialog-create/dialog-collection-create.tsx +++ b/src/views-components/dialog-create/dialog-collection-create.tsx @@ -3,113 +3,38 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import { reduxForm, Field } from 'redux-form'; -import { compose } from 'redux'; -import { TextField } from '~/components/text-field/text-field'; -import { Dialog, DialogActions, DialogContent, DialogTitle } from '@material-ui/core/'; -import { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress } from '@material-ui/core'; - -import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION } from '~/validators/validators'; -import { FileUpload } from "~/components/file-upload/file-upload"; -import { connect, DispatchProp } from "react-redux"; -import { RootState } from "~/store/store"; +import { InjectedFormProps } from 'redux-form'; +import { WithDialogProps } from '~/store/dialog/with-dialog'; +import { CollectionCreateFormDialogData } from '~/store/collections/collection-create-actions'; import { collectionUploaderActions, UploadFile } from "~/store/collections/uploader/collection-uploader-actions"; - -type CssRules = "button" | "lastButton" | "formContainer" | "createProgress" | "dialogActions"; - -const styles: StyleRulesCallback = theme => ({ - button: { - marginLeft: theme.spacing.unit - }, - lastButton: { - marginLeft: theme.spacing.unit, - marginRight: "20px", - }, - formContainer: { - display: "flex", - flexDirection: "column", - }, - createProgress: { - position: "absolute", - minWidth: "20px", - right: "110px" - }, - dialogActions: { - marginBottom: theme.spacing.unit * 3 - } -}); - -interface DialogCollectionDataProps { - open: boolean; - handleSubmit: any; - submitting: boolean; - invalid: boolean; - pristine: boolean; - files: UploadFile[]; -} - -interface DialogCollectionActionProps { - handleClose: () => void; - onSubmit: (data: { name: string, description: string }, files: UploadFile[]) => void; -} - -type DialogCollectionProps = DialogCollectionDataProps & DialogCollectionActionProps & DispatchProp & WithStyles; - -export const COLLECTION_CREATE_DIALOG = "collectionCreateDialog"; - -export const DialogCollectionCreate = compose( - connect((state: RootState) => ({ - files: state.collections.uploader - })), - reduxForm({ form: COLLECTION_CREATE_DIALOG }), - withStyles(styles))( - class DialogCollectionCreate extends React.Component { - render() { - const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine, files } = this.props; - const busy = submitting || files.reduce( - (prev, curr) => prev + (curr.loaded > 0 && curr.loaded < curr.total ? 1 : 0), 0 - ) > 0; - return ( - -
onSubmit(data, files))}> - Create a collection - - - - this.props.dispatch(collectionUploaderActions.SET_UPLOAD_FILES(files))} /> - - - - - {busy && } - -
-
- ); - } - } - ); +import { FormDialog } from '~/components/form-dialog/form-dialog'; +import { CollectionNameField, CollectionDescriptionField } from '~/views-components/form-fields/collection-form-fields'; +import { FileUpload } from '~/components/file-upload/file-upload'; + +// interface DialogCollectionDataProps { +// open: boolean; +// handleSubmit: any; +// submitting: boolean; +// invalid: boolean; +// pristine: boolean; +// files: UploadFile[]; +// } + +type DialogCollectionProps = WithDialogProps<{}> & InjectedFormProps; + +export const DialogCollectionCreate = (props: DialogCollectionProps) => + ; + +const CollectionAddFields = () => + + + {/* this.props.dispatch(collectionUploaderActions.SET_UPLOAD_FILES(files))} /> */} +; \ No newline at end of file diff --git a/src/views-components/dialog-create/dialog-project-create.tsx b/src/views-components/dialog-create/dialog-project-create.tsx index d32582ea..8c1d3800 100644 --- a/src/views-components/dialog-create/dialog-project-create.tsx +++ b/src/views-components/dialog-create/dialog-project-create.tsx @@ -3,99 +3,23 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import { reduxForm, Field } from 'redux-form'; -import { compose } from 'redux'; -import { TextField } from '~/components/text-field/text-field'; -import { Dialog, DialogActions, DialogContent, DialogTitle } from '@material-ui/core/'; -import { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress } from '@material-ui/core'; +import { InjectedFormProps } from 'redux-form'; +import { WithDialogProps } from '~/store/dialog/with-dialog'; +import { ProjectCreateFormDialogData } from '~/store/projects/project-create-actions'; +import { FormDialog } from '~/components/form-dialog/form-dialog'; +import { ProjectNameField, ProjectDescriptionField } from '~/views-components/form-fields/project-form-fields'; -import { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from '~/validators/validators'; -import { PROJECT_CREATE_DIALOG } from '~/store/project/project-action'; +type DialogCollectionProps = WithDialogProps<{}> & InjectedFormProps; -type CssRules = "button" | "lastButton" | "formContainer" | "dialog" | "dialogTitle" | "createProgress" | "dialogActions"; +export const DialogProjectCreate = (props: DialogCollectionProps) => + ; -const styles: StyleRulesCallback = theme => ({ - button: { - marginLeft: theme.spacing.unit - }, - lastButton: { - marginLeft: theme.spacing.unit, - marginRight: "20px", - }, - formContainer: { - display: "flex", - flexDirection: "column", - marginTop: "20px", - }, - dialogTitle: { - paddingBottom: "0" - }, - dialog: { - minWidth: "600px", - minHeight: "320px" - }, - createProgress: { - position: "absolute", - minWidth: "20px", - right: "95px" - }, - dialogActions: { - marginBottom: "24px" - } -}); -interface DialogProjectProps { - open: boolean; - handleClose: () => void; - onSubmit: (data: { name: string, description: string }) => void; - handleSubmit: any; - submitting: boolean; - invalid: boolean; - pristine: boolean; -} - -export const DialogProjectCreate = compose( - reduxForm({ form: PROJECT_CREATE_DIALOG }), - withStyles(styles))( - class DialogProjectCreate extends React.Component> { - render() { - const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine } = this.props; - - return ( - -
-
onSubmit(data))}> - Create a - project - - - - - - - - {submitting && } - -
-
-
- ); - } - } -); +const ProjectAddFields = () => + + +; diff --git a/src/views-components/dialog-forms/copy-collection-dialog.ts b/src/views-components/dialog-forms/copy-collection-dialog.ts new file mode 100644 index 00000000..ee6293ab --- /dev/null +++ b/src/views-components/dialog-forms/copy-collection-dialog.ts @@ -0,0 +1,19 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { compose } from "redux"; +import { withDialog } from "~/store/dialog/with-dialog"; +import { reduxForm } from 'redux-form'; +import { COLLECTION_COPY_FORM_NAME, CollectionCopyFormDialogData, copyCollection } from '~/store/collections/collection-copy-actions'; +import { DialogCollectionCopy } from "~/views-components/dialog-copy/dialog-collection-copy"; + +export const CopyCollectionDialog = compose( + withDialog(COLLECTION_COPY_FORM_NAME), + reduxForm({ + form: COLLECTION_COPY_FORM_NAME, + onSubmit: (data, dispatch) => { + dispatch(copyCollection(data)); + } + }) +)(DialogCollectionCopy); \ No newline at end of file diff --git a/src/views-components/dialog-forms/create-collection-dialog.ts b/src/views-components/dialog-forms/create-collection-dialog.ts new file mode 100644 index 00000000..d2699d83 --- /dev/null +++ b/src/views-components/dialog-forms/create-collection-dialog.ts @@ -0,0 +1,22 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { compose } from "redux"; +import { reduxForm } from 'redux-form'; +import { withDialog } from "~/store/dialog/with-dialog"; +import { addCollection, COLLECTION_CREATE_FORM_NAME, CollectionCreateFormDialogData } from '~/store/collections/collection-create-actions'; +import { UploadFile } from "~/store/collections/uploader/collection-uploader-actions"; +import { DialogCollectionCreate } from "~/views-components/dialog-create/dialog-collection-create"; + +export const CreateCollectionDialog = compose( + withDialog(COLLECTION_CREATE_FORM_NAME), + reduxForm({ + form: COLLECTION_CREATE_FORM_NAME, + onSubmit: (data, dispatch) => { + dispatch(addCollection(data)); + } + }) +)(DialogCollectionCreate); + +// onSubmit: (data: { name: string, description: string }, files: UploadFile[]) => void; \ No newline at end of file diff --git a/src/views-components/dialog-forms/create-project-dialog.ts b/src/views-components/dialog-forms/create-project-dialog.ts new file mode 100644 index 00000000..2e87517c --- /dev/null +++ b/src/views-components/dialog-forms/create-project-dialog.ts @@ -0,0 +1,19 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { compose } from "redux"; +import { reduxForm } from 'redux-form'; +import { withDialog } from "~/store/dialog/with-dialog"; +import { addProject, PROJECT_CREATE_FORM_NAME, ProjectCreateFormDialogData } from '~/store/projects/project-create-actions'; +import { DialogProjectCreate } from '~/views-components/dialog-create/dialog-project-create'; + +export const CreateProjectDialog = compose( + withDialog(PROJECT_CREATE_FORM_NAME), + reduxForm({ + form: PROJECT_CREATE_FORM_NAME, + onSubmit: (data, dispatch) => { + dispatch(addProject(data)); + } + }) +)(DialogProjectCreate); \ No newline at end of file diff --git a/src/views-components/move-collection-dialog/move-collection-dialog.ts b/src/views-components/dialog-forms/move-collection-dialog.ts similarity index 63% rename from src/views-components/move-collection-dialog/move-collection-dialog.ts rename to src/views-components/dialog-forms/move-collection-dialog.ts index 783f0c78..38d6d033 100644 --- a/src/views-components/move-collection-dialog/move-collection-dialog.ts +++ b/src/views-components/dialog-forms/move-collection-dialog.ts @@ -5,16 +5,16 @@ import { compose } from "redux"; import { withDialog } from "~/store/dialog/with-dialog"; import { reduxForm } from 'redux-form'; -import { MoveToFormDialog } from '../move-to-dialog/move-to-dialog'; -import { MOVE_COLLECTION_DIALOG, moveCollection } from '~/store/move-collection-dialog/move-collection-dialog'; +import { DialogMoveTo } from '~/views-components/dialog-move/dialog-move-to'; +import { COLLECTION_MOVE_FORM_NAME, moveCollection } from '~/store/collections/collection-move-actions'; import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog'; export const MoveCollectionDialog = compose( - withDialog(MOVE_COLLECTION_DIALOG), + withDialog(COLLECTION_MOVE_FORM_NAME), reduxForm({ - form: MOVE_COLLECTION_DIALOG, + form: COLLECTION_MOVE_FORM_NAME, onSubmit: (data, dispatch) => { dispatch(moveCollection(data)); } }) -)(MoveToFormDialog); +)(DialogMoveTo); diff --git a/src/views-components/move-project-dialog/move-project-dialog.ts b/src/views-components/dialog-forms/move-project-dialog.ts similarity index 59% rename from src/views-components/move-project-dialog/move-project-dialog.ts rename to src/views-components/dialog-forms/move-project-dialog.ts index 9ec67486..dd102b14 100644 --- a/src/views-components/move-project-dialog/move-project-dialog.ts +++ b/src/views-components/dialog-forms/move-project-dialog.ts @@ -5,18 +5,18 @@ import { compose } from "redux"; import { withDialog } from "~/store/dialog/with-dialog"; import { reduxForm } from 'redux-form'; -import { MOVE_PROJECT_DIALOG } from '~/store/move-project-dialog/move-project-dialog'; -import { moveProject } from '~/store/move-project-dialog/move-project-dialog'; +import { PROJECT_MOVE_FORM_NAME } from '~/store/projects/project-move-actions'; +import { moveProject } from '~/store/projects/project-move-actions'; import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog'; -import { MoveToFormDialog } from '../move-to-dialog/move-to-dialog'; +import { DialogMoveTo } from '~/views-components/dialog-move/dialog-move-to'; export const MoveProjectDialog = compose( - withDialog(MOVE_PROJECT_DIALOG), + withDialog(PROJECT_MOVE_FORM_NAME), reduxForm({ - form: MOVE_PROJECT_DIALOG, + form: PROJECT_MOVE_FORM_NAME, onSubmit: (data, dispatch) => { dispatch(moveProject(data)); } }) -)(MoveToFormDialog); +)(DialogMoveTo); diff --git a/src/views-components/dialog-forms/update-collection-dialog.ts b/src/views-components/dialog-forms/update-collection-dialog.ts new file mode 100644 index 00000000..2c4296d8 --- /dev/null +++ b/src/views-components/dialog-forms/update-collection-dialog.ts @@ -0,0 +1,19 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { compose } from "redux"; +import { reduxForm } from 'redux-form'; +import { withDialog } from "~/store/dialog/with-dialog"; +import { DialogCollectionUpdate } from '~/views-components/dialog-update/dialog-collection-update'; +import { editCollection, COLLECTION_UPDATE_FORM_NAME, CollectionUpdateFormDialogData } from '~/store/collections/collection-update-actions'; + +export const UpdateCollectionDialog = compose( + withDialog(COLLECTION_UPDATE_FORM_NAME), + reduxForm({ + form: COLLECTION_UPDATE_FORM_NAME, + onSubmit: (data, dispatch) => { + dispatch(editCollection(data)); + } + }) +)(DialogCollectionUpdate); \ No newline at end of file diff --git a/src/views-components/dialog-forms/update-project-dialog.ts b/src/views-components/dialog-forms/update-project-dialog.ts new file mode 100644 index 00000000..598d0b19 --- /dev/null +++ b/src/views-components/dialog-forms/update-project-dialog.ts @@ -0,0 +1,19 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { compose } from "redux"; +import { reduxForm } from 'redux-form'; +import { withDialog } from "~/store/dialog/with-dialog"; +import { DialogProjectUpdate } from '~/views-components/dialog-update/dialog-project-update'; +import { editProject, PROJECT_UPDATE_FORM_NAME, ProjectUpdateFormDialogData } from '~/store/projects/project-update-actions'; + +export const UpdateProjectDialog = compose( + withDialog(PROJECT_UPDATE_FORM_NAME), + reduxForm({ + form: PROJECT_UPDATE_FORM_NAME, + onSubmit: (data, dispatch) => { + dispatch(editProject(data)); + } + }) +)(DialogProjectUpdate); \ No newline at end of file diff --git a/src/views-components/dialog-move/dialog-move-to.tsx b/src/views-components/dialog-move/dialog-move-to.tsx new file mode 100644 index 00000000..425b9e46 --- /dev/null +++ b/src/views-components/dialog-move/dialog-move-to.tsx @@ -0,0 +1,26 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from "react"; +import { InjectedFormProps, Field } from 'redux-form'; +import { WithDialogProps } from '~/store/dialog/with-dialog'; +import { FormDialog } from '~/components/form-dialog/form-dialog'; +import { ProjectTreePickerField } from '~/views-components/project-tree-picker/project-tree-picker'; +import { MOVE_TO_VALIDATION } from '~/validators/validators'; +import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog'; + +export const DialogMoveTo = (props: WithDialogProps & InjectedFormProps) => + ; + +const MoveToDialogFields = () => + ; + diff --git a/src/views-components/dialog-update/dialog-collection-update.tsx b/src/views-components/dialog-update/dialog-collection-update.tsx index 18c43f2d..b98e0e84 100644 --- a/src/views-components/dialog-update/dialog-collection-update.tsx +++ b/src/views-components/dialog-update/dialog-collection-update.tsx @@ -3,103 +3,23 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import { reduxForm, Field } from 'redux-form'; -import { compose } from 'redux'; -import { ArvadosTheme } from '~/common/custom-theme'; -import { Dialog, DialogActions, DialogContent, DialogTitle, StyleRulesCallback, withStyles, WithStyles, Button, CircularProgress } from '@material-ui/core'; -import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION } from '~/validators/validators'; -import { COLLECTION_FORM_NAME } from '~/store/collections/updater/collection-updater-action'; -import { TextField } from '~/components/text-field/text-field'; - -type CssRules = 'content' | 'actions' | 'buttonWrapper' | 'saveButton' | 'circularProgress'; - -const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ - content: { - display: 'flex', - flexDirection: 'column' - }, - actions: { - margin: 0, - padding: `${theme.spacing.unit}px ${theme.spacing.unit * 3 - theme.spacing.unit / 2}px - ${theme.spacing.unit * 3}px ${theme.spacing.unit * 3}px` - }, - buttonWrapper: { - position: 'relative' - }, - saveButton: { - boxShadow: 'none' - }, - circularProgress: { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 0, - margin: 'auto' - } -}); - -interface DialogCollectionDataProps { - open: boolean; - handleSubmit: any; - submitting: boolean; - invalid: boolean; - pristine: boolean; -} - -interface DialogCollectionAction { - handleClose: () => void; - onSubmit: (data: { name: string, description: string }) => void; -} - -type DialogCollectionProps = DialogCollectionDataProps & DialogCollectionAction & WithStyles; - -export const DialogCollectionUpdate = compose( - reduxForm({ form: COLLECTION_FORM_NAME }), - withStyles(styles))( - - class DialogCollectionUpdate extends React.Component { - - render() { - const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine } = this.props; - return ( - - -
onSubmit(data))}> - Edit Collection - - - - - - -
- - {submitting && } -
-
-
-
- ); - } - } - ); +import { InjectedFormProps } from 'redux-form'; +import { WithDialogProps } from '~/store/dialog/with-dialog'; +import { CollectionUpdateFormDialogData } from '~/store/collections/collection-update-actions'; +import { FormDialog } from '~/components/form-dialog/form-dialog'; +import { CollectionNameField, CollectionDescriptionField } from '~/views-components/form-fields/collection-form-fields'; + +type DialogCollectionProps = WithDialogProps<{}> & InjectedFormProps; + +export const DialogCollectionUpdate = (props: DialogCollectionProps) => + ; + +const CollectionEditFields = () => + + +; diff --git a/src/views-components/dialog-update/dialog-project-update.tsx b/src/views-components/dialog-update/dialog-project-update.tsx index b5e788c0..49b97a6f 100644 --- a/src/views-components/dialog-update/dialog-project-update.tsx +++ b/src/views-components/dialog-update/dialog-project-update.tsx @@ -3,99 +3,23 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; -import { reduxForm, Field } from 'redux-form'; -import { compose } from 'redux'; -import { ArvadosTheme } from '~/common/custom-theme'; -import { StyleRulesCallback, WithStyles, withStyles, Dialog, DialogTitle, DialogContent, DialogActions, CircularProgress, Button } from '../../../node_modules/@material-ui/core'; -import { TextField } from '~/components/text-field/text-field'; -import { PROJECT_FORM_NAME } from '~/store/project/project-action'; -import { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from '~/validators/validators'; - -type CssRules = 'content' | 'actions' | 'buttonWrapper' | 'saveButton' | 'circularProgress'; - -const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ - content: { - display: 'flex', - flexDirection: 'column' - }, - actions: { - margin: 0, - padding: `${theme.spacing.unit}px ${theme.spacing.unit * 3 - theme.spacing.unit / 2}px - ${theme.spacing.unit * 3}px ${theme.spacing.unit * 3}px` - }, - buttonWrapper: { - position: 'relative' - }, - saveButton: { - boxShadow: 'none' - }, - circularProgress: { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 0, - margin: 'auto' - } -}); - -interface DialogProjectDataProps { - open: boolean; - handleSubmit: any; - submitting: boolean; - invalid: boolean; - pristine: boolean; -} - -interface DialogProjectActionProps { - handleClose: () => void; - onSubmit: (data: { name: string, description: string }) => void; -} - -type DialogProjectProps = DialogProjectDataProps & DialogProjectActionProps & WithStyles; - -export const DialogProjectUpdate = compose( - reduxForm({ form: PROJECT_FORM_NAME }), - withStyles(styles))( - - class DialogProjectUpdate extends React.Component { - render() { - const { handleSubmit, handleClose, onSubmit, open, classes, submitting, invalid, pristine } = this.props; - return -
onSubmit(data))}> - Edit Project - - - - - - -
- - {submitting && } -
-
-
-
; - } - } - ); +import { InjectedFormProps } from 'redux-form'; +import { WithDialogProps } from '~/store/dialog/with-dialog'; +import { ProjectUpdateFormDialogData } from '~/store/projects/project-update-actions'; +import { FormDialog } from '~/components/form-dialog/form-dialog'; +import { ProjectNameField, ProjectDescriptionField } from '~/views-components/form-fields/project-form-fields'; + +type DialogProjectProps = WithDialogProps<{}> & InjectedFormProps; + +export const DialogProjectUpdate = (props: DialogProjectProps) => + ; + +const ProjectEditFields = () => + + +; diff --git a/src/views-components/form-dialog/collection-form-dialog.tsx b/src/views-components/form-fields/collection-form-fields.tsx similarity index 93% rename from src/views-components/form-dialog/collection-form-dialog.tsx rename to src/views-components/form-fields/collection-form-fields.tsx index d5f1d852..10c807b6 100644 --- a/src/views-components/form-dialog/collection-form-dialog.tsx +++ b/src/views-components/form-fields/collection-form-fields.tsx @@ -6,7 +6,7 @@ import * as React from "react"; import { Field, WrappedFieldProps } from "redux-form"; import { TextField } from "~/components/text-field/text-field"; import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION, COLLECTION_PROJECT_VALIDATION } from "~/validators/validators"; -import { ProjectTreePicker } from "../project-tree-picker/project-tree-picker"; +import { ProjectTreePicker } from "~/views-components/project-tree-picker/project-tree-picker"; export const CollectionPartialCopyFields = () =>
diff --git a/src/views-components/form-fields/project-form-fields.tsx b/src/views-components/form-fields/project-form-fields.tsx new file mode 100644 index 00000000..630877e7 --- /dev/null +++ b/src/views-components/form-fields/project-form-fields.tsx @@ -0,0 +1,22 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from "react"; +import { Field } from "redux-form"; +import { TextField } from "~/components/text-field/text-field"; +import { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from "~/validators/validators"; + +export const ProjectNameField = () => + ; + +export const ProjectDescriptionField = () => + ; \ No newline at end of file diff --git a/src/views-components/move-to-dialog/move-to-dialog.tsx b/src/views-components/move-to-dialog/move-to-dialog.tsx deleted file mode 100644 index 4c27722c..00000000 --- a/src/views-components/move-to-dialog/move-to-dialog.tsx +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import * as React from "react"; -import { InjectedFormProps, Field, WrappedFieldProps } from 'redux-form'; -import { WithDialogProps } from '~/store/dialog/with-dialog'; -import { FormDialog } from '~/components/form-dialog/form-dialog'; -import { ProjectTreePicker } from '~/views-components/project-tree-picker/project-tree-picker'; -import { Typography } from "@material-ui/core"; -import { MOVE_TO_VALIDATION } from '~/validators/validators'; -import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog'; - -export const MoveToFormDialog = (props: WithDialogProps & InjectedFormProps) => - ; - -const MoveToDialogFields = () => - ; - -const ProjectPicker = (props: WrappedFieldProps) => -
- - {props.meta.dirty && props.meta.error && - - {props.meta.error} - } -
; - -const handleChange = (props: WrappedFieldProps) => (value: string) => - props.input.value === value - ? props.input.onChange('') - : props.input.onChange(value); diff --git a/src/views-components/project-copy-dialog/project-copy-dialog.tsx b/src/views-components/project-copy-dialog/project-copy-dialog.tsx deleted file mode 100644 index dedf507d..00000000 --- a/src/views-components/project-copy-dialog/project-copy-dialog.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 -import { Dispatch, compose } from "redux"; -import { withDialog } from "~/store/dialog/with-dialog"; -import { dialogActions } from "~/store/dialog/dialog-actions"; -import { ProjectCopy, CopyFormData } from "~/components/project-copy/project-copy"; -import { reduxForm, startSubmit, stopSubmit, initialize } from 'redux-form'; -import { resetPickerProjectTree } from "~/store/project-tree-picker/project-tree-picker-actions"; - -export const PROJECT_COPY_DIALOG = 'projectCopy'; -export const openProjectCopyDialog = (data: { projectUuid: string, name: string }) => - (dispatch: Dispatch) => { - dispatch(resetPickerProjectTree()); - const initialData: CopyFormData = { name: `Copy of: ${data.name}`, projectUuid: '', uuid: data.projectUuid }; - dispatch(initialize(PROJECT_COPY_DIALOG, initialData)); - dispatch(dialogActions.OPEN_DIALOG({ id: PROJECT_COPY_DIALOG, data: {} })); - }; - -export const ProjectCopyDialog = compose( - withDialog(PROJECT_COPY_DIALOG), - reduxForm({ - form: PROJECT_COPY_DIALOG, - onSubmit: (data, dispatch) => { - dispatch(startSubmit(PROJECT_COPY_DIALOG)); - setTimeout(() => dispatch(stopSubmit(PROJECT_COPY_DIALOG, { name: 'Invalid path' })), 2000); - } - }) -)(ProjectCopy); \ No newline at end of file diff --git a/src/views-components/project-tree-picker/project-tree-picker.tsx b/src/views-components/project-tree-picker/project-tree-picker.tsx index 51220e65..3859180f 100644 --- a/src/views-components/project-tree-picker/project-tree-picker.tsx +++ b/src/views-components/project-tree-picker/project-tree-picker.tsx @@ -16,6 +16,7 @@ import { createTreePickerNode } from "~/store/tree-picker/tree-picker"; import { RootState } from "~/store/store"; import { ServiceRepository } from "~/services/services"; import { FilterBuilder } from "~/common/api/filter-builder"; +import { WrappedFieldProps } from 'redux-form'; type ProjectTreePickerProps = Pick; @@ -142,4 +143,17 @@ export const receiveTreePickerData = (nodeId: string, projects: ProjectResource[ dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId })); }; +export const ProjectTreePickerField = (props: WrappedFieldProps) => +
+ + {props.meta.dirty && props.meta.error && + + {props.meta.error} + } +
; + +const handleChange = (props: WrappedFieldProps) => (value: string) => + props.input.value === value + ? props.input.onChange('') + : props.input.onChange(value); diff --git a/src/views-components/update-collection-dialog/update-collection-dialog..tsx b/src/views-components/update-collection-dialog/update-collection-dialog..tsx deleted file mode 100644 index 239df589..00000000 --- a/src/views-components/update-collection-dialog/update-collection-dialog..tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { SubmissionError } from "redux-form"; -import { RootState } from "~/store/store"; -import { snackbarActions } from "~/store/snackbar/snackbar-actions"; -import { collectionUpdaterActions, updateCollection } from "~/store/collections/updater/collection-updater-action"; -import { dataExplorerActions } from "~/store/data-explorer/data-explorer-action"; -import { PROJECT_PANEL_ID } from "~/views/project-panel/project-panel"; -import { DialogCollectionUpdate } from "../dialog-update/dialog-collection-update"; - -const mapStateToProps = (state: RootState) => ({ - open: state.collections.updater.opened -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - handleClose: () => { - dispatch(collectionUpdaterActions.CLOSE_COLLECTION_UPDATER()); - }, - onSubmit: (data: { name: string, description: string }) => { - return dispatch(editCollection(data)) - .catch((e: any) => { - if(e.errors) { - throw new SubmissionError({ name: e.errors.join("").includes("UniqueViolation") ? "Collection with this name already exists." : "" }); - } - }); - } -}); - -const editCollection = (data: { name: string, description: string }) => - (dispatch: Dispatch) => { - return dispatch(updateCollection(data)).then(() => { - dispatch(snackbarActions.OPEN_SNACKBAR({ - message: "Collection has been successfully updated.", - hideDuration: 2000 - })); - dispatch(dataExplorerActions.REQUEST_ITEMS({ id: PROJECT_PANEL_ID })); - }); - }; - -export const UpdateCollectionDialog = connect(mapStateToProps, mapDispatchToProps)(DialogCollectionUpdate); diff --git a/src/views-components/update-project-dialog/update-project-dialog.tsx b/src/views-components/update-project-dialog/update-project-dialog.tsx deleted file mode 100644 index 0ea23c8f..00000000 --- a/src/views-components/update-project-dialog/update-project-dialog.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { SubmissionError } from "redux-form"; -import { RootState } from "~/store/store"; -import { snackbarActions } from "~/store/snackbar/snackbar-actions"; -import { DialogProjectUpdate } from "../dialog-update/dialog-project-update"; -import { projectActions, updateProject } from "~/store/project/project-action"; - -const mapStateToProps = (state: RootState) => ({ - open: state.projects.updater.opened -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - handleClose: () => { - dispatch(projectActions.CLOSE_PROJECT_UPDATER()); - }, - onSubmit: (data: { name: string, description: string }) => { - return dispatch(editProject(data)) - .catch((e: any) => { - if (e.errors) { - throw new SubmissionError({ name: e.errors.join("").includes("UniqueViolation") ? "Project with this name already exists." : "" }); - } - }); - } -}); - -const editProject = (data: { name: string, description: string }) => - (dispatch: Dispatch, getState: () => RootState) => { - const { uuid } = getState().projects.updater; - return dispatch(updateProject(data)).then(() => { - dispatch(snackbarActions.OPEN_SNACKBAR({ - message: "Project has been successfully updated.", - hideDuration: 2000 - })); - }); - }; - -export const UpdateProjectDialog = connect(mapStateToProps, mapDispatchToProps)(DialogProjectUpdate); diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 00d6c7ce..37c247ef 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -15,30 +15,30 @@ import { TreeItem } from "~/components/tree/tree"; import { ProjectPanel } from "~/views/project-panel/project-panel"; import { DetailsPanel } from '~/views-components/details-panel/details-panel'; import { ArvadosTheme } from '~/common/custom-theme'; -import { CreateProjectDialog } from "~/views-components/create-project-dialog/create-project-dialog"; import { detailsPanelActions } from "~/store/details-panel/details-panel-action"; import { ProjectResource } from '~/models/project'; import { ContextMenu } from "~/views-components/context-menu/context-menu"; import { FavoritePanel } from "../favorite-panel/favorite-panel"; import { CurrentTokenDialog } from '~/views-components/current-token-dialog/current-token-dialog'; import { Snackbar } from '~/views-components/snackbar/snackbar'; -import { CreateCollectionDialog } from '~/views-components/create-collection-dialog/create-collection-dialog'; import { CollectionPanel } from '../collection-panel/collection-panel'; -import { UpdateCollectionDialog } from '~/views-components/update-collection-dialog/update-collection-dialog.'; -import { UpdateProjectDialog } from '~/views-components/update-project-dialog/update-project-dialog'; import { AuthService } from "~/services/auth-service/auth-service"; import { RenameFileDialog } from '~/views-components/rename-file-dialog/rename-file-dialog'; import { FileRemoveDialog } from '~/views-components/file-remove-dialog/file-remove-dialog'; import { MultipleFilesRemoveDialog } from '~/views-components/file-remove-dialog/multiple-files-remove-dialog'; import { DialogCollectionCreateWithSelectedFile } from '~/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected'; import { UploadCollectionFilesDialog } from '~/views-components/upload-collection-files-dialog/upload-collection-files-dialog'; -import { ProjectCopyDialog } from '~/views-components/project-copy-dialog/project-copy-dialog'; import { CollectionPartialCopyDialog } from '~/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog'; -import { MoveProjectDialog } from '~/views-components/move-project-dialog/move-project-dialog'; -import { MoveCollectionDialog } from '~/views-components/move-collection-dialog/move-collection-dialog'; import { SidePanel } from '~/views-components/side-panel/side-panel'; import { Routes } from '~/routes/routes'; import { Breadcrumbs } from '~/views-components/breadcrumbs/breadcrumbs'; +import { CreateProjectDialog } from '~/views-components/dialog-forms/create-project-dialog'; +import { CreateCollectionDialog } from '~/views-components/dialog-forms/create-collection-dialog'; +import { CopyCollectionDialog } from '~/views-components/dialog-forms/copy-collection-dialog'; +import { UpdateCollectionDialog } from '~/views-components/dialog-forms/update-collection-dialog'; +import { UpdateProjectDialog } from '~/views-components/dialog-forms/update-project-dialog'; +import { MoveProjectDialog } from '~/views-components/dialog-forms/move-project-dialog'; +import { MoveCollectionDialog } from '~/views-components/dialog-forms/move-collection-dialog'; const APP_BAR_HEIGHT = 100; @@ -184,7 +184,7 @@ export const Workbench = withStyles(styles)( - +