mirror of
https://github.com/veeso/termscp.git
synced 2026-09-27 06:21:22 -07:00
Migrated termscp to tui-realm 1.x
This commit is contained in:
committed by
Christian Visintin
parent
30851a78e8
commit
54b5583d1a
@@ -33,8 +33,6 @@ use crate::system::environment;
|
||||
|
||||
// Ext
|
||||
use std::path::PathBuf;
|
||||
use tui_realm_stdlib::{InputPropsBuilder, RadioPropsBuilder};
|
||||
use tuirealm::PropsBuilder;
|
||||
|
||||
impl AuthActivity {
|
||||
/// ### del_bookmark
|
||||
@@ -234,12 +232,8 @@ impl AuthActivity {
|
||||
/// Load bookmark data into the gui components
|
||||
fn load_bookmark_into_gui(&mut self, bookmark: FileTransferParams) {
|
||||
// Load parameters into components
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_RADIO_PROTOCOL) {
|
||||
let props = RadioPropsBuilder::from(props)
|
||||
.with_value(Self::protocol_enum_to_opt(bookmark.protocol))
|
||||
.build();
|
||||
self.view.update(super::COMPONENT_RADIO_PROTOCOL, props);
|
||||
}
|
||||
self.protocol = bookmark.protocol;
|
||||
self.mount_protocol(bookmark.protocol);
|
||||
match bookmark.params {
|
||||
ProtocolParams::AwsS3(params) => self.load_bookmark_s3_into_gui(params),
|
||||
ProtocolParams::Generic(params) => self.load_bookmark_generic_into_gui(params),
|
||||
@@ -247,51 +241,15 @@ impl AuthActivity {
|
||||
}
|
||||
|
||||
fn load_bookmark_generic_into_gui(&mut self, params: GenericProtocolParams) {
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_ADDR) {
|
||||
let props = InputPropsBuilder::from(props)
|
||||
.with_value(params.address.clone())
|
||||
.build();
|
||||
self.view.update(super::COMPONENT_INPUT_ADDR, props);
|
||||
}
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_PORT) {
|
||||
let props = InputPropsBuilder::from(props)
|
||||
.with_value(params.port.to_string())
|
||||
.build();
|
||||
self.view.update(super::COMPONENT_INPUT_PORT, props);
|
||||
}
|
||||
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_USERNAME) {
|
||||
let props = InputPropsBuilder::from(props)
|
||||
.with_value(params.username.as_deref().unwrap_or_default().to_string())
|
||||
.build();
|
||||
self.view.update(super::COMPONENT_INPUT_USERNAME, props);
|
||||
}
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_PASSWORD) {
|
||||
let props = InputPropsBuilder::from(props)
|
||||
.with_value(params.password.as_deref().unwrap_or_default().to_string())
|
||||
.build();
|
||||
self.view.update(super::COMPONENT_INPUT_PASSWORD, props);
|
||||
}
|
||||
self.mount_address(params.address.as_str());
|
||||
self.mount_port(params.port);
|
||||
self.mount_username(params.username.as_deref().unwrap_or(""));
|
||||
self.mount_password(params.password.as_deref().unwrap_or(""));
|
||||
}
|
||||
|
||||
fn load_bookmark_s3_into_gui(&mut self, params: AwsS3Params) {
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_S3_BUCKET) {
|
||||
let props = InputPropsBuilder::from(props)
|
||||
.with_value(params.bucket_name.clone())
|
||||
.build();
|
||||
self.view.update(super::COMPONENT_INPUT_S3_BUCKET, props);
|
||||
}
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_S3_REGION) {
|
||||
let props = InputPropsBuilder::from(props)
|
||||
.with_value(params.region.clone())
|
||||
.build();
|
||||
self.view.update(super::COMPONENT_INPUT_S3_REGION, props);
|
||||
}
|
||||
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_S3_PROFILE) {
|
||||
let props = InputPropsBuilder::from(props)
|
||||
.with_value(params.profile.as_deref().unwrap_or_default().to_string())
|
||||
.build();
|
||||
self.view.update(super::COMPONENT_INPUT_S3_PROFILE, props);
|
||||
}
|
||||
self.mount_s3_bucket(params.bucket_name.as_str());
|
||||
self.mount_s3_region(params.region.as_str());
|
||||
self.mount_s3_profile(params.profile.as_deref().unwrap_or(""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
//! ## Bookmarks
|
||||
//!
|
||||
//! auth activity bookmarks components
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
use super::Msg;
|
||||
|
||||
use tui_realm_stdlib::{Input, List, Radio};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, InputType, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
// -- bookmark list
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct BookmarksList {
|
||||
component: List,
|
||||
}
|
||||
|
||||
impl BookmarksList {
|
||||
pub fn new(bookmarks: &[String], color: Color) -> Self {
|
||||
Self {
|
||||
component: List::default()
|
||||
.borders(Borders::default().color(color).modifiers(BorderType::Plain))
|
||||
.highlighted_color(color)
|
||||
.rewind(true)
|
||||
.scroll(true)
|
||||
.step(4)
|
||||
.title("Bookmarks", Alignment::Left)
|
||||
.rows(
|
||||
bookmarks
|
||||
.iter()
|
||||
.map(|x| vec![TextSpan::from(x.as_str())])
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for BookmarksList {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||
self.perform(Cmd::Move(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageDown,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageUp, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::Usize(choice)) => Some(Msg::LoadBookmark(choice)),
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => Some(Msg::BookmarksListBlur),
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => Some(Msg::BookmarksTabBlur),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => Some(Msg::ShowDeleteBookmarkPopup),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- recents list
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct RecentsList {
|
||||
component: List,
|
||||
}
|
||||
|
||||
impl RecentsList {
|
||||
pub fn new(bookmarks: &[String], color: Color) -> Self {
|
||||
Self {
|
||||
component: List::default()
|
||||
.borders(Borders::default().color(color).modifiers(BorderType::Plain))
|
||||
.highlighted_color(color)
|
||||
.rewind(true)
|
||||
.scroll(true)
|
||||
.step(4)
|
||||
.title("Recent connections", Alignment::Left)
|
||||
.rows(
|
||||
bookmarks
|
||||
.iter()
|
||||
.map(|x| vec![TextSpan::from(x.as_str())])
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for RecentsList {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||
self.perform(Cmd::Move(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageDown,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageUp, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::Usize(choice)) => Some(Msg::LoadRecent(choice)),
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => Some(Msg::RececentsListBlur),
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => Some(Msg::BookmarksTabBlur),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => Some(Msg::ShowDeleteRecentPopup),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- delete bookmark
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct DeleteBookmarkPopup {
|
||||
component: Radio,
|
||||
}
|
||||
|
||||
impl DeleteBookmarkPopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(&["Yes", "No"])
|
||||
.value(1)
|
||||
.rewind(true)
|
||||
.foreground(color)
|
||||
.title("Delete selected bookmark?", Alignment::Center),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for DeleteBookmarkPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::CloseDeleteBookmark),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::DeleteBookmark)
|
||||
} else {
|
||||
Some(Msg::CloseDeleteBookmark)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- delete recent
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct DeleteRecentPopup {
|
||||
component: Radio,
|
||||
}
|
||||
|
||||
impl DeleteRecentPopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(&["Yes", "No"])
|
||||
.value(1)
|
||||
.rewind(true)
|
||||
.foreground(color)
|
||||
.title("Delete selected recent host?", Alignment::Center),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for DeleteRecentPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::CloseDeleteRecent),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::DeleteRecent)
|
||||
} else {
|
||||
Some(Msg::CloseDeleteRecent)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- bookmark name
|
||||
|
||||
// -- save password
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct BookmarkSavePassword {
|
||||
component: Radio,
|
||||
}
|
||||
|
||||
impl BookmarkSavePassword {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::Reset)
|
||||
.sides(BorderSides::BOTTOM | BorderSides::LEFT | BorderSides::RIGHT)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(&["Yes", "No"])
|
||||
.value(0)
|
||||
.rewind(true)
|
||||
.foreground(color)
|
||||
.title("Save password?", Alignment::Center),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for BookmarkSavePassword {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::CloseSaveBookmark),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => Some(Msg::SaveBookmark),
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => Some(Msg::SaveBookmarkPasswordBlur),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- new bookmark name
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct BookmarkName {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl BookmarkName {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::Reset)
|
||||
.sides(BorderSides::TOP | BorderSides::LEFT | BorderSides::RIGHT)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Bookmark name", Alignment::Left)
|
||||
.input_type(InputType::Text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for BookmarkName {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Cancel);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Delete);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => Some(Msg::SaveBookmark),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => Some(Msg::BookmarkNameBlur),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,694 @@
|
||||
//! ## Form
|
||||
//!
|
||||
//! auth activity components for file transfer params form
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
use super::{FileTransferProtocol, Msg};
|
||||
|
||||
use tui_realm_stdlib::{Input, Radio};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
// -- protocol
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct ProtocolRadio {
|
||||
component: Radio,
|
||||
}
|
||||
|
||||
impl ProtocolRadio {
|
||||
pub fn new(default_protocol: FileTransferProtocol, color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(&["SFTP", "SCP", "FTP", "FTPS", "AWS S3"])
|
||||
.foreground(color)
|
||||
.rewind(true)
|
||||
.title("Protocol", Alignment::Left)
|
||||
.value(Self::protocol_enum_to_opt(default_protocol)),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### protocol_opt_to_enum
|
||||
///
|
||||
/// Convert radio index for protocol into a `FileTransferProtocol`
|
||||
fn protocol_opt_to_enum(protocol: usize) -> FileTransferProtocol {
|
||||
match protocol {
|
||||
1 => FileTransferProtocol::Scp,
|
||||
2 => FileTransferProtocol::Ftp(false),
|
||||
3 => FileTransferProtocol::Ftp(true),
|
||||
4 => FileTransferProtocol::AwsS3,
|
||||
_ => FileTransferProtocol::Sftp,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### protocol_enum_to_opt
|
||||
///
|
||||
/// Convert `FileTransferProtocol` enum into radio group index
|
||||
fn protocol_enum_to_opt(protocol: FileTransferProtocol) -> usize {
|
||||
match protocol {
|
||||
FileTransferProtocol::Sftp => 0,
|
||||
FileTransferProtocol::Scp => 1,
|
||||
FileTransferProtocol::Ftp(false) => 2,
|
||||
FileTransferProtocol::Ftp(true) => 3,
|
||||
FileTransferProtocol::AwsS3 => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ProtocolRadio {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
let result = match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => self.perform(Cmd::Move(Direction::Left)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => self.perform(Cmd::Move(Direction::Right)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => return Some(Msg::Connect),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => return Some(Msg::ProtocolBlurDown),
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => return Some(Msg::ProtocolBlurUp),
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => return Some(Msg::ParamsFormBlur),
|
||||
_ => return None,
|
||||
};
|
||||
match result {
|
||||
CmdResult::Changed(State::One(StateValue::Usize(choice))) => {
|
||||
Some(Msg::ProtocolChanged(Self::protocol_opt_to_enum(choice)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- address
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputAddress {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl InputAddress {
|
||||
pub fn new(host: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("127.0.0.1", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Remote host", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(host),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputAddress {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Cancel);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Delete);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => Some(Msg::Connect),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => Some(Msg::AddressBlurDown),
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => Some(Msg::AddressBlurUp),
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => Some(Msg::ParamsFormBlur),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- port number
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputPort {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl InputPort {
|
||||
pub fn new(port: u16, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("22", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.input_type(InputType::UnsignedInteger)
|
||||
.input_len(5)
|
||||
.title("Port number", Alignment::Left)
|
||||
.value(port.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputPort {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Cancel);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Delete);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => Some(Msg::Connect),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => Some(Msg::PortBlurDown),
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => Some(Msg::PortBlurUp),
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => Some(Msg::ParamsFormBlur),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- username
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputUsername {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl InputUsername {
|
||||
pub fn new(username: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("root", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Username", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(username),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputUsername {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Cancel);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Delete);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => Some(Msg::Connect),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => Some(Msg::UsernameBlurDown),
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => Some(Msg::UsernameBlurUp),
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => Some(Msg::ParamsFormBlur),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- password
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputPassword {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl InputPassword {
|
||||
pub fn new(password: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Password", Alignment::Left)
|
||||
.input_type(InputType::Password('*'))
|
||||
.value(password),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputPassword {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Cancel);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Delete);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => Some(Msg::Connect),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => Some(Msg::PasswordBlurDown),
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => Some(Msg::PasswordBlurUp),
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => Some(Msg::ParamsFormBlur),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- s3 bucket
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputS3Bucket {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl InputS3Bucket {
|
||||
pub fn new(bucket: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("my-bucket", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Bucket name", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(bucket),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3Bucket {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Cancel);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Delete);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => Some(Msg::Connect),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => Some(Msg::S3BucketBlurDown),
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => Some(Msg::S3BucketBlurUp),
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => Some(Msg::ParamsFormBlur),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- s3 bucket
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputS3Region {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl InputS3Region {
|
||||
pub fn new(region: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("eu-west-1", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Region", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(region),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3Region {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Cancel);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Delete);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => Some(Msg::Connect),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => Some(Msg::S3RegionBlurDown),
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => Some(Msg::S3RegionBlurUp),
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => Some(Msg::ParamsFormBlur),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- s3 bucket
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InputS3Profile {
|
||||
component: Input,
|
||||
}
|
||||
|
||||
impl InputS3Profile {
|
||||
pub fn new(profile: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Input::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("default", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Profile", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(profile),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3Profile {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Cancel);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Backspace,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Delete);
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => Some(Msg::Connect),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => Some(Msg::S3ProfileBlurDown),
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => Some(Msg::S3ProfileBlurUp),
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => Some(Msg::ParamsFormBlur),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//! ## Components
|
||||
//!
|
||||
//! auth activity components
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
use super::{FileTransferProtocol, Msg};
|
||||
|
||||
mod bookmarks;
|
||||
mod form;
|
||||
mod popup;
|
||||
mod text;
|
||||
|
||||
pub use bookmarks::{
|
||||
BookmarkName, BookmarkSavePassword, BookmarksList, DeleteBookmarkPopup, DeleteRecentPopup,
|
||||
RecentsList,
|
||||
};
|
||||
pub use form::{
|
||||
InputAddress, InputPassword, InputPort, InputS3Bucket, InputS3Profile, InputS3Region,
|
||||
InputUsername, ProtocolRadio,
|
||||
};
|
||||
pub use popup::{
|
||||
ErrorPopup, InfoPopup, InstallUpdatePopup, Keybindings, QuitPopup, ReleaseNotes, WaitPopup,
|
||||
WindowSizeError,
|
||||
};
|
||||
pub use text::{HelpText, NewVersionDisclaimer, Subtitle, Title};
|
||||
|
||||
use tui_realm_stdlib::Phantom;
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::{Component, MockComponent};
|
||||
|
||||
// -- global listener
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct GlobalListener {
|
||||
component: Phantom,
|
||||
}
|
||||
|
||||
impl Default for GlobalListener {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
component: Phantom::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for GlobalListener {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::ShowQuitPopup),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('c'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}) => Some(Msg::EnterSetup),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('h'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}) => Some(Msg::ShowKeybindingsPopup),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('r'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}) => Some(Msg::ShowReleaseNotes),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('s'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}) => Some(Msg::ShowSaveBookmarkPopup),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
//! ## Popup
|
||||
//!
|
||||
//! auth activity popups
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
use super::Msg;
|
||||
|
||||
use tui_realm_stdlib::{List, Paragraph, Radio, Textarea};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
// -- error popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct ErrorPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
|
||||
impl ErrorPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text(&[TextSpan::from(text.as_ref())])
|
||||
.wrap(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ErrorPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
..
|
||||
}) => Some(Msg::CloseErrorPopup),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- info popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InfoPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
|
||||
impl InfoPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text(&[TextSpan::from(text.as_ref())])
|
||||
.wrap(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InfoPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
..
|
||||
}) => Some(Msg::CloseInfoPopup),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- wait popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct WaitPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
|
||||
impl WaitPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text(&[TextSpan::from(text.as_ref())])
|
||||
.wrap(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for WaitPopup {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// -- window size error
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct WindowSizeError {
|
||||
component: Paragraph,
|
||||
}
|
||||
|
||||
impl WindowSizeError {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text(&[TextSpan::from(
|
||||
"termscp requires at least 24 lines of height to run",
|
||||
)])
|
||||
.wrap(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for WindowSizeError {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// -- quit popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct QuitPopup {
|
||||
component: Radio,
|
||||
}
|
||||
|
||||
impl QuitPopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Quit termscp?", Alignment::Center)
|
||||
.rewind(true)
|
||||
.choices(&["Yes", "No"]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::CloseQuitPopup),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Quit)
|
||||
} else {
|
||||
Some(Msg::CloseQuitPopup)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- install update popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct InstallUpdatePopup {
|
||||
component: Radio,
|
||||
}
|
||||
|
||||
impl InstallUpdatePopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Install update?", Alignment::Center)
|
||||
.rewind(true)
|
||||
.choices(&["Yes", "No"]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InstallUpdatePopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::CloseInstallUpdatePopup),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Left));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::InstallUpdate)
|
||||
} else {
|
||||
Some(Msg::CloseInstallUpdatePopup)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- release notes popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct ReleaseNotes {
|
||||
component: Textarea,
|
||||
}
|
||||
|
||||
impl ReleaseNotes {
|
||||
pub fn new(notes: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Textarea::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Release notes", Alignment::Center)
|
||||
.text_rows(
|
||||
notes
|
||||
.lines()
|
||||
.map(TextSpan::from)
|
||||
.collect::<Vec<TextSpan>>()
|
||||
.as_slice(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ReleaseNotes {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
..
|
||||
}) => Some(Msg::CloseInstallUpdatePopup),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||
self.perform(Cmd::Move(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageDown,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageUp, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- keybindings popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct Keybindings {
|
||||
component: List,
|
||||
}
|
||||
|
||||
impl Keybindings {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: List::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.highlighted_str("? ")
|
||||
.title("Keybindings", Alignment::Center)
|
||||
.scroll(true)
|
||||
.step(4)
|
||||
.rows(
|
||||
TableBuilder::default()
|
||||
.add_col(TextSpan::new("<ESC>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Quit termscp"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<TAB>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Switch from form and bookmarks"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<RIGHT/LEFT>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Switch bookmark tab"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Move up/down in current tab"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<ENTER>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Connect/Load bookmark"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<DEL|E>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Delete selected bookmark"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+C>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Enter setup"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+S>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Save bookmark"))
|
||||
.build(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Keybindings {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
..
|
||||
}) => Some(Msg::CloseKeybindingsPopup),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||
self.perform(Cmd::Move(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageDown,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageUp, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
//! ## Text
|
||||
//!
|
||||
//! auth activity texts
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 Christian Visintin
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
use super::Msg;
|
||||
|
||||
use tui_realm_stdlib::{Label, Span};
|
||||
use tuirealm::props::{Color, TextModifiers, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
|
||||
// -- Title
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct Title {
|
||||
component: Label,
|
||||
}
|
||||
|
||||
impl Default for Title {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
component: Label::default()
|
||||
.modifiers(TextModifiers::BOLD | TextModifiers::ITALIC)
|
||||
.text("$ termscp"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Title {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// -- subtitle
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct Subtitle {
|
||||
component: Label,
|
||||
}
|
||||
|
||||
impl Default for Subtitle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
component: Label::default()
|
||||
.modifiers(TextModifiers::BOLD | TextModifiers::ITALIC)
|
||||
.text(format!("$ version {}", env!("CARGO_PKG_VERSION"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Subtitle {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// -- new version disclaimer
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct NewVersionDisclaimer {
|
||||
component: Span,
|
||||
}
|
||||
|
||||
impl NewVersionDisclaimer {
|
||||
pub fn new(new_version: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Span::default().foreground(color).spans(&[
|
||||
TextSpan::from("termscp "),
|
||||
TextSpan::new(new_version).underlined().bold(),
|
||||
TextSpan::from(
|
||||
" is NOW available! Install update and view release notes with <CTRL+R>",
|
||||
),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for NewVersionDisclaimer {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// -- HelpText
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct HelpText {
|
||||
component: Span,
|
||||
}
|
||||
|
||||
impl HelpText {
|
||||
pub fn new(key_color: Color) -> Self {
|
||||
Self {
|
||||
component: Span::default().spans(&[
|
||||
TextSpan::new("Press ").bold(),
|
||||
TextSpan::new("<CTRL+H>").bold().fg(key_color),
|
||||
TextSpan::new(" to show keybindings; ").bold(),
|
||||
TextSpan::new("<CTRL+C>").bold().fg(key_color),
|
||||
TextSpan::new(" to enter setup").bold(),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for HelpText {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -31,32 +31,6 @@ use crate::system::auto_update::{Release, Update, UpdateStatus};
|
||||
use crate::system::notifications::Notification;
|
||||
|
||||
impl AuthActivity {
|
||||
/// ### protocol_opt_to_enum
|
||||
///
|
||||
/// Convert radio index for protocol into a `FileTransferProtocol`
|
||||
pub(super) fn protocol_opt_to_enum(protocol: usize) -> FileTransferProtocol {
|
||||
match protocol {
|
||||
1 => FileTransferProtocol::Scp,
|
||||
2 => FileTransferProtocol::Ftp(false),
|
||||
3 => FileTransferProtocol::Ftp(true),
|
||||
4 => FileTransferProtocol::AwsS3,
|
||||
_ => FileTransferProtocol::Sftp,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### protocol_enum_to_opt
|
||||
///
|
||||
/// Convert `FileTransferProtocol` enum into radio group index
|
||||
pub(super) fn protocol_enum_to_opt(protocol: FileTransferProtocol) -> usize {
|
||||
match protocol {
|
||||
FileTransferProtocol::Sftp => 0,
|
||||
FileTransferProtocol::Scp => 1,
|
||||
FileTransferProtocol::Ftp(false) => 2,
|
||||
FileTransferProtocol::Ftp(true) => 3,
|
||||
FileTransferProtocol::AwsS3 => 4,
|
||||
}
|
||||
}
|
||||
|
||||
/// ### get_default_port_for_protocol
|
||||
///
|
||||
/// Get the default port for protocol
|
||||
@@ -91,9 +65,8 @@ impl AuthActivity {
|
||||
///
|
||||
/// Collect host params as `FileTransferParams`
|
||||
pub(super) fn collect_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
||||
let protocol: FileTransferProtocol = self.get_protocol();
|
||||
match protocol {
|
||||
FileTransferProtocol::AwsS3 => self.collect_s3_host_params(protocol),
|
||||
match self.protocol {
|
||||
FileTransferProtocol::AwsS3 => self.collect_s3_host_params(),
|
||||
protocol => self.collect_generic_host_params(protocol),
|
||||
}
|
||||
}
|
||||
@@ -135,10 +108,7 @@ impl AuthActivity {
|
||||
/// ### collect_s3_host_params
|
||||
///
|
||||
/// Get input values from fields or return an error if fields are invalid to work as aws s3
|
||||
pub(super) fn collect_s3_host_params(
|
||||
&self,
|
||||
protocol: FileTransferProtocol,
|
||||
) -> Result<FileTransferParams, &'static str> {
|
||||
pub(super) fn collect_s3_host_params(&self) -> Result<FileTransferParams, &'static str> {
|
||||
let (bucket, region, profile): (String, String, Option<String>) =
|
||||
self.get_s3_params_input();
|
||||
if bucket.is_empty() {
|
||||
@@ -148,7 +118,7 @@ impl AuthActivity {
|
||||
return Err("Invalid region");
|
||||
}
|
||||
Ok(FileTransferParams {
|
||||
protocol,
|
||||
protocol: FileTransferProtocol::AwsS3,
|
||||
params: ProtocolParams::AwsS3(AwsS3Params::new(bucket, region, profile)),
|
||||
entry_directory: None,
|
||||
})
|
||||
|
||||
+140
-81
@@ -27,6 +27,7 @@
|
||||
*/
|
||||
// Sub modules
|
||||
mod bookmarks;
|
||||
mod components;
|
||||
mod misc;
|
||||
mod update;
|
||||
mod view;
|
||||
@@ -39,37 +40,101 @@ use crate::system::bookmarks_client::BookmarksClient;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
|
||||
// Includes
|
||||
use crossterm::event::Event;
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use tuirealm::{Update, View};
|
||||
use std::time::Duration;
|
||||
use tuirealm::listener::EventListenerCfg;
|
||||
use tuirealm::{application::PollStrategy, Application, NoUserEvent, Update};
|
||||
|
||||
// -- components
|
||||
const COMPONENT_TEXT_H1: &str = "TEXT_H1";
|
||||
const COMPONENT_TEXT_H2: &str = "TEXT_H2";
|
||||
const COMPONENT_TEXT_NEW_VERSION: &str = "TEXT_NEW_VERSION";
|
||||
const COMPONENT_TEXT_NEW_VERSION_NOTES: &str = "TEXTAREA_NEW_VERSION";
|
||||
const COMPONENT_TEXT_FOOTER: &str = "TEXT_FOOTER";
|
||||
const COMPONENT_TEXT_HELP: &str = "TEXT_HELP";
|
||||
const COMPONENT_TEXT_ERROR: &str = "TEXT_ERROR";
|
||||
const COMPONENT_TEXT_INFO: &str = "TEXT_INFO";
|
||||
const COMPONENT_TEXT_WAIT: &str = "TEXT_WAIT";
|
||||
const COMPONENT_TEXT_SIZE_ERR: &str = "TEXT_SIZE_ERR";
|
||||
const COMPONENT_INPUT_ADDR: &str = "INPUT_ADDRESS";
|
||||
const COMPONENT_INPUT_PORT: &str = "INPUT_PORT";
|
||||
const COMPONENT_INPUT_USERNAME: &str = "INPUT_USERNAME";
|
||||
const COMPONENT_INPUT_PASSWORD: &str = "INPUT_PASSWORD";
|
||||
const COMPONENT_INPUT_BOOKMARK_NAME: &str = "INPUT_BOOKMARK_NAME";
|
||||
const COMPONENT_INPUT_S3_BUCKET: &str = "INPUT_S3_BUCKET";
|
||||
const COMPONENT_INPUT_S3_REGION: &str = "INPUT_S3_REGION";
|
||||
const COMPONENT_INPUT_S3_PROFILE: &str = "INPUT_S3_PROFILE";
|
||||
const COMPONENT_RADIO_PROTOCOL: &str = "RADIO_PROTOCOL";
|
||||
const COMPONENT_RADIO_QUIT: &str = "RADIO_QUIT";
|
||||
const COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK: &str = "RADIO_DELETE_BOOKMARK";
|
||||
const COMPONENT_RADIO_BOOKMARK_DEL_RECENT: &str = "RADIO_DELETE_RECENT";
|
||||
const COMPONENT_RADIO_BOOKMARK_SAVE_PWD: &str = "RADIO_SAVE_PASSWORD";
|
||||
const COMPONENT_RADIO_INSTALL_UPDATE: &str = "RADIO_INSTALL_UPDATE";
|
||||
const COMPONENT_BOOKMARKS_LIST: &str = "BOOKMARKS_LIST";
|
||||
const COMPONENT_RECENTS_LIST: &str = "RECENTS_LIST";
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub enum Id {
|
||||
Address,
|
||||
BookmarkName,
|
||||
BookmarkSavePassword,
|
||||
BookmarksList,
|
||||
DeleteBookmarkPopup,
|
||||
DeleteRecentPopup,
|
||||
ErrorPopup,
|
||||
GlobalListener,
|
||||
HelpText,
|
||||
InfoPopup,
|
||||
InstallUpdatePopup,
|
||||
Keybindings,
|
||||
NewVersionChangelog,
|
||||
NewVersionDisclaimer,
|
||||
Password,
|
||||
Port,
|
||||
Protocol,
|
||||
QuitPopup,
|
||||
RecentsList,
|
||||
S3Bucket,
|
||||
S3Profile,
|
||||
S3Region,
|
||||
Subtitle,
|
||||
Title,
|
||||
Username,
|
||||
WaitPopup,
|
||||
WindowSizeError,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Msg {
|
||||
AddressBlurDown,
|
||||
AddressBlurUp,
|
||||
BookmarksListBlur,
|
||||
BookmarksTabBlur,
|
||||
CloseDeleteBookmark,
|
||||
CloseDeleteRecent,
|
||||
CloseErrorPopup,
|
||||
CloseInfoPopup,
|
||||
CloseInstallUpdatePopup,
|
||||
CloseKeybindingsPopup,
|
||||
CloseQuitPopup,
|
||||
CloseSaveBookmark,
|
||||
Connect,
|
||||
DeleteBookmark,
|
||||
DeleteRecent,
|
||||
EnterSetup,
|
||||
InstallUpdate,
|
||||
LoadBookmark(usize),
|
||||
LoadRecent(usize),
|
||||
ParamsFormBlur,
|
||||
PasswordBlurDown,
|
||||
PasswordBlurUp,
|
||||
PortBlurDown,
|
||||
PortBlurUp,
|
||||
ProtocolBlurDown,
|
||||
ProtocolBlurUp,
|
||||
ProtocolChanged(FileTransferProtocol),
|
||||
Quit,
|
||||
RececentsListBlur,
|
||||
S3BucketBlurDown,
|
||||
S3BucketBlurUp,
|
||||
S3ProfileBlurDown,
|
||||
S3ProfileBlurUp,
|
||||
S3RegionBlurDown,
|
||||
S3RegionBlurUp,
|
||||
SaveBookmark,
|
||||
BookmarkNameBlur,
|
||||
SaveBookmarkPasswordBlur,
|
||||
ShowDeleteBookmarkPopup,
|
||||
ShowDeleteRecentPopup,
|
||||
ShowKeybindingsPopup,
|
||||
ShowQuitPopup,
|
||||
ShowReleaseNotes,
|
||||
ShowSaveBookmarkPopup,
|
||||
UsernameBlurDown,
|
||||
UsernameBlurUp,
|
||||
None,
|
||||
}
|
||||
|
||||
/// ## InputMask
|
||||
///
|
||||
/// Auth form input mask
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum InputMask {
|
||||
Generic,
|
||||
AwsS3,
|
||||
}
|
||||
|
||||
// Store keys
|
||||
const STORE_KEY_LATEST_VERSION: &str = "AUTH_LATEST_VERSION";
|
||||
@@ -79,34 +144,39 @@ const STORE_KEY_RELEASE_NOTES: &str = "AUTH_RELEASE_NOTES";
|
||||
///
|
||||
/// AuthActivity is the data holder for the authentication activity
|
||||
pub struct AuthActivity {
|
||||
exit_reason: Option<ExitReason>,
|
||||
context: Option<Context>,
|
||||
view: View,
|
||||
app: Application<Id, Msg, NoUserEvent>,
|
||||
bookmarks_client: Option<BookmarksClient>,
|
||||
redraw: bool, // Should ui actually be redrawned?
|
||||
bookmarks_list: Vec<String>, // List of bookmarks
|
||||
recents_list: Vec<String>, // list of recents
|
||||
}
|
||||
|
||||
impl Default for AuthActivity {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
/// List of bookmarks
|
||||
bookmarks_list: Vec<String>,
|
||||
/// List of recent hosts
|
||||
recents_list: Vec<String>,
|
||||
/// Exit reason
|
||||
exit_reason: Option<ExitReason>,
|
||||
/// Should redraw ui
|
||||
redraw: bool,
|
||||
/// Protocol
|
||||
protocol: FileTransferProtocol,
|
||||
context: Option<Context>,
|
||||
}
|
||||
|
||||
impl AuthActivity {
|
||||
/// ### new
|
||||
///
|
||||
/// Instantiates a new AuthActivity
|
||||
pub fn new() -> AuthActivity {
|
||||
pub fn new(ticks: Duration) -> AuthActivity {
|
||||
AuthActivity {
|
||||
exit_reason: None,
|
||||
app: Application::init(
|
||||
EventListenerCfg::default()
|
||||
.default_input_listener(ticks)
|
||||
.poll_timeout(ticks),
|
||||
),
|
||||
context: None,
|
||||
view: View::init(),
|
||||
bookmarks_client: None,
|
||||
redraw: true, // True at startup
|
||||
bookmarks_list: Vec::new(),
|
||||
exit_reason: None,
|
||||
recents_list: Vec::new(),
|
||||
redraw: true,
|
||||
protocol: FileTransferProtocol::Sftp,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,9 +212,11 @@ impl AuthActivity {
|
||||
///
|
||||
/// Get current input mask to show
|
||||
fn input_mask(&self) -> InputMask {
|
||||
match self.get_protocol() {
|
||||
match self.protocol {
|
||||
FileTransferProtocol::AwsS3 => InputMask::AwsS3,
|
||||
_ => InputMask::Generic,
|
||||
FileTransferProtocol::Ftp(_)
|
||||
| FileTransferProtocol::Scp
|
||||
| FileTransferProtocol::Sftp => InputMask::Generic,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,9 +234,11 @@ impl Activity for AuthActivity {
|
||||
// Set context
|
||||
self.context = Some(context);
|
||||
// Clear terminal
|
||||
self.context_mut().clear_screen();
|
||||
if let Err(err) = self.context_mut().terminal().clear_screen() {
|
||||
error!("Failed to clear screen: {}", err);
|
||||
}
|
||||
// Put raw mode on enabled
|
||||
if let Err(err) = enable_raw_mode() {
|
||||
if let Err(err) = self.context_mut().terminal().enable_raw_mode() {
|
||||
error!("Failed to enter raw mode: {}", err);
|
||||
}
|
||||
// If check for updates is enabled, check for updates
|
||||
@@ -194,24 +268,23 @@ impl Activity for AuthActivity {
|
||||
if self.context.is_none() {
|
||||
return;
|
||||
}
|
||||
// Read one event
|
||||
if let Ok(Some(event)) = self.context().input_hnd().read_event() {
|
||||
// Set redraw to true
|
||||
self.redraw = true;
|
||||
// Handle on resize
|
||||
if let Event::Resize(_, h) = event {
|
||||
self.check_minimum_window_size(h);
|
||||
// Tick
|
||||
match self.app.tick(PollStrategy::UpTo(3)) {
|
||||
Ok(messages) => {
|
||||
for msg in messages.into_iter() {
|
||||
let mut msg = Some(msg);
|
||||
while msg.is_some() {
|
||||
msg = self.update(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
self.mount_error(format!("Application error: {}", err));
|
||||
}
|
||||
// Handle event on view and update
|
||||
let msg = self.view.on(event);
|
||||
self.update(msg);
|
||||
}
|
||||
// Redraw if necessary
|
||||
// View
|
||||
if self.redraw {
|
||||
// View
|
||||
self.view();
|
||||
// Set redraw to false
|
||||
self.redraw = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,26 +304,12 @@ impl Activity for AuthActivity {
|
||||
/// This function finally releases the context
|
||||
fn on_destroy(&mut self) -> Option<Context> {
|
||||
// Disable raw mode
|
||||
if let Err(err) = disable_raw_mode() {
|
||||
if let Err(err) = self.context_mut().terminal().disable_raw_mode() {
|
||||
error!("Failed to disable raw mode: {}", err);
|
||||
}
|
||||
self.context.as_ref()?;
|
||||
// Clear terminal and return
|
||||
match self.context.take() {
|
||||
Some(mut ctx) => {
|
||||
ctx.clear_screen();
|
||||
Some(ctx)
|
||||
}
|
||||
None => None,
|
||||
if let Err(err) = self.context_mut().terminal().clear_screen() {
|
||||
error!("Failed to clear screen: {}", err);
|
||||
}
|
||||
self.context.take()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## InputMask
|
||||
///
|
||||
/// Auth form input mask
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum InputMask {
|
||||
Generic,
|
||||
AwsS3,
|
||||
}
|
||||
|
||||
+207
-400
@@ -1,6 +1,6 @@
|
||||
//! ## AuthActivity
|
||||
//! ## Update
|
||||
//!
|
||||
//! `auth_activity` is the module which implements the authentication activity
|
||||
//! Update impl
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
@@ -25,415 +25,222 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
// locals
|
||||
use super::{
|
||||
AuthActivity, FileTransferProtocol, InputMask, COMPONENT_BOOKMARKS_LIST, COMPONENT_INPUT_ADDR,
|
||||
COMPONENT_INPUT_BOOKMARK_NAME, COMPONENT_INPUT_PASSWORD, COMPONENT_INPUT_PORT,
|
||||
COMPONENT_INPUT_S3_BUCKET, COMPONENT_INPUT_S3_PROFILE, COMPONENT_INPUT_S3_REGION,
|
||||
COMPONENT_INPUT_USERNAME, COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK,
|
||||
COMPONENT_RADIO_BOOKMARK_DEL_RECENT, COMPONENT_RADIO_BOOKMARK_SAVE_PWD,
|
||||
COMPONENT_RADIO_INSTALL_UPDATE, COMPONENT_RADIO_PROTOCOL, COMPONENT_RADIO_QUIT,
|
||||
COMPONENT_RECENTS_LIST, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP, COMPONENT_TEXT_INFO,
|
||||
COMPONENT_TEXT_NEW_VERSION_NOTES, COMPONENT_TEXT_SIZE_ERR, COMPONENT_TEXT_WAIT,
|
||||
};
|
||||
use crate::ui::keymap::*;
|
||||
use tui_realm_stdlib::InputPropsBuilder;
|
||||
use tuirealm::{Msg, Payload, PropsBuilder, Update, Value};
|
||||
use super::{AuthActivity, ExitReason, Id, InputMask, Msg, Update};
|
||||
|
||||
// -- update
|
||||
use tuirealm::{State, StateValue};
|
||||
|
||||
impl Update for AuthActivity {
|
||||
/// ### update
|
||||
///
|
||||
/// Update auth activity model based on msg
|
||||
/// The function exits when returns None
|
||||
fn update(&mut self, msg: Option<(String, Msg)>) -> Option<(String, Msg)> {
|
||||
let ref_msg: Option<(&str, &Msg)> = msg.as_ref().map(|(s, msg)| (s.as_str(), msg));
|
||||
// Match msg
|
||||
match ref_msg {
|
||||
None => None, // Exit after None
|
||||
Some(msg) => match msg {
|
||||
// Focus ( DOWN )
|
||||
(COMPONENT_RADIO_PROTOCOL, key) if key == &MSG_KEY_DOWN => {
|
||||
// Give focus based on current mask
|
||||
match self.input_mask() {
|
||||
InputMask::Generic => self.view.active(COMPONENT_INPUT_ADDR),
|
||||
InputMask::AwsS3 => self.view.active(COMPONENT_INPUT_S3_BUCKET),
|
||||
};
|
||||
None
|
||||
}
|
||||
// -- generic mask (DOWN)
|
||||
(COMPONENT_INPUT_ADDR, key) if key == &MSG_KEY_DOWN => {
|
||||
self.view.active(COMPONENT_INPUT_PORT);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_PORT, key) if key == &MSG_KEY_DOWN => {
|
||||
self.view.active(COMPONENT_INPUT_USERNAME);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_USERNAME, key) if key == &MSG_KEY_DOWN => {
|
||||
self.view.active(COMPONENT_INPUT_PASSWORD);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_PASSWORD, key) if key == &MSG_KEY_DOWN => {
|
||||
self.view.active(COMPONENT_RADIO_PROTOCOL);
|
||||
None
|
||||
}
|
||||
// -- s3 mask (DOWN)
|
||||
(COMPONENT_INPUT_S3_BUCKET, key) if key == &MSG_KEY_DOWN => {
|
||||
self.view.active(COMPONENT_INPUT_S3_REGION);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_S3_REGION, key) if key == &MSG_KEY_DOWN => {
|
||||
self.view.active(COMPONENT_INPUT_S3_PROFILE);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_S3_PROFILE, key) if key == &MSG_KEY_DOWN => {
|
||||
self.view.active(COMPONENT_RADIO_PROTOCOL);
|
||||
None
|
||||
}
|
||||
// Focus ( UP )
|
||||
// -- generic (UP)
|
||||
(COMPONENT_INPUT_PASSWORD, key) if key == &MSG_KEY_UP => {
|
||||
self.view.active(COMPONENT_INPUT_USERNAME);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_USERNAME, key) if key == &MSG_KEY_UP => {
|
||||
self.view.active(COMPONENT_INPUT_PORT);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_PORT, key) if key == &MSG_KEY_UP => {
|
||||
self.view.active(COMPONENT_INPUT_ADDR);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_ADDR, key) if key == &MSG_KEY_UP => {
|
||||
self.view.active(COMPONENT_RADIO_PROTOCOL);
|
||||
None
|
||||
}
|
||||
// -- s3 (UP)
|
||||
(COMPONENT_INPUT_S3_BUCKET, key) if key == &MSG_KEY_UP => {
|
||||
self.view.active(COMPONENT_RADIO_PROTOCOL);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_S3_REGION, key) if key == &MSG_KEY_UP => {
|
||||
self.view.active(COMPONENT_INPUT_S3_BUCKET);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_S3_PROFILE, key) if key == &MSG_KEY_UP => {
|
||||
self.view.active(COMPONENT_INPUT_S3_REGION);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_PROTOCOL, key) if key == &MSG_KEY_UP => {
|
||||
// Give focus based on current mask
|
||||
match self.input_mask() {
|
||||
InputMask::Generic => self.view.active(COMPONENT_INPUT_PASSWORD),
|
||||
InputMask::AwsS3 => self.view.active(COMPONENT_INPUT_S3_PROFILE),
|
||||
};
|
||||
None
|
||||
}
|
||||
// Protocol - On Change
|
||||
(COMPONENT_RADIO_PROTOCOL, Msg::OnChange(Payload::One(Value::Usize(protocol)))) => {
|
||||
// If port is standard, update the current port with default for selected protocol
|
||||
let protocol: FileTransferProtocol = Self::protocol_opt_to_enum(*protocol);
|
||||
// Get port
|
||||
let port: u16 = self.get_input_port();
|
||||
match Self::is_port_standard(port) {
|
||||
false => None, // Return None
|
||||
true => {
|
||||
self.update_input_port(Self::get_default_port_for_protocol(protocol))
|
||||
}
|
||||
impl Update<Msg> for AuthActivity {
|
||||
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||
self.redraw = true;
|
||||
match msg.unwrap_or(Msg::None) {
|
||||
Msg::AddressBlurDown => {
|
||||
assert!(self.app.active(&Id::Port).is_ok());
|
||||
}
|
||||
Msg::AddressBlurUp => {
|
||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
||||
}
|
||||
Msg::BookmarksListBlur => {
|
||||
assert!(self.app.active(&Id::RecentsList).is_ok());
|
||||
}
|
||||
Msg::BookmarkNameBlur => {
|
||||
assert!(self.app.active(&Id::BookmarkSavePassword).is_ok());
|
||||
}
|
||||
Msg::BookmarksTabBlur => {
|
||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
||||
}
|
||||
Msg::CloseDeleteBookmark => {
|
||||
assert!(self.app.umount(&Id::DeleteBookmarkPopup).is_ok());
|
||||
}
|
||||
Msg::CloseDeleteRecent => {
|
||||
assert!(self.app.umount(&Id::DeleteRecentPopup).is_ok());
|
||||
}
|
||||
Msg::CloseErrorPopup => {
|
||||
self.umount_error();
|
||||
}
|
||||
Msg::CloseInfoPopup => {
|
||||
self.umount_info();
|
||||
}
|
||||
Msg::CloseInstallUpdatePopup => {
|
||||
assert!(self.app.umount(&Id::NewVersionChangelog).is_ok());
|
||||
assert!(self.app.umount(&Id::InstallUpdatePopup).is_ok());
|
||||
}
|
||||
Msg::CloseKeybindingsPopup => {
|
||||
self.umount_help();
|
||||
}
|
||||
Msg::CloseQuitPopup => self.umount_quit(),
|
||||
Msg::CloseSaveBookmark => {
|
||||
assert!(self.app.umount(&Id::BookmarkName).is_ok());
|
||||
assert!(self.app.umount(&Id::BookmarkSavePassword).is_ok());
|
||||
}
|
||||
Msg::Connect => {
|
||||
match self.collect_host_params() {
|
||||
Err(err) => {
|
||||
// mount error
|
||||
self.mount_error(err);
|
||||
}
|
||||
Ok(params) => {
|
||||
self.save_recent();
|
||||
// Set file transfer params to context
|
||||
self.context_mut().set_ftparams(params);
|
||||
// Set exit reason
|
||||
self.exit_reason = Some(super::ExitReason::Connect);
|
||||
}
|
||||
}
|
||||
// Bookmarks commands
|
||||
// <RIGHT> / <LEFT>
|
||||
(COMPONENT_BOOKMARKS_LIST, key) if key == &MSG_KEY_RIGHT => {
|
||||
// Give focus to recents
|
||||
self.view.active(COMPONENT_RECENTS_LIST);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RECENTS_LIST, key) if key == &MSG_KEY_LEFT => {
|
||||
// Give focus to bookmarks
|
||||
self.view.active(COMPONENT_BOOKMARKS_LIST);
|
||||
None
|
||||
}
|
||||
// <DEL | 'E'>
|
||||
(COMPONENT_BOOKMARKS_LIST, key)
|
||||
if key == &MSG_KEY_DEL || key == &MSG_KEY_CHAR_E =>
|
||||
{
|
||||
// Show delete popup
|
||||
self.mount_bookmark_del_dialog();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RECENTS_LIST, key) if key == &MSG_KEY_DEL || key == &MSG_KEY_CHAR_E => {
|
||||
// Show delete popup
|
||||
self.mount_recent_del_dialog();
|
||||
None
|
||||
}
|
||||
// Enter
|
||||
(COMPONENT_BOOKMARKS_LIST, Msg::OnSubmit(Payload::One(Value::Usize(idx)))) => {
|
||||
self.load_bookmark(*idx);
|
||||
// Give focus to input password (or to protocol if not generic)
|
||||
self.view.active(match self.input_mask() {
|
||||
InputMask::Generic => COMPONENT_INPUT_PASSWORD,
|
||||
InputMask::AwsS3 => COMPONENT_INPUT_S3_BUCKET,
|
||||
});
|
||||
None
|
||||
}
|
||||
(COMPONENT_RECENTS_LIST, Msg::OnSubmit(Payload::One(Value::Usize(idx)))) => {
|
||||
self.load_recent(*idx);
|
||||
// Give focus to input password
|
||||
self.view.active(match self.input_mask() {
|
||||
InputMask::Generic => COMPONENT_INPUT_PASSWORD,
|
||||
InputMask::AwsS3 => COMPONENT_INPUT_S3_BUCKET,
|
||||
});
|
||||
None
|
||||
}
|
||||
// Bookmark radio
|
||||
// Del bookmarks
|
||||
(
|
||||
COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK,
|
||||
Msg::OnSubmit(Payload::One(Value::Usize(index))),
|
||||
) => {
|
||||
// hide bookmark delete
|
||||
}
|
||||
Msg::DeleteBookmark => {
|
||||
if let Ok(State::One(StateValue::Usize(idx))) = self.app.state(&Id::BookmarksList) {
|
||||
// Umount dialog
|
||||
self.umount_bookmark_del_dialog();
|
||||
// Index must be 0 => YES
|
||||
match *index {
|
||||
0 => {
|
||||
// Get selected bookmark
|
||||
match self.view.get_state(COMPONENT_BOOKMARKS_LIST) {
|
||||
Some(Payload::One(Value::Usize(index))) => {
|
||||
// Delete bookmark
|
||||
self.del_bookmark(index);
|
||||
// Update bookmarks
|
||||
self.view_bookmarks()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
(
|
||||
COMPONENT_RADIO_BOOKMARK_DEL_RECENT,
|
||||
Msg::OnSubmit(Payload::One(Value::Usize(index))),
|
||||
) => {
|
||||
// hide bookmark delete
|
||||
self.umount_recent_del_dialog();
|
||||
// Index must be 0 => YES
|
||||
match *index {
|
||||
0 => {
|
||||
// Get selected bookmark
|
||||
match self.view.get_state(COMPONENT_RECENTS_LIST) {
|
||||
Some(Payload::One(Value::Usize(index))) => {
|
||||
// Delete recent
|
||||
self.del_recent(index);
|
||||
// Update bookmarks
|
||||
self.view_recent_connections()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
// <ESC> hide tab
|
||||
(COMPONENT_RADIO_BOOKMARK_DEL_RECENT, key) if key == &MSG_KEY_ESC => {
|
||||
self.umount_recent_del_dialog();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_BOOKMARK_DEL_RECENT, _) => None,
|
||||
(COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, key) if key == &MSG_KEY_ESC => {
|
||||
self.umount_bookmark_del_dialog();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, _) => None,
|
||||
// Error message
|
||||
(COMPONENT_TEXT_ERROR, key) if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => {
|
||||
// Umount text error
|
||||
self.umount_error();
|
||||
None
|
||||
}
|
||||
// -- Text info
|
||||
(COMPONENT_TEXT_INFO, key) if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => {
|
||||
// Umount text info
|
||||
self.umount_info();
|
||||
None
|
||||
}
|
||||
(COMPONENT_TEXT_ERROR, _) | (COMPONENT_TEXT_INFO, _) => None,
|
||||
// -- Text wait
|
||||
(COMPONENT_TEXT_WAIT, _) => None,
|
||||
// -- Release notes
|
||||
(COMPONENT_TEXT_NEW_VERSION_NOTES, key) if key == &MSG_KEY_ESC => {
|
||||
// Umount release notes
|
||||
self.umount_release_notes();
|
||||
None
|
||||
}
|
||||
(COMPONENT_TEXT_NEW_VERSION_NOTES, key) if key == &MSG_KEY_TAB => {
|
||||
// Focus to radio update
|
||||
self.view.active(COMPONENT_RADIO_INSTALL_UPDATE);
|
||||
None
|
||||
}
|
||||
(COMPONENT_TEXT_NEW_VERSION_NOTES, _) => None,
|
||||
// -- Install update radio
|
||||
(COMPONENT_RADIO_INSTALL_UPDATE, Msg::OnSubmit(Payload::One(Value::Usize(0)))) => {
|
||||
// Install update
|
||||
self.install_update();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_INSTALL_UPDATE, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => {
|
||||
// Umount
|
||||
self.umount_release_notes();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_INSTALL_UPDATE, key) if key == &MSG_KEY_TAB => {
|
||||
// Focus to changelog
|
||||
self.view.active(COMPONENT_TEXT_NEW_VERSION_NOTES);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_INSTALL_UPDATE, _) => None,
|
||||
// Help
|
||||
(_, key) if key == &MSG_KEY_CTRL_H => {
|
||||
// Show help
|
||||
self.mount_help();
|
||||
None
|
||||
}
|
||||
// Release notes
|
||||
(_, key) if key == &MSG_KEY_CTRL_R => {
|
||||
// Show release notes
|
||||
self.mount_release_notes();
|
||||
None
|
||||
}
|
||||
(COMPONENT_TEXT_HELP, key) if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => {
|
||||
// Hide text help
|
||||
self.umount_help();
|
||||
None
|
||||
}
|
||||
(COMPONENT_TEXT_HELP, _) => None,
|
||||
// Enter setup
|
||||
(_, key) if key == &MSG_KEY_CTRL_C => {
|
||||
self.exit_reason = Some(super::ExitReason::EnterSetup);
|
||||
None
|
||||
}
|
||||
// Save bookmark; show popup
|
||||
(_, key) if key == &MSG_KEY_CTRL_S => {
|
||||
// Show popup
|
||||
self.mount_bookmark_save_dialog();
|
||||
// Give focus to bookmark name
|
||||
self.view.active(COMPONENT_INPUT_BOOKMARK_NAME);
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_BOOKMARK_NAME, key) if key == &MSG_KEY_DOWN => {
|
||||
// Give focus to pwd
|
||||
self.view.active(COMPONENT_RADIO_BOOKMARK_SAVE_PWD);
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_BOOKMARK_SAVE_PWD, key) if key == &MSG_KEY_UP => {
|
||||
// Give focus to pwd
|
||||
self.view.active(COMPONENT_INPUT_BOOKMARK_NAME);
|
||||
None
|
||||
}
|
||||
// Save bookmark
|
||||
(COMPONENT_INPUT_BOOKMARK_NAME, Msg::OnSubmit(_))
|
||||
| (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, Msg::OnSubmit(_)) => {
|
||||
// Get values
|
||||
let bookmark_name: String =
|
||||
match self.view.get_state(COMPONENT_INPUT_BOOKMARK_NAME) {
|
||||
Some(Payload::One(Value::Str(s))) => s,
|
||||
_ => String::new(),
|
||||
};
|
||||
let save_pwd: bool = matches!(
|
||||
self.view.get_state(COMPONENT_RADIO_BOOKMARK_SAVE_PWD),
|
||||
Some(Payload::One(Value::Usize(0)))
|
||||
);
|
||||
// Save bookmark
|
||||
if !bookmark_name.is_empty() {
|
||||
self.save_bookmark(bookmark_name, save_pwd);
|
||||
}
|
||||
// Umount popup
|
||||
self.umount_bookmark_save_dialog();
|
||||
// Reload bookmarks
|
||||
// Delete bookmark
|
||||
self.del_bookmark(idx);
|
||||
// Update bookmarks
|
||||
self.view_bookmarks()
|
||||
}
|
||||
// Hide save bookmark
|
||||
(COMPONENT_INPUT_BOOKMARK_NAME, key) | (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, key)
|
||||
if key == &MSG_KEY_ESC =>
|
||||
{
|
||||
// Umount popup
|
||||
self.umount_bookmark_save_dialog();
|
||||
None
|
||||
}
|
||||
(COMPONENT_INPUT_BOOKMARK_NAME, _) | (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, _) => None,
|
||||
// Quit dialog
|
||||
(COMPONENT_RADIO_QUIT, Msg::OnSubmit(Payload::One(Value::Usize(choice)))) => {
|
||||
// If choice is 0, quit termscp
|
||||
if *choice == 0 {
|
||||
self.exit_reason = Some(super::ExitReason::Quit);
|
||||
}
|
||||
self.umount_quit();
|
||||
None
|
||||
}
|
||||
(COMPONENT_RADIO_QUIT, key) if key == &MSG_KEY_ESC => {
|
||||
self.umount_quit();
|
||||
None
|
||||
}
|
||||
// -- text size error; block everything
|
||||
(COMPONENT_TEXT_SIZE_ERR, _) => None,
|
||||
// <TAB> bookmarks
|
||||
(COMPONENT_BOOKMARKS_LIST, key) | (COMPONENT_RECENTS_LIST, key)
|
||||
if key == &MSG_KEY_TAB =>
|
||||
{
|
||||
// Give focus to address
|
||||
self.view.active(COMPONENT_RADIO_PROTOCOL);
|
||||
None
|
||||
}
|
||||
// Any <TAB>, go to bookmarks
|
||||
(_, key) if key == &MSG_KEY_TAB => {
|
||||
self.view.active(COMPONENT_BOOKMARKS_LIST);
|
||||
None
|
||||
}
|
||||
// On submit on any unhandled (connect)
|
||||
(_, Msg::OnSubmit(_)) => self.on_unhandled_submit(),
|
||||
(_, key) if key == &MSG_KEY_ENTER => self.on_unhandled_submit(),
|
||||
// <ESC> => Quit
|
||||
(_, key) if key == &MSG_KEY_ESC => {
|
||||
self.mount_quit();
|
||||
None
|
||||
}
|
||||
(_, _) => None, // Ignore other events
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthActivity {
|
||||
fn update_input_port(&mut self, port: u16) -> Option<(String, Msg)> {
|
||||
match self.view.get_props(COMPONENT_INPUT_PORT) {
|
||||
None => None,
|
||||
Some(props) => {
|
||||
let props = InputPropsBuilder::from(props)
|
||||
.with_value(port.to_string())
|
||||
.build();
|
||||
self.view.update(COMPONENT_INPUT_PORT, props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_unhandled_submit(&mut self) -> Option<(String, Msg)> {
|
||||
// Validate fields
|
||||
match self.collect_host_params() {
|
||||
Err(err) => {
|
||||
// mount error
|
||||
self.mount_error(err);
|
||||
Msg::DeleteRecent => {
|
||||
if let Ok(State::One(StateValue::Usize(idx))) = self.app.state(&Id::RecentsList) {
|
||||
// Umount dialog
|
||||
self.umount_recent_del_dialog();
|
||||
// Delete recent
|
||||
self.del_recent(idx);
|
||||
// Update recents
|
||||
self.view_recent_connections();
|
||||
}
|
||||
}
|
||||
Ok(params) => {
|
||||
self.save_recent();
|
||||
// Set file transfer params to context
|
||||
self.context_mut().set_ftparams(params);
|
||||
// Set exit reason
|
||||
self.exit_reason = Some(super::ExitReason::Connect);
|
||||
Msg::EnterSetup => {
|
||||
self.exit_reason = Some(ExitReason::EnterSetup);
|
||||
}
|
||||
Msg::InstallUpdate => {
|
||||
self.install_update();
|
||||
}
|
||||
Msg::LoadBookmark(i) => {
|
||||
self.load_bookmark(i);
|
||||
// Give focus to input password (or to protocol if not generic)
|
||||
assert!(self
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Password,
|
||||
InputMask::AwsS3 => &Id::S3Bucket,
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
Msg::LoadRecent(i) => {
|
||||
self.load_recent(i);
|
||||
// Give focus to input password (or to protocol if not generic)
|
||||
assert!(self
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Password,
|
||||
InputMask::AwsS3 => &Id::S3Bucket,
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
Msg::ParamsFormBlur => {
|
||||
assert!(self.app.active(&Id::BookmarksList).is_ok());
|
||||
}
|
||||
Msg::PasswordBlurDown => {
|
||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
||||
}
|
||||
Msg::PasswordBlurUp => {
|
||||
assert!(self.app.active(&Id::Username).is_ok());
|
||||
}
|
||||
Msg::PortBlurDown => {
|
||||
assert!(self.app.active(&Id::Username).is_ok());
|
||||
}
|
||||
Msg::PortBlurUp => {
|
||||
assert!(self.app.active(&Id::Address).is_ok());
|
||||
}
|
||||
Msg::ProtocolBlurDown => {
|
||||
assert!(self
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Address,
|
||||
InputMask::AwsS3 => &Id::S3Bucket,
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
Msg::ProtocolBlurUp => {
|
||||
assert!(self
|
||||
.app
|
||||
.active(match self.input_mask() {
|
||||
InputMask::Generic => &Id::Password,
|
||||
InputMask::AwsS3 => &Id::S3Profile,
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
Msg::ProtocolChanged(protocol) => {
|
||||
self.protocol = protocol;
|
||||
// Update port
|
||||
let port: u16 = self.get_input_port();
|
||||
if Self::is_port_standard(port) {
|
||||
self.mount_port(Self::get_default_port_for_protocol(protocol));
|
||||
}
|
||||
}
|
||||
Msg::Quit => {
|
||||
self.exit_reason = Some(ExitReason::Quit);
|
||||
}
|
||||
Msg::RececentsListBlur => {
|
||||
assert!(self.app.active(&Id::BookmarksList).is_ok());
|
||||
}
|
||||
Msg::S3BucketBlurDown => {
|
||||
assert!(self.app.active(&Id::S3Region).is_ok());
|
||||
}
|
||||
Msg::S3BucketBlurUp => {
|
||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
||||
}
|
||||
Msg::S3RegionBlurDown => {
|
||||
assert!(self.app.active(&Id::S3Profile).is_ok());
|
||||
}
|
||||
Msg::S3RegionBlurUp => {
|
||||
assert!(self.app.active(&Id::S3Bucket).is_ok());
|
||||
}
|
||||
Msg::S3ProfileBlurDown => {
|
||||
assert!(self.app.active(&Id::Protocol).is_ok());
|
||||
}
|
||||
Msg::S3ProfileBlurUp => {
|
||||
assert!(self.app.active(&Id::S3Region).is_ok());
|
||||
}
|
||||
Msg::SaveBookmark => {
|
||||
// get bookmark name
|
||||
let (name, save_password) = self.get_new_bookmark();
|
||||
// Save bookmark
|
||||
if !name.is_empty() {
|
||||
self.save_bookmark(name, save_password);
|
||||
}
|
||||
// Umount popup
|
||||
self.umount_bookmark_save_dialog();
|
||||
// Reload bookmarks
|
||||
self.view_bookmarks()
|
||||
}
|
||||
Msg::SaveBookmarkPasswordBlur => {
|
||||
assert!(self.app.active(&Id::BookmarkName).is_ok());
|
||||
}
|
||||
Msg::ShowDeleteBookmarkPopup => {
|
||||
self.mount_bookmark_del_dialog();
|
||||
}
|
||||
Msg::ShowDeleteRecentPopup => {
|
||||
self.mount_recent_del_dialog();
|
||||
}
|
||||
Msg::ShowKeybindingsPopup => {
|
||||
self.mount_keybindings();
|
||||
}
|
||||
Msg::ShowQuitPopup => {
|
||||
self.mount_quit();
|
||||
}
|
||||
Msg::ShowReleaseNotes => {
|
||||
self.mount_release_notes();
|
||||
}
|
||||
Msg::ShowSaveBookmarkPopup => {
|
||||
self.mount_bookmark_save_dialog();
|
||||
}
|
||||
Msg::UsernameBlurDown => {
|
||||
assert!(self.app.active(&Id::Password).is_ok());
|
||||
}
|
||||
Msg::UsernameBlurUp => {
|
||||
assert!(self.app.active(&Id::Port).is_ok());
|
||||
}
|
||||
Msg::None => {}
|
||||
}
|
||||
// Return None
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
+503
-582
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user