Skip to content

Commit 3df24ad

Browse files
committed
feat(web): keep download actions discoverable
1 parent 7106b10 commit 3df24ad

3 files changed

Lines changed: 144 additions & 58 deletions

File tree

‎pysus/download_queue.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass(frozen=True)
5+
class DownloadActionAvailability:
6+
add: bool
7+
remove: bool
8+
clear: bool
9+
download: bool
10+
11+
12+
def download_action_availability(
13+
selected_result_count: int,
14+
selected_queue_count: int,
15+
queue_size: int,
16+
) -> DownloadActionAvailability:
17+
"""Return which download-queue actions are currently available."""
18+
has_queue = queue_size > 0
19+
return DownloadActionAvailability(
20+
add=selected_result_count > 0,
21+
remove=selected_queue_count > 0,
22+
clear=has_queue,
23+
download=has_queue,
24+
)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from pysus.download_queue import download_action_availability
2+
3+
4+
def test_actions_are_disabled_without_selections_or_queue():
5+
actions = download_action_availability(0, 0, 0)
6+
7+
assert not actions.add
8+
assert not actions.remove
9+
assert not actions.clear
10+
assert not actions.download
11+
12+
13+
def test_result_selection_enables_only_add_for_empty_queue():
14+
actions = download_action_availability(2, 0, 0)
15+
16+
assert actions.add
17+
assert not actions.remove
18+
assert not actions.clear
19+
assert not actions.download
20+
21+
22+
def test_queue_enables_clear_and_download_without_selection():
23+
actions = download_action_availability(0, 0, 3)
24+
25+
assert not actions.add
26+
assert not actions.remove
27+
assert actions.clear
28+
assert actions.download
29+
30+
31+
def test_selected_queue_item_enables_removal():
32+
actions = download_action_availability(0, 1, 3)
33+
34+
assert not actions.add
35+
assert actions.remove
36+
assert actions.clear
37+
assert actions.download

‎pysus/web/pages/1_client.py‎

Lines changed: 83 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pysus import CACHEPATH
99
from pysus.api.client import PySUS
1010
from pysus.api.models import BaseRemoteFile
11+
from pysus.download_queue import download_action_availability
1112
from pysus.native_dir_picker import native_dir_picker
1213
from pysus.web.translations import t
1314

@@ -746,11 +747,16 @@ def _show_results(pysus: PySUS, client: str) -> None:
746747
)
747748

748749
selected_indices = event.selection.get("rows", []) # type: ignore
750+
actions = download_action_availability(
751+
len(selected_indices), 0, len(download_queue)
752+
)
749753

750754
col1, col2 = st.columns([3, 1])
751755
with col2:
752-
if selected_indices and st.button(
753-
t("add_to_queue", _lang()), width="stretch"
756+
if st.button(
757+
t("add_to_queue", _lang()),
758+
width="stretch",
759+
disabled=not actions.add,
754760
):
755761
new_indices = [
756762
i for i in selected_indices if i not in queued_indices
@@ -764,79 +770,98 @@ def _show_results(pysus: PySUS, client: str) -> None:
764770
st.divider()
765771
st.subheader(t("queue_title", _lang(), count=str(len(download_queue))))
766772

767-
if not download_queue:
768-
st.caption(t("queue_empty", _lang()))
769-
return
773+
remove_indices: list[int] = []
774+
dir_key = f"_dl_dir_{client}"
775+
if download_queue:
776+
qcache_key = f"_queue_rows_{client}"
777+
qcache_fp = (id(files), tuple(queued_indices))
778+
if st.session_state.get("_q_cache_fp") != qcache_fp:
779+
st.session_state["_q_cache_fp"] = qcache_fp
780+
st.session_state[qcache_key] = [
781+
{
782+
"": idx,
783+
"File": f.basename,
784+
"Size (bytes)": int(f.size),
785+
"Size": naturalsize(int(f.size)),
786+
}
787+
for idx, f in enumerate(download_queue)
788+
]
789+
queue_df = pd.DataFrame(st.session_state[qcache_key]).set_index("")
790+
791+
selection = st.dataframe(
792+
queue_df,
793+
width="stretch",
794+
height=min(35 * len(download_queue) + 38, 250),
795+
on_select="rerun",
796+
selection_mode="multi-row",
797+
column_config=_size_column_config(),
798+
)
770799

771-
qcache_key = f"_queue_rows_{client}"
772-
qcache_fp = (id(files), tuple(queued_indices))
773-
if st.session_state.get("_q_cache_fp") != qcache_fp:
774-
st.session_state["_q_cache_fp"] = qcache_fp
775-
st.session_state[qcache_key] = [
776-
{
777-
"": idx,
778-
"File": f.basename,
779-
"Size (bytes)": int(f.size),
780-
"Size": naturalsize(int(f.size)),
781-
}
782-
for idx, f in enumerate(download_queue)
783-
]
784-
queue_df = pd.DataFrame(st.session_state[qcache_key]).set_index("")
800+
remove_indices = selection.selection.get("rows", []) # type: ignore
785801

786-
selection = st.dataframe(
787-
queue_df,
788-
width="stretch",
789-
height=min(35 * len(download_queue) + 38, 250),
790-
on_select="rerun",
791-
selection_mode="multi-row",
792-
column_config=_size_column_config(),
793-
)
802+
dataset_name = st.session_state.get(
803+
f"_query_dataset_{client}", ""
804+
).lower()
805+
default_dir = str(
806+
CACHEPATH / "downloads" / client / (dataset_name or "data")
807+
)
794808

795-
remove_indices = selection.selection.get("rows", []) # type: ignore
809+
if dir_key not in st.session_state:
810+
st.session_state[dir_key] = default_dir
796811

797-
dataset_name = st.session_state.get(f"_query_dataset_{client}", "").lower()
798-
default_dir = str(
799-
CACHEPATH / "downloads" / client / (dataset_name or "data")
800-
)
812+
pending = st.session_state.pop("_dl_pending_" + client, None)
813+
if pending:
814+
st.session_state[dir_key] = pending
801815

802-
dir_key = f"_dl_dir_{client}"
803-
if dir_key not in st.session_state:
804-
st.session_state[dir_key] = default_dir
805-
806-
pending = st.session_state.pop("_dl_pending_" + client, None)
807-
if pending:
808-
st.session_state[dir_key] = pending
809-
810-
col_dir, col_btn = st.columns([4, 1])
811-
with col_dir:
812-
st.text_input(
813-
t("save_to", _lang()),
814-
key=dir_key,
815-
label_visibility="collapsed",
816-
)
817-
with col_btn:
818-
if st.button(t("browse", _lang()), width="stretch"):
819-
folder = native_dir_picker(
820-
title=t("browse_dir_title", _lang()),
821-
initialdir=st.session_state[dir_key],
816+
col_dir, col_btn = st.columns([4, 1])
817+
with col_dir:
818+
st.text_input(
819+
t("save_to", _lang()),
820+
key=dir_key,
821+
label_visibility="collapsed",
822822
)
823-
if folder:
824-
st.session_state["_dl_pending_" + client] = folder
825-
st.rerun()
823+
with col_btn:
824+
if st.button(t("browse", _lang()), width="stretch"):
825+
folder = native_dir_picker(
826+
title=t("browse_dir_title", _lang()),
827+
initialdir=st.session_state[dir_key],
828+
)
829+
if folder:
830+
st.session_state["_dl_pending_" + client] = folder
831+
st.rerun()
832+
else:
833+
st.caption(t("queue_empty", _lang()))
834+
835+
actions = download_action_availability(
836+
len(selected_indices), len(remove_indices), len(download_queue)
837+
)
826838
col1, col2, col3 = st.columns(3)
827839
with col1:
828-
if remove_indices and st.button(t("remove", _lang()), width="stretch"):
840+
if st.button(
841+
t("remove", _lang()),
842+
width="stretch",
843+
disabled=not actions.remove,
844+
):
829845
remove_targets = {queued_indices[i] for i in remove_indices}
830846
st.session_state[queue_key] = [
831847
i for i in queued_indices if i not in remove_targets
832848
]
833849
st.rerun()
834850
with col2:
835-
if st.button(t("clear", _lang()), width="stretch"):
851+
if st.button(
852+
t("clear", _lang()),
853+
width="stretch",
854+
disabled=not actions.clear,
855+
):
836856
st.session_state[queue_key] = []
837857
st.rerun()
838858
with col3:
839-
if st.button(t("download", _lang()), width="stretch", type="primary"):
859+
if st.button(
860+
t("download", _lang()),
861+
width="stretch",
862+
type="primary",
863+
disabled=not actions.download,
864+
):
840865
_download_selected(
841866
pysus, client, download_queue, st.session_state[dir_key]
842867
)

0 commit comments

Comments
 (0)