Merge branch 'master'
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Tue, 7 Aug 2018 12:20:35 +0000 (14:20 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Tue, 7 Aug 2018 12:20:35 +0000 (14:20 +0200)
Feature #13952

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

1  2 
src/views-components/dialog-create/dialog-collection-create.tsx
src/views-components/project-tree-picker/project-tree-picker.tsx
src/views/workbench/workbench.tsx

index c599b22d7828ba6e53a6ec038f3f185f76d06e38,804aae11f72f3e85f69eab01660b121d8a789b5f..0686904ab3403aba2a60ae1f5599a129acbeb77d
@@@ -5,11 -5,18 +5,15 @@@
  import * as React from 'react';
  import { reduxForm, Field } from 'redux-form';
  import { compose } from 'redux';
 -import TextField from '@material-ui/core/TextField';
 -import Dialog from '@material-ui/core/Dialog';
 -import DialogActions from '@material-ui/core/DialogActions';
 -import DialogContent from '@material-ui/core/DialogContent';
 -import DialogTitle from '@material-ui/core/DialogTitle';
 +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-project/create-project-validator';
+ import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION } from '../../validators/create-collection/create-collection-validator';
+ 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";
  
@@@ -45,54 -53,86 +50,64 @@@ interface DialogCollectionCreateProps 
      submitting: boolean;
      invalid: boolean;
      pristine: boolean;
+     files: UploadFile[];
  }
  
 -interface TextFieldProps {
 -    label: string;
 -    floatinglabeltext: string;
 -    className?: string;
 -    input?: string;
 -    meta?: any;
 -}
 -
  export const DialogCollectionCreate = compose(
+     connect((state: RootState) => ({
+         files: state.collections.uploader
+     })),
      reduxForm({ form: 'collectionCreateDialog' }),
      withStyles(styles))(
-         class DialogCollectionCreate extends React.Component<DialogCollectionCreateProps & WithStyles<CssRules>> {
 -    class DialogCollectionCreate extends React.Component<DialogCollectionCreateProps & DispatchProp & WithStyles<CssRules>> {
 -        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"
++        class DialogCollectionCreate extends React.Component<DialogCollectionCreateProps & DispatchProp & WithStyles<CssRules>> {
 +            render() {
-                 const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine } = this.props;
++                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))}>
++                        <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={this.renderTextField}
 -                                    floatinglabeltext="Collection Name"
 +                                    component={TextField}
                                      validate={COLLECTION_NAME_VALIDATION}
                                      className={classes.textField}
 -                                    label="Collection Name"/>
 -                            <Field name="description"
 +                                    label="Collection Name" />
 +                                <Field name="description"
                                      disabled={submitting}
 -                                    component={this.renderTextField}
 -                                    floatinglabeltext="Description - optional"
 +                                    component={TextField}
                                      validate={COLLECTION_DESCRIPTION_VALIDATION}
                                      className={classes.textField}
 -                                    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"
 +                                    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={submitting}>CANCEL</Button>
+                                     disabled={busy}>CANCEL</Button>
 -                            <Button type="submit"
 +                                <Button type="submit"
                                      className={classes.lastButton}
                                      color="primary"
-                                     disabled={invalid || submitting || pristine}
+                                     disabled={invalid || busy || pristine}
                                      variant="contained">
 -                                CREATE A COLLECTION
 +                                    CREATE A COLLECTION
                              </Button>
-                                 {submitting && <CircularProgress size={20} className={classes.createProgress} />}
 -                            {busy && <CircularProgress size={20} className={classes.createProgress}/>}
 -                        </DialogActions>
 -                    </form>
 -                </Dialog>
 -            );
++                                {busy && <CircularProgress size={20} className={classes.createProgress} />}
 +                            </DialogActions>
 +                        </form>
 +                    </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}
 -            />
 -        )
 -    }
 -);
 +    );
index 6effd86d56b6eb9d224dfd8cfcbc6a7a25808bf1,0000000000000000000000000000000000000000..1c343a2dde1d8a4acf364d95f6f0a451508f5fee
mode 100644,000000..100644
--- /dev/null
@@@ -1,75 -1,0 +1,75 @@@
-             .create<ProjectResource>()
 +// Copyright (C) The Arvados Authors. All rights reserved.
 +//
 +// SPDX-License-Identifier: AGPL-3.0
 +
 +import * as React from "react";
 +import { Dispatch } from "redux";
 +import { connect } from "react-redux";
 +import { Typography } from "@material-ui/core";
 +import { TreePicker } from "../tree-picker/tree-picker";
 +import { TreeProps, TreeItem, TreeItemStatus } from "../../components/tree/tree";
 +import { ProjectResource } from "../../models/project";
 +import { treePickerActions } from "../../store/tree-picker/tree-picker-actions";
 +import { ListItemTextIcon } from "../../components/list-item-text-icon/list-item-text-icon";
 +import { ProjectIcon } from "../../components/icon/icon";
 +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";
 +
 +type ProjectTreePickerProps = Pick<TreeProps<ProjectResource>, 'toggleItemActive' | 'toggleItemOpen'>;
 +
 +const mapDispatchToProps = (dispatch: Dispatch, props: {onChange: (projectUuid: string) => void}): ProjectTreePickerProps => ({
 +    toggleItemActive: id => {
 +        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ id }));
 +        props.onChange(id);
 +    },
 +    toggleItemOpen: (id, status) => {
 +        status === TreeItemStatus.INITIAL
 +            ? dispatch<any>(loadProjectTreePickerProjects(id))
 +            : dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id }));
 +    }
 +});
 +
 +export const ProjectTreePicker = connect(undefined, mapDispatchToProps)((props: ProjectTreePickerProps) =>
 +    <div style={{display: 'flex', flexDirection: 'column'}}>
 +        <Typography variant='caption' style={{flexShrink: 0}}>
 +            Select a project
 +        </Typography>
 +        <div style={{flexGrow: 1, overflow: 'auto'}}>
 +            <TreePicker {...props} render={renderTreeItem} />
 +        </div>
 +    </div>);
 +
 +// TODO: move action creator to store directory
 +export const loadProjectTreePickerProjects = (id: string) =>
 +    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 +        dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id }));
 +
 +        const ownerUuid = id.length === 0 ? services.authService.getUuid() || '' : id;
 +
 +        const filters = FilterBuilder
++            .create()
 +            .addEqual('ownerUuid', ownerUuid);
 +
 +        const { items } = await services.projectService.list({ filters });
 +
 +        dispatch<any>(receiveProjectTreePickerData(id, items));
 +    };
 +
 +const renderTreeItem = (item: TreeItem<ProjectResource>) =>
 +    <ListItemTextIcon
 +        icon={ProjectIcon}
 +        name={item.data.name}
 +        isActive={item.active}
 +        hasMargin={true} />;
 +
 +// TODO: move action creator to store directory
 +const receiveProjectTreePickerData = (id: string, projects: ProjectResource[]) =>
 +    (dispatch: Dispatch) => {
 +        dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
 +            id,
 +            nodes: projects.map(project => createTreePickerNode({ id: project.uuid, value: project }))
 +        }));
 +        dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id }));
 +    };
index 147c0a8ae24cc6cddc6d59d6a08f96b90bee35f8,69d809869995d3c3344a043b0901076ec9633add..a8552eef824053bf7c197bc85d18758045a77bdd
@@@ -38,14 -38,12 +38,14 @@@ import { Snackbar } from '../../views-c
  import { favoritePanelActions } from '../../store/favorite-panel/favorite-panel-action';
  import { CreateCollectionDialog } from '../../views-components/create-collection-dialog/create-collection-dialog';
  import { CollectionPanel } from '../collection-panel/collection-panel';
- import { loadCollection } from '../../store/collection-panel/collection-panel-action';
+ import { loadCollection, loadCollectionTags } from '../../store/collection-panel/collection-panel-action';
  import { getCollectionUrl } from '../../models/collection';
 -import { RemoveDialog } from '../../views-components/remove-dialog/remove-dialog';
 -import { RenameDialog } from '../../views-components/rename-dialog/rename-dialog';
  import { UpdateCollectionDialog } from '../../views-components/update-collection-dialog/update-collection-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';
  
  const DRAWER_WITDH = 240;
  const APP_BAR_HEIGHT = 100;