[ARVADOS-WORKBENCH2] created: 1.1.4-600-geebf512

Git user git at public.curoverse.com
Thu Aug 16 03:04:23 EDT 2018


        at  eebf51242e597fd8430f1e92a5e9076b3d623ab5 (commit)


commit eebf51242e597fd8430f1e92a5e9076b3d623ab5
Author: Janicki Artur <artur.janicki at contractors.roche.com>
Date:   Thu Aug 16 09:04:05 2018 +0200

    init edit project feature, refactor forms - add textField component
    
    Feature #13833
    
    Arvados-DCO-1.1-Signed-off-by: Janicki Artur <artur.janicki at contractors.roche.com>

diff --git a/src/store/project/project-action.ts b/src/store/project/project-action.ts
index bef50d1..7080090 100644
--- a/src/store/project/project-action.ts
+++ b/src/store/project/project-action.ts
@@ -9,12 +9,16 @@ import { FilterBuilder } from "~/common/api/filter-builder";
 import { RootState } from "../store";
 import { checkPresenceInFavorites } from "../favorites/favorites-actions";
 import { ServiceRepository } from "~/services/services";
+import { projectPanelActions } from "~/store/project-panel/project-panel-action";
 
 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 }>(),
@@ -26,6 +30,8 @@ export const projectActions = unionize({
     value: 'payload'
 });
 
+export const PROJECT_FORM_NAME = 'projectEditDialog';
+
 export const getProjectList = (parentUuid: string = '') => 
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         dispatch(projectActions.PROJECTS_REQUEST(parentUuid));
@@ -50,4 +56,17 @@ export const createProject = (project: Partial<ProjectResource>) =>
             .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));
+                // ToDo: Update Panel Details
+            });
+    };
+
 export type ProjectAction = UnionOf<typeof projectActions>;
diff --git a/src/store/project/project-reducer.test.ts b/src/store/project/project-reducer.test.ts
index cd96afc..d5963f3 100644
--- a/src/store/project/project-reducer.test.ts
+++ b/src/store/project/project-reducer.test.ts
@@ -35,6 +35,10 @@ describe('project-reducer', () => {
             creator: {
                 opened: false,
                 ownerUuid: "",
+            },
+            updater: {
+                opened: false,
+                uuid: ''
             }
         });
     });
@@ -50,6 +54,7 @@ describe('project-reducer', () => {
             }],
             currentItemId: "1",
             creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
         };
         const project = {
             items: [{
@@ -61,6 +66,7 @@ describe('project-reducer', () => {
             }],
             currentItemId: "",
             creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
         };
 
         const state = projectsReducer(initialState, projectActions.RESET_PROJECT_TREE_ACTIVITY(initialState.items[0].id));
@@ -77,7 +83,8 @@ describe('project-reducer', () => {
                 status: TreeItemStatus.PENDING
             }],
             currentItemId: "1",
-            creator: { opened: false, ownerUuid: "" }
+            creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
         };
         const project = {
             items: [{
@@ -89,6 +96,7 @@ describe('project-reducer', () => {
             }],
             currentItemId: "1",
             creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
         };
 
         const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(initialState.items[0].id));
@@ -106,7 +114,8 @@ describe('project-reducer', () => {
                 status: TreeItemStatus.PENDING,
             }],
             currentItemId: "1",
-            creator: { opened: false, ownerUuid: "" }
+            creator: { opened: false, ownerUuid: "" },
+            updater: { opened: false, uuid: '' }
         };
         const project = {
             items: [{
@@ -118,6 +127,7 @@ describe('project-reducer', () => {
             }],
             currentItemId: "1",
             creator: { opened: false, ownerUuid: "" },
+
         };
 
         const state = projectsReducer(initialState, projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(initialState.items[0].id));
diff --git a/src/store/project/project-reducer.ts b/src/store/project/project-reducer.ts
index 4249007..bb07486 100644
--- a/src/store/project/project-reducer.ts
+++ b/src/store/project/project-reducer.ts
@@ -11,7 +11,8 @@ import { ProjectResource } from "~/models/project";
 export type ProjectState = {
     items: Array<TreeItem<ProjectResource>>,
     currentItemId: string,
-    creator: ProjectCreator
+    creator: ProjectCreator,
+    updater: ProjectUpdater
 };
 
 interface ProjectCreator {
@@ -20,6 +21,11 @@ interface ProjectCreator {
     error?: string;
 }
 
+interface ProjectUpdater {
+    opened: boolean;
+    uuid: string;
+}
+
 export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
     let item;
     for (const t of tree) {
@@ -100,12 +106,24 @@ const updateCreator = (state: ProjectState, creator: Partial<ProjectCreator>) =>
     }
 });
 
+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: ''
     }
 };
 
@@ -116,6 +134,9 @@ export const projectsReducer = (state: ProjectState = initialState, action: Proj
         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/validators/create-collection/create-collection-validator.tsx b/src/validators/create-collection/create-collection-validator.tsx
deleted file mode 100644
index 2d8e1f5..0000000
--- a/src/validators/create-collection/create-collection-validator.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { require } from '../require';
-import { maxLength } from '../max-length';
-
-export const COLLECTION_NAME_VALIDATION = [require, maxLength(255)];
-export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)];
\ No newline at end of file
diff --git a/src/validators/create-project/create-project-validator.tsx b/src/validators/create-project/create-project-validator.tsx
deleted file mode 100644
index ddea8be..0000000
--- a/src/validators/create-project/create-project-validator.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { require } from '../require';
-import { maxLength } from '../max-length';
-
-export const PROJECT_NAME_VALIDATION = [require, maxLength(255)];
-export const PROJECT_DESCRIPTION_VALIDATION = [maxLength(255)];
-export const COLLECTION_NAME_VALIDATION = [require, maxLength(255)];
-export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)];
-export const COLLECTION_PROJECT_VALIDATION = [require];
diff --git a/src/validators/validators.tsx b/src/validators/validators.tsx
index fdeb8fa..edd0782 100644
--- a/src/validators/validators.tsx
+++ b/src/validators/validators.tsx
@@ -6,4 +6,11 @@ import { require } from './require';
 import { maxLength } from './max-length';
 
 export const TAG_KEY_VALIDATION = [require, maxLength(255)];
-export const TAG_VALUE_VALIDATION = [require, maxLength(255)];
\ No newline at end of file
+export const TAG_VALUE_VALIDATION = [require, maxLength(255)];
+
+export const PROJECT_NAME_VALIDATION = [require, maxLength(255)];
+export const PROJECT_DESCRIPTION_VALIDATION = [maxLength(255)];
+
+export const COLLECTION_NAME_VALIDATION = [require, maxLength(255)];
+export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)];
+export const COLLECTION_PROJECT_VALIDATION = [require];
\ No newline at end of file
diff --git a/src/views-components/context-menu/action-sets/project-action-set.ts b/src/views-components/context-menu/action-sets/project-action-set.ts
index 8944685..1b000c8 100644
--- a/src/views-components/context-menu/action-sets/project-action-set.ts
+++ b/src/views-components/context-menu/action-sets/project-action-set.ts
@@ -2,28 +2,39 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { reset } from "redux-form";
+import { reset, initialize } from "redux-form";
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
-import { projectActions } from "~/store/project/project-action";
-import { NewProjectIcon } from "~/components/icon/icon";
+import { projectActions, PROJECT_FORM_NAME } from "~/store/project/project-action";
+import { NewProjectIcon, RenameIcon } 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 { PROJECT_CREATE_DIALOG } from "../../dialog-create/dialog-project-create";
 
-export const projectActionSet: ContextMenuActionSet = [[{
-    icon: NewProjectIcon,
-    name: "New project",
-    execute: (dispatch, resource) => {
-        dispatch(reset(PROJECT_CREATE_DIALOG));
-        dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid }));
+export const projectActionSet: ContextMenuActionSet = [[
+    {
+        icon: NewProjectIcon,
+        name: "New project",
+        execute: (dispatch, resource) => {
+            dispatch(reset(PROJECT_CREATE_DIALOG));
+            dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: 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 }));
+        }
+    },
+    {
+        component: ToggleFavoriteAction,
+        execute: (dispatch, resource) => {
+            dispatch<any>(toggleFavorite(resource)).then(() => {
+                dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
+            });
+        }
     }
-}, {
-    component: ToggleFavoriteAction,
-    execute: (dispatch, resource) => {
-        dispatch<any>(toggleFavorite(resource)).then(() => {
-            dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
-        });
-    }
-}]];
+]];
diff --git a/src/views-components/dialog-create/dialog-collection-create-selected.tsx b/src/views-components/dialog-create/dialog-collection-create-selected.tsx
index 0dc590a..af2536d 100644
--- a/src/views-components/dialog-create/dialog-collection-create-selected.tsx
+++ b/src/views-components/dialog-create/dialog-collection-create-selected.tsx
@@ -7,7 +7,7 @@ import { InjectedFormProps, Field, WrappedFieldProps } from "redux-form";
 import { Dialog, DialogTitle, DialogContent, DialogActions, Button, CircularProgress } from "@material-ui/core";
 import { WithDialogProps } from "~/store/dialog/with-dialog";
 import { TextField } from "~/components/text-field/text-field";
-import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION, COLLECTION_PROJECT_VALIDATION } from "~/validators/create-project/create-project-validator";
+import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION, COLLECTION_PROJECT_VALIDATION } from "~/validators/validators";
 import { ProjectTreePicker } from "../project-tree-picker/project-tree-picker";
 
 export const DialogCollectionCreateWithSelected = (props: WithDialogProps<string> & InjectedFormProps<{ name: string }>) =>
diff --git a/src/views-components/dialog-create/dialog-collection-create.tsx b/src/views-components/dialog-create/dialog-collection-create.tsx
index 7f2e411..af0e33f 100644
--- a/src/views-components/dialog-create/dialog-collection-create.tsx
+++ b/src/views-components/dialog-create/dialog-collection-create.tsx
@@ -9,13 +9,13 @@ 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/create-collection/create-collection-validator';
+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 { collectionUploaderActions, UploadFile } from "~/store/collections/uploader/collection-uploader-actions";
 
-type CssRules = "button" | "lastButton" | "formContainer" | "textField" | "createProgress" | "dialogActions";
+type CssRules = "button" | "lastButton" | "formContainer" | "createProgress" | "dialogActions";
 
 const styles: StyleRulesCallback<CssRules> = theme => ({
     button: {
@@ -29,9 +29,6 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
         display: "flex",
         flexDirection: "column",
     },
-    textField: {
-        marginBottom: theme.spacing.unit * 3
-    },
     createProgress: {
         position: "absolute",
         minWidth: "20px",
@@ -42,10 +39,8 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     }
 });
 
-interface DialogCollectionCreateProps {
+interface DialogCollectionDataProps {
     open: boolean;
-    handleClose: () => void;
-    onSubmit: (data: { name: string, description: string }, files: UploadFile[]) => void;
     handleSubmit: any;
     submitting: boolean;
     invalid: boolean;
@@ -53,6 +48,13 @@ interface DialogCollectionCreateProps {
     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(
@@ -61,7 +63,7 @@ export const DialogCollectionCreate = compose(
     })),
     reduxForm({ form: COLLECTION_CREATE_DIALOG }),
     withStyles(styles))(
-        class DialogCollectionCreate extends React.Component<DialogCollectionCreateProps & DispatchProp & WithStyles<CssRules>> {
+    class DialogCollectionCreate extends React.Component<DialogCollectionProps> {
             render() {
                 const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine, files } = this.props;
                 const busy = submitting || files.reduce(
@@ -82,13 +84,11 @@ export const DialogCollectionCreate = compose(
                                     disabled={submitting}
                                     component={TextField}
                                     validate={COLLECTION_NAME_VALIDATION}
-                                    className={classes.textField}
                                     label="Collection Name" />
                                 <Field name="description"
                                     disabled={submitting}
                                     component={TextField}
                                     validate={COLLECTION_DESCRIPTION_VALIDATION}
-                                    className={classes.textField}
                                     label="Description - optional" />
                                 <FileUpload
                                     files={files}
diff --git a/src/views-components/dialog-create/dialog-project-create.tsx b/src/views-components/dialog-create/dialog-project-create.tsx
index c3d8415..e77114b 100644
--- a/src/views-components/dialog-create/dialog-project-create.tsx
+++ b/src/views-components/dialog-create/dialog-project-create.tsx
@@ -9,9 +9,9 @@ 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 { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from '~/validators/create-project/create-project-validator';
+import { PROJECT_NAME_VALIDATION, PROJECT_DESCRIPTION_VALIDATION } from '~/validators/validators';
 
-type CssRules = "button" | "lastButton" | "formContainer" | "textField" | "dialog" | "dialogTitle" | "createProgress" | "dialogActions";
+type CssRules = "button" | "lastButton" | "formContainer" | "dialog" | "dialogTitle" | "createProgress" | "dialogActions";
 
 const styles: StyleRulesCallback<CssRules> = theme => ({
     button: {
@@ -29,9 +29,6 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     dialogTitle: {
         paddingBottom: "0"
     },
-    textField: {
-        marginTop: "32px",
-    },
     dialog: {
         minWidth: "600px",
         minHeight: "320px"
@@ -78,12 +75,10 @@ export const DialogProjectCreate = compose(
                                 <Field name="name"
                                        component={TextField}
                                        validate={PROJECT_NAME_VALIDATION}
-                                       className={classes.textField}
                                        label="Project Name"/>
                                 <Field name="description"
                                        component={TextField}
                                        validate={PROJECT_DESCRIPTION_VALIDATION}
-                                       className={classes.textField}
                                        label="Description - optional"/>
                             </DialogContent>
                             <DialogActions className={classes.dialogActions}>
diff --git a/src/views-components/dialog-update/dialog-collection-update.tsx b/src/views-components/dialog-update/dialog-collection-update.tsx
index d97ff41..18c43f2 100644
--- a/src/views-components/dialog-update/dialog-collection-update.tsx
+++ b/src/views-components/dialog-update/dialog-collection-update.tsx
@@ -6,11 +6,12 @@ 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, TextField, StyleRulesCallback, withStyles, WithStyles, Button, CircularProgress } from '@material-ui/core';
-import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION } from '~/validators/create-collection/create-collection-validator';
+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' | 'textField' | 'buttonWrapper' | 'saveButton' | 'circularProgress';
+type CssRules = 'content' | 'actions' | 'buttonWrapper' | 'saveButton' | 'circularProgress';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     content: {
@@ -22,9 +23,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         padding: `${theme.spacing.unit}px ${theme.spacing.unit * 3 - theme.spacing.unit / 2}px 
                 ${theme.spacing.unit * 3}px ${theme.spacing.unit * 3}px`
     },
-    textField: {
-        marginBottom: theme.spacing.unit * 3
-    },
     buttonWrapper: {
         position: 'relative'
     },
@@ -56,14 +54,6 @@ interface DialogCollectionAction {
 
 type DialogCollectionProps = DialogCollectionDataProps & DialogCollectionAction & WithStyles<CssRules>;
 
-interface TextFieldProps {
-    label: string;
-    floatinglabeltext: string;
-    className?: string;
-    input?: string;
-    meta?: any;
-}
-
 export const DialogCollectionUpdate = compose(
     reduxForm({ form: COLLECTION_FORM_NAME }),
     withStyles(styles))(
@@ -83,19 +73,15 @@ export const DialogCollectionUpdate = compose(
                         <form onSubmit={handleSubmit((data: any) => onSubmit(data))}>
                             <DialogTitle>Edit Collection</DialogTitle>
                             <DialogContent className={classes.content}>
-                                <Field name="name"
+                                <Field name='name'
                                     disabled={submitting}
-                                    component={this.renderTextField}
-                                    floatinglabeltext="Collection Name"
+                                    component={TextField}
                                     validate={COLLECTION_NAME_VALIDATION}
-                                    className={classes.textField}
                                     label="Collection Name" />
-                                <Field name="description"
+                                <Field name='description'
                                     disabled={submitting}
-                                    component={this.renderTextField}
-                                    floatinglabeltext="Description - optional"
+                                    component={TextField}
                                     validate={COLLECTION_DESCRIPTION_VALIDATION}
-                                    className={classes.textField}
                                     label="Description - optional" />
                             </DialogContent>
                             <DialogActions className={classes.actions}>
@@ -115,17 +101,5 @@ export const DialogCollectionUpdate = compose(
                     </Dialog>
                 );
             }
-
-            renderTextField = ({ input, label, meta: { touched, error }, ...custom }: TextFieldProps) => (
-                <TextField
-                    helperText={touched && error}
-                    label={label}
-                    className={this.props.classes.textField}
-                    error={touched && !!error}
-                    autoComplete='off'
-                    {...input}
-                    {...custom}
-                />
-            )
         }
     );
diff --git a/src/views-components/dialog-update/dialog-project-update.tsx b/src/views-components/dialog-update/dialog-project-update.tsx
new file mode 100644
index 0000000..5dde00a
--- /dev/null
+++ b/src/views-components/dialog-update/dialog-project-update.tsx
@@ -0,0 +1,101 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// 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 Collection</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>;
+            }
+        }
+    );
diff --git a/src/views-components/update-project-dialog/update-project-dialog.tsx b/src/views-components/update-project-dialog/update-project-dialog.tsx
new file mode 100644
index 0000000..c455842
--- /dev/null
+++ b/src/views-components/update-project-dialog/update-project-dialog.tsx
@@ -0,0 +1,42 @@
+// 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") ? "CProject 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);
diff --git a/src/views/collection-panel/collection-tag-form.tsx b/src/views/collection-panel/collection-tag-form.tsx
index 8f25404..83ad0ca 100644
--- a/src/views/collection-panel/collection-tag-form.tsx
+++ b/src/views/collection-panel/collection-tag-form.tsx
@@ -6,20 +6,15 @@ import * as React from 'react';
 import { reduxForm, Field, reset } from 'redux-form';
 import { compose, Dispatch } from 'redux';
 import { ArvadosTheme } from '~/common/custom-theme';
-import { StyleRulesCallback, withStyles, WithStyles, TextField, Button, CircularProgress } from '@material-ui/core';
+import { StyleRulesCallback, withStyles, WithStyles, Button, CircularProgress, Grid } from '@material-ui/core';
 import { TagProperty } from '~/models/tag';
+import { TextField } from '~/components/text-field/text-field';
 import { createCollectionTag, COLLECTION_TAG_FORM_NAME } from '~/store/collection-panel/collection-panel-action';
 import { TAG_VALUE_VALIDATION, TAG_KEY_VALIDATION } from '~/validators/validators';
 
-type CssRules = 'form' | 'textField' | 'buttonWrapper' | 'saveButton' | 'circularProgress';
+type CssRules = 'buttonWrapper' | 'saveButton' | 'circularProgress';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-    form: {
-        marginBottom: theme.spacing.unit * 4
-    },
-    textField: {
-        marginRight: theme.spacing.unit
-    },
     buttonWrapper: {
         position: 'relative',
         display: 'inline-block'
@@ -47,14 +42,6 @@ interface CollectionTagFormActionProps {
     handleSubmit: any;
 }
 
-interface TextFieldProps {
-    label: string;
-    floatinglabeltext: string;
-    className?: string;
-    input?: string;
-    meta?: any;
-}
-
 type CollectionTagFormProps = CollectionTagFormDataProps & CollectionTagFormActionProps & WithStyles<CssRules>;
 
 export const CollectionTagForm = compose(
@@ -67,52 +54,41 @@ export const CollectionTagForm = compose(
     }),
     withStyles(styles))(
 
-    class CollectionTagForm extends React.Component<CollectionTagFormProps> {
+        class CollectionTagForm extends React.Component<CollectionTagFormProps> {
 
             render() {
                 const { classes, submitting, pristine, invalid, handleSubmit } = this.props;
                 return (
-                    <form className={classes.form} onSubmit={handleSubmit}>
-                        <Field name="key"
-                            disabled={submitting}
-                            component={this.renderTextField}
-                            floatinglabeltext="Key"
-                            validate={TAG_KEY_VALIDATION}
-                            className={classes.textField}
-                            label="Key" />
-                        <Field name="value"
-                            disabled={submitting}
-                            component={this.renderTextField}
-                            floatinglabeltext="Value"
-                            validate={TAG_VALUE_VALIDATION}
-                            className={classes.textField}
-                            label="Value" />
-                        <div className={classes.buttonWrapper}>
-                            <Button type="submit" className={classes.saveButton}
-                                color="primary"
-                                size='small'
-                                disabled={invalid || submitting || pristine}
-                                variant="contained">
-                                ADD
-                            </Button>
-                            {submitting && <CircularProgress size={20} className={classes.circularProgress} />}
-                        </div>
+                    <form onSubmit={handleSubmit}>
+                        <Grid container justify="flex-start" alignItems="baseline" spacing={24}>
+                            <Grid item xs={3} component={"span"}>
+                                <Field name="key"
+                                    disabled={submitting}
+                                    component={TextField}
+                                    validate={TAG_KEY_VALIDATION}
+                                    label="Key" />
+                            </Grid>
+                            <Grid item xs={5} component={"span"}>
+                                <Field name="value"
+                                    disabled={submitting}
+                                    component={TextField}
+                                    validate={TAG_VALUE_VALIDATION}
+                                    label="Value" />
+                            </Grid>
+                            <Grid item component={"span"} className={classes.buttonWrapper}>
+                                <Button type="submit" className={classes.saveButton}
+                                    color="primary"
+                                    size='small'
+                                    disabled={invalid || submitting || pristine}
+                                    variant="contained">
+                                    ADD
+                                </Button>
+                                {submitting && <CircularProgress size={20} className={classes.circularProgress} />}
+                            </Grid>
+                        </Grid>
                     </form>
                 );
             }
-
-            renderTextField = ({ input, label, meta: { touched, error }, ...custom }: TextFieldProps) => (
-                <TextField
-                    helperText={touched && error}
-                    label={label}
-                    className={this.props.classes.textField}
-                    error={touched && !!error}
-                    autoComplete='off'
-                    {...input}
-                    {...custom}
-                />
-            )
-
         }
 
     );
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index a0be372..a38afb7 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -42,6 +42,7 @@ import { CollectionPanel } from '../collection-panel/collection-panel';
 import { loadCollection, loadCollectionTags } from '~/store/collection-panel/collection-panel-action';
 import { getCollectionUrl } from '~/models/collection';
 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';
@@ -245,6 +246,7 @@ export const Workbench = withStyles(styles)(
                         <FileRemoveDialog />
                         <MultipleFilesRemoveDialog />
                         <UpdateCollectionDialog />
+                        <UpdateProjectDialog />
                         <CurrentTokenDialog
                             currentToken={this.props.currentToken}
                             open={this.state.isCurrentTokenDialogOpen}

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list