From 494fa7f422c7471701a2bb1a8ee245ba12249b2f Mon Sep 17 00:00:00 2001
From: pkupczyk <piotr.kupczyk@id.ethz.ch>
Date: Tue, 6 Dec 2022 17:15:01 +0100
Subject: [PATCH] SSDM-13191 : New Navigation - migrate Admin UI from old
 component to new component - first version of the new types navigation

---
 openbis_ng_ui/src/js/common/consts/ids.js     |   4 +-
 .../src/js/components/types/Types.jsx         |   2 +-
 .../components/types/browser2/TypeBrowser.jsx |  51 ++++
 .../types/browser2/TypeBrowserConsts.js       |  27 ++
 .../types/browser2/TypeBrowserController.js   |  24 ++
 .../TypeBrowserControllerLoadNodePath.js      | 144 +++++++++
 .../TypeBrowserControllerLoadNodesFiltered.js | 282 ++++++++++++++++++
 ...ypeBrowserControllerLoadNodesUnfiltered.js | 231 ++++++++++++++
 8 files changed, 763 insertions(+), 2 deletions(-)
 create mode 100644 openbis_ng_ui/src/js/components/types/browser2/TypeBrowser.jsx
 create mode 100644 openbis_ng_ui/src/js/components/types/browser2/TypeBrowserConsts.js
 create mode 100644 openbis_ng_ui/src/js/components/types/browser2/TypeBrowserController.js
 create mode 100644 openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodePath.js
 create mode 100644 openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodesFiltered.js
 create mode 100644 openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodesUnfiltered.js

diff --git a/openbis_ng_ui/src/js/common/consts/ids.js b/openbis_ng_ui/src/js/common/consts/ids.js
index b0dd040ce02..d6148d85314 100644
--- a/openbis_ng_ui/src/js/common/consts/ids.js
+++ b/openbis_ng_ui/src/js/common/consts/ids.js
@@ -29,6 +29,7 @@ const PERSONAL_ACCESS_TOKEN_GRID_ID = 'personal_access_token_grid'
 
 // browsers
 const DATABASE_BROWSER_ID = 'database_browser'
+const TYPE_BROWSER_ID = 'type_browser'
 
 export default {
   // app
@@ -63,5 +64,6 @@ export default {
   PERSONAL_ACCESS_TOKEN_GRID_ID,
 
   // browsers
-  DATABASE_BROWSER_ID
+  DATABASE_BROWSER_ID,
+  TYPE_BROWSER_ID
 }
diff --git a/openbis_ng_ui/src/js/components/types/Types.jsx b/openbis_ng_ui/src/js/components/types/Types.jsx
index ca248816699..6c6862ddf98 100644
--- a/openbis_ng_ui/src/js/components/types/Types.jsx
+++ b/openbis_ng_ui/src/js/components/types/Types.jsx
@@ -2,7 +2,7 @@ import React from 'react'
 import { withStyles } from '@material-ui/core/styles'
 import Content from '@src/js/components/common/content/Content.jsx'
 import ContentTab from '@src/js/components/common/content/ContentTab.jsx'
-import TypeBrowser from '@src/js/components/types/browser/TypeBrowser.jsx'
+import TypeBrowser from '@src/js/components/types/browser2/TypeBrowser.jsx'
 import TypeSearch from '@src/js/components/types/search/TypeSearch.jsx'
 import EntityTypeForm from '@src/js/components/types/form/entitytype/EntityTypeForm.jsx'
 import VocabularyTypeForm from '@src/js/components/types/form/vocabularytype/VocabularyTypeForm.jsx'
diff --git a/openbis_ng_ui/src/js/components/types/browser2/TypeBrowser.jsx b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowser.jsx
new file mode 100644
index 00000000000..1c7b214fd25
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowser.jsx
@@ -0,0 +1,51 @@
+import _ from 'lodash'
+import React from 'react'
+import BrowserWithSettings from '@src/js/components/common/browser2/BrowserWithSettings.jsx'
+import TypeBrowserController from '@src/js/components/types/browser2/TypeBrowserController.js'
+import AppController from '@src/js/components/AppController.js'
+import pages from '@src/js/common/consts/pages.js'
+import ids from '@src/js/common/consts/ids.js'
+import logger from '@src/js/common/logger.js'
+
+class TypeBrowser extends React.Component {
+  constructor(props) {
+    super(props)
+    this.controller = this.props.controller || new TypeBrowserController()
+  }
+
+  componentDidMount() {
+    this.componentDidUpdate(null)
+  }
+
+  componentDidUpdate(prevProps) {
+    const prevSelectedObject = prevProps ? prevProps.selectedObject : null
+    const selectedObject = this.props.selectedObject
+
+    if (!_.isEqual(prevSelectedObject, selectedObject)) {
+      this.controller.selectObject(this.props.selectedObject)
+    }
+  }
+
+  render() {
+    logger.log(logger.DEBUG, 'TypeBrowser.render')
+    return (
+      <BrowserWithSettings
+        id={ids.TYPE_BROWSER_ID}
+        controller={this.controller}
+        onSelectedChange={selectedObject => {
+          if (selectedObject) {
+            AppController.getInstance().objectOpen(
+              pages.TYPES,
+              selectedObject.type,
+              selectedObject.id
+            )
+          }
+        }}
+      />
+    )
+  }
+}
+
+export default AppController.getInstance().withState(() => ({
+  selectedObject: AppController.getInstance().getSelectedObject(pages.TYPES)
+}))(TypeBrowser)
diff --git a/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserConsts.js b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserConsts.js
new file mode 100644
index 00000000000..e3e502893fe
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserConsts.js
@@ -0,0 +1,27 @@
+import messages from '@src/js/common/messages.js'
+
+const TYPE_ROOT = 'root'
+const TYPE_WARNING = 'warning'
+
+const TEXT_OBJECT_TYPES = messages.get(messages.OBJECT_TYPES)
+const TEXT_COLLECTION_TYPES = messages.get(messages.COLLECTION_TYPES)
+const TEXT_DATA_SET_TYPES = messages.get(messages.DATA_SET_TYPES)
+const TEXT_MATERIAL_TYPES = messages.get(messages.MATERIAL_TYPES)
+const TEXT_VOCABULARY_TYPES = messages.get(messages.VOCABULARY_TYPES)
+const TEXT_PROPERTY_TYPES = messages.get(messages.PROPERTY_TYPES)
+
+function nodeId(...parts) {
+  return parts.join('__')
+}
+
+export default {
+  nodeId,
+  TYPE_ROOT,
+  TYPE_WARNING,
+  TEXT_OBJECT_TYPES,
+  TEXT_COLLECTION_TYPES,
+  TEXT_DATA_SET_TYPES,
+  TEXT_MATERIAL_TYPES,
+  TEXT_VOCABULARY_TYPES,
+  TEXT_PROPERTY_TYPES
+}
diff --git a/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserController.js b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserController.js
new file mode 100644
index 00000000000..5194088051a
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserController.js
@@ -0,0 +1,24 @@
+import BrowserController from '@src/js/components/common/browser2/BrowserController.js'
+import TypeBrowserControllerLoadNodePath from '@src/js/components/types/browser2/TypeBrowserControllerLoadNodePath.js'
+import TypeBrowserControllerLoadNodesFiltered from '@src/js/components/types/browser2/TypeBrowserControllerLoadNodesFiltered.js'
+import TypeBrowserControllerLoadNodesUnfiltered from '@src/js/components/types/browser2/TypeBrowserControllerLoadNodesUnfiltered.js'
+
+export default class TypeBrowserController extends BrowserController {
+  async doLoadNodePath(params) {
+    return await new TypeBrowserControllerLoadNodePath().doLoadNodePath(params)
+  }
+
+  async doLoadNodes(params) {
+    const { filter } = params
+
+    if (filter) {
+      return await new TypeBrowserControllerLoadNodesFiltered().doLoadFilteredNodes(
+        params
+      )
+    } else {
+      return await new TypeBrowserControllerLoadNodesUnfiltered().doLoadUnfilteredNodes(
+        params
+      )
+    }
+  }
+}
diff --git a/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodePath.js b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodePath.js
new file mode 100644
index 00000000000..6dbea1ea989
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodePath.js
@@ -0,0 +1,144 @@
+import TypeBrowserConsts from '@src/js/components/types/browser2/TypeBrowserConsts.js'
+import openbis from '@src/js/services/openbis.js'
+import objectType from '@src/js/common/consts/objectType.js'
+
+export default class TypeBrowserConstsLoadNodePath {
+  async doLoadNodePath(params) {
+    const { object } = params
+
+    if (object.type === objectType.OVERVIEW) {
+      if (object.id === objectType.OBJECT_TYPE) {
+        return this.createFolderPath(
+          objectType.OBJECT_TYPE,
+          TypeBrowserConsts.TEXT_OBJECT_TYPES
+        )
+      } else if (object.id === objectType.COLLECTION_TYPE) {
+        return this.createFolderPath(
+          objectType.COLLECTION_TYPE,
+          TypeBrowserConsts.TEXT_COLLECTION_TYPES
+        )
+      } else if (object.id === objectType.DATA_SET_TYPE) {
+        return this.createFolderPath(
+          objectType.DATA_SET_TYPE,
+          TypeBrowserConsts.TEXT_DATA_SET_TYPES
+        )
+      } else if (object.id === objectType.MATERIAL_TYPE) {
+        return this.createFolderPath(
+          objectType.MATERIAL_TYPE,
+          TypeBrowserConsts.TEXT_MATERIAL_TYPES
+        )
+      } else if (object.id === objectType.VOCABULARY_TYPE) {
+        return this.createFolderPath(
+          objectType.VOCABULARY_TYPE,
+          TypeBrowserConsts.TEXT_VOCABULARY_TYPES
+        )
+      } else if (object.id === objectType.PROPERTY_TYPE) {
+        return this.createFolderPath(
+          objectType.PROPERTY_TYPE,
+          TypeBrowserConsts.TEXT_PROPERTY_TYPES
+        )
+      }
+    } else if (object.type === objectType.OBJECT_TYPE) {
+      const id = new openbis.EntityTypePermId(object.id)
+      const fetchOptions = new openbis.SampleTypeFetchOptions()
+
+      const types = await openbis.getSampleTypes([id], fetchOptions)
+      const type = types[object.id]
+
+      return this.createTypePath(
+        object,
+        type,
+        objectType.OBJECT_TYPE,
+        TypeBrowserConsts.TEXT_OBJECT_TYPES
+      )
+    } else if (object.type === objectType.COLLECTION_TYPE) {
+      const id = new openbis.EntityTypePermId(object.id)
+      const fetchOptions = new openbis.ExperimentTypeFetchOptions()
+
+      const types = await openbis.getExperimentTypes([id], fetchOptions)
+      const type = types[object.id]
+
+      return this.createTypePath(
+        object,
+        type,
+        objectType.COLLECTION_TYPE,
+        TypeBrowserConsts.TEXT_COLLECTION_TYPES
+      )
+    } else if (object.type === objectType.DATA_SET_TYPE) {
+      const id = new openbis.EntityTypePermId(object.id)
+      const fetchOptions = new openbis.DataSetTypeFetchOptions()
+
+      const types = await openbis.getDataSetTypes([id], fetchOptions)
+      const type = types[object.id]
+
+      return this.createTypePath(
+        object,
+        type,
+        objectType.DATA_SET_TYPE,
+        TypeBrowserConsts.TEXT_DATA_SET_TYPES
+      )
+    } else if (object.type === objectType.MATERIAL_TYPE) {
+      const id = new openbis.EntityTypePermId(object.id)
+      const fetchOptions = new openbis.MaterialTypeFetchOptions()
+
+      const types = await openbis.getMaterialTypes([id], fetchOptions)
+      const type = types[object.id]
+
+      return this.createTypePath(
+        object,
+        type,
+        objectType.MATERIAL_TYPE,
+        TypeBrowserConsts.TEXT_MATERIAL_TYPES
+      )
+    } else if (object.type === objectType.VOCABULARY_TYPE) {
+      const id = new openbis.VocabularyPermId(object.id)
+      const fetchOptions = new openbis.VocabularyFetchOptions()
+
+      const types = await openbis.getVocabularies([id], fetchOptions)
+      const type = types[object.id]
+
+      return this.createTypePath(
+        object,
+        type,
+        objectType.VOCABULARY_TYPE,
+        TypeBrowserConsts.TEXT_VOCABULARY_TYPES
+      )
+    } else {
+      return null
+    }
+  }
+
+  createFolderPath(folderObjectType, folderText) {
+    return [
+      {
+        id: TypeBrowserConsts.nodeId(
+          TypeBrowserConsts.TYPE_ROOT,
+          folderObjectType
+        ),
+        object: { type: objectType.OVERVIEW, id: folderObjectType },
+        text: folderText
+      }
+    ]
+  }
+
+  createTypePath(object, type, folderObjectType, folderText) {
+    if (type) {
+      const folderPath = this.createFolderPath(folderObjectType, folderText)
+      return [
+        ...folderPath,
+        {
+          id: TypeBrowserConsts.nodeId(
+            TypeBrowserConsts.TYPE_ROOT,
+            folderObjectType,
+            folderObjectType,
+            type.getCode()
+          ),
+          object,
+          text: object.id
+        }
+      ]
+    } else {
+      return null
+    }
+  }
+}
diff --git a/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodesFiltered.js b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodesFiltered.js
new file mode 100644
index 00000000000..68a90958c2b
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodesFiltered.js
@@ -0,0 +1,282 @@
+import _ from 'lodash'
+import TypeBrowserConsts from '@src/js/components/types/browser2/TypeBrowserConsts.js'
+import openbis from '@src/js/services/openbis.js'
+import objectType from '@src/js/common/consts/objectType.js'
+import messages from '@src/js/common/messages.js'
+import compare from '@src/js/common/compare.js'
+
+const LOAD_LIMIT = 50
+const TOTAL_LOAD_LIMIT = 500
+
+export default class TypeBrowserConstsLoadNodesFiltered {
+  async doLoadFilteredNodes(params) {
+    const { node } = params
+
+    const [
+      objectTypes,
+      collectionTypes,
+      dataSetTypes,
+      materialTypes,
+      vocabularyTypes
+    ] = await Promise.all([
+      this.searchObjectTypes(params),
+      this.searchCollectionTypes(params),
+      this.searchDataSetTypes(params),
+      this.searchMaterialTypes(params),
+      this.searchVocabularyTypes(params)
+    ])
+
+    const loadedCount =
+      objectTypes.getObjects().length +
+      collectionTypes.getObjects().length +
+      dataSetTypes.getObjects().length +
+      materialTypes.getObjects().length +
+      vocabularyTypes.getObjects().length
+
+    const totalCount =
+      objectTypes.getTotalCount() +
+      collectionTypes.getTotalCount() +
+      dataSetTypes.getTotalCount() +
+      materialTypes.getTotalCount() +
+      vocabularyTypes.getTotalCount()
+
+    if (totalCount > TOTAL_LOAD_LIMIT) {
+      return this.tooManyResultsFound(node)
+    }
+
+    const result = {
+      nodes: [],
+      loadMore: {
+        offset: 0,
+        limit: TOTAL_LOAD_LIMIT,
+        loadedCount: loadedCount,
+        totalCount: totalCount,
+        append: false
+      }
+    }
+
+    if (node.internalRoot) {
+      const root = {
+        id: TypeBrowserConsts.TYPE_ROOT,
+        object: {
+          type: TypeBrowserConsts.TYPE_ROOT
+        },
+        canHaveChildren: true
+      }
+      root.children = this.doLoadFilteredNodes({
+        ...params,
+        node: root
+      })
+      result.nodes.push(root)
+    } else if (node.object.type === TypeBrowserConsts.TYPE_ROOT) {
+      if (!_.isEmpty(objectTypes.getObjects())) {
+        const objectTypesNode = this.createObjectTypesNode(
+          params,
+          objectTypes.getObjects()
+        )
+        result.nodes.push(objectTypesNode)
+      }
+      if (!_.isEmpty(collectionTypes.getObjects())) {
+        const collectionTypesNode = this.createCollectionTypesNode(
+          params,
+          collectionTypes.getObjects()
+        )
+        result.nodes.push(collectionTypesNode)
+      }
+      if (!_.isEmpty(dataSetTypes.getObjects())) {
+        const dataSetTypesNode = this.createDataSetTypesNode(
+          params,
+          dataSetTypes.getObjects()
+        )
+        result.nodes.push(dataSetTypesNode)
+      }
+      if (!_.isEmpty(materialTypes.getObjects())) {
+        const materialTypesNode = this.createMaterialTypesNode(
+          params,
+          materialTypes.getObjects()
+        )
+        result.nodes.push(materialTypesNode)
+      }
+      if (!_.isEmpty(vocabularyTypes.getObjects())) {
+        const vocabularyTypesNode = this.createVocabularyTypesNode(
+          params,
+          vocabularyTypes.getObjects()
+        )
+        result.nodes.push(vocabularyTypesNode)
+      }
+    }
+
+    return result
+  }
+
+  tooManyResultsFound(node) {
+    return {
+      nodes: [
+        {
+          id: TypeBrowserConsts.nodeId(node.id, TypeBrowserConsts.TYPE_WARNING),
+          message: {
+            type: 'warning',
+            text: messages.get(messages.TOO_MANY_FILTERED_RESULTS_FOUND)
+          },
+          selectable: false
+        }
+      ]
+    }
+  }
+
+  async searchObjectTypes(params) {
+    const { filter, offset, limit } = params
+
+    const criteria = new openbis.SampleTypeSearchCriteria()
+    criteria.withCode().thatContains(filter)
+
+    const fetchOptions = new openbis.SampleTypeFetchOptions()
+    fetchOptions.from(offset)
+    fetchOptions.count(limit || LOAD_LIMIT)
+
+    const result = await openbis.searchSampleTypes(criteria, fetchOptions)
+
+    return result
+  }
+
+  async searchCollectionTypes(params) {
+    const { filter, offset, limit } = params
+
+    const criteria = new openbis.ExperimentTypeSearchCriteria()
+    criteria.withCode().thatContains(filter)
+
+    const fetchOptions = new openbis.ExperimentTypeFetchOptions()
+    fetchOptions.from(offset)
+    fetchOptions.count(limit || LOAD_LIMIT)
+
+    const result = await openbis.searchExperimentTypes(criteria, fetchOptions)
+
+    return result
+  }
+
+  async searchDataSetTypes(params) {
+    const { filter, offset, limit } = params
+
+    const criteria = new openbis.DataSetTypeSearchCriteria()
+    criteria.withCode().thatContains(filter)
+
+    const fetchOptions = new openbis.DataSetTypeFetchOptions()
+    fetchOptions.from(offset)
+    fetchOptions.count(limit || LOAD_LIMIT)
+
+    const result = await openbis.searchDataSetTypes(criteria, fetchOptions)
+
+    return result
+  }
+
+  async searchMaterialTypes(params) {
+    const { filter, offset, limit } = params
+
+    const criteria = new openbis.MaterialTypeSearchCriteria()
+    criteria.withCode().thatContains(filter)
+
+    const fetchOptions = new openbis.MaterialTypeFetchOptions()
+    fetchOptions.from(offset)
+    fetchOptions.count(limit || LOAD_LIMIT)
+
+    const result = await openbis.searchMaterialTypes(criteria, fetchOptions)
+
+    return result
+  }
+
+  async searchVocabularyTypes(params) {
+    const { filter, offset, limit } = params
+
+    const criteria = new openbis.VocabularySearchCriteria()
+    criteria.withCode().thatContains(filter)
+
+    const fetchOptions = new openbis.VocabularyFetchOptions()
+    fetchOptions.from(offset)
+    fetchOptions.count(limit || LOAD_LIMIT)
+
+    const result = await openbis.searchVocabularies(criteria, fetchOptions)
+
+    return result
+  }
+
+  createObjectTypesNode(params, types) {
+    return this.createNodesFolder(
+      params,
+      types,
+      objectType.OBJECT_TYPE,
+      TypeBrowserConsts.TEXT_OBJECT_TYPES
+    )
+  }
+
+  createCollectionTypesNode(params, types) {
+    return this.createNodesFolder(
+      params,
+      types,
+      objectType.COLLECTION_TYPE,
+      TypeBrowserConsts.TEXT_COLLECTION_TYPES
+    )
+  }
+
+  createDataSetTypesNode(params, types) {
+    return this.createNodesFolder(
+      params,
+      types,
+      objectType.DATA_SET_TYPE,
+      TypeBrowserConsts.TEXT_DATA_SET_TYPES
+    )
+  }
+
+  createMaterialTypesNode(params, types) {
+    return this.createNodesFolder(
+      params,
+      types,
+      objectType.MATERIAL_TYPE,
+      TypeBrowserConsts.TEXT_MATERIAL_TYPES
+    )
+  }
+
+  createVocabularyTypesNode(params, types) {
+    return this.createNodesFolder(
+      params,
+      types,
+      objectType.VOCABULARY_TYPE,
+      TypeBrowserConsts.TEXT_VOCABULARY_TYPES
+    )
+  }
+
+  createNodesFolder(params, types, folderObjectType, folderText) {
+    const { node } = params
+
+    const folderNode = {
+      id: TypeBrowserConsts.nodeId(node.id, folderObjectType),
+      text: folderText,
+      object: {
+        type: objectType.OVERVIEW,
+        id: folderObjectType
+      },
+      canHaveChildren: true,
+      children: { nodes: [] },
+      expanded: true
+    }
+
+    types.sort((o1, o2) => compare(o1.code, o2.code))
+
+    types.forEach(type => {
+      const typeNode = {
+        id: TypeBrowserConsts.nodeId(
+          folderNode.id,
+          folderObjectType,
+          type.code
+        ),
+        text: type.code,
+        object: {
+          type: folderObjectType,
+          id: type.code
+        }
+      }
+      folderNode.children.nodes.push(typeNode)
+    })
+
+    return folderNode
+  }
+}
diff --git a/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodesUnfiltered.js b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodesUnfiltered.js
new file mode 100644
index 00000000000..c8f8a1f11f0
--- /dev/null
+++ b/openbis_ng_ui/src/js/components/types/browser2/TypeBrowserControllerLoadNodesUnfiltered.js
@@ -0,0 +1,231 @@
+import _ from 'lodash'
+import TypeBrowserConsts from '@src/js/components/types/browser2/TypeBrowserConsts.js'
+import openbis from '@src/js/services/openbis.js'
+import objectType from '@src/js/common/consts/objectType.js'
+import compare from '@src/js/common/compare.js'
+
+const LOAD_LIMIT = 50
+
+export default class TypeBrowserConstsLoadNodesUnfiltered {
+  async doLoadUnfilteredNodes(params) {
+    const { node } = params
+
+    if (node.internalRoot) {
+      return {
+        nodes: [
+          {
+            id: TypeBrowserConsts.TYPE_ROOT,
+            object: {
+              type: TypeBrowserConsts.TYPE_ROOT
+            },
+            canHaveChildren: true
+          }
+        ]
+      }
+    } else if (node.object.type === TypeBrowserConsts.TYPE_ROOT) {
+      const nodes = []
+
+      await this.addObjectTypesNode(params, nodes)
+      await this.addCollectionTypesNode(params, nodes)
+      await this.addDataSetTypesNode(params, nodes)
+      await this.addMaterialTypesNode(params, nodes)
+      await this.addVocabularyTypesNode(params, nodes)
+      await this.addPropertyTypesNode(params, nodes)
+
+      return {
+        nodes: nodes
+      }
+    } else if (node.object.type === objectType.OVERVIEW) {
+      if (node.object.id === objectType.OBJECT_TYPE) {
+        return await this.searchObjectTypes(params)
+      } else if (node.object.id === objectType.COLLECTION_TYPE) {
+        return await this.searchCollectionTypes(params)
+      } else if (node.object.id === objectType.DATA_SET_TYPE) {
+        return await this.searchDataSetTypes(params)
+      } else if (node.object.id === objectType.MATERIAL_TYPE) {
+        return await this.searchMaterialTypes(params)
+      } else if (node.object.id === objectType.VOCABULARY_TYPE) {
+        return await this.searchVocabularyTypes(params)
+      } else if (node.object.id === objectType.PROPERTY_TYPE) {
+        return await this.searchPropertyTypes(params)
+      }
+    } else {
+      return null
+    }
+  }
+
+  async searchObjectTypes(params) {
+    const criteria = new openbis.SampleTypeSearchCriteria()
+    const fetchOptions = new openbis.SampleTypeFetchOptions()
+
+    const result = await openbis.searchSampleTypes(criteria, fetchOptions)
+
+    return this.createNodes(params, objectType.OBJECT_TYPE, result)
+  }
+
+  async searchCollectionTypes(params) {
+    const criteria = new openbis.ExperimentTypeSearchCriteria()
+    const fetchOptions = new openbis.ExperimentTypeFetchOptions()
+
+    const result = await openbis.searchExperimentTypes(criteria, fetchOptions)
+
+    return this.createNodes(params, objectType.COLLECTION_TYPE, result)
+  }
+
+  async searchDataSetTypes(params) {
+    const criteria = new openbis.DataSetTypeSearchCriteria()
+    const fetchOptions = new openbis.DataSetTypeFetchOptions()
+
+    const result = await openbis.searchDataSetTypes(criteria, fetchOptions)
+
+    return this.createNodes(params, objectType.DATA_SET_TYPE, result)
+  }
+
+  async searchMaterialTypes(params) {
+    const criteria = new openbis.MaterialTypeSearchCriteria()
+    const fetchOptions = new openbis.MaterialTypeFetchOptions()
+
+    const result = await openbis.searchMaterialTypes(criteria, fetchOptions)
+
+    return this.createNodes(params, objectType.MATERIAL_TYPE, result)
+  }
+
+  async searchVocabularyTypes(params) {
+    const criteria = new openbis.VocabularySearchCriteria()
+    const fetchOptions = new openbis.VocabularyFetchOptions()
+
+    const result = await openbis.searchVocabularies(criteria, fetchOptions)
+
+    return this.createNodes(params, objectType.VOCABULARY_TYPE, result)
+  }
+
+  async addObjectTypesNode(params, nodes) {
+    const folderNode = await this.createNodesFolder(
+      params,
+      objectType.OBJECT_TYPE,
+      TypeBrowserConsts.TEXT_OBJECT_TYPES,
+      this.searchObjectTypes.bind(this)
+    )
+    if (folderNode) {
+      nodes.push(folderNode)
+    }
+  }
+
+  async addCollectionTypesNode(params, nodes) {
+    const folderNode = await this.createNodesFolder(
+      params,
+      objectType.COLLECTION_TYPE,
+      TypeBrowserConsts.TEXT_COLLECTION_TYPES,
+      this.searchCollectionTypes.bind(this)
+    )
+    if (folderNode) {
+      nodes.push(folderNode)
+    }
+  }
+
+  async addDataSetTypesNode(params, nodes) {
+    const folderNode = await this.createNodesFolder(
+      params,
+      objectType.DATA_SET_TYPE,
+      TypeBrowserConsts.TEXT_DATA_SET_TYPES,
+      this.searchDataSetTypes.bind(this)
+    )
+    if (folderNode) {
+      nodes.push(folderNode)
+    }
+  }
+
+  async addMaterialTypesNode(params, nodes) {
+    const folderNode = await this.createNodesFolder(
+      params,
+      objectType.MATERIAL_TYPE,
+      TypeBrowserConsts.TEXT_MATERIAL_TYPES,
+      this.searchMaterialTypes.bind(this)
+    )
+    if (folderNode) {
+      nodes.push(folderNode)
+    }
+  }
+
+  async addVocabularyTypesNode(params, nodes) {
+    const folderNode = await this.createNodesFolder(
+      params,
+      objectType.VOCABULARY_TYPE,
+      TypeBrowserConsts.TEXT_VOCABULARY_TYPES,
+      this.searchVocabularyTypes.bind(this)
+    )
+    if (folderNode) {
+      nodes.push(folderNode)
+    }
+  }
+
+  async addPropertyTypesNode(params, nodes) {
+    const folderNode = await this.createNodesFolder(
+      params,
+      objectType.PROPERTY_TYPE,
+      TypeBrowserConsts.TEXT_PROPERTY_TYPES,
+      () => []
+    )
+    if (folderNode) {
+      folderNode.canHaveChildren = false
+      nodes.push(folderNode)
+    }
+  }
+
+  createNodes(params, objectType, result) {
+    const { node, offset } = params
+
+    let nodes = result.getObjects().map(type => ({
+      id: TypeBrowserConsts.nodeId(node.id, objectType, type.getCode()),
+      text: type.getCode(),
+      object: {
+        type: objectType,
+        id: type.getCode()
+      }
+    }))
+
+    nodes.sort((n1, n2) => compare(n1.text, n2.text))
+    nodes = nodes.slice(offset, offset + LOAD_LIMIT)
+
+    if (_.isEmpty(nodes)) {
+      return null
+    } else {
+      return {
+        nodes: nodes,
+        loadMore: {
+          offset: offset + nodes.length,
+          loadedCount: offset + nodes.length,
+          totalCount: result.getTotalCount(),
+          append: offset > 0
+        }
+      }
+    }
+  }
+
+  async createNodesFolder(params, folderObjectType, folderText, search) {
+    const { node } = params
+
+    const folderNode = {
+      id: TypeBrowserConsts.nodeId(node.id, folderObjectType),
+      text: folderText,
+      object: {
+        type: objectType.OVERVIEW,
+        id: folderObjectType
+      },
+      canHaveChildren: true,
+      selectable: true
+    }
+
+    const nodes = await search({
+      ...params,
+      node: folderNode
+    })
+
+    if (nodes) {
+      folderNode.children = nodes
+      return folderNode
+    } else {
+      return null
+    }
+  }
+}
-- 
GitLab