From d1af457494186adb375ce2c012cb58685e0556e3 Mon Sep 17 00:00:00 2001 From: Michal Klobukowski Date: Thu, 4 Oct 2018 22:11:21 +0200 Subject: [PATCH] Move common operations to tree model Feature #13862 Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski --- src/components/file-tree/file-tree.tsx | 2 +- src/components/tree/tree.tsx | 14 +- src/models/collection-file.ts | 9 +- src/models/tree.test.ts | 69 ++++--- src/models/tree.ts | 85 +++++++++ .../collection-manifest-mapper.ts | 8 +- src/store/breadcrumbs/breadcrumbs-actions.ts | 2 +- .../collection-panel-files-reducer.test.ts | 8 +- .../file-tree-picker-actions.ts | 18 -- .../file-tree-picker-reducer.ts | 73 -------- .../file-tree-picker/file-tree-picker.ts | 25 --- .../side-panel-tree-actions.ts | 84 +++++---- src/store/store.ts | 2 - src/store/tree-picker/tree-picker-actions.ts | 13 +- .../tree-picker/tree-picker-reducer.test.ts | 76 ++++---- src/store/tree-picker/tree-picker-reducer.ts | 65 ++----- src/store/tree-picker/tree-picker.ts | 15 +- .../dialog-file-selection.tsx | 29 --- .../dialog-forms/file-selection-dialog.ts | 22 --- .../file-tree-picker/file-tree-picker.tsx | 173 ------------------ .../file-tree-picker/main-file-tree-picker.ts | 57 ------ .../project-tree-picker.tsx | 57 +++--- .../side-panel-tree/side-panel-tree.tsx | 7 +- .../tree-picker/tree-picker.ts | 40 ++-- src/views/workbench/workbench.tsx | 3 - 25 files changed, 323 insertions(+), 633 deletions(-) delete mode 100644 src/store/file-tree-picker/file-tree-picker-actions.ts delete mode 100644 src/store/file-tree-picker/file-tree-picker-reducer.ts delete mode 100644 src/store/file-tree-picker/file-tree-picker.ts delete mode 100644 src/views-components/dialog-file-selection/dialog-file-selection.tsx delete mode 100644 src/views-components/dialog-forms/file-selection-dialog.ts delete mode 100644 src/views-components/file-tree-picker/file-tree-picker.tsx delete mode 100644 src/views-components/file-tree-picker/main-file-tree-picker.ts diff --git a/src/components/file-tree/file-tree.tsx b/src/components/file-tree/file-tree.tsx index 06fc8b7855..165b4bfcf1 100644 --- a/src/components/file-tree/file-tree.tsx +++ b/src/components/file-tree/file-tree.tsx @@ -24,7 +24,7 @@ export class FileTree extends React.Component { onContextMenu={this.handleContextMenu} toggleItemActive={this.handleToggleActive} toggleItemOpen={this.handleToggle} - onSelectionChange={this.handleSelectionChange} />; + toggleItemSelection={this.handleSelectionChange} />; } handleContextMenu = (event: React.MouseEvent, item: TreeItem) => { diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx index c892d7d2c8..247661f181 100644 --- a/src/components/tree/tree.tsx +++ b/src/components/tree/tree.tsx @@ -60,7 +60,8 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ width: theme.spacing.unit * 3, height: theme.spacing.unit * 3, margin: `0 ${theme.spacing.unit}px`, - color: theme.palette.grey["500"] + padding: 0, + color: theme.palette.grey["500"], } }); @@ -88,7 +89,7 @@ export interface TreeProps { level?: number; onContextMenu: (event: React.MouseEvent, item: TreeItem) => void; showSelection?: boolean; - onSelectionChange?: (event: React.MouseEvent, item: TreeItem) => void; + toggleItemSelection?: (event: React.MouseEvent, item: TreeItem) => void; disableRipple?: boolean; } @@ -134,7 +135,7 @@ export const Tree = withStyles(styles)( toggleItemActive={toggleItemActive} level={level + 1} onContextMenu={onContextMenu} - onSelectionChange={this.props.onSelectionChange} /> + toggleItemSelection={this.props.toggleItemSelection} /> } )} ; @@ -164,10 +165,11 @@ export const Tree = withStyles(styles)( this.props.onContextMenu(event, item) handleCheckboxChange = (item: TreeItem) => { - const { onSelectionChange } = this.props; - return onSelectionChange + const { toggleItemSelection } = this.props; + return toggleItemSelection ? (event: React.MouseEvent) => { - onSelectionChange(event, item); + event.stopPropagation(); + toggleItemSelection(event, item); } : undefined; } diff --git a/src/models/collection-file.ts b/src/models/collection-file.ts index d74ada6008..37e18cfc0d 100644 --- a/src/models/collection-file.ts +++ b/src/models/collection-file.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { Tree, createTree, setNode } from './tree'; +import { Tree, createTree, setNode, TreeNodeStatus } from './tree'; export type CollectionFilesTree = Tree; @@ -60,6 +60,11 @@ export const createCollectionFilesTree = (data: Array()); }; \ No newline at end of file diff --git a/src/models/tree.test.ts b/src/models/tree.test.ts index 375a012054..b36cfad83d 100644 --- a/src/models/tree.test.ts +++ b/src/models/tree.test.ts @@ -12,77 +12,88 @@ describe('Tree', () => { }); it('sets new node', () => { - const newTree = Tree.setNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' })(tree); + const newTree = Tree.setNode(mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }))(tree); expect(Tree.getNode('Node 1')(newTree)).toEqual({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }); }); it('adds new node reference to parent children', () => { const [newTree] = [tree] - .map(Tree.setNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' })) - .map(Tree.setNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 2' })); + .map(Tree.setNode(mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }))) + .map(Tree.setNode(mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 2' }))); expect(Tree.getNode('Node 1')(newTree)).toEqual({ children: ['Node 2'], id: 'Node 1', parent: '', value: 'Value 1' }); }); it('gets node ancestors', () => { const newTree = [ - { children: [], id: 'Node 1', parent: '', value: 'Value 1' }, - { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }, - { children: [], id: 'Node 3', parent: 'Node 2', value: 'Value 1' } + mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 3', parent: 'Node 2', value: 'Value 1' }), ].reduce((tree, node) => Tree.setNode(node)(tree), tree); expect(Tree.getNodeAncestorsIds('Node 3')(newTree)).toEqual(['Node 1', 'Node 2']); }); it('gets node descendants', () => { const newTree = [ - { children: [], id: 'Node 1', parent: '', value: 'Value 1' }, - { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }, - { children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }, - { children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' }, - { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' } + mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }), ].reduce((tree, node) => Tree.setNode(node)(tree), tree); expect(Tree.getNodeDescendantsIds('Node 1')(newTree)).toEqual(['Node 2', 'Node 3', 'Node 2.1', 'Node 3.1']); }); it('gets root descendants', () => { const newTree = [ - { children: [], id: 'Node 1', parent: '', value: 'Value 1' }, - { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }, - { children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }, - { children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' }, - { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' } + mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }), ].reduce((tree, node) => Tree.setNode(node)(tree), tree); expect(Tree.getNodeDescendantsIds('')(newTree)).toEqual(['Node 1', 'Node 2', 'Node 3', 'Node 2.1', 'Node 3.1']); }); it('gets node children', () => { const newTree = [ - { children: [], id: 'Node 1', parent: '', value: 'Value 1' }, - { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }, - { children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }, - { children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' }, - { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' } + mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 3', parent: 'Node 1', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }), ].reduce((tree, node) => Tree.setNode(node)(tree), tree); expect(Tree.getNodeChildrenIds('Node 1')(newTree)).toEqual(['Node 2', 'Node 3']); }); it('gets root children', () => { const newTree = [ - { children: [], id: 'Node 1', parent: '', value: 'Value 1' }, - { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }, - { children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }, - { children: [], id: 'Node 3', parent: '', value: 'Value 1' }, - { children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' } + mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 3', parent: '', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 3.1', parent: 'Node 3', value: 'Value 1' }), ].reduce((tree, node) => Tree.setNode(node)(tree), tree); expect(Tree.getNodeChildrenIds('')(newTree)).toEqual(['Node 1', 'Node 3']); }); it('maps tree', () => { const newTree = [ - { children: [], id: 'Node 1', parent: '', value: 'Value 1' }, - { children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 2' }, + mockTreeNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' }), + mockTreeNode({ children: [], id: 'Node 2', parent: 'Node 1', value: 'Value 2' }), ].reduce((tree, node) => Tree.setNode(node)(tree), tree); const mappedTree = Tree.mapTreeValues(value => parseInt(value.split(' ')[1], 10))(newTree); - expect(Tree.getNode('Node 2')(mappedTree)).toEqual({ children: [], id: 'Node 2', parent: 'Node 1', value: 2 }, ); + expect(Tree.getNode('Node 2')(mappedTree)).toEqual({ children: [], id: 'Node 2', parent: 'Node 1', value: 2 }); }); +}); + +const mockTreeNode = (node: Partial>): Tree.TreeNode => ({ + children: [], + id: '', + parent: '', + value: '', + active: false, + selected: false, + expanded: false, + status: Tree.TreeNodeStatus.INITIAL, }); \ No newline at end of file diff --git a/src/models/tree.ts b/src/models/tree.ts index a5fb49cff4..b649a40fce 100644 --- a/src/models/tree.ts +++ b/src/models/tree.ts @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 +import { pipe } from 'lodash/fp'; export type Tree = Record>; export const TREE_ROOT_ID = ''; @@ -11,6 +12,16 @@ export interface TreeNode { value: T; id: string; parent: string; + active: boolean; + selected: boolean; + expanded: boolean; + status: TreeNodeStatus; +} + +export enum TreeNodeStatus { + INITIAL = 'INITIAL', + PENDING = 'PENDING', + LOADED = 'LOADED', } export const createTree = (): Tree => ({}); @@ -18,6 +29,7 @@ export const createTree = (): Tree => ({}); export const getNode = (id: string) => (tree: Tree): TreeNode | undefined => tree[id]; export const setNode = (node: TreeNode) => (tree: Tree): Tree => { + console.log(node); const [newTree] = [tree] .map(tree => getNode(node.id)(tree) === node ? tree @@ -95,6 +107,79 @@ export const getNodeChildrenIds = (id: string) => (tree: Tree): string[] = export const mapIdsToNodes = (ids: string[]) => (tree: Tree) => ids.map(id => getNode(id)(tree)).filter((node): node is TreeNode => node !== undefined); +export const activateNode = (id: string) => (tree: Tree) => + mapTree(node => node.id === id ? { ...node, active: true } : { ...node, active: false })(tree); + + +export const expandNode = (...ids: string[]) => (tree: Tree) => + mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: true } : node)(tree); + +export const collapseNode = (...ids: string[]) => (tree: Tree) => + mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: false } : node)(tree); + +export const toggleNodeCollapse = (...ids: string[]) => (tree: Tree) => + mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: !node.expanded } : node)(tree); + +export const setNodeStatus = (id: string) => (status: TreeNodeStatus) => (tree: Tree) => { + const node = getNode(id)(tree); + return node + ? setNode({ ...node, status })(tree) + : tree; +}; + +export const toggleNodeSelection = (id: string) => (tree: Tree) => { + const node = getNode(id)(tree); + return node + ? pipe( + setNode({ ...node, selected: !node.selected }), + toggleAncestorsSelection(id), + toggleDescendantsSelection(id))(tree) + : tree; + +}; + +export const initTreeNode = (data: Pick, 'id' | 'value'>): TreeNode => ({ + children: [], + active: false, + selected: false, + expanded: false, + status: TreeNodeStatus.INITIAL, + parent: '', + ...data, +}); + +const toggleDescendantsSelection = (id: string) => (tree: Tree) => { + const node = getNode(id)(tree); + if (node) { + return getNodeDescendants(id)(tree) + .reduce((newTree, subNode) => + setNode({ ...subNode, selected: node.selected })(newTree), + tree); + } + return tree; +}; + +const toggleAncestorsSelection = (id: string) => (tree: Tree) => { + const ancestors = getNodeAncestorsIds(id)(tree).reverse(); + return ancestors.reduce((newTree, parent) => parent ? toggleParentNodeSelection(parent)(newTree) : newTree, tree); +}; + +const toggleParentNodeSelection = (id: string) => (tree: Tree) => { + const node = getNode(id)(tree); + if (node) { + const parentNode = getNode(node.id)(tree); + if (parentNode) { + const selected = parentNode.children + .map(id => getNode(id)(tree)) + .every(node => node !== undefined && node.selected); + return setNode({ ...parentNode, selected })(tree); + } + return setNode(node)(tree); + } + return tree; +}; + + const mapNodeValue = (mapFn: (value: T) => R) => (node: TreeNode): TreeNode => ({ ...node, value: mapFn(node.value) }); diff --git a/src/services/collection-files-service/collection-manifest-mapper.ts b/src/services/collection-files-service/collection-manifest-mapper.ts index 0c7e91deec..6e64f833e5 100644 --- a/src/services/collection-files-service/collection-manifest-mapper.ts +++ b/src/services/collection-files-service/collection-manifest-mapper.ts @@ -4,7 +4,7 @@ import { uniqBy, groupBy } from 'lodash'; import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "~/models/keep-manifest"; -import { TreeNode, setNode, createTree, getNodeDescendantsIds, getNodeValue } from '~/models/tree'; +import { TreeNode, setNode, createTree, getNodeDescendantsIds, getNodeValue, TreeNodeStatus } from '~/models/tree'; import { CollectionFilesTree, CollectionFile, CollectionDirectory, createCollectionDirectory, createCollectionFile, CollectionFileType } from '../../models/collection-file'; export const mapCollectionFilesTreeToManifest = (tree: CollectionFilesTree): KeepManifest => { @@ -30,7 +30,11 @@ export const mapCollectionFileToTreeNode = (file: CollectionFile): TreeNode => ([ diff --git a/src/store/breadcrumbs/breadcrumbs-actions.ts b/src/store/breadcrumbs/breadcrumbs-actions.ts index a5ded34eaf..8b1eb2b0cc 100644 --- a/src/store/breadcrumbs/breadcrumbs-actions.ts +++ b/src/store/breadcrumbs/breadcrumbs-actions.ts @@ -28,7 +28,7 @@ const getSidePanelTreeBreadcrumbs = (uuid: string) => (treePicker: TreePicker): const nodes = getSidePanelTreeBranch(uuid)(treePicker); return nodes.map(node => typeof node.value === 'string' - ? { label: node.value, uuid: node.nodeId } + ? { label: node.value, uuid: node.id } : { label: node.value.name, uuid: node.value.uuid }); }; diff --git a/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.test.ts b/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.test.ts index 90dedaaaa4..3964ee4769 100644 --- a/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.test.ts +++ b/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.test.ts @@ -5,7 +5,7 @@ import { collectionPanelFilesReducer } from "./collection-panel-files-reducer"; import { collectionPanelFilesAction } from "./collection-panel-files-actions"; import { CollectionFile, CollectionDirectory, createCollectionFile, createCollectionDirectory } from "~/models/collection-file"; -import { createTree, setNode, getNodeValue, mapTreeValues } from "~/models/tree"; +import { createTree, setNode, getNodeValue, mapTreeValues, TreeNodeStatus } from "~/models/tree"; import { CollectionPanelFile, CollectionPanelDirectory } from "./collection-panel-files-state"; describe('CollectionPanelFilesReducer', () => { @@ -26,7 +26,11 @@ describe('CollectionPanelFilesReducer', () => { children: [], id: file.id, parent: file.path, - value: file + value: file, + active: false, + selected: false, + expanded: false, + status: TreeNodeStatus.INITIAL, })(tree), createTree()); const collectionPanelFilesTree = collectionPanelFilesReducer( diff --git a/src/store/file-tree-picker/file-tree-picker-actions.ts b/src/store/file-tree-picker/file-tree-picker-actions.ts deleted file mode 100644 index 18d0f5f8e4..0000000000 --- a/src/store/file-tree-picker/file-tree-picker-actions.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { unionize, ofType, UnionOf } from "~/common/unionize"; - -import { TreePickerNode } from "./file-tree-picker"; - -export const fileTreePickerActions = unionize({ - LOAD_TREE_PICKER_NODE: ofType<{ nodeId: string, pickerId: string }>(), - LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ nodeId: string, nodes: Array, pickerId: string }>(), - TOGGLE_TREE_PICKER_NODE_COLLAPSE: ofType<{ nodeId: string, pickerId: string }>(), - TOGGLE_TREE_PICKER_NODE_SELECT: ofType<{ nodeId: string, pickerId: string }>(), - EXPAND_TREE_PICKER_NODES: ofType<{ nodeIds: string[], pickerId: string }>(), - RESET_TREE_PICKER: ofType<{ pickerId: string }>() -}); - -export type FileTreePickerAction = UnionOf; diff --git a/src/store/file-tree-picker/file-tree-picker-reducer.ts b/src/store/file-tree-picker/file-tree-picker-reducer.ts deleted file mode 100644 index 59e33a9987..0000000000 --- a/src/store/file-tree-picker/file-tree-picker-reducer.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { createTree, setNodeValueWith, TreeNode, setNode, mapTreeValues, Tree } from "~/models/tree"; -import { TreePicker, TreePickerNode } from "./file-tree-picker"; -import { fileTreePickerActions, FileTreePickerAction } from "./file-tree-picker-actions"; -import { TreeItemStatus } from "~/components/tree/tree"; -import { compose } from "redux"; -import { getNode } from '~/models/tree'; - -export const fileTreePickerReducer = (state: TreePicker = {}, action: FileTreePickerAction) => - fileTreePickerActions.match(action, { - LOAD_TREE_PICKER_NODE: ({ nodeId, pickerId }) => - updateOrCreatePicker(state, pickerId, setNodeValueWith(setPending)(nodeId)), - LOAD_TREE_PICKER_NODE_SUCCESS: ({ nodeId, nodes, pickerId }) => - updateOrCreatePicker(state, pickerId, compose(receiveNodes(nodes)(nodeId), setNodeValueWith(setLoaded)(nodeId))), - TOGGLE_TREE_PICKER_NODE_COLLAPSE: ({ nodeId, pickerId }) => - updateOrCreatePicker(state, pickerId, setNodeValueWith(toggleCollapse)(nodeId)), - TOGGLE_TREE_PICKER_NODE_SELECT: ({ nodeId, pickerId }) => - updateOrCreatePicker(state, pickerId, mapTreeValues(toggleSelect(nodeId))), - RESET_TREE_PICKER: ({ pickerId }) => - updateOrCreatePicker(state, pickerId, createTree), - EXPAND_TREE_PICKER_NODES: ({ pickerId, nodeIds }) => - updateOrCreatePicker(state, pickerId, mapTreeValues(expand(nodeIds))), - default: () => state - }); - -const updateOrCreatePicker = (state: TreePicker, pickerId: string, func: (value: Tree) => Tree) => { - const picker = state[pickerId] || createTree(); - const updatedPicker = func(picker); - return { ...state, [pickerId]: updatedPicker }; -}; - -const expand = (ids: string[]) => (node: TreePickerNode): TreePickerNode => - ids.some(id => id === node.nodeId) - ? { ...node, collapsed: false } - : node; - -const setPending = (value: TreePickerNode): TreePickerNode => - ({ ...value, status: TreeItemStatus.PENDING }); - -const setLoaded = (value: TreePickerNode): TreePickerNode => - ({ ...value, status: TreeItemStatus.LOADED }); - -const toggleCollapse = (value: TreePickerNode): TreePickerNode => - ({ ...value, collapsed: !value.collapsed }); - -const toggleSelect = (nodeId: string) => (value: TreePickerNode): TreePickerNode => - value.nodeId === nodeId - ? ({ ...value, selected: !value.selected }) - : ({ ...value, selected: false }); - -const receiveNodes = (nodes: Array) => (parent: string) => (state: Tree) => { - const parentNode = getNode(parent)(state); - let newState = state; - if (parentNode) { - newState = setNode({ ...parentNode, children: [] })(state); - } - return nodes.reduce((tree, node) => { - const oldNode = getNode(node.nodeId)(state) || { value: {} }; - const newNode = createTreeNode(parent)(node); - const value = { ...oldNode.value, ...newNode.value }; - return setNode({ ...newNode, value })(tree); - }, newState); -}; - -const createTreeNode = (parent: string) => (node: TreePickerNode): TreeNode => ({ - children: [], - id: node.nodeId, - parent, - value: node -}); diff --git a/src/store/file-tree-picker/file-tree-picker.ts b/src/store/file-tree-picker/file-tree-picker.ts deleted file mode 100644 index 259a4b8d53..0000000000 --- a/src/store/file-tree-picker/file-tree-picker.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { Tree } from "~/models/tree"; -import { TreeItemStatus } from "~/components/tree/tree"; - -export type TreePicker = { [key: string]: Tree }; - -export interface TreePickerNode { - nodeId: string; - value: Value; - selected: boolean; - collapsed: boolean; - status: TreeItemStatus; -} - -export const createTreePickerNode = (data: { nodeId: string, value: any }) => ({ - ...data, - selected: false, - collapsed: true, - status: TreeItemStatus.INITIAL -}); - -export const getTreePicker = (id: string) => (state: TreePicker): Tree> | undefined => state[id]; \ No newline at end of file diff --git a/src/store/side-panel-tree/side-panel-tree-actions.ts b/src/store/side-panel-tree/side-panel-tree-actions.ts index 22a83dda8c..ec593b9ba3 100644 --- a/src/store/side-panel-tree/side-panel-tree-actions.ts +++ b/src/store/side-panel-tree/side-panel-tree-actions.ts @@ -4,14 +4,12 @@ import { Dispatch } from 'redux'; import { treePickerActions } from "~/store/tree-picker/tree-picker-actions"; -import { createTreePickerNode, TreePickerNode } from '~/store/tree-picker/tree-picker'; import { RootState } from '../store'; import { ServiceRepository } from '~/services/services'; import { FilterBuilder } from '~/services/api/filter-builder'; import { resourcesActions } from '../resources/resources-actions'; import { getTreePicker, TreePicker } from '../tree-picker/tree-picker'; -import { TreeItemStatus } from "~/components/tree/tree"; -import { getNodeAncestors, getNodeValue, getNodeAncestorsIds, getNode } from '~/models/tree'; +import { getNodeAncestors, getNodeAncestorsIds, getNode, TreeNode, initTreeNode, TreeNodeStatus } from '~/models/tree'; import { ProjectResource } from '~/models/project'; import { OrderBuilder } from '../../services/api/order-builder'; @@ -29,11 +27,11 @@ export const SIDE_PANEL_TREE = 'sidePanelTree'; export const getSidePanelTree = (treePicker: TreePicker) => getTreePicker(SIDE_PANEL_TREE)(treePicker); -export const getSidePanelTreeBranch = (uuid: string) => (treePicker: TreePicker): Array> => { +export const getSidePanelTreeBranch = (uuid: string) => (treePicker: TreePicker): Array> => { const tree = getSidePanelTree(treePicker); if (tree) { - const ancestors = getNodeAncestors(uuid)(tree).map(node => node.value); - const node = getNodeValue(uuid)(tree); + const ancestors = getNodeAncestors(uuid)(tree); + const node = getNode(uuid)(tree); if (node) { return [...ancestors, node]; } @@ -54,16 +52,16 @@ export const isSidePanelTreeCategory = (id: string) => SIDE_PANEL_CATEGORIES.som export const initSidePanelTree = () => (dispatch: Dispatch, getState: () => RootState, { authService }: ServiceRepository) => { const rootProjectUuid = authService.getUuid() || ''; - const nodes = SIDE_PANEL_CATEGORIES.map(nodeId => createTreePickerNode({ nodeId, value: nodeId })); - const projectsNode = createTreePickerNode({ nodeId: rootProjectUuid, value: SidePanelTreeCategory.PROJECTS }); + const nodes = SIDE_PANEL_CATEGORIES.map(id => initTreeNode({ id, value: id })); + const projectsNode = initTreeNode({ id: rootProjectUuid, value: SidePanelTreeCategory.PROJECTS }); dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ - nodeId: '', + id: '', pickerId: SIDE_PANEL_TREE, nodes: [projectsNode, ...nodes] })); SIDE_PANEL_CATEGORIES.forEach(category => { dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ - nodeId: category, + id: category, pickerId: SIDE_PANEL_TREE, nodes: [] })); @@ -75,7 +73,7 @@ export const loadSidePanelTreeProjects = (projectUuid: string) => const treePicker = getTreePicker(SIDE_PANEL_TREE)(getState().treePicker); const node = treePicker ? getNode(projectUuid)(treePicker) : undefined; if (node || projectUuid === '') { - dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId: projectUuid, pickerId: SIDE_PANEL_TREE })); + dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id: projectUuid, pickerId: SIDE_PANEL_TREE })); const params = { filters: new FilterBuilder() .addEqual('ownerUuid', projectUuid) @@ -86,81 +84,81 @@ export const loadSidePanelTreeProjects = (projectUuid: string) => }; const { items } = await services.projectService.list(params); dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ - nodeId: projectUuid, + id: projectUuid, pickerId: SIDE_PANEL_TREE, - nodes: items.map(item => createTreePickerNode({ nodeId: item.uuid, value: item })), + nodes: items.map(item => initTreeNode({ id: item.uuid, value: item })), })); dispatch(resourcesActions.SET_RESOURCES(items)); } }; -export const activateSidePanelTreeItem = (nodeId: string) => +export const activateSidePanelTreeItem = (id: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const node = getSidePanelTreeNode(nodeId)(getState().treePicker); - if (node && !node.selected) { - dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId, pickerId: SIDE_PANEL_TREE })); + const node = getSidePanelTreeNode(id)(getState().treePicker); + if (node && !node.active) { + dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id, pickerId: SIDE_PANEL_TREE })); } - if (!isSidePanelTreeCategory(nodeId)) { - await dispatch(activateSidePanelTreeProject(nodeId)); + if (!isSidePanelTreeCategory(id)) { + await dispatch(activateSidePanelTreeProject(id)); } }; -export const activateSidePanelTreeProject = (nodeId: string) => +export const activateSidePanelTreeProject = (id: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const { treePicker } = getState(); - const node = getSidePanelTreeNode(nodeId)(treePicker); - if (node && node.status !== TreeItemStatus.LOADED) { - await dispatch(loadSidePanelTreeProjects(nodeId)); + const node = getSidePanelTreeNode(id)(treePicker); + if (node && node.status !== TreeNodeStatus.LOADED) { + await dispatch(loadSidePanelTreeProjects(id)); } else if (node === undefined) { - await dispatch(activateSidePanelTreeBranch(nodeId)); + await dispatch(activateSidePanelTreeBranch(id)); } dispatch(treePickerActions.EXPAND_TREE_PICKER_NODES({ - nodeIds: getSidePanelTreeNodeAncestorsIds(nodeId)(treePicker), + ids: getSidePanelTreeNodeAncestorsIds(id)(treePicker), pickerId: SIDE_PANEL_TREE })); - dispatch(expandSidePanelTreeItem(nodeId)); + dispatch(expandSidePanelTreeItem(id)); }; -export const activateSidePanelTreeBranch = (nodeId: string) => +export const activateSidePanelTreeBranch = (id: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const ancestors = await services.ancestorsService.ancestors(nodeId, services.authService.getUuid() || ''); + const ancestors = await services.ancestorsService.ancestors(id, services.authService.getUuid() || ''); for (const ancestor of ancestors) { await dispatch(loadSidePanelTreeProjects(ancestor.uuid)); } dispatch(treePickerActions.EXPAND_TREE_PICKER_NODES({ - nodeIds: ancestors.map(ancestor => ancestor.uuid), + ids: ancestors.map(ancestor => ancestor.uuid), pickerId: SIDE_PANEL_TREE })); - dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId, pickerId: SIDE_PANEL_TREE })); + dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id, pickerId: SIDE_PANEL_TREE })); }; -export const toggleSidePanelTreeItemCollapse = (nodeId: string) => +export const toggleSidePanelTreeItemCollapse = (id: string) => async (dispatch: Dispatch, getState: () => RootState) => { - const node = getSidePanelTreeNode(nodeId)(getState().treePicker); - if (node && node.status === TreeItemStatus.INITIAL) { - await dispatch(loadSidePanelTreeProjects(node.nodeId)); + const node = getSidePanelTreeNode(id)(getState().treePicker); + if (node && node.status === TreeNodeStatus.INITIAL) { + await dispatch(loadSidePanelTreeProjects(node.id)); } - dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId: SIDE_PANEL_TREE })); + dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId: SIDE_PANEL_TREE })); }; -export const expandSidePanelTreeItem = (nodeId: string) => +export const expandSidePanelTreeItem = (id: string) => async (dispatch: Dispatch, getState: () => RootState) => { - const node = getSidePanelTreeNode(nodeId)(getState().treePicker); - if (node && node.collapsed) { - dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId: SIDE_PANEL_TREE })); + const node = getSidePanelTreeNode(id)(getState().treePicker); + if (node && !node.expanded) { + dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId: SIDE_PANEL_TREE })); } }; -export const getSidePanelTreeNode = (nodeId: string) => (treePicker: TreePicker) => { +export const getSidePanelTreeNode = (id: string) => (treePicker: TreePicker) => { const sidePanelTree = getTreePicker(SIDE_PANEL_TREE)(treePicker); return sidePanelTree - ? getNodeValue(nodeId)(sidePanelTree) + ? getNode(id)(sidePanelTree) : undefined; }; -export const getSidePanelTreeNodeAncestorsIds = (nodeId: string) => (treePicker: TreePicker) => { +export const getSidePanelTreeNodeAncestorsIds = (id: string) => (treePicker: TreePicker) => { const sidePanelTree = getTreePicker(SIDE_PANEL_TREE)(treePicker); return sidePanelTree - ? getNodeAncestorsIds(nodeId)(sidePanelTree) + ? getNodeAncestorsIds(id)(sidePanelTree) : []; }; diff --git a/src/store/store.ts b/src/store/store.ts index 4c08a39c4b..21e5029a03 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -38,7 +38,6 @@ import { progressIndicatorReducer } from './progress-indicator/progress-indicato import { runProcessPanelReducer } from '~/store/run-process-panel/run-process-panel-reducer'; import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service'; import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions'; -import { fileTreePickerReducer } from './file-tree-picker/file-tree-picker-reducer'; const composeEnhancers = (process.env.NODE_ENV === 'development' && @@ -100,6 +99,5 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({ fileUploader: fileUploaderReducer, processPanel: processPanelReducer, progressIndicator: progressIndicatorReducer, - fileTreePicker: fileTreePickerReducer, runProcessPanel: runProcessPanelReducer }); diff --git a/src/store/tree-picker/tree-picker-actions.ts b/src/store/tree-picker/tree-picker-actions.ts index 5b04389af6..0385766f5f 100644 --- a/src/store/tree-picker/tree-picker-actions.ts +++ b/src/store/tree-picker/tree-picker-actions.ts @@ -3,15 +3,16 @@ // SPDX-License-Identifier: AGPL-3.0 import { unionize, ofType, UnionOf } from "~/common/unionize"; +import { TreeNode } from '~/models/tree'; -import { TreePickerNode } from "./tree-picker"; export const treePickerActions = unionize({ - LOAD_TREE_PICKER_NODE: ofType<{ nodeId: string, pickerId: string }>(), - LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ nodeId: string, nodes: Array, pickerId: string }>(), - TOGGLE_TREE_PICKER_NODE_COLLAPSE: ofType<{ nodeId: string, pickerId: string }>(), - TOGGLE_TREE_PICKER_NODE_SELECT: ofType<{ nodeId: string, pickerId: string }>(), - EXPAND_TREE_PICKER_NODES: ofType<{ nodeIds: string[], pickerId: string }>(), + LOAD_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(), + 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 }>(), + 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 }>() }); diff --git a/src/store/tree-picker/tree-picker-reducer.test.ts b/src/store/tree-picker/tree-picker-reducer.test.ts index e09d12d777..56a3ba5d29 100644 --- a/src/store/tree-picker/tree-picker-reducer.test.ts +++ b/src/store/tree-picker/tree-picker-reducer.test.ts @@ -3,103 +3,103 @@ // SPDX-License-Identifier: AGPL-3.0 import { createTree, getNodeValue, getNodeChildrenIds } from "~/models/tree"; -import { TreePickerNode, createTreePickerNode } from "./tree-picker"; import { treePickerReducer } from "./tree-picker-reducer"; import { treePickerActions } from "./tree-picker-actions"; import { TreeItemStatus } from "~/components/tree/tree"; +import { initTreeNode } from '~/models/tree'; describe('TreePickerReducer', () => { it('LOAD_TREE_PICKER_NODE - initial state', () => { - const tree = createTree(); - const newState = treePickerReducer({}, treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId: '1', pickerId: "projects" })); + const tree = createTree<{}>(); + const newState = treePickerReducer({}, treePickerActions.LOAD_TREE_PICKER_NODE({ id: '1', pickerId: "projects" })); expect(newState).toEqual({ 'projects': tree }); }); it('LOAD_TREE_PICKER_NODE', () => { - const node = createTreePickerNode({ nodeId: '1', value: '1' }); + const node = initTreeNode({ id: '1', value: '1' }); const [newState] = [{ - projects: createTree() + projects: createTree<{}>() }] - .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" }))) - .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId: '1', pickerId: "projects" }))); + .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" }))) + .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE({ id: '1', pickerId: "projects" }))); expect(getNodeValue('1')(newState.projects)).toEqual({ - ...createTreePickerNode({ nodeId: '1', value: '1' }), + ...initTreeNode({ id: '1', value: '1' }), status: TreeItemStatus.PENDING }); }); it('LOAD_TREE_PICKER_NODE_SUCCESS - initial state', () => { - const subNode = createTreePickerNode({ nodeId: '1.1', value: '1.1' }); - const newState = treePickerReducer({}, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [subNode], pickerId: "projects" })); + const subNode = initTreeNode({ id: '1.1', value: '1.1' }); + const newState = treePickerReducer({}, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [subNode], pickerId: "projects" })); expect(getNodeChildrenIds('')(newState.projects)).toEqual(['1.1']); }); it('LOAD_TREE_PICKER_NODE_SUCCESS', () => { - const node = createTreePickerNode({ nodeId: '1', value: '1' }); - const subNode = createTreePickerNode({ nodeId: '1.1', value: '1.1' }); + const node = initTreeNode({ id: '1', value: '1' }); + const subNode = initTreeNode({ id: '1.1', value: '1.1' }); const [newState] = [{ - projects: createTree() + projects: createTree<{}>() }] - .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" }))) - .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '1', nodes: [subNode], pickerId: "projects" }))); + .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" }))) + .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '1', nodes: [subNode], pickerId: "projects" }))); expect(getNodeChildrenIds('1')(newState.projects)).toEqual(['1.1']); expect(getNodeValue('1')(newState.projects)).toEqual({ - ...createTreePickerNode({ nodeId: '1', value: '1' }), + ...initTreeNode({ id: '1', value: '1' }), status: TreeItemStatus.LOADED }); }); it('TOGGLE_TREE_PICKER_NODE_COLLAPSE - collapsed', () => { - const node = createTreePickerNode({ nodeId: '1', value: '1' }); + const node = initTreeNode({ id: '1', value: '1' }); const [newState] = [{ - projects: createTree() + projects: createTree<{}>() }] - .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" }))) - .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId: '1', pickerId: "projects" }))); + .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" }))) + .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1', pickerId: "projects" }))); expect(getNodeValue('1')(newState.projects)).toEqual({ - ...createTreePickerNode({ nodeId: '1', value: '1' }), + ...initTreeNode({ id: '1', value: '1' }), collapsed: false }); }); it('TOGGLE_TREE_PICKER_NODE_COLLAPSE - expanded', () => { - const node = createTreePickerNode({ nodeId: '1', value: '1' }); + const node = initTreeNode({ id: '1', value: '1' }); const [newState] = [{ - projects: createTree() + projects: createTree<{}>() }] - .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" }))) - .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId: '1', pickerId: "projects" }))) - .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId: '1', pickerId: "projects" }))); + .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" }))) + .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1', pickerId: "projects" }))) + .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1', pickerId: "projects" }))); expect(getNodeValue('1')(newState.projects)).toEqual({ - ...createTreePickerNode({ nodeId: '1', value: '1' }), + ...initTreeNode({ id: '1', value: '1' }), collapsed: true }); }); it('TOGGLE_TREE_PICKER_NODE_SELECT - selected', () => { - const node = createTreePickerNode({ nodeId: '1', value: '1' }); + const node = initTreeNode({ id: '1', value: '1' }); const [newState] = [{ - projects: createTree() + projects: createTree<{}>() }] - .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" }))) - .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId: '1', pickerId: "projects" }))); + .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" }))) + .map(state => treePickerReducer(state, treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: '1', pickerId: "projects" }))); expect(getNodeValue('1')(newState.projects)).toEqual({ - ...createTreePickerNode({ nodeId: '1', value: '1' }), + ...initTreeNode({ id: '1', value: '1' }), selected: true }); }); it('TOGGLE_TREE_PICKER_NODE_SELECT - not selected', () => { - const node = createTreePickerNode({ nodeId: '1', value: '1' }); + const node = initTreeNode({ id: '1', value: '1' }); const [newState] = [{ - projects: createTree() + projects: createTree<{}>() }] - .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ nodeId: '', nodes: [node], pickerId: "projects" }))) - .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId: '1', pickerId: "projects" }))) - .map(state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId: '1', pickerId: "projects" }))); + .map(state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" }))) + .map(state => treePickerReducer(state, treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: '1', pickerId: "projects" }))) + .map(state => treePickerReducer(state, treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: '1', pickerId: "projects" }))); expect(getNodeValue('1')(newState.projects)).toEqual({ - ...createTreePickerNode({ nodeId: '1', value: '1' }), + ...initTreeNode({ id: '1', value: '1' }), selected: false }); }); diff --git a/src/store/tree-picker/tree-picker-reducer.ts b/src/store/tree-picker/tree-picker-reducer.ts index b0d9bc94ca..054cb442b2 100644 --- a/src/store/tree-picker/tree-picker-reducer.ts +++ b/src/store/tree-picker/tree-picker-reducer.ts @@ -2,72 +2,45 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { createTree, setNodeValueWith, TreeNode, setNode, mapTreeValues, Tree } from "~/models/tree"; -import { TreePicker, TreePickerNode } from "./tree-picker"; +import { createTree, TreeNode, setNode, Tree, TreeNodeStatus, setNodeStatus, expandNode } from '~/models/tree'; +import { TreePicker } from "./tree-picker"; import { treePickerActions, TreePickerAction } from "./tree-picker-actions"; -import { TreeItemStatus } from "~/components/tree/tree"; import { compose } from "redux"; -import { getNode } from '../../models/tree'; +import { getNode, toggleNodeCollapse, toggleNodeSelection } from '~/models/tree'; +import { activateNode } from '../../models/tree'; export const treePickerReducer = (state: TreePicker = {}, action: TreePickerAction) => treePickerActions.match(action, { - LOAD_TREE_PICKER_NODE: ({ nodeId, pickerId }) => - updateOrCreatePicker(state, pickerId, setNodeValueWith(setPending)(nodeId)), - LOAD_TREE_PICKER_NODE_SUCCESS: ({ nodeId, nodes, pickerId }) => - updateOrCreatePicker(state, pickerId, compose(receiveNodes(nodes)(nodeId), setNodeValueWith(setLoaded)(nodeId))), - TOGGLE_TREE_PICKER_NODE_COLLAPSE: ({ nodeId, pickerId }) => - updateOrCreatePicker(state, pickerId, setNodeValueWith(toggleCollapse)(nodeId)), - TOGGLE_TREE_PICKER_NODE_SELECT: ({ nodeId, pickerId }) => - updateOrCreatePicker(state, pickerId, mapTreeValues(toggleSelect(nodeId))), + LOAD_TREE_PICKER_NODE: ({ id, pickerId }) => + updateOrCreatePicker(state, pickerId, setNodeStatus(id)(TreeNodeStatus.PENDING)), + LOAD_TREE_PICKER_NODE_SUCCESS: ({ id, nodes, pickerId }) => + updateOrCreatePicker(state, pickerId, compose(receiveNodes(nodes)(id), setNodeStatus(id)(TreeNodeStatus.LOADED))), + TOGGLE_TREE_PICKER_NODE_COLLAPSE: ({ id, pickerId }) => + updateOrCreatePicker(state, pickerId, toggleNodeCollapse(id)), + ACTIVATE_TREE_PICKER_NODE: ({ id, pickerId }) => + updateOrCreatePicker(state, pickerId, activateNode(id)), + TOGGLE_TREE_PICKER_NODE_SELECTION: ({ id, pickerId }) => + updateOrCreatePicker(state, pickerId, toggleNodeSelection(id)), RESET_TREE_PICKER: ({ pickerId }) => updateOrCreatePicker(state, pickerId, createTree), - EXPAND_TREE_PICKER_NODES: ({ pickerId, nodeIds }) => - updateOrCreatePicker(state, pickerId, mapTreeValues(expand(nodeIds))), + EXPAND_TREE_PICKER_NODES: ({ pickerId, ids }) => + updateOrCreatePicker(state, pickerId, expandNode(...ids)), default: () => state }); -const updateOrCreatePicker = (state: TreePicker, pickerId: string, func: (value: Tree) => Tree) => { +const updateOrCreatePicker = (state: TreePicker, pickerId: string, func: (value: Tree) => Tree) => { const picker = state[pickerId] || createTree(); const updatedPicker = func(picker); return { ...state, [pickerId]: updatedPicker }; }; -const expand = (ids: string[]) => (node: TreePickerNode): TreePickerNode => - ids.some(id => id === node.nodeId) - ? { ...node, collapsed: false } - : node; - -const setPending = (value: TreePickerNode): TreePickerNode => - ({ ...value, status: TreeItemStatus.PENDING }); - -const setLoaded = (value: TreePickerNode): TreePickerNode => - ({ ...value, status: TreeItemStatus.LOADED }); - -const toggleCollapse = (value: TreePickerNode): TreePickerNode => - ({ ...value, collapsed: !value.collapsed }); - -const toggleSelect = (nodeId: string) => (value: TreePickerNode): TreePickerNode => - value.nodeId === nodeId - ? ({ ...value, selected: !value.selected }) - : ({ ...value, selected: false }); - -const receiveNodes = (nodes: Array) => (parent: string) => (state: Tree) => { +const receiveNodes = (nodes: Array>) => (parent: string) => (state: Tree) => { const parentNode = getNode(parent)(state); let newState = state; if (parentNode) { newState = setNode({ ...parentNode, children: [] })(state); } return nodes.reduce((tree, node) => { - const oldNode = getNode(node.nodeId)(state) || { value: {} }; - const newNode = createTreeNode(parent)(node); - const value = { ...oldNode.value, ...newNode.value }; - return setNode({ ...newNode, value })(tree); + return setNode({ ...node, parent })(tree); }, newState); }; - -const createTreeNode = (parent: string) => (node: TreePickerNode): TreeNode => ({ - children: [], - id: node.nodeId, - parent, - value: node -}); diff --git a/src/store/tree-picker/tree-picker.ts b/src/store/tree-picker/tree-picker.ts index 259a4b8d53..a4127fa3bb 100644 --- a/src/store/tree-picker/tree-picker.ts +++ b/src/store/tree-picker/tree-picker.ts @@ -3,17 +3,10 @@ // SPDX-License-Identifier: AGPL-3.0 import { Tree } from "~/models/tree"; -import { TreeItemStatus } from "~/components/tree/tree"; +import { TreeItemStatus } from '~/components/tree/tree'; +export type TreePicker = { [key: string]: Tree }; -export type TreePicker = { [key: string]: Tree }; - -export interface TreePickerNode { - nodeId: string; - value: Value; - selected: boolean; - collapsed: boolean; - status: TreeItemStatus; -} +export const getTreePicker = (id: string) => (state: TreePicker): Tree | undefined => state[id]; export const createTreePickerNode = (data: { nodeId: string, value: any }) => ({ ...data, @@ -21,5 +14,3 @@ export const createTreePickerNode = (data: { nodeId: string, value: any }) => ({ collapsed: true, status: TreeItemStatus.INITIAL }); - -export const getTreePicker = (id: string) => (state: TreePicker): Tree> | undefined => state[id]; \ No newline at end of file diff --git a/src/views-components/dialog-file-selection/dialog-file-selection.tsx b/src/views-components/dialog-file-selection/dialog-file-selection.tsx deleted file mode 100644 index e7185c0560..0000000000 --- a/src/views-components/dialog-file-selection/dialog-file-selection.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import * as React from 'react'; -import { InjectedFormProps, Field } from 'redux-form'; -import { WithDialogProps } from '~/store/dialog/with-dialog'; -import { CollectionCreateFormDialogData } from '~/store/collections/collection-create-actions'; -import { FormDialog } from '~/components/form-dialog/form-dialog'; -import { require } from '~/validators/require'; -import { FileTreePickerField } from '~/views-components/file-tree-picker/file-tree-picker'; - -type FileSelectionProps = WithDialogProps<{}> & InjectedFormProps; - -export const DialogFileSelection = (props: FileSelectionProps) => - ; - -const FileSelectionFields = () => - ; - -const FILES_FIELD_VALIDATION = [require]; \ No newline at end of file diff --git a/src/views-components/dialog-forms/file-selection-dialog.ts b/src/views-components/dialog-forms/file-selection-dialog.ts deleted file mode 100644 index 4a3f2afeca..0000000000 --- a/src/views-components/dialog-forms/file-selection-dialog.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { compose } from "redux"; -import { reduxForm } from 'redux-form'; -import { withDialog } from "~/store/dialog/with-dialog"; -import { FILE_SELECTION } from '~/store/file-selection/file-selection-actions'; -import { DialogFileSelection } from '~/views-components/dialog-file-selection/dialog-file-selection'; -import { dialogActions } from '~/store/dialog/dialog-actions'; - -export const FileSelectionDialog = compose( - withDialog(FILE_SELECTION), - reduxForm({ - form: FILE_SELECTION, - onSubmit: (data, dispatch) => { - console.log(data); - dispatch(dialogActions.CLOSE_DIALOG({ id: FILE_SELECTION })); - return data; - } - }) -)(DialogFileSelection); \ No newline at end of file diff --git a/src/views-components/file-tree-picker/file-tree-picker.tsx b/src/views-components/file-tree-picker/file-tree-picker.tsx deleted file mode 100644 index 129cb75015..0000000000 --- a/src/views-components/file-tree-picker/file-tree-picker.tsx +++ /dev/null @@ -1,173 +0,0 @@ -// 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 { Typography } from "@material-ui/core"; -import { MainFileTreePicker, MainFileTreePickerProps } from "./main-file-tree-picker"; -import { TreeItem, TreeItemStatus } from "~/components/tree/tree"; -import { ProjectResource } from "~/models/project"; -import { fileTreePickerActions } from "~/store/file-tree-picker/file-tree-picker-actions"; -import { ListItemTextIcon } from "~/components/list-item-text-icon/list-item-text-icon"; -import { ProjectIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon, CollectionIcon } from '~/components/icon/icon'; -import { createTreePickerNode } from "~/store/tree-picker/tree-picker"; -import { RootState } from "~/store/store"; -import { ServiceRepository } from "~/services/services"; -import { FilterBuilder } from "~/services/api/filter-builder"; -import { WrappedFieldProps } from 'redux-form'; -import { ResourceKind, extractUuidKind } from '~/models/resource'; -import { GroupContentsResource } from '~/services/groups-service/groups-service'; -import { loadCollectionFiles } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions'; - -type FileTreePickerProps = Pick; - -const mapDispatchToProps = (dispatch: Dispatch, props: { onChange: (projectUuid: string) => void }): FileTreePickerProps => ({ - onContextMenu: () => { return; }, - toggleItemActive: (nodeId, status, pickerId) => { - getNotSelectedTreePickerKind(pickerId) - .forEach(pickerId => dispatch(fileTreePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId: '', pickerId }))); - dispatch(fileTreePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId, pickerId })); - - props.onChange(nodeId); - }, - toggleItemOpen: (nodeId, status, pickerId) => { - dispatch(toggleItemOpen(nodeId, status, pickerId)); - } -}); - -const toggleItemOpen = (nodeId: string, status: TreeItemStatus, pickerId: string) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - if (status === TreeItemStatus.INITIAL) { - if (pickerId === TreePickerId.PROJECTS) { - dispatch(loadProjectTreePicker(nodeId)); - } else if (pickerId === TreePickerId.FAVORITES) { - dispatch(loadFavoriteTreePicker(nodeId === services.authService.getUuid() ? '' : nodeId)); - } else { - // TODO: load sharedWithMe - } - } else { - dispatch(fileTreePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId })); - } - }; - -const getNotSelectedTreePickerKind = (pickerId: string) => { - return [TreePickerId.PROJECTS, TreePickerId.FAVORITES, TreePickerId.SHARED_WITH_ME].filter(nodeId => nodeId !== pickerId); -}; - -enum TreePickerId { - PROJECTS = 'Projects', - SHARED_WITH_ME = 'Shared with me', - FAVORITES = 'Favorites' -} - -export const FileTreePicker = connect(undefined, mapDispatchToProps)((props: FileTreePickerProps) => -
-
- - - -
-
); - -export const loadProjectTreePicker = (nodeId: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(fileTreePickerActions.LOAD_TREE_PICKER_NODE({ nodeId, pickerId: TreePickerId.PROJECTS })); - - const ownerUuid = nodeId.length === 0 ? services.authService.getUuid() || '' : nodeId; - - const filters = new FilterBuilder() - .addIsA("uuid", [ResourceKind.PROJECT, ResourceKind.COLLECTION]) - .addEqual('ownerUuid', ownerUuid) - .getFilters(); - - // TODO: loadfiles from collections - const { items } = (extractUuidKind(nodeId) === ResourceKind.COLLECTION) - ? dispatch(loadCollectionFiles(nodeId)) - : await services.groupsService.contents(ownerUuid, { filters }); - - await dispatch(receiveTreePickerData(nodeId, items, TreePickerId.PROJECTS)); - }; - -export const loadFavoriteTreePicker = (nodeId: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const parentId = services.authService.getUuid() || ''; - - if (nodeId === '') { - dispatch(fileTreePickerActions.LOAD_TREE_PICKER_NODE({ nodeId: parentId, pickerId: TreePickerId.FAVORITES })); - const { items } = await services.favoriteService.list(parentId); - - dispatch(receiveTreePickerData(parentId, items as ProjectResource[], TreePickerId.FAVORITES)); - } else { - dispatch(fileTreePickerActions.LOAD_TREE_PICKER_NODE({ nodeId, pickerId: TreePickerId.FAVORITES })); - const filters = new FilterBuilder() - .addEqual('ownerUuid', nodeId) - .getFilters(); - - const { items } = (extractUuidKind(nodeId) === ResourceKind.COLLECTION) - ? dispatch(loadCollectionFiles(nodeId)) - : await services.groupsService.contents(parentId, { filters }); - - - dispatch(receiveTreePickerData(nodeId, items, TreePickerId.FAVORITES)); - } - }; - -const getProjectPickerIcon = (item: TreeItem) => { - switch (item.data.name) { - case TreePickerId.FAVORITES: - return FavoriteIcon; - case TreePickerId.PROJECTS: - return ProjectsIcon; - case TreePickerId.SHARED_WITH_ME: - return ShareMeIcon; - default: - return getResourceIcon(item); - } -}; - -const getResourceIcon = (item: TreeItem) => { - switch (item.data.kind) { - case ResourceKind.COLLECTION: - return CollectionIcon; - case ResourceKind.PROJECT: - return ProjectIcon; - default: - return ProjectIcon; - } -}; - -const renderTreeItem = (item: TreeItem) => - ; - - -export const receiveTreePickerData = (nodeId: string, items: GroupContentsResource[] = [], pickerId: string) => - (dispatch: Dispatch) => { - dispatch(fileTreePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ - nodeId, - nodes: items.map(item => createTreePickerNode({ nodeId: item.uuid, value: item })), - pickerId, - })); - - dispatch(fileTreePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId })); - }; - -export const FileTreePickerField = (props: WrappedFieldProps) => -
- - {props.meta.dirty && props.meta.error && - - {props.meta.error} - } -
; - -const handleChange = (props: WrappedFieldProps) => (value: string) => - props.input.value === value - ? props.input.onChange('') - : props.input.onChange(value); - diff --git a/src/views-components/file-tree-picker/main-file-tree-picker.ts b/src/views-components/file-tree-picker/main-file-tree-picker.ts deleted file mode 100644 index dc52ea6c53..0000000000 --- a/src/views-components/file-tree-picker/main-file-tree-picker.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { connect } from "react-redux"; -import { Tree, TreeProps, TreeItem, TreeItemStatus } from "~/components/tree/tree"; -import { RootState } from "~/store/store"; -import { createTreePickerNode, TreePickerNode } from "~/store/file-tree-picker/file-tree-picker"; -import { getNodeValue, getNodeChildrenIds, Tree as Ttree, createTree } from "~/models/tree"; -import { Dispatch } from "redux"; - -export interface MainFileTreePickerProps { - pickerId: string; - onContextMenu: (event: React.MouseEvent, nodeId: string, pickerId: string) => void; - toggleItemOpen: (nodeId: string, status: TreeItemStatus, pickerId: string) => void; - toggleItemActive: (nodeId: string, status: TreeItemStatus, pickerId: string) => void; -} - -const memoizedMapStateToProps = () => { - let prevTree: Ttree; - let mappedProps: Pick, 'items'>; - return (state: RootState, props: MainFileTreePickerProps): Pick, 'items'> => { - const tree = state.treePicker[props.pickerId] || createTree(); - if(tree !== prevTree){ - prevTree = tree; - mappedProps = { - items: getNodeChildrenIds('')(tree) - .map(treePickerToTreeItems(tree)) - }; - } - return mappedProps; - }; -}; - -const mapDispatchToProps = (dispatch: Dispatch, props: MainFileTreePickerProps): Pick, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive'> => ({ - onContextMenu: (event, item) => props.onContextMenu(event, item.id, props.pickerId), - toggleItemActive: (id, status) => props.toggleItemActive(id, status, props.pickerId), - toggleItemOpen: (id, status) => props.toggleItemOpen(id, status, props.pickerId) -}); - -export const MainFileTreePicker = connect(memoizedMapStateToProps(), mapDispatchToProps)(Tree); - -const treePickerToTreeItems = (tree: Ttree) => - (id: string): TreeItem => { - const node: TreePickerNode = getNodeValue(id)(tree) || createTreePickerNode({ nodeId: '', value: 'InvalidNode' }); - const items = getNodeChildrenIds(node.nodeId)(tree) - .map(treePickerToTreeItems(tree)); - return { - active: node.selected, - data: node.value, - id: node.nodeId, - items: items.length > 0 ? items : undefined, - open: !node.collapsed, - status: node.status - }; - }; - diff --git a/src/views-components/project-tree-picker/project-tree-picker.tsx b/src/views-components/project-tree-picker/project-tree-picker.tsx index 9139ee7c20..16ab12284b 100644 --- a/src/views-components/project-tree-picker/project-tree-picker.tsx +++ b/src/views-components/project-tree-picker/project-tree-picker.tsx @@ -12,40 +12,43 @@ 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, FavoriteIcon, ProjectsIcon, ShareMeIcon } from "~/components/icon/icon"; -import { createTreePickerNode } from "~/store/tree-picker/tree-picker"; import { RootState } from "~/store/store"; import { ServiceRepository } from "~/services/services"; import { FilterBuilder } from "~/services/api/filter-builder"; import { WrappedFieldProps } from 'redux-form'; +import { initTreeNode } from '~/models/tree'; -type ProjectTreePickerProps = Pick; +type ProjectTreePickerProps = Pick; const mapDispatchToProps = (dispatch: Dispatch, props: { onChange: (projectUuid: string) => void }): ProjectTreePickerProps => ({ onContextMenu: () => { return; }, - toggleItemActive: (nodeId, status, pickerId) => { + toggleItemActive: (id, status, pickerId) => { getNotSelectedTreePickerKind(pickerId) - .forEach(pickerId => dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId: '', pickerId }))); - dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECT({ nodeId, pickerId })); + .forEach(pickerId => dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: '', pickerId }))); + dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id, pickerId })); - props.onChange(nodeId); + props.onChange(id); + }, + toggleItemOpen: (id, status, pickerId) => { + dispatch(toggleItemOpen(id, status, pickerId)); + }, + toggleItemSelection: (id, pickerId) => { + dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id, pickerId })); }, - toggleItemOpen: (nodeId, status, pickerId) => { - dispatch(toggleItemOpen(nodeId, status, pickerId)); - } }); -const toggleItemOpen = (nodeId: string, status: TreeItemStatus, pickerId: string) => +const toggleItemOpen = (id: string, status: TreeItemStatus, pickerId: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { if (status === TreeItemStatus.INITIAL) { if (pickerId === TreePickerId.PROJECTS) { - dispatch(loadProjectTreePickerProjects(nodeId)); + dispatch(loadProjectTreePickerProjects(id)); } else if (pickerId === TreePickerId.FAVORITES) { - dispatch(loadFavoriteTreePickerProjects(nodeId === services.authService.getUuid() ? '' : nodeId)); + dispatch(loadFavoriteTreePickerProjects(id === services.authService.getUuid() ? '' : id)); } else { // TODO: load sharedWithMe } } else { - dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId })); + dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId })); } }; @@ -73,11 +76,11 @@ export const ProjectTreePicker = connect(undefined, mapDispatchToProps)((props: // TODO: move action creator to store directory -export const loadProjectTreePickerProjects = (nodeId: string) => +export const loadProjectTreePickerProjects = (id: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId, pickerId: TreePickerId.PROJECTS })); + dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId: TreePickerId.PROJECTS })); - const ownerUuid = nodeId.length === 0 ? services.authService.getUuid() || '' : nodeId; + const ownerUuid = id.length === 0 ? services.authService.getUuid() || '' : id; const filters = new FilterBuilder() .addEqual('ownerUuid', ownerUuid) @@ -85,27 +88,27 @@ export const loadProjectTreePickerProjects = (nodeId: string) => const { items } = await services.projectService.list({ filters }); - dispatch(receiveTreePickerData(nodeId, items, TreePickerId.PROJECTS)); + dispatch(receiveTreePickerData(id, items, TreePickerId.PROJECTS)); }; -export const loadFavoriteTreePickerProjects = (nodeId: string) => +export const loadFavoriteTreePickerProjects = (id: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const parentId = services.authService.getUuid() || ''; - if (nodeId === '') { - dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId: parentId, pickerId: TreePickerId.FAVORITES })); + if (id === '') { + dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id: parentId, pickerId: TreePickerId.FAVORITES })); const { items } = await services.favoriteService.list(parentId); dispatch(receiveTreePickerData(parentId, items as ProjectResource[], TreePickerId.FAVORITES)); } else { - dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ nodeId, pickerId: TreePickerId.FAVORITES })); + dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId: TreePickerId.FAVORITES })); const filters = new FilterBuilder() - .addEqual('ownerUuid', nodeId) + .addEqual('ownerUuid', id) .getFilters(); const { items } = await services.projectService.list({ filters }); - dispatch(receiveTreePickerData(nodeId, items, TreePickerId.FAVORITES)); + dispatch(receiveTreePickerData(id, items, TreePickerId.FAVORITES)); } }; @@ -132,15 +135,15 @@ const renderTreeItem = (item: TreeItem) => // TODO: move action creator to store directory -export const receiveTreePickerData = (nodeId: string, projects: ProjectResource[], pickerId: string) => +export const receiveTreePickerData = (id: string, projects: ProjectResource[], pickerId: string) => (dispatch: Dispatch) => { dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ - nodeId, - nodes: projects.map(project => createTreePickerNode({ nodeId: project.uuid, value: project })), + id, + nodes: projects.map(project => initTreeNode({ id: project.uuid, value: project })), pickerId, })); - dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ nodeId, pickerId })); + dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId })); }; export const ProjectTreePickerField = (props: WrappedFieldProps) => diff --git a/src/views-components/side-panel-tree/side-panel-tree.tsx b/src/views-components/side-panel-tree/side-panel-tree.tsx index 4d4760fac3..96224cfa79 100644 --- a/src/views-components/side-panel-tree/side-panel-tree.tsx +++ b/src/views-components/side-panel-tree/side-panel-tree.tsx @@ -13,13 +13,13 @@ import { ProjectIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon, TrashIcon } from import { RecentIcon, WorkflowIcon } from '~/components/icon/icon'; import { activateSidePanelTreeItem, toggleSidePanelTreeItemCollapse, SIDE_PANEL_TREE, SidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions'; import { openSidePanelContextMenu } from '~/store/context-menu/context-menu-actions'; - +import { noop } from 'lodash'; export interface SidePanelTreeProps { onItemActivation: (id: string) => void; sidePanelProgress?: boolean; } -type SidePanelTreeActionProps = Pick; +type SidePanelTreeActionProps = Pick; const mapDispatchToProps = (dispatch: Dispatch, props: SidePanelTreeProps): SidePanelTreeActionProps => ({ onContextMenu: (event, id) => { @@ -31,7 +31,8 @@ const mapDispatchToProps = (dispatch: Dispatch, props: SidePanelTreeProps): Side }, toggleItemOpen: (nodeId) => { dispatch(toggleSidePanelTreeItemCollapse(nodeId)); - } + }, + toggleItemSelection: noop, }); export const SidePanelTree = connect(undefined, mapDispatchToProps)( diff --git a/src/views-components/tree-picker/tree-picker.ts b/src/views-components/tree-picker/tree-picker.ts index 8b7630ab8c..ff119132e2 100644 --- a/src/views-components/tree-picker/tree-picker.ts +++ b/src/views-components/tree-picker/tree-picker.ts @@ -5,23 +5,24 @@ import { connect } from "react-redux"; import { Tree, TreeProps, TreeItem, TreeItemStatus } from "~/components/tree/tree"; import { RootState } from "~/store/store"; -import { createTreePickerNode, TreePickerNode } from "~/store/tree-picker/tree-picker"; -import { getNodeValue, getNodeChildrenIds, Tree as Ttree, createTree } from "~/models/tree"; +import { getNodeValue, getNodeChildrenIds, Tree as Ttree, createTree, getNode, TreeNodeStatus } from '~/models/tree'; import { Dispatch } from "redux"; +import { initTreeNode } from '../../models/tree'; export interface TreePickerProps { pickerId: string; onContextMenu: (event: React.MouseEvent, nodeId: string, pickerId: string) => void; toggleItemOpen: (nodeId: string, status: TreeItemStatus, pickerId: string) => void; toggleItemActive: (nodeId: string, status: TreeItemStatus, pickerId: string) => void; + toggleItemSelection: (nodeId: string, pickerId: string) => void; } const memoizedMapStateToProps = () => { - let prevTree: Ttree; + let prevTree: Ttree; let mappedProps: Pick, 'items'>; return (state: RootState, props: TreePickerProps): Pick, 'items'> => { const tree = state.treePicker[props.pickerId] || createTree(); - if(tree !== prevTree){ + if (tree !== prevTree) { prevTree = tree; mappedProps = { items: getNodeChildrenIds('')(tree) @@ -32,26 +33,39 @@ const memoizedMapStateToProps = () => { }; }; -const mapDispatchToProps = (dispatch: Dispatch, props: TreePickerProps): Pick, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive'> => ({ +const mapDispatchToProps = (dispatch: Dispatch, props: TreePickerProps): Pick, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive' | 'toggleItemSelection'> => ({ onContextMenu: (event, item) => props.onContextMenu(event, item.id, props.pickerId), toggleItemActive: (id, status) => props.toggleItemActive(id, status, props.pickerId), - toggleItemOpen: (id, status) => props.toggleItemOpen(id, status, props.pickerId) + toggleItemOpen: (id, status) => props.toggleItemOpen(id, status, props.pickerId), + toggleItemSelection: (_, item) => props.toggleItemSelection(item.id, props.pickerId), }); export const TreePicker = connect(memoizedMapStateToProps(), mapDispatchToProps)(Tree); -const treePickerToTreeItems = (tree: Ttree) => +const treePickerToTreeItems = (tree: Ttree) => (id: string): TreeItem => { - const node: TreePickerNode = getNodeValue(id)(tree) || createTreePickerNode({ nodeId: '', value: 'InvalidNode' }); - const items = getNodeChildrenIds(node.nodeId)(tree) + const node = getNode(id)(tree) || initTreeNode({ id: '', value: 'InvalidNode' }); + const items = getNodeChildrenIds(node.id)(tree) .map(treePickerToTreeItems(tree)); return { - active: node.selected, + active: node.active, data: node.value, - id: node.nodeId, + id: node.id, items: items.length > 0 ? items : undefined, - open: !node.collapsed, - status: node.status + open: node.expanded, + selected: node.selected, + status: treeNodeStatusToTreeItem(node.status), }; }; +export const treeNodeStatusToTreeItem = (status: TreeNodeStatus) => { + switch (status) { + case TreeNodeStatus.INITIAL: + return TreeItemStatus.INITIAL; + case TreeNodeStatus.PENDING: + return TreeItemStatus.PENDING; + case TreeNodeStatus.LOADED: + return TreeItemStatus.LOADED; + } +}; + diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 553afa4d09..49ce4725bb 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -41,7 +41,6 @@ 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 { FileSelectionDialog } from '~/views-components/dialog-forms/file-selection-dialog'; type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content'; @@ -114,8 +113,6 @@ export const WorkbenchPanel = - - -- 2.39.5