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() });
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");
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");
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()}
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");
const dataExplorer = mount(<DataExplorer
{...mockDataExplorerProps()}
items={[]}
- />);
+ onSetColumns={jest.fn()} />);
expect(dataExplorer.find(DataTable)).toHaveLength(0);
expect(dataExplorer.find(DefaultView)).toHaveLength(1);
});
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" }]}
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);
onChangeRowsPerPage: jest.fn(),
onContextMenu: jest.fn(),
defaultIcon: ProjectIcon,
+ onSetColumns: jest.fn(),
defaultMessages: ['testing'],
});
onClose={props.closeDialog}
disableBackdropClick={props.submitting}
disableEscapeKeyDown={props.submitting}
- fullWidth>
+ fullWidth
+ maxWidth='sm'>
<form>
<DialogTitle className={props.classes.dialogTitle}>
{props.dialogTitle}
+++ /dev/null
-// 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
+++ /dev/null
-// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
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 }));
}
}
--- /dev/null
+// 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
// 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
});
+++ /dev/null
-// 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);
- });
-});
+++ /dev/null
-// 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
- });
-};
+++ /dev/null
-// 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
- });
-};
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 }>(),
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({
}).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>;
status: TreeItemStatus.INITIAL
}
],
- currentItemId: "",
- creator: {
- opened: false,
- ownerUuid: "",
- },
- updater: {
- opened: false,
- uuid: ''
- }
+ currentItemId: ""
});
});
active: true,
status: TreeItemStatus.PENDING
}],
- currentItemId: "1",
- creator: { opened: false, ownerUuid: "" },
- updater: { opened: false, uuid: '' }
+ currentItemId: "1"
};
const project = {
items: [{
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));
active: false,
status: TreeItemStatus.PENDING
}],
- currentItemId: "1",
- creator: { opened: false, ownerUuid: "" },
- updater: { opened: false, uuid: '' }
+ currentItemId: "1"
};
const project = {
items: [{
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));
active: false,
status: TreeItemStatus.PENDING,
}],
- currentItemId: "1",
- creator: { opened: false, ownerUuid: "" },
- updater: { opened: false, uuid: '' }
+ currentItemId: "1"
};
const project = {
items: [{
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));
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;
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);
--- /dev/null
+// 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
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 });
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 }));
}
}
--- /dev/null
+// 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
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];
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),
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));
}
},
{
icon: CopyIcon,
name: "Copy to project",
execute: (dispatch, resource) => {
- dispatch<any>(openProjectCopyDialog({name: resource.name, projectUuid: resource.uuid}));
+ dispatch<any>(openCollectionCopyDialog(resource));
}
},
{
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));
}
},
{
icon: CopyIcon,
name: "Copy to project",
execute: (dispatch, resource) => {
- dispatch<any>(openProjectCopyDialog({name: resource.name, projectUuid: resource.uuid}));
+ dispatch<any>(openCollectionCopyDialog(resource));
},
},
{
//
// 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));
}
},
{
icon: CopyIcon,
name: "Copy to project",
execute: (dispatch, resource) => {
- dispatch<any>(openProjectCopyDialog({name: resource.name, projectUuid: resource.uuid}));
+ // add code
}
- }
+ },
]];
//
// 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));
}
}
]];
+++ /dev/null
-// 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);
-
+++ /dev/null
-// 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);
--- /dev/null
+// 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>;
// 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
// 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>;
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
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);
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);
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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} />;
+
// 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>;
// 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>;
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>
--- /dev/null
+// 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
+++ /dev/null
-// 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);
+++ /dev/null
-// 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
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'>;
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);
+++ /dev/null
-// 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);
+++ /dev/null
-// 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);
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;
<CollectionPartialCopyDialog />
<DialogCollectionCreateWithSelectedFile />
<FileRemoveDialog />
- <ProjectCopyDialog />
+ <CopyCollectionDialog />
<MultipleFilesRemoveDialog />
<UpdateCollectionDialog />
<UploadCollectionFilesDialog />