Yes, you should be able to display a sort of navigation based upon uploaded files.
Yes, the files would need to be saved, I believe you are doing this already. Are you saving the filepath somewhere?
How do you want the nav to look?
Yes, you should be able to display a sort of navigation based upon uploaded files.
Yes, the files would need to be saved, I believe you are doing this already. Are you saving the filepath somewhere?
How do you want the nav to look?
I have a list of fully qualified filepaths which I would like to display as a file tree, something like this:
Iāve found some css examples that are a bit useful but nothing plotly specific. Without having to do any heavy lifting Iām hoping to find an elegant solution here.
Edit: Viewing file contents is out of scope. Iām only concerned with a user being able to view a directory structure.
You can do something like that with an Accordion component. Hereās and example using dash-mantine-components:
from dash import Dash
import dash_mantine_components as dmc
from dash_iconify import DashIconify
app = Dash(__name__)
pages = ["home.py", "page1.py", "page2.py"]
assets = ["mycss.css", "app.png", "page1.png"]
def make_file(file_name):
return dmc.Text(
[DashIconify(icon="akar-icons:file"), " ", file_name], style={"paddingTop": 10}
)
def make_folder(folder_name):
return [DashIconify(icon="akar-icons:folder"), " ", folder_name]
file_dir = dmc.Accordion(
[
dmc.AccordionItem([make_file(f) for f in assets], label=make_folder("assets")),
dmc.AccordionItem([make_file(f) for f in pages], label=make_folder("pages")),
],
multiple=True,
)
app.layout = dmc.Container([file_dir, make_file("app.py")])
if __name__ == "__main__":
app.run_server(debug=True)
@AnnMarieW This is awesome and just what I am looking for, thank you! This is one of the reasons why I love this community so much, folks are willing to help out so readily to share information.
If youāre interested in knowing the motivation, Iām building a simple UI to allow users to upload their apps to Heroku / whatever. Baking this into dashtools.
Glad I could help!
Sounds like an awesome addition to dashtools! Looking forward to checking out the next release
I was able to build out a fairly easy class to generate trees given a filepath:
class FileTree:
def __init__(self, filepath: os.PathLike):
self.filepath = filepath
def render(self) -> dmc.Accordion:
return dmc.Accordion(
self.build_tree(self.filepath, isRoot=True),
multiple=True)
def flatten(self, l):
return [item for sublist in l for item in sublist]
def make_file(self, file_name):
return dmc.Text(
[DashIconify(icon="akar-icons:file"), " ", file_name], style={"paddingTop": '5px'}
)
def make_folder(self, folder_name):
return [DashIconify(icon="akar-icons:folder"), " ", folder_name]
def build_tree(self, path, isRoot=False):
d = []
if os.path.isdir(path):
children = self.flatten([self.build_tree(os.path.join(path, x))
for x in os.listdir(path)])
if isRoot:
d.append(
dmc.AccordionItem(
children=children,
label=self.make_folder(os.path.basename(path)))
)
else:
d.append(
dmc.Accordion(children=[
dmc.AccordionItem(
children=children,
label=self.make_folder(os.path.basename(path)))
],
multiple=True)
)
else:
d.append(self.make_file(os.path.basename(path)))
return d
# Usage
import dash_mantine_components as dmc
import os
from dash import Dash
from dash_iconify import DashIconify
app = Dash()
filepath = 'Users/andrew' # Any filepath here
app.layout = FileTree(filepath).render()
app.run_server()
Iām not sure how to make components but I feel that this could be useful to people. Might move this conversation to a new #show-and-tell thread if there is enough interest.
This is awesome! Itās definitely worth itās own show-and-tell post
hi @andrew-hossack @AnnMarieW awesome work! Do you know how we can add a search bar at the top of the file tree and match the search string with any of the tree nodes as shown in the image below:
For this you could use an input event on a search bar, where it auto expands all and hides based upon matches. Or adds a class to the element.
@jinnyzor I am not sure how to match the search string with the Accordion
items. Any hints on how to do so?
Sure, here you go, this is a generic example and based off of dmc:
from dash import Dash, dcc, Input, Output
import dash_mantine_components as dmc
from dash_iconify import DashIconify
app = Dash(__name__, external_scripts=[
"https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"
],)
pages = ["home.py", "page1.py", "page2.py"]
assets = ["mycss.css", "app.png", "page1.png"]
def make_file(file_name):
return dmc.Text(
[DashIconify(icon="akar-icons:file"), " ", file_name], style={"paddingTop": 10}
)
def make_folder(folder_name):
return [DashIconify(icon="akar-icons:folder"), " ", folder_name]
file_dir = dmc.Accordion(
[
dmc.AccordionItem([make_file(f) for f in assets], label=make_folder("assets")),
dmc.AccordionItem([make_file(f) for f in pages], label=make_folder("pages")),
],
multiple=True,
)
app.layout = dmc.Container([dcc.Input(id='search', placeholder='search directory'), file_dir, make_file("app.py")])
app.clientside_callback(
"""
function search(v) {
if (v) {
$(".mantine-Accordion-item").addClass("mantine-Accordion-itemOpened").addClass("mantine-1xa9lqx")
$(".mantine-Accordion-item .mantine-1avyp1d").attr("style", "box-sizing: border-box;")
$(".mantine-Accordion-item .mantine-1avyp1d > div").attr("style", "opacity: 1; transition: opacity 200ms ease 0s;")
$(".mantine-Accordion-item .mantine-1avyp1d").attr("aria-hidden", false)
$(".mantine-Accordion-item").each( function() {
if ($(this).text().includes(v)) {
$(this).removeClass("hidden")
} else {
$(this).addClass("hidden")
}
}
)
$(".mantine-Accordion-item .mantine-Text-root").each( function () {
if ($(this).text().includes(v)) {
$(this).removeClass("hidden")
} else {
$(this).addClass("hidden")
}
})
} else {
$(".mantine-Accordion-item").removeClass("mantine-Accordion-itemOpened")
$(".mantine-Accordion-item").removeClass("mantine-1xa9lqx")
$(".mantine-Accordion-item").removeClass("hidden")
$(".mantine-Accordion-item .mantine-Text-root").removeClass("hidden")
$(".mantine-Accordion-item .mantine-1avyp1d").attr("style", "box-sizing: border-box; height: 0px; overflow: hidden; display: none;")
$(".mantine-Accordion-item .mantine-1avyp1d").attr("aria-hidden", true)
$(".mantine-Accordion-item .mantine-1avyp1d > div").attr("style", "opacity: 0; transition: opacity 200ms ease 0s;")
}
return window.dash_clientside.no_update
}
""",
Output('search', 'id'),
Input('search','value'),
prevent_intial_call=True
)
if __name__ == "__main__":
app.run_server(debug=True)
It uses jquery in order to be able to navigate quickly, imo. If you are using this for navigation, be sure to empty out the string on navigation click.
@jinnyzor Thanks for the code! Iām not very strong on JavaScript but the code you shared seems to expand every Accordion
item regardless of whether the search string matches the item, as shown in the shared video. Ideally I would like to expand only the items that contain the search string. For example, if the search string is mycss
then only the folder assets
should be expanded and not both the assets
and pages
folders. If there is no match, no item should be expanded. Any help would be much appreciated!
Hmm. Weird. I thought it was working for meā¦
Itās based upon the class names.
@jinnyzor is there anything I need to change before running the code? I simply copied and pasted and ran the code with no changes.
You shouldnāt have to, it is expanding⦠So part of the code is workingā¦
What are your versions?
@jinnyzor these are my versions:
dash 2.6.2
dash-core-components 2.0.0
dash-extensions 0.1.6
dash-html-components 2.0.0
dash-mantine-components 0.10.2
Oh!
Iām sorry. I have this is my css file.
.hidden {
display: none
}
Itās just second nature I guess. I have that as a default in all of my websites. XD
@jinnyzor Thanks a lot! That fixed the issue.
Thereās a couple of minor issues however.
Accordion
item seems to be in the reverse orientation. The arrows have to be clicked to get them in the right orientation.Accordion
items, except that of the first Accordion
item, do not work the first time they are clicked. They have to be clicked twice in order for the items to contract. For example, when I type āaā in the search bar, the Accordion
items, āassetsā and āpagesā, expand. When I click the up arrow next to āassetsā, āassetsā contracts, which is the desired behavior. However, when I do the same with āpagesā, i.e. click the up arrow next to āpagesā, it does nothing. I have to click it again for āpagesā to contract.Hereās a little video showing the two issues: