From e4de9a43cee1a8859cb2a42ea01723d632621ce4 Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Sat, 27 Feb 2021 17:05:15 -0500 Subject: [PATCH] 17426: Add plugin ability to modify +New and account menu Arvados-DCO-1.1-Signed-off-by: Peter Amstutz --- src/common/plugintypes.ts | 14 +++- src/plugins.tsx | 3 + src/plugins/blank/index.tsx | 3 + src/plugins/example/index.tsx | 40 +++++++++-- .../main-app-bar/account-menu.tsx | 71 +++++++++++-------- .../main-app-bar/main-app-bar.tsx | 21 +++--- .../side-panel-button/side-panel-button.tsx | 42 ++++++++--- src/views/workbench/workbench.tsx | 4 +- 8 files changed, 140 insertions(+), 58 deletions(-) diff --git a/src/common/plugintypes.ts b/src/common/plugintypes.ts index dfbe7c45..bda92b67 100644 --- a/src/common/plugintypes.ts +++ b/src/common/plugintypes.ts @@ -5,15 +5,18 @@ import * as React from 'react'; import { Dispatch } from 'redux'; import { RootStore, RootState } from '~/store/store'; +import { ResourcesState } from '~/store/resources/resources'; +import { Location } from 'history'; -export type RouteListReducer = (startingList: React.ReactElement[]) => React.ReactElement[]; +export type ElementListReducer = (startingList: React.ReactElement[]) => React.ReactElement[]; export type CategoriesListReducer = (startingList: string[]) => string[]; export type NavigateMatcher = (dispatch: Dispatch, getState: () => RootState, uuid: string) => boolean; export type LocationChangeMatcher = (store: RootStore, pathname: string) => boolean; +export type EnableNew = (location: Location, currentItemId: string, currentUserUUID: string | undefined, resources: ResourcesState) => boolean; export interface PluginConfig { // Customize the list of possible center panels by adding or removing Route components. - centerPanelList: RouteListReducer[]; + centerPanelList: ElementListReducer[]; // Customize the list of side panel categories sidePanelCategories: CategoriesListReducer[]; @@ -32,4 +35,11 @@ export interface PluginConfig { appBarMiddle?: React.ReactElement; appBarRight?: React.ReactElement; + + // Customize the list menu items in the account menu + accountMenuList: ElementListReducer[]; + + enableNewButtonMatchers: EnableNew[]; + + newButtonMenuList: ElementListReducer[]; } diff --git a/src/plugins.tsx b/src/plugins.tsx index 83593f23..3a58a8c2 100644 --- a/src/plugins.tsx +++ b/src/plugins.tsx @@ -13,6 +13,9 @@ export const pluginConfig: PluginConfig = { appBarLeft: undefined, appBarMiddle: undefined, appBarRight: undefined, + accountMenuList: [], + enableNewButtonMatchers: [], + newButtonMenuList: [] }; // Starting here, import and register your Workbench 2 plugins. // diff --git a/src/plugins/blank/index.tsx b/src/plugins/blank/index.tsx index 9471372d..0074c02a 100644 --- a/src/plugins/blank/index.tsx +++ b/src/plugins/blank/index.tsx @@ -13,6 +13,9 @@ export const register = (pluginConfig: PluginConfig) => { pluginConfig.sidePanelCategories.push((cats: string[]): string[] => []); + pluginConfig.accountMenuList.push((elms) => []); + pluginConfig.newButtonMenuList.push((elms) => []); + pluginConfig.appBarLeft = ; pluginConfig.appBarMiddle = ; pluginConfig.appBarRight = ; diff --git a/src/plugins/example/index.tsx b/src/plugins/example/index.tsx index 4fa98966..b8bfcb0f 100644 --- a/src/plugins/example/index.tsx +++ b/src/plugins/example/index.tsx @@ -14,16 +14,36 @@ import { Route, matchPath } from "react-router"; import { RootStore } from '~/store/store'; import { activateSidePanelTreeItem } from '~/store/side-panel-tree/side-panel-tree-actions'; import { setSidePanelBreadcrumbs } from '~/store/breadcrumbs/breadcrumbs-actions'; +import { DispatchProp, connect } from 'react-redux'; +import { MenuItem } from "@material-ui/core"; +import { propertiesActions } from '~/store/properties/properties-actions'; +import { Location } from 'history'; const categoryName = "Plugin Example"; export const routePath = "/examplePlugin"; +const propertyKey = "Example_menu_item_pressed_count"; -const ExamplePluginMainPanel = (props: {}) => { - return - This is a example main panel plugin. - ; +interface ExampleProps { + pressedCount: number; +} + +const exampleMapStateToProps = (state: RootState) => ({ pressedCount: state.properties[propertyKey] || 0 }); + +const incrementPressedCount = (dispatch: Dispatch, pressedCount: number) => { + dispatch(propertiesActions.SET_PROPERTY({ key: propertyKey, value: pressedCount + 1 })); }; +const ExampleMenuComponent = connect(exampleMapStateToProps)( + ({ pressedCount, dispatch }: ExampleProps & DispatchProp) => + incrementPressedCount(dispatch, pressedCount)}>Example menu item +); + +const ExamplePluginMainPanel = connect(exampleMapStateToProps)( + ({ pressedCount }: ExampleProps) => + + This is a example main panel plugin. The example menu item has been pressed {pressedCount} times. + ); + export const register = (pluginConfig: PluginConfig) => { pluginConfig.centerPanelList.push((elms) => { @@ -31,6 +51,16 @@ export const register = (pluginConfig: PluginConfig) => { return elms; }); + pluginConfig.accountMenuList.push((elms) => { + elms.push(); + return elms; + }); + + pluginConfig.newButtonMenuList.push((elms) => { + elms.push(); + return elms; + }); + pluginConfig.navigateToHandlers.push((dispatch: Dispatch, getState: () => RootState, uuid: string) => { if (uuid === categoryName) { dispatch(push(routePath)); @@ -49,4 +79,6 @@ export const register = (pluginConfig: PluginConfig) => { } return false; }); + + pluginConfig.enableNewButtonMatchers.push((location: Location) => (!!matchPath(location.pathname, { path: routePath, exact: true }))); }; diff --git a/src/views-components/main-app-bar/account-menu.tsx b/src/views-components/main-app-bar/account-menu.tsx index ea3a2dd9..7892b8a7 100644 --- a/src/views-components/main-app-bar/account-menu.tsx +++ b/src/views-components/main-app-bar/account-menu.tsx @@ -20,6 +20,8 @@ import { navigateToLinkAccount } from '~/store/navigation/navigation-action'; import { openUserVirtualMachines } from "~/store/virtual-machines/virtual-machines-actions"; +import { pluginConfig } from '~/plugins'; +import { ElementListReducer } from '~/common/plugintypes'; interface AccountMenuProps { user?: User; @@ -57,38 +59,47 @@ const styles: StyleRulesCallback = () => ({ }); export const AccountMenuComponent = - ({ user, dispatch, currentRoute, workbenchURL, apiToken, localCluster, classes }: AccountMenuProps & DispatchProp & WithStyles) => - user - ? } - id="account-menu" - title="Account Management" - key={currentRoute}> - - {getUserDisplayName(user)} {user.uuid.substr(0, 5) !== localCluster && `(${user.uuid.substr(0, 5)})`} - - {user.isActive ? <> - dispatch(openUserVirtualMachines())}>Virtual Machines - {!user.isAdmin && dispatch(openRepositoriesPanel())}>Repositories} - { - dispatch(getNewExtraToken(true)); - dispatch(openTokenDialog); - }}>Get API token - dispatch(navigateToSshKeysUser)}>Ssh Keys - dispatch(navigateToSiteManager)}>Site Manager - dispatch(navigateToMyAccount)}>My account - dispatch(navigateToLinkAccount)}>Link account - : null} + ({ user, dispatch, currentRoute, workbenchURL, apiToken, localCluster, classes }: AccountMenuProps & DispatchProp & WithStyles) => { + let accountMenuItems = <> + dispatch(openUserVirtualMachines())}>Virtual Machines + dispatch(openRepositoriesPanel())}>Repositories + { + dispatch(getNewExtraToken(true)); + dispatch(openTokenDialog); + }}>Get API token + dispatch(navigateToSshKeysUser)}>Ssh Keys + dispatch(navigateToSiteManager)}>Site Manager + dispatch(navigateToMyAccount)}>My account + dispatch(navigateToLinkAccount)}>Link account Switch to Workbench v1 - - dispatch(authActions.LOGOUT({deleteLinkData: true}))}> - Logout - - - : null; + ; -export const AccountMenu = withStyles(styles)( connect(mapStateToProps)(AccountMenuComponent) ); + const reduceItemsFn: (a: React.ReactElement[], + b: ElementListReducer) => React.ReactElement[] = (a, b) => b(a); + + accountMenuItems = React.createElement(React.Fragment, null, + pluginConfig.accountMenuList.reduce(reduceItemsFn, React.Children.toArray(accountMenuItems.props.children))); + + return user + ? } + id="account-menu" + title="Account Management" + key={currentRoute}> + + {getUserDisplayName(user)} {user.uuid.substr(0, 5) !== localCluster && `(${user.uuid.substr(0, 5)})`} + + {user.isActive && accountMenuItems} + + dispatch(authActions.LOGOUT({ deleteLinkData: true }))}> + Logout + + + : null; + }; + +export const AccountMenu = withStyles(styles)(connect(mapStateToProps)(AccountMenuComponent)); diff --git a/src/views-components/main-app-bar/main-app-bar.tsx b/src/views-components/main-app-bar/main-app-bar.tsx index 7bec7b24..44cbe20d 100644 --- a/src/views-components/main-app-bar/main-app-bar.tsx +++ b/src/views-components/main-app-bar/main-app-bar.tsx @@ -47,7 +47,7 @@ export const MainAppBar = withStyles(styles)( ({props.uuidPrefix}) - + {props.buildInfo} } @@ -65,14 +65,17 @@ export const MainAppBar = withStyles(styles)( alignItems="center" justify="flex-end" wrap="nowrap"> - {pluginConfig.appBarRight || - (props.user ? <> - - - {props.user.isAdmin && } - - : - )} + {props.user ? <> + + + {pluginConfig.appBarRight || + <> + {props.user.isAdmin && } + + } + : + pluginConfig.appBarRight || + } diff --git a/src/views-components/side-panel-button/side-panel-button.tsx b/src/views-components/side-panel-button/side-panel-button.tsx index 3ca2f0d6..4c25bcfe 100644 --- a/src/views-components/side-panel-button/side-panel-button.tsx +++ b/src/views-components/side-panel-button/side-panel-button.tsx @@ -18,6 +18,9 @@ import { matchProjectRoute } from '~/routes/routes'; import { GroupResource } from '~/models/group'; import { ResourcesState, getResource } from '~/store/resources/resources'; import { extractUuidKind, ResourceKind } from '~/models/resource'; +import { pluginConfig } from '~/plugins'; +import { ElementListReducer } from '~/common/plugintypes'; +import { Location } from 'history'; type CssRules = 'button' | 'menuItem' | 'icon'; @@ -37,7 +40,7 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ }); interface SidePanelDataProps { - location: any; + location: Location; currentItemId: string; resources: ResourcesState; currentUserUUID: string | undefined; @@ -91,6 +94,31 @@ export const SidePanelButton = withStyles(styles)( enabled = true; } } + + for (const enableFn of pluginConfig.enableNewButtonMatchers) { + if (enableFn(location, currentItemId, currentUserUUID, resources)) { + enabled = true; + } + } + + let menuItems = <> + + New collection + + + Run a process + + + New project + + ; + + const reduceItemsFn: (a: React.ReactElement[], + b: ElementListReducer) => React.ReactElement[] = (a, b) => b(a); + + menuItems = React.createElement(React.Fragment, null, + pluginConfig.newButtonMenuList.reduce(reduceItemsFn, React.Children.toArray(menuItems.props.children))); + return @@ -109,15 +137,7 @@ export const SidePanelButton = withStyles(styles)( onClose={this.handleClose} onClick={this.handleClose} transformOrigin={transformOrigin}> - - New collection - - - Run a process - - - New project - + {menuItems} @@ -150,4 +170,4 @@ export const SidePanelButton = withStyles(styles)( } } ) -); \ No newline at end of file +); diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 113cbd67..78ec3c87 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -102,7 +102,7 @@ import { AutoLogout } from '~/views-components/auto-logout/auto-logout'; import { RestoreCollectionVersionDialog } from '~/views-components/collections-dialog/restore-version-dialog'; import { WebDavS3InfoDialog } from '~/views-components/webdav-s3-dialog/webdav-s3-dialog'; import { pluginConfig } from '~/plugins'; -import { RouteListReducer } from '~/common/plugintypes'; +import { ElementListReducer } from '~/common/plugintypes'; type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content'; @@ -183,7 +183,7 @@ let routes = <> ; const reduceRoutesFn: (a: React.ReactElement[], - b: RouteListReducer) => React.ReactElement[] = (a, b) => b(a); + b: ElementListReducer) => React.ReactElement[] = (a, b) => b(a); routes = React.createElement(React.Fragment, null, pluginConfig.centerPanelList.reduce(reduceRoutesFn, React.Children.toArray(routes.props.children))); -- 2.30.2