onClose={props.closeDialog}
disableBackdropClick={props.submitting}
disableEscapeKeyDown={props.submitting}
- fullWidth>
+ fullWidth
+ maxWidth='sm'>
<form>
<DialogTitle className={props.classes.dialogTitle}>
{props.dialogTitle}
import { updateDetails } from "~/store/details-panel/details-panel-action";
export const projectActions = unionize({
- 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 }>(),
});
};
-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<any>(updateDetails(project));
- });
- };
-
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,
- updater: ProjectUpdater
+ currentItemId: string
};
interface ProjectUpdater {
return items;
}
-const updateProject = (state: ProjectState, updater?: Partial<ProjectUpdater>) => ({
- ...state,
- updater: {
- ...state.updater,
- ...updater
- }
-});
-
const initialState: ProjectState = {
items: [],
- currentItemId: "",
- updater: {
- opened: false,
- uuid: ''
- }
+ currentItemId: ""
};
export const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => {
return projectActions.match(action, {
- 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 { 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
import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
import { openMoveProjectDialog } from '~/store/move-project-dialog/move-project-dialog';
import { PROJECT_CREATE_FORM_NAME, openProjectCreateDialog } from '~/store/projects/project-create-actions';
+import { openProjectUpdateDialog } from '~/store/projects/project-update-actions';
export const projectActionSet: ContextMenuActionSet = [[
{
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));
}
},
{
--- /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
// 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>;
+++ /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 { CreateCollectionDialog } from '~/views-components/dialog-forms/create-collection-dialog';
import { UpdateCollectionDialog } from '~/views-components/dialog-forms/update-collection-dialog';
import { CreateProjectDialog } from '~/views-components/dialog-forms/create-project-dialog';
-
-import { UpdateProjectDialog } from '~/views-components/update-project-dialog/update-project-dialog';
+import { UpdateProjectDialog } from '~/views-components/dialog-forms/update-project-dialog';
import { ProjectPanel } from "~/views/project-panel/project-panel";
import { AuthService } from "~/services/auth-service/auth-service";