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 export const collectionPanelFilesReducer = (state: CollectionPanelFilesState = createTree(), action: CollectionPanelFilesAction) => {
11 // Low-level tree handling setNode() func does in-place data modifications
12 // for performance reasons, so we pass a copy of 'state' to avoid side effects.
13 return collectionPanelFilesAction.match(action, {
14 SET_COLLECTION_FILES: files =>
15 mergeCollectionPanelFilesStates({ ...state }, mapTree(mapCollectionFileToCollectionPanelFile)(files)),
17 TOGGLE_COLLECTION_FILE_COLLAPSE: data =>
18 toggleCollapse(data.id)({ ...state }),
20 TOGGLE_COLLECTION_FILE_SELECTION: data => [{ ...state }]
21 .map(toggleSelected(data.id))
22 .map(toggleAncestors(data.id))
23 .map(toggleDescendants(data.id))[0],
25 ON_SEARCH_CHANGE: (data) =>
26 mapTreeValues((v: CollectionPanelDirectory | CollectionPanelFile) => {
27 if (v.type === CollectionFileType.DIRECTORY) {
28 return ({ ...v, collapsed: data.length === 0 });
34 SELECT_ALL_COLLECTION_FILES: () =>
35 mapTreeValues(v => ({ ...v, selected: true }))({ ...state }),
37 UNSELECT_ALL_COLLECTION_FILES: () =>
38 mapTreeValues(v => ({ ...v, selected: false }))({ ...state }),
41 }) as CollectionPanelFilesState;
44 const toggleCollapse = (id: string) => (tree: CollectionPanelFilesState) =>
45 setNodeValueWith((v: CollectionPanelDirectory | CollectionPanelFile) =>
46 v.type === CollectionFileType.DIRECTORY
47 ? { ...v, collapsed: !v.collapsed }
51 const toggleSelected = (id: string) => (tree: CollectionPanelFilesState) =>
52 setNodeValueWith((v: CollectionPanelDirectory | CollectionPanelFile) => ({ ...v, selected: !v.selected }))(id)(tree);
55 const toggleDescendants = (id: string) => (tree: CollectionPanelFilesState) => {
56 const node = getNode(id)(tree);
57 if (node && node.value.type === CollectionFileType.DIRECTORY) {
58 return getNodeDescendantsIds(id)(tree)
59 .reduce((newTree, id) =>
60 setNodeValueWith(v => ({ ...v, selected: node.value.selected }))(id)(newTree), tree);
65 const toggleAncestors = (id: string) => (tree: CollectionPanelFilesState) => {
66 const ancestors = getNodeAncestorsIds(id)(tree).reverse();
67 return ancestors.reduce((newTree, parent) => parent ? toggleParentNode(parent)(newTree) : newTree, tree);
70 const toggleParentNode = (id: string) => (tree: CollectionPanelFilesState) => {
71 const node = getNode(id)(tree);
73 const parentNode = getNode(node.id)(tree);
75 const selected = parentNode.children
76 .map(id => getNode(id)(tree))
77 .every(node => node !== undefined && node.value.selected);
78 return setNodeValueWith(v => ({ ...v, selected }))(parentNode.id)(tree);
80 return setNode(node)(tree);