From 150809ad1dec37a7e6e599fc3e41722a5a209a16 Mon Sep 17 00:00:00 2001
From: pkupczyk <piotr.kupczyk@id.ethz.ch>
Date: Thu, 26 Jan 2023 09:06:05 +0100
Subject: [PATCH] SSDM-11608 : Navigation : collapse/expand all feature

---
 openbis_ng_ui/src/js/common/messages.js       |  2 +
 .../js/components/common/browser/Browser.jsx  | 16 ++++----
 .../common/browser/BrowserController.js       |  8 ++++
 .../components/common/browser/BrowserNode.jsx | 16 ++++++--
 ...l.jsx => BrowserNodeCollapseExpandAll.jsx} | 28 ++++++++++----
 .../common/browser/BrowserTreeController.js   | 38 +++++++++++++++++++
 6 files changed, 90 insertions(+), 18 deletions(-)
 rename openbis_ng_ui/src/js/components/common/browser/{BrowserNodeCollapseAll.jsx => BrowserNodeCollapseExpandAll.jsx} (61%)

diff --git a/openbis_ng_ui/src/js/common/messages.js b/openbis_ng_ui/src/js/common/messages.js
index de419a8b908..d0e5f764b2d 100644
--- a/openbis_ng_ui/src/js/common/messages.js
+++ b/openbis_ng_ui/src/js/common/messages.js
@@ -89,6 +89,7 @@ const keys = {
   EVALUATE: 'EVALUATE',
   EVENT_TYPE: 'EVENT_TYPE',
   EXECUTE: 'EXECUTE',
+  EXPAND_ALL: 'EXPAND_ALL',
   EXPORT: 'EXPORT',
   EXPORT_PLAIN_TEXT_WARNING: 'EXPORT_PLAIN_TEXT_WARNING',
   EXPORTS: 'EXPORTS',
@@ -382,6 +383,7 @@ const messages_en = {
   [keys.EVALUATE]: 'Evaluate',
   [keys.EVENT_TYPE]: 'Event Type',
   [keys.EXECUTE]: 'Execute',
+  [keys.EXPAND_ALL]: 'Expand all',
   [keys.EXPORT]: 'Export',
   [keys.EXPORT_PLAIN_TEXT_WARNING]: 'Do not use this file for Batch Update! This file does not contain rich text formatting. If used for Batch Update, all rich text formatting in the updated entries will be lost!',
   [keys.EXPORTS]: 'Exports',
diff --git a/openbis_ng_ui/src/js/components/common/browser/Browser.jsx b/openbis_ng_ui/src/js/components/common/browser/Browser.jsx
index a99b01023fb..a25d3709506 100644
--- a/openbis_ng_ui/src/js/components/common/browser/Browser.jsx
+++ b/openbis_ng_ui/src/js/components/common/browser/Browser.jsx
@@ -7,7 +7,7 @@ import FilterField from '@src/js/components/common/form/FilterField.jsx'
 import BrowserRoot from '@src/js/components/common/browser/BrowserRoot.jsx'
 import BrowserNode from '@src/js/components/common/browser/BrowserNode.jsx'
 import BrowserNodeAutoShowSelected from '@src/js/components/common/browser/BrowserNodeAutoShowSelected.jsx'
-import BrowserNodeCollapseAll from '@src/js/components/common/browser/BrowserNodeCollapseAll.jsx'
+import BrowserNodeCollapseExpandAll from '@src/js/components/common/browser/BrowserNodeCollapseExpandAll.jsx'
 import logger from '@src/js/common/logger.js'
 
 const styles = theme => ({
@@ -136,6 +136,9 @@ class Browser extends React.PureComponent {
     const { controller } = this
     const { classes } = this.props
 
+    const node = controller.getNodeSetAsRoot() ||
+      controller.getRoot() || { canHaveChildren: true }
+
     return (
       <FilterField
         filter={controller.getFilter() || ''}
@@ -148,12 +151,11 @@ class Browser extends React.PureComponent {
               value={controller.isAutoShowSelectedObject()}
               onClick={controller.changeAutoShowSelectedObject}
             />
-            <BrowserNodeCollapseAll
-              node={
-                controller.getNodeSetAsRoot() ||
-                controller.getRoot() || { canHaveChildren: true }
-              }
-              onClick={controller.collapseAllNodes}
+            <BrowserNodeCollapseExpandAll
+              node={node}
+              expand={controller.isExpandAllNodesAvailable(node.id)}
+              onCollapseAll={controller.collapseAllNodes}
+              onExpandAll={controller.expandAllNodes}
             />
           </div>
         }
diff --git a/openbis_ng_ui/src/js/components/common/browser/BrowserController.js b/openbis_ng_ui/src/js/components/common/browser/BrowserController.js
index 6ded0747e1a..dd7e2699c53 100644
--- a/openbis_ng_ui/src/js/components/common/browser/BrowserController.js
+++ b/openbis_ng_ui/src/js/components/common/browser/BrowserController.js
@@ -231,6 +231,14 @@ export default class BrowserController {
     await this._getTreeController().collapseAllNodes(nodeId)
   }
 
+  async expandAllNodes(nodeId) {
+    await this._getTreeController().expandAllNodes(nodeId)
+  }
+
+  isExpandAllNodesAvailable(nodeId) {
+    return this._getTreeController().isExpandAllNodesAvailable(nodeId)
+  }
+
   async setNodeAsRoot(node) {
     let nodeSetAsRoot = null
 
diff --git a/openbis_ng_ui/src/js/components/common/browser/BrowserNode.jsx b/openbis_ng_ui/src/js/components/common/browser/BrowserNode.jsx
index a505c24fdcf..d3c149e17e2 100644
--- a/openbis_ng_ui/src/js/components/common/browser/BrowserNode.jsx
+++ b/openbis_ng_ui/src/js/components/common/browser/BrowserNode.jsx
@@ -14,7 +14,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
 import BrowserNode from '@src/js/components/common/browser/BrowserNode.jsx'
 import BrowserNodeSetAsRoot from '@src/js/components/common/browser/BrowserNodeSetAsRoot.jsx'
 import BrowserNodeSortings from '@src/js/components/common/browser/BrowserNodeSortings.jsx'
-import BrowserNodeCollapseAll from '@src/js/components/common/browser/BrowserNodeCollapseAll.jsx'
+import BrowserNodeCollapseExpandAll from '@src/js/components/common/browser/BrowserNodeCollapseExpandAll.jsx'
 import Message from '@src/js/components/common/form/Message.jsx'
 import messages from '@src/js/common/messages.js'
 import util from '@src/js/common/util.js'
@@ -105,6 +105,11 @@ class BrowserNodeClass extends React.PureComponent {
     controller.collapseAllNodes(nodeId)
   }
 
+  handleExpandAll(nodeId) {
+    const { controller } = this.props
+    controller.expandAllNodes(nodeId)
+  }
+
   handleSetAsRoot(nodeId) {
     const { controller } = this.props
     controller.setNodeAsRoot(nodeId)
@@ -239,7 +244,7 @@ class BrowserNodeClass extends React.PureComponent {
   }
 
   renderOptions(node) {
-    const { classes } = this.props
+    const { controller, classes } = this.props
 
     return (
       <div className={classes.options}>
@@ -249,7 +254,12 @@ class BrowserNodeClass extends React.PureComponent {
           onChange={this.handleSortingChange}
           onClearCustom={this.handleCustomSortingClear}
         />
-        <BrowserNodeCollapseAll node={node} onClick={this.handleCollapseAll} />
+        <BrowserNodeCollapseExpandAll
+          node={node}
+          expand={controller.isExpandAllNodesAvailable(node.id)}
+          onCollapseAll={this.handleCollapseAll}
+          onExpandAll={this.handleExpandAll}
+        />
       </div>
     )
   }
diff --git a/openbis_ng_ui/src/js/components/common/browser/BrowserNodeCollapseAll.jsx b/openbis_ng_ui/src/js/components/common/browser/BrowserNodeCollapseExpandAll.jsx
similarity index 61%
rename from openbis_ng_ui/src/js/components/common/browser/BrowserNodeCollapseAll.jsx
rename to openbis_ng_ui/src/js/components/common/browser/BrowserNodeCollapseExpandAll.jsx
index cd97d36e80b..def224e7e85 100644
--- a/openbis_ng_ui/src/js/components/common/browser/BrowserNodeCollapseAll.jsx
+++ b/openbis_ng_ui/src/js/components/common/browser/BrowserNodeCollapseExpandAll.jsx
@@ -4,9 +4,9 @@ import { withStyles } from '@material-ui/core/styles'
 import Tooltip from '@src/js/components/common/form/Tooltip.jsx'
 import IconButton from '@material-ui/core/IconButton'
 import UnfoldLessIcon from '@material-ui/icons/UnfoldLess'
+import UnfoldMoreIcon from '@material-ui/icons/UnfoldMore'
 import messages from '@src/js/common/messages.js'
 import logger from '@src/js/common/logger.js'
-import id from 'date-fns/esm/locale/id/index.js'
 
 const styles = theme => ({
   container: {
@@ -18,7 +18,7 @@ const styles = theme => ({
   }
 })
 
-class BrowserNodeCollapseAll extends React.PureComponent {
+class BrowserNodeCollapseExpandAll extends React.PureComponent {
   constructor(props) {
     super(props)
     autoBind(this)
@@ -27,16 +27,20 @@ class BrowserNodeCollapseAll extends React.PureComponent {
   handleClick(event) {
     event.preventDefault()
     event.stopPropagation()
-    const { node } = this.props
+    const { node, expand } = this.props
     if (node.id) {
-      this.props.onClick(node.id)
+      if (expand) {
+        this.props.onExpandAll(node.id)
+      } else {
+        this.props.onCollapseAll(node.id)
+      }
     }
   }
 
   render() {
     logger.log(logger.DEBUG, 'BrowserNodeCollapseAll.render')
 
-    const { node, classes } = this.props
+    const { node, expand, classes } = this.props
 
     if (!node || !node.canHaveChildren) {
       return null
@@ -44,13 +48,21 @@ class BrowserNodeCollapseAll extends React.PureComponent {
 
     return (
       <div className={classes.container}>
-        <Tooltip title={messages.get(messages.COLLAPSE_ALL)}>
+        <Tooltip
+          title={messages.get(
+            expand ? messages.EXPAND_ALL : messages.COLLAPSE_ALL
+          )}
+        >
           <IconButton
             size='small'
             onClick={this.handleClick}
             classes={{ root: classes.button }}
           >
-            <UnfoldLessIcon fontSize='small' />
+            {expand ? (
+              <UnfoldMoreIcon fontSize='small' />
+            ) : (
+              <UnfoldLessIcon fontSize='small' />
+            )}
           </IconButton>
         </Tooltip>
       </div>
@@ -58,4 +70,4 @@ class BrowserNodeCollapseAll extends React.PureComponent {
   }
 }
 
-export default withStyles(styles)(BrowserNodeCollapseAll)
+export default withStyles(styles)(BrowserNodeCollapseExpandAll)
diff --git a/openbis_ng_ui/src/js/components/common/browser/BrowserTreeController.js b/openbis_ng_ui/src/js/components/common/browser/BrowserTreeController.js
index dd3cc51ac30..f967ef51907 100644
--- a/openbis_ng_ui/src/js/components/common/browser/BrowserTreeController.js
+++ b/openbis_ng_ui/src/js/components/common/browser/BrowserTreeController.js
@@ -26,6 +26,7 @@ export default class BrowserTreeController {
       nodes: {},
       selectedObject: null,
       expandedIds: {},
+      expandAllIds: {},
       sortingIds: {}
     })
     this.context = context
@@ -48,6 +49,7 @@ export default class BrowserTreeController {
       nodes: {},
       selectedObject: state.selectedObject,
       expandedIds: {},
+      expandAllIds: {},
       sortingIds: {},
       customSortings: {}
     }
@@ -588,6 +590,19 @@ export default class BrowserTreeController {
         this._doCollapseNode(newState, node.id, false)
       }
 
+      const idsToExpandAll = _.difference(
+        Object.keys(state.expandedIds),
+        Object.keys(newState.expandedIds)
+      )
+
+      if (!_.isEmpty(idsToExpandAll)) {
+        const newExpandAllIds = {
+          ...newState.expandAllIds,
+          [nodeId]: idsToExpandAll
+        }
+        newState.expandAllIds = newExpandAllIds
+      }
+
       await this.context.setState(newState)
       this._saveSettings()
     }
@@ -625,6 +640,29 @@ export default class BrowserTreeController {
     }
   }
 
+  async expandAllNodes(nodeId) {
+    const state = this.context.getState()
+    const idsToExpand = state.expandAllIds[nodeId]
+
+    if (!_.isEmpty(idsToExpand)) {
+      const newState = { ...state }
+      newState.expandAllIds = { ...state.expandAllIds }
+
+      idsToExpand.forEach(idToExpand => {
+        this._doExpandNode(newState, idToExpand)
+      })
+      delete newState.expandAllIds[nodeId]
+
+      this.context.setState(newState)
+    }
+  }
+
+  isExpandAllNodesAvailable(nodeId) {
+    const state = this.context.getState()
+    const idsToExpand = state.expandAllIds[nodeId]
+    return !_.isEmpty(idsToExpand)
+  }
+
   async selectObject(nodeObject) {
     const state = this.context.getState()
 
-- 
GitLab