#!/usr/bin/env python # coding: utf-8 # # Example: Building a File Browser with ipytree # # In this example, we demonstrate how to create an alternative file browser UI using the `ipytree` widget. # # ## Collecting the files # # First let's define some imports. # In[ ]: get_ipython().run_line_magic('pip', 'install -q ipylab') # In[ ]: import os from fnmatch import fnmatch from pathlib import PurePath # Since the example is living in the source repository, we also define a list of folders to exclude, so they are not displayed in the tree. # In[ ]: EXCLUDES = { ".git", ".github", ".vscode", "build", "dist", "lib", "node_modules", "__pycache__", ".ipynb_checkpoints" } # Now let's define a function to collect all the files, starting from a `root_path`. # In[ ]: def collect_files(root_path='..'): files = [] for dirpath, dirnames, filenames in os.walk(root_path, followlinks=True): dirnames[:] = [d for d in dirnames if d not in EXCLUDES] for f in filenames: fullpath = PurePath(dirpath).relative_to(root_path).joinpath(f) if fullpath.parts not in files: files.append(fullpath.parts) files.sort() return files # In[ ]: files = collect_files() # Let's show a subset of these files. # In[ ]: files[:15] # Now let's build a tree structure that will be used to build the tree widget. # In[ ]: tree = {} for f in files: node = tree for part in f: if part not in node: node[part] = {} node = node[part] # ## Building the tree widget # # We first import `ipytree`'s `Node` and `Tree` widgets. # In[ ]: from ipytree import Node, Tree from traitlets import Unicode # The following class derives from the base `Node` widget, and adds a `fullpath` property to store the full path to the file. This will be useful when opening the file using JupyterLab's command. # In[ ]: class TreeNode(Node): fullpath = Unicode("").tag(sync=True) # The following function traverse the tree structure created above, and creates the corresponding widgets. # In[ ]: def create_tree_widget(root, path, depth=0): node = Tree() if depth == 0 else TreeNode() for name, children in root.items(): fullpath = path + [name] if len(children) == 0: leaf = TreeNode(name) leaf.fullpath = os.path.join(*fullpath) leaf.icon = 'file' leaf.icon_style = 'warning' node.add_node(leaf) else: subtree = create_tree_widget(children, fullpath, depth + 1) subtree.icon = 'folder' subtree.icon_style = 'info' subtree.name = name node.add_node(subtree) return node # In[ ]: file_tree = create_tree_widget(tree, []) # We can now display the tree in the notebook to make sure that it looks correct. # In[ ]: file_tree # ## Adding the tree to the left area in JupyterLab # # Now that the tree is ready, we can start adding it to other areas in JupyterLab outside of the notebook. # # Let's first create the frontend widget to serve as the proxy to JupyterLab. # In[ ]: from ipylab import JupyterFrontEnd # In[ ]: app = JupyterFrontEnd() # Let's also define a couple of buttons to: # # - open the selected files # - expand all nodes of the tre # - collapse all nodes of the tree # In[ ]: from ipywidgets import Button, Layout, HBox, VBox open_button = Button(description='Open', button_style='success', icon='folder') expand_button = Button(description='Expand', button_style='info', icon='chevron-down') collapse_button = Button(description='Collapse', button_style='info', icon='chevron-right') hbox = HBox([ open_button, expand_button, collapse_button ], layout=Layout(overflow='unset')) hbox # Let's now add the callbacks to catch click events. # In[ ]: def expand_tree(tree, expand=True): for node in tree.nodes: node.opened = expand def on_expand_click(b): expand_tree(file_tree) def on_collapse_click(b): expand_tree(file_tree, False) expand_button.on_click(on_expand_click) collapse_button.on_click(on_collapse_click) # When the "Open" button is clicked, we call `app.commands.execute` with the path to the file to open it in the JupyterLab interface. # In[ ]: def on_open_clicked(b): for node in file_tree.selected_nodes: filepath = node.fullpath if filepath: app.commands.execute('docmanager:open', { 'path': filepath}) open_button.on_click(on_open_clicked) # Let's collapse the tree as its initial state, and add some overflow so it can be scrolled. # In[ ]: expand_tree(file_tree, False) file_tree.layout = Layout(overflow='auto') # The Panel will store both the buttons and the file tree right below. # In[ ]: from ipylab import Panel panel = Panel(children=[hbox, file_tree]) panel.title.label = 'File Browser' panel.title.icon_class = 'jp-FileIcon' panel.layout = Layout(overflow='auto') # Finally, we can add the file browser to the left area! We can also change `'left'` to `'right'` if you prefer adding it to the right area. # In[ ]: app.shell.add(panel, 'left', {'rank': 10000}) # In[ ]: