Hey,
I made this widget based on the pmui.Select:
- The Select lists all folders and files at a location on server side
- If the user clics on a folder, the select list is updated to the clicked folder
- if the user clics on a file, the select list closes and the file path is saved
The widget uses the dropdown_open attribute to trigger updates, it works fine until we want to close the dropdown menu without selecting a file, in which case we would need to clic twice on the “.” (. and … being options to navigate in folders) so we see we don’t try to move anymore to accept it to be closed.
An option i see would be to have access to a parameter in the event saying if it closed due to a menu item clic, or a escape button/anywhere out of the widget clic. Do you guys see another clean way to get the information?
from typing import List, Tuple, Union
import param
import os
from pathlib import Path
import panel_material_ui as pmui
from panel.viewable import Viewer
class ServerFileBrowser(Viewer):
""" The ServerFileBrowser is a file explorer based on a Select widget that lets browse on the server side.
"""
current_folder: Path
"""Crrently browsing folder"""
selected_file: param.String = param.String()
"""Full path of currently selected file"""
def __init__(
self,
folder_path: Union[str, Path] = None,
**kwargs
):
"""File browser on the server side based on a pmui.Select widget
Parameters
----------
folder_path : Union[str, Path], optional
Base folder path, by default None
Raises
----------
ValueError
Not None provided folder path does not exist
"""
super().__init__(**kwargs)
if folder_path is None:
self.current_folder = Path(__file__).parent
else:
if not os.path.isdir(folder_path):
raise ValueError(f"Provided folder path does not exist {folder_path}")
self.current_folder = Path(folder_path)
self.selected_file = ""
self.select = pmui.Select(groups={
"Directories": [".", ".."] + [e for e in os.listdir(self.current_folder) if os.path.isdir(e)],
"Files": [e for e in os.listdir(self.current_folder) if os.path.isfile(e)],
}, size="small", margin=(0, 0, 0, 0))
self.select.param.watch(self.on_value_change, "dropdown_open")
# Saving values required to be able to close the dropdown menu without selection
self._past_value = None
self._past_path = None
def on_value_change(self, event: param.Event):
"""Event triggered by a dropdown open change, it updates the content of the select if a value was selected:
- If a folder is selected, the select updates its content to the folder content, and the folder path is saved;
- If a file is selected, its path is saved in selected_file.
Parameters
----------
event : param.Event
Change event
"""
# Checking if the dropdown is closing:
# - option was selected
# - either the value changed, or the folder changed (if .. is selected)
# - the panel was closed
# - neither the value not the folder was changed
if not self.select.dropdown_open and (self._past_value != self.select.value or self._past_path != self.current_folder):
self._past_value = self.select.value
self._past_path = self.current_folder
if self.select.value is None:
pass
elif os.path.isfile(self.current_folder / self.select.value):
print("Selected file : ", self.current_folder / self.select.value)
self.selected_file = str(self.current_folder / self.select.value)
elif os.path.isdir(self.current_folder / self.select.value):
self.current_folder = (self.current_folder / self.select.value).resolve()
try:
folder_list, file_list = self._get_folder_sorted_content(self.current_folder)
self.select.disabled_options = []
self.select.groups = {
"Directories": [".", ".."] + folder_list,
"Files": file_list,
}
except PermissionError:
# We don't have access to the folder, so we go back to the former
self.select.disabled_options += [self.select.value]
self.current_folder = (self.current_folder / "..").resolve()
self.selected_file = ""
self.select.dropdown_open = True
def _get_folder_sorted_content(self, path:Path) -> Tuple[List[str], List[str]]:
"""Returns the alphabetically sorded list of folders and files in the provided path, if access is allowed
Parameters
----------
path : Path
Folder path to display
Returns
-------
Tuple[List[str], List[str]]
List of folder, list of files
Raises
------
PermissionError
Access to the requested folder is denied
"""
try:
folder_list = [e for e in os.listdir(path) if os.path.isdir(path / e)]
file_list = [e for e in os.listdir(path) if os.path.isfile(path / e)]
folder_list.sort()
file_list.sort()
return folder_list, file_list
except PermissionError:
raise PermissionError
def __panel__(self):
"""Redered panel
Returns
-------
pn.viewable.Viewable
pmui.Select widget displayed
"""
return self.select
if __name__ == "__main__":
ServerFileBrowser().show()
Thanks