Skip to content

Commit 1c2690d

Browse files
cs7-shreyshreyJoeZiminskipre-commit-ci[bot]
authored
Suggest next sub ses remote (#484)
* add: custom spinner widget * add: search remote for suggestions checkbox * remove: minor bug * add: tooltips for search remote checkbox * convert fill input to a worker; add a spinner to indicate loading * fix merge conflicts, slight refactor * update: display popup for suggesting next sub/ses remote * update: handle popup closing on error * slight refactor * rename remote to central and change tooltip * refactor functions and some minor changes * add: new tests for searching central for suggestions feature * fix: failing existing tests * first attempt at fixing tests * Apply suggestions from code review edit: some comments and docstrings Co-authored-by: Joe Ziminski <[email protected]> * refactor: apply code review changes * fix minor bug * first attempt at testing error popup * minor changes * remove: existing textual version bug * fix: minor bug * update: enhanced decorator for input box * refactor: changes to decorator specifying id of clicked element * Revert `node` rename * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix linting. * Revert node->event. * Use correct event types, and use these in the double click decorator. --------- Co-authored-by: shrey <shrey@kali> Co-authored-by: Joe Ziminski <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: JoeZiminski <[email protected]>
1 parent be550d5 commit 1c2690d

File tree

16 files changed

+534
-68
lines changed

16 files changed

+534
-68
lines changed

datashuttle/configs/canonical_configs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ def get_tui_config_defaults() -> Dict:
241241
"bypass_validation": False,
242242
"overwrite_existing_files": "never",
243243
"dry_run": False,
244+
"suggest_next_sub_ses_central": False,
244245
}
245246
}
246247

datashuttle/datashuttle_class.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1547,7 +1547,11 @@ def _update_settings_with_new_canonical_keys(self, settings: Dict):
15471547
if "tui" not in settings:
15481548
settings.update(canonical_tui_configs)
15491549

1550-
for key in ["overwrite_existing_files", "dry_run"]:
1550+
for key in [
1551+
"overwrite_existing_files",
1552+
"dry_run",
1553+
"suggest_next_sub_ses_central",
1554+
]:
15511555
if key not in settings["tui"]:
15521556
settings["tui"][key] = canonical_tui_configs["tui"][key]
15531557

datashuttle/tui/app.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ def load_project_page(self, interface: Interface) -> None:
115115
def show_modal_error_dialog(self, message: str) -> None:
116116
self.push_screen(modal_dialogs.MessageBox(message, border_color="red"))
117117

118+
def show_modal_error_dialog_from_main_thread(self, message: str) -> None:
119+
"""
120+
Used to call `show_modal_error_dialog from main thread when executing
121+
in another thread. Throws error when called from main thread.
122+
"""
123+
self.call_from_thread(self.show_modal_error_dialog, message)
124+
118125
def handle_open_filesystem_browser(self, path_: Path) -> None:
119126
"""
120127
Open the system file browser to the path with the `showinfm`

datashuttle/tui/css/tui_menu.tcss

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,16 @@ CreateFoldersSettingsScreen {
313313
width: 80%;
314314
background: $primary-background;
315315
border: tall $panel-lighten-3;
316-
}
316+
}
317+
#checkbox_container {
318+
height: 65%;
319+
overflow: hidden auto;
320+
}
321+
#toplevel_folder_select_container {
322+
height: 15%;
323+
}
317324
#template_top_container {
318-
height: 50%;
325+
height: 70%;
319326
background: $primary-background;
320327
border: tall $panel-lighten-3;
321328
overflow: hidden auto;
@@ -436,6 +443,41 @@ ConfirmAndAwaitTransferPopup {
436443
content-align: center middle;
437444
}
438445

446+
/* Suggest next subject / session loading pop up --------------------------------------------------- */
447+
448+
SearchingCentralForNextSubSesPopup {
449+
align: center middle;
450+
}
451+
452+
#searching_top_container {
453+
align: center middle;
454+
height: 15;
455+
width: 65;
456+
border: tall $panel-lighten-1;
457+
background: $primary-background;
458+
}
459+
460+
#searching_top_container:light {
461+
background: $boost;
462+
border: tall $panel-darken-3;
463+
}
464+
465+
#searching_message_label {
466+
align: center middle;
467+
text-align: center;
468+
overflow: hidden auto;
469+
width: 100%;
470+
margin: 1 0 0 0;
471+
}
472+
473+
#searching_animated_indicator {
474+
align: center middle;
475+
padding: 0;
476+
height: 3;
477+
margin: 1 0 0 0;
478+
content-align: center middle;
479+
}
480+
439481
/* Light Mode Error Screen */
440482

441483
MessageBox:light > #confirm_top_container {

datashuttle/tui/interface.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -432,28 +432,28 @@ def get_textual_compatible_project_configs(self) -> Configs:
432432
return cfg_to_load
433433

434434
def get_next_sub(
435-
self, top_level_folder: TopLevelFolder
435+
self, top_level_folder: TopLevelFolder, include_central: bool
436436
) -> InterfaceOutput:
437437
try:
438438
next_sub = self.project.get_next_sub(
439439
top_level_folder,
440440
return_with_prefix=True,
441-
include_central=False,
441+
include_central=include_central,
442442
)
443443
return True, next_sub
444444
except BaseException as e:
445445
return False, str(e)
446446

447447
def get_next_ses(
448-
self, top_level_folder: TopLevelFolder, sub: str
448+
self, top_level_folder: TopLevelFolder, sub: str, include_central: bool
449449
) -> InterfaceOutput:
450450

451451
try:
452452
next_ses = self.project.get_next_ses(
453453
top_level_folder,
454454
sub,
455455
return_with_prefix=True,
456-
include_central=False,
456+
include_central=include_central,
457457
)
458458
return True, next_ses
459459
except BaseException as e:

datashuttle/tui/screens/create_folder_settings.py

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ def compose(self) -> ComposeResult:
8686
"""
8787

8888
bypass_validation = self.interface.tui_settings["bypass_validation"]
89+
suggest_next_sub_ses_central = self.interface.tui_settings[
90+
"suggest_next_sub_ses_central"
91+
]
8992

9093
yield Container(
9194
Horizontal(
@@ -97,43 +100,52 @@ def compose(self) -> ComposeResult:
97100
self.interface,
98101
id="create_folders_settings_toplevel_select",
99102
),
100-
),
101-
Checkbox(
102-
"Bypass validation",
103-
value=bypass_validation,
104-
id="create_folders_settings_bypass_validation_checkbox",
103+
id="toplevel_folder_select_container",
105104
),
106105
Container(
107-
Horizontal(
108-
Checkbox(
109-
"Template Validation",
110-
id="template_settings_validation_on_checkbox",
111-
value=self.interface.get_name_templates()["on"],
112-
),
113-
id="template_inner_horizontal_container",
106+
Checkbox(
107+
"Search Central For Suggestions",
108+
value=suggest_next_sub_ses_central,
109+
id="suggest_next_sub_ses_central_checkbox",
110+
),
111+
Checkbox(
112+
"Bypass validation",
113+
value=bypass_validation,
114+
id="create_folders_settings_bypass_validation_checkbox",
114115
),
115116
Container(
116-
Label(explanation, id="template_message_label"),
117+
Horizontal(
118+
Checkbox(
119+
"Template validation",
120+
id="template_settings_validation_on_checkbox",
121+
value=self.interface.get_name_templates()["on"],
122+
),
123+
id="template_inner_horizontal_container",
124+
),
117125
Container(
118-
RadioSet(
119-
RadioButton(
120-
"Subject",
121-
id="template_settings_subject_radiobutton",
122-
value=sub_on,
123-
),
124-
RadioButton(
125-
"Session",
126-
id="template_settings_session_radiobutton",
127-
value=ses_on,
126+
Label(explanation, id="template_message_label"),
127+
Container(
128+
RadioSet(
129+
RadioButton(
130+
"Subject",
131+
id="template_settings_subject_radiobutton",
132+
value=sub_on,
133+
),
134+
RadioButton(
135+
"Session",
136+
id="template_settings_session_radiobutton",
137+
value=ses_on,
138+
),
139+
id="template_settings_radioset",
128140
),
129-
id="template_settings_radioset",
141+
Input(id="template_settings_input"),
142+
id="template_other_widgets_container",
130143
),
131-
Input(id="template_settings_input"),
132-
id="template_other_widgets_container",
144+
id="template_inner_container",
133145
),
134-
id="template_inner_container",
146+
id="template_top_container",
135147
),
136-
id="template_top_container",
148+
id="checkbox_container",
137149
),
138150
Container(),
139151
Button("Close", id="create_folders_settings_close_button"),
@@ -145,6 +157,7 @@ def on_mount(self) -> None:
145157
"#create_folders_settings_toplevel_select",
146158
"#create_folders_settings_bypass_validation_checkbox",
147159
"#template_settings_validation_on_checkbox",
160+
"#suggest_next_sub_ses_central_checkbox",
148161
]:
149162
self.query_one(id).tooltip = get_tooltip(id)
150163

@@ -235,6 +248,10 @@ def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
235248
self.query_one("#template_inner_container").disabled = (
236249
disable_container
237250
)
251+
elif event.checkbox.id == "suggest_next_sub_ses_central_checkbox":
252+
self.interface.save_tui_settings(
253+
is_on, "suggest_next_sub_ses_central"
254+
)
238255

239256
def on_radio_set_changed(self, event: RadioSet.Changed) -> None:
240257
"""

datashuttle/tui/screens/modal_dialogs.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
from pathlib import Path
88

99
from textual.app import ComposeResult
10+
from textual.widgets import DirectoryTree
1011
from textual.worker import Worker
1112

1213
from datashuttle.tui.app import TuiApp
13-
from datashuttle.utils.custom_types import InterfaceOutput
14+
from datashuttle.utils.custom_types import InterfaceOutput, Prefix
1415

1516
from pathlib import Path
1617

@@ -19,7 +20,10 @@
1920
from textual.widgets import Button, Input, Label, LoadingIndicator, Static
2021

2122
from datashuttle.tui.custom_widgets import CustomDirectoryTree
22-
from datashuttle.tui.utils.tui_decorators import require_double_click
23+
from datashuttle.tui.utils.tui_decorators import (
24+
ClickInfo,
25+
require_double_click,
26+
)
2327

2428

2529
class MessageBox(ModalScreen):
@@ -137,6 +141,27 @@ async def handle_transfer_and_update_ui_when_complete(self) -> None:
137141
self.app.show_modal_error_dialog(output)
138142

139143

144+
class SearchingCentralForNextSubSesPopup(ModalScreen):
145+
"""
146+
A popup to show message and a loading indicator when awaiting search next sub/ses across
147+
the folders present in both local and central machines. This search happens in a separate
148+
thread so as to allow TUI to display the loading indicate without freezing.
149+
150+
Only displayed when the `include_central` flag is checked and the connection method is "ssh".
151+
"""
152+
153+
def __init__(self, sub_or_ses: Prefix) -> None:
154+
super().__init__()
155+
self.message = f"Searching central for next {sub_or_ses}"
156+
157+
def compose(self) -> ComposeResult:
158+
yield Container(
159+
Label(self.message, id="searching_message_label"),
160+
LoadingIndicator(id="searching_animated_indicator"),
161+
id="searching_top_container",
162+
)
163+
164+
140165
class SelectDirectoryTreeScreen(ModalScreen):
141166
"""
142167
A modal screen that includes a DirectoryTree to browse
@@ -165,7 +190,7 @@ def __init__(
165190
path_ = Path().home()
166191
self.path_ = path_
167192

168-
self.prev_click_time = 0
193+
self.click_info = ClickInfo()
169194

170195
def compose(self) -> ComposeResult:
171196

@@ -186,11 +211,13 @@ def compose(self) -> ComposeResult:
186211
)
187212

188213
@require_double_click
189-
def on_directory_tree_directory_selected(self, node) -> None:
190-
if node.path.is_file():
214+
def on_directory_tree_directory_selected(
215+
self, event: DirectoryTree.DirectorySelected
216+
) -> None:
217+
if event.path.is_file():
191218
return
192219
else:
193-
self.dismiss(node.path)
220+
self.dismiss(event.path)
194221

195222
def on_button_pressed(self, event: Button.Pressed) -> None:
196223
if event.button.id == "cancel_button":

0 commit comments

Comments
 (0)