mirror of
https://github.com/veeso/termscp.git
synced 2026-09-26 05:51:20 -07:00
chmod popup (#183)
* feat: chmod popup * fix: windows shall not allows chmod on localhost
This commit is contained in:
@@ -35,6 +35,8 @@
|
|||||||
|
|
||||||
Released on ??
|
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 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 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
|
- [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 |
|
| `<W>` | Open file with provided program | With |
|
||||||
| `<X>` | Execute a command | eXecute |
|
| `<X>` | Execute a command | eXecute |
|
||||||
| `<Y>` | Toggle synchronized browsing | sYnc |
|
| `<Y>` | Toggle synchronized browsing | sYnc |
|
||||||
|
| `<Z>` | Change file mode | |
|
||||||
| `<CTRL+A>` | Select all files | |
|
| `<CTRL+A>` | Select all files | |
|
||||||
| `<CTRL+C>` | Abort file transfer process | |
|
| `<CTRL+C>` | Abort file transfer process | |
|
||||||
| `<CTRL+T>` | Show all synchronized paths | Track |
|
| `<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 |
|
| `<W>` | Abrir archivo con el programa proporcionado | With |
|
||||||
| `<X>` | Ejecutar un comando | eXecute |
|
| `<X>` | Ejecutar un comando | eXecute |
|
||||||
| `<Y>` | Alternar navegación sincronizada | sYnc |
|
| `<Y>` | Alternar navegación sincronizada | sYnc |
|
||||||
|
| `<Z>` | Cambiar ppermisos de archivo | |
|
||||||
| `<CTRL+A>` | Seleccionar todos los archivos | |
|
| `<CTRL+A>` | Seleccionar todos los archivos | |
|
||||||
| `<CTRL+C>` | Abortar el proceso de transferencia de archivos | |
|
| `<CTRL+C>` | Abortar el proceso de transferencia de archivos | |
|
||||||
| `<CTRL+T>` | Mostrar todas las rutas sincronizadas | Track |
|
| `<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 |
|
| `<W>` | Ouvrir le fichier avec le programme spécifié | With |
|
||||||
| `<X>` | Exécuter une commande | eXecute |
|
| `<X>` | Exécuter une commande | eXecute |
|
||||||
| `<Y>` | Basculer la navigation synchronisée | sYnc |
|
| `<Y>` | Basculer la navigation synchronisée | sYnc |
|
||||||
|
| `<Z>` | Changer permissions de fichier | |
|
||||||
| `<CTRL+A>` | Sélectionner tous les fichiers | |
|
| `<CTRL+A>` | Sélectionner tous les fichiers | |
|
||||||
| `<CTRL+C>` | Abandonner le processus de transfert de fichiers | |
|
| `<CTRL+C>` | Abandonner le processus de transfert de fichiers | |
|
||||||
| `<CTRL+T>` | Afficher tous les chemins synchronisés | Track |
|
| `<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 |
|
| `<W>` | Apri il file con il programma specificato | With |
|
||||||
| `<X>` | Esegui comando shell | eXecute |
|
| `<X>` | Esegui comando shell | eXecute |
|
||||||
| `<Y>` | Abilita/disabilita Sync-Browsing | sYnc |
|
| `<Y>` | Abilita/disabilita Sync-Browsing | sYnc |
|
||||||
|
| `<Z>` | Modifica permessi file | |
|
||||||
| `<CTRL+A>` | Seleziona tutti i file | |
|
| `<CTRL+A>` | Seleziona tutti i file | |
|
||||||
| `<CTRL+C>` | Annulla trasferimento file | |
|
| `<CTRL+C>` | Annulla trasferimento file | |
|
||||||
| `<CTRL+T>` | Visualizza tutti i percorsi sincronizzati | Track |
|
| `<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 |
|
| `<W>` | Open file with provided program | With |
|
||||||
| `<X>` | Execute a command | eXecute |
|
| `<X>` | Execute a command | eXecute |
|
||||||
| `<Y>` | Toggle synchronized browsing | sYnc |
|
| `<Y>` | Toggle synchronized browsing | sYnc |
|
||||||
|
| `<Z>` | Change file mode | |
|
||||||
| `<CTRL+A>` | Select all files | |
|
| `<CTRL+A>` | Select all files | |
|
||||||
| `<CTRL+C>` | Abort file transfer process | |
|
| `<CTRL+C>` | Abort file transfer process | |
|
||||||
| `<CTRL+T>` | Show all synchronized paths | Track |
|
| `<CTRL+T>` | Show all synchronized paths | Track |
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到
|
|||||||
| `<W>` | 使用指定程序打开文件 | With |
|
| `<W>` | 使用指定程序打开文件 | With |
|
||||||
| `<X>` | 运行命令 | eXecute |
|
| `<X>` | 运行命令 | eXecute |
|
||||||
| `<Y>` | 是否开启同步浏览 | sYnc |
|
| `<Y>` | 是否开启同步浏览 | sYnc |
|
||||||
|
| `<Z>` | 更改文件权限 | |
|
||||||
| `<CTRL+A>` | 选中所有文件 | |
|
| `<CTRL+A>` | 选中所有文件 | |
|
||||||
| `<CTRL+C>` | 终止文件传输 | |
|
| `<CTRL+C>` | 终止文件传输 | |
|
||||||
| `<CTRL+T>` | 显示所有同步路径 | Track |
|
| `<CTRL+T>` | 显示所有同步路径 | Track |
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
// locals
|
// locals
|
||||||
// ext
|
// 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 tuirealm::tui::style::Color;
|
||||||
|
|
||||||
use crate::utils::fmt::fmt_color;
|
use crate::utils::fmt::fmt_color;
|
||||||
|
|||||||
@@ -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
|
//! `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;
|
pub(self) use remotefs::File;
|
||||||
use tuirealm::{State, StateValue};
|
use tuirealm::{State, StateValue};
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ pub(self) use super::{
|
|||||||
|
|
||||||
// actions
|
// actions
|
||||||
pub(crate) mod change_dir;
|
pub(crate) mod change_dir;
|
||||||
|
pub(crate) mod chmod;
|
||||||
pub(crate) mod copy;
|
pub(crate) mod copy;
|
||||||
pub(crate) mod delete;
|
pub(crate) mod delete;
|
||||||
pub(crate) mod edit;
|
pub(crate) mod edit;
|
||||||
@@ -35,6 +37,27 @@ pub(crate) enum SelectedFile {
|
|||||||
None,
|
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)]
|
#[derive(Debug)]
|
||||||
enum SelectedFileIndex {
|
enum SelectedFileIndex {
|
||||||
One(usize),
|
One(usize),
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ mod transfer;
|
|||||||
|
|
||||||
pub use misc::FooterBar;
|
pub use misc::FooterBar;
|
||||||
pub use popups::{
|
pub use popups::{
|
||||||
CopyPopup, DeletePopup, DisconnectPopup, ErrorPopup, ExecPopup, FatalPopup, FileInfoPopup,
|
ChmodPopup, CopyPopup, DeletePopup, DisconnectPopup, ErrorPopup, ExecPopup, FatalPopup,
|
||||||
FindPopup, GoToPopup, KeybindingsPopup, MkdirPopup, NewfilePopup, OpenWithPopup,
|
FileInfoPopup, FindPopup, GoToPopup, KeybindingsPopup, MkdirPopup, NewfilePopup, OpenWithPopup,
|
||||||
ProgressBarFull, ProgressBarPartial, QuitPopup, RenamePopup, ReplacePopup,
|
ProgressBarFull, ProgressBarPartial, QuitPopup, RenamePopup, ReplacePopup,
|
||||||
ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote,
|
ReplacingFilesListPopup, SaveAsPopup, SortingPopup, StatusBarLocal, StatusBarRemote,
|
||||||
SymlinkPopup, SyncBrowsingMkdirPopup, WaitPopup, WatchedPathsList, WatcherPopup,
|
SymlinkPopup, SyncBrowsingMkdirPopup, WaitPopup, WatchedPathsList, WatcherPopup,
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ use super::{Msg, PendingActionMsg, TransferMsg, UiMsg};
|
|||||||
use crate::explorer::FileSorting;
|
use crate::explorer::FileSorting;
|
||||||
use crate::utils::fmt::fmt_time;
|
use crate::utils::fmt::fmt_time;
|
||||||
|
|
||||||
|
mod chmod;
|
||||||
|
|
||||||
|
pub use chmod::ChmodPopup;
|
||||||
|
|
||||||
#[derive(MockComponent)]
|
#[derive(MockComponent)]
|
||||||
pub struct CopyPopup {
|
pub struct CopyPopup {
|
||||||
component: Input,
|
component: Input,
|
||||||
@@ -746,6 +750,8 @@ impl KeybindingsPopup {
|
|||||||
.add_col(TextSpan::from(
|
.add_col(TextSpan::from(
|
||||||
" Open text file with preferred editor",
|
" 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_row()
|
||||||
.add_col(TextSpan::new("<Q|F10>").bold().fg(key_color))
|
.add_col(TextSpan::new("<Q|F10>").bold().fg(key_color))
|
||||||
.add_col(TextSpan::from(" Quit termscp"))
|
.add_col(TextSpan::from(" Quit termscp"))
|
||||||
@@ -779,6 +785,8 @@ impl KeybindingsPopup {
|
|||||||
.add_col(TextSpan::from(
|
.add_col(TextSpan::from(
|
||||||
" Toggle synchronized browsing",
|
" Toggle synchronized browsing",
|
||||||
))
|
))
|
||||||
|
.add_col(TextSpan::new("<Z>").bold().fg(key_color))
|
||||||
|
.add_col(TextSpan::from(" Change file permissions"))
|
||||||
.add_row()
|
.add_row()
|
||||||
.add_col(TextSpan::new("<DEL|F8|E>").bold().fg(key_color))
|
.add_col(TextSpan::new("<DEL|F8|E>").bold().fg(key_color))
|
||||||
.add_col(TextSpan::from(" Delete selected file"))
|
.add_col(TextSpan::from(" Delete selected 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'),
|
code: Key::Char('w'),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
}) => Some(Msg::Ui(UiMsg::ShowOpenWithPopup)),
|
}) => Some(Msg::Ui(UiMsg::ShowOpenWithPopup)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('z'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::ShowChmodPopup)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,6 +312,10 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
|||||||
code: Key::Char('w'),
|
code: Key::Char('w'),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
}) => Some(Msg::Ui(UiMsg::ShowOpenWithPopup)),
|
}) => Some(Msg::Ui(UiMsg::ShowOpenWithPopup)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('z'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::ShowChmodPopup)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,6 +497,10 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
|||||||
code: Key::Char('w'),
|
code: Key::Char('w'),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
}) => Some(Msg::Ui(UiMsg::ShowOpenWithPopup)),
|
}) => Some(Msg::Ui(UiMsg::ShowOpenWithPopup)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('z'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::ShowChmodPopup)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ use crate::system::watcher::FsWatcher;
|
|||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
enum Id {
|
enum Id {
|
||||||
|
ChmodPopup,
|
||||||
CopyPopup,
|
CopyPopup,
|
||||||
DeletePopup,
|
DeletePopup,
|
||||||
DisconnectPopup,
|
DisconnectPopup,
|
||||||
@@ -93,6 +94,7 @@ enum PendingActionMsg {
|
|||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum TransferMsg {
|
enum TransferMsg {
|
||||||
AbortTransfer,
|
AbortTransfer,
|
||||||
|
Chmod(remotefs::fs::UnixPex),
|
||||||
CopyFileTo(String),
|
CopyFileTo(String),
|
||||||
CreateSymlink(String),
|
CreateSymlink(String),
|
||||||
DeleteFile,
|
DeleteFile,
|
||||||
@@ -119,6 +121,7 @@ enum TransferMsg {
|
|||||||
enum UiMsg {
|
enum UiMsg {
|
||||||
ChangeFileSorting(FileSorting),
|
ChangeFileSorting(FileSorting),
|
||||||
ChangeTransferWindow,
|
ChangeTransferWindow,
|
||||||
|
CloseChmodPopup,
|
||||||
CloseCopyPopup,
|
CloseCopyPopup,
|
||||||
CloseDeletePopup,
|
CloseDeletePopup,
|
||||||
CloseDisconnectPopup,
|
CloseDisconnectPopup,
|
||||||
@@ -144,6 +147,7 @@ enum UiMsg {
|
|||||||
LogBackTabbed,
|
LogBackTabbed,
|
||||||
Quit,
|
Quit,
|
||||||
ReplacePopupTabbed,
|
ReplacePopupTabbed,
|
||||||
|
ShowChmodPopup,
|
||||||
ShowCopyPopup,
|
ShowCopyPopup,
|
||||||
ShowDeletePopup,
|
ShowDeletePopup,
|
||||||
ShowDisconnectPopup,
|
ShowDisconnectPopup,
|
||||||
|
|||||||
@@ -32,6 +32,22 @@ impl FileTransferActivity {
|
|||||||
TransferMsg::AbortTransfer => {
|
TransferMsg::AbortTransfer => {
|
||||||
self.transfer.abort();
|
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) => {
|
TransferMsg::CopyFileTo(dest) => {
|
||||||
self.umount_copy();
|
self.umount_copy();
|
||||||
self.mount_blocking_wait("Copying file(s)…");
|
self.mount_blocking_wait("Copying file(s)…");
|
||||||
@@ -336,6 +352,7 @@ impl FileTransferActivity {
|
|||||||
|
|
||||||
fn update_ui(&mut self, msg: UiMsg) -> Option<Msg> {
|
fn update_ui(&mut self, msg: UiMsg) -> Option<Msg> {
|
||||||
match msg {
|
match msg {
|
||||||
|
UiMsg::CloseChmodPopup => self.umount_chmod(),
|
||||||
UiMsg::ChangeFileSorting(sorting) => {
|
UiMsg::ChangeFileSorting(sorting) => {
|
||||||
match self.browser.tab() {
|
match self.browser.tab() {
|
||||||
FileExplorerTab::Local | FileExplorerTab::FindLocal => {
|
FileExplorerTab::Local | FileExplorerTab::FindLocal => {
|
||||||
@@ -422,6 +439,32 @@ impl FileTransferActivity {
|
|||||||
assert!(self.app.active(&Id::ReplacePopup).is_ok());
|
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::ShowCopyPopup => self.mount_copy(),
|
||||||
UiMsg::ShowDeletePopup => self.mount_radio_delete(),
|
UiMsg::ShowDeletePopup => self.mount_radio_delete(),
|
||||||
UiMsg::ShowDisconnectPopup => self.mount_disconnect(),
|
UiMsg::ShowDisconnectPopup => self.mount_disconnect(),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
// locals
|
// locals
|
||||||
// Ext
|
// Ext
|
||||||
use remotefs::fs::File;
|
use remotefs::fs::{File, UnixPex};
|
||||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||||
use tuirealm::tui::layout::{Constraint, Direction, Layout};
|
use tuirealm::tui::layout::{Constraint, Direction, Layout};
|
||||||
use tuirealm::tui::widgets::Clear;
|
use tuirealm::tui::widgets::Clear;
|
||||||
@@ -158,6 +158,11 @@ impl FileTransferActivity {
|
|||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
// make popup
|
// make popup
|
||||||
self.app.view(&Id::CopyPopup, f, 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) {
|
} else if self.app.mounted(&Id::FindPopup) {
|
||||||
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
let popup = Popup(Size::Percentage(40), Size::Unit(3)).draw_in(f.size());
|
||||||
f.render_widget(Clear, popup);
|
f.render_widget(Clear, popup);
|
||||||
@@ -435,6 +440,24 @@ impl FileTransferActivity {
|
|||||||
let _ = self.app.umount(&Id::DisconnectPopup);
|
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) {
|
pub(super) fn mount_copy(&mut self) {
|
||||||
let input_color = self.theme().misc_input_dialog;
|
let input_color = self.theme().misc_input_dialog;
|
||||||
assert!(self
|
assert!(self
|
||||||
@@ -1068,9 +1091,14 @@ impl FileTransferActivity {
|
|||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
||||||
Id::WatchedPathsList,
|
Id::WatchedPathsList,
|
||||||
)))),
|
)))),
|
||||||
Box::new(SubClause::Not(Box::new(SubClause::IsMounted(
|
Box::new(SubClause::And(
|
||||||
Id::WaitPopup,
|
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