From 79dd9e2303134909a3c48fa59f5cc6b121f34062 Mon Sep 17 00:00:00 2001 From: Christian Visintin Date: Wed, 10 May 2023 17:04:24 +0200 Subject: [PATCH] chmod popup (#183) * feat: chmod popup * fix: windows shall not allows chmod on localhost --- CHANGELOG.md | 2 + docs/de/man.md | 1 + docs/es/man.md | 1 + docs/fr/man.md | 1 + docs/it/man.md | 1 + docs/man.md | 1 + docs/zh-CN/man.md | 1 + src/config/themes.rs | 3 +- .../activities/filetransfer/actions/chmod.rs | 101 +++++++ src/ui/activities/filetransfer/actions/mod.rs | 23 ++ .../activities/filetransfer/components/mod.rs | 4 +- .../filetransfer/components/popups.rs | 8 + .../filetransfer/components/popups/chmod.rs | 267 ++++++++++++++++++ .../filetransfer/components/transfer/mod.rs | 12 + src/ui/activities/filetransfer/mod.rs | 4 + src/ui/activities/filetransfer/update.rs | 43 +++ src/ui/activities/filetransfer/view.rs | 36 ++- 17 files changed, 502 insertions(+), 7 deletions(-) create mode 100644 src/ui/activities/filetransfer/actions/chmod.rs create mode 100644 src/ui/activities/filetransfer/components/popups/chmod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0689a89..6794b32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ Released on ?? +- **Change file permissions**: you can now change file permissions easily with the permissions popup pressing `Z` in the explorer. + - [Issue 172](https://github.com/veeso/termscp/issues/172) - [Issue 153](https://github.com/veeso/termscp/issues/153): show a loading message when loading directory's content - [Issue 176](https://github.com/veeso/termscp/issues/176): debug log is now written to CACHE_DIR - [Issue 173](https://github.com/veeso/termscp/issues/173): allow unknown fields in ssh2 configuration file diff --git a/docs/de/man.md b/docs/de/man.md index 2dd5eba..85a875c 100644 --- a/docs/de/man.md +++ b/docs/de/man.md @@ -214,6 +214,7 @@ In order to change panel you need to type `` to move the remote explorer p | `` | Open file with provided program | With | | `` | Execute a command | eXecute | | `` | Toggle synchronized browsing | sYnc | +| `` | Change file mode | | | `` | Select all files | | | `` | Abort file transfer process | | | `` | Show all synchronized paths | Track | diff --git a/docs/es/man.md b/docs/es/man.md index a39585a..cad7200 100644 --- a/docs/es/man.md +++ b/docs/es/man.md @@ -214,6 +214,7 @@ Para cambiar de panel, debe escribir `` para mover el panel del explorador | `` | Abrir archivo con el programa proporcionado | With | | `` | Ejecutar un comando | eXecute | | `` | Alternar navegación sincronizada | sYnc | +| `` | Cambiar ppermisos de archivo | | | `` | Seleccionar todos los archivos | | | `` | Abortar el proceso de transferencia de archivos | | | `` | Mostrar todas las rutas sincronizadas | Track | diff --git a/docs/fr/man.md b/docs/fr/man.md index e459e79..dacc17a 100644 --- a/docs/fr/man.md +++ b/docs/fr/man.md @@ -212,6 +212,7 @@ Pour changer de panneau, vous devez taper `` pour déplacer le panneau de | `` | Ouvrir le fichier avec le programme spécifié | With | | `` | Exécuter une commande | eXecute | | `` | Basculer la navigation synchronisée | sYnc | +| `` | Changer permissions de fichier | | | `` | Sélectionner tous les fichiers | | | `` | Abandonner le processus de transfert de fichiers | | | `` | Afficher tous les chemins synchronisés | Track | diff --git a/docs/it/man.md b/docs/it/man.md index 3833e7d..68d3d15 100644 --- a/docs/it/man.md +++ b/docs/it/man.md @@ -208,6 +208,7 @@ Per cambiare pannello ti puoi muovere con le frecce, `` per andare sul pan | `` | Apri il file con il programma specificato | With | | `` | Esegui comando shell | eXecute | | `` | Abilita/disabilita Sync-Browsing | sYnc | +| `` | Modifica permessi file | | | `` | Seleziona tutti i file | | | `` | Annulla trasferimento file | | | `` | Visualizza tutti i percorsi sincronizzati | Track | diff --git a/docs/man.md b/docs/man.md index f8ad187..6018972 100644 --- a/docs/man.md +++ b/docs/man.md @@ -212,6 +212,7 @@ In order to change panel you need to type `` to move the remote explorer p | `` | Open file with provided program | With | | `` | Execute a command | eXecute | | `` | Toggle synchronized browsing | sYnc | +| `` | Change file mode | | | `` | Select all files | | | `` | Abort file transfer process | | | `` | Show all synchronized paths | Track | diff --git a/docs/zh-CN/man.md b/docs/zh-CN/man.md index 67ad99b..b7accc2 100644 --- a/docs/zh-CN/man.md +++ b/docs/zh-CN/man.md @@ -209,6 +209,7 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到 | `` | 使用指定程序打开文件 | With | | `` | 运行命令 | eXecute | | `` | 是否开启同步浏览 | sYnc | +| `` | 更改文件权限 | | | `` | 选中所有文件 | | | `` | 终止文件传输 | | | `` | 显示所有同步路径 | Track | diff --git a/src/config/themes.rs b/src/config/themes.rs index 7101f9e..f9ecfa0 100644 --- a/src/config/themes.rs +++ b/src/config/themes.rs @@ -4,7 +4,8 @@ // locals // ext -use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer}; +use serde::de::Error as DeError; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use tuirealm::tui::style::Color; use crate::utils::fmt::fmt_color; diff --git a/src/ui/activities/filetransfer/actions/chmod.rs b/src/ui/activities/filetransfer/actions/chmod.rs new file mode 100644 index 0000000..ffcc7ff --- /dev/null +++ b/src/ui/activities/filetransfer/actions/chmod.rs @@ -0,0 +1,101 @@ +use remotefs::fs::UnixPex; + +use super::{FileTransferActivity, LogLevel}; + +impl FileTransferActivity { + #[cfg(target_family = "unix")] + pub fn action_local_chmod(&mut self, mode: UnixPex) { + let files = self.get_local_selected_entries().get_files(); + + for file in files { + if let Err(err) = self.host.chmod(file.path(), mode) { + self.log_and_alert( + LogLevel::Error, + format!( + "could not change mode for {}: {}", + file.path().display(), + err + ), + ); + return; + } + self.log( + LogLevel::Info, + format!("changed mode to {:#o} for {}", u32::from(mode), file.name()), + ); + } + } + + pub fn action_remote_chmod(&mut self, mode: UnixPex) { + let files = self.get_remote_selected_entries().get_files(); + + for file in files { + let mut metadata = file.metadata.clone(); + metadata.mode = Some(mode); + + if let Err(err) = self.client.setstat(file.path(), metadata) { + self.log_and_alert( + LogLevel::Error, + format!( + "could not change mode for {}: {}", + file.path().display(), + err + ), + ); + return; + } + self.log( + LogLevel::Info, + format!("changed mode to {:#o} for {}", u32::from(mode), file.name()), + ); + } + } + + #[cfg(target_family = "unix")] + pub fn action_find_local_chmod(&mut self, mode: UnixPex) { + let files = self.get_found_selected_entries().get_files(); + + for file in files { + if let Err(err) = self.host.chmod(file.path(), mode) { + self.log_and_alert( + LogLevel::Error, + format!( + "could not change mode for {}: {}", + file.path().display(), + err + ), + ); + return; + } + self.log( + LogLevel::Info, + format!("changed mode to {:#o} for {}", u32::from(mode), file.name()), + ); + } + } + + pub fn action_find_remote_chmod(&mut self, mode: UnixPex) { + let files = self.get_found_selected_entries().get_files(); + + for file in files { + let mut metadata = file.metadata.clone(); + metadata.mode = Some(mode); + + if let Err(err) = self.client.setstat(file.path(), metadata) { + self.log_and_alert( + LogLevel::Error, + format!( + "could not change mode for {}: {}", + file.path().display(), + err + ), + ); + return; + } + self.log( + LogLevel::Info, + format!("changed mode to {:#o} for {}", u32::from(mode), file.name()), + ); + } + } +} diff --git a/src/ui/activities/filetransfer/actions/mod.rs b/src/ui/activities/filetransfer/actions/mod.rs index c2cc9b2..d3b1942 100644 --- a/src/ui/activities/filetransfer/actions/mod.rs +++ b/src/ui/activities/filetransfer/actions/mod.rs @@ -2,6 +2,7 @@ //! //! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall +use remotefs::fs::UnixPex; pub(self) use remotefs::File; use tuirealm::{State, StateValue}; @@ -13,6 +14,7 @@ pub(self) use super::{ // actions pub(crate) mod change_dir; +pub(crate) mod chmod; pub(crate) mod copy; pub(crate) mod delete; pub(crate) mod edit; @@ -35,6 +37,27 @@ pub(crate) enum SelectedFile { None, } +impl SelectedFile { + /// Get file mode for `SelectedFile` + /// In case is `Many` the first item mode is returned + pub fn unix_pex(&self) -> Option { + match self { + Self::Many(files) => files.iter().next().and_then(|file| file.metadata().mode), + Self::One(file) => file.metadata().mode, + Self::None => None, + } + } + + /// Get files as vec + pub fn get_files(self) -> Vec { + match self { + Self::One(file) => vec![file], + Self::Many(files) => files, + Self::None => vec![], + } + } +} + #[derive(Debug)] enum SelectedFileIndex { One(usize), diff --git a/src/ui/activities/filetransfer/components/mod.rs b/src/ui/activities/filetransfer/components/mod.rs index 6ec0681..3354081 100644 --- a/src/ui/activities/filetransfer/components/mod.rs +++ b/src/ui/activities/filetransfer/components/mod.rs @@ -16,8 +16,8 @@ mod transfer; pub use misc::FooterBar; pub use popups::{ - CopyPopup, DeletePopup, DisconnectPopup, ErrorPopup, ExecPopup, FatalPopup, FileInfoPopup, - FindPopup, GoToPopup, KeybindingsPopup, MkdirPopup, NewfilePopup, OpenWithPopup, + ChmodPopup, CopyPopup, DeletePopup, DisconnectPopup, ErrorPopup, ExecPopup, FatalPopup, + FileInfoPopup, FindPopup, GoToPopup, KeybindingsPopup, MkdirPopup, NewfilePopup, OpenWithPopup, ProgressBarFull, ProgressBarPartial, QuitPopup, RenamePopup, ReplacePopup, ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote, SymlinkPopup, SyncBrowsingMkdirPopup, WaitPopup, WatchedPathsList, WatcherPopup, diff --git a/src/ui/activities/filetransfer/components/popups.rs b/src/ui/activities/filetransfer/components/popups.rs index bb13da9..90f23c7 100644 --- a/src/ui/activities/filetransfer/components/popups.rs +++ b/src/ui/activities/filetransfer/components/popups.rs @@ -21,6 +21,10 @@ use super::{Msg, PendingActionMsg, TransferMsg, UiMsg}; use crate::explorer::FileSorting; use crate::utils::fmt::fmt_time; +mod chmod; + +pub use chmod::ChmodPopup; + #[derive(MockComponent)] pub struct CopyPopup { component: Input, @@ -746,6 +750,8 @@ impl KeybindingsPopup { .add_col(TextSpan::from( " Open text file with preferred editor", )) + .add_col(TextSpan::new("

").bold().fg(key_color)) + .add_col(TextSpan::from(" Toggle log panel")) .add_row() .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Quit termscp")) @@ -779,6 +785,8 @@ impl KeybindingsPopup { .add_col(TextSpan::from( " Toggle synchronized browsing", )) + .add_col(TextSpan::new("").bold().fg(key_color)) + .add_col(TextSpan::from(" Change file permissions")) .add_row() .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Delete selected file")) diff --git a/src/ui/activities/filetransfer/components/popups/chmod.rs b/src/ui/activities/filetransfer/components/popups/chmod.rs new file mode 100644 index 0000000..8bac148 --- /dev/null +++ b/src/ui/activities/filetransfer/components/popups/chmod.rs @@ -0,0 +1,267 @@ +use remotefs::fs::{UnixPex, UnixPexClass}; +use tui_realm_stdlib::Checkbox; +use tuirealm::command::{Cmd, CmdResult, Direction}; +use tuirealm::event::{Key, KeyEvent}; +use tuirealm::props::{Alignment, AttrValue, Attribute, BorderSides, Borders, Color}; +use tuirealm::tui::layout::{Constraint, Direction as LayoutDirection, Layout}; +use tuirealm::{Component, Event, MockComponent, NoUserEvent, Props, State, StateValue}; + +use super::{Msg, TransferMsg, UiMsg}; + +#[derive(Default, Clone, Copy, PartialEq, Eq)] +pub enum Item { + #[default] + User, + Group, + Others, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct States { + focus: Item, +} + +/// Permissions popup for chmod command +pub struct ChmodPopup { + props: Props, + states: States, + title: String, + color: Color, + user: Checkbox, + group: Checkbox, + others: Checkbox, +} + +/// Make checkbox values from unix pex class +fn make_pex_values(mode: UnixPexClass) -> Vec { + let mut values = Vec::with_capacity(3); + if mode.read() { + values.push(0); + } + if mode.write() { + values.push(1); + } + if mode.execute() { + values.push(2); + } + + values +} + +impl ChmodPopup { + pub fn new(pex: UnixPex, color: Color, title: String) -> Self { + Self { + props: Props::default(), + color, + title, + states: States { + focus: Item::default(), + }, + user: Checkbox::default() + .foreground(color) + .choices(&["Read", "Write", "Execute"]) + .title("User", Alignment::Left) + .borders(Borders::default().sides(BorderSides::NONE)) + .values(&make_pex_values(pex.user())) + .rewind(true), + group: Checkbox::default() + .foreground(color) + .choices(&["Read", "Write", "Execute"]) + .title("Group", Alignment::Left) + .borders(Borders::default().sides(BorderSides::NONE)) + .values(&make_pex_values(pex.group())) + .rewind(true), + others: Checkbox::default() + .foreground(color) + .choices(&["Read", "Write", "Execute"]) + .title("Others", Alignment::Left) + .borders(Borders::default().sides(BorderSides::NONE)) + .values(&make_pex_values(pex.others())) + .rewind(true), + } + } + + fn get_active_checkbox(&mut self) -> &'_ mut Checkbox { + match self.states.focus { + Item::Group => &mut self.group, + Item::Others => &mut self.others, + Item::User => &mut self.user, + } + } + + fn toggle_checkbox_focus(&mut self, value: bool) { + match self.states.focus { + Item::User => self.user.attr(Attribute::Focus, AttrValue::Flag(value)), + Item::Group => self.group.attr(Attribute::Focus, AttrValue::Flag(value)), + Item::Others => self.others.attr(Attribute::Focus, AttrValue::Flag(value)), + } + } + + fn active_checkbox_up(&mut self) { + self.toggle_checkbox_focus(false); + let next = match self.states.focus { + Item::User => Item::Others, + Item::Group => Item::User, + Item::Others => Item::Group, + }; + + self.states.focus = next; + + self.toggle_checkbox_focus(true); + } + + fn active_checkbox_down(&mut self) { + self.toggle_checkbox_focus(false); + let next = match self.states.focus { + Item::User => Item::Group, + Item::Group => Item::Others, + Item::Others => Item::User, + }; + + self.states.focus = next; + + self.toggle_checkbox_focus(true); + } + + fn checkbox_state_to_pex_class(state: State) -> UnixPexClass { + let values: Vec = state + .unwrap_vec() + .into_iter() + .map(|x| x.unwrap_usize()) + .collect(); + + UnixPexClass::new( + values.contains(&0), + values.contains(&1), + values.contains(&2), + ) + } + + fn get_mode(&self) -> UnixPex { + UnixPex::new( + Self::checkbox_state_to_pex_class(self.user.state()), + Self::checkbox_state_to_pex_class(self.group.state()), + Self::checkbox_state_to_pex_class(self.others.state()), + ) + } +} + +impl MockComponent for ChmodPopup { + fn attr(&mut self, attr: tuirealm::Attribute, value: AttrValue) { + self.props.set(attr, value.clone()); + + if attr == Attribute::Focus { + self.get_active_checkbox().attr(attr, value); + } else { + self.user.attr(attr, value.clone()); + self.group.attr(attr, value.clone()); + self.others.attr(attr, value); + } + } + + fn perform(&mut self, cmd: Cmd) -> CmdResult { + match cmd { + Cmd::Move(Direction::Left) | Cmd::Move(Direction::Right) => { + self.get_active_checkbox().perform(cmd) + } + Cmd::Move(Direction::Up) => { + self.active_checkbox_up(); + CmdResult::None + } + Cmd::Move(Direction::Down) => { + self.active_checkbox_down(); + CmdResult::None + } + Cmd::Toggle => self.get_active_checkbox().perform(cmd), + Cmd::Submit => CmdResult::Submit(self.state()), + _ => CmdResult::None, + } + } + + fn query(&self, attr: tuirealm::Attribute) -> Option { + self.props.get(attr) + } + + fn state(&self) -> State { + State::One(StateValue::U32(self.get_mode().into())) + } + + fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::tui::layout::Rect) { + if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) != AttrValue::Flag(true) { + return; + } + let chunks = Layout::default() + .direction(LayoutDirection::Vertical) + .margin(1) + .constraints( + [ + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + ] + .as_ref(), + ) + .split(area); + + let focus = self + .props + .get_or(Attribute::Focus, AttrValue::Flag(false)) + .unwrap_flag(); + + let div = tui_realm_stdlib::utils::get_block( + Borders::default().color(self.color), + Some((self.title.clone(), Alignment::Center)), + focus, + None, + ); + + frame.render_widget(div, area); + + self.user.view(frame, chunks[0]); + self.group.view(frame, chunks[1]); + self.others.view(frame, chunks[2]); + } +} + +impl Component for ChmodPopup { + fn on(&mut self, ev: Event) -> Option { + match ev { + Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { + Some(Msg::Ui(UiMsg::CloseChmodPopup)) + } + 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::Up, .. }) => { + self.perform(Cmd::Move(Direction::Up)); + Some(Msg::None) + } + Event::Keyboard(KeyEvent { + code: Key::Down, .. + }) => { + self.perform(Cmd::Move(Direction::Down)); + Some(Msg::None) + } + Event::Keyboard(KeyEvent { + code: Key::Char(' '), + .. + }) => { + self.perform(Cmd::Toggle); + Some(Msg::None) + } + Event::Keyboard(KeyEvent { + code: Key::Enter, .. + }) => Some(Msg::Transfer(TransferMsg::Chmod(self.get_mode()))), + _ => None, + } + } +} diff --git a/src/ui/activities/filetransfer/components/transfer/mod.rs b/src/ui/activities/filetransfer/components/transfer/mod.rs index f8981b6..df6b305 100644 --- a/src/ui/activities/filetransfer/components/transfer/mod.rs +++ b/src/ui/activities/filetransfer/components/transfer/mod.rs @@ -127,6 +127,10 @@ impl Component for ExplorerFind { code: Key::Char('w'), modifiers: KeyModifiers::NONE, }) => Some(Msg::Ui(UiMsg::ShowOpenWithPopup)), + Event::Keyboard(KeyEvent { + code: Key::Char('z'), + modifiers: KeyModifiers::NONE, + }) => Some(Msg::Ui(UiMsg::ShowChmodPopup)), _ => None, } } @@ -308,6 +312,10 @@ impl Component for ExplorerLocal { code: Key::Char('w'), modifiers: KeyModifiers::NONE, }) => Some(Msg::Ui(UiMsg::ShowOpenWithPopup)), + Event::Keyboard(KeyEvent { + code: Key::Char('z'), + modifiers: KeyModifiers::NONE, + }) => Some(Msg::Ui(UiMsg::ShowChmodPopup)), _ => None, } } @@ -489,6 +497,10 @@ impl Component for ExplorerRemote { code: Key::Char('w'), modifiers: KeyModifiers::NONE, }) => Some(Msg::Ui(UiMsg::ShowOpenWithPopup)), + Event::Keyboard(KeyEvent { + code: Key::Char('z'), + modifiers: KeyModifiers::NONE, + }) => Some(Msg::Ui(UiMsg::ShowChmodPopup)), _ => None, } } diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index d0ab6a4..d64f330 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -38,6 +38,7 @@ use crate::system::watcher::FsWatcher; #[derive(Debug, Eq, PartialEq, Clone, Hash)] enum Id { + ChmodPopup, CopyPopup, DeletePopup, DisconnectPopup, @@ -93,6 +94,7 @@ enum PendingActionMsg { #[derive(Debug, PartialEq)] enum TransferMsg { AbortTransfer, + Chmod(remotefs::fs::UnixPex), CopyFileTo(String), CreateSymlink(String), DeleteFile, @@ -119,6 +121,7 @@ enum TransferMsg { enum UiMsg { ChangeFileSorting(FileSorting), ChangeTransferWindow, + CloseChmodPopup, CloseCopyPopup, CloseDeletePopup, CloseDisconnectPopup, @@ -144,6 +147,7 @@ enum UiMsg { LogBackTabbed, Quit, ReplacePopupTabbed, + ShowChmodPopup, ShowCopyPopup, ShowDeletePopup, ShowDisconnectPopup, diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index e5915ea..1da2840 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -32,6 +32,22 @@ impl FileTransferActivity { TransferMsg::AbortTransfer => { self.transfer.abort(); } + TransferMsg::Chmod(mode) => { + self.umount_chmod(); + self.mount_blocking_wait("Applying new file mode…"); + match self.browser.tab() { + #[cfg(target_family = "unix")] + FileExplorerTab::Local => self.action_local_chmod(mode), + #[cfg(target_family = "unix")] + FileExplorerTab::FindLocal => self.action_find_local_chmod(mode), + FileExplorerTab::Remote => self.action_remote_chmod(mode), + FileExplorerTab::FindRemote => self.action_find_remote_chmod(mode), + #[cfg(target_family = "windows")] + FileExplorerTab::Local | FileExplorerTab::FindLocal => {} + } + self.umount_wait(); + self.update_browser_file_list(); + } TransferMsg::CopyFileTo(dest) => { self.umount_copy(); self.mount_blocking_wait("Copying file(s)…"); @@ -336,6 +352,7 @@ impl FileTransferActivity { fn update_ui(&mut self, msg: UiMsg) -> Option { match msg { + UiMsg::CloseChmodPopup => self.umount_chmod(), UiMsg::ChangeFileSorting(sorting) => { match self.browser.tab() { FileExplorerTab::Local | FileExplorerTab::FindLocal => { @@ -422,6 +439,32 @@ impl FileTransferActivity { assert!(self.app.active(&Id::ReplacePopup).is_ok()); } } + UiMsg::ShowChmodPopup => { + let selected_file = match self.browser.tab() { + #[cfg(target_family = "unix")] + FileExplorerTab::Local => self.get_local_selected_entries(), + #[cfg(target_family = "unix")] + FileExplorerTab::FindLocal => self.get_found_selected_entries(), + FileExplorerTab::Remote => self.get_remote_selected_entries(), + FileExplorerTab::FindRemote => self.get_found_selected_entries(), + #[cfg(target_family = "windows")] + FileExplorerTab::Local | FileExplorerTab::FindLocal => SelectedFile::None, + }; + if let Some(mode) = selected_file.unix_pex() { + self.mount_chmod( + mode, + match selected_file { + SelectedFile::Many(files) => { + format!("changing mode for {} files…", files.len()) + } + SelectedFile::One(file) => { + format!("changing mode for {}…", file.name()) + } + SelectedFile::None => "".to_string(), + }, + ); + } + } UiMsg::ShowCopyPopup => self.mount_copy(), UiMsg::ShowDeletePopup => self.mount_radio_delete(), UiMsg::ShowDisconnectPopup => self.mount_disconnect(), diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index 6b73a5d..c504fde 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -4,7 +4,7 @@ // locals // Ext -use remotefs::fs::File; +use remotefs::fs::{File, UnixPex}; use tuirealm::event::{Key, KeyEvent, KeyModifiers}; use tuirealm::tui::layout::{Constraint, Direction, Layout}; use tuirealm::tui::widgets::Clear; @@ -158,6 +158,11 @@ impl FileTransferActivity { f.render_widget(Clear, popup); // make popup self.app.view(&Id::CopyPopup, f, popup); + } else if self.app.mounted(&Id::ChmodPopup) { + let popup = Popup(Size::Percentage(50), Size::Unit(12)).draw_in(f.size()); + f.render_widget(Clear, popup); + // make popup + self.app.view(&Id::ChmodPopup, f, popup); } else if self.app.mounted(&Id::FindPopup) { let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size()); f.render_widget(Clear, popup); @@ -435,6 +440,24 @@ impl FileTransferActivity { let _ = self.app.umount(&Id::DisconnectPopup); } + pub(super) fn mount_chmod(&mut self, mode: UnixPex, title: String) { + // Mount + let color = self.theme().misc_input_dialog; + assert!(self + .app + .remount( + Id::ChmodPopup, + Box::new(components::ChmodPopup::new(mode, color, title)), + vec![], + ) + .is_ok()); + assert!(self.app.active(&Id::ChmodPopup).is_ok()); + } + + pub(super) fn umount_chmod(&mut self) { + let _ = self.app.umount(&Id::ChmodPopup); + } + pub(super) fn mount_copy(&mut self) { let input_color = self.theme().misc_input_dialog; assert!(self @@ -1068,9 +1091,14 @@ impl FileTransferActivity { Box::new(SubClause::Not(Box::new(SubClause::IsMounted( Id::WatchedPathsList, )))), - Box::new(SubClause::Not(Box::new(SubClause::IsMounted( - Id::WaitPopup, - )))), + Box::new(SubClause::And( + Box::new(SubClause::Not(Box::new(SubClause::IsMounted( + Id::ChmodPopup, + )))), + Box::new(SubClause::Not(Box::new(SubClause::IsMounted( + Id::WaitPopup, + )))), + )), )), )), )),