Merge branch 'master'
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Mon, 27 Aug 2018 12:09:16 +0000 (14:09 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Mon, 27 Aug 2018 12:09:16 +0000 (14:09 +0200)
Feature #14102

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

47 files changed:
src/components/data-explorer/data-explorer.test.tsx
src/components/form-dialog/form-dialog.tsx
src/components/move-to-dialog/move-to-dialog.tsx [deleted file]
src/components/project-copy/project-copy.tsx [deleted file]
src/store/collections/collection-copy-actions.ts [new file with mode: 0644]
src/store/collections/collection-create-actions.ts [new file with mode: 0644]
src/store/collections/collection-move-actions.ts [moved from src/store/move-collection-dialog/move-collection-dialog.ts with 72% similarity]
src/store/collections/collection-update-actions.ts [new file with mode: 0644]
src/store/collections/collections-reducer.ts
src/store/collections/creator/collection-creator-reducer.test.ts [deleted file]
src/store/collections/creator/collection-creator-reducer.ts [deleted file]
src/store/collections/updater/collection-updater-reducer.ts [deleted file]
src/store/project/project-action.ts
src/store/project/project-reducer.test.ts
src/store/project/project-reducer.ts
src/store/projects/project-create-actions.ts [new file with mode: 0644]
src/store/projects/project-move-actions.ts [moved from src/store/move-project-dialog/move-project-dialog.ts with 71% similarity]
src/store/projects/project-update-actions.ts [new file with mode: 0644]
src/validators/validators.tsx
src/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog.tsx
src/views-components/context-menu/action-sets/collection-action-set.ts
src/views-components/context-menu/action-sets/collection-resource-action-set.ts
src/views-components/context-menu/action-sets/project-action-set.ts
src/views-components/context-menu/action-sets/root-project-action-set.ts
src/views-components/create-collection-dialog/create-collection-dialog.tsx [deleted file]
src/views-components/create-project-dialog/create-project-dialog.tsx [deleted file]
src/views-components/dialog-copy/dialog-collection-copy.tsx [new file with mode: 0644]
src/views-components/dialog-create/dialog-collection-create.tsx
src/views-components/dialog-create/dialog-project-create.tsx
src/views-components/dialog-forms/copy-collection-dialog.ts [new file with mode: 0644]
src/views-components/dialog-forms/create-collection-dialog.ts [new file with mode: 0644]
src/views-components/dialog-forms/create-project-dialog.ts [new file with mode: 0644]
src/views-components/dialog-forms/move-collection-dialog.ts [moved from src/views-components/move-collection-dialog/move-collection-dialog.ts with 63% similarity]
src/views-components/dialog-forms/move-project-dialog.ts [moved from src/views-components/move-project-dialog/move-project-dialog.ts with 59% similarity]
src/views-components/dialog-forms/update-collection-dialog.ts [new file with mode: 0644]
src/views-components/dialog-forms/update-project-dialog.ts [new file with mode: 0644]
src/views-components/dialog-move/dialog-move-to.tsx [new file with mode: 0644]
src/views-components/dialog-update/dialog-collection-update.tsx
src/views-components/dialog-update/dialog-project-update.tsx
src/views-components/form-fields/collection-form-fields.tsx [moved from src/views-components/form-dialog/collection-form-dialog.tsx with 93% similarity]
src/views-components/form-fields/project-form-fields.tsx [new file with mode: 0644]
src/views-components/move-to-dialog/move-to-dialog.tsx [deleted file]
src/views-components/project-copy-dialog/project-copy-dialog.tsx [deleted file]
src/views-components/project-tree-picker/project-tree-picker.tsx
src/views-components/update-collection-dialog/update-collection-dialog..tsx [deleted file]
src/views-components/update-project-dialog/update-project-dialog.tsx [deleted file]
src/views/workbench/workbench.tsx

index a34ab1c8edfeaf97d4f01fb3a1ee51f8b200aad2..3e447b4015480091b66e6ee39f5b6d5560ea6cc6 100644 (file)
@@ -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("<DataExplorer />", () => {
 
     it("communicates with <SearchInput/>", () => {
         const onSearch = jest.fn();
+        const onSetColumns = jest.fn();
         const dataExplorer = mount(<DataExplorer
             {...mockDataExplorerProps()}
             items={[{ name: "item 1" }]}
             searchValue="search value"
-            onSearch={onSearch} />);
+            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("<DataExplorer />", () => {
 
     it("communicates with <ColumnSelector/>", () => {
         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(<DataExplorer
             {...mockDataExplorerProps()}
             columns={columns}
             onColumnToggle={onColumnToggle}
-            items={[{ name: "item 1" }]} />);
+            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("<DataExplorer />", () => {
         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(<DataExplorer
             {...mockDataExplorerProps()}
@@ -55,7 +61,8 @@ describe("<DataExplorer />", () => {
             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("<DataExplorer />", () => {
         const dataExplorer = mount(<DataExplorer
             {...mockDataExplorerProps()}
             items={[]}
-        />);
+            onSetColumns={jest.fn()} />);
         expect(dataExplorer.find(DataTable)).toHaveLength(0);
         expect(dataExplorer.find(DefaultView)).toHaveLength(1);
     });
@@ -78,6 +85,7 @@ describe("<DataExplorer />", () => {
     it("communicates with <TablePagination/>", () => {
         const onChangePage = jest.fn();
         const onChangeRowsPerPage = jest.fn();
+        const onSetColumns = jest.fn();
         const dataExplorer = mount(<DataExplorer
             {...mockDataExplorerProps()}
             items={[{ name: "item 1" }]}
@@ -85,7 +93,7 @@ describe("<DataExplorer />", () => {
             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'],
 });
index dee89249e231dc3b17e406572e4e8de5952b7870..150dc4b66519da12c402f851934e4df36876b7fb 100644 (file)
@@ -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'>
         <form>
             <DialogTitle className={props.classes.dialogTitle}>
                 {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 (file)
index 2bfc2c3..0000000
+++ /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<string> & InjectedFormProps<{ name: string }>) =>
-    <form>
-        <Dialog open={props.open}
-            disableBackdropClick={true}
-            disableEscapeKeyDown={true}>
-            <DialogTitle>Move to</DialogTitle>
-            <DialogContent>
-                <Field
-                    name="projectUuid"
-                    component={Picker}
-                    validate={MOVE_TO_VALIDATION} />
-            </DialogContent>
-            <DialogActions>
-                <Button
-                    variant='flat'
-                    color='primary'
-                    disabled={props.submitting}
-                    onClick={props.closeDialog}>
-                    Cancel
-                    </Button>
-                <Button
-                    variant='contained'
-                    color='primary'
-                    type='submit'
-                    onClick={props.handleSubmit}
-                    disabled={props.pristine || props.invalid || props.submitting}>
-                    {props.submitting ? <CircularProgress size={20} /> : 'Move'}
-                </Button>
-            </DialogActions>
-        </Dialog>
-    </form>;
-
-const Picker = (props: WrappedFieldProps) =>
-    <div style={{ width: '400px', height: '144px', display: 'flex', flexDirection: 'column' }}>
-       <ProjectTreePicker onChange={projectUuid => props.input.onChange(projectUuid)} /> 
-    </div>;
\ 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 (file)
index 34c8e6d..0000000
+++ /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<string> & InjectedFormProps<CopyFormData>) =>
-    <form>
-        <Dialog open={props.open}
-            disableBackdropClick={true}
-            disableEscapeKeyDown={true}>
-            <DialogTitle>Make a copy</DialogTitle>
-            <DialogContent>
-                <Field
-                    name="name"
-                    component={TextField}
-                    validate={COPY_NAME_VALIDATION}
-                    label="Enter a new name for the copy" />
-                <Field
-                    name="projectUuid"
-                    component={Picker}
-                    validate={MAKE_A_COPY_VALIDATION} />
-            </DialogContent>
-            <DialogActions>
-                <Button
-                    variant='flat'
-                    color='primary'
-                    disabled={props.submitting}
-                    onClick={props.closeDialog}>
-                    Cancel
-                    </Button>
-                <Button
-                    variant='contained'
-                    color='primary'
-                    type='submit'
-                    onClick={props.handleSubmit}
-                    disabled={props.pristine || props.invalid || props.submitting}>
-                    {props.submitting && <CircularProgress size={20} style={{position: 'absolute'}}/>}
-                    Copy
-                </Button>                
-            </DialogActions>
-        </Dialog>
-    </form>;
-const Picker = (props: WrappedFieldProps) =>
-    <div style={{ width: '400px', height: '144px', display: 'flex', flexDirection: 'column' }}>
-        <ProjectTreePicker onChange={projectUuid => props.input.onChange(projectUuid)} />
-    </div>; 
\ 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 (file)
index 0000000..80f577e
--- /dev/null
@@ -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<any>(resetPickerProjectTree());
+        const initialData: CollectionCopyFormDialogData = { name: `Copy of: ${resource.name}`, ownerUuid: '', uuid: resource.uuid };
+        dispatch<any>(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 (file)
index 0000000..d8d292c
--- /dev/null
@@ -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<any>(createCollection(data));
+        dispatch(snackbarActions.OPEN_SNACKBAR({
+            message: "Collection has been successfully created.",
+            hideDuration: 2000
+        }));
+    };
+
+export const createCollection = (collection: Partial<CollectionResource>) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        dispatch(startSubmit(COLLECTION_CREATE_FORM_NAME));
+        try {
+            const newCollection = await services.collectionService.create(collection);
+            await dispatch<any>(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
similarity index 72%
rename from src/store/move-collection-dialog/move-collection-dialog.ts
rename to src/store/collections/collection-move-actions.ts
index 5950679956e7ac3b7e4655c09ff97e321580489e..6fc836f80e123064bd6f99d35a5ed3fe47291f86 100644 (file)
@@ -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<any>(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 (file)
index 0000000..27c3bfc
--- /dev/null
@@ -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<any>(updateCollection(data));
+        dispatch(snackbarActions.OPEN_SNACKBAR({
+            message: "Collection has been successfully updated.",
+            hideDuration: 2000
+        }));
+    };
+
+export const updateCollection = (collection: Partial<CollectionResource>) =>
+    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<any>(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
index b2ee4550662a99b4531bb6071d42ab80907f93e1..c7601505cbd270c587a1ae9ec3f0e04ad354980a 100644 (file)
@@ -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 (file)
index 5aa9dcf..0000000
+++ /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 (file)
index 72c999d..0000000
+++ /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<CollectionCreatorState>) => ({
-    ...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 (file)
index 97d010f..0000000
+++ /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<CollectionUpdaterState>) => ({
-    ...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
-    });
-};
index 53e09cc64eb9bb14744f344a47ae4e36a5d9cdb5..4f03ae1c7dbe174713b29fc3dbed21b5582d27e8 100644 (file)
@@ -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<Partial<ProjectResource>>(),
-    CREATE_PROJECT_SUCCESS: ofType<ProjectResource>(),
-    OPEN_PROJECT_UPDATER: ofType<{ uuid: string }>(),
-    CLOSE_PROJECT_UPDATER: ofType<{}>(),
-    UPDATE_PROJECT_SUCCESS: ofType<ProjectResource>(),
     REMOVE_PROJECT: ofType<string>(),
     PROJECTS_REQUEST: ofType<string>(),
     PROJECTS_SUCCESS: ofType<{ projects: ProjectResource[], parentItemId?: string }>(),
@@ -29,9 +20,7 @@ export const projectActions = unionize({
     RESET_PROJECT_TREE_ACTIVITY: ofType<string>()
 });
 
-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<any>(updateFavorites(projects.map(project => project.uuid)));
+            dispatch<any>(resourcesActions.SET_RESOURCES(projects));
             return projects;
         });
     };
 
-export const createProject = (project: Partial<ProjectResource>) =>
-    (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<ProjectResource>) =>
-    (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<any>(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<typeof projectActions>;
index 8cd3121eecb871c8d9fa538bcfdb6983cf1322f6..56a62534655bd791cb05c459d74047683b8c40f3 100644 (file)
@@ -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));
index bb0748657ee1e64b3d28b5e8bc923fc062ca6ea9..452f6be3e7be1a59eee7b42cda10108437e04327 100644 (file)
@@ -10,17 +10,9 @@ import { ProjectResource } from "~/models/project";
 
 export type ProjectState = {
     items: Array<TreeItem<ProjectResource>>,
-    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<TreeItem<ProjectResource>>, projects: Pro
     return items;
 }
 
-const updateCreator = (state: ProjectState, creator: Partial<ProjectCreator>) => ({
-    ...state,
-    creator: {
-        ...state.creator,
-        ...creator
-    }
-});
-
-const updateProject = (state: ProjectState, updater?: Partial<ProjectUpdater>) => ({
-    ...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 (file)
index 0000000..d1bc827
--- /dev/null
@@ -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<any>(createProject(data));
+        dispatch(snackbarActions.OPEN_SNACKBAR({
+            message: "Project has been successfully created.",
+            hideDuration: 2000
+        }));
+    };
+
+
+const createProject = (project: Partial<ProjectResource>) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        dispatch(startSubmit(PROJECT_CREATE_FORM_NAME));
+        try {
+            const newProject = await services.projectService.create(project);
+            dispatch<any>(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
similarity index 71%
rename from src/store/move-project-dialog/move-project-dialog.ts
rename to src/store/projects/project-move-actions.ts
index 9e8e6f9b1ac1905406e7d1987df695ea73760ead..52d17ac814c43904de5f2b4347b3ad5b224ee961 100644 (file)
@@ -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<any>(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<any>(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 (file)
index 0000000..e674d29
--- /dev/null
@@ -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<any>(updateProject(data));
+        dispatch(snackbarActions.OPEN_SNACKBAR({
+            message: "Project has been successfully updated.",
+            hideDuration: 2000
+        }));
+    };
+
+export const updateProject = (project: Partial<ProjectResource>) =>
+    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<any>(getProjectList(updatedProject.ownerUuid));
+            dispatch<any>(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
index 106c74da76f054a5dc8af4d615c4f316592fb623..95edadfdd1809b23e83aff5ec47043f761b9e30f 100644 (file)
@@ -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];
index e230470b418e065b0c01956d340fbbcd07b85907..86fc360ef8d1626f60937730e2d577173cca726d 100644 (file)
@@ -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),
index 5fb4f2d11d0058864a2a58ab61076957c961f656..b3fdc3fbab642561d823cddd003a70b9ab288149 100644 (file)
@@ -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<any>(openUpdater(resource));
+            dispatch<any>(openCollectionUpdateDialog(resource));
         }
     },
     {
@@ -43,7 +43,7 @@ export const collectionActionSet: ContextMenuActionSet = [[
         icon: CopyIcon,
         name: "Copy to project",
         execute: (dispatch, resource) => {
-            dispatch<any>(openProjectCopyDialog({name: resource.name, projectUuid: resource.uuid}));
+            dispatch<any>(openCollectionCopyDialog(resource));
         }
     },
     {
index ab93f6bb80d68a7c7f16b256cfedb8caaf1918ae..a299b9370ca93f39a437338b486ab639935b323e 100644 (file)
@@ -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<any>(openUpdater(resource));
+            dispatch<any>(openCollectionUpdateDialog(resource));
         }
     },
     {
@@ -43,7 +43,7 @@ export const collectionResourceActionSet: ContextMenuActionSet = [[
         icon: CopyIcon,
         name: "Copy to project",
         execute: (dispatch, resource) => {
-            dispatch<any>(openProjectCopyDialog({name: resource.name, projectUuid: resource.uuid}));
+            dispatch<any>(openCollectionCopyDialog(resource));
         },
     },
     {
index 7f83fb24db07fe34c8113ccd66d942d846344425..af10aedf804201b2a1c8bb662e1dcf4d15313936 100644 (file)
@@ -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<any>(openProjectCreator(resource.uuid))
+        execute: (dispatch, resource) => {
+            dispatch<any>(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<any>(openProjectUpdateDialog(resource));
         }
     },
     {
@@ -43,7 +43,7 @@ export const projectActionSet: ContextMenuActionSet = [[
         icon: CopyIcon,
         name: "Copy to project",
         execute: (dispatch, resource) => {
-            dispatch<any>(openProjectCopyDialog({name: resource.name, projectUuid: resource.uuid}));
+            // add code
         }
-    }
+    },
 ]];
index eb4a9a30d6423bcc1e499c2b5c262a4d96a34e2a..386c5162f3c6ab9858c55f9b25468acbd98b353c 100644 (file)
@@ -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<any>(openProjectCreator(resource.uuid))
+        execute: (dispatch, resource) => {
+            dispatch<any>(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<any>(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 (file)
index 94eb82f..0000000
+++ /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<any>(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<any>(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 (file)
index 43f56ed..0000000
+++ /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<any>(createProject(data)).then(() => {
-            dispatch(snackbarActions.OPEN_SNACKBAR({
-                message: "Project has been successfully created.",
-                hideDuration: 2000
-            }));
-            dispatch(projectPanelActions.REQUEST_ITEMS());
-            dispatch<any>(getProjectList(ownerUuid));
-        });
-    };
-
-const mapDispatchToProps = (dispatch: Dispatch) => ({
-    handleClose: () => {
-        dispatch(projectActions.CLOSE_PROJECT_CREATOR());
-    },
-    onSubmit: (data: { name: string, description: string }) => {
-        return dispatch<any>(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 (file)
index 0000000..029db57
--- /dev/null
@@ -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<string> & InjectedFormProps<CollectionCopyFormDialogData>;
+
+export const DialogCollectionCopy = (props: CopyFormDialogProps) =>
+    <FormDialog
+        dialogTitle='Make a copy'
+        formFields={CollectionCopyFields}
+        submitLabel='Copy'
+        {...props}
+    />;
+
+const CollectionCopyFields = () => <span>
+    <Field
+        name='name'
+        component={TextField}
+        validate={COPY_NAME_VALIDATION}
+        label="Enter a new name for the copy" />
+    <Field
+        name="ownerUuid"
+        component={ProjectTreePickerField}
+        validate={COPY_FILE_VALIDATION} />
+</span>;
index af0e33f1b4260fab01a15bef29fef1985701ecdd..2fb007b8a6a6e102039f75e39912a2bfc67812b7 100644 (file)
 // 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<CssRules> = 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<CssRules>;
-
-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<DialogCollectionProps> {
-            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 (
-                    <Dialog
-                        open={open}
-                        onClose={handleClose}
-                        fullWidth={true}
-                        maxWidth='sm'
-                        disableBackdropClick={true}
-                        disableEscapeKeyDown={true}>
-                        <form onSubmit={handleSubmit((data: any) => onSubmit(data, files))}>
-                            <DialogTitle id="form-dialog-title">Create a collection</DialogTitle>
-                            <DialogContent className={classes.formContainer}>
-                                <Field name="name"
-                                    disabled={submitting}
-                                    component={TextField}
-                                    validate={COLLECTION_NAME_VALIDATION}
-                                    label="Collection Name" />
-                                <Field name="description"
-                                    disabled={submitting}
-                                    component={TextField}
-                                    validate={COLLECTION_DESCRIPTION_VALIDATION}
-                                    label="Description - optional" />
-                                <FileUpload
-                                    files={files}
-                                    disabled={busy}
-                                    onDrop={files => this.props.dispatch(collectionUploaderActions.SET_UPLOAD_FILES(files))} />
-                            </DialogContent>
-                            <DialogActions className={classes.dialogActions}>
-                                <Button onClick={handleClose} className={classes.button} color="primary"
-                                    disabled={busy}>CANCEL</Button>
-                                <Button type="submit"
-                                    className={classes.lastButton}
-                                    color="primary"
-                                    disabled={invalid || busy || pristine}
-                                    variant="contained">
-                                    CREATE A COLLECTION
-                            </Button>
-                                {busy && <CircularProgress size={20} className={classes.createProgress} />}
-                            </DialogActions>
-                        </form>
-                    </Dialog>
-                );
-            }
-        }
-    );
+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<CollectionCreateFormDialogData>;
+
+export const DialogCollectionCreate = (props: DialogCollectionProps) =>
+    <FormDialog
+        dialogTitle='Create a collection'
+        formFields={CollectionAddFields}
+        submitLabel='Create a Collection'
+        {...props}
+    />;
+
+const CollectionAddFields = () => <span>
+    <CollectionNameField />
+    <CollectionDescriptionField />
+    {/* <FileUpload
+        files={this.props.files}
+        disabled={busy}
+        onDrop={files => this.props.dispatch(collectionUploaderActions.SET_UPLOAD_FILES(files))} /> */}
+</span>;
\ No newline at end of file
index d32582ea2fa52263b3d3abdf08e3eb15e8eb0932..8c1d3800a0e1b275d9a12a3d02bfe2cf87828b1d 100644 (file)
@@ -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<ProjectCreateFormDialogData>;
 
-type CssRules = "button" | "lastButton" | "formContainer" | "dialog" | "dialogTitle" | "createProgress" | "dialogActions";
+export const DialogProjectCreate = (props: DialogCollectionProps) =>
+    <FormDialog
+        dialogTitle='Create a project'
+        formFields={ProjectAddFields}
+        submitLabel='Create a Project'
+        {...props}
+    />;
 
-const styles: StyleRulesCallback<CssRules> = 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<DialogProjectProps & WithStyles<CssRules>> {
-        render() {
-            const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine } = this.props;
-
-            return (
-                <Dialog
-                    open={open}
-                    onClose={handleClose}
-                    disableBackdropClick={true}
-                    disableEscapeKeyDown={true}>
-                    <div className={classes.dialog}>
-                        <form onSubmit={handleSubmit((data: any) => onSubmit(data))}>
-                            <DialogTitle id="form-dialog-title" className={classes.dialogTitle}>Create a
-                                project</DialogTitle>
-                            <DialogContent className={classes.formContainer}>
-                                <Field name="name"
-                                       component={TextField}
-                                       validate={PROJECT_NAME_VALIDATION}
-                                       label="Project Name"/>
-                                <Field name="description"
-                                       component={TextField}
-                                       validate={PROJECT_DESCRIPTION_VALIDATION}
-                                       label="Description - optional"/>
-                            </DialogContent>
-                            <DialogActions className={classes.dialogActions}>
-                                <Button onClick={handleClose} className={classes.button} color="primary"
-                                        disabled={submitting}>CANCEL</Button>
-                                <Button type="submit"
-                                        className={classes.lastButton}
-                                        color="primary"
-                                        disabled={invalid || submitting || pristine}
-                                        variant="contained">
-                                    CREATE A PROJECT
-                                </Button>
-                                {submitting && <CircularProgress size={20} className={classes.createProgress}/>}
-                            </DialogActions>
-                        </form>
-                    </div>
-                </Dialog>
-            );
-        }
-    }
-);
+const ProjectAddFields = () => <span>
+    <ProjectNameField />
+    <ProjectDescriptionField />
+</span>;
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 (file)
index 0000000..ee6293a
--- /dev/null
@@ -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<CollectionCopyFormDialogData>({
+        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 (file)
index 0000000..d2699d8
--- /dev/null
@@ -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<CollectionCreateFormDialogData>({
+        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 (file)
index 0000000..2e87517
--- /dev/null
@@ -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<ProjectCreateFormDialogData>({
+        form: PROJECT_CREATE_FORM_NAME,
+        onSubmit: (data, dispatch) => {
+            dispatch(addProject(data));
+        }
+    })
+)(DialogProjectCreate);
\ No newline at end of file
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 783f0c78479d81e687a46e7e09a8e253dfba9015..38d6d0339288031485bcd51bbaf98932fc7bcdd7 100644 (file)
@@ -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<MoveToFormDialogData>({
-        form: MOVE_COLLECTION_DIALOG,
+        form: COLLECTION_MOVE_FORM_NAME,
         onSubmit: (data, dispatch) => {
             dispatch(moveCollection(data));
         }
     })
-)(MoveToFormDialog);
+)(DialogMoveTo);
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 9ec67486f63597c2bf2c327d01cadaa6d7f34f2f..dd102b145f745d009cbfe2ee6cd7f90b918e525d 100644 (file)
@@ -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<MoveToFormDialogData>({
-        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 (file)
index 0000000..2c4296d
--- /dev/null
@@ -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<CollectionUpdateFormDialogData>({
+        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 (file)
index 0000000..598d0b1
--- /dev/null
@@ -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<ProjectUpdateFormDialogData>({
+        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 (file)
index 0000000..425b9e4
--- /dev/null
@@ -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<string> & InjectedFormProps<MoveToFormDialogData>) =>
+    <FormDialog
+        dialogTitle='Move to'
+        formFields={MoveToDialogFields}
+        submitLabel='Move'
+        {...props}
+    />;
+
+const MoveToDialogFields = () =>
+    <Field
+        name="ownerUuid"
+        component={ProjectTreePickerField}
+        validate={MOVE_TO_VALIDATION} />;
+
index 18c43f2d008a1cbd407410d2389e3de8532a9fd0..b98e0e840f71ae94b98de0dc99636703db22e13f 100644 (file)
 // 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<CssRules> = (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<CssRules>;
-
-export const DialogCollectionUpdate = compose(
-    reduxForm({ form: COLLECTION_FORM_NAME }),
-    withStyles(styles))(
-
-        class DialogCollectionUpdate extends React.Component<DialogCollectionProps> {
-
-            render() {
-                const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine } = this.props;
-                return (
-                    <Dialog open={open}
-                        onClose={handleClose}
-                        fullWidth={true}
-                        maxWidth='sm'
-                        disableBackdropClick={true}
-                        disableEscapeKeyDown={true}>
-
-                        <form onSubmit={handleSubmit((data: any) => onSubmit(data))}>
-                            <DialogTitle>Edit Collection</DialogTitle>
-                            <DialogContent className={classes.content}>
-                                <Field name='name'
-                                    disabled={submitting}
-                                    component={TextField}
-                                    validate={COLLECTION_NAME_VALIDATION}
-                                    label="Collection Name" />
-                                <Field name='description'
-                                    disabled={submitting}
-                                    component={TextField}
-                                    validate={COLLECTION_DESCRIPTION_VALIDATION}
-                                    label="Description - optional" />
-                            </DialogContent>
-                            <DialogActions className={classes.actions}>
-                                <Button onClick={handleClose} color="primary"
-                                    disabled={submitting}>CANCEL</Button>
-                                <div className={classes.buttonWrapper}>
-                                    <Button type="submit" className={classes.saveButton}
-                                        color="primary"
-                                        disabled={invalid || submitting || pristine}
-                                        variant="contained">
-                                        SAVE
-                                    </Button>
-                                    {submitting && <CircularProgress size={20} className={classes.circularProgress} />}
-                                </div>
-                            </DialogActions>
-                        </form>
-                    </Dialog>
-                );
-            }
-        }
-    );
+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<CollectionUpdateFormDialogData>;
+
+export const DialogCollectionUpdate = (props: DialogCollectionProps) =>
+    <FormDialog
+        dialogTitle='Edit Collection'
+        formFields={CollectionEditFields}
+        submitLabel='Save'
+        {...props}
+    />;
+
+const CollectionEditFields = () => <span>
+    <CollectionNameField />
+    <CollectionDescriptionField />
+</span>;
index b5e788c026ce1693fc447a5940f97e0d48591f76..49b97a6f9ab1f08f7b3d889db720821a8a43ef18 100644 (file)
@@ -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<CssRules> = (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<CssRules>;
-
-export const DialogProjectUpdate = compose(
-    reduxForm({ form: PROJECT_FORM_NAME }),
-    withStyles(styles))(
-
-        class DialogProjectUpdate extends React.Component<DialogProjectProps> {
-            render() {
-                const { handleSubmit, handleClose, onSubmit, open, classes, submitting, invalid, pristine } = this.props;
-                return <Dialog open={open}
-                    onClose={handleClose}
-                    fullWidth={true}
-                    maxWidth='sm'
-                    disableBackdropClick={true}
-                    disableEscapeKeyDown={true}>
-                    <form onSubmit={handleSubmit((data: any) => onSubmit(data))}>
-                        <DialogTitle>Edit Project</DialogTitle>
-                        <DialogContent className={classes.content}>
-                            <Field name='name' 
-                                disabled={submitting}
-                                component={TextField}
-                                validate={PROJECT_NAME_VALIDATION}
-                                label="Project Name" />
-                            <Field name='description' 
-                                disabled={submitting}
-                                component={TextField} 
-                                validate={PROJECT_DESCRIPTION_VALIDATION}
-                                label="Description - optional" />
-                        </DialogContent>
-                        <DialogActions className={classes.actions}>
-                            <Button onClick={handleClose} color="primary"
-                                disabled={submitting}>CANCEL</Button>
-                            <div className={classes.buttonWrapper}>
-                                <Button type="submit" className={classes.saveButton}
-                                    color="primary"
-                                    disabled={invalid || submitting || pristine}
-                                    variant="contained">
-                                    SAVE
-                                </Button>
-                                {submitting && <CircularProgress size={20} className={classes.circularProgress} />}
-                            </div>
-                        </DialogActions>
-                    </form>
-                </Dialog>;
-            }
-        }
-    );
+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<ProjectUpdateFormDialogData>;
+
+export const DialogProjectUpdate = (props: DialogProjectProps) =>
+    <FormDialog
+        dialogTitle='Edit Project'
+        formFields={ProjectEditFields}
+        submitLabel='Save'
+        {...props}
+    />;
+
+const ProjectEditFields = () => <span>
+    <ProjectNameField />
+    <ProjectDescriptionField />
+</span>;
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 d5f1d8521aae89fe693ce44efb9e1248eca42413..10c807b6211bbacf0316abcf95d04b8407708f1e 100644 (file)
@@ -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 = () => <div style={{ display: 'flex' }}>
     <div>
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 (file)
index 0000000..630877e
--- /dev/null
@@ -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 = () =>
+    <Field
+        name='name'
+        component={TextField}
+        validate={PROJECT_NAME_VALIDATION}
+        label="Project Name" />;
+
+export const ProjectDescriptionField = () =>
+    <Field
+        name='description'
+        component={TextField}
+        validate={PROJECT_DESCRIPTION_VALIDATION}
+        label="Description - optional" />;
\ 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 (file)
index 4c27722..0000000
+++ /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<string> & InjectedFormProps<MoveToFormDialogData>) =>
-    <FormDialog
-        dialogTitle='Move to'
-        formFields={MoveToDialogFields}
-        submitLabel='Move'
-        {...props}
-    />;
-
-const MoveToDialogFields = () =>
-    <Field
-        name="ownerUuid"
-        component={ProjectPicker}
-        validate={MOVE_TO_VALIDATION} />;
-
-const ProjectPicker = (props: WrappedFieldProps) =>
-    <div style={{ height: '200px', display: 'flex', flexDirection: 'column' }}>
-        <ProjectTreePicker onChange={handleChange(props)} />
-        {props.meta.dirty && props.meta.error &&
-            <Typography variant='caption' color='error'>
-                {props.meta.error}
-            </Typography>}
-    </div>;
-
-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 (file)
index dedf507..0000000
+++ /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<any>(resetPickerProjectTree());
-        const initialData: CopyFormData = { name: `Copy of: ${data.name}`, projectUuid: '', uuid: data.projectUuid };
-        dispatch<any>(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
index 51220e655ddea6db395460fb963a14f5051b4aa8..3859180f0e2d81f0308070d399a1b836d5fa277a 100644 (file)
@@ -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<TreePickerProps, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen'>;
 
@@ -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) =>
+    <div style={{ height: '200px', display: 'flex', flexDirection: 'column' }}>
+        <ProjectTreePicker onChange={handleChange(props)} />
+        {props.meta.dirty && props.meta.error &&
+            <Typography variant='caption' color='error'>
+                {props.meta.error}
+            </Typography>}
+    </div>;
+
+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 (file)
index 239df58..0000000
+++ /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<any>(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<any>(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 (file)
index 0ea23c8..0000000
+++ /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<any>(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<any>(updateProject(data)).then(() => {
-            dispatch(snackbarActions.OPEN_SNACKBAR({
-                message: "Project has been successfully updated.",
-                hideDuration: 2000
-            }));
-        });
-    };
-
-export const UpdateProjectDialog = connect(mapStateToProps, mapDispatchToProps)(DialogProjectUpdate);
index 00d6c7cec738d52d6d6519cb5bc19a3cbf69130a..37c247ef23c6719debc5e9e8c5a74e67b20dbcbe 100644 (file)
@@ -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)(
                         <CollectionPartialCopyDialog />
                         <DialogCollectionCreateWithSelectedFile />
                         <FileRemoveDialog />
-                        <ProjectCopyDialog />
+                        <CopyCollectionDialog />
                         <MultipleFilesRemoveDialog />
                         <UpdateCollectionDialog />
                         <UploadCollectionFilesDialog />