From a781af5eae4f4e7f6e07a2b205ad4e54e9e8ec7a Mon Sep 17 00:00:00 2001 From: Michal Klobukowski Date: Fri, 13 Jul 2018 16:18:05 +0200 Subject: [PATCH] Implement better pattern for hanling actions in context menu Feature #13805 Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski --- .../api/common-resource-service.test.ts | 2 +- src/components/attribute/attribute.tsx | 2 +- src/components/context-menu/context-menu.tsx | 23 +++-- .../data-explorer/data-explorer.tsx | 8 +- src/components/empty-state/empty-state.tsx | 2 +- .../groups-service/groups-service.test.ts | 2 +- .../project-service/project-service.test.ts | 2 +- .../context-menu/context-menu-reducer.ts | 8 +- .../project-panel/project-panel-middleware.ts | 6 +- src/store/store.ts | 2 +- .../context-menu/context-menu-item-set.ts | 12 +++ .../context-menu/context-menu.tsx | 89 +++++-------------- .../context-menu/empty-item-set.ts | 13 +++ src/views-components/context-menu/index.ts | 17 ++++ .../context-menu/project-item-set.ts | 43 +++++++++ .../context-menu/root-project-item-set.ts | 21 +++++ .../create-project-dialog.tsx | 2 +- src/views/workbench/workbench.tsx | 5 +- 18 files changed, 155 insertions(+), 104 deletions(-) create mode 100644 src/views-components/context-menu/context-menu-item-set.ts create mode 100644 src/views-components/context-menu/empty-item-set.ts create mode 100644 src/views-components/context-menu/index.ts create mode 100644 src/views-components/context-menu/project-item-set.ts create mode 100644 src/views-components/context-menu/root-project-item-set.ts diff --git a/src/common/api/common-resource-service.test.ts b/src/common/api/common-resource-service.test.ts index 7093b59c..b8b1f44c 100644 --- a/src/common/api/common-resource-service.test.ts +++ b/src/common/api/common-resource-service.test.ts @@ -4,7 +4,7 @@ import CommonResourceService from "./common-resource-service"; import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; +import MockAdapter from "axios-mock-adapter/types"; describe("CommonResourceService", () => { const axiosInstance = axios.create(); diff --git a/src/components/attribute/attribute.tsx b/src/components/attribute/attribute.tsx index ea35f5bf..c0874f7d 100644 --- a/src/components/attribute/attribute.tsx +++ b/src/components/attribute/attribute.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import Typography from '@material-ui/core/Typography'; import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles'; -import { ArvadosTheme } from 'src/common/custom-theme'; +import { ArvadosTheme } from '../../common/custom-theme'; interface AttributeDataProps { label: string; diff --git a/src/components/context-menu/context-menu.tsx b/src/components/context-menu/context-menu.tsx index 56261818..49b65927 100644 --- a/src/components/context-menu/context-menu.tsx +++ b/src/components/context-menu/context-menu.tsx @@ -5,24 +5,23 @@ import * as React from "react"; import { Popover, List, ListItem, ListItemIcon, ListItemText, Divider } from "@material-ui/core"; import { DefaultTransformOrigin } from "../popover/helpers"; -export interface ContextMenuAction { +export interface ContextMenuItem { name: string; icon: string; - openCreateDialog?: boolean; } -export type ContextMenuActionGroup = ContextMenuAction[]; +export type ContextMenuItemGroup = ContextMenuItem[]; export interface ContextMenuProps { anchorEl?: HTMLElement; - actions: ContextMenuActionGroup[]; - onActionClick: (action: ContextMenuAction) => void; + items: ContextMenuItemGroup[]; + onItemClick: (action: ContextMenuItem) => void; onClose: () => void; } export default class ContextMenu extends React.PureComponent { render() { - const { anchorEl, actions, onClose, onActionClick } = this.props; + const { anchorEl, items, onClose, onItemClick } = this.props; return { anchorOrigin={DefaultTransformOrigin} onContextMenu={this.handleContextMenu}> - {actions.map((group, groupIndex) => + {items.map((group, groupIndex) => - {group.map((action, actionIndex) => + {group.map((item, actionIndex) => onActionClick(action)}> + onClick={() => onItemClick(item)}> - + - {action.name} + {item.name} )} - {groupIndex < actions.length - 1 && } + {groupIndex < items.length - 1 && } )} ; diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx index e851ca99..1073ddd8 100644 --- a/src/components/data-explorer/data-explorer.tsx +++ b/src/components/data-explorer/data-explorer.tsx @@ -5,10 +5,10 @@ import * as React from 'react'; import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, Theme, WithStyles, TablePagination, IconButton } from '@material-ui/core'; import MoreVertIcon from "@material-ui/icons/MoreVert"; -import ColumnSelector from "../../components/column-selector/column-selector"; -import DataTable, { DataColumns } from "../../components/data-table/data-table"; -import { DataColumn } from "../../components/data-table/data-column"; -import { DataTableFilterItem } from '../../components/data-table-filters/data-table-filters'; +import ColumnSelector from "../column-selector/column-selector"; +import DataTable, { DataColumns } from "../data-table/data-table"; +import { DataColumn } from "../data-table/data-column"; +import { DataTableFilterItem } from '../data-table-filters/data-table-filters'; import SearchInput from '../search-input/search-input'; interface DataExplorerProps { diff --git a/src/components/empty-state/empty-state.tsx b/src/components/empty-state/empty-state.tsx index 205053b5..9ab30635 100644 --- a/src/components/empty-state/empty-state.tsx +++ b/src/components/empty-state/empty-state.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import Typography from '@material-ui/core/Typography'; import { WithStyles, withStyles, StyleRulesCallback } from '@material-ui/core/styles'; -import { ArvadosTheme } from 'src/common/custom-theme'; +import { ArvadosTheme } from '../../common/custom-theme'; import IconBase, { IconTypes } from '../icon/icon'; export interface EmptyStateDataProps { diff --git a/src/services/groups-service/groups-service.test.ts b/src/services/groups-service/groups-service.test.ts index 2562a595..92d22778 100644 --- a/src/services/groups-service/groups-service.test.ts +++ b/src/services/groups-service/groups-service.test.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; +import MockAdapter from "axios-mock-adapter/types"; import GroupsService from "./groups-service"; describe("GroupsService", () => { diff --git a/src/services/project-service/project-service.test.ts b/src/services/project-service/project-service.test.ts index 68df2450..76da3d86 100644 --- a/src/services/project-service/project-service.test.ts +++ b/src/services/project-service/project-service.test.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; +import MockAdapter from "axios-mock-adapter/types"; import ProjectService from "./project-service"; import FilterBuilder from "../../common/api/filter-builder"; import { ProjectResource } from "../../models/project"; diff --git a/src/store/context-menu/context-menu-reducer.ts b/src/store/context-menu/context-menu-reducer.ts index 129c2af4..147f0943 100644 --- a/src/store/context-menu/context-menu-reducer.ts +++ b/src/store/context-menu/context-menu-reducer.ts @@ -17,13 +17,7 @@ export interface ContextMenuPosition { export interface ContextMenuResource { uuid: string; - kind: ContextMenuKind; -} - -export enum ContextMenuKind { - RootProject = "RootProject", - Project = "Project", - Collection = "Collection" + kind: string; } const initialState = { diff --git a/src/store/project-panel/project-panel-middleware.ts b/src/store/project-panel/project-panel-middleware.ts index e72b6c1b..80fb7fa3 100644 --- a/src/store/project-panel/project-panel-middleware.ts +++ b/src/store/project-panel/project-panel-middleware.ts @@ -3,11 +3,11 @@ // SPDX-License-Identifier: AGPL-3.0 import { Middleware } from "redux"; -import actions from "../../store/data-explorer/data-explorer-action"; +import actions from "../data-explorer/data-explorer-action"; import { PROJECT_PANEL_ID, columns, ProjectPanelFilter, ProjectPanelColumnNames } from "../../views/project-panel/project-panel"; import { groupsService } from "../../services/services"; -import { RootState } from "../../store/store"; -import { getDataExplorer, DataExplorerState } from "../../store/data-explorer/data-explorer-reducer"; +import { RootState } from "../store"; +import { getDataExplorer, DataExplorerState } from "../data-explorer/data-explorer-reducer"; import { resourceToDataItem, ProjectPanelItem } from "../../views/project-panel/project-panel-item"; import FilterBuilder from "../../common/api/filter-builder"; import { DataColumns } from "../../components/data-table/data-table"; diff --git a/src/store/store.ts b/src/store/store.ts index 130028ad..d87c8031 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -11,7 +11,7 @@ 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 { 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'; diff --git a/src/views-components/context-menu/context-menu-item-set.ts b/src/views-components/context-menu/context-menu-item-set.ts new file mode 100644 index 00000000..0b207ad2 --- /dev/null +++ b/src/views-components/context-menu/context-menu-item-set.ts @@ -0,0 +1,12 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch } from "redux"; +import { ContextMenuItemGroup, ContextMenuItem } from "../../components/context-menu/context-menu"; +import { ContextMenuResource } from "../../store/context-menu/context-menu-reducer"; + +export interface ContextMenuItemSet { + handleItem (dispatch: Dispatch, action: ContextMenuItem, resource: ContextMenuResource): void; + getItems (): ContextMenuItemGroup[]; +} diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx index bfd833a8..93cb644a 100644 --- a/src/views-components/context-menu/context-menu.tsx +++ b/src/views-components/context-menu/context-menu.tsx @@ -5,33 +5,31 @@ import { connect, Dispatch, DispatchProp } from "react-redux"; import { RootState } from "../../store/store"; import actions from "../../store/context-menu/context-menu-actions"; -import ContextMenu, { ContextMenuAction, ContextMenuProps } from "../../components/context-menu/context-menu"; +import ContextMenu, { ContextMenuProps, ContextMenuItem } from "../../components/context-menu/context-menu"; import { createAnchorAt } from "../../components/popover/helpers"; -import projectActions from "../../store/project/project-action"; -import { ContextMenuResource, ContextMenuKind } from "../../store/context-menu/context-menu-reducer"; +import { ContextMenuResource } from "../../store/context-menu/context-menu-reducer"; +import { ContextMenuItemSet } from "./context-menu-item-set"; +import { emptyItemSet } from "./empty-item-set"; - -type DataProps = Pick & { resource?: ContextMenuResource }; +type DataProps = Pick & { resource?: ContextMenuResource }; const mapStateToProps = (state: RootState): DataProps => { const { position, resource } = state.contextMenu; return { anchorEl: resource ? createAnchorAt(position) : undefined, - actions: resource ? menuActions[resource.kind] : [], + items: getMenuItemSet(resource).getItems(), resource }; }; -type ActionProps = Pick & { onActionClick: (action: ContextMenuAction, resource?: ContextMenuResource) => void }; +type ActionProps = Pick & { onItemClick: (item: ContextMenuItem, resource?: ContextMenuResource) => void }; const mapDispatchToProps = (dispatch: Dispatch): ActionProps => ({ onClose: () => { dispatch(actions.CLOSE_CONTEXT_MENU()); }, - onActionClick: (action: ContextMenuAction, resource?: ContextMenuResource) => { + onItemClick: (item: ContextMenuItem, resource?: ContextMenuResource) => { dispatch(actions.CLOSE_CONTEXT_MENU()); if (resource) { - if (action.name === "New project") { - dispatch(projectActions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid })); - } + getMenuItemSet(resource).handleItem(dispatch, item, resource); } } }); @@ -39,65 +37,20 @@ const mapDispatchToProps = (dispatch: Dispatch): ActionProps => ({ const mergeProps = ({ resource, ...dataProps }: DataProps, actionProps: ActionProps): ContextMenuProps => ({ ...dataProps, ...actionProps, - onActionClick: (action: ContextMenuAction) => { - actionProps.onActionClick(action, resource); + onItemClick: item => { + actionProps.onItemClick(item, resource); } }); -export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ContextMenu); +export const ContextMenuHOC = connect(mapStateToProps, mapDispatchToProps, mergeProps)(ContextMenu); -const menuActions = { - [ContextMenuKind.RootProject]: [[{ - icon: "fas fa-plus fa-fw", - name: "New project" - }]], - [ContextMenuKind.Project]: [[{ - icon: "fas fa-plus fa-fw", - name: "New project" - }, { - 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" - } - ]], - [ContextMenuKind.Collection]: [[{ - 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" - } - ]] +const menuItemSets = new Map(); + +export const addMenuItemsSet = (name: string, itemSet: ContextMenuItemSet) => { + menuItemSets.set(name, itemSet); }; + +const getMenuItemSet = (resource?: ContextMenuResource): ContextMenuItemSet => { + return resource ? menuItemSets.get(resource.kind) || emptyItemSet : emptyItemSet; +}; + diff --git a/src/views-components/context-menu/empty-item-set.ts b/src/views-components/context-menu/empty-item-set.ts new file mode 100644 index 00000000..209b75cd --- /dev/null +++ b/src/views-components/context-menu/empty-item-set.ts @@ -0,0 +1,13 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ContextMenuItemGroup } from "../../components/context-menu/context-menu"; +import { ContextMenuItemSet } from "./context-menu-item-set"; + +export const emptyItemSet: ContextMenuItemSet = { + getItems: () => items, + handleItem: () => { return; } +}; + +const items: ContextMenuItemGroup[] = []; \ No newline at end of file diff --git a/src/views-components/context-menu/index.ts b/src/views-components/context-menu/index.ts new file mode 100644 index 00000000..92933300 --- /dev/null +++ b/src/views-components/context-menu/index.ts @@ -0,0 +1,17 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ContextMenuHOC, addMenuItemsSet } from "./context-menu"; +import { projectItemSet } from "./project-item-set"; +import { rootProjectItemSet } from "./root-project-item-set"; + +export default ContextMenuHOC; + +export enum ContextMenuKind { + RootProject = "RootProject", + Project = "Project" +} + +addMenuItemsSet(ContextMenuKind.RootProject, rootProjectItemSet); +addMenuItemsSet(ContextMenuKind.Project, projectItemSet); \ No newline at end of file diff --git a/src/views-components/context-menu/project-item-set.ts b/src/views-components/context-menu/project-item-set.ts new file mode 100644 index 00000000..583bbaaa --- /dev/null +++ b/src/views-components/context-menu/project-item-set.ts @@ -0,0 +1,43 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ContextMenuItemGroup } from "../../components/context-menu/context-menu"; +import { ContextMenuItemSet } from "./context-menu-item-set"; +import actions from "../../store/project/project-action"; + +export const projectItemSet: ContextMenuItemSet = { + getItems: () => items, + handleItem: (dispatch, item, resource) => { + if (item.name === "New project") { + dispatch(actions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid })); + } + } +}; + +const items: ContextMenuItemGroup[] = [[{ + icon: "fas fa-plus fa-fw", + name: "New project" +}, { + 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" +} +]]; \ No newline at end of file diff --git a/src/views-components/context-menu/root-project-item-set.ts b/src/views-components/context-menu/root-project-item-set.ts new file mode 100644 index 00000000..ae760f0f --- /dev/null +++ b/src/views-components/context-menu/root-project-item-set.ts @@ -0,0 +1,21 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ContextMenuItemGroup } from "../../components/context-menu/context-menu"; +import { ContextMenuItemSet } from "./context-menu-item-set"; +import actions from "../../store/project/project-action"; + +export const rootProjectItemSet: ContextMenuItemSet = { + getItems: () => items, + handleItem: (dispatch, item, resource) => { + if (item.name === "New project") { + dispatch(actions.OPEN_PROJECT_CREATOR({ ownerUuid: resource.uuid })); + } + } +}; + +const items: ContextMenuItemGroup[] = [[{ + icon: "fas fa-plus fa-fw", + name: "New project" +}]]; \ No newline at end of file diff --git a/src/views-components/create-project-dialog/create-project-dialog.tsx b/src/views-components/create-project-dialog/create-project-dialog.tsx index 701ceee1..6b69b793 100644 --- a/src/views-components/create-project-dialog/create-project-dialog.tsx +++ b/src/views-components/create-project-dialog/create-project-dialog.tsx @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import { connect } from "react-redux"; -import { Dispatch } from "../../../node_modules/redux"; +import { Dispatch } from "redux"; import { RootState } from "../../store/store"; import DialogProjectCreate from "../dialog-create/dialog-project-create"; import actions, { createProject, getProjectList } from "../../store/project/project-action"; diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 52332e8b..13e22d8c 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -26,7 +26,7 @@ import projectActions from "../../store/project/project-action"; import ProjectPanel from "../project-panel/project-panel"; import DetailsPanel from '../../views-components/details-panel/details-panel'; import { ArvadosTheme } from '../../common/custom-theme'; -import ContextMenu from "../../views-components/context-menu/context-menu"; +import ContextMenu, { ContextMenuKind } from "../../views-components/context-menu"; import CreateProjectDialog from "../../views-components/create-project-dialog/create-project-dialog"; import { authService } from '../../services/services'; @@ -35,7 +35,6 @@ import contextMenuActions from "../../store/context-menu/context-menu-actions"; import { SidePanelIdentifiers } from '../../store/side-panel/side-panel-reducer'; import { ProjectResource } from '../../models/project'; import { ResourceKind } from '../../models/resource'; -import { ContextMenuKind } from '../../store/context-menu/context-menu-reducer'; const drawerWidth = 240; const appBarHeight = 100; @@ -217,7 +216,7 @@ class Workbench extends React.Component { this.props.dispatch(setProjectItem(itemId, ItemMode.OPEN))} - onContextMenu={(event, item) => this.openContextMenu(event, item.data.uuid, ContextMenuKind.Project)} + 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)); -- 2.30.2