diff --git a/sepal_ui/sepalwidgets/inputs.py b/sepal_ui/sepalwidgets/inputs.py index 4f309bdb..2c0de078 100644 --- a/sepal_ui/sepalwidgets/inputs.py +++ b/sepal_ui/sepalwidgets/inputs.py @@ -52,7 +52,6 @@ reason="Empty v_model will be treated as empty string: :code:`v_model=''`.", ) class DatePicker(v.Layout, SepalWidget): - menu: Optional[v.Menu] = None "the menu widget to display the datepicker" @@ -178,13 +177,15 @@ def is_valid_date(date: str) -> bool: class FileInput(v.Flex, SepalWidget): - extensions: List[str] = [] "list: the extensions list" folder: Path = Path.home() "the current folder" + initial_folder: Path = Path.home() + "the starting point of the file input" + file: t.Unicode = t.Unicode("").tag(sync=True) "the current file" @@ -223,6 +224,7 @@ def __init__( v_model: str = "", clearable: bool = False, root: Union[str, Path] = "", + cache=False, **kwargs, ) -> None: """Custom input field to select a file in the sepal folders. @@ -237,8 +239,10 @@ def __init__( kwargs: any parameter from a v.Flex abject. If set, 'children' will be overwritten. """ self.extensions = extensions + self.initial_folder = folder self.folder = Path(folder) self.root = str(root) if isinstance(root, Path) else root + self.cache_dirs = {} self.selected_file = v.TextField( readonly=True, @@ -330,9 +334,8 @@ def reset(self, *args) -> Self: # do nothing if nothing is set to avoids extremely long waiting # time when multiple fileInput are reset at the same time as in the aoiView if self.v_model is not None: - # move to root - self._on_file_select({"new": Path.home()}) + self._on_file_select({"new": self.initial_folder}) # remove v_model self.v_model = "" @@ -369,7 +372,10 @@ def _on_file_select(self, change: dict) -> Self: if new_value.is_dir(): self.folder = new_value - self._change_folder() + + # don't change folder if the folder is the parent of the root + if not self.folder == Path(self.root).parent: + self._change_folder() elif new_value.is_file(): self.file = str(new_value) @@ -406,11 +412,14 @@ def _get_items(self) -> List[v.ListItem]: el for el in list_dir if el.is_dir() or el.suffix in self.extensions ] + if folder in self.cache_dirs: + if self.cache_dirs[folder]["files"] == list_dir: + return self.cache_dirs[folder]["items"] + folder_list = [] file_list = [] for el in list_dir: - if el.is_dir(): icon = self.ICON_STYLE[""]["icon"] color = self.ICON_STYLE[""]["color"][v.theme.dark] @@ -439,10 +448,7 @@ def _get_items(self) -> List[v.ListItem]: folder_list = humansorted(folder_list, key=lambda x: x.value) file_list = humansorted(file_list, key=lambda x: x.value) - folder_list.extend(file_list) - # add the parent item if root is set and is not reached yet - # if root is not set then we always display it parent_item = v.ListItem( value=str(folder.parent), children=[ @@ -459,16 +465,17 @@ def _get_items(self) -> List[v.ListItem]: ), ], ) - root_folder = Path(self.root) - if self.root == "": - folder_list.insert(0, parent_item) - elif root_folder in folder.parents: - folder_list.insert(0, parent_item) + + folder_list.extend(file_list) + folder_list.insert(0, parent_item) + + self.cache_dirs.setdefault(folder, {}) + self.cache_dirs[folder]["files"] = list_dir + self.cache_dirs[folder]["items"] = folder_list return folder_list def _on_reload(self, *args) -> None: - # force the update of the current folder self._change_folder() @@ -484,7 +491,6 @@ def close_menu(self, change: dict) -> None: class LoadTableField(v.Col, SepalWidget): - fileInput: Optional[FileInput] = None "The file input to select the .csv or .txt file" @@ -638,7 +644,6 @@ def _set_v_model(self, key: str, value: Any) -> None: class AssetSelect(v.Combobox, SepalWidget): - TYPES: dict = { "IMAGE": ms.widgets.asset_select.types[0], "TABLE": ms.widgets.asset_select.types[1], @@ -723,7 +728,6 @@ def _validate(self, change: dict) -> None: self.v_model = self.v_model.strip() if change["new"]: - # check that the asset can be accessed try: self.asset_info = ee.data.getAsset(self.v_model) @@ -735,7 +739,6 @@ def _validate(self, change: dict) -> None: ) except Exception: - self.error_messages = ms.widgets.asset_select.no_access self.valid = self.error_messages is None @@ -745,13 +748,11 @@ def _validate(self, change: dict) -> None: @sd.switch("loading", "disabled") def _get_items(self, *args) -> Self: - # init the item list items = [] # add the default values if needed if self.default_asset: - if isinstance(self.default_asset, str): self.default_asset = [self.default_asset] @@ -828,7 +829,6 @@ def _toggle_pwd(self, *args) -> None: class NumberField(v.TextField, SepalWidget): - max_: t.Int = t.Int(10).tag(sync=True) "Maximum selectable number." @@ -879,7 +879,6 @@ def decrement(self, *args) -> None: class VectorField(v.Col, SepalWidget): - original_gdf: Optional[gpd.GeoDataFrame] = None "The originally selected dataframe" diff --git a/tests/test_sepalwidgets/test_FileInput.py b/tests/test_sepalwidgets/test_FileInput.py index 291d3f1d..125198e1 100644 --- a/tests/test_sepalwidgets/test_FileInput.py +++ b/tests/test_sepalwidgets/test_FileInput.py @@ -3,7 +3,6 @@ from pathlib import Path from typing import List -import ipyvuetify as v import pytest from traitlets import Any @@ -170,9 +169,18 @@ def test_root(file_input: sw.FileInput, root_dir: Path) -> None: # set the root to the current folder and reload file_input.root = str(root_dir) file_input._on_reload() - first_title_item = file_input.get_children(klass=v.ListItemTitle)[0] - assert ".. /" not in first_title_item.children[0] + current_items = file_input.file_list + + # Try to go to the parent root folder + root_parent = Path(file_input.root).parent + + file_input._on_file_select({"new": root_parent}) + + new_items = file_input.file_list + + # Assert that trying to go to the parent root folder does not work + assert current_items == new_items return