I made some further improvements to the FileTree.
Folders in FileTree are now accompanied by a checkbox. If a checkbox is selected, that folder is used as the root of a new FileTree.
The fictituous folder ‘…’ is added on top of the folder, so that the user can traverse back to the initial root. I limited this so that the initial root can not be escaped by the user.
For clarity and ease of use, I put all in a working example in a single file:
from dash import Dash, Input, Output, html, ALL, no_update, callback_context
import dash_mantine_components as dmc
from dash_iconify import DashIconify
from pathlib import Path
class FileTree:
def __init__(self, filepath: Path):
self.filepath = Path(filepath)
def render(self) -> dmc.AccordionMultiple:
return dmc.AccordionMultiple(
children=self.build_tree(self.filepath, is_root=True)
)
def flatten(self, input):
return [item for sublist in input for item in sublist]
def make_file(self, file_name):
return dmc.Group([
# Spacing element to align the checkbox with the file icon
dmc.Space(w=35),
DashIconify(icon="akar-icons:file"),
dmc.Text(file_name)
], style={"paddingTop": '5px'})
def make_folder(self, folder_name, path):
return dmc.Group([
dmc.Checkbox(id={'type': 'folder_checkbox', 'index': str(path)}),
DashIconify(icon="akar-icons:folder"),
dmc.Text(folder_name)
])
def build_tree(self, path, is_root=False):
d = []
path = Path(path)
if path.is_dir():
children = self.flatten([self.build_tree(child)
for child in path.iterdir()])
if is_root and path != INITIAL_FOLDER:
d.append(
dmc.AccordionItem([
dmc.AccordionControl(
self.make_folder('..', path.parent)),
], value='..')
)
d.append(
dmc.AccordionItem([
dmc.AccordionControl(
self.make_folder(path.name, path)),
dmc.AccordionPanel(children=children)
], value=str(path))
)
else:
d.append(self.make_file(path.name))
return d
INITIAL_FOLDER = Path(r'C:\data\experimental')
app = Dash(__name__)
# Define the callback
@app.callback(
Output('filetree_div', 'children'),
Output('selected_folder_title', 'children'),
Input({'type': 'folder_checkbox', 'index': ALL}, 'checked')
)
def update_output(checked_values):
'''
Update the file tree and selected folder title based on the checked boxes
'''
if checked_values is None:
return 'No paths selected'
# Extract the paths of the checked checkboxes
checked_paths = [item['id']['index'] for item, checked in zip(
callback_context.inputs_list[0], checked_values) if checked]
if checked_paths:
# Render a new FileTree with the selected folder as the root
return FileTree(checked_paths[0]).render(), checked_paths[0]
else:
return no_update
# Add an output div to your layout
app.layout = html.Div([
html.H2(id='selected_folder_title', children=str(INITIAL_FOLDER)),
html.Div(id='filetree_div',
children=FileTree(INITIAL_FOLDER).render())
])
if __name__ == '__main__':
app.run_server(debug=True)