[ARVADOS-WORKBENCH2] updated: 2.1.0-218-gddaff5cb

Git user git at public.arvados.org
Sat Feb 27 22:05:44 UTC 2021


Summary of changes:
 src/common/plugintypes.ts                          | 14 ++++-
 src/plugins.tsx                                    |  3 +
 src/plugins/blank/index.tsx                        |  3 +
 src/plugins/example/index.tsx                      | 40 +++++++++++--
 src/views-components/main-app-bar/account-menu.tsx | 65 +++++++++++++---------
 src/views-components/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, 137 insertions(+), 55 deletions(-)

       via  ddaff5cb9937340eed1c8f3b59053146dcefa3b8 (commit)
      from  9cc4f706feecd621f9d0121942bb0faa881ec926 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit ddaff5cb9937340eed1c8f3b59053146dcefa3b8
Author: Peter Amstutz <peter.amstutz at curii.com>
Date:   Sat Feb 27 17:05:15 2021 -0500

    17426: Add plugin ability to modify +New and account menu
    
    Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz at curii.com>

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 = <span />;
     pluginConfig.appBarMiddle = <span />;
     pluginConfig.appBarRight = <span />;
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 <Typography>
-        This is a example main panel plugin.
-    </Typography>;
+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<any>) =>
+        <MenuItem onClick={() => incrementPressedCount(dispatch, pressedCount)}>Example menu item</MenuItem >
+);
+
+const ExamplePluginMainPanel = connect(exampleMapStateToProps)(
+    ({ pressedCount }: ExampleProps) =>
+        <Typography>
+            This is a example main panel plugin.  The example menu item has been pressed {pressedCount} times.
+	</Typography>);
+
 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(<ExampleMenuComponent />);
+        return elms;
+    });
+
+    pluginConfig.newButtonMenuList.push((elms) => {
+        elms.push(<ExampleMenuComponent />);
+        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 6e844cc8..2d262b3f 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,35 +59,44 @@ const styles: StyleRulesCallback<CssRules> = () => ({
 });
 
 export const AccountMenuComponent =
-    ({ user, dispatch, currentRoute, workbenchURL, apiToken, localCluster, classes }: AccountMenuProps & DispatchProp<any> & WithStyles<CssRules>) =>
-        user
-        ? <DropdownMenu
-            icon={<UserPanelIcon />}
-            id="account-menu"
-            title="Account Management"
-            key={currentRoute}>
-            <MenuItem disabled>
-                {getUserDisplayName(user)} {user.uuid.substr(0, 5) !== localCluster && `(${user.uuid.substr(0, 5)})`}
-            </MenuItem>
-            {user.isActive ? <>
-                <MenuItem onClick={() => dispatch(openUserVirtualMachines())}>Virtual Machines</MenuItem>
-                {!user.isAdmin && <MenuItem onClick={() => dispatch(openRepositoriesPanel())}>Repositories</MenuItem>}
-                <MenuItem onClick={() => dispatch(openCurrentTokenDialog)}>Current token</MenuItem>
-                <MenuItem onClick={() => dispatch(navigateToSshKeysUser)}>Ssh Keys</MenuItem>
-                <MenuItem onClick={() => dispatch(navigateToSiteManager)}>Site Manager</MenuItem>
-                <MenuItem onClick={() => dispatch(navigateToMyAccount)}>My account</MenuItem>
-                <MenuItem onClick={() => dispatch(navigateToLinkAccount)}>Link account</MenuItem>
-            </> : null}
+    ({ user, dispatch, currentRoute, workbenchURL, apiToken, localCluster, classes }: AccountMenuProps & DispatchProp<any> & WithStyles<CssRules>) => {
+        let accountMenuItems = <>
+            <MenuItem onClick={() => dispatch(openUserVirtualMachines())}>Virtual Machines</MenuItem>
+            <MenuItem onClick={() => dispatch(openRepositoriesPanel())}>Repositories</MenuItem>
+            <MenuItem onClick={() => dispatch(openCurrentTokenDialog)}>Current token</MenuItem>
+            <MenuItem onClick={() => dispatch(navigateToSshKeysUser)}>Ssh Keys</MenuItem>
+            <MenuItem onClick={() => dispatch(navigateToSiteManager)}>Site Manager</MenuItem>
+            <MenuItem onClick={() => dispatch(navigateToMyAccount)}>My account</MenuItem>
+            <MenuItem onClick={() => dispatch(navigateToLinkAccount)}>Link account</MenuItem>
             <MenuItem>
                 <a href={`${workbenchURL.replace(/\/$/, "")}/${wb1URL(currentRoute)}?api_token=${apiToken}`}
                     className={classes.link}>
                     Switch to Workbench v1</a></MenuItem>
-            <Divider />
-            <MenuItem data-cy="logout-menuitem"
-                onClick={() => dispatch(authActions.LOGOUT({deleteLinkData: true}))}>
-                Logout
-            </MenuItem>
-        </DropdownMenu>
-        : 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
+            ? <DropdownMenu
+                icon={<UserPanelIcon />}
+                id="account-menu"
+                title="Account Management"
+                key={currentRoute}>
+                <MenuItem disabled>
+                    {getUserDisplayName(user)} {user.uuid.substr(0, 5) !== localCluster && `(${user.uuid.substr(0, 5)})`}
+                </MenuItem>
+                {user.isActive && accountMenuItems}
+                <Divider />
+                <MenuItem data-cy="logout-menuitem"
+                    onClick={() => dispatch(authActions.LOGOUT({ deleteLinkData: true }))}>
+                    Logout
+		 </MenuItem>
+            </DropdownMenu>
+            : 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)(
                         <Typography variant='h6' color="inherit" noWrap>
                             <Link to={Routes.ROOT} className={props.classes.link}>
                                 <span dangerouslySetInnerHTML={{ __html: props.siteBanner }} /> ({props.uuidPrefix})
-                            </Link>
+                </Link>
                         </Typography>
                         <Typography variant="caption" color="inherit">{props.buildInfo}</Typography>
                     </Grid>}
@@ -65,14 +65,17 @@ export const MainAppBar = withStyles(styles)(
                         alignItems="center"
                         justify="flex-end"
                         wrap="nowrap">
-                        {pluginConfig.appBarRight ||
-                            (props.user ? <>
-                                <NotificationsMenu />
-                                <AccountMenu />
-                                {props.user.isAdmin && <AdminMenu />}
-                                <HelpMenu />
-                            </> :
-                                <HelpMenu />)}
+                        {props.user ? <>
+                            <NotificationsMenu />
+                            <AccountMenu />
+                            {pluginConfig.appBarRight ||
+                                <>
+                                    {props.user.isAdmin && <AdminMenu />}
+                                    <HelpMenu />
+                                </>}
+                        </> :
+                            pluginConfig.appBarRight || <HelpMenu />
+                        }
                     </Grid>
                 </Grid>
             </Toolbar>
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<CssRules> = (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 = <>
+                    <MenuItem data-cy='side-panel-new-collection' className={classes.menuItem} onClick={this.handleNewCollectionClick}>
+                        <CollectionIcon className={classes.icon} /> New collection
+                    </MenuItem>
+                    <MenuItem data-cy='side-panel-run-process' className={classes.menuItem} onClick={this.handleRunProcessClick}>
+                        <ProcessIcon className={classes.icon} /> Run a process
+                    </MenuItem>
+                    <MenuItem data-cy='side-panel-new-project' className={classes.menuItem} onClick={this.handleNewProjectClick}>
+                        <ProjectIcon className={classes.icon} /> New project
+                    </MenuItem>
+                </>;
+
+                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 <Toolbar>
                     <Grid container>
                         <Grid container item xs alignItems="center" justify="flex-start">
@@ -109,15 +137,7 @@ export const SidePanelButton = withStyles(styles)(
                                 onClose={this.handleClose}
                                 onClick={this.handleClose}
                                 transformOrigin={transformOrigin}>
-                                <MenuItem data-cy='side-panel-new-collection' className={classes.menuItem} onClick={this.handleNewCollectionClick}>
-                                    <CollectionIcon className={classes.icon} /> New collection
-                                </MenuItem>
-                                <MenuItem data-cy='side-panel-run-process' className={classes.menuItem} onClick={this.handleRunProcessClick}>
-                                    <ProcessIcon className={classes.icon} /> Run a process
-                                </MenuItem>
-                                <MenuItem data-cy='side-panel-new-project' className={classes.menuItem} onClick={this.handleNewProjectClick}>
-                                    <ProjectIcon className={classes.icon} /> New project
-                                </MenuItem>
+                                {menuItems}
                             </Menu>
                         </Grid>
                     </Grid>
@@ -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 f3872615..cd3eaf0b 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)));
 

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list