import { FileTree } from '../file-tree/file-tree';
import { IconButton, Grid, Typography, StyleRulesCallback, withStyles, WithStyles, CardHeader, Card, Button } from '@material-ui/core';
import { CustomizeTableIcon } from '../icon/icon';
-import { connect, DispatchProp } from "react-redux";
-import { Dispatch } from "redux";
-import { RootState } from "~/store/store";
-import { ServiceRepository } from "~/services/services";
export interface CollectionPanelFilesProps {
items: Array<TreeItem<FileTreeData>>;
}
});
-const renameFile = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- services.collectionFilesService.renameTest();
-};
-
-
export const CollectionPanelFiles =
- connect()(
withStyles(styles)(
- ({ onItemMenuOpen, onOptionsMenuOpen, classes, dispatch, ...treeProps }: CollectionPanelFilesProps & DispatchProp & WithStyles<CssRules>) =>
- <Card className={classes.root}>
- <CardHeader
- title="Files"
- action={
- <Button onClick={
- () => {
- dispatch<any>(renameFile());
- }}
- variant='raised'
- color='primary'
- size='small'>
- Upload data
+ ({ onItemMenuOpen, onOptionsMenuOpen, onUploadDataClick, classes, ...treeProps }: CollectionPanelFilesProps & WithStyles<CssRules>) =>
+ <Card className={classes.root}>
+ <CardHeader
+ title="Files"
+ action={
+ <Button onClick={onUploadDataClick}
+ variant='raised'
+ color='primary'
+ size='small'>
+ Upload data
</Button>
- } />
- <CardHeader
- className={classes.cardSubheader}
- action={
- <IconButton onClick={onOptionsMenuOpen}>
- <CustomizeTableIcon />
- </IconButton>
- } />
- <Grid container justify="space-between">
- <Typography variant="caption" className={classes.nameHeader}>
- Name
+ } />
+ <CardHeader
+ className={classes.cardSubheader}
+ action={
+ <IconButton onClick={onOptionsMenuOpen}>
+ <CustomizeTableIcon />
+ </IconButton>
+ } />
+ <Grid container justify="space-between">
+ <Typography variant="caption" className={classes.nameHeader}>
+ Name
</Typography>
- <Typography variant="caption" className={classes.fileSizeHeader}>
- File size
+ <Typography variant="caption" className={classes.fileSizeHeader}>
+ File size
</Typography>
- </Grid>
- <FileTree onMenuOpen={onItemMenuOpen} {...treeProps} />
- </Card>)
-);
+ </Grid>
+ <FileTree onMenuOpen={onItemMenuOpen} {...treeProps} />
+ </Card>);
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { FileUpload } from "~/components/file-upload/file-upload";
+import { UploadFile } from '~/store/collections/uploader/collection-uploader-actions';
+import { Dialog, DialogTitle, DialogContent, DialogActions } from '@material-ui/core/';
+import { Button, CircularProgress } from '@material-ui/core';
+import { WithDialogProps } from '../../store/dialog/with-dialog';
+
+export interface FilesUploadDialogProps {
+ files: UploadFile[];
+ uploading: boolean;
+ onSubmit: () => void;
+ onChange: (files: File[]) => void;
+}
+
+export const FilesUploadDialog = (props: FilesUploadDialogProps & WithDialogProps<{}>) =>
+ <Dialog open={props.open}
+ disableBackdropClick={true}
+ disableEscapeKeyDown={true}
+ fullWidth={true}
+ maxWidth='sm'>
+ <DialogTitle>Upload data</DialogTitle>
+ <DialogContent>
+ <FileUpload
+ files={props.files}
+ disabled={props.uploading}
+ onDrop={props.onChange}
+ />
+ </DialogContent>
+ <DialogActions>
+ <Button
+ variant='flat'
+ color='primary'
+ disabled={props.uploading}
+ onClick={props.closeDialog}>
+ Cancel
+ </Button>
+ <Button
+ variant='contained'
+ color='primary'
+ type='submit'
+ onClick={props.onSubmit}
+ disabled={props.uploading}>
+ {props.uploading
+ ? <CircularProgress size={20} />
+ : 'Upload data'}
+ </Button>
+ </DialogActions>
+ </Dialog>;
\ No newline at end of file
});
};
+export const uploadCollectionFiles = (collectionUuid: string, files: File[]) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(collectionUploaderActions.START_UPLOAD());
+ return services.collectionService.uploadFiles(collectionUuid, files,
+ (fileId, loaded, total, currentTime) => {
+ dispatch(collectionUploaderActions.SET_UPLOAD_PROGRESS({ fileId, loaded, total, currentTime }));
+ })
+ .then(() => {
+ dispatch(collectionUploaderActions.CLEAR_UPLOAD());
+ });
+ };
+
+export const uploadCurrentCollectionFiles = (files: File[]) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const currentCollection = getState().collectionPanel.item;
+ if (currentCollection) {
+ return dispatch<any>(uploadCollectionFiles(currentCollection.uuid, files));
+ }
+ };
+
export type CollectionCreateAction = UnionOf<typeof collectionCreateActions>;
// SPDX-License-Identifier: AGPL-3.0\r
\r
import { default as unionize, ofType, UnionOf } from "unionize";\r
+import { Dispatch } from 'redux';\r
+import { RootState } from '~/store/store';\r
+import { ServiceRepository } from '~/services/services';\r
+import { dialogActions } from '~/store/dialog/dialog-actions';\r
+import { loadCollectionFiles } from '../../collection-panel/collection-panel-files/collection-panel-files-actions';\r
+import { snackbarActions } from "~/store/snackbar/snackbar-actions";\r
\r
export interface UploadFile {\r
id: number;\r
SET_UPLOAD_PROGRESS: ofType<{ fileId: number, loaded: number, total: number, currentTime: number }>(),\r
CLEAR_UPLOAD: ofType()\r
}, {\r
- tag: 'type',\r
- value: 'payload'\r
-});\r
+ tag: 'type',\r
+ value: 'payload'\r
+ });\r
\r
export type CollectionUploaderAction = UnionOf<typeof collectionUploaderActions>;\r
+\r
+export const uploadCollectionFiles = (collectionUuid: string) =>\r
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {\r
+ dispatch(collectionUploaderActions.START_UPLOAD());\r
+ const files = getState().collections.uploader.map(file => file.file);\r
+ return services.collectionService.uploadFiles(collectionUuid, files,\r
+ (fileId, loaded, total, currentTime) => {\r
+ dispatch(collectionUploaderActions.SET_UPLOAD_PROGRESS({ fileId, loaded, total, currentTime }));\r
+ })\r
+ .then(() => {\r
+ dispatch(collectionUploaderActions.CLEAR_UPLOAD());\r
+ });\r
+ };\r
+\r
+export const uploadCurrentCollectionFiles = () =>\r
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {\r
+ const currentCollection = getState().collectionPanel.item;\r
+ if (currentCollection) {\r
+ await dispatch<any>(uploadCollectionFiles(currentCollection.uuid));\r
+ dispatch<any>(loadCollectionFiles(currentCollection.uuid));\r
+ dispatch(closeUploadCollectionFilesDialog());\r
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Data has been uploaded.', hideDuration: 2000 }));\r
+ }\r
+ };\r
+\r
+export const UPLOAD_COLLECTION_FILES_DIALOG = 'uploadCollectionFilesDialog';\r
+export const openUploadCollectionFilesDialog = () => (dispatch: Dispatch) => {\r
+ dispatch(collectionUploaderActions.CLEAR_UPLOAD());\r
+ dispatch<any>(dialogActions.OPEN_DIALOG({ id: UPLOAD_COLLECTION_FILES_DIALOG, data: {} }));\r
+};\r
+\r
+export const closeUploadCollectionFilesDialog = () => dialogActions.CLOSE_DIALOG({ id: UPLOAD_COLLECTION_FILES_DIALOG });\r
import { ContextMenuKind } from "../context-menu/context-menu";
import { Tree, getNodeChildren, getNode } from "~/models/tree";
import { CollectionFileType } from "~/models/collection-file";
+import { openUploadCollectionFilesDialog } from '~/store/collections/uploader/collection-uploader-actions';
const memoizedMapStateToProps = () => {
let prevState: CollectionPanelFilesState;
};
const mapDispatchToProps = (dispatch: Dispatch): Pick<CollectionPanelFilesProps, 'onUploadDataClick' | 'onCollapseToggle' | 'onSelectionToggle' | 'onItemMenuOpen' | 'onOptionsMenuOpen'> => ({
- onUploadDataClick: () => { return; },
+ onUploadDataClick: () => {
+ dispatch<any>(openUploadCollectionFilesDialog());
+ },
onCollapseToggle: (id) => {
dispatch(collectionPanelFilesAction.TOGGLE_COLLECTION_FILE_COLLAPSE({ id }));
},
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { connect } from "react-redux";
+import { Dispatch, compose } from "redux";
+import { withDialog } from '~/store/dialog/with-dialog';
+import { FilesUploadDialog } from '~/components/file-upload-dialog/file-upload-dialog';
+import { RootState } from '../../store/store';
+import { uploadCurrentCollectionFiles, UPLOAD_COLLECTION_FILES_DIALOG, collectionUploaderActions } from '~/store/collections/uploader/collection-uploader-actions';
+
+const mapStateToProps = (state: RootState) => ({
+ files: state.collections.uploader,
+ uploading: state.collections.uploader.some(file => file.loaded < file.total)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+ onSubmit: () => {
+ dispatch<any>(uploadCurrentCollectionFiles());
+ },
+ onChange: (files: File[]) => {
+ dispatch(collectionUploaderActions.SET_UPLOAD_FILES(files));
+ }
+});
+
+export const UploadCollectionFilesDialog = compose(
+ withDialog(UPLOAD_COLLECTION_FILES_DIALOG),
+ connect(mapStateToProps, mapDispatchToProps)
+)(FilesUploadDialog);
\ No newline at end of file
import { DialogCollectionCreateWithSelectedFile } from '~/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected';
import { COLLECTION_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-collection-create';
import { PROJECT_CREATE_DIALOG } from '~/views-components/dialog-create/dialog-project-create';
+import { UploadCollectionFilesDialog } from '~/views-components/upload-collection-files-dialog/upload-collection-files-dialog';
const DRAWER_WITDH = 240;
const APP_BAR_HEIGHT = 100;
<main className={classes.contentWrapper}>
<div className={classes.content}>
<Switch>
- <Route path='/' exact render={() => <Redirect to={`/projects/${this.props.authService.getUuid()}`} />} />
+ <Route path='/' exact render={() => <Redirect to={`/projects/${this.props.authService.getUuid()}`} />} />
<Route path="/projects/:id" render={this.renderProjectPanel} />
<Route path="/favorites" render={this.renderFavoritePanel} />
<Route path="/collections/:id" render={this.renderCollectionPanel} />
<FileRemoveDialog />
<MultipleFilesRemoveDialog />
<UpdateCollectionDialog />
+ <UploadCollectionFilesDialog />
<CurrentTokenDialog
currentToken={this.props.currentToken}
open={this.state.isCurrentTokenDialogOpen}