diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e06188..68ddc33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ Released on FIXME: ?? -> 🏄 Summer update 2021🌴 +> 🍹 Summer update 2021 🍨 - **Open any file** in explorer: - Open file with default program for file type with `` @@ -36,12 +36,15 @@ Released on FIXME: ?? - **❗ BREAKING CHANGE ❗**: if you start using keyring on Linux, all the saved password will be lost - **Installation script**: - From now on, in case cargo is used to install termscp, all the cargo dependencies will be installed +- **In-app release notes** + - Possibility to see the release note of the new available release whenever a new version is available + - Just press `` when a new version is available from the auth activity to read the release notes - Bugfix: - Fixed broken input cursor when typing UTF8 characters (tui-realm 0.3.2) - Dependencies: - Added `open 1.7.0` - Updated `textwrap` to `0.14.0` - - Updated `tui-realm` to `0.4.1` + - Updated `tui-realm` to `0.4.3` ## 0.5.1 diff --git a/Cargo.lock b/Cargo.lock index c4d2717..a0a85cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "aes" version = "0.6.0" @@ -1451,9 +1453,9 @@ dependencies = [ [[package]] name = "tuirealm" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9897335542e4a4a87ad391419c35e54b4088661e671ba53e578fbbb1154740c2" +checksum = "0fcbd06f2aa6a2424aaa245c10e8767fe3f0fee234ac8c144cb15eaf2ee37ce9" dependencies = [ "crossterm", "textwrap", diff --git a/Cargo.toml b/Cargo.toml index 4780b63..20db1b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ tempfile = "3.1.0" textwrap = "0.14.0" thiserror = "^1.0.0" toml = "0.5.8" -tuirealm = { version = "0.4.2", features = [ "with-components" ] } +tuirealm = { version = "0.4.3", features = [ "with-components" ] } ureq = { version = "2.1.0", features = [ "json" ] } whoami = "1.1.1" wildmatch = "2.0.0" diff --git a/src/ui/activities/auth/mod.rs b/src/ui/activities/auth/mod.rs index a88ca79..a899541 100644 --- a/src/ui/activities/auth/mod.rs +++ b/src/ui/activities/auth/mod.rs @@ -47,6 +47,7 @@ use tuirealm::{Update, View}; 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"; @@ -66,6 +67,7 @@ const COMPONENT_RECENTS_LIST: &str = "RECENTS_LIST"; // Store keys const STORE_KEY_LATEST_VERSION: &str = "AUTH_LATEST_VERSION"; +const STORE_KEY_RELEASE_NOTES: &str = "AUTH_RELEASE_NOTES"; /// ### AuthActivity /// @@ -111,16 +113,13 @@ impl AuthActivity { let ctx: &Context = self.context.as_ref().unwrap(); if !ctx.store.isset(STORE_KEY_LATEST_VERSION) { debug!("Version is not set in storage"); - let mut new_version: Option = match ctx.config_client.as_ref() { + let mut github_tag: Option = match ctx.config_client.as_ref() { Some(client) => { if client.get_check_for_updates() { debug!("Check for updates is enabled"); // Send request match git::check_for_updates(env!("CARGO_PKG_VERSION")) { - Ok(version) => { - info!("Latest version is: {:?}", version); - version - } + Ok(github_tag) => github_tag, Err(err) => { // Report error error!("Failed to get latest version: {}", err); @@ -140,9 +139,18 @@ impl AuthActivity { }; let ctx: &mut Context = self.context.as_mut().unwrap(); // Set version into the store (or just a flag) - match new_version.take() { - Some(new_version) => ctx.store.set_string(STORE_KEY_LATEST_VERSION, new_version), // If Some, set String - None => ctx.store.set(STORE_KEY_LATEST_VERSION), // If None, just set flag + match github_tag.take() { + Some(git::GithubTag { tag_name, body }) => { + // If some store version and release notes + info!("Latest version is: {}", tag_name); + ctx.store.set_string(STORE_KEY_LATEST_VERSION, tag_name); + ctx.store.set_string(STORE_KEY_RELEASE_NOTES, body); + } + None => { + info!("Latest version is: {} (current)", env!("CARGO_PKG_VERSION")); + // Just set flag as check + ctx.store.set(STORE_KEY_LATEST_VERSION); + } } } } diff --git a/src/ui/activities/auth/update.rs b/src/ui/activities/auth/update.rs index 23971bb..7173850 100644 --- a/src/ui/activities/auth/update.rs +++ b/src/ui/activities/auth/update.rs @@ -32,7 +32,7 @@ use super::{ COMPONENT_INPUT_PORT, COMPONENT_INPUT_USERNAME, COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, COMPONENT_RADIO_BOOKMARK_DEL_RECENT, COMPONENT_RADIO_BOOKMARK_SAVE_PWD, COMPONENT_RADIO_PROTOCOL, COMPONENT_RADIO_QUIT, COMPONENT_RECENTS_LIST, COMPONENT_TEXT_ERROR, - COMPONENT_TEXT_HELP, COMPONENT_TEXT_SIZE_ERR, + COMPONENT_TEXT_HELP, COMPONENT_TEXT_NEW_VERSION_NOTES, COMPONENT_TEXT_SIZE_ERR, }; use crate::ui::keymap::*; use tuirealm::components::InputPropsBuilder; @@ -116,18 +116,6 @@ impl Update for AuthActivity { } } } - // bookmarks - (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_TAB) - | (COMPONENT_RECENTS_LIST, &MSG_KEY_TAB) => { - // Give focus to address - self.view.active(COMPONENT_INPUT_ADDR); - None - } - // Any , go to bookmarks - (_, &MSG_KEY_TAB) => { - self.view.active(COMPONENT_BOOKMARKS_LIST); - None - } // Bookmarks commands // / (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_RIGHT) => { @@ -219,10 +207,12 @@ impl Update for AuthActivity { self.umount_recent_del_dialog(); None } + (COMPONENT_RADIO_BOOKMARK_DEL_RECENT, _) => None, (COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, &MSG_KEY_ESC) => { self.umount_bookmark_del_dialog(); None } + (COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, _) => None, // Error message (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => { // Umount text error @@ -230,17 +220,31 @@ impl Update for AuthActivity { None } (COMPONENT_TEXT_ERROR, _) => None, + (COMPONENT_TEXT_NEW_VERSION_NOTES, &MSG_KEY_ESC) + | (COMPONENT_TEXT_NEW_VERSION_NOTES, &MSG_KEY_ENTER) => { + // Umount release notes + self.umount_release_notes(); + None + } + (COMPONENT_TEXT_NEW_VERSION_NOTES, _) => None, // Help (_, &MSG_KEY_CTRL_H) => { // Show help self.mount_help(); None } + // Release notes + (_, &MSG_KEY_CTRL_R) => { + // Show release notes + self.mount_release_notes(); + None + } (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => { // Hide text help self.umount_help(); None } + (COMPONENT_TEXT_HELP, _) => None, // Enter setup (_, &MSG_KEY_CTRL_C) => { self.exit_reason = Some(super::ExitReason::EnterSetup); @@ -308,6 +312,18 @@ impl Update for AuthActivity { } // -- text size error; block everything (COMPONENT_TEXT_SIZE_ERR, _) => None, + // bookmarks + (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_TAB) + | (COMPONENT_RECENTS_LIST, &MSG_KEY_TAB) => { + // Give focus to address + self.view.active(COMPONENT_INPUT_ADDR); + None + } + // Any , go to bookmarks + (_, &MSG_KEY_TAB) => { + self.view.active(COMPONENT_BOOKMARKS_LIST); + None + } // On submit on any unhandled (connect) (_, Msg::OnSubmit(_)) | (_, &MSG_KEY_ENTER) => { // Match key for all other components diff --git a/src/ui/activities/auth/view.rs b/src/ui/activities/auth/view.rs index eb55543..fe7a03d 100644 --- a/src/ui/activities/auth/view.rs +++ b/src/ui/activities/auth/view.rs @@ -39,6 +39,7 @@ use tuirealm::components::{ radio::{Radio, RadioPropsBuilder}, scrolltable::{ScrollTablePropsBuilder, Scrolltable}, span::{Span, SpanPropsBuilder}, + textarea::{Textarea, TextareaPropsBuilder}, }; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, @@ -185,17 +186,15 @@ impl AuthActivity { self.view.mount( super::COMPONENT_TEXT_NEW_VERSION, Box::new(Span::new( - SpanPropsBuilder::default() + SpanPropsBuilder::default() .with_foreground(Color::Yellow) - .with_spans( - vec![ - TextSpan::from("termscp "), - TextSpanBuilder::new(version).underlined().bold().build(), - TextSpan::from(" is now available! Download it from ") - ] - ) - .build() - )) + .with_spans(vec![ + TextSpan::from("termscp "), + TextSpanBuilder::new(version).underlined().bold().build(), + TextSpan::from(" is NOW available! Get it from ; view release notes with "), + ]) + .build(), + )), ); } // Bookmarks @@ -346,6 +345,15 @@ impl AuthActivity { .render(super::COMPONENT_RADIO_BOOKMARK_DEL_RECENT, f, popup); } } + if let Some(props) = self.view.get_props(super::COMPONENT_TEXT_NEW_VERSION_NOTES) { + if props.visible { + // make popup + let popup = draw_area_in(f.size(), 90, 90); + f.render_widget(Clear, popup); + self.view + .render(super::COMPONENT_TEXT_NEW_VERSION_NOTES, f, popup); + } + } if let Some(props) = self.view.get_props(super::COMPONENT_TEXT_HELP) { if props.visible { // make popup @@ -753,6 +761,35 @@ impl AuthActivity { self.view.umount(super::COMPONENT_TEXT_HELP); } + /// ### mount_release_notes + /// + /// mount release notes text area + pub(super) fn mount_release_notes(&mut self) { + if let Some(ctx) = self.context.as_ref() { + if let Some(release_notes) = ctx.store.get_string(super::STORE_KEY_RELEASE_NOTES) { + // make spans + let spans: Vec = release_notes.lines().map(TextSpan::from).collect(); + self.view.mount( + super::COMPONENT_TEXT_NEW_VERSION_NOTES, + Box::new(Textarea::new( + TextareaPropsBuilder::default() + .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) + .with_texts(Some(String::from("Release notes")), spans) + .build(), + )), + ); + self.view.active(super::COMPONENT_TEXT_NEW_VERSION_NOTES); + } + } + } + + /// ### umount_release_notes + /// + /// Umount release notes text area + pub(super) fn umount_release_notes(&mut self) { + self.view.umount(super::COMPONENT_TEXT_NEW_VERSION_NOTES); + } + /// ### get_input /// /// Collect input values from view diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 55834ee..0443bd5 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -379,6 +379,7 @@ impl Update for FileTransferActivity { _ => None, } } + (COMPONENT_INPUT_COPY, _) => None, // -- exec popup (COMPONENT_INPUT_EXEC, &MSG_KEY_ESC) => { self.umount_exec(); @@ -399,6 +400,7 @@ impl Update for FileTransferActivity { _ => None, } } + (COMPONENT_INPUT_EXEC, _) => None, // -- find popup (COMPONENT_INPUT_FIND, &MSG_KEY_ESC) => { self.umount_find_input(); @@ -466,6 +468,7 @@ impl Update for FileTransferActivity { _ => None, } } + (COMPONENT_INPUT_GOTO, _) => None, // -- make directory (COMPONENT_INPUT_MKDIR, &MSG_KEY_ESC) => { self.umount_mkdir(); @@ -485,6 +488,7 @@ impl Update for FileTransferActivity { _ => None, } } + (COMPONENT_INPUT_MKDIR, _) => None, // -- new file (COMPONENT_INPUT_NEWFILE, &MSG_KEY_ESC) => { self.umount_newfile(); @@ -504,6 +508,7 @@ impl Update for FileTransferActivity { _ => None, } } + (COMPONENT_INPUT_NEWFILE, _) => None, // -- open with (COMPONENT_INPUT_OPEN_WITH, &MSG_KEY_ESC) => { self.umount_openwith(); @@ -520,6 +525,7 @@ impl Update for FileTransferActivity { self.umount_openwith(); None } + (COMPONENT_INPUT_OPEN_WITH, _) => None, // -- rename (COMPONENT_INPUT_RENAME, &MSG_KEY_ESC) => { self.umount_rename(); @@ -539,6 +545,7 @@ impl Update for FileTransferActivity { _ => None, } } + (COMPONENT_INPUT_RENAME, _) => None, // -- save as (COMPONENT_INPUT_SAVEAS, &MSG_KEY_ESC) => { self.umount_saveas(); @@ -563,12 +570,14 @@ impl Update for FileTransferActivity { FileExplorerTab::FindRemote => self.update_local_filelist(), } } + (COMPONENT_INPUT_SAVEAS, _) => None, // -- fileinfo (COMPONENT_LIST_FILEINFO, &MSG_KEY_ENTER) | (COMPONENT_LIST_FILEINFO, &MSG_KEY_ESC) => { self.umount_file_info(); None } + (COMPONENT_LIST_FILEINFO, _) => None, // -- delete (COMPONENT_RADIO_DELETE, &MSG_KEY_ESC) | (COMPONENT_RADIO_DELETE, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => { @@ -612,6 +621,7 @@ impl Update for FileTransferActivity { FileExplorerTab::FindRemote => self.update_remote_filelist(), } } + (COMPONENT_RADIO_DELETE, _) => None, // -- disconnect (COMPONENT_RADIO_DISCONNECT, &MSG_KEY_ESC) | (COMPONENT_RADIO_DISCONNECT, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => { @@ -623,6 +633,7 @@ impl Update for FileTransferActivity { self.umount_disconnect(); None } + (COMPONENT_RADIO_DISCONNECT, _) => None, // -- quit (COMPONENT_RADIO_QUIT, &MSG_KEY_ESC) | (COMPONENT_RADIO_QUIT, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => { @@ -634,6 +645,7 @@ impl Update for FileTransferActivity { self.umount_quit(); None } + (COMPONENT_RADIO_QUIT, _) => None, // -- sorting (COMPONENT_RADIO_SORTING, &MSG_KEY_ESC) | (COMPONENT_RADIO_SORTING, Msg::OnSubmit(_)) => { @@ -666,27 +678,32 @@ impl Update for FileTransferActivity { _ => None, } } + (COMPONENT_RADIO_SORTING, _) => None, // -- error (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) => { self.umount_error(); None } + (COMPONENT_TEXT_ERROR, _) => None, // -- fatal (COMPONENT_TEXT_FATAL, &MSG_KEY_ESC) | (COMPONENT_TEXT_FATAL, &MSG_KEY_ENTER) => { self.exit_reason = Some(super::ExitReason::Disconnect); None } + (COMPONENT_TEXT_FATAL, _) => None, // -- help (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) | (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) => { self.umount_help(); None } + (COMPONENT_TEXT_HELP, _) => None, // -- progress bar (COMPONENT_PROGRESS_BAR_PARTIAL, &MSG_KEY_CTRL_C) => { // Set transfer aborted to True self.transfer.abort(); None } + (COMPONENT_PROGRESS_BAR_PARTIAL, _) => None, // -- fallback (_, _) => None, // Nothing to do }, diff --git a/src/ui/activities/setup/mod.rs b/src/ui/activities/setup/mod.rs index dc99cc8..f27ce9d 100644 --- a/src/ui/activities/setup/mod.rs +++ b/src/ui/activities/setup/mod.rs @@ -79,11 +79,6 @@ pub struct SetupActivity { impl Default for SetupActivity { fn default() -> Self { - // Initialize user input - let mut user_input_buffer: Vec = Vec::with_capacity(16); - for _ in 0..16 { - user_input_buffer.push(String::new()); - } SetupActivity { exit_reason: None, context: None, diff --git a/src/ui/activities/setup/update.rs b/src/ui/activities/setup/update.rs index ce4b4e2..7531413 100644 --- a/src/ui/activities/setup/update.rs +++ b/src/ui/activities/setup/update.rs @@ -114,6 +114,7 @@ impl Update for SetupActivity { self.umount_error(); None } + (COMPONENT_TEXT_ERROR, _) => None, // Exit (COMPONENT_RADIO_QUIT, Msg::OnSubmit(Payload::One(Value::Usize(0)))) => { // Save changes @@ -135,12 +136,14 @@ impl Update for SetupActivity { self.umount_quit(); None } + (COMPONENT_RADIO_QUIT, _) => None, // Close help (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => { // Umount help self.umount_help(); None } + (COMPONENT_TEXT_HELP, _) => None, // Delete key (COMPONENT_RADIO_DEL_SSH_KEY, Msg::OnSubmit(Payload::One(Value::Usize(0)))) => { // Delete key @@ -156,6 +159,7 @@ impl Update for SetupActivity { self.umount_del_ssh_key(); None } + (COMPONENT_RADIO_DEL_SSH_KEY, _) => None, // Save popup (COMPONENT_RADIO_SAVE, Msg::OnSubmit(Payload::One(Value::Usize(0)))) => { // Save config @@ -170,6 +174,7 @@ impl Update for SetupActivity { self.umount_save_popup(); None } + (COMPONENT_RADIO_SAVE, _) => None, // Edit SSH Key // Change view (COMPONENT_LIST_SSH_KEYS, &MSG_KEY_TAB) => { diff --git a/src/utils/git.rs b/src/utils/git.rs index 6aae409..7ed9bea 100644 --- a/src/utils/git.rs +++ b/src/utils/git.rs @@ -30,38 +30,39 @@ use super::parser::parse_semver; // Others use serde::Deserialize; -#[derive(Deserialize)] -struct TagInfo { - tag_name: String, +#[derive(Debug, Deserialize)] +/// ## GithubTag +/// +/// Info related to a github tag +pub struct GithubTag { + pub tag_name: String, + pub body: String, } /// ### check_for_updates /// /// Check if there is a new version available for termscp. /// This is performed through the Github API -/// In case of success returns Ok(Option), where the Option is Some(new_version); otherwise if no version is available, return None +/// In case of success returns Ok(Option), where the Option is Some(new_version); otherwise if no version is available, return None /// In case of error returns Error with the error description -pub fn check_for_updates(current_version: &str) -> Result, String> { +pub fn check_for_updates(current_version: &str) -> Result, String> { // Send request - let github_version: Result = + let github_tag: Result = match ureq::get("https://api.github.com/repos/veeso/termscp/releases/latest").call() { - Ok(response) => match response.into_json::() { - Ok(tag_info) => Ok(tag_info.tag_name), - Err(err) => Err(err.to_string()), - }, + Ok(response) => response.into_json::().map_err(|x| x.to_string()), Err(err) => Err(err.to_string()), }; // Check version - match github_version { + match github_tag { Err(err) => Err(err), - Ok(version) => { + Ok(tag) => { // Parse version - match parse_semver(version.as_str()) { + match parse_semver(tag.tag_name.as_str()) { Some(new_version) => { // Check if version is different if new_version.as_str() > current_version { - Ok(Some(new_version)) // New version is available + Ok(Some(tag)) // New version is available } else { Ok(None) // No new version }