diff --git a/dp_wizard/shiny/__init__.py b/dp_wizard/shiny/__init__.py index 385d5488..7211d5cb 100644 --- a/dp_wizard/shiny/__init__.py +++ b/dp_wizard/shiny/__init__.py @@ -103,7 +103,7 @@ def server(input: Inputs, output: Outputs, session: Session): # pragma: no cove private_csv_path = reactive.value(str(initial_private_csv_path)) column_names = reactive.value(initial_column_names) - public_csv_path = reactive.value("") + public_csv_path = reactive.value(None) analysis_types = reactive.value({}) lower_bounds = reactive.value({}) upper_bounds = reactive.value({}) @@ -123,7 +123,6 @@ def server(input: Inputs, output: Outputs, session: Session): # pragma: no cove session, is_demo=cli_info.is_demo, in_cloud=cli_info.in_cloud, - initial_public_csv_path="", initial_private_csv_path=str(initial_private_csv_path), public_csv_path=public_csv_path, private_csv_path=private_csv_path, diff --git a/dp_wizard/shiny/components/inputs.py b/dp_wizard/shiny/components/inputs.py index 20272c65..8a922cda 100644 --- a/dp_wizard/shiny/components/inputs.py +++ b/dp_wizard/shiny/components/inputs.py @@ -1,7 +1,31 @@ +from pathlib import Path from math import log10 from shiny import ui +parent_name = "../" + + +def get_file_selector_choices(cwd: Path): + return [None, parent_name] + sorted( + path.name + ("/" if path.is_dir() else "") + for path in cwd.iterdir() + if path.name.endswith(".csv") or path.is_dir() + ) + + +def file_selector(id: str, label: str, cwd: Path): + from dp_wizard.shiny.components.outputs import info_md_box + + return ui.input_select( + id, + [label, " from:", ui.tags.br(), ui.tags.strong(cwd.name + "/")], + get_file_selector_choices(cwd), + size="6", + selected=None, + ) + + def log_slider(id: str, lower_bound: float, upper_bound: float): # Rather than engineer a new widget, hide the numbers we don't want, # and insert the log values via CSS. diff --git a/dp_wizard/shiny/dataset_panel.py b/dp_wizard/shiny/dataset_panel.py index 008b6a5e..71b2b6bb 100644 --- a/dp_wizard/shiny/dataset_panel.py +++ b/dp_wizard/shiny/dataset_panel.py @@ -9,6 +9,11 @@ PUBLIC_PRIVATE_TEXT, ) from dp_wizard.utils.csv_helper import get_csv_names_mismatch +from dp_wizard.shiny.components.inputs import ( + get_file_selector_choices, + file_selector, + parent_name, +) from dp_wizard.shiny.components.outputs import ( output_code_sample, demo_tooltip, @@ -51,19 +56,40 @@ def dataset_server( session: Session, is_demo: bool, in_cloud: bool, - initial_public_csv_path: str, initial_private_csv_path: str, - public_csv_path: reactive.Value[str], + public_csv_path: reactive.Value[Path | None], private_csv_path: reactive.Value[str], column_names: reactive.Value[list[str]], contributions: reactive.Value[int], ): # pragma: no cover + public_csv_dir = reactive.value(Path.cwd()) + @reactive.effect - @reactive.event(input.public_csv_path) - def _on_public_csv_path_change(): - path = input.public_csv_path()[0]["datapath"] - public_csv_path.set(path) - column_names.set(read_csv_names(Path(path))) + @reactive.event(input.public_csv_name) + def _on_public_csv_name_change(): + name = input.public_csv_name() + if not name: + column_names.set([]) + elif name == parent_name: + new_dir = public_csv_dir().parent + public_csv_dir.set(new_dir) + public_csv_path.set(None) + ui.update_select( + "public_csv_name", choices=get_file_selector_choices(new_dir) + ) + column_names.set([]) + elif name.endswith("/"): + new_dir = public_csv_dir() / name + public_csv_dir.set(new_dir) + public_csv_path.set(None) + ui.update_select( + "public_csv_name", choices=get_file_selector_choices(new_dir) + ) + column_names.set([]) + else: + new_path = public_csv_dir() / name + public_csv_path.set(new_path) + column_names.set(read_csv_names(new_path)) @reactive.effect @reactive.event(input.private_csv_path) @@ -157,12 +183,7 @@ def input_files_ui(): # - After file upload, the internal copy of the file # is renamed to something like "0.csv". return ui.row( - ui.input_file( - "public_csv_path", - "Choose Public CSV", - accept=[".csv"], - placeholder=Path(initial_public_csv_path).name, - ), + file_selector("public_csv_name", "Choose Public CSV", public_csv_dir()), ui.input_file( "private_csv_path", [