onContextMenu={this.handleContextMenu}
toggleItemActive={this.handleToggleActive}
toggleItemOpen={this.handleToggle}
- onSelectionChange={this.handleSelectionChange} />;
+ toggleItemSelection={this.handleSelectionChange} />;
}
handleContextMenu = (event: React.MouseEvent<any>, item: TreeItem<FileTreeData>) => {
this.props.onMenuOpen(event, item);
}
- handleToggle = (id: string, status: TreeItemStatus) => {
+ handleToggle = (event: React.MouseEvent<{}>, { id, status }: TreeItem<{}>) => {
this.props.onCollapseToggle(id, status);
}
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"],
}
});
}
export interface TreeProps<T> {
+ disableRipple?: boolean;
items?: Array<TreeItem<T>>;
- render: (item: TreeItem<T>, level?: number) => ReactElement<{}>;
- toggleItemOpen: (id: string, status: TreeItemStatus) => void;
- toggleItemActive: (id: string, status: TreeItemStatus) => void;
level?: number;
onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
+ render: (item: TreeItem<T>, level?: number) => ReactElement<{}>;
showSelection?: boolean;
- onSelectionChange?: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
- disableRipple?: boolean;
+ toggleItemActive: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
+ toggleItemOpen: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
+ toggleItemSelection?: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
}
export const Tree = withStyles(styles)(
<div key={`item/${level}/${idx}`}>
<ListItem button className={listItem} style={{ paddingLeft: (level + 1) * 20 }}
disableRipple={disableRipple}
- onClick={() => toggleItemActive(it.id, it.status)}
+ onClick={event => toggleItemActive(event, it)}
onContextMenu={this.handleRowContextMenu(it)}>
{it.status === TreeItemStatus.PENDING ?
<CircularProgress size={10} className={loader} /> : null}
- <i onClick={this.handleToggleItemOpen(it.id, it.status)}
+ <i onClick={this.handleToggleItemOpen(it)}
className={toggableIconContainer}>
<ListItemIcon className={this.getToggableIconClassNames(it.open, it.active)}>
{this.getProperArrowAnimation(it.status, it.items!)}
toggleItemActive={toggleItemActive}
level={level + 1}
onContextMenu={onContextMenu}
- onSelectionChange={this.props.onSelectionChange} />
+ toggleItemSelection={this.props.toggleItemSelection} />
</Collapse>}
</div>)}
</List>;
this.props.onContextMenu(event, item)
handleCheckboxChange = (item: TreeItem<T>) => {
- const { onSelectionChange } = this.props;
- return onSelectionChange
+ const { toggleItemSelection } = this.props;
+ return toggleItemSelection
? (event: React.MouseEvent<HTMLElement>) => {
- onSelectionChange(event, item);
+ event.stopPropagation();
+ toggleItemSelection(event, item);
}
: undefined;
}
- handleToggleItemOpen = (id: string, status: TreeItemStatus) => (event: React.MouseEvent<HTMLElement>) => {
+ handleToggleItemOpen = (item: TreeItem<T>) => (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation();
- this.props.toggleItemOpen(id, status);
+ this.props.toggleItemOpen(event, item);
}
}
);
import { setUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions';
import { trashedCollectionActionSet } from '~/views-components/context-menu/action-sets/trashed-collection-action-set';
import { ContainerRequestState } from '~/models/container-request';
-import { MountKind } from './models/mount-types';
+import { MountKind } from '~/models/mount-types';
+import { receiveTreePickerData, loadUserProject } from '~/store/tree-picker/tree-picker-actions';
+import { loadProject, loadCollection, initUserProject } from './store/tree-picker/tree-picker-actions';
+import { ResourceKind } from '~/models/resource';
const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev");
const getGitCommit = () => "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substr(0, 7);
await store.dispatch(loadWorkbench());
addRouteChangeHandlers(history, store);
// createEnumCollectorWorkflow(services);
+ store.dispatch(initUserProject('testPicker1'));
+ store.dispatch(initUserProject('testPicker2'));
+ store.dispatch(initUserProject('testPicker3'));
+ // await store.dispatch(loadCollection(
+ // 'c97qk-4zz18-9sn8ygaf62chkkd',
+ // 'testPicker',
+ // ));
}
};
};
-const createPrimitivesCollectorWorkflow = ({workflowService}:ServiceRepository) => {
+const createPrimitivesCollectorWorkflow = ({ workflowService }: ServiceRepository) => {
workflowService.create({
- name: 'Primitive values collector',
- description: 'Workflow for collecting primitive values',
- definition: "cwlVersion: v1.0\n$graph:\n- class: CommandLineTool\n requirements:\n - listing:\n - entryname: input_collector.log\n entry: |\n \"flag\":\n $(inputs.example_flag)\n \"string\":\n $(inputs.example_string)\n \"int\":\n $(inputs.example_int)\n \"long\":\n $(inputs.example_long)\n \"float\":\n $(inputs.example_float)\n \"double\":\n $(inputs.example_double)\n class: InitialWorkDirRequirement\n inputs:\n - type: double\n id: '#input_collector.cwl/example_double'\n - type: boolean\n id: '#input_collector.cwl/example_flag'\n - type: float\n id: '#input_collector.cwl/example_float'\n - type: int\n id: '#input_collector.cwl/example_int'\n - type: long\n id: '#input_collector.cwl/example_long'\n - type: string\n id: '#input_collector.cwl/example_string'\n outputs:\n - type: File\n outputBinding:\n glob: '*'\n id: '#input_collector.cwl/output'\n baseCommand: [echo]\n id: '#input_collector.cwl'\n- class: Workflow\n doc: Workflw for collecting primitive values\n inputs:\n - type: double\n label: Double value\n doc: This should allow for entering a decimal number (64-bit).\n id: '#main/example_double'\n default: 0.3333333333333333\n - type: boolean\n label: Boolean Flag\n doc: This should render as in checkbox.\n id: '#main/example_flag'\n default: true\n - type: float\n label: Float value\n doc: This should allow for entering a decimal number (32-bit).\n id: '#main/example_float'\n default: 0.15625\n - type: int\n label: Integer Number\n doc: This should allow for entering a number (32-bit signed).\n id: '#main/example_int'\n default: 2147483647\n - type: long\n label: Long Number\n doc: This should allow for entering a number (64-bit signed).\n id: '#main/example_long'\n default: 9223372036854775807\n - type: string\n label: Freetext\n doc: This should allow for entering an arbitrary char sequence.\n id: '#main/example_string'\n default: This is a string\n outputs:\n - type: File\n outputSource: '#main/input_collector/output'\n id: '#main/log_file'\n steps:\n - run: '#input_collector.cwl'\n in:\n - source: '#main/example_double'\n id: '#main/input_collector/example_double'\n - source: '#main/example_flag'\n id: '#main/input_collector/example_flag'\n - source: '#main/example_float'\n id: '#main/input_collector/example_float'\n - source: '#main/example_int'\n id: '#main/input_collector/example_int'\n - source: '#main/example_long'\n id: '#main/input_collector/example_long'\n - source: '#main/example_string'\n id: '#main/input_collector/example_string'\n out: ['#main/input_collector/output']\n id: '#main/input_collector'\n id: '#main'\n",
- });
+ name: 'Primitive values collector',
+ description: 'Workflow for collecting primitive values',
+ definition: "cwlVersion: v1.0\n$graph:\n- class: CommandLineTool\n requirements:\n - listing:\n - entryname: input_collector.log\n entry: |\n \"flag\":\n $(inputs.example_flag)\n \"string\":\n $(inputs.example_string)\n \"int\":\n $(inputs.example_int)\n \"long\":\n $(inputs.example_long)\n \"float\":\n $(inputs.example_float)\n \"double\":\n $(inputs.example_double)\n class: InitialWorkDirRequirement\n inputs:\n - type: double\n id: '#input_collector.cwl/example_double'\n - type: boolean\n id: '#input_collector.cwl/example_flag'\n - type: float\n id: '#input_collector.cwl/example_float'\n - type: int\n id: '#input_collector.cwl/example_int'\n - type: long\n id: '#input_collector.cwl/example_long'\n - type: string\n id: '#input_collector.cwl/example_string'\n outputs:\n - type: File\n outputBinding:\n glob: '*'\n id: '#input_collector.cwl/output'\n baseCommand: [echo]\n id: '#input_collector.cwl'\n- class: Workflow\n doc: Workflw for collecting primitive values\n inputs:\n - type: double\n label: Double value\n doc: This should allow for entering a decimal number (64-bit).\n id: '#main/example_double'\n default: 0.3333333333333333\n - type: boolean\n label: Boolean Flag\n doc: This should render as in checkbox.\n id: '#main/example_flag'\n default: true\n - type: float\n label: Float value\n doc: This should allow for entering a decimal number (32-bit).\n id: '#main/example_float'\n default: 0.15625\n - type: int\n label: Integer Number\n doc: This should allow for entering a number (32-bit signed).\n id: '#main/example_int'\n default: 2147483647\n - type: long\n label: Long Number\n doc: This should allow for entering a number (64-bit signed).\n id: '#main/example_long'\n default: 9223372036854775807\n - type: string\n label: Freetext\n doc: This should allow for entering an arbitrary char sequence.\n id: '#main/example_string'\n default: This is a string\n outputs:\n - type: File\n outputSource: '#main/input_collector/output'\n id: '#main/log_file'\n steps:\n - run: '#input_collector.cwl'\n in:\n - source: '#main/example_double'\n id: '#main/input_collector/example_double'\n - source: '#main/example_flag'\n id: '#main/input_collector/example_flag'\n - source: '#main/example_float'\n id: '#main/input_collector/example_float'\n - source: '#main/example_int'\n id: '#main/input_collector/example_int'\n - source: '#main/example_long'\n id: '#main/input_collector/example_long'\n - source: '#main/example_string'\n id: '#main/input_collector/example_string'\n out: ['#main/input_collector/output']\n id: '#main/input_collector'\n id: '#main'\n",
+ });
};
-const createEnumCollectorWorkflow = ({workflowService}:ServiceRepository) => {
+const createEnumCollectorWorkflow = ({ workflowService }: ServiceRepository) => {
workflowService.create({
- name: 'Enum values collector',
- description: 'Workflow for collecting enum values',
- definition: "cwlVersion: v1.0\n$graph:\n- class: CommandLineTool\n requirements:\n - listing:\n - entryname: input_collector.log\n entry: |\n \"enum_type\":\n $(inputs.enum_type)\n\n class: InitialWorkDirRequirement\n inputs:\n - type:\n type: enum\n symbols: ['#input_collector.cwl/enum_type/OTU table', '#input_collector.cwl/enum_type/Pathway\n table', '#input_collector.cwl/enum_type/Function table', '#input_collector.cwl/enum_type/Ortholog\n table']\n id: '#input_collector.cwl/enum_type'\n outputs:\n - type: File\n outputBinding:\n glob: '*'\n id: '#input_collector.cwl/output'\n baseCommand: [echo]\n id: '#input_collector.cwl'\n- class: Workflow\n doc: This is the description of the workflow\n inputs:\n - type:\n type: enum\n symbols: ['#main/enum_type/OTU table', '#main/enum_type/Pathway table', '#main/enum_type/Function\n table', '#main/enum_type/Ortholog table']\n name: '#enum_typef4179c7f-45f9-482d-a5db-1abb86698384'\n label: Enumeration Type\n doc: This should render as a drop-down menu.\n id: '#main/enum_type'\n default: OTU table\n outputs:\n - type: File\n outputSource: '#main/input_collector/output'\n id: '#main/log_file'\n steps:\n - run: '#input_collector.cwl'\n in:\n - source: '#main/enum_type'\n id: '#main/input_collector/enum_type'\n out: ['#main/input_collector/output']\n id: '#main/input_collector'\n id: '#main'\n",
- });
+ name: 'Enum values collector',
+ description: 'Workflow for collecting enum values',
+ definition: "cwlVersion: v1.0\n$graph:\n- class: CommandLineTool\n requirements:\n - listing:\n - entryname: input_collector.log\n entry: |\n \"enum_type\":\n $(inputs.enum_type)\n\n class: InitialWorkDirRequirement\n inputs:\n - type:\n type: enum\n symbols: ['#input_collector.cwl/enum_type/OTU table', '#input_collector.cwl/enum_type/Pathway\n table', '#input_collector.cwl/enum_type/Function table', '#input_collector.cwl/enum_type/Ortholog\n table']\n id: '#input_collector.cwl/enum_type'\n outputs:\n - type: File\n outputBinding:\n glob: '*'\n id: '#input_collector.cwl/output'\n baseCommand: [echo]\n id: '#input_collector.cwl'\n- class: Workflow\n doc: This is the description of the workflow\n inputs:\n - type:\n type: enum\n symbols: ['#main/enum_type/OTU table', '#main/enum_type/Pathway table', '#main/enum_type/Function\n table', '#main/enum_type/Ortholog table']\n name: '#enum_typef4179c7f-45f9-482d-a5db-1abb86698384'\n label: Enumeration Type\n doc: This should render as a drop-down menu.\n id: '#main/enum_type'\n default: OTU table\n outputs:\n - type: File\n outputSource: '#main/input_collector/output'\n id: '#main/log_file'\n steps:\n - run: '#input_collector.cwl'\n in:\n - source: '#main/enum_type'\n id: '#main/input_collector/enum_type'\n out: ['#main/input_collector/output']\n id: '#main/input_collector'\n id: '#main'\n",
+ });
};
const createSampleProcess = ({ containerRequestService }: ServiceRepository) => {
"class": "CommandLineTool",
"requirements": [
{
- "listing": [
- {
- "entryname": "input_collector.log",
- "entry": "$(inputs.single_file.basename)\n"
- }
- ],
- "class": "InitialWorkDirRequirement"
+ "listing": [
+ {
+ "entryname": "input_collector.log",
+ "entry": "$(inputs.single_file.basename)\n"
+ }
+ ],
+ "class": "InitialWorkDirRequirement"
}
- ],
+ ],
"inputs": [
{
"type": "File",
"class": "File",
"location": "keep:233454526794c0a2d56a305baeff3d30+145/1.txt",
"basename": "fileA"
- }
+ }
},
}
},
//
// SPDX-License-Identifier: AGPL-3.0
-import { Tree, createTree, setNode } from './tree';
+import { Tree, createTree, setNode, TreeNodeStatus } from './tree';
export type CollectionFilesTree = Tree<CollectionDirectory | CollectionFile>;
children: [],
id: item.id,
parent: item.path,
- value: item
+ value: item,
+ active: false,
+ selected: false,
+ expanded: false,
+ status: TreeNodeStatus.INITIAL
+
})(tree), createTree<CollectionDirectory | CollectionFile>());
};
\ No newline at end of file
// SPDX-License-Identifier: AGPL-3.0
import * as Tree from './tree';
+import { initTreeNode } from './tree';
+import { pipe } from 'lodash/fp';
describe('Tree', () => {
let tree: Tree.Tree<string>;
});
it('sets new node', () => {
- const newTree = Tree.setNode({ children: [], id: 'Node 1', parent: '', value: 'Value 1' })(tree);
- expect(Tree.getNode('Node 1')(newTree)).toEqual({ children: [], id: 'Node 1', parent: '', value: 'Value 1' });
+ const newTree = Tree.setNode(initTreeNode({ id: 'Node 1', value: 'Value 1' }))(tree);
+ expect(Tree.getNode('Node 1')(newTree)).toEqual(initTreeNode({ id: 'Node 1', 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' }));
+ const newTree = pipe(
+ Tree.setNode(initTreeNode({ id: 'Node 1', parent: '', value: 'Value 1' })),
+ Tree.setNode(initTreeNode({ id: 'Node 2', parent: 'Node 1', value: 'Value 2' })),
+ )(tree);
- expect(Tree.getNode('Node 1')(newTree)).toEqual({ children: ['Node 2'], id: 'Node 1', parent: '', value: 'Value 1' });
+ expect(Tree.getNode('Node 1')(newTree)).toEqual({
+ ...initTreeNode({ id: 'Node 1', parent: '', value: 'Value 1' }),
+ children: ['Node 2']
+ });
});
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' }
+ initTreeNode({ id: 'Node 1', parent: '', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 2', parent: 'Node 1', value: 'Value 1' }),
+ initTreeNode({ 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' }
+ initTreeNode({ id: 'Node 1', parent: '', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 2', parent: 'Node 1', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 3', parent: 'Node 1', value: 'Value 1' }),
+ initTreeNode({ 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' }
+ initTreeNode({ id: 'Node 1', parent: '', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 2', parent: 'Node 1', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 3', parent: 'Node 1', value: 'Value 1' }),
+ initTreeNode({ 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' }
+ initTreeNode({ id: 'Node 1', parent: '', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 2', parent: 'Node 1', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 3', parent: 'Node 1', value: 'Value 1' }),
+ initTreeNode({ 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' }
+ initTreeNode({ id: 'Node 1', parent: '', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 2', parent: 'Node 1', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 2.1', parent: 'Node 2', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 3', parent: '', value: 'Value 1' }),
+ initTreeNode({ 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' },
+ initTreeNode({ id: 'Node 1', parent: '', value: 'Value 1' }),
+ initTreeNode({ id: 'Node 2', parent: 'Node 1', value: 'Value 2' }),
].reduce((tree, node) => Tree.setNode(node)(tree), tree);
const mappedTree = Tree.mapTreeValues<string, number>(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(initTreeNode({id: 'Node 2', parent: 'Node 1', value: 2 }));
});
-});
\ No newline at end of file
+});
//
// SPDX-License-Identifier: AGPL-3.0
+import { pipe } from 'lodash/fp';
export type Tree<T> = Record<string, TreeNode<T>>;
export const TREE_ROOT_ID = '';
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 = <T>(): Tree<T> => ({});
export const getNode = (id: string) => <T>(tree: Tree<T>): TreeNode<T> | undefined => tree[id];
export const setNode = <T>(node: TreeNode<T>) => (tree: Tree<T>): Tree<T> => {
- const [newTree] = [tree]
- .map(tree => getNode(node.id)(tree) === node
+ return pipe(
+ (tree: Tree<T>) => getNode(node.id)(tree) === node
? tree
- : { ...tree, [node.id]: node })
- .map(addChild(node.parent, node.id));
- return newTree;
+ : { ...tree, [node.id]: node },
+ addChild(node.parent, node.id)
+ )(tree);
};
export const getNodeValue = (id: string) => <T>(tree: Tree<T>) => {
export const mapIdsToNodes = (ids: string[]) => <T>(tree: Tree<T>) =>
ids.map(id => getNode(id)(tree)).filter((node): node is TreeNode<T> => node !== undefined);
+export const activateNode = (id: string) => <T>(tree: Tree<T>) =>
+ mapTree(node => node.id === id ? { ...node, active: true } : { ...node, active: false })(tree);
+
+
+export const expandNode = (...ids: string[]) => <T>(tree: Tree<T>) =>
+ mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: true } : node)(tree);
+
+export const collapseNode = (...ids: string[]) => <T>(tree: Tree<T>) =>
+ mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: false } : node)(tree);
+
+export const toggleNodeCollapse = (...ids: string[]) => <T>(tree: Tree<T>) =>
+ mapTree(node => ids.some(id => id === node.id) ? { ...node, expanded: !node.expanded } : node)(tree);
+
+export const setNodeStatus = (id: string) => (status: TreeNodeStatus) => <T>(tree: Tree<T>) => {
+ const node = getNode(id)(tree);
+ return node
+ ? setNode({ ...node, status })(tree)
+ : tree;
+};
+
+export const toggleNodeSelection = (id: string) => <T>(tree: Tree<T>) => {
+ const node = getNode(id)(tree);
+ return node
+ ? pipe(
+ setNode({ ...node, selected: !node.selected }),
+ toggleAncestorsSelection(id),
+ toggleDescendantsSelection(id))(tree)
+ : tree;
+
+};
+
+export const initTreeNode = <T>(data: Pick<TreeNode<T>, 'id' | 'value'> & {parent?: string}): TreeNode<T> => ({
+ children: [],
+ active: false,
+ selected: false,
+ expanded: false,
+ status: TreeNodeStatus.INITIAL,
+ parent: '',
+ ...data,
+});
+
+const toggleDescendantsSelection = (id: string) => <T>(tree: Tree<T>) => {
+ 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) => <T>(tree: Tree<T>) => {
+ const ancestors = getNodeAncestorsIds(id)(tree).reverse();
+ return ancestors.reduce((newTree, parent) => parent ? toggleParentNodeSelection(parent)(newTree) : newTree, tree);
+};
+
+const toggleParentNodeSelection = (id: string) => <T>(tree: Tree<T>) => {
+ 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 = <T, R>(mapFn: (value: T) => R) => (node: TreeNode<T>): TreeNode<R> =>
({ ...node, value: mapFn(node.value) });
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 => {
children: [],
id: file.id,
parent: file.path,
- value: file
+ value: file,
+ active: false,
+ selected: false,
+ expanded: false,
+ status: TreeNodeStatus.INITIAL,
});
export const manifestToCollectionFiles = (manifest: KeepManifest): Array<CollectionDirectory | CollectionFile> => ([
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 });
};
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', () => {
children: [],
id: file.id,
parent: file.path,
- value: file
+ value: file,
+ active: false,
+ selected: false,
+ expanded: false,
+ status: TreeNodeStatus.INITIAL,
})(tree), createTree<CollectionFile | CollectionDirectory>());
const collectionPanelFilesTree = collectionPanelFilesReducer(
+++ /dev/null
-// 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<TreePickerNode>, 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<typeof fileTreePickerActions>;
+++ /dev/null
-// 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<TreePickerNode>) => Tree<TreePickerNode>) => {
- 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<TreePickerNode>) => (parent: string) => (state: Tree<TreePickerNode>) => {
- 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<TreePickerNode> => ({
- children: [],
- id: node.nodeId,
- parent,
- value: node
-});
+++ /dev/null
-// 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<TreePickerNode> };
-
-export interface TreePickerNode<Value = any> {
- 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 = <Value = {}>(id: string) => (state: TreePicker): Tree<TreePickerNode<Value>> | undefined => state[id];
\ No newline at end of file
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';
export const getSidePanelTree = (treePicker: TreePicker) =>
getTreePicker<ProjectResource | string>(SIDE_PANEL_TREE)(treePicker);
-export const getSidePanelTreeBranch = (uuid: string) => (treePicker: TreePicker): Array<TreePickerNode<ProjectResource | string>> => {
+export const getSidePanelTreeBranch = (uuid: string) => (treePicker: TreePicker): Array<TreeNode<ProjectResource | string>> => {
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];
}
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: []
}));
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)
};
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<any>(activateSidePanelTreeProject(nodeId));
+ if (!isSidePanelTreeCategory(id)) {
+ await dispatch<any>(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<any>(loadSidePanelTreeProjects(nodeId));
+ const node = getSidePanelTreeNode(id)(treePicker);
+ if (node && node.status !== TreeNodeStatus.LOADED) {
+ await dispatch<any>(loadSidePanelTreeProjects(id));
} else if (node === undefined) {
- await dispatch<any>(activateSidePanelTreeBranch(nodeId));
+ await dispatch<any>(activateSidePanelTreeBranch(id));
}
dispatch(treePickerActions.EXPAND_TREE_PICKER_NODES({
- nodeIds: getSidePanelTreeNodeAncestorsIds(nodeId)(treePicker),
+ ids: getSidePanelTreeNodeAncestorsIds(id)(treePicker),
pickerId: SIDE_PANEL_TREE
}));
- dispatch<any>(expandSidePanelTreeItem(nodeId));
+ dispatch<any>(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<any>(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<any>(loadSidePanelTreeProjects(node.nodeId));
+ const node = getSidePanelTreeNode(id)(getState().treePicker);
+ if (node && node.status === TreeNodeStatus.INITIAL) {
+ await dispatch<any>(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)
: [];
};
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';
import { searchBarReducer } from './search-bar/search-bar-reducer';
const composeEnhancers =
fileUploader: fileUploaderReducer,
processPanel: processPanelReducer,
progressIndicator: progressIndicatorReducer,
- fileTreePicker: fileTreePickerReducer,
runProcessPanel: runProcessPanelReducer,
searchBar: searchBarReducer
});
// SPDX-License-Identifier: AGPL-3.0
import { unionize, ofType, UnionOf } from "~/common/unionize";
-
-import { TreePickerNode } from "./tree-picker";
+import { TreeNode, initTreeNode, getNodeDescendants, getNodeDescendantsIds, getNodeValue, TreeNodeStatus } from '~/models/tree';
+import { Dispatch } from 'redux';
+import { RootState } from '~/store/store';
+import { ServiceRepository } from '~/services/services';
+import { FilterBuilder } from '~/services/api/filter-builder';
+import { pipe } from 'lodash/fp';
+import { ResourceKind } from '~/models/resource';
+import { GroupContentsResource } from '../../services/groups-service/groups-service';
+import { CollectionDirectory, CollectionFile } from '../../models/collection-file';
export const treePickerActions = unionize({
- LOAD_TREE_PICKER_NODE: ofType<{ nodeId: string, pickerId: string }>(),
- LOAD_TREE_PICKER_NODE_SUCCESS: ofType<{ nodeId: string, nodes: Array<TreePickerNode>, 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<TreeNode<any>>, 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 }>()
});
export type TreePickerAction = UnionOf<typeof treePickerActions>;
+
+interface ReceiveTreePickerDataParams<T> {
+ data: T[];
+ extractNodeData: (value: T) => { id: string, value: T, status?: TreeNodeStatus };
+ id: string;
+ pickerId: string;
+}
+export const receiveTreePickerData = <T>(params: ReceiveTreePickerDataParams<T>) =>
+ (dispatch: Dispatch) => {
+ const { data, extractNodeData, id, pickerId, } = params;
+ dispatch(treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
+ id,
+ nodes: data.map(item => initTreeNode(extractNodeData(item))),
+ pickerId,
+ }));
+ dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
+ };
+export const loadProject = (id: string, pickerId: string, includeCollections = false, includeFiles = false) =>
+ async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => {
+
+ dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId }));
+
+ const filters = pipe(
+ (fb: FilterBuilder) => fb.addEqual('ownerUuid', id),
+ fb => includeCollections
+ ? fb.addIsA('uuid', [ResourceKind.PROJECT, ResourceKind.COLLECTION])
+ : fb.addIsA('uuid', [ResourceKind.PROJECT]),
+ fb => fb.getFilters(),
+ )(new FilterBuilder());
+
+ const { items } = await services.groupsService.contents(id, { filters });
+
+ dispatch<any>(receiveTreePickerData<GroupContentsResource>({
+ id,
+ pickerId,
+ data: items,
+ extractNodeData: item => ({
+ id: item.uuid,
+ value: item,
+ status: item.kind === ResourceKind.PROJECT
+ ? TreeNodeStatus.INITIAL
+ : includeFiles
+ ? TreeNodeStatus.INITIAL
+ : TreeNodeStatus.LOADED
+ }),
+ }));
+ };
+
+export const loadCollection = (id: string, pickerId: string) =>
+ async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => {
+ dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId }));
+
+ const files = await services.collectionService.files(id);
+ const data = getNodeDescendants('')(files).map(node => node.value);
+
+ dispatch<any>(receiveTreePickerData<CollectionDirectory | CollectionFile>({
+ id,
+ pickerId,
+ data,
+ extractNodeData: value => ({
+ id: value.id,
+ status: TreeNodeStatus.LOADED,
+ value,
+ }),
+ }));
+ };
+
+
+export const initUserProject = (pickerId: string) =>
+ async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ const uuid = services.authService.getUuid();
+ if (uuid) {
+ dispatch(receiveTreePickerData({
+ id: '',
+ pickerId,
+ data: [{ uuid, name: 'Projects' }],
+ extractNodeData: value => ({
+ id: value.uuid,
+ status: TreeNodeStatus.INITIAL,
+ value,
+ }),
+ }));
+ }
+ };
+export const loadUserProject = (pickerId: string, includeCollections = false, includeFiles = false) =>
+ async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ const uuid = services.authService.getUuid();
+ if (uuid) {
+ dispatch(loadProject(uuid, pickerId, includeCollections, includeFiles));
+ }
+ };
+
+
+export const initSharedProject = (pickerId: string) =>
+ async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(receiveTreePickerData({
+ id: '',
+ pickerId,
+ data: [{ uuid: 'Shared with me', name: 'Shared with me' }],
+ extractNodeData: value => ({
+ id: value.uuid,
+ status: TreeNodeStatus.INITIAL,
+ value,
+ }),
+ }));
+ };
//
// SPDX-License-Identifier: AGPL-3.0
-import { createTree, getNodeValue, getNodeChildrenIds } from "~/models/tree";
-import { TreePickerNode, createTreePickerNode } from "./tree-picker";
+import { createTree, getNodeChildrenIds, getNode, TreeNodeStatus } from '~/models/tree';
+import { pipe } from 'lodash/fp';
import { treePickerReducer } from "./tree-picker-reducer";
import { treePickerActions } from "./tree-picker-actions";
-import { TreeItemStatus } from "~/components/tree/tree";
+import { TreePicker } from './tree-picker';
+import { initTreeNode } from '~/models/tree';
describe('TreePickerReducer', () => {
it('LOAD_TREE_PICKER_NODE - initial state', () => {
- const tree = createTree<TreePickerNode>();
- 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 [newState] = [{
- projects: createTree<TreePickerNode>()
- }]
- .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" })));
+ const node = initTreeNode({ id: '1', value: '1' });
+ const newState = pipe(
+ (state: TreePicker) => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })),
+ state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE({ id: '1', pickerId: "projects" }))
+ )({ projects: createTree<{}>() });
- expect(getNodeValue('1')(newState.projects)).toEqual({
- ...createTreePickerNode({ nodeId: '1', value: '1' }),
- status: TreeItemStatus.PENDING
+ expect(getNode('1')(newState.projects)).toEqual({
+ ...initTreeNode({ id: '1', value: '1' }),
+ status: TreeNodeStatus.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 [newState] = [{
- projects: createTree<TreePickerNode>()
- }]
- .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" })));
+ const node = initTreeNode({ id: '1', value: '1' });
+ const subNode = initTreeNode({ id: '1.1', value: '1.1' });
+ const newState = pipe(
+ (state: TreePicker) => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })),
+ state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '1', nodes: [subNode], pickerId: "projects" }))
+ )({ projects: createTree<{}>() });
expect(getNodeChildrenIds('1')(newState.projects)).toEqual(['1.1']);
- expect(getNodeValue('1')(newState.projects)).toEqual({
- ...createTreePickerNode({ nodeId: '1', value: '1' }),
- status: TreeItemStatus.LOADED
+ expect(getNode('1')(newState.projects)).toEqual({
+ ...initTreeNode({ id: '1', value: '1' }),
+ children: ['1.1'],
+ status: TreeNodeStatus.LOADED
});
});
- it('TOGGLE_TREE_PICKER_NODE_COLLAPSE - collapsed', () => {
- const node = createTreePickerNode({ nodeId: '1', value: '1' });
- const [newState] = [{
- projects: createTree<TreePickerNode>()
- }]
- .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" })));
- expect(getNodeValue('1')(newState.projects)).toEqual({
- ...createTreePickerNode({ nodeId: '1', value: '1' }),
- collapsed: false
+ it('TOGGLE_TREE_PICKER_NODE_COLLAPSE - expanded', () => {
+ const node = initTreeNode({ id: '1', value: '1' });
+ const newState = pipe(
+ (state: TreePicker) => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })),
+ state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1', pickerId: "projects" }))
+ )({ projects: createTree<{}>() });
+ expect(getNode('1')(newState.projects)).toEqual({
+ ...initTreeNode({ id: '1', value: '1' }),
+ expanded: true
});
});
it('TOGGLE_TREE_PICKER_NODE_COLLAPSE - expanded', () => {
- const node = createTreePickerNode({ nodeId: '1', value: '1' });
- const [newState] = [{
- projects: createTree<TreePickerNode>()
- }]
- .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" })));
- expect(getNodeValue('1')(newState.projects)).toEqual({
- ...createTreePickerNode({ nodeId: '1', value: '1' }),
- collapsed: true
+ const node = initTreeNode({ id: '1', value: '1' });
+ const newState = pipe(
+ (state: TreePicker) => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })),
+ state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1', pickerId: "projects" })),
+ state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id: '1', pickerId: "projects" })),
+ )({ projects: createTree<{}>() });
+ expect(getNode('1')(newState.projects)).toEqual({
+ ...initTreeNode({ id: '1', value: '1' }),
+ expanded: false
});
});
- it('TOGGLE_TREE_PICKER_NODE_SELECT - selected', () => {
- const node = createTreePickerNode({ nodeId: '1', value: '1' });
- const [newState] = [{
- projects: createTree<TreePickerNode>()
- }]
- .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" })));
- expect(getNodeValue('1')(newState.projects)).toEqual({
- ...createTreePickerNode({ nodeId: '1', value: '1' }),
- selected: true
+ it('ACTIVATE_TREE_PICKER_NODE', () => {
+ const node = initTreeNode({ id: '1', value: '1' });
+ const newState = pipe(
+ (state: TreePicker) => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })),
+ state => treePickerReducer(state, treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: '1', pickerId: "projects" })),
+ )({ projects: createTree<{}>() });
+ expect(getNode('1')(newState.projects)).toEqual({
+ ...initTreeNode({ id: '1', value: '1' }),
+ active: true
});
});
- it('TOGGLE_TREE_PICKER_NODE_SELECT - not selected', () => {
- const node = createTreePickerNode({ nodeId: '1', value: '1' });
- const [newState] = [{
- projects: createTree<TreePickerNode>()
- }]
- .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" })));
- expect(getNodeValue('1')(newState.projects)).toEqual({
- ...createTreePickerNode({ nodeId: '1', value: '1' }),
- selected: false
+ it('TOGGLE_TREE_PICKER_NODE_SELECTION', () => {
+ const node = initTreeNode({ id: '1', value: '1' });
+ const subNode = initTreeNode({ id: '1.1', value: '1.1' });
+ const newState = pipe(
+ (state: TreePicker) => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '', nodes: [node], pickerId: "projects" })),
+ state => treePickerReducer(state, treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: '1', nodes: [subNode], pickerId: "projects" })),
+ state => treePickerReducer(state, treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id: '1.1', pickerId: "projects" })),
+ )({ projects: createTree<{}>() });
+ expect(getNode('1')(newState.projects)).toEqual({
+ ...initTreeNode({ id: '1', value: '1' }),
+ selected: true,
+ children: ['1.1'],
+ status: TreeNodeStatus.LOADED,
});
});
});
//
// 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 { activateNode, getNode, toggleNodeCollapse, toggleNodeSelection } 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<TreePickerNode>) => Tree<TreePickerNode>) => {
+const updateOrCreatePicker = <V>(state: TreePicker, pickerId: string, func: (value: Tree<V>) => Tree<V>) => {
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<TreePickerNode>) => (parent: string) => (state: Tree<TreePickerNode>) => {
+const receiveNodes = <V>(nodes: Array<TreeNode<V>>) => (parent: string) => (state: Tree<V>) => {
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<TreePickerNode> => ({
- children: [],
- id: node.nodeId,
- parent,
- value: node
-});
// 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<any> };
-export type TreePicker = { [key: string]: Tree<TreePickerNode> };
-
-export interface TreePickerNode<Value = any> {
- nodeId: string;
- value: Value;
- selected: boolean;
- collapsed: boolean;
- status: TreeItemStatus;
-}
+export const getTreePicker = <Value = {}>(id: string) => (state: TreePicker): Tree<Value> | undefined => state[id];
export const createTreePickerNode = (data: { nodeId: string, value: any }) => ({
...data,
collapsed: true,
status: TreeItemStatus.INITIAL
});
-
-export const getTreePicker = <Value = {}>(id: string) => (state: TreePicker): Tree<TreePickerNode<Value>> | undefined => state[id];
\ No newline at end of file
+++ /dev/null
-// 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<CollectionCreateFormDialogData>;
-
-export const DialogFileSelection = (props: FileSelectionProps) =>
- <FormDialog
- dialogTitle='Choose a file'
- formFields={FileSelectionFields}
- submitLabel='Ok'
- {...props}
- />;
-
-const FileSelectionFields = () =>
- <Field
- name='tree'
- validate={FILES_FIELD_VALIDATION}
- component={FileTreePickerField} />;
-
-const FILES_FIELD_VALIDATION = [require];
\ No newline at end of file
+++ /dev/null
-// 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) => {
- dispatch(dialogActions.CLOSE_DIALOG({ id: FILE_SELECTION }));
- return data;
- }
- })
-)(DialogFileSelection);
\ No newline at end of file
+++ /dev/null
-// 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<MainFileTreePickerProps, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen'>;
-
-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<any>(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<any>(loadProjectTreePicker(nodeId));
- } else if (pickerId === TreePickerId.FAVORITES) {
- dispatch<any>(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) =>
- <div style={{ display: 'flex', flexDirection: 'column' }}>
- <div style={{ flexGrow: 1, overflow: 'auto' }}>
- <MainFileTreePicker {...props} render={renderTreeItem} pickerId={TreePickerId.PROJECTS} />
- <MainFileTreePicker {...props} render={renderTreeItem} pickerId={TreePickerId.SHARED_WITH_ME} />
- <MainFileTreePicker {...props} render={renderTreeItem} pickerId={TreePickerId.FAVORITES} />
- </div>
- </div>);
-
-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<any>(loadCollectionFiles(nodeId))
- : await services.groupsService.contents(ownerUuid, { filters });
-
- await dispatch<any>(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<any>(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<any>(loadCollectionFiles(nodeId))
- : await services.groupsService.contents(parentId, { filters });
-
-
- dispatch<any>(receiveTreePickerData(nodeId, items, TreePickerId.FAVORITES));
- }
- };
-
-const getProjectPickerIcon = (item: TreeItem<ProjectResource>) => {
- 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<GroupContentsResource>) => {
- switch (item.data.kind) {
- case ResourceKind.COLLECTION:
- return CollectionIcon;
- case ResourceKind.PROJECT:
- return ProjectIcon;
- default:
- return ProjectIcon;
- }
-};
-
-const renderTreeItem = (item: TreeItem<ProjectResource>) =>
- <ListItemTextIcon
- icon={getProjectPickerIcon(item)}
- name={item.data.name}
- isActive={item.active}
- hasMargin={true} />;
-
-
-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) =>
- <div style={{ height: '200px', display: 'flex', flexDirection: 'column' }}>
- <FileTreePicker onChange={handleChange(props)} />
- {props.meta.dirty && props.meta.error &&
- <Typography variant='caption' color='error'>
- {props.meta.error}
- </Typography>}
- </div>;
-
-const handleChange = (props: WrappedFieldProps) => (value: string) =>
- props.input.value === value
- ? props.input.onChange('')
- : props.input.onChange(value);
-
+++ /dev/null
-// 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<HTMLElement>, 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<TreePickerNode>;
- let mappedProps: Pick<TreeProps<any>, 'items'>;
- return (state: RootState, props: MainFileTreePickerProps): Pick<TreeProps<any>, '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<TreeProps<any>, '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<TreePickerNode>) =>
- (id: string): TreeItem<any> => {
- 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
- };
- };
-
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<TreePickerProps, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen'>;
+type ProjectTreePickerProps = Pick<TreePickerProps<ProjectResource>, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>;
const mapDispatchToProps = (dispatch: Dispatch, props: { onChange: (projectUuid: string) => void }): ProjectTreePickerProps => ({
onContextMenu: () => { return; },
- toggleItemActive: (nodeId, status, pickerId) => {
+ toggleItemActive: (_, { id }, 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<any>(toggleItemOpen(id, status, pickerId));
+ },
+ toggleItemSelection: (_, { id }, pickerId) => {
+ dispatch<any>(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id, pickerId }));
},
- toggleItemOpen: (nodeId, status, pickerId) => {
- dispatch<any>(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<any>(loadProjectTreePickerProjects(nodeId));
+ dispatch<any>(loadProjectTreePickerProjects(id));
} else if (pickerId === TreePickerId.FAVORITES) {
- dispatch<any>(loadFavoriteTreePickerProjects(nodeId === services.authService.getUuid() ? '' : nodeId));
+ dispatch<any>(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 }));
}
};
// 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)
const { items } = await services.projectService.list({ filters });
- dispatch<any>(receiveTreePickerData(nodeId, items, TreePickerId.PROJECTS));
+ dispatch<any>(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<any>(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<any>(receiveTreePickerData(nodeId, items, TreePickerId.FAVORITES));
+ dispatch<any>(receiveTreePickerData(id, items, TreePickerId.FAVORITES));
}
};
// 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) =>
}
});
-export interface ProjectTreeProps {
+export interface ProjectTreeProps<T> {
projects: Array<TreeItem<ProjectResource>>;
- toggleOpen: (id: string, status: TreeItemStatus) => void;
- toggleActive: (id: string, status: TreeItemStatus) => void;
+ toggleOpen: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
+ toggleActive: (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>) => void;
onContextMenu: (event: React.MouseEvent<HTMLElement>, item: TreeItem<ProjectResource>) => void;
}
export const ProjectTree = withStyles(styles)(
- class ProjectTreeGeneric<T> extends React.Component<ProjectTreeProps & WithStyles<CssRules>> {
+ class ProjectTreeGeneric<T> extends React.Component<ProjectTreeProps<T> & WithStyles<CssRules>> {
render(): ReactElement<any> {
const { classes, projects, toggleOpen, toggleActive, onContextMenu } = this.props;
return (
icon={ProjectIcon}
name={project.data.name}
isActive={project.active}
- hasMargin={true}/>
- }/>
+ hasMargin={true} />
+ } />
</div>
);
}
--- /dev/null
+// 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;
+}
+
+type ProjectsTreePickerItem = ProjectsTreePickerRootItem | GroupContentsResource | CollectionDirectory | CollectionFile;
+type PickedTreePickerProps = Pick<TreePickerProps<ProjectsTreePickerItem>, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>;
+
+export interface ProjectsTreePickerDataProps {
+ pickerId: string;
+ includeCollections?: boolean;
+ includeFiles?: boolean;
+ rootItemIcon: IconType;
+ loadRootItem: (item: TreeItem<ProjectsTreePickerRootItem>, pickerId: string, includeCollections?: boolean, inlcudeFiles?: boolean) => void;
+}
+
+export interface ProjectsTreePickerActionProps {
+}
+
+export type ProjectsTreePickerProps = ProjectsTreePickerDataProps & ProjectsTreePickerActionProps;
+
+const mapStateToProps = (_: any, { pickerId, rootItemIcon }: ProjectsTreePickerProps) => ({
+ render: renderTreeItem(rootItemIcon),
+ pickerId,
+});
+
+const mapDispatchToProps = (dispatch: Dispatch, props: 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<any>(
+ data.kind === ResourceKind.COLLECTION
+ ? loadCollection(id, pickerId)
+ : loadProject(id, pickerId, props.includeCollections, props.includeFiles)
+ );
+ } else if (!('type' in data) && props.loadRootItem) {
+ props.loadRootItem(item as TreeItem<ProjectsTreePickerRootItem>, pickerId, props.includeCollections, props.includeFiles);
+ }
+ } else if (status === TreeItemStatus.LOADED) {
+ dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
+ }
+ },
+ toggleItemSelection: (_, { id }, pickerId) => {
+ dispatch<any>(treePickerActions.TOGGLE_TREE_PICKER_NODE_SELECTION({ id, pickerId }));
+ },
+});
+
+export const ProjectsTreePicker = connect(mapStateToProps, mapDispatchToProps)(TreePicker);
+
+const getProjectPickerIcon = ({ data }: TreeItem<ProjectsTreePickerItem>, 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<ProjectResource>) =>
+ <ListItemTextIcon
+ icon={getProjectPickerIcon(item, rootItemIcon)}
+ name={item.data.name}
+ isActive={item.active}
+ hasMargin={true} />;
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { connect } from 'react-redux';
+import { ProjectsTreePicker, ProjectsTreePickerProps } from '~/views-components/projects-tree-picker/projects-tree-picker';
+import { Dispatch } from 'redux';
+import { loadUserProject } from '~/store/tree-picker/tree-picker-actions';
+import { ShareIcon } from '~/components/icon/icon';
+
+export const UserProjectsTreePicker = connect(() => ({
+ rootItemIcon: ShareIcon,
+}), (dispatch: Dispatch): Pick<ProjectsTreePickerProps, 'loadRootItem'> => ({
+ loadRootItem: (_, pickerId, includeCollections, includeFiles) => {
+ dispatch<any>(loadUserProject(pickerId, includeCollections, includeFiles));
+ },
+}))(ProjectsTreePicker);
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { connect } from 'react-redux';
+import { ProjectsTreePicker, ProjectsTreePickerProps } from '~/views-components/projects-tree-picker/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(() => ({
+ rootItemIcon: ProjectIcon,
+}), (dispatch: Dispatch): Pick<ProjectsTreePickerProps, 'loadRootItem'> => ({
+ loadRootItem: (_, pickerId, includeCollections, includeFiles) => {
+ dispatch<any>(loadUserProject(pickerId, includeCollections, includeFiles));
+ },
+}))(ProjectsTreePicker);
\ No newline at end of file
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<TreePickerProps, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen'>;
+type SidePanelTreeActionProps = Pick<TreePickerProps<ProjectResource | string>, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen' | 'toggleItemSelection'>;
const mapDispatchToProps = (dispatch: Dispatch, props: SidePanelTreeProps): SidePanelTreeActionProps => ({
- onContextMenu: (event, id) => {
+ onContextMenu: (event, { id }) => {
dispatch<any>(openSidePanelContextMenu(event, id));
},
- toggleItemActive: (nodeId) => {
- dispatch<any>(activateSidePanelTreeItem(nodeId));
- props.onItemActivation(nodeId);
+ toggleItemActive: (_, { id }) => {
+ dispatch<any>(activateSidePanelTreeItem(id));
+ props.onItemActivation(id);
},
- toggleItemOpen: (nodeId) => {
- dispatch<any>(toggleSidePanelTreeItemCollapse(nodeId));
- }
+ toggleItemOpen: (_, { id }) => {
+ dispatch<any>(toggleSidePanelTreeItemCollapse(id));
+ },
+ toggleItemSelection: noop,
});
export const SidePanelTree = connect(undefined, mapDispatchToProps)(
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 { getNodeChildrenIds, Tree as Ttree, createTree, getNode, TreeNodeStatus } from '~/models/tree';
import { Dispatch } from "redux";
+import { initTreeNode } from '../../models/tree';
-export interface TreePickerProps {
+type Callback<T> = (event: React.MouseEvent<HTMLElement>, item: TreeItem<T>, pickerId: string) => void;
+export interface TreePickerProps<T> {
pickerId: string;
- onContextMenu: (event: React.MouseEvent<HTMLElement>, nodeId: string, pickerId: string) => void;
- toggleItemOpen: (nodeId: string, status: TreeItemStatus, pickerId: string) => void;
- toggleItemActive: (nodeId: string, status: TreeItemStatus, pickerId: string) => void;
+ onContextMenu: Callback<T>;
+ toggleItemOpen: Callback<T>;
+ toggleItemActive: Callback<T>;
+ toggleItemSelection: Callback<T>;
}
const memoizedMapStateToProps = () => {
- let prevTree: Ttree<TreePickerNode>;
+ let prevTree: Ttree<any>;
let mappedProps: Pick<TreeProps<any>, 'items'>;
- return (state: RootState, props: TreePickerProps): Pick<TreeProps<any>, 'items'> => {
+ return <T>(state: RootState, props: TreePickerProps<T>): Pick<TreeProps<T>, 'items'> => {
const tree = state.treePicker[props.pickerId] || createTree();
- if(tree !== prevTree){
+ if (tree !== prevTree) {
prevTree = tree;
mappedProps = {
items: getNodeChildrenIds('')(tree)
};
};
-const mapDispatchToProps = (dispatch: Dispatch, props: TreePickerProps): Pick<TreeProps<any>, '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)
+const mapDispatchToProps = (_: Dispatch, props: TreePickerProps<any>): Pick<TreeProps<any>, 'onContextMenu' | 'toggleItemOpen' | 'toggleItemActive' | 'toggleItemSelection'> => ({
+ onContextMenu: (event, item) => props.onContextMenu(event, item, props.pickerId),
+ toggleItemActive: (event, item) => props.toggleItemActive(event, item, props.pickerId),
+ toggleItemOpen: (event, item) => props.toggleItemOpen(event, item, props.pickerId),
+ toggleItemSelection: (event, item) => props.toggleItemSelection(event, item, props.pickerId),
});
export const TreePicker = connect(memoizedMapStateToProps(), mapDispatchToProps)(Tree);
-const treePickerToTreeItems = (tree: Ttree<TreePickerNode>) =>
+const treePickerToTreeItems = (tree: Ttree<any>) =>
(id: string): TreeItem<any> => {
- 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;
+ }
+};
+
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';
+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 { ProjectsTreePicker } from '~/views-components/projects-tree-picker/projects-tree-picker';
+import { UserProjectsTreePicker } from '~/views-components/projects-tree-picker/user-projects-tree-picker';
type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
type WorkbenchPanelProps = WithStyles<CssRules>;
-export const WorkbenchPanel =
- withStyles(styles)(({ classes }: WorkbenchPanelProps) =>
+export const WorkbenchPanel =
+ withStyles(styles)(({ classes }: WorkbenchPanelProps) =>
<Grid container item xs className={classes.root}>
<Grid container item xs className={classes.container}>
<SplitterLayout customClassName={classes.splitter} percentage={true}
<MainContentBar />
</Grid>
<Grid item xs className={classes.content}>
+ <UserProjectsTreePicker pickerId='testPicker1'/>
+ <UserProjectsTreePicker pickerId='testPicker2' includeCollections/>
+ <UserProjectsTreePicker pickerId='testPicker3' includeCollections includeFiles/>
<Switch>
<Route path={Routes.PROJECTS} component={ProjectPanel} />
<Route path={Routes.COLLECTIONS} component={CollectionPanel} />
<CreateProjectDialog />
<CurrentTokenDialog />
<FileRemoveDialog />
- <FileRemoveDialog />
- <FileSelectionDialog />
<FilesUploadCollectionDialog />
<MoveCollectionDialog />
<MoveProcessDialog />