From: Pawel Kowalczyk Date: Mon, 23 Jul 2018 12:14:16 +0000 (+0200) Subject: merge conflicts X-Git-Tag: 1.2.0~33^2~7 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/10ce16c28de952f6533ca3cc9df909269e3d2a53 merge conflicts Feature #13781 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- 10ce16c28de952f6533ca3cc9df909269e3d2a53 diff --cc package.json index a8c56177,0c06a6f1..06fa893f --- a/package.json +++ b/package.json @@@ -3,12 -3,11 +3,12 @@@ "version": "0.1.0", "private": true, "dependencies": { - "@material-ui/core": "1.2.1", + "@material-ui/core": "1.4.0", "@material-ui/icons": "1.1.0", - "@types/lodash": "4.14.109", + "@types/lodash": "4.14.112", + "@types/redux-form": "^7.4.1", "axios": "0.18.0", - "classnames": "^2.2.6", + "classnames": "2.2.6", "lodash": "4.17.10", "react": "16.4.1", "react-dom": "16.4.1", @@@ -41,13 -40,11 +41,13 @@@ "@types/react-router-dom": "4.2.7", "@types/react-router-redux": "5.0.15", "@types/redux-devtools": "3.0.44", + "@types/redux-form": "^7.4.1", - "axios-mock-adapter": "^1.15.0", - "enzyme": "^3.3.0", - "enzyme-adapter-react-16": "^1.1.1", + "axios-mock-adapter": "1.15.0", + "enzyme": "3.3.0", + "enzyme-adapter-react-16": "1.1.1", "jest-localstorage-mock": "2.2.0", "redux-devtools": "3.4.1", + "redux-form": "^7.4.2", "typescript": "2.9.2" }, "moduleNameMapper": { diff --cc src/common/api/common-resource-service.ts index 39825c0e,2541feab..3956fb73 --- a/src/common/api/common-resource-service.ts +++ b/src/common/api/common-resource-service.ts @@@ -3,9 -3,9 +3,9 @@@ // SPDX-License-Identifier: AGPL-3.0 import * as _ from "lodash"; - import FilterBuilder from "./filter-builder"; - import OrderBuilder from "./order-builder"; + import { FilterBuilder } from "./filter-builder"; + import { OrderBuilder } from "./order-builder"; -import { AxiosInstance } from "axios"; +import { AxiosInstance, AxiosPromise } from "axios"; import { Resource } from "../../models/resource"; export interface ListArguments { @@@ -26,12 -26,7 +26,12 @@@ export interface ListResults itemsAvailable: number; } +export interface Errors { + errors: string[]; + errorToken: string; +} + - export default class CommonResourceService { + export class CommonResourceService { static mapResponseKeys = (response: any): Promise => CommonResourceService.mapKeys(_.camelCase)(response.data) diff --cc src/store/project/project-action.ts index 3da60f65,2a7a5c12..075e77d1 --- a/src/store/project/project-action.ts +++ b/src/store/project/project-action.ts @@@ -41,11 -42,11 +41,10 @@@ export const createProject = (project: (dispatch: Dispatch, getState: () => RootState) => { const { ownerUuid } = getState().projects.creator; const projectData = { ownerUuid, ...project }; - dispatch(actions.CREATE_PROJECT(projectData)); + dispatch(projectActions.CREATE_PROJECT(projectData)); return projectService .create(projectData) - .then(project => dispatch(actions.CREATE_PROJECT_SUCCESS(project))); - .then(project => dispatch(projectActions.CREATE_PROJECT_SUCCESS(project))) - .catch(() => dispatch(projectActions.CREATE_PROJECT_ERROR("Could not create a project"))); ++ .then(project => dispatch(projectActions.CREATE_PROJECT_SUCCESS(project))); }; - export type ProjectAction = UnionOf; - export default actions; + export type ProjectAction = UnionOf; diff --cc src/store/project/project-reducer.ts index a329e812,40356c0c..94a451a8 --- a/src/store/project/project-reducer.ts +++ b/src/store/project/project-reducer.ts @@@ -112,12 -111,13 +112,12 @@@ const initialState: ProjectState = }; - const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => { - return actions.match(action, { - OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true }), + export const projectsReducer = (state: ProjectState = initialState, action: ProjectAction) => { + return projectActions.match(action, { + OPEN_PROJECT_CREATOR: ({ ownerUuid }) => updateCreator(state, { ownerUuid, opened: true, pending: false }), CLOSE_PROJECT_CREATOR: () => updateCreator(state, { opened: false }), - CREATE_PROJECT: () => updateCreator(state, { opened: false, pending: true }), - CREATE_PROJECT_SUCCESS: () => updateCreator(state, { ownerUuid: "", pending: false }), - CREATE_PROJECT_ERROR: () => updateCreator(state, { ownerUuid: "", pending: false }), + CREATE_PROJECT: () => updateCreator(state, { error: undefined }), + CREATE_PROJECT_SUCCESS: () => updateCreator(state, { opened: false, ownerUuid: "" }), REMOVE_PROJECT: () => state, PROJECTS_REQUEST: itemId => { const items = _.cloneDeep(state.items); diff --cc src/store/store.ts index 956fb460,adb7ddde..01b06b95 --- a/src/store/store.ts +++ b/src/store/store.ts @@@ -7,13 -7,13 +7,14 @@@ import { routerMiddleware, routerReduce import thunkMiddleware from 'redux-thunk'; import { History } from "history"; - import projectsReducer, { ProjectState } from "./project/project-reducer"; - import sidePanelReducer, { SidePanelState } from './side-panel/side-panel-reducer'; - import authReducer, { AuthState } from "./auth/auth-reducer"; - import dataExplorerReducer, { DataExplorerState } from './data-explorer/data-explorer-reducer'; - import { projectPanelMiddleware } from '../store/project-panel/project-panel-middleware'; - import detailsPanelReducer, { DetailsPanelState } from './details-panel/details-panel-reducer'; + import { projectsReducer, ProjectState } from "./project/project-reducer"; + import { sidePanelReducer, SidePanelState } from './side-panel/side-panel-reducer'; + import { authReducer, AuthState } from "./auth/auth-reducer"; + import { dataExplorerReducer, DataExplorerState } from './data-explorer/data-explorer-reducer'; + import { projectPanelMiddleware } from './project-panel/project-panel-middleware'; + import { detailsPanelReducer, DetailsPanelState } from './details-panel/details-panel-reducer'; + import { contextMenuReducer, ContextMenuState } from './context-menu/context-menu-reducer'; +import { reducer as formReducer } from 'redux-form'; const composeEnhancers = (process.env.NODE_ENV === 'development' && @@@ -36,7 -37,7 +38,8 @@@ const rootReducer = combineReducers( dataExplorer: dataExplorerReducer, sidePanel: sidePanelReducer, detailsPanel: detailsPanelReducer, - contextMenu: contextMenuReducer ++ contextMenu: contextMenuReducer, + form: formReducer }); diff --cc src/views-components/create-project-dialog/create-project-dialog.tsx index f75c4593,2f3e0b7f..43621bf7 --- a/src/views-components/create-project-dialog/create-project-dialog.tsx +++ b/src/views-components/create-project-dialog/create-project-dialog.tsx @@@ -4,12 -4,10 +4,12 @@@ 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 actions, { createProject, getProjectList } from "../../store/project/project-action"; - import dataExplorerActions from "../../store/data-explorer/data-explorer-action"; -import { DialogProjectCreate as DialogProjectCreateComponent } from "../dialog-create/dialog-project-create"; ++import DialogProjectCreate from "../dialog-create/dialog-project-create"; + import { projectActions, createProject, getProjectList } from "../../store/project/project-action"; + import { dataExplorerActions } from "../../store/data-explorer/data-explorer-action"; import { PROJECT_PANEL_ID } from "../../views/project-panel/project-panel"; const mapStateToProps = (state: RootState) => ({ @@@ -27,14 -25,11 +27,14 @@@ export const addProject = (data: { name const mapDispatchToProps = (dispatch: Dispatch) => ({ handleClose: () => { - dispatch(actions.CLOSE_PROJECT_CREATOR()); + dispatch(projectActions.CLOSE_PROJECT_CREATOR()); }, onSubmit: (data: { name: string, description: string }) => { - dispatch(submit(data)); + return dispatch(addProject(data)) + .catch((e: any) => { + throw new SubmissionError({ name: e.errors.join("").includes("UniqueViolation") ? "Project with this name already exists." : "" }); + }); } }); - export default connect(mapStateToProps, mapDispatchToProps)(DialogProjectCreate); -export const CreateProjectDialog = connect(mapStateToProps, mapDispatchToProps)(DialogProjectCreateComponent); ++export const CreateProjectDialog = connect(mapStateToProps, mapDispatchToProps)(DialogProjectCreate); diff --cc src/views-components/dialog-create/dialog-project-create.tsx index 6fb8a699,aefb8159..34c655e2 --- a/src/views-components/dialog-create/dialog-project-create.tsx +++ b/src/views-components/dialog-create/dialog-project-create.tsx @@@ -122,7 -37,106 +122,7 @@@ const styles: StyleRulesCallback void; - onSubmit: (data: { name: string, description: string }) => void; -} - -interface DialogState { - name: string; - description: string; - isNameValid: boolean; - isDescriptionValid: boolean; -} - -export const DialogProjectCreate = withStyles(styles)( - class extends React.Component> { - state: DialogState = { - name: '', - description: '', - isNameValid: false, - isDescriptionValid: true - }; - - render() { - const { name, description } = this.state; - const { classes, open, handleClose } = this.props; - - return ( - -
- Create a project - - this.isNameValid(e)} - isRequired={true} - render={hasError => - this.handleProjectName(e)} - label="Project name" - error={hasError} - fullWidth/>}/> - this.isDescriptionValid(e)} - isRequired={false} - render={hasError => - this.handleDescriptionValue(e)} - label="Description - optional" - error={hasError} - fullWidth/>}/> - - - - - -
-
- ); - } - - handleSubmit = () => { - this.props.onSubmit({ - name: this.state.name, - description: this.state.description - }); - } - - handleProjectName(e: React.ChangeEvent) { - this.setState({ - name: e.target.value, - }); - } - - handleDescriptionValue(e: React.ChangeEvent) { - this.setState({ - description: e.target.value, - }); - } - - isNameValid(value: boolean | string) { - this.setState({ - isNameValid: value, - }); - } - - isDescriptionValid(value: boolean | string) { - this.setState({ - isDescriptionValid: value, - }); - } - } -); +export default compose( + reduxForm({ form: 'projectCreateDialog' }), + withStyles(styles) - )(DialogProjectCreate); ++)(DialogProjectCreate); diff --cc src/views/workbench/workbench.tsx index b2bdac80,a62b713a..b1e7cd78 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@@ -111,207 -104,157 +104,157 @@@ interface WorkbenchState }; } - - class Workbench extends React.Component { - state = { - contextMenu: { - anchorEl: undefined, - itemUuid: undefined - }, - isCreationDialogOpen: false, - anchorEl: null, - searchText: "", - breadcrumbs: [], - menuItems: { - accountMenu: [ - { - label: "Logout", - action: () => this.props.dispatch(authActions.LOGOUT()) - }, - { - label: "My account", - action: () => this.props.dispatch(push("/my-account")) - } - ], - helpMenu: [ - { - label: "Help", - action: () => this.props.dispatch(push("/help")) + export const Workbench = withStyles(styles)( + connect( + (state: RootState) => ({ + projects: state.projects.items, + currentProjectId: state.projects.currentItemId, + user: state.auth.user, + sidePanelItems: state.sidePanel + }) + )( + class extends React.Component { + state = { + isCreationDialogOpen: false, + anchorEl: null, + searchText: "", + breadcrumbs: [], + menuItems: { + accountMenu: [ + { + label: "Logout", + action: () => this.props.dispatch(authActions.LOGOUT()) + }, + { + label: "My account", + action: () => this.props.dispatch(push("/my-account")) + } + ], + helpMenu: [ + { + label: "Help", + action: () => this.props.dispatch(push("/help")) + } + ], + anonymousMenu: [ + { + label: "Sign in", + action: () => this.props.dispatch(authActions.LOGIN()) + } + ] } - ], - anonymousMenu: [ - { - label: "Sign in", - action: () => this.props.dispatch(authActions.LOGIN()) - } - ] - } - }; + }; - mainAppBarActions: MainAppBarActionProps = { - onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => { - this.props.dispatch(setProjectItem(itemId, ItemMode.BOTH)); - this.props.dispatch(loadDetails(itemId, ResourceKind.Project)); - }, - onSearch: searchText => { - this.setState({ searchText }); - this.props.dispatch(push(`/search?q=${searchText}`)); - }, - onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action(), - onDetailsPanelToggle: () => { - this.props.dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL()); - }, - onContextMenu: (event: React.MouseEvent, breadcrumb: NavBreadcrumb) => { - this.openContextMenu(event, breadcrumb.itemId); - } - }; + render() { + const path = getTreePath(this.props.projects, this.props.currentProjectId); + const breadcrumbs = path.map(item => ({ + label: item.data.name, + itemId: item.data.uuid, + status: item.status + })); - toggleSidePanelOpen = (itemId: string) => { - this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId)); - } + const { classes, user } = this.props; + return ( +
+
+ +
+ {user && + +
+ this.openContextMenu(event, authService.getUuid() || "", ContextMenuKind.RootProject)}> + this.props.dispatch(setProjectItem(itemId, ItemMode.OPEN))} + onContextMenu={(event, item) => this.openContextMenu(event, item.data.uuid, ContextMenuKind.Project)} + toggleActive={itemId => { + this.props.dispatch(setProjectItem(itemId, ItemMode.ACTIVE)); + this.props.dispatch(loadDetails(itemId, ResourceKind.Project)); + this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.Projects)); + }} /> + + } +
+
+ + + +
- { user && } ++ {user && } +
+ + +
+ ); + } - toggleSidePanelActive = (itemId: string) => { - this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId)); - this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId)); - this.props.dispatch(push("/")); - } + renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => this.props.dispatch(setProjectItem(itemId, ItemMode.ACTIVE))} + onContextMenu={(event, item) => this.openContextMenu(event, item.uuid, ContextMenuKind.Project)} + onDialogOpen={this.handleCreationDialogOpen} + onItemClick={item => { + this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind)); + }} + onItemDoubleClick={item => { + this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE)); + this.props.dispatch(loadDetails(item.uuid, ResourceKind.Project)); + }} + {...props} /> - handleCreationDialogOpen = (itemUuid: string) => { - this.closeContextMenu(); - this.props.dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: itemUuid })); - } + mainAppBarActions: MainAppBarActionProps = { + onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => { + this.props.dispatch(setProjectItem(itemId, ItemMode.BOTH)); + this.props.dispatch(loadDetails(itemId, ResourceKind.Project)); + }, + onSearch: searchText => { + this.setState({ searchText }); + this.props.dispatch(push(`/search?q=${searchText}`)); + }, + onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action(), + onDetailsPanelToggle: () => { + this.props.dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL()); + }, + onContextMenu: (event: React.MouseEvent, breadcrumb: NavBreadcrumb) => { + this.openContextMenu(event, breadcrumb.itemId, ContextMenuKind.Project); + } + }; + toggleSidePanelOpen = (itemId: string) => { + this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId)); + } - openContextMenu = (event: React.MouseEvent, itemUuid: string) => { - event.preventDefault(); - this.setState({ - contextMenu: { - anchorEl: mockAnchorFromMouseEvent(event), - itemUuid + toggleSidePanelActive = (itemId: string) => { + this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId)); + this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId)); + this.props.dispatch(push("/")); } - }); - } - closeContextMenu = () => { - this.setState({ contextMenu: {} }); - } + handleCreationDialogOpen = (itemUuid: string) => { + this.props.dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: itemUuid })); + } - openCreateDialog = (item: ContextMenuAction) => { - const { itemUuid } = this.state.contextMenu; - if (item.openCreateDialog && itemUuid) { - this.handleCreationDialogOpen(itemUuid); + openContextMenu = (event: React.MouseEvent, itemUuid: string, kind: ContextMenuKind) => { + event.preventDefault(); + this.props.dispatch( + contextMenuActions.OPEN_CONTEXT_MENU({ + position: { x: event.clientX, y: event.clientY }, + resource: { uuid: itemUuid, kind } + }) + ); + } } - } - - render() { - const path = getTreePath(this.props.projects, this.props.currentProjectId); - const breadcrumbs = path.map(item => ({ - label: item.data.name, - itemId: item.data.uuid, - status: item.status - })); - - const { classes, user } = this.props; - return ( -
-
- -
- {user && - -
- this.openContextMenu(event, authService.getUuid() || "")}> - this.props.dispatch(setProjectItem(itemId, ItemMode.OPEN))} - onContextMenu={(event, item) => this.openContextMenu(event, item.data.uuid)} - toggleActive={itemId => { - this.props.dispatch(setProjectItem(itemId, ItemMode.ACTIVE)); - this.props.dispatch(loadDetails(itemId, ResourceKind.Project)); - this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(SidePanelIdentifiers.Projects)); - }} /> - - } -
-
- - - -
- -
- - -
- ); - } - - renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => this.props.dispatch(setProjectItem(itemId, ItemMode.ACTIVE))} - onContextMenu={(event, item) => this.openContextMenu(event, item.uuid)} - onDialogOpen={this.handleCreationDialogOpen} - onItemClick={item => { - this.props.dispatch(loadDetails(item.uuid, item.kind as ResourceKind)); - }} - onItemDoubleClick={item => { - this.props.dispatch(setProjectItem(item.uuid, ItemMode.ACTIVE)); - this.props.dispatch(loadDetails(item.uuid, ResourceKind.Project)); - }} - {...props} /> - } - - const contextMenuActions = [[{ - icon: "fas fa-plus fa-fw", - name: "New project", - openCreateDialog: true - }, { - icon: "fas fa-users fa-fw", - name: "Share" - }, { - icon: "fas fa-sign-out-alt fa-fw", - name: "Move to" - }, { - icon: "fas fa-star fa-fw", - name: "Add to favourite" - }, { - icon: "fas fa-edit fa-fw", - name: "Rename" - }, { - icon: "fas fa-copy fa-fw", - name: "Make a copy" - }, { - icon: "fas fa-download fa-fw", - name: "Download" - }], [{ - icon: "fas fa-trash-alt fa-fw", - name: "Remove" - } - ]]; - - export default connect( - (state: RootState) => ({ - projects: state.projects.items, - currentProjectId: state.projects.currentItemId, - user: state.auth.user, - sidePanelItems: state.sidePanel - }) - )( - withStyles(styles)(Workbench) + ) ); diff --cc yarn.lock index 0034169d,6960aaf6..3557ebef --- a/yarn.lock +++ b/yarn.lock @@@ -3347,13 -3334,9 +3345,13 @@@ hmac-drbg@^1.0.0 minimalistic-crypto-utils "^1.0.1" hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0: - version "2.5.4" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.4.tgz#fc3b1ac05d2ae3abedec84eba846511b0d4fcc4f" + version "2.5.5" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" +hoist-non-react-statics@^2.5.4: + version "2.5.5" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" + home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"