Skip to content

Commit b0c1648

Browse files
committed
add target path popup for file download in UI
1 parent c52f440 commit b0c1648

15 files changed

Lines changed: 315 additions & 168 deletions

File tree

coman/src/app/ids.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub enum Id {
88
InfoPopup,
99
ErrorPopup,
1010
LoginPopup,
11+
DownloadPopup,
1112
SystemSelectPopup,
1213
FileView,
1314
}

coman/src/app/messages.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::path::PathBuf;
2+
13
use crate::{app::user_events::UserEvent, cscs::api_client::System};
24

35
#[derive(Debug, PartialEq)]
@@ -27,6 +29,12 @@ pub enum LoginPopupMsg {
2729
LoginDone(String, String),
2830
}
2931
#[derive(Debug, PartialEq)]
32+
pub enum DownloadPopupMsg {
33+
Opened(PathBuf),
34+
PathSet(PathBuf, PathBuf),
35+
Closed,
36+
}
37+
#[derive(Debug, PartialEq)]
3038
pub enum SystemSelectMsg {
3139
Opened(Vec<System>),
3240
Closed,
@@ -57,6 +65,7 @@ pub enum Msg {
5765
InfoPopup(InfoPopupMsg),
5866
ErrorPopup(ErrorPopupMsg),
5967
LoginPopup(LoginPopupMsg),
68+
DownloadPopup(DownloadPopupMsg),
6069
SystemSelectPopup(SystemSelectMsg),
6170
Error(String),
6271
Info(String),

coman/src/app/model.rs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,23 @@ use tuirealm::{
1313
use crate::{
1414
app::{
1515
ids::Id,
16-
messages::{CscsMsg, ErrorPopupMsg, InfoPopupMsg, JobMsg, LoginPopupMsg, MenuMsg, Msg, SystemSelectMsg, View},
16+
messages::{
17+
CscsMsg, DownloadPopupMsg, ErrorPopupMsg, InfoPopupMsg, JobMsg, LoginPopupMsg, MenuMsg, Msg,
18+
SystemSelectMsg, View,
19+
},
1720
user_events::{CscsEvent, UserEvent},
1821
},
1922
components::{
20-
context_menu::ContextMenu, error_popup::ErrorPopup, info_popup::InfoPopup, login_popup::LoginPopup,
21-
system_select_popup::SystemSelectPopup, workload_list::WorkloadList,
23+
context_menu::ContextMenu, download_popup::DownloadTargetInput, error_popup::ErrorPopup, info_popup::InfoPopup,
24+
login_popup::LoginPopup, system_select_popup::SystemSelectPopup, workload_list::WorkloadList,
2225
workload_log::WorkloadLog,
2326
},
24-
cscs::handlers::{cscs_login, cscs_system_set},
27+
cscs::{
28+
handlers::{cscs_login, cscs_system_set},
29+
ports::TreeAction,
30+
},
2531
trace_dbg,
26-
util::ui::draw_area_in_absolute,
32+
util::ui::{draw_area_in_absolute, draw_area_in_absolute_fixed_height},
2733
};
2834

2935
pub struct Model<T>
@@ -54,6 +60,9 @@ where
5460

5561
/// Allows creating user events based on messages
5662
pub user_event_tx: mpsc::Sender<UserEvent>,
63+
64+
/// Allows interacting with the file Api
65+
pub file_tree_tx: mpsc::Sender<TreeAction>,
5766
}
5867

5968
impl<T> Model<T>
@@ -67,6 +76,7 @@ where
6776
select_system_tx: mpsc::Sender<()>,
6877
job_log_tx: mpsc::Sender<Option<usize>>,
6978
user_event_tx: mpsc::Sender<UserEvent>,
79+
file_tree_tx: mpsc::Sender<TreeAction>,
7080
) -> Self {
7181
Self {
7282
app,
@@ -78,6 +88,7 @@ where
7888
select_system_tx,
7989
job_log_tx,
8090
user_event_tx,
91+
file_tree_tx,
8192
}
8293
}
8394

@@ -125,6 +136,10 @@ where
125136
let popup = draw_area_in_absolute(f.area(), 10);
126137
f.render_widget(Clear, popup);
127138
app.view(&Id::SystemSelectPopup, f, popup);
139+
} else if app.mounted(&Id::DownloadPopup) {
140+
let popup = draw_area_in_absolute_fixed_height(f.area(), 10, 3);
141+
f.render_widget(Clear, popup);
142+
app.view(&Id::DownloadPopup, f, popup);
128143
}
129144
})
130145
.is_ok()
@@ -227,6 +242,38 @@ where
227242
}
228243
}
229244
}
245+
fn handle_download_popup_msg(&mut self, msg: DownloadPopupMsg) -> Option<Msg> {
246+
match msg {
247+
DownloadPopupMsg::Opened(remote_path) => {
248+
if self.app.mounted(&Id::DownloadPopup) {
249+
assert!(self.app.umount(&Id::DownloadPopup).is_ok());
250+
}
251+
assert!(
252+
self.app
253+
.mount(
254+
Id::DownloadPopup,
255+
Box::new(DownloadTargetInput::new(remote_path)),
256+
vec![]
257+
)
258+
.is_ok()
259+
);
260+
assert!(self.app.active(&Id::DownloadPopup).is_ok());
261+
None
262+
}
263+
DownloadPopupMsg::PathSet(remote, local) => {
264+
assert!(self.app.umount(&Id::DownloadPopup).is_ok());
265+
let file_tx = self.file_tree_tx.clone();
266+
tokio::spawn(async move {
267+
file_tx.send(TreeAction::Download(remote, local)).await.unwrap();
268+
});
269+
None
270+
}
271+
DownloadPopupMsg::Closed => {
272+
assert!(self.app.umount(&Id::DownloadPopup).is_ok());
273+
None
274+
}
275+
}
276+
}
230277
fn handle_menu_msg(&mut self, msg: MenuMsg) -> Option<Msg> {
231278
match msg {
232279
MenuMsg::Opened => {
@@ -328,6 +375,7 @@ where
328375
Msg::Menu(menu_msg) => self.handle_menu_msg(menu_msg),
329376
Msg::ErrorPopup(popup_msg) => self.handle_error_popup_msg(popup_msg),
330377
Msg::InfoPopup(popup_msg) => self.handle_info_popup_msg(popup_msg),
378+
Msg::DownloadPopup(popup_msg) => self.handle_download_popup_msg(popup_msg),
331379
Msg::Cscs(CscsMsg::Login(client_id, client_secret)) => {
332380
let event_tx = self.user_event_tx.clone();
333381
let error_tx = self.error_tx.clone();

coman/src/app/user_events.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::path::PathBuf;
2-
31
use crate::{
42
app::messages::View,
53
cscs::api_client::{Job, PathEntry, System},
@@ -16,8 +14,8 @@ pub enum CscsEvent {
1614
#[derive(Debug, Eq, Clone, PartialEq, PartialOrd, Ord)]
1715
pub enum FileEvent {
1816
List(String, Vec<PathEntry>), // Id, Subpaths
19-
DownloadFile(PathBuf),
2017
DownloadCurrentFile,
18+
DownloadSuccessful,
2119
}
2220
#[derive(Debug, Eq, Clone, PartialOrd, Ord)]
2321
pub enum UserEvent {

coman/src/components/context_menu.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use tuirealm::{
88

99
use crate::app::{
1010
messages::{MenuMsg, Msg, View},
11-
user_events::UserEvent,
11+
user_events::{FileEvent, UserEvent},
1212
};
1313

1414
#[derive(MockComponent)]
@@ -42,9 +42,8 @@ impl ContextMenu {
4242
.add_row()
4343
.add_col(TextSpan::from("Switch System").fg(Color::Cyan))
4444
.add_row()
45-
// TODO: removed until we add a popup to set the target path for downloads
46-
// .add_col(TextSpan::from("Download").fg(Color::Cyan))
47-
// .add_row()
45+
.add_col(TextSpan::from("Download").fg(Color::Cyan))
46+
.add_row()
4847
.add_col(TextSpan::from("Quit").fg(Color::Cyan))
4948
.add_row()
5049
.build()
@@ -53,10 +52,10 @@ impl ContextMenu {
5352
match index {
5453
0 => Some(Msg::Menu(MenuMsg::CscsLogin)),
5554
1 => Some(Msg::Menu(MenuMsg::CscsSwitchSystem)),
56-
// 2 => Some(Msg::Menu(MenuMsg::Event(UserEvent::File(
57-
// FileEvent::DownloadCurrentFile,
58-
// )))),
59-
2 => Some(Msg::AppClose),
55+
2 => Some(Msg::Menu(MenuMsg::Event(UserEvent::File(
56+
FileEvent::DownloadCurrentFile,
57+
)))),
58+
3 => Some(Msg::AppClose),
6059
_ => Some(Msg::Menu(MenuMsg::Closed)),
6160
}
6261
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use std::path::PathBuf;
2+
3+
use tui_realm_stdlib::Input;
4+
use tuirealm::{
5+
Component, Event, MockComponent, State, StateValue,
6+
command::{Cmd, CmdResult, Direction, Position},
7+
event::{Key, KeyEvent, KeyModifiers},
8+
props::{Alignment, BorderType, Borders, Color, InputType, Style},
9+
};
10+
11+
use crate::app::{
12+
messages::{DownloadPopupMsg, Msg},
13+
user_events::UserEvent,
14+
};
15+
16+
#[derive(MockComponent)]
17+
pub struct DownloadTargetInput {
18+
component: Input,
19+
remote_path: PathBuf,
20+
}
21+
22+
impl DownloadTargetInput {
23+
pub fn new(remote_path: PathBuf) -> Self {
24+
Self {
25+
component: Input::default()
26+
.borders(Borders::default().modifiers(BorderType::Thick).color(Color::Green))
27+
.input_type(InputType::Custom(
28+
|path| {
29+
let path = PathBuf::from(path);
30+
let parent = path.parent();
31+
match parent {
32+
Some(parent) => parent.exists() && path.to_str().is_some(),
33+
None => false,
34+
}
35+
},
36+
|_, _| true,
37+
))
38+
.title("Download Target Path", Alignment::Left)
39+
.invalid_style(Style::default().fg(Color::Red)),
40+
remote_path,
41+
}
42+
}
43+
}
44+
45+
impl Component<Msg, UserEvent> for DownloadTargetInput {
46+
fn on(&mut self, ev: tuirealm::Event<UserEvent>) -> Option<Msg> {
47+
let _ = match ev {
48+
Event::Keyboard(KeyEvent { code: Key::Left, .. }) => self.perform(Cmd::Move(Direction::Left)),
49+
Event::Keyboard(KeyEvent { code: Key::Right, .. }) => self.perform(Cmd::Move(Direction::Right)),
50+
Event::Keyboard(KeyEvent { code: Key::Home, .. }) => self.perform(Cmd::GoTo(Position::Begin)),
51+
Event::Keyboard(KeyEvent { code: Key::End, .. }) => self.perform(Cmd::GoTo(Position::End)),
52+
Event::Keyboard(KeyEvent { code: Key::Delete, .. }) => self.perform(Cmd::Cancel),
53+
Event::Keyboard(KeyEvent {
54+
code: Key::Backspace, ..
55+
}) => self.perform(Cmd::Delete),
56+
Event::Keyboard(KeyEvent {
57+
code: Key::Char(ch),
58+
modifiers: KeyModifiers::NONE,
59+
}) => self.perform(Cmd::Type(ch)),
60+
Event::Keyboard(KeyEvent {
61+
code: Key::Enter,
62+
modifiers: KeyModifiers::NONE,
63+
}) => {
64+
if let State::One(StateValue::String(target)) = self.state() {
65+
let target = PathBuf::from(target);
66+
return Some(Msg::DownloadPopup(DownloadPopupMsg::PathSet(
67+
self.remote_path.clone(),
68+
target,
69+
)));
70+
}
71+
72+
CmdResult::None
73+
}
74+
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
75+
return Some(Msg::DownloadPopup(DownloadPopupMsg::Closed));
76+
}
77+
_ => CmdResult::None,
78+
};
79+
Some(Msg::None)
80+
}
81+
}

coman/src/components/file_tree.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::iter;
1+
use std::{iter, path::PathBuf};
22

33
use tokio::sync::mpsc;
44
use tui_realm_treeview::{Node, NodeValue, TREE_CMD_CLOSE, TREE_CMD_OPEN, Tree, TreeView};
@@ -11,11 +11,10 @@ use tuirealm::{
1111

1212
use crate::{
1313
app::{
14-
messages::Msg,
14+
messages::{DownloadPopupMsg, Msg},
1515
user_events::{FileEvent, UserEvent},
1616
},
1717
cscs::{api_client::PathType, ports::TreeAction},
18-
trace_dbg,
1918
};
2019

2120
#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)]
@@ -57,7 +56,7 @@ impl FileTree {
5756

5857
// Load root node
5958
let tree_tx = file_tree_tx.clone();
60-
tokio::spawn(async move { tree_tx.send(TreeAction::List("/".to_owned())).await.unwrap() });
59+
tokio::spawn(async move { tree_tx.send(TreeAction::List(PathBuf::from("/"))).await.unwrap() });
6160

6261
Self {
6362
component: TreeView::default()
@@ -98,7 +97,7 @@ impl Component<Msg, UserEvent> for FileTree {
9897
// try loading children if there are none
9998
let tree_tx = self.file_tree_tx.clone();
10099
tokio::spawn(async move {
101-
tree_tx.send(TreeAction::List(current_id)).await.unwrap();
100+
tree_tx.send(TreeAction::List(PathBuf::from(current_id))).await.unwrap();
102101
});
103102
CmdResult::None
104103
} else {
@@ -149,11 +148,8 @@ impl Component<Msg, UserEvent> for FileTree {
149148
}
150149
Event::User(UserEvent::File(FileEvent::DownloadCurrentFile)) => {
151150
if let State::One(StateValue::String(id)) = self.state() {
152-
trace_dbg!("download current file");
153-
let tree_tx = self.file_tree_tx.clone();
154-
tokio::spawn(async move {
155-
tree_tx.send(TreeAction::Download(id)).await.unwrap();
156-
});
151+
let path = PathBuf::from(id);
152+
return Some(Msg::DownloadPopup(DownloadPopupMsg::Opened(path)));
157153
}
158154
CmdResult::None
159155
}

coman/src/components/global_listener.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use tuirealm::{
55
};
66

77
use crate::app::{
8-
messages::{MenuMsg, Msg, SystemSelectMsg, View},
9-
user_events::{CscsEvent, UserEvent},
8+
messages::{InfoPopupMsg, MenuMsg, Msg, SystemSelectMsg, View},
9+
user_events::{CscsEvent, FileEvent, UserEvent},
1010
};
1111

1212
#[derive(Default, MockComponent)]
@@ -46,6 +46,9 @@ impl Component<Msg, UserEvent> for GlobalListener {
4646
Event::User(UserEvent::Cscs(CscsEvent::SelectSystemList(systems))) => {
4747
Some(Msg::SystemSelectPopup(SystemSelectMsg::Opened(systems)))
4848
}
49+
Event::User(UserEvent::File(FileEvent::DownloadSuccessful)) => Some(Msg::InfoPopup(InfoPopupMsg::Opened(
50+
"File successfully downloaded".to_owned(),
51+
))),
4952
_ => None,
5053
}
5154
}

coman/src/components/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub(crate) mod context_menu;
2+
pub(crate) mod download_popup;
23
pub(crate) mod error_popup;
34
pub(crate) mod file_tree;
45
pub(crate) mod global_listener;

coman/src/config.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use eyre::eyre;
88
use lazy_static::lazy_static;
99
use serde::{Deserialize, Serialize};
1010

11-
1211
const DEFAULT_CONFIG_TOML: &str = include_str!("../.config/config.toml");
1312

1413
#[derive(Clone, Debug, Serialize, Deserialize, Default)]

0 commit comments

Comments
 (0)