From 01be2240bf05a3f276ade06f9d169be5c4d21c72 Mon Sep 17 00:00:00 2001 From: Xing Wang Date: Tue, 23 Apr 2024 11:46:19 +0200 Subject: [PATCH] Build NodeTree on demand (#583) This PR introduces the idea that node tree should only be built on demand. - do not build tree recursively, only show the top-level - build the sub-tree when users select it, and expand it. --- aiidalab_widgets_base/nodes.py | 42 +++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/aiidalab_widgets_base/nodes.py b/aiidalab_widgets_base/nodes.py index d67d31749..9f4bf766a 100644 --- a/aiidalab_widgets_base/nodes.py +++ b/aiidalab_widgets_base/nodes.py @@ -67,8 +67,8 @@ def __init__(self, pk, name, **kwargs): super().__init__(name=name, **kwargs) @tl.default("opened") - def _default_openend(self): - return False + def _default_opened(self): + return True class AiidaProcessNodeTreeNode(AiidaNodeTreeNode): @@ -91,7 +91,7 @@ class CalcFunctionTreeNode(AiidaProcessNodeTreeNode): class AiidaOutputsTreeNode(ipytree.Node): icon = tl.Unicode("folder").tag(sync=True) - disabled = tl.Bool(True).tag(sync=True) + disabled = tl.Bool(False).tag(sync=True) def __init__(self, name, parent_pk, namespaces: tuple[str, ...] = (), **kwargs): self.parent_pk = parent_pk @@ -141,6 +141,15 @@ def _refresh_output(self): display(self._tree) def _observe_tree_selected_nodes(self, change): + for node in change["new"]: + # find the selected node and build the tree from it, so that users can expand and explore the tree + node_pk = ( + node.parent_pk + if isinstance(node, AiidaOutputsTreeNode) + else getattr(node, "pk", None) + ) + + self._build_tree(self.find_node(node_pk, getattr(node, "namespaces", None))) return self.set_trait( "selected_nodes", tuple( @@ -180,9 +189,19 @@ def _to_tree_node(cls, node, name=None, **kwargs): name = calc_info(node) else: name = str(node) - return cls.NODE_TYPE.get(type(node), UnknownTypeTreeNode)( + tree_node = cls.NODE_TYPE.get(type(node), UnknownTypeTreeNode)( pk=node.pk, name=name, **kwargs ) + # Set the style based on the process state of the node + if isinstance(node, orm.ProcessNode): + process_state = ( + engine.ProcessState.EXCEPTED if node.is_failed else node.process_state + ) + tree_node.icon_style = cls.PROCESS_STATE_STYLE.get( + process_state, cls.PROCESS_STATE_STYLE_DEFAULT + ) + + return tree_node @classmethod def _find_called(cls, root): @@ -251,8 +270,8 @@ def _find_children(cls, root): @classmethod def _build_tree(cls, root): - """Recursively build a tree nodes graph for a given tree node.""" - root.nodes = [cls._build_tree(child) for child in cls._find_children(root)] + """Build a tree nodes graph for a given tree node.""" + root.nodes = list(cls._find_children(root)) return root @classmethod @@ -284,9 +303,16 @@ def update(self, _=None): for tree_node in self._walk_tree(root_node): self._update_tree_node(tree_node) - def find_node(self, pk): + def find_node(self, pk, namespaces=None): + """Find a node by its pk and namespaces. + If node is an output node, it is identified by the parent pk and namespaces, otherwise by the pk.""" for node in self._walk_tree(self._tree): - if getattr(node, "pk", None) == pk: + node_pk = ( + node.parent_pk + if isinstance(node, AiidaOutputsTreeNode) + else getattr(node, "pk", None) + ) + if node_pk == pk and getattr(node, "namespaces", None) == namespaces: return node raise KeyError(pk)