diff --git a/pysus/download_queue.py b/pysus/download_queue.py new file mode 100644 index 00000000..8518d9e7 --- /dev/null +++ b/pysus/download_queue.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class DownloadActionAvailability: + add: bool + remove: bool + clear: bool + download: bool + + +def download_action_availability( + selected_result_count: int, + selected_queue_count: int, + queue_size: int, +) -> DownloadActionAvailability: + """Return which download-queue actions are currently available.""" + has_queue = queue_size > 0 + return DownloadActionAvailability( + add=selected_result_count > 0, + remove=selected_queue_count > 0, + clear=has_queue, + download=has_queue, + ) diff --git a/pysus/tests/web/test_download_queue.py b/pysus/tests/web/test_download_queue.py new file mode 100644 index 00000000..09e05c7b --- /dev/null +++ b/pysus/tests/web/test_download_queue.py @@ -0,0 +1,28 @@ +from pysus.download_queue import download_action_availability + + +def test_actions_are_disabled_without_selections_or_queue(): + actions = download_action_availability(0, 0, 0) + + assert not actions.add + assert not actions.remove + assert not actions.clear + assert not actions.download + + +def test_result_selection_enables_only_add_for_empty_queue(): + actions = download_action_availability(2, 0, 0) + + assert actions.add + assert not actions.remove + assert not actions.clear + assert not actions.download + + +def test_queue_enables_clear_download_and_selected_removal(): + actions = download_action_availability(0, 1, 3) + + assert not actions.add + assert actions.remove + assert actions.clear + assert actions.download diff --git a/pysus/web/pages/1_client.py b/pysus/web/pages/1_client.py index fd5cfdb5..c87e878c 100644 --- a/pysus/web/pages/1_client.py +++ b/pysus/web/pages/1_client.py @@ -8,6 +8,7 @@ from pysus import CACHEPATH from pysus.api.client import PySUS from pysus.api.models import BaseRemoteFile +from pysus.download_queue import download_action_availability from pysus.web.translations import t STATES = [ @@ -809,11 +810,16 @@ def _show_results(pysus: PySUS, client: str) -> None: ) selected_indices = event.selection.get("rows", []) # type: ignore + actions = download_action_availability( + len(selected_indices), 0, len(download_queue) + ) col1, col2 = st.columns([3, 1]) with col2: - if selected_indices and st.button( - t("add_to_queue", _lang()), width="stretch" + if st.button( + t("add_to_queue", _lang()), + width="stretch", + disabled=not actions.add, ): new_indices = [ i for i in selected_indices if i not in queued_indices @@ -827,79 +833,98 @@ def _show_results(pysus: PySUS, client: str) -> None: st.divider() st.subheader(t("queue_title", _lang(), count=str(len(download_queue)))) - if not download_queue: - st.caption(t("queue_empty", _lang())) - return + remove_indices: list[int] = [] + dir_key = f"_dl_dir_{client}" + if download_queue: + qcache_key = f"_queue_rows_{client}" + qcache_fp = (id(files), tuple(queued_indices)) + if st.session_state.get("_q_cache_fp") != qcache_fp: + st.session_state["_q_cache_fp"] = qcache_fp + st.session_state[qcache_key] = [ + { + "": idx, + "File": f.basename, + "Size (bytes)": int(f.size), + "Size": naturalsize(int(f.size)), + } + for idx, f in enumerate(download_queue) + ] + queue_df = pd.DataFrame(st.session_state[qcache_key]).set_index("") + + selection = st.dataframe( + queue_df, + width="stretch", + height=min(35 * len(download_queue) + 38, 250), + on_select="rerun", + selection_mode="multi-row", + column_config=_size_column_config(), + ) - qcache_key = f"_queue_rows_{client}" - qcache_fp = (id(files), tuple(queued_indices)) - if st.session_state.get("_q_cache_fp") != qcache_fp: - st.session_state["_q_cache_fp"] = qcache_fp - st.session_state[qcache_key] = [ - { - "": idx, - "File": f.basename, - "Size (bytes)": int(f.size), - "Size": naturalsize(int(f.size)), - } - for idx, f in enumerate(download_queue) - ] - queue_df = pd.DataFrame(st.session_state[qcache_key]).set_index("") + remove_indices = selection.selection.get("rows", []) # type: ignore - selection = st.dataframe( - queue_df, - width="stretch", - height=min(35 * len(download_queue) + 38, 250), - on_select="rerun", - selection_mode="multi-row", - column_config=_size_column_config(), - ) + dataset_name = st.session_state.get( + f"_query_dataset_{client}", "" + ).lower() + default_dir = str( + CACHEPATH / "downloads" / client / (dataset_name or "data") + ) - remove_indices = selection.selection.get("rows", []) # type: ignore + if dir_key not in st.session_state: + st.session_state[dir_key] = default_dir - dataset_name = st.session_state.get(f"_query_dataset_{client}", "").lower() - default_dir = str( - CACHEPATH / "downloads" / client / (dataset_name or "data") - ) + pending = st.session_state.pop("_dl_pending_" + client, None) + if pending: + st.session_state[dir_key] = pending - dir_key = f"_dl_dir_{client}" - if dir_key not in st.session_state: - st.session_state[dir_key] = default_dir - - pending = st.session_state.pop("_dl_pending_" + client, None) - if pending: - st.session_state[dir_key] = pending - - col_dir, col_btn = st.columns([4, 1]) - with col_dir: - st.text_input( - t("save_to", _lang()), - key=dir_key, - label_visibility="collapsed", - ) - with col_btn: - if st.button(t("browse", _lang()), width="stretch"): - folder = _native_dir_picker( - title=t("browse_dir_title", _lang()), - initialdir=st.session_state[dir_key], + col_dir, col_btn = st.columns([4, 1]) + with col_dir: + st.text_input( + t("save_to", _lang()), + key=dir_key, + label_visibility="collapsed", ) - if folder: - st.session_state["_dl_pending_" + client] = folder - st.rerun() + with col_btn: + if st.button(t("browse", _lang()), width="stretch"): + folder = _native_dir_picker( + title=t("browse_dir_title", _lang()), + initialdir=st.session_state[dir_key], + ) + if folder: + st.session_state["_dl_pending_" + client] = folder + st.rerun() + else: + st.caption(t("queue_empty", _lang())) + + actions = download_action_availability( + len(selected_indices), len(remove_indices), len(download_queue) + ) col1, col2, col3 = st.columns(3) with col1: - if remove_indices and st.button(t("remove", _lang()), width="stretch"): + if st.button( + t("remove", _lang()), + width="stretch", + disabled=not actions.remove, + ): remove_targets = {queued_indices[i] for i in remove_indices} st.session_state[queue_key] = [ i for i in queued_indices if i not in remove_targets ] st.rerun() with col2: - if st.button(t("clear", _lang()), width="stretch"): + if st.button( + t("clear", _lang()), + width="stretch", + disabled=not actions.clear, + ): st.session_state[queue_key] = [] st.rerun() with col3: - if st.button(t("download", _lang()), width="stretch", type="primary"): + if st.button( + t("download", _lang()), + width="stretch", + type="primary", + disabled=not actions.download, + ): _download_selected( pysus, client, download_queue, st.session_state[dir_key] )