18874: Add 'services/workbench2/' from commit 'f6f88d9ca9cdeeeebfadcfe999789bfb9f69e5c6'
[arvados.git] / services / workbench2 / src / views-components / projects-tree-picker / generic-projects-tree-picker.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from "react";
6 import { Dispatch } from "redux";
7 import { connect } from "react-redux";
8 import { isEqual } from 'lodash/fp';
9 import { TreeItem, TreeItemStatus } from 'components/tree/tree';
10 import { ProjectResource } from "models/project";
11 import { treePickerActions } from "store/tree-picker/tree-picker-actions";
12 import { ListItemTextIcon } from "components/list-item-text-icon/list-item-text-icon";
13 import { ProjectIcon, FileInputIcon, IconType, CollectionIcon } from 'components/icon/icon';
14 import { loadProject, loadCollection } from 'store/tree-picker/tree-picker-actions';
15 import { ProjectsTreePickerItem, ProjectsTreePickerRootItem } from 'store/tree-picker/tree-picker-middleware';
16 import { ResourceKind } from 'models/resource';
17 import { TreePickerProps, TreePicker } from "views-components/tree-picker/tree-picker";
18 import { CollectionFileType } from 'models/collection-file';
19
20
21 type PickedTreePickerProps = Pick<TreePickerProps<ProjectsTreePickerItem>, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>;
22
23 export interface ProjectsTreePickerDataProps {
24     includeCollections?: boolean;
25     includeDirectories?: boolean;
26     includeFiles?: boolean;
27     rootItemIcon: IconType;
28     showSelection?: boolean;
29     relatedTreePickers?: string[];
30     disableActivation?: string[];
31     options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
32     loadRootItem: (item: TreeItem<ProjectsTreePickerRootItem>, pickerId: string,
33         includeCollections?: boolean, includeDirectories?: boolean, includeFiles?: boolean, options?: { showOnlyOwned: boolean, showOnlyWritable: boolean }) => void;
34 }
35
36 export type ProjectsTreePickerProps = ProjectsTreePickerDataProps & Partial<PickedTreePickerProps>;
37
38 const mapStateToProps = (_: any, { rootItemIcon, showSelection }: ProjectsTreePickerProps) => ({
39     render: renderTreeItem(rootItemIcon),
40     showSelection: isSelectionVisible(showSelection),
41 });
42
43 const mapDispatchToProps = (dispatch: Dispatch, { loadRootItem, includeCollections, includeDirectories, includeFiles, relatedTreePickers, options, ...props }: ProjectsTreePickerProps): PickedTreePickerProps => ({
44     onContextMenu: () => { return; },
45     toggleItemActive: (event, item, pickerId) => {
46
47         const { disableActivation = [] } = props;
48         if (disableActivation.some(isEqual(item.id))) {
49             return;
50         }
51
52         dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: item.id, pickerId, relatedTreePickers }));
53         if (props.toggleItemActive) {
54             props.toggleItemActive(event, item, pickerId);
55         }
56     },
57     toggleItemOpen: (_, item, pickerId) => {
58         const { id, data, status } = item;
59         if (status === TreeItemStatus.INITIAL) {
60             if ('kind' in data) {
61                 dispatch<any>(
62                     data.kind === ResourceKind.COLLECTION
63                         ? loadCollection(id, pickerId, includeDirectories, includeFiles)
64                         : loadProject({ id, pickerId, includeCollections, includeDirectories, includeFiles, options })
65                 );
66             } else if (!('type' in data) && loadRootItem) {
67                 loadRootItem(item as TreeItem<ProjectsTreePickerRootItem>, pickerId, includeCollections, includeDirectories, includeFiles, options);
68             }
69         } else if (status === TreeItemStatus.LOADED) {
70             dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
71         }
72     },
73     toggleItemSelection: (event, item, pickerId) => {
74         dispatch<any>(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id: item.id, pickerId }));
75         if (props.toggleItemSelection) {
76             props.toggleItemSelection(event, item, pickerId);
77         }
78     },
79 });
80
81 export const ProjectsTreePicker = connect(mapStateToProps, mapDispatchToProps)(TreePicker);
82
83 const getProjectPickerIcon = ({ data }: TreeItem<ProjectsTreePickerItem>, rootIcon: IconType): IconType => {
84     if ('headKind' in data) {
85         switch (data.headKind) {
86             case ResourceKind.COLLECTION:
87                 return CollectionIcon;
88             default:
89                 return ProjectIcon;
90         }
91     }
92     if ('kind' in data) {
93         switch (data.kind) {
94             case ResourceKind.COLLECTION:
95                 return CollectionIcon;
96             default:
97                 return ProjectIcon;
98         }
99     } else if ('type' in data) {
100         switch (data.type) {
101             case CollectionFileType.FILE:
102                 return FileInputIcon;
103             default:
104                 return ProjectIcon;
105         }
106     } else {
107         return rootIcon;
108     }
109 };
110
111 const isSelectionVisible = (shouldBeVisible?: boolean) =>
112     ({ status, items }: TreeItem<ProjectsTreePickerItem>): boolean => {
113         if (shouldBeVisible) {
114             if (items && items.length > 0) {
115                 return items.every(isSelectionVisible(shouldBeVisible));
116             }
117             return status === TreeItemStatus.LOADED;
118         }
119         return false;
120     };
121
122 const renderTreeItem = (rootItemIcon: IconType) => (item: TreeItem<ProjectResource>) =>
123     <ListItemTextIcon
124         icon={getProjectPickerIcon(item, rootItemIcon)}
125         name={item.data.name}
126         isActive={item.active}
127         hasMargin={true} />;