From: Michal Klobukowski Date: Tue, 9 Oct 2018 14:37:18 +0000 (+0200) Subject: Create basic cumulative projects picker X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/4af7aea9d89d1775a79288cb9433e0ec9e1176e4?hp=22b21c3a28e0ef59e10168fab5aa633cc25cbab4 Create basic cumulative projects picker Feature #13862 Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski --- diff --git a/src/index.tsx b/src/index.tsx index 16cf86a368..c1238a545f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -43,7 +43,7 @@ import { trashedCollectionActionSet } from '~/views-components/context-menu/acti import { ContainerRequestState } from '~/models/container-request'; import { MountKind } from '~/models/mount-types'; import { receiveTreePickerData, loadUserProject } from '~/store/tree-picker/tree-picker-actions'; -import { loadProject, loadCollection, initUserProject, initSharedProject, initFavoritesProject } from './store/tree-picker/tree-picker-actions'; +import { loadProject, loadCollection, initUserProject, initSharedProject, initFavoritesProject, initProjectsTreePicker } from './store/tree-picker/tree-picker-actions'; import { ResourceKind } from '~/models/resource'; const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev"); @@ -117,15 +117,10 @@ const initListener = (history: History, store: RootStore, services: ServiceRepos await store.dispatch(loadWorkbench()); addRouteChangeHandlers(history, store); // createEnumCollectorWorkflow(services); - store.dispatch(initUserProject('testPicker1')); - store.dispatch(initUserProject('testPicker2')); - store.dispatch(initUserProject('testPicker3')); - store.dispatch(initSharedProject('testPicker4')); - store.dispatch(initSharedProject('testPicker5')); - store.dispatch(initSharedProject('testPicker6')); - store.dispatch(initFavoritesProject('testPicker7')); - store.dispatch(initFavoritesProject('testPicker8')); - store.dispatch(initFavoritesProject('testPicker9')); + store.dispatch(initProjectsTreePicker('testPicker1')); + store.dispatch(initProjectsTreePicker('testPicker2')); + store.dispatch(initProjectsTreePicker('testPicker3')); + // await store.dispatch(loadCollection( // 'c97qk-4zz18-9sn8ygaf62chkkd', // 'testPicker', diff --git a/src/models/tree.ts b/src/models/tree.ts index 69d2e93769..cce27b125a 100644 --- a/src/models/tree.ts +++ b/src/models/tree.ts @@ -109,6 +109,8 @@ export const mapIdsToNodes = (ids: string[]) => (tree: Tree) => export const activateNode = (id: string) => (tree: Tree) => mapTree(node => node.id === id ? { ...node, active: true } : { ...node, active: false })(tree); +export const deactivateNode = (tree: Tree) => + mapTree(node => node.active ? { ...node, active: false } : node)(tree); export const expandNode = (...ids: string[]) => (tree: Tree) => mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: true } : node)(tree); diff --git a/src/store/tree-picker/tree-picker-actions.ts b/src/store/tree-picker/tree-picker-actions.ts index 41ee62f8e1..06f73c3407 100644 --- a/src/store/tree-picker/tree-picker-actions.ts +++ b/src/store/tree-picker/tree-picker-actions.ts @@ -18,6 +18,7 @@ export const treePickerActions = unionize({ LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ id: string, nodes: Array>, pickerId: string }>(), TOGGLE_TREE_PICKER_NODE_COLLAPSE: ofType<{ id: string, pickerId: string }>(), ACTIVATE_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(), + DEACTIVATE_TREE_PICKER_NODE: ofType<{ pickerId: string }>(), TOGGLE_TREE_PICKER_NODE_SELECTION: ofType<{ id: string, pickerId: string }>(), EXPAND_TREE_PICKER_NODES: ofType<{ ids: string[], pickerId: string }>(), RESET_TREE_PICKER: ofType<{ pickerId: string }>() @@ -25,6 +26,19 @@ export const treePickerActions = unionize({ export type TreePickerAction = UnionOf; +export const getProjectsTreePickerIds = (pickerId: string) => ({ + home: `${pickerId}_home`, + shared: `${pickerId}_shared`, + favorites: `${pickerId}_favorites`, +}); +export const initProjectsTreePicker = (pickerId: string) => +async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => { + const {home, shared, favorites} = getProjectsTreePickerIds(pickerId); + dispatch(initUserProject(home)); + dispatch(initSharedProject(shared)); + dispatch(initFavoritesProject(favorites)); +}; + interface ReceiveTreePickerDataParams { data: T[]; extractNodeData: (value: T) => { id: string, value: T, status?: TreeNodeStatus }; diff --git a/src/store/tree-picker/tree-picker-reducer.ts b/src/store/tree-picker/tree-picker-reducer.ts index 69c49052b5..2df567efee 100644 --- a/src/store/tree-picker/tree-picker-reducer.ts +++ b/src/store/tree-picker/tree-picker-reducer.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { createTree, TreeNode, setNode, Tree, TreeNodeStatus, setNodeStatus, expandNode } from '~/models/tree'; +import { createTree, TreeNode, setNode, Tree, TreeNodeStatus, setNodeStatus, expandNode, deactivateNode } from '~/models/tree'; import { TreePicker } from "./tree-picker"; import { treePickerActions, TreePickerAction } from "./tree-picker-actions"; import { compose } from "redux"; @@ -18,6 +18,8 @@ export const treePickerReducer = (state: TreePicker = {}, action: TreePickerActi updateOrCreatePicker(state, pickerId, toggleNodeCollapse(id)), ACTIVATE_TREE_PICKER_NODE: ({ id, pickerId }) => updateOrCreatePicker(state, pickerId, activateNode(id)), + DEACTIVATE_TREE_PICKER_NODE: ({ pickerId }) => + updateOrCreatePicker(state, pickerId, deactivateNode), TOGGLE_TREE_PICKER_NODE_SELECTION: ({ id, pickerId }) => updateOrCreatePicker(state, pickerId, toggleNodeSelection(id)), RESET_TREE_PICKER: ({ pickerId }) => diff --git a/src/views-components/projects-tree-picker/favorites-tree-picker.tsx b/src/views-components/projects-tree-picker/favorites-tree-picker.tsx index 74cb16a25a..09704066a1 100644 --- a/src/views-components/projects-tree-picker/favorites-tree-picker.tsx +++ b/src/views-components/projects-tree-picker/favorites-tree-picker.tsx @@ -3,12 +3,12 @@ // SPDX-License-Identifier: AGPL-3.0 import { connect } from 'react-redux'; -import { ProjectsTreePicker, ProjectsTreePickerProps } from '~/views-components/projects-tree-picker/projects-tree-picker'; +import { ProjectsTreePicker, ProjectsTreePickerProps } from '~/views-components/projects-tree-picker/generic-projects-tree-picker'; import { Dispatch } from 'redux'; import { FavoriteIcon } from '~/components/icon/icon'; import { loadFavoritesProject } from '~/store/tree-picker/tree-picker-actions'; -export const FavoritesProjectsTreePicker = connect(() => ({ +export const FavoritesTreePicker = connect(() => ({ rootItemIcon: FavoriteIcon, }), (dispatch: Dispatch): Pick => ({ loadRootItem: (_, pickerId, includeCollections, includeFiles) => { diff --git a/src/views-components/projects-tree-picker/generic-projects-tree-picker.tsx b/src/views-components/projects-tree-picker/generic-projects-tree-picker.tsx new file mode 100644 index 0000000000..1ddbe3ac6c --- /dev/null +++ b/src/views-components/projects-tree-picker/generic-projects-tree-picker.tsx @@ -0,0 +1,98 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from "react"; +import { Dispatch } from "redux"; +import { connect } from "react-redux"; +import { TreeItem, TreeItemStatus } from '~/components/tree/tree'; +import { ProjectResource } from "~/models/project"; +import { treePickerActions } from "~/store/tree-picker/tree-picker-actions"; +import { ListItemTextIcon } from "~/components/list-item-text-icon/list-item-text-icon"; +import { ProjectIcon, InputIcon, IconType, CollectionIcon } from '~/components/icon/icon'; +import { loadProject, loadCollection } from '~/store/tree-picker/tree-picker-actions'; +import { GroupContentsResource } from '~/services/groups-service/groups-service'; +import { CollectionDirectory, CollectionFile, CollectionFileType } from '~/models/collection-file'; +import { ResourceKind } from '~/models/resource'; +import { TreePickerProps, TreePicker } from "~/views-components/tree-picker/tree-picker"; + +export interface ProjectsTreePickerRootItem { + id: string; + name: string; +} + +export type ProjectsTreePickerItem = ProjectsTreePickerRootItem | GroupContentsResource | CollectionDirectory | CollectionFile; +type PickedTreePickerProps = Pick, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>; + +export interface ProjectsTreePickerDataProps { + pickerId: string; + includeCollections?: boolean; + includeFiles?: boolean; + rootItemIcon: IconType; + loadRootItem: (item: TreeItem, pickerId: string, includeCollections?: boolean, inlcudeFiles?: boolean) => void; +} + +export type ProjectsTreePickerProps = ProjectsTreePickerDataProps & Partial; + +const mapStateToProps = (_: any, { pickerId, rootItemIcon }: ProjectsTreePickerProps) => ({ + render: renderTreeItem(rootItemIcon), + pickerId, +}); + +const mapDispatchToProps = (dispatch: Dispatch, { loadRootItem, includeCollections, includeFiles, ...props }: ProjectsTreePickerProps): PickedTreePickerProps => ({ + onContextMenu: () => { return; }, + toggleItemActive: (event, item, pickerId) => { + dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: item.id, pickerId })); + if (props.toggleItemActive) { + props.toggleItemActive(event, item, pickerId); + } + }, + toggleItemOpen: (_, item, pickerId) => { + const { id, data, status } = item; + if (status === TreeItemStatus.INITIAL) { + if ('kind' in data) { + dispatch( + data.kind === ResourceKind.COLLECTION + ? loadCollection(id, pickerId) + : loadProject({ id, pickerId, includeCollections, includeFiles }) + ); + } else if (!('type' in data) && loadRootItem) { + loadRootItem(item as TreeItem, pickerId, includeCollections, includeFiles); + } + } else if (status === TreeItemStatus.LOADED) { + dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId })); + } + }, + toggleItemSelection: (_, { id }, pickerId) => { + dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id, pickerId })); + }, +}); + +export const ProjectsTreePicker = connect(mapStateToProps, mapDispatchToProps)(TreePicker); + +const getProjectPickerIcon = ({ data }: TreeItem, rootIcon: IconType): IconType => { + if ('kind' in data) { + switch (data.kind) { + case ResourceKind.COLLECTION: + return CollectionIcon; + default: + return ProjectIcon; + } + } else if ('type' in data) { + switch (data.type) { + case CollectionFileType.FILE: + return InputIcon; + default: + return ProjectIcon; + } + } else { + return rootIcon; + } +}; + +const renderTreeItem = (rootItemIcon: IconType) => (item: TreeItem) => + ; diff --git a/src/views-components/projects-tree-picker/user-projects-tree-picker.tsx b/src/views-components/projects-tree-picker/home-tree-picker.tsx similarity index 82% rename from src/views-components/projects-tree-picker/user-projects-tree-picker.tsx rename to src/views-components/projects-tree-picker/home-tree-picker.tsx index 10a0b20865..45f0b5c037 100644 --- a/src/views-components/projects-tree-picker/user-projects-tree-picker.tsx +++ b/src/views-components/projects-tree-picker/home-tree-picker.tsx @@ -3,12 +3,12 @@ // SPDX-License-Identifier: AGPL-3.0 import { connect } from 'react-redux'; -import { ProjectsTreePicker, ProjectsTreePickerProps } from '~/views-components/projects-tree-picker/projects-tree-picker'; +import { ProjectsTreePicker, ProjectsTreePickerProps } from '~/views-components/projects-tree-picker/generic-projects-tree-picker'; import { Dispatch } from 'redux'; import { loadUserProject } from '~/store/tree-picker/tree-picker-actions'; import { ProjectIcon } from '~/components/icon/icon'; -export const UserProjectsTreePicker = connect(() => ({ +export const HomeTreePicker = connect(() => ({ rootItemIcon: ProjectIcon, }), (dispatch: Dispatch): Pick => ({ loadRootItem: (_, pickerId, includeCollections, includeFiles) => { diff --git a/src/views-components/projects-tree-picker/projects-tree-picker.tsx b/src/views-components/projects-tree-picker/projects-tree-picker.tsx index be030efff7..7982813827 100644 --- a/src/views-components/projects-tree-picker/projects-tree-picker.tsx +++ b/src/views-components/projects-tree-picker/projects-tree-picker.tsx @@ -2,97 +2,28 @@ // // SPDX-License-Identifier: AGPL-3.0 -import * as React from "react"; -import { Dispatch } from "redux"; -import { connect } from "react-redux"; -import { TreeItem, TreeItemStatus } from '~/components/tree/tree'; -import { ProjectResource } from "~/models/project"; -import { treePickerActions } from "~/store/tree-picker/tree-picker-actions"; -import { ListItemTextIcon } from "~/components/list-item-text-icon/list-item-text-icon"; -import { ProjectIcon, InputIcon, IconType, CollectionIcon } from '~/components/icon/icon'; -import { loadProject, loadCollection } from '~/store/tree-picker/tree-picker-actions'; -import { GroupContentsResource } from '~/services/groups-service/groups-service'; -import { CollectionDirectory, CollectionFile, CollectionFileType } from '~/models/collection-file'; -import { ResourceKind } from '~/models/resource'; -import { TreePickerProps, TreePicker } from "~/views-components/tree-picker/tree-picker"; - -export interface ProjectsTreePickerRootItem { - id: string; - name: string; -} - -type ProjectsTreePickerItem = ProjectsTreePickerRootItem | GroupContentsResource | CollectionDirectory | CollectionFile; -type PickedTreePickerProps = Pick, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>; - -export interface ProjectsTreePickerDataProps { +import * as React from 'react'; +import { Dispatch } from 'redux'; +import { connect } from 'react-redux'; +import { HomeTreePicker } from '~/views-components/projects-tree-picker/home-tree-picker'; +import { SharedTreePicker } from '~/views-components/projects-tree-picker/shared-tree-picker'; +import { FavoritesTreePicker } from '~/views-components/projects-tree-picker/favorites-tree-picker'; +import { getProjectsTreePickerIds, treePickerActions } from '~/store/tree-picker/tree-picker-actions'; +import { TreeItem } from '~/components/tree/tree'; +import { ProjectsTreePickerItem } from './generic-projects-tree-picker'; + +export interface ProjectsTreePickerProps { pickerId: string; includeCollections?: boolean; includeFiles?: boolean; - rootItemIcon: IconType; - loadRootItem: (item: TreeItem, pickerId: string, includeCollections?: boolean, inlcudeFiles?: boolean) => void; -} - -export interface ProjectsTreePickerActionProps { + toggleItemActive?: (event: React.MouseEvent, item: TreeItem, pickerId: string) => void; } -export type ProjectsTreePickerProps = ProjectsTreePickerDataProps & ProjectsTreePickerActionProps; - -const mapStateToProps = (_: any, { pickerId, rootItemIcon }: ProjectsTreePickerProps) => ({ - render: renderTreeItem(rootItemIcon), - pickerId, -}); - -const mapDispatchToProps = (dispatch: Dispatch, { loadRootItem, includeCollections, includeFiles }: ProjectsTreePickerProps): PickedTreePickerProps => ({ - onContextMenu: () => { return; }, - toggleItemActive: (_, { id }, pickerId) => { - dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id, pickerId })); - }, - toggleItemOpen: (_, item, pickerId) => { - const { id, data, status } = item; - if (status === TreeItemStatus.INITIAL) { - if ('kind' in data) { - dispatch( - data.kind === ResourceKind.COLLECTION - ? loadCollection(id, pickerId) - : loadProject({ id, pickerId, includeCollections, includeFiles }) - ); - } else if (!('type' in data) && loadRootItem) { - loadRootItem(item as TreeItem, pickerId, includeCollections, includeFiles); - } - } else if (status === TreeItemStatus.LOADED) { - dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId })); - } - }, - toggleItemSelection: (_, { id }, pickerId) => { - dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id, pickerId })); - }, -}); - -export const ProjectsTreePicker = connect(mapStateToProps, mapDispatchToProps)(TreePicker); - -const getProjectPickerIcon = ({ data }: TreeItem, rootIcon: IconType): IconType => { - if ('kind' in data) { - switch (data.kind) { - case ResourceKind.COLLECTION: - return CollectionIcon; - default: - return ProjectIcon; - } - } else if ('type' in data) { - switch (data.type) { - case CollectionFileType.FILE: - return InputIcon; - default: - return ProjectIcon; - } - } else { - return rootIcon; - } -}; - -const renderTreeItem = (rootItemIcon: IconType) => (item: TreeItem) => - ; +export const ProjectsTreePicker = ({ pickerId, ...props }: ProjectsTreePickerProps) => { + const { home, shared, favorites } = getProjectsTreePickerIds(pickerId); + return
+ + + +
; + }; diff --git a/src/views-components/projects-tree-picker/shared-projects-tree-picker.tsx b/src/views-components/projects-tree-picker/shared-tree-picker.tsx similarity index 73% rename from src/views-components/projects-tree-picker/shared-projects-tree-picker.tsx rename to src/views-components/projects-tree-picker/shared-tree-picker.tsx index 6a05c2aac9..a986b1bb8c 100644 --- a/src/views-components/projects-tree-picker/shared-projects-tree-picker.tsx +++ b/src/views-components/projects-tree-picker/shared-tree-picker.tsx @@ -3,12 +3,12 @@ // SPDX-License-Identifier: AGPL-3.0 import { connect } from 'react-redux'; -import { ProjectsTreePicker, ProjectsTreePickerProps } from '~/views-components/projects-tree-picker/projects-tree-picker'; +import { ProjectsTreePicker, ProjectsTreePickerProps } from '~/views-components/projects-tree-picker/generic-projects-tree-picker'; import { Dispatch } from 'redux'; import { ShareMeIcon } from '~/components/icon/icon'; -import { loadProject } from '../../store/tree-picker/tree-picker-actions'; +import { loadProject } from '~/store/tree-picker/tree-picker-actions'; -export const SharedProjectsTreePicker = connect(() => ({ +export const SharedTreePicker = connect(() => ({ rootItemIcon: ShareMeIcon, }), (dispatch: Dispatch): Pick => ({ loadRootItem: (_, pickerId, includeCollections, includeFiles) => { diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 6c02dd272b..dd68cb8f8c 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -41,14 +41,10 @@ import { SharedWithMePanel } from '~/views/shared-with-me-panel/shared-with-me-p import { RunProcessPanel } from '~/views/run-process-panel/run-process-panel'; import SplitterLayout from 'react-splitter-layout'; import { WorkflowPanel } from '~/views/workflow-panel/workflow-panel'; -import { TreePicker } from '../../views-components/tree-picker/tree-picker'; -import { noop } from 'lodash'; -import { TreeItem } from '~/components/tree/tree'; -import { GroupContentsResource } from '~/services/groups-service/groups-service'; +import { HomeTreePicker } from '~/views-components/projects-tree-picker/home-tree-picker'; +import { SharedTreePicker } from '~/views-components/projects-tree-picker/shared-tree-picker'; +import { FavoritesTreePicker } from '../../views-components/projects-tree-picker/favorites-tree-picker'; import { ProjectsTreePicker } from '~/views-components/projects-tree-picker/projects-tree-picker'; -import { UserProjectsTreePicker } from '~/views-components/projects-tree-picker/user-projects-tree-picker'; -import { SharedProjectsTreePicker } from '~/views-components/projects-tree-picker/shared-projects-tree-picker'; -import { FavoritesProjectsTreePicker } from '../../views-components/projects-tree-picker/favorites-tree-picker'; type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content'; @@ -96,15 +92,12 @@ export const WorkbenchPanel = - - - - - - - - - +

Projects only

+ +

Collections included

+ +

Files included

+ console.log(args)}/>