15768: project copy-to-clipboard works Arvados-DCO-1.1-Signed-off-by: Lisa Knox ...
[arvados-workbench2.git] / src / views-components / context-menu / context-menu.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { connect } from 'react-redux';
6 import { RootState } from 'store/store';
7 import { contextMenuActions, ContextMenuResource } from 'store/context-menu/context-menu-actions';
8 import { ContextMenu as ContextMenuComponent, ContextMenuProps, ContextMenuItem } from 'components/context-menu/context-menu';
9 import { createAnchorAt } from 'components/popover/helpers';
10 import { ContextMenuActionSet, ContextMenuAction } from './context-menu-action-set';
11 import { Dispatch } from 'redux';
12 import { memoize } from 'lodash';
13 import { sortByProperty } from 'common/array-utils';
14 type DataProps = Pick<ContextMenuProps, 'anchorEl' | 'items' | 'open'> & { resource?: ContextMenuResource };
15 const mapStateToProps = (state: RootState): DataProps => {
16     const { open, position, resource } = state.contextMenu;
17
18     const filteredItems = getMenuActionSet(resource).map((group) =>
19         group.filter((item) => {
20             if (resource && item.filters) {
21                 // Execute all filters on this item, every returns true IFF all filters return true
22                 return item.filters.every((filter) => filter(state, resource));
23             } else {
24                 return true;
25             }
26         })
27     );
28
29     return {
30         anchorEl: resource ? createAnchorAt(position) : undefined,
31         items: filteredItems,
32         open,
33         resource,
34     };
35 };
36
37 type ActionProps = Pick<ContextMenuProps, 'onClose'> & { onItemClick: (item: ContextMenuItem, resource?: ContextMenuResource) => void };
38 const mapDispatchToProps = (dispatch: Dispatch): ActionProps => ({
39     onClose: () => {
40         dispatch(contextMenuActions.CLOSE_CONTEXT_MENU());
41     },
42     onItemClick: (action: ContextMenuAction, resource?: ContextMenuResource) => {
43         dispatch(contextMenuActions.CLOSE_CONTEXT_MENU());
44         if (resource) {
45             action.execute(dispatch, [resource]);
46         }
47     },
48 });
49
50 const handleItemClick = memoize(
51     (resource: DataProps['resource'], onItemClick: ActionProps['onItemClick']): ContextMenuProps['onItemClick'] =>
52         (item) => {
53             onItemClick(item, resource);
54         }
55 );
56
57 const mergeProps = ({ resource, ...dataProps }: DataProps, actionProps: ActionProps): ContextMenuProps => ({
58     ...dataProps,
59     ...actionProps,
60     onItemClick: handleItemClick(resource, actionProps.onItemClick),
61 });
62
63 export const ContextMenu = connect(mapStateToProps, mapDispatchToProps, mergeProps)(ContextMenuComponent);
64
65 const menuActionSets = new Map<string, ContextMenuActionSet>();
66
67 export const addMenuActionSet = (name: string, itemSet: ContextMenuActionSet) => {
68     const sorted = itemSet.map((items) => items.sort(sortByProperty('name')));
69     menuActionSets.set(name, sorted);
70 };
71
72 const emptyActionSet: ContextMenuActionSet = [];
73 const getMenuActionSet = (resource?: ContextMenuResource): ContextMenuActionSet =>
74     resource ? menuActionSets.get(resource.menuKind) || emptyActionSet : emptyActionSet;
75
76 export enum ContextMenuKind {
77     API_CLIENT_AUTHORIZATION = 'ApiClientAuthorization',
78     ROOT_PROJECT = 'RootProject',
79     PROJECT = 'Project',
80     FILTER_GROUP = 'FilterGroup',
81     READONLY_PROJECT = 'ReadOnlyProject',
82     FROZEN_PROJECT = 'FrozenProject',
83     FROZEN_PROJECT_ADMIN = 'FrozenProjectAdmin',
84     PROJECT_ADMIN = 'ProjectAdmin',
85     FILTER_GROUP_ADMIN = 'FilterGroupAdmin',
86     RESOURCE = 'Resource',
87     FAVORITE = 'Favorite',
88     TRASH = 'Trash',
89     COLLECTION_FILES = 'CollectionFiles',
90     READONLY_COLLECTION_FILES = 'ReadOnlyCollectionFiles',
91     COLLECTION_FILE_ITEM = 'CollectionFileItem',
92     COLLECTION_DIRECTORY_ITEM = 'CollectionDirectoryItem',
93     READONLY_COLLECTION_FILE_ITEM = 'ReadOnlyCollectionFileItem',
94     READONLY_COLLECTION_DIRECTORY_ITEM = 'ReadOnlyCollectionDirectoryItem',
95     COLLECTION_FILES_NOT_SELECTED = 'CollectionFilesNotSelected',
96     COLLECTION = 'Collection',
97     COLLECTION_ADMIN = 'CollectionAdmin',
98     READONLY_COLLECTION = 'ReadOnlyCollection',
99     OLD_VERSION_COLLECTION = 'OldVersionCollection',
100     TRASHED_COLLECTION = 'TrashedCollection',
101     PROCESS = 'Process',
102     PROCESS_ADMIN = 'ProcessAdmin',
103     PROCESS_RESOURCE = 'ProcessResource',
104     READONLY_PROCESS_RESOURCE = 'ReadOnlyProcessResource',
105     PROCESS_LOGS = 'ProcessLogs',
106     REPOSITORY = 'Repository',
107     SSH_KEY = 'SshKey',
108     VIRTUAL_MACHINE = 'VirtualMachine',
109     KEEP_SERVICE = 'KeepService',
110     USER = 'User',
111     GROUPS = 'Group',
112     GROUP_MEMBER = 'GroupMember',
113     PERMISSION_EDIT = 'PermissionEdit',
114     LINK = 'Link',
115     WORKFLOW = 'Workflow',
116     SEARCH_RESULTS = 'SearchResults',
117 }