mirror of
https://github.com/veeso/termscp.git
synced 2025-12-06 17:15:35 -08:00
chmod popup (#183)
* feat: chmod popup * fix: windows shall not allows chmod on localhost
This commit is contained in:
committed by
GitHub
parent
b4fa50a666
commit
79dd9e2303
@@ -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
|
||||
|
||||
@@ -214,6 +214,7 @@ In order to change panel you need to type `<LEFT>` to move the remote explorer p
|
||||
| `<W>` | Open file with provided program | With |
|
||||
| `<X>` | Execute a command | eXecute |
|
||||
| `<Y>` | Toggle synchronized browsing | sYnc |
|
||||
| `<Z>` | Change file mode | |
|
||||
| `<CTRL+A>` | Select all files | |
|
||||
| `<CTRL+C>` | Abort file transfer process | |
|
||||
| `<CTRL+T>` | Show all synchronized paths | Track |
|
||||
|
||||
@@ -214,6 +214,7 @@ Para cambiar de panel, debe escribir `<LEFT>` para mover el panel del explorador
|
||||
| `<W>` | Abrir archivo con el programa proporcionado | With |
|
||||
| `<X>` | Ejecutar un comando | eXecute |
|
||||
| `<Y>` | Alternar navegación sincronizada | sYnc |
|
||||
| `<Z>` | Cambiar ppermisos de archivo | |
|
||||
| `<CTRL+A>` | Seleccionar todos los archivos | |
|
||||
| `<CTRL+C>` | Abortar el proceso de transferencia de archivos | |
|
||||
| `<CTRL+T>` | Mostrar todas las rutas sincronizadas | Track |
|
||||
|
||||
@@ -212,6 +212,7 @@ Pour changer de panneau, vous devez taper `<LEFT>` pour déplacer le panneau de
|
||||
| `<W>` | Ouvrir le fichier avec le programme spécifié | With |
|
||||
| `<X>` | Exécuter une commande | eXecute |
|
||||
| `<Y>` | Basculer la navigation synchronisée | sYnc |
|
||||
| `<Z>` | Changer permissions de fichier | |
|
||||
| `<CTRL+A>` | Sélectionner tous les fichiers | |
|
||||
| `<CTRL+C>` | Abandonner le processus de transfert de fichiers | |
|
||||
| `<CTRL+T>` | Afficher tous les chemins synchronisés | Track |
|
||||
|
||||
@@ -208,6 +208,7 @@ Per cambiare pannello ti puoi muovere con le frecce, `<LEFT>` per andare sul pan
|
||||
| `<W>` | Apri il file con il programma specificato | With |
|
||||
| `<X>` | Esegui comando shell | eXecute |
|
||||
| `<Y>` | Abilita/disabilita Sync-Browsing | sYnc |
|
||||
| `<Z>` | Modifica permessi file | |
|
||||
| `<CTRL+A>` | Seleziona tutti i file | |
|
||||
| `<CTRL+C>` | Annulla trasferimento file | |
|
||||
| `<CTRL+T>` | Visualizza tutti i percorsi sincronizzati | Track |
|
||||
|
||||
@@ -212,6 +212,7 @@ In order to change panel you need to type `<LEFT>` to move the remote explorer p
|
||||
| `<W>` | Open file with provided program | With |
|
||||
| `<X>` | Execute a command | eXecute |
|
||||
| `<Y>` | Toggle synchronized browsing | sYnc |
|
||||
| `<Z>` | Change file mode | |
|
||||
| `<CTRL+A>` | Select all files | |
|
||||
| `<CTRL+C>` | Abort file transfer process | |
|
||||
| `<CTRL+T>` | Show all synchronized paths | Track |
|
||||
|
||||
@@ -209,6 +209,7 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到
|
||||
| `<W>` | 使用指定程序打开文件 | With |
|
||||
| `<X>` | 运行命令 | eXecute |
|
||||
| `<Y>` | 是否开启同步浏览 | sYnc |
|
||||
| `<Z>` | 更改文件权限 | |
|
||||
| `<CTRL+A>` | 选中所有文件 | |
|
||||
| `<CTRL+C>` | 终止文件传输 | |
|
||||
| `<CTRL+T>` | 显示所有同步路径 | Track |
|
||||
|
||||
@@ -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;
|
||||
|
||||
101
src/ui/activities/filetransfer/actions/chmod.rs
Normal file
101
src/ui/activities/filetransfer/actions/chmod.rs
Normal file
@@ -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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<UnixPex> {
|
||||
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<File> {
|
||||
match self {
|
||||
Self::One(file) => vec![file],
|
||||
Self::Many(files) => files,
|
||||
Self::None => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SelectedFileIndex {
|
||||
One(usize),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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("<P>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Toggle log panel"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<Q|F10>").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("<Z>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Change file permissions"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<DEL|F8|E>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Delete selected file"))
|
||||
|
||||
267
src/ui/activities/filetransfer/components/popups/chmod.rs
Normal file
267
src/ui/activities/filetransfer/components/popups/chmod.rs
Normal file
@@ -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<usize> {
|
||||
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<usize> = 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<AttrValue> {
|
||||
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<Msg, NoUserEvent> for ChmodPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,10 @@ impl Component<Msg, NoUserEvent> 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<Msg, NoUserEvent> 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<Msg, NoUserEvent> 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Msg> {
|
||||
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(),
|
||||
|
||||
@@ -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,
|
||||
)))),
|
||||
)),
|
||||
)),
|
||||
)),
|
||||
)),
|
||||
|
||||
Reference in New Issue
Block a user