14990: added 404 page with wildcard route
[arvados-workbench2.git] / 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 * as 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, InputIcon, IconType, CollectionIcon } from '~/components/icon/icon';
14 import { loadProject, loadCollection } from '~/store/tree-picker/tree-picker-actions';
15 import { GroupContentsResource } from '~/services/groups-service/groups-service';
16 import { CollectionDirectory, CollectionFile, CollectionFileType } from '~/models/collection-file';
17 import { ResourceKind } from '~/models/resource';
18 import { TreePickerProps, TreePicker } from "~/views-components/tree-picker/tree-picker";
19 import { LinkResource } from "~/models/link";
20
21 export interface ProjectsTreePickerRootItem {
22     id: string;
23     name: string;
24 }
25
26 export type ProjectsTreePickerItem = ProjectsTreePickerRootItem | GroupContentsResource | CollectionDirectory | CollectionFile | LinkResource;
27 type PickedTreePickerProps = Pick<TreePickerProps<ProjectsTreePickerItem>, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>;
28
29 export interface ProjectsTreePickerDataProps {
30     includeCollections?: boolean;
31     includeFiles?: boolean;
32     rootItemIcon: IconType;
33     showSelection?: boolean;
34     relatedTreePickers?: string[];
35     disableActivation?: string[];
36     loadRootItem: (item: TreeItem<ProjectsTreePickerRootItem>, pickerId: string, includeCollections?: boolean, includeFiles?: boolean) => void;
37 }
38
39 export type ProjectsTreePickerProps = ProjectsTreePickerDataProps & Partial<PickedTreePickerProps>;
40
41 const mapStateToProps = (_: any, { rootItemIcon, showSelection }: ProjectsTreePickerProps) => ({
42     render: renderTreeItem(rootItemIcon),
43     showSelection: isSelectionVisible(showSelection),
44 });
45
46 const mapDispatchToProps = (dispatch: Dispatch, { loadRootItem, includeCollections, includeFiles, relatedTreePickers, ...props }: ProjectsTreePickerProps): PickedTreePickerProps => ({
47     onContextMenu: () => { return; },
48     toggleItemActive: (event, item, pickerId) => {
49
50         const { disableActivation = [] } = props;
51         if (disableActivation.some(isEqual(item.id))) {
52             return;
53         }
54
55         dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: item.id, pickerId, relatedTreePickers }));
56         if (props.toggleItemActive) {
57             props.toggleItemActive(event, item, pickerId);
58         }
59     },
60     toggleItemOpen: (_, item, pickerId) => {
61         const { id, data, status } = item;
62         if (status === TreeItemStatus.INITIAL) {
63             if ('kind' in data) {
64                 dispatch<any>(
65                     data.kind === ResourceKind.COLLECTION
66                         ? loadCollection(id, pickerId)
67                         : loadProject({ id, pickerId, includeCollections, includeFiles })
68                 );
69             } else if (!('type' in data) && loadRootItem) {
70                 loadRootItem(item as TreeItem<ProjectsTreePickerRootItem>, pickerId, includeCollections, includeFiles);
71             }
72         } else if (status === TreeItemStatus.LOADED) {
73             dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
74         }
75     },
76     toggleItemSelection: (event, item, pickerId) => {
77         dispatch<any>(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id: item.id, pickerId }));
78         if (props.toggleItemSelection) {
79             props.toggleItemSelection(event, item, pickerId);
80         }
81     },
82 });
83
84 export const ProjectsTreePicker = connect(mapStateToProps, mapDispatchToProps)(TreePicker);
85
86 const getProjectPickerIcon = ({ data }: TreeItem<ProjectsTreePickerItem>, rootIcon: IconType): IconType => {
87     if ('headKind' in data) {
88         switch (data.headKind) {
89             case ResourceKind.COLLECTION:
90                 return CollectionIcon;
91             default:
92                 return ProjectIcon;
93         }
94     }
95     if ('kind' in data) {
96         switch (data.kind) {
97             case ResourceKind.COLLECTION:
98                 return CollectionIcon;
99             default:
100                 return ProjectIcon;
101         }
102     } else if ('type' in data) {
103         switch (data.type) {
104             case CollectionFileType.FILE:
105                 return InputIcon;
106             default:
107                 return ProjectIcon;
108         }
109     } else {
110         return rootIcon;
111     }
112 };
113
114 const isSelectionVisible = (shouldBeVisible?: boolean) =>
115     ({ status, items }: TreeItem<ProjectsTreePickerItem>): boolean => {
116         if (shouldBeVisible) {
117             if (items && items.length > 0) {
118                 return items.every(isSelectionVisible(shouldBeVisible));
119             }
120             return status === TreeItemStatus.LOADED;
121         }
122         return false;
123     };
124
125 const renderTreeItem = (rootItemIcon: IconType) => (item: TreeItem<ProjectResource>) =>
126     <ListItemTextIcon
127         icon={getProjectPickerIcon(item, rootItemIcon)}
128         name={item.data.name}
129         isActive={item.active}
130         hasMargin={true} />;