1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import { CollectionPanelFilesState, CollectionPanelFile, CollectionPanelDirectory, mapCollectionFileToCollectionPanelFile, mergeCollectionPanelFilesStates } from './collection-panel-files-state';
6 import { CollectionPanelFilesAction, collectionPanelFilesAction } from "./collection-panel-files-actions";
7 import { createTree, mapTreeValues, getNode, setNode, getNodeAncestorsIds, getNodeDescendantsIds, setNodeValueWith, mapTree } from "models/tree";
8 import { CollectionFileType } from "models/collection-file";
10 let fetchedFiles: any = {};
12 export const collectionPanelFilesReducer = (state: CollectionPanelFilesState = createTree(), action: CollectionPanelFilesAction) => {
13 // Low-level tree handling setNode() func does in-place data modifications
14 // for performance reasons, so we pass a copy of 'state' to avoid side effects.
15 return collectionPanelFilesAction.match(action, {
16 SET_COLLECTION_FILES: files => {
18 return mergeCollectionPanelFilesStates({ ...state }, mapTree(mapCollectionFileToCollectionPanelFile)(files));
21 TOGGLE_COLLECTION_FILE_COLLAPSE: data =>
22 toggleCollapse(data.id)({ ...state }),
24 TOGGLE_COLLECTION_FILE_SELECTION: data => [{ ...state }]
25 .map(toggleSelected(data.id))
26 .map(toggleAncestors(data.id))
27 .map(toggleDescendants(data.id))[0],
29 ON_SEARCH_CHANGE: (searchValue) => {
30 const fileIds: string[] = [];
31 const directoryIds: string[] = [];
32 const filteredFiles = Object.keys(fetchedFiles)
33 .filter((key: string) => {
34 const node = fetchedFiles[key];
36 if (node.value === undefined) {
40 const { id, value: { type, name } } = node;
42 if (type === CollectionFileType.DIRECTORY) {
43 directoryIds.push(id);
47 const includeFile = name.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
55 .reduce((prev, next) => {
56 const node = JSON.parse(JSON.stringify(fetchedFiles[next]));
57 const { value: { type }, children } = node;
59 node.children = node.children.filter((key: string) => {
60 const isFile = directoryIds.indexOf(key) === -1;
62 fileIds.indexOf(key) > -1 :
63 !!fileIds.find(id => id.indexOf(key) > -1);
66 if (type === CollectionFileType.FILE || children.length > 0) {
73 return mapTreeValues((v: CollectionPanelDirectory | CollectionPanelFile) => {
74 if (v.type === CollectionFileType.DIRECTORY) {
77 collapsed: searchValue.length === 0,
82 })({ ...filteredFiles });
85 SELECT_ALL_COLLECTION_FILES: () =>
86 mapTreeValues((v: any) => ({ ...v, selected: true }))({ ...state }),
88 UNSELECT_ALL_COLLECTION_FILES: () =>
89 mapTreeValues((v: any) => ({ ...v, selected: false }))({ ...state }),
92 }) as CollectionPanelFilesState;
95 const toggleCollapse = (id: string) => (tree: CollectionPanelFilesState) =>
96 setNodeValueWith((v: CollectionPanelDirectory | CollectionPanelFile) =>
97 v.type === CollectionFileType.DIRECTORY
98 ? { ...v, collapsed: !v.collapsed }
102 const toggleSelected = (id: string) => (tree: CollectionPanelFilesState) =>
103 setNodeValueWith((v: CollectionPanelDirectory | CollectionPanelFile) => ({ ...v, selected: !v.selected }))(id)(tree);
106 const toggleDescendants = (id: string) => (tree: CollectionPanelFilesState) => {
107 const node = getNode(id)(tree);
108 if (node && node.value.type === CollectionFileType.DIRECTORY) {
109 return getNodeDescendantsIds(id)(tree)
110 .reduce((newTree, id) =>
111 setNodeValueWith((v: any) => ({ ...v, selected: node.value.selected }))(id)(newTree), tree);
116 const toggleAncestors = (id: string) => (tree: CollectionPanelFilesState) => {
117 const ancestors = getNodeAncestorsIds(id)(tree).reverse();
118 return ancestors.reduce((newTree, parent) => parent ? toggleParentNode(parent)(newTree) : newTree, tree);
121 const toggleParentNode = (id: string) => (tree: CollectionPanelFilesState) => {
122 const node = getNode(id)(tree);
124 const parentNode = getNode(node.id)(tree);
126 const selected = parentNode.children
127 .map(id => getNode(id)(tree))
128 .every(node => node !== undefined && node.value.selected);
129 return setNodeValueWith((v: any) => ({ ...v, selected }))(parentNode.id)(tree);
131 return setNode(node)(tree);