Skip to content

Commit

Permalink
elyra-ai#1865 Add dividers for the context toolbar (elyra-ai#1866)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomlyn authored Apr 12, 2024
1 parent a36457b commit 643f5ab
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import ColorPicker from "../color-picker";
const CM_TOOLBAR_GAP = 2;
const CM_ICON_SIZE = 32;
const CM_ICON_PAD = 2;
const DIVIDER_SIZE = 1;
const ICON_SIZE_PLUS_GAP = CM_ICON_SIZE + CM_TOOLBAR_GAP;
const PADDING = 2;

Expand Down Expand Up @@ -104,17 +105,22 @@ class CommonCanvasContextToolbar extends React.Component {

// Returns the width of the context toolbar.
getContextToolbarWidth(toolbarItems, overflowMenuItems) {
const dividers = toolbarItems.filter((i) => i.divider);
const dividersCount = dividers.length;
const dividersWidth = dividersCount * DIVIDER_SIZE;

// If there is at least one overflow item, we will need an overflow
// icon which will increase the toolbar items by one.
const overflowItemCount = overflowMenuItems.length > 0 ? 1 : 0;
const toolbarItemsCount = toolbarItems.length + overflowItemCount;
const buttonsCount = toolbarItems.length + overflowItemCount - dividersCount;
const buttonsWidth = (buttonsCount * (CM_ICON_SIZE + CM_ICON_PAD));

// If we have some overflow menu items, we reduce the width by five pixels
// which forces the overflow menu and the overflow icon to be shown. We
// use 5 pixels because this is how many are needed to make the toolbar
// work correcty with differnet browser magnificaitons.
const reduction = overflowMenuItems.length > 0 ? 5 : 0;
return (toolbarItemsCount * (CM_ICON_SIZE + CM_ICON_PAD)) - reduction;
return buttonsWidth + dividersWidth - reduction;
}

// Removes leading and trailing dividers from the items array and any
Expand Down Expand Up @@ -210,7 +216,7 @@ class CommonCanvasContextToolbar extends React.Component {
let contextToolbar = null;

if (this.props.showContextMenu) {
const toolbarItems = this.props.contextMenuDef.filter((cmItem) => cmItem.toolbarItem && !cmItem.divider);
const toolbarItems = this.props.contextMenuDef.filter((cmItem) => cmItem.toolbarItem);
let overflowMenuItems = this.props.contextMenuDef.filter((cmItem) => !cmItem.toolbarItem);
overflowMenuItems = this.removeUnnecessaryDividers(overflowMenuItems);
const toolbarConfig = this.getToolbarConfig({ toolbarItems, overflowMenuItems });
Expand All @@ -224,6 +230,7 @@ class CommonCanvasContextToolbar extends React.Component {
: pos.x - toolbarWidth;
let y = pos.y - ICON_SIZE_PLUS_GAP;

// Make sure the context toolbar is fully inside the viewport.
({ x, y } = this.adjustPosToFit(x, y, toolbarWidth, ICON_SIZE_PLUS_GAP));

contextToolbar = (
Expand Down
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ nav:
- External Pipeline Flows: 03.08-external-pipeline-flows.md
- Read Only or Locked Flows: 03.09-read-only-or-locked-flows.md
- Commmand Stack: 03.10-command-stack.md
- Internal Actions: 03.11-internal-actions.md
- Deprecated:
- Context Menu Wrapper: 03.30.01-context-menu-wrapper.md
- Flow Validation: 03.30.02-flow-validation.md
Expand Down
209 changes: 105 additions & 104 deletions docs/pages/03.03.01-context-menu-handler.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
# Context Menu Handler

This callback is optional. You don't need to implement anything for it. If implemented it must return an array of actions that describe what options are displayed on the context menu or context toolbar.
This callback is optional. You don't need to implement anything for it. If implemented it must return an array of actions that describe what options are displayed on the [context menu](01.03-context-menu.md) or [context toolbar](01.04-context-toolbar.md).

## contextMenuHandler
```js
contextMenuHandler(source, defaultMenu)
```
This callback is used for both 'context menus' and, if the [`enableContextToolbar`](03.02.01-canvas-config.md#enablecontexttoolbar) canvas config option is set to `true`, for 'context toolbars'.

If this callback is not provided common canvas will handle context menu/toolbars, and their actions, internally. You only need to implement this callback if you want to add or remove options to/from the context menu/toolbar.
If this callback is not provided common canvas will handle context menu/toolbars, and their actions, internally. You only need to implement this callback if you want to add or remove options to/from the context menu/toolbar or provide your own menus in place of the default ones.

### For Context Menu
## For Context Menu

For context menus this will be dependent on what object or set of objects the context menu was requested for by the user.
For context menus, this will be dependent on what object or set of objects the context menu was requested for by the user.

This callback will be called if the user performs a context menu gesture (such as mouse 'right click') on a:
This callback will be called if the user performs a context menu gesture (such as mouse 'right click' or clicking an ellipsis icon) on a:

* node
* link
* comment
* port
* on the canvas background or
* if a number of objects are selected, the combination of objects.
* Node
* Link
* Comment
* Port
* On the canvas background or
* Combination of objects - if a number of objects are selected

This callback must return an array that defines the menu to be displayed. If the callback is *not* provided, a default context menu (the defaultMenu passed into the handler) will be displayed by the common canvas.
#### 'source' parameter

The source object passed in looks like this:
```js
Expand All @@ -34,116 +34,117 @@ The source object passed in looks like this:
mousePos: {x: "10", y:"20"}
}
```
**type** - Indicates type of object for which the context menu was selected. Can be "node", "port", "link", "canvas" or "comment"
**type** - Indicates the type of object for which the context menu was selected. Can be "node", "input_port", "output_port", "link", "canvas" or "comment"

**targetObject** - The object for which the context menu was requested. Only provided when type is "node" or "comment"
**targetObject** - The object for which the context menu was requested. Not provided when type is "canvas".

**selectedObjectIds** - An array containing the IDs of all currently selected nodes and/or comments
**selectedObjectIds** - An array containing the IDs of all currently selected nodes and/or comments and/or links.

**mousePos** - An object containing the coords of the mouse when the context menu was requested

The callback would need to return an array, that describes the context menu to be displayed, that looks something like this:
```js
[
{action: "deleteSelectedObjects", label: "Delete"},
{divider: true},
{action: "myAction", label: "My Action"}
{action: "myUnavailableAction", label: "A test disabled item", enable: false}
]
```
There is one element in the array for each entry in the context menu. An entry can be either a context menu item, which consists of a label and an action, or a divider, whose field would need to be set to true.

Actions can be either internal (implemented inside the common canvas, like "deleteSelectedObjects" above) or external (like "myAction").

Existing internal actions are:

* selectAll
* cut
* copy
* paste
* undo
* redo
* createSupernode
* expandSupernode
* collapseSupernode
* deleteSelectedObjects
* createComment
* deleteLink
* disconnectNode
* highlightBranch
* highlightDownstream
* highlightUpstream
* unhighlight

External actions are custom actions you want common canvas to display for your application. To get common canvas to display your action you would need to return an array from the callback that includes a menu item for the action.

When the user clicks the option in the context menu matching your action common canvas will call the [Edit Action Handler](03.03.03-edit-action-handler.md) callback so you'll need to implement some code in that callback to execute the intended action. If you want to simply add your action to the default context menu provided by common canvas you can take the defaultMenu parameter provided to the callback and add your menu item to it. Alternatively, you can provide a complete new context menu of your own.
#### 'defaultMenu' parameter

Here is a sample implementation of contextMenuHandler, which takes a source object (described above) and the defaultMenu as parameters, and adds a custom action to the default menu when the user 'right clicks' the canvas background.
This is an array describing the default menu that common-canvas would usually display. If necessary, you can modify this array with your own elements or remove elements and then return the modified array.

### Return

The callback must return an array, that describes the context menu to be displayed. If the callback returns a null, then no menu will be displayed.

There is one element in the array for each entry in the context menu. An entry can be either a context menu item, which consists of a label and an action, or a divider, whose field would need to be set to true. An action can be disabled by setting the 'enable' field to false.

Here's an example of a contextMenuHandler:

```js
contextMenuHandler(source, defaultMenu) {
let customMenu = defaultMenu;
if (source.type === "canvas") {
customMenu = customMenu.concat({ action: "myAction", label: "My Action" });
}
return customMenu;
if (source.type === "node") {
return [
{ action: "deleteSelectedObjects", label: "Delete" },
{ divider: true},
{ action: "myAction", label: "My Action" },
{ action: "paste", label: "Paste from clipboard", enable: false }
];
}
return defaultMenu;
}
```
In addition to adding the context menu item, you would also need to implement the [Edit Action Handler](03.03.03-edit-action-handler.md) callback to execute the action, like this:

The above array will produce a context menu like this:

<img src="../assets/context-menu.png" width="300" />

When the user clicks an action in the menu the action is executed either internally or externally.

### Internal acitons

Internal actions are implemented inside the common canvas, like "deleteSelectedObjects" in the example above. Common-canvas supports a large number of [internal actions](03.11-internal-actions.md).

### External actions

External actions are custom actions you want common canvas to display for your application like "myAction", in the example above.
Tip: To avoid any future name clashes with internal actions that might be added it is recommended you should make sure you action names are unique. For example, by adding a prefix to your application specfic actions.

### Handling actions

When the user clicks an option in the context menu (or context toolbar) it causes the [Before Edit Action Handler](03.03.02-before-edit-action-handler.md) and then the [Edit Action Handler](03.03.03-edit-action-handler.md) callbacks to be called.


### Customizing the default context menu

If you want to simply add your action to the default context menu provided by common canvas you can take the defaultMenu parameter provided to the callback, and add your menu item to it. Alternatively, you can provide a complete new context menu of your own.

Here is a sample implementation of contextMenuHandler, which takes a source object (described above) and the defaultMenu as parameters, and adds a custom action to the default menu when the user 'right clicks' the canvas background.

```js
editActionHandler(data) {
if (data.editType === "myAction") {
// Execute my action code here.
}
contextMenuHandler(source, defaultMenu) {
let customMenu = defaultMenu;
if (source.type === "canvas") {
customMenu = customMenu.concat({ action: "myAction", label: "My Action" });
}
return customMenu;
}
```
Tip: To avoid any future name clashes with internal actions you should make sure you action names are unique. For example, you could add a prefix to your action names eg. `$MyApp_action` where `$MayApp_` is the prefix for all your actions.

### For Context Toolbar
## For Context Toolbar

To display a context toolbar the same type of array is returned as described above for context menu. However, there are some extra fields for the action elements in the array. These are

* isToolbarItem: This is a boolean. The default is false. If set to true the action will be added to the toolbar and if set to false the action will be displayed in the overflow menu.
* icon: This is the icon to display for the action. If `isToolbarItem` is set to true you must provide an icon otherwise the action will show as an empty space in the toolbar. If an icon is specified and `isToolbarItem` is set to false, teh icon will be displayed next to the action in the overflow menu.


The contents of the context toolbar is dependent on which object the mouse cursor is currently hovering over (which may be different to any of the currently selected objects). You should make sure the actions you return in the array are applicable to the object the mouse cursor is hovering over or, if it is hovering over a selected object and other objects are alos selected, to the set of selected objects.

## Action names with built in icons

If you use any of the following action names common-canvas will automatically display an appropriate Carbon icon for that action either if it appears as a button in the toolbar or if it appears in the overflow menu.

| Action | Carbon Icon |
|--------------------------|---------------------|
| stop | StopFilledAlt |
| run | Play |
| undo | Undo |
| redo | Redo |
| cut | Cut |
| copy | Copy |
| paste | Paste |
| clipboard | Result |
| createComment | AddComment |
| createAutoComment | AddComment |
| setCommentEditingMode | Edit |
| setNodeLabelEditingMode | Edit |
| commentsShow | Chat |
| commentsHide | ChatOff |
| colorBackground | ColorPalette |
| deleteLink | TrashCan |
| deleteSelectedObjects | TrashCan |
| zoomIn | ZoomIn |
| zoomOut | ZoomOut |
| zoomToFit | CenterToFit |
| arrangeHorizontally | ArrangeHorizont |
| arrangeVertically | ArrangeVertical |
| toggleNotificationPanel | NotificationCounter |
| paletteOpen | OpenPanelFilledLeft |
| paletteClose | OpenPanelFilledLeft |
| paletteToggle | OpenPanelFilledLeft |
| expandSuperNodeInPlace | Maximize |
| collapseSuperNodeInPlace | Minimize |
| displaySubPipeline | Launch |
* icon: This is the icon to display for the action. If `isToolbarItem` is set to true you must provide an icon otherwise the action will show as an empty space in the toolbar. If an icon is specified and `isToolbarItem` is set to false, the icon will be displayed next to the action in the overflow menu. For many internal actions, common canvas will automatically display an appropriate carbon icon. See the [Internal Actions page](03.11-internal-actions.md/#action-names-with-built-in-icons) for a list of actions that have associated icons.

Dividers can also be added to the context toolbar by specifying 'toolbarItem: true'

```js
import { Add } from "@carbon/react/icons";

...

contextMenuHandler(source, defaultMenu) {
if (source.type === "node") {
return [
{ action: "deleteSelectedObjects", label: "Delete", toolbarItem: true },
{ divider: true, toolbarItem: true },
{ action: "myAction1", label: "My Action1", toolbarItem: true, icon: (<Add />) },
{ action: "disconnectNode", label: "Disconnect", enable: false },
{ action: "cut", label: "Cut" },
{ action: "copy", label: "Copy" }
];
}
return defaultMenu;
}
```
This will produce a context toolbar like this:

<img src="../assets/context-toolbar.png" width="400" />

And when the overflow icon is clicked, like this:

<img src="../assets/context-toolbar-open-overflow.png" width="400" />


#### Warning
The contents of the context toolbar is dependent on which object the mouse cursor is currently **hovering** over (which may be different to any of the **currently selected** objects). You should make sure the actions you return in the array are applicable to the object the mouse cursor is hovering over or, if it is hovering over a selected object and other objects are alos selected, to the set of selected objects.

To help decide whether the mouse cursor is hovering over a selected object or not, the application can call the canvas controller's helper function: 'isContextToolbarForNonSelectedObj(source)'. This will return true if the mouse cursor is over a non-selected object.



34 changes: 23 additions & 11 deletions docs/pages/03.03.03-edit-action-handler.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
# Edit Action Handler

This callback is optional. You don't *need* to implement anything for it and it doesn't return anything. It is called whenever the user does the following types of action on the canvas:
This callback is optional. You don't *need* to implement anything for it and it doesn't return anything. It is called whenever the user does the following gestures on the canvas:

* Clicks a tool/icon in the toolbar.
* Clicks an option in the context menu or context toolbar
* Presses a [key combination](03.05-keyboard-support.md) on the keyboard to cause the canvas to change.
* Performs some direct manipulation on the canvas such as:
* Creates a node (createNode)
* Moves one or a set of nodes/comments (moveObjects)
* Edits a comment (editComment)
* Links two nodes together (linkNodes)
* Links a comment to a node (linkComment)
* Resizes a supernode (resizeObjects)
* Resizes a comment (editComment)
* Expands a supernode in place (expandSuperNodeInPlace)
* Navigates into a sub-flow in a supernode (displaySubPipeline)
* Navigates out of a sub-flow in a supernode (displayPreviousPipeline)
* Create a node
* Moves one or a set of nodes/comments
* Edits a comment
* Links two nodes together
* etc

These is will either perform one of the many [internal actions](03.11-internal-actions.md) supported by common canvas or, if you have customized the context menu/toolbar or the maon canvas toolbar, your own external action.

## editActionHandler
```js
Expand Down Expand Up @@ -48,3 +45,18 @@ This callback is called *after* the common-canvas internal object model has been

2. **command parameter** - This is a Javascript class which is the command object that was added to the command stack and executed to run the action 'requested' by the user. If the user performed an `undo` action this will be the command that has been undone. If the user performed a `redo` action this will be the command that was redone. The command object may contain fields which are connected with the execution of the command.

### Handling external action

If you specified your application's own 'external' action you can do whatever is necessary in this callback.

The `editType` field of the first parameter, passed in to the callback, will be set to the action name.

Here's a simple expmple:

```js
editActionHandler(data, command) {
if (data.editType === "myAction") {
// Execute my action code here.
}
}
```
Loading

0 comments on commit 643f5ab

Please sign in to comment.