mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
feat: 132 queuing transfers (#332)
the logic of selecting files has been extended! From now on selecting file will put the files into a transfer queue, which is shown on the bottom panel. When a file is selected the file is added to the queue with a destination path, which is the **current other explorer path at the moment of selection. It is possible to navigate to the transfer queue by using `P` and pressing `ENTER` on a file will remove it from the transfer queue.Other commands will work as well on the transfer queue, like `COPY`, `MOVE`, `DELETE`, `RENAME`. closes #132
This commit is contained in:
committed by
GitHub
parent
368570592f
commit
ec75ae1486
@@ -18,14 +18,15 @@ impl FileTransferActivity {
|
||||
self.local_copy_file(&entry, dest_path.as_path());
|
||||
}
|
||||
SelectedFile::Many(entries) => {
|
||||
// Try to copy each file to Input/{FILE_NAME}
|
||||
let base_path: PathBuf = PathBuf::from(input);
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
let mut dest_path: PathBuf = base_path.clone();
|
||||
for (entry, mut dest_path) in entries.into_iter() {
|
||||
dest_path.push(entry.name());
|
||||
self.local_copy_file(entry, dest_path.as_path());
|
||||
self.local_copy_file(&entry, dest_path.as_path());
|
||||
}
|
||||
|
||||
// clear selection
|
||||
self.host_bridge_mut().clear_queue();
|
||||
self.reload_host_bridge_filelist();
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
}
|
||||
@@ -39,14 +40,15 @@ impl FileTransferActivity {
|
||||
self.remote_copy_file(entry, dest_path.as_path());
|
||||
}
|
||||
SelectedFile::Many(entries) => {
|
||||
// Try to copy each file to Input/{FILE_NAME}
|
||||
let base_path: PathBuf = PathBuf::from(input);
|
||||
// Iter files
|
||||
for entry in entries.into_iter() {
|
||||
let mut dest_path: PathBuf = base_path.clone();
|
||||
for (entry, mut dest_path) in entries.into_iter() {
|
||||
dest_path.push(entry.name());
|
||||
self.remote_copy_file(entry, dest_path.as_path());
|
||||
}
|
||||
|
||||
// clear selection
|
||||
self.remote_mut().clear_queue();
|
||||
self.reload_remote_filelist();
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,14 @@ impl FileTransferActivity {
|
||||
}
|
||||
SelectedFile::Many(entries) => {
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
for (entry, _) in entries.iter() {
|
||||
// Delete file
|
||||
self.local_remove_file(entry);
|
||||
}
|
||||
|
||||
// clear selection
|
||||
self.host_bridge_mut().clear_queue();
|
||||
self.reload_host_bridge_filelist();
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
}
|
||||
@@ -33,10 +37,14 @@ impl FileTransferActivity {
|
||||
}
|
||||
SelectedFile::Many(entries) => {
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
for (entry, _) in entries.iter() {
|
||||
// Delete file
|
||||
self.remote_remove_file(entry);
|
||||
}
|
||||
|
||||
// clear selection
|
||||
self.remote_mut().clear_queue();
|
||||
self.reload_remote_filelist();
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ impl FileTransferActivity {
|
||||
pub(crate) fn action_edit_local_file(&mut self) {
|
||||
let entries: Vec<File> = match self.get_local_selected_entries() {
|
||||
SelectedFile::One(entry) => vec![entry],
|
||||
SelectedFile::Many(entries) => entries,
|
||||
SelectedFile::Many(entries) => entries.into_iter().map(|(f, _)| f).collect(),
|
||||
SelectedFile::None => vec![],
|
||||
};
|
||||
// Edit all entries
|
||||
@@ -38,12 +38,16 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear selection
|
||||
self.host_bridge_mut().clear_queue();
|
||||
self.reload_host_bridge_filelist();
|
||||
}
|
||||
|
||||
pub(crate) fn action_edit_remote_file(&mut self) {
|
||||
let entries: Vec<File> = match self.get_remote_selected_entries() {
|
||||
SelectedFile::One(entry) => vec![entry],
|
||||
SelectedFile::Many(entries) => entries,
|
||||
SelectedFile::Many(entries) => entries.into_iter().map(|(f, _)| f).collect(),
|
||||
SelectedFile::None => vec![],
|
||||
};
|
||||
// Edit all entries
|
||||
@@ -60,6 +64,10 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clear selection
|
||||
self.remote_mut().clear_queue();
|
||||
self.reload_remote_filelist();
|
||||
}
|
||||
|
||||
/// Edit a file on localhost
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::{File, FileTransferActivity, LogLevel, SelectedFile, TransferOpts, Tr
|
||||
impl FileTransferActivity {
|
||||
pub(crate) fn action_find_changedir(&mut self) {
|
||||
// Match entry
|
||||
if let SelectedFile::One(entry) = self.get_found_selected_entries() {
|
||||
if let Some(entry) = self.get_found_selected_file() {
|
||||
debug!("Changedir to: {}", entry.name());
|
||||
// Get path: if a directory, use directory path; if it is a File, get parent path
|
||||
let path = if entry.is_dir() {
|
||||
@@ -103,11 +103,12 @@ impl FileTransferActivity {
|
||||
// Check which file would be replaced
|
||||
let existing_files: Vec<&File> = entries
|
||||
.iter()
|
||||
.filter(|x| {
|
||||
.filter(|(x, dest_path)| {
|
||||
self.remote_file_exists(
|
||||
Self::file_to_check_many(x, dest_path.as_path()).as_path(),
|
||||
)
|
||||
})
|
||||
.map(|(x, _)| x)
|
||||
.collect();
|
||||
// Check whether to replace files
|
||||
if !existing_files.is_empty()
|
||||
@@ -117,7 +118,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::Many(entries),
|
||||
TransferPayload::TransferQueue(entries),
|
||||
dest_path.as_path(),
|
||||
None,
|
||||
) {
|
||||
@@ -134,11 +135,12 @@ impl FileTransferActivity {
|
||||
// Check which file would be replaced
|
||||
let existing_files: Vec<&File> = entries
|
||||
.iter()
|
||||
.filter(|x| {
|
||||
.filter(|(x, dest_path)| {
|
||||
self.host_bridge_file_exists(
|
||||
Self::file_to_check_many(x, dest_path.as_path()).as_path(),
|
||||
)
|
||||
})
|
||||
.map(|(x, _)| x)
|
||||
.collect();
|
||||
// Check whether to replace files
|
||||
if !existing_files.is_empty()
|
||||
@@ -148,7 +150,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
if let Err(err) = self.filetransfer_recv(
|
||||
TransferPayload::Many(entries),
|
||||
TransferPayload::TransferQueue(entries),
|
||||
dest_path.as_path(),
|
||||
None,
|
||||
) {
|
||||
@@ -157,6 +159,12 @@ impl FileTransferActivity {
|
||||
format!("Could not download file: {err}"),
|
||||
);
|
||||
}
|
||||
|
||||
// clear selection
|
||||
if let Some(f) = self.found_mut() {
|
||||
f.clear_queue();
|
||||
self.update_find_list();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,10 +180,16 @@ impl FileTransferActivity {
|
||||
}
|
||||
SelectedFile::Many(entries) => {
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
for (entry, _) in entries.iter() {
|
||||
// Delete file
|
||||
self.remove_found_file(entry);
|
||||
}
|
||||
|
||||
// clear selection
|
||||
if let Some(f) = self.found_mut() {
|
||||
f.clear_queue();
|
||||
self.update_find_list();
|
||||
}
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
}
|
||||
@@ -200,10 +214,15 @@ impl FileTransferActivity {
|
||||
}
|
||||
SelectedFile::Many(entries) => {
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
for (entry, _) in entries.iter() {
|
||||
// Open file
|
||||
self.open_found_file(entry, None);
|
||||
}
|
||||
// clear selection
|
||||
if let Some(f) = self.found_mut() {
|
||||
f.clear_queue();
|
||||
self.update_find_list();
|
||||
}
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
}
|
||||
@@ -217,10 +236,15 @@ impl FileTransferActivity {
|
||||
}
|
||||
SelectedFile::Many(entries) => {
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
for (entry, _) in entries.iter() {
|
||||
// Open file
|
||||
self.open_found_file(entry, Some(with));
|
||||
}
|
||||
// clear selection
|
||||
if let Some(f) = self.found_mut() {
|
||||
f.clear_queue();
|
||||
self.update_find_list();
|
||||
}
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
}
|
||||
|
||||
19
src/ui/activities/filetransfer/actions/mark.rs
Normal file
19
src/ui/activities/filetransfer/actions/mark.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
//! ## FileTransferActivity
|
||||
//!
|
||||
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
|
||||
|
||||
use super::FileTransferActivity;
|
||||
|
||||
impl FileTransferActivity {
|
||||
pub(crate) fn action_mark_file(&mut self, index: usize) {
|
||||
self.enqueue_file(index);
|
||||
}
|
||||
|
||||
pub(crate) fn action_mark_all(&mut self) {
|
||||
self.enqueue_all();
|
||||
}
|
||||
|
||||
pub(crate) fn action_mark_clear(&mut self) {
|
||||
self.clear_queue();
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,19 @@
|
||||
//!
|
||||
//! `filetransfer_activiy` is the module which implements the Filetransfer activity, which is the main activity afterall
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use remotefs::File;
|
||||
use remotefs::fs::UnixPex;
|
||||
use tuirealm::{State, StateValue};
|
||||
|
||||
use super::browser::FileExplorerTab;
|
||||
use super::lib::browser::FoundExplorerTab;
|
||||
use super::{
|
||||
FileTransferActivity, Id, LogLevel, Msg, PendingActionMsg, TransferMsg, TransferOpts,
|
||||
TransferPayload, UiMsg,
|
||||
};
|
||||
use crate::explorer::FileExplorer;
|
||||
|
||||
// actions
|
||||
pub(crate) mod change_dir;
|
||||
@@ -21,6 +25,7 @@ pub(crate) mod edit;
|
||||
pub(crate) mod exec;
|
||||
pub(crate) mod filter;
|
||||
pub(crate) mod find;
|
||||
pub(crate) mod mark;
|
||||
pub(crate) mod mkdir;
|
||||
pub(crate) mod newfile;
|
||||
pub(crate) mod open;
|
||||
@@ -36,7 +41,8 @@ pub(crate) mod watcher;
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum SelectedFile {
|
||||
One(File),
|
||||
Many(Vec<File>),
|
||||
/// List of file with their destination path
|
||||
Many(Vec<(File, PathBuf)>),
|
||||
None,
|
||||
}
|
||||
|
||||
@@ -45,7 +51,10 @@ impl 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::Many(files) => files
|
||||
.iter()
|
||||
.next()
|
||||
.and_then(|(file, _)| file.metadata().mode),
|
||||
Self::One(file) => file.metadata().mode,
|
||||
Self::None => None,
|
||||
}
|
||||
@@ -55,7 +64,7 @@ impl SelectedFile {
|
||||
pub fn get_files(self) -> Vec<File> {
|
||||
match self {
|
||||
Self::One(file) => vec![file],
|
||||
Self::Many(files) => files,
|
||||
Self::Many(files) => files.into_iter().map(|(f, _)| f).collect(),
|
||||
Self::None => vec![],
|
||||
}
|
||||
}
|
||||
@@ -64,7 +73,6 @@ impl SelectedFile {
|
||||
#[derive(Debug)]
|
||||
enum SelectedFileIndex {
|
||||
One(usize),
|
||||
Many(Vec<usize>),
|
||||
None,
|
||||
}
|
||||
|
||||
@@ -77,68 +85,42 @@ impl From<Option<&File>> for SelectedFile {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<&File>> for SelectedFile {
|
||||
fn from(files: Vec<&File>) -> Self {
|
||||
SelectedFile::Many(files.into_iter().cloned().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl FileTransferActivity {
|
||||
/// Get local file entry
|
||||
pub(crate) fn get_local_selected_entries(&self) -> SelectedFile {
|
||||
match self.get_selected_index(&Id::ExplorerHostBridge) {
|
||||
SelectedFileIndex::One(idx) => SelectedFile::from(self.host_bridge().get(idx)),
|
||||
SelectedFileIndex::Many(files) => {
|
||||
let files: Vec<&File> = files
|
||||
.iter()
|
||||
.filter_map(|x| self.host_bridge().get(*x)) // Usize to Option<File>
|
||||
.collect();
|
||||
SelectedFile::from(files)
|
||||
}
|
||||
SelectedFileIndex::None => SelectedFile::None,
|
||||
}
|
||||
pub(crate) fn get_local_selected_entries(&mut self) -> SelectedFile {
|
||||
self.get_selected_files(&Id::ExplorerHostBridge)
|
||||
}
|
||||
|
||||
pub(crate) fn get_local_selected_file(&self) -> Option<File> {
|
||||
self.get_selected_file(&Id::ExplorerHostBridge)
|
||||
}
|
||||
|
||||
/// Get remote file entry
|
||||
pub(crate) fn get_remote_selected_entries(&self) -> SelectedFile {
|
||||
match self.get_selected_index(&Id::ExplorerRemote) {
|
||||
SelectedFileIndex::One(idx) => SelectedFile::from(self.remote().get(idx)),
|
||||
SelectedFileIndex::Many(files) => {
|
||||
let files: Vec<&File> = files
|
||||
.iter()
|
||||
.filter_map(|x| self.remote().get(*x)) // Usize to Option<File>
|
||||
.collect();
|
||||
SelectedFile::from(files)
|
||||
}
|
||||
SelectedFileIndex::None => SelectedFile::None,
|
||||
}
|
||||
pub(crate) fn get_remote_selected_entries(&mut self) -> SelectedFile {
|
||||
self.get_selected_files(&Id::ExplorerRemote)
|
||||
}
|
||||
|
||||
pub(crate) fn get_remote_selected_file(&self) -> Option<File> {
|
||||
self.get_selected_file(&Id::ExplorerRemote)
|
||||
}
|
||||
|
||||
/// Returns whether only one entry is selected on local host
|
||||
pub(crate) fn is_local_selected_one(&self) -> bool {
|
||||
pub(crate) fn is_local_selected_one(&mut self) -> bool {
|
||||
matches!(self.get_local_selected_entries(), SelectedFile::One(_))
|
||||
}
|
||||
|
||||
/// Returns whether only one entry is selected on remote host
|
||||
pub(crate) fn is_remote_selected_one(&self) -> bool {
|
||||
pub(crate) fn is_remote_selected_one(&mut self) -> bool {
|
||||
matches!(self.get_remote_selected_entries(), SelectedFile::One(_))
|
||||
}
|
||||
|
||||
/// Get remote file entry
|
||||
pub(crate) fn get_found_selected_entries(&self) -> SelectedFile {
|
||||
match self.get_selected_index(&Id::ExplorerFind) {
|
||||
SelectedFileIndex::One(idx) => {
|
||||
SelectedFile::from(self.found().as_ref().unwrap().get(idx))
|
||||
}
|
||||
SelectedFileIndex::Many(files) => {
|
||||
let files: Vec<&File> = files
|
||||
.iter()
|
||||
.filter_map(|x| self.found().as_ref().unwrap().get(*x)) // Usize to Option<File>
|
||||
.collect();
|
||||
SelectedFile::from(files)
|
||||
}
|
||||
SelectedFileIndex::None => SelectedFile::None,
|
||||
}
|
||||
pub(crate) fn get_found_selected_entries(&mut self) -> SelectedFile {
|
||||
self.get_selected_files(&Id::ExplorerFind)
|
||||
}
|
||||
|
||||
pub(crate) fn get_found_selected_file(&self) -> Option<File> {
|
||||
self.get_selected_file(&Id::ExplorerFind)
|
||||
}
|
||||
|
||||
// -- private
|
||||
@@ -146,17 +128,69 @@ impl FileTransferActivity {
|
||||
fn get_selected_index(&self, id: &Id) -> SelectedFileIndex {
|
||||
match self.app.state(id) {
|
||||
Ok(State::One(StateValue::Usize(idx))) => SelectedFileIndex::One(idx),
|
||||
Ok(State::Vec(files)) => {
|
||||
let list: Vec<usize> = files
|
||||
.iter()
|
||||
.map(|x| match x {
|
||||
StateValue::Usize(v) => *v,
|
||||
_ => 0,
|
||||
})
|
||||
.collect();
|
||||
SelectedFileIndex::Many(list)
|
||||
}
|
||||
_ => SelectedFileIndex::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_selected_files(&mut self, id: &Id) -> SelectedFile {
|
||||
let browser = self.browser_by_id(id);
|
||||
// if transfer queue is not empty, return that
|
||||
let transfer_queue = browser.enqueued().clone();
|
||||
if !transfer_queue.is_empty() {
|
||||
return SelectedFile::Many(
|
||||
transfer_queue
|
||||
.iter()
|
||||
.filter_map(|(src, dest)| {
|
||||
let src_file = self.get_file_from_path(id, src)?;
|
||||
Some((src_file, dest.clone()))
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
let browser = self.browser_by_id(id);
|
||||
// if no transfer queue, return selected files
|
||||
match self.get_selected_index(id) {
|
||||
SelectedFileIndex::One(idx) => {
|
||||
let Some(f) = browser.get(idx) else {
|
||||
return SelectedFile::None;
|
||||
};
|
||||
SelectedFile::One(f.clone())
|
||||
}
|
||||
SelectedFileIndex::None => SelectedFile::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_file_from_path(&mut self, id: &Id, path: &Path) -> Option<File> {
|
||||
match *id {
|
||||
Id::ExplorerHostBridge => self.host_bridge.stat(path).ok(),
|
||||
Id::ExplorerRemote => self.client.stat(path).ok(),
|
||||
Id::ExplorerFind => {
|
||||
let found = self.browser.found_tab().unwrap();
|
||||
match found {
|
||||
FoundExplorerTab::Local => self.host_bridge.stat(path).ok(),
|
||||
FoundExplorerTab::Remote => self.client.stat(path).ok(),
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn browser_by_id(&self, id: &Id) -> &FileExplorer {
|
||||
match *id {
|
||||
Id::ExplorerHostBridge => self.host_bridge(),
|
||||
Id::ExplorerRemote => self.remote(),
|
||||
Id::ExplorerFind => self.found().as_ref().unwrap(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_selected_file(&self, id: &Id) -> Option<File> {
|
||||
let browser = self.browser_by_id(id);
|
||||
// if no transfer queue, return selected files
|
||||
match self.get_selected_index(id) {
|
||||
SelectedFileIndex::One(idx) => browser.get(idx).cloned(),
|
||||
SelectedFileIndex::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,24 +13,32 @@ impl FileTransferActivity {
|
||||
pub(crate) fn action_open_local(&mut self) {
|
||||
let entries: Vec<File> = match self.get_local_selected_entries() {
|
||||
SelectedFile::One(entry) => vec![entry],
|
||||
SelectedFile::Many(entries) => entries,
|
||||
SelectedFile::Many(entries) => entries.into_iter().map(|(f, _)| f).collect(),
|
||||
SelectedFile::None => vec![],
|
||||
};
|
||||
entries
|
||||
.iter()
|
||||
.for_each(|x| self.action_open_local_file(x, None));
|
||||
|
||||
// clear selection
|
||||
self.host_bridge_mut().clear_queue();
|
||||
self.reload_host_bridge_filelist();
|
||||
}
|
||||
|
||||
/// Open local file
|
||||
pub(crate) fn action_open_remote(&mut self) {
|
||||
let entries: Vec<File> = match self.get_remote_selected_entries() {
|
||||
SelectedFile::One(entry) => vec![entry],
|
||||
SelectedFile::Many(entries) => entries,
|
||||
SelectedFile::Many(entries) => entries.into_iter().map(|(f, _)| f).collect(),
|
||||
SelectedFile::None => vec![],
|
||||
};
|
||||
entries
|
||||
.iter()
|
||||
.for_each(|x| self.action_open_remote_file(x, None));
|
||||
|
||||
// clear selection
|
||||
self.remote_mut().clear_queue();
|
||||
self.reload_remote_filelist();
|
||||
}
|
||||
|
||||
/// Perform open lopcal file
|
||||
@@ -86,26 +94,33 @@ impl FileTransferActivity {
|
||||
pub(crate) fn action_local_open_with(&mut self, with: &str) {
|
||||
let entries: Vec<File> = match self.get_local_selected_entries() {
|
||||
SelectedFile::One(entry) => vec![entry],
|
||||
SelectedFile::Many(entries) => entries,
|
||||
SelectedFile::Many(entries) => entries.into_iter().map(|(f, _)| f).collect(),
|
||||
SelectedFile::None => vec![],
|
||||
};
|
||||
// Open all entries
|
||||
entries
|
||||
.iter()
|
||||
.for_each(|x| self.action_open_local_file(x, Some(with)));
|
||||
|
||||
// clear selection
|
||||
self.host_bridge_mut().clear_queue();
|
||||
}
|
||||
|
||||
/// Open selected file with provided application
|
||||
pub(crate) fn action_remote_open_with(&mut self, with: &str) {
|
||||
let entries: Vec<File> = match self.get_remote_selected_entries() {
|
||||
SelectedFile::One(entry) => vec![entry],
|
||||
SelectedFile::Many(entries) => entries,
|
||||
SelectedFile::Many(entries) => entries.into_iter().map(|(f, _)| f).collect(),
|
||||
SelectedFile::None => vec![],
|
||||
};
|
||||
// Open all entries
|
||||
entries
|
||||
.iter()
|
||||
.for_each(|x| self.action_open_remote_file(x, Some(with)));
|
||||
|
||||
// clear selection
|
||||
self.remote_mut().clear_queue();
|
||||
self.reload_remote_filelist();
|
||||
}
|
||||
|
||||
fn open_bridged_file(&mut self, entry: &File, open_with: Option<&str>) {
|
||||
|
||||
@@ -18,13 +18,15 @@ impl FileTransferActivity {
|
||||
}
|
||||
SelectedFile::Many(entries) => {
|
||||
// Try to copy each file to Input/{FILE_NAME}
|
||||
let base_path: PathBuf = PathBuf::from(input);
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
let mut dest_path: PathBuf = base_path.clone();
|
||||
for (entry, mut dest_path) in entries.into_iter() {
|
||||
dest_path.push(entry.name());
|
||||
self.local_rename_file(entry, dest_path.as_path());
|
||||
self.local_rename_file(&entry, dest_path.as_path());
|
||||
}
|
||||
|
||||
// clear selection
|
||||
self.host_bridge_mut().clear_queue();
|
||||
self.reload_host_bridge_filelist();
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
}
|
||||
@@ -38,13 +40,16 @@ impl FileTransferActivity {
|
||||
}
|
||||
SelectedFile::Many(entries) => {
|
||||
// Try to copy each file to Input/{FILE_NAME}
|
||||
let base_path: PathBuf = PathBuf::from(input);
|
||||
// Iter files
|
||||
for entry in entries.iter() {
|
||||
let mut dest_path: PathBuf = base_path.clone();
|
||||
for (entry, mut dest_path) in entries.into_iter() {
|
||||
dest_path.push(entry.name());
|
||||
self.remote_rename_file(entry, dest_path.as_path());
|
||||
self.remote_rename_file(&entry, dest_path.as_path());
|
||||
}
|
||||
|
||||
// clear selection
|
||||
self.remote_mut().clear_queue();
|
||||
// reload remote
|
||||
self.reload_remote_filelist();
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
}
|
||||
|
||||
@@ -64,11 +64,12 @@ impl FileTransferActivity {
|
||||
// Check which file would be replaced
|
||||
let existing_files: Vec<&File> = entries
|
||||
.iter()
|
||||
.filter(|x| {
|
||||
.filter(|(x, dest_path)| {
|
||||
self.remote_file_exists(
|
||||
Self::file_to_check_many(x, dest_path.as_path()).as_path(),
|
||||
)
|
||||
})
|
||||
.map(|(x, _)| x)
|
||||
.collect();
|
||||
// Check whether to replace files
|
||||
if !existing_files.is_empty() && !self.should_replace_files(existing_files) {
|
||||
@@ -76,7 +77,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
if let Err(err) = self.filetransfer_send(
|
||||
TransferPayload::Many(entries),
|
||||
TransferPayload::TransferQueue(entries),
|
||||
dest_path.as_path(),
|
||||
None,
|
||||
) {
|
||||
@@ -86,6 +87,10 @@ impl FileTransferActivity {
|
||||
format!("Could not upload file: {err}"),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// clear selection
|
||||
self.host_bridge_mut().clear_queue();
|
||||
self.reload_host_bridge_filelist();
|
||||
}
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
@@ -128,11 +133,12 @@ impl FileTransferActivity {
|
||||
// Check which file would be replaced
|
||||
let existing_files: Vec<&File> = entries
|
||||
.iter()
|
||||
.filter(|x| {
|
||||
.filter(|(x, dest_path)| {
|
||||
self.host_bridge_file_exists(
|
||||
Self::file_to_check_many(x, dest_path.as_path()).as_path(),
|
||||
)
|
||||
})
|
||||
.map(|(x, _)| x)
|
||||
.collect();
|
||||
// Check whether to replace files
|
||||
if !existing_files.is_empty() && !self.should_replace_files(existing_files) {
|
||||
@@ -140,7 +146,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
if let Err(err) = self.filetransfer_recv(
|
||||
TransferPayload::Many(entries),
|
||||
TransferPayload::TransferQueue(entries),
|
||||
dest_path.as_path(),
|
||||
None,
|
||||
) {
|
||||
@@ -150,6 +156,11 @@ impl FileTransferActivity {
|
||||
format!("Could not download file: {err}"),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// clear selection
|
||||
self.remote_mut().clear_queue();
|
||||
// reload remote
|
||||
self.reload_remote_filelist();
|
||||
}
|
||||
}
|
||||
SelectedFile::None => {}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
// locals
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::{FileTransferActivity, LogLevel, SelectedFile};
|
||||
use super::{FileTransferActivity, LogLevel};
|
||||
|
||||
impl FileTransferActivity {
|
||||
/// Create symlink on localhost
|
||||
pub(crate) fn action_local_symlink(&mut self, name: String) {
|
||||
if let SelectedFile::One(entry) = self.get_local_selected_entries() {
|
||||
if let Some(entry) = self.get_local_selected_file() {
|
||||
match self
|
||||
.host_bridge
|
||||
.symlink(PathBuf::from(name.as_str()).as_path(), entry.path())
|
||||
@@ -34,7 +34,7 @@ impl FileTransferActivity {
|
||||
|
||||
/// Copy file on remote
|
||||
pub(crate) fn action_remote_symlink(&mut self, name: String) {
|
||||
if let SelectedFile::One(entry) = self.get_remote_selected_entries() {
|
||||
if let Some(entry) = self.get_remote_selected_file() {
|
||||
match self
|
||||
.client
|
||||
.symlink(PathBuf::from(name.as_str()).as_path(), entry.path())
|
||||
|
||||
@@ -166,6 +166,12 @@ impl Component<Msg, NoUserEvent> for Log {
|
||||
self.perform(Cmd::Move(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => Some(Msg::Ui(UiMsg::BottomPanelRight)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => Some(Msg::Ui(UiMsg::BottomPanelLeft)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageUp, ..
|
||||
}) => {
|
||||
|
||||
@@ -12,6 +12,7 @@ use super::{Msg, PendingActionMsg, TransferMsg, UiMsg};
|
||||
mod log;
|
||||
mod misc;
|
||||
mod popups;
|
||||
mod selected_files;
|
||||
mod transfer;
|
||||
|
||||
pub use misc::FooterBar;
|
||||
@@ -26,6 +27,7 @@ pub use popups::{
|
||||
pub use transfer::{ExplorerFind, ExplorerFuzzy, ExplorerLocal, ExplorerRemote};
|
||||
|
||||
pub use self::log::Log;
|
||||
pub use self::selected_files::SelectedFilesList;
|
||||
|
||||
#[derive(Default, MockComponent)]
|
||||
pub struct GlobalListener {
|
||||
|
||||
@@ -670,7 +670,7 @@ impl KeybindingsPopup {
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<P>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Toggle log panel"))
|
||||
.add_col(TextSpan::from(" Toggle bottom panel"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<Q|F10>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Quit termscp"))
|
||||
|
||||
126
src/ui/activities/filetransfer/components/selected_files.rs
Normal file
126
src/ui/activities/filetransfer/components/selected_files.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use tui_realm_stdlib::List;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{MarkQueue, Msg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct SelectedFilesList {
|
||||
component: List,
|
||||
paths: Vec<PathBuf>,
|
||||
queue: MarkQueue,
|
||||
}
|
||||
|
||||
impl SelectedFilesList {
|
||||
pub fn new(
|
||||
paths: &[(PathBuf, PathBuf)],
|
||||
queue: MarkQueue,
|
||||
color: Color,
|
||||
title: &'static str,
|
||||
) -> Self {
|
||||
let enqueued_paths = paths
|
||||
.iter()
|
||||
.map(|(src, _)| src.clone())
|
||||
.collect::<Vec<PathBuf>>();
|
||||
|
||||
Self {
|
||||
queue,
|
||||
paths: enqueued_paths,
|
||||
component: List::default()
|
||||
.borders(Borders::default().color(color).modifiers(BorderType::Plain))
|
||||
.rewind(true)
|
||||
.scroll(true)
|
||||
.step(4)
|
||||
.highlighted_color(color)
|
||||
.highlighted_str("➤ ")
|
||||
.title(title, Alignment::Left)
|
||||
.rows(
|
||||
paths
|
||||
.iter()
|
||||
.map(|(src, dest)| {
|
||||
vec![
|
||||
TextSpan::from(Self::filename(src)),
|
||||
TextSpan::from(" -> "),
|
||||
TextSpan::from(Self::filename(dest)),
|
||||
]
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn filename(p: &Path) -> String {
|
||||
p.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SelectedFilesList {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||
self.perform(Cmd::Move(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageDown,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageUp, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => Some(Msg::Ui(UiMsg::BottomPanelRight)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => Some(Msg::Ui(UiMsg::BottomPanelLeft)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::BackTab | Key::Tab | Key::Char('p'),
|
||||
..
|
||||
}) => Some(Msg::Ui(UiMsg::LogBackTabbed)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter | Key::Delete,
|
||||
..
|
||||
}) => {
|
||||
// unmark the selected file
|
||||
let State::One(StateValue::Usize(idx)) = self.state() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let path = self.paths.get(idx)?;
|
||||
|
||||
Some(Msg::Ui(UiMsg::MarkRemove(self.queue, path.clone())))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,20 +17,17 @@ const PROP_DOT_DOT: &str = "dot_dot";
|
||||
/// OwnStates contains states for this component
|
||||
#[derive(Clone, Default)]
|
||||
struct OwnStates {
|
||||
list_index: usize, // Index of selected element in list
|
||||
selected: Vec<usize>, // Selected files
|
||||
list_index: usize, // Index of selected element in list
|
||||
list_len: usize, // Length of the list
|
||||
dot_dot: bool,
|
||||
}
|
||||
|
||||
impl OwnStates {
|
||||
/// Initialize list states
|
||||
pub fn init_list_states(&mut self, len: usize, has_dot_dot: bool) {
|
||||
self.selected = Vec::with_capacity(len + if has_dot_dot { 1 } else { 0 });
|
||||
self.list_len = len + if has_dot_dot { 1 } else { 0 };
|
||||
self.fix_list_index();
|
||||
}
|
||||
|
||||
/// Return current value for list index
|
||||
pub fn list_index(&self) -> usize {
|
||||
self.list_index
|
||||
self.dot_dot = has_dot_dot;
|
||||
}
|
||||
|
||||
/// Incremenet list index.
|
||||
@@ -44,6 +41,14 @@ impl OwnStates {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn real_index(&self) -> usize {
|
||||
if self.dot_dot {
|
||||
self.list_index.saturating_sub(1)
|
||||
} else {
|
||||
self.list_index
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrement list index
|
||||
/// If `can_rewind` is `true` the index rewinds when boundary is reached
|
||||
pub fn decr_list_index(&mut self, can_rewind: bool) {
|
||||
@@ -68,22 +73,7 @@ impl OwnStates {
|
||||
|
||||
/// Returns the length of the file list, which is actually the capacity of the selection vector
|
||||
pub fn list_len(&self) -> usize {
|
||||
self.selected.capacity()
|
||||
}
|
||||
|
||||
/// Returns whether the file with index `entry` is selected
|
||||
pub fn is_selected(&self, entry: usize) -> bool {
|
||||
self.selected.contains(&entry)
|
||||
}
|
||||
|
||||
/// Returns whether the selection is currently empty
|
||||
pub fn is_selection_empty(&self) -> bool {
|
||||
self.selected.is_empty()
|
||||
}
|
||||
|
||||
/// Returns current file selection
|
||||
pub fn get_selection(&self) -> Vec<usize> {
|
||||
self.selected.clone()
|
||||
self.list_len
|
||||
}
|
||||
|
||||
/// Keep index if possible, otherwise set to lenght - 1
|
||||
@@ -94,44 +84,6 @@ impl OwnStates {
|
||||
self.list_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// -- select manipulation
|
||||
|
||||
/// Select or deselect file with provided entry index
|
||||
pub fn toggle_file(&mut self, entry: usize) {
|
||||
match self.is_selected(entry) {
|
||||
true => self.deselect(entry),
|
||||
false => self.select(entry),
|
||||
}
|
||||
// increment index
|
||||
self.incr_list_index(false);
|
||||
}
|
||||
|
||||
/// Select all files
|
||||
pub fn select_all(&mut self, has_dot_dot: bool) {
|
||||
for i in 0..self.list_len() {
|
||||
self.select(i + if has_dot_dot { 1 } else { 0 });
|
||||
}
|
||||
}
|
||||
|
||||
/// Select all files
|
||||
pub fn deselect_all(&mut self) {
|
||||
self.selected.clear();
|
||||
}
|
||||
|
||||
/// Select provided index if not selected yet
|
||||
fn select(&mut self, entry: usize) {
|
||||
if !self.is_selected(entry) {
|
||||
self.selected.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove element file with associated index
|
||||
fn deselect(&mut self, entry: usize) {
|
||||
if self.is_selected(entry) {
|
||||
self.selected.retain(|&x| x != entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -222,27 +174,12 @@ impl MockComponent for FileList {
|
||||
Some(table) => init_table_iter
|
||||
.iter()
|
||||
.chain(table.iter())
|
||||
.enumerate()
|
||||
.map(|(num, row)| {
|
||||
let real_num = num;
|
||||
let num = if self.has_dot_dot() {
|
||||
num.checked_sub(1).unwrap_or_default()
|
||||
} else {
|
||||
num
|
||||
};
|
||||
|
||||
.map(|row| {
|
||||
let columns: Vec<Span> = row
|
||||
.iter()
|
||||
.map(|col| {
|
||||
let (fg, bg, mut modifiers) =
|
||||
let (fg, bg, modifiers) =
|
||||
tui_realm_stdlib::utils::use_or_default_styles(&self.props, col);
|
||||
if !(self.has_dot_dot() && real_num == 0)
|
||||
&& self.states.is_selected(num)
|
||||
{
|
||||
modifiers |= TextModifiers::REVERSED
|
||||
| TextModifiers::UNDERLINED
|
||||
| TextModifiers::ITALIC;
|
||||
}
|
||||
|
||||
Span::styled(
|
||||
col.content.clone(),
|
||||
@@ -302,20 +239,11 @@ impl MockComponent for FileList {
|
||||
return State::One(StateValue::String("..".to_string()));
|
||||
}
|
||||
|
||||
match self.states.is_selection_empty() {
|
||||
true => State::One(StateValue::Usize(if self.has_dot_dot() {
|
||||
self.states.list_index.checked_sub(1).unwrap_or_default()
|
||||
} else {
|
||||
self.states.list_index
|
||||
})),
|
||||
false => State::Vec(
|
||||
self.states
|
||||
.get_selection()
|
||||
.into_iter()
|
||||
.map(StateValue::Usize)
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
State::One(StateValue::Usize(if self.has_dot_dot() {
|
||||
self.states.list_index.checked_sub(1).unwrap_or_default()
|
||||
} else {
|
||||
self.states.list_index
|
||||
}))
|
||||
}
|
||||
|
||||
fn perform(&mut self, cmd: Cmd) -> CmdResult {
|
||||
@@ -374,25 +302,18 @@ impl MockComponent for FileList {
|
||||
CmdResult::None
|
||||
}
|
||||
}
|
||||
Cmd::Custom(FILE_LIST_CMD_SELECT_ALL) => {
|
||||
self.states.select_all(self.has_dot_dot());
|
||||
CmdResult::None
|
||||
}
|
||||
Cmd::Custom(FILE_LIST_CMD_DESELECT_ALL) => {
|
||||
self.states.deselect_all();
|
||||
CmdResult::None
|
||||
}
|
||||
Cmd::Toggle => {
|
||||
if self.has_dot_dot() && self.states.list_index() == 0 {
|
||||
if self.states.list_index == 0 && self.has_dot_dot() {
|
||||
return CmdResult::None;
|
||||
}
|
||||
|
||||
self.states.toggle_file(if self.has_dot_dot() {
|
||||
self.states.list_index().checked_sub(1).unwrap_or_default()
|
||||
} else {
|
||||
self.states.list_index()
|
||||
});
|
||||
CmdResult::None
|
||||
let index = self.states.real_index();
|
||||
self.states.list_index = self
|
||||
.states
|
||||
.list_index
|
||||
.saturating_add(1)
|
||||
.min(self.states.list_len.saturating_sub(1));
|
||||
CmdResult::Changed(State::One(StateValue::Usize(index)))
|
||||
}
|
||||
_ => CmdResult::None,
|
||||
}
|
||||
|
||||
@@ -132,21 +132,26 @@ impl ExplorerFuzzy {
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Custom(file_list::FILE_LIST_CMD_SELECT_ALL));
|
||||
Some(Msg::None)
|
||||
Some(Msg::Ui(UiMsg::MarkAll))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('a'),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Custom(file_list::FILE_LIST_CMD_DESELECT_ALL));
|
||||
Some(Msg::None)
|
||||
Some(Msg::Ui(UiMsg::MarkClear))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('m'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Toggle);
|
||||
Some(Msg::None)
|
||||
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
|
||||
self.perform(Cmd::Toggle)
|
||||
else {
|
||||
return Some(Msg::None);
|
||||
};
|
||||
|
||||
Some(Msg::Ui(UiMsg::MarkFile(index)))
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
|
||||
self.perform(Cmd::Change);
|
||||
@@ -277,21 +282,26 @@ impl Component<Msg, NoUserEvent> for ExplorerFind {
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Custom(file_list::FILE_LIST_CMD_SELECT_ALL));
|
||||
Some(Msg::None)
|
||||
Some(Msg::Ui(UiMsg::MarkAll))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('a'),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Custom(file_list::FILE_LIST_CMD_DESELECT_ALL));
|
||||
Some(Msg::None)
|
||||
Some(Msg::Ui(UiMsg::MarkClear))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('m'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Toggle);
|
||||
Some(Msg::None)
|
||||
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
|
||||
self.perform(Cmd::Toggle)
|
||||
else {
|
||||
return Some(Msg::None);
|
||||
};
|
||||
|
||||
Some(Msg::Ui(UiMsg::MarkFile(index)))
|
||||
}
|
||||
// -- comp msg
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
@@ -410,21 +420,26 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Custom(file_list::FILE_LIST_CMD_SELECT_ALL));
|
||||
Some(Msg::None)
|
||||
Some(Msg::Ui(UiMsg::MarkAll))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('a'),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Custom(file_list::FILE_LIST_CMD_DESELECT_ALL));
|
||||
Some(Msg::None)
|
||||
Some(Msg::Ui(UiMsg::MarkClear))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('m'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Toggle);
|
||||
Some(Msg::None)
|
||||
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
|
||||
self.perform(Cmd::Toggle)
|
||||
else {
|
||||
return Some(Msg::None);
|
||||
};
|
||||
|
||||
Some(Msg::Ui(UiMsg::MarkFile(index)))
|
||||
}
|
||||
// -- comp msg
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
@@ -508,7 +523,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('p'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
|
||||
}) => Some(Msg::Ui(UiMsg::GoToTransferQueue)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('r') | Key::Function(6),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
@@ -619,21 +634,26 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Custom(file_list::FILE_LIST_CMD_SELECT_ALL));
|
||||
Some(Msg::None)
|
||||
Some(Msg::Ui(UiMsg::MarkAll))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('a'),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Custom(file_list::FILE_LIST_CMD_DESELECT_ALL));
|
||||
Some(Msg::None)
|
||||
Some(Msg::Ui(UiMsg::MarkClear))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('m'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
let _ = self.perform(Cmd::Toggle);
|
||||
Some(Msg::None)
|
||||
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
|
||||
self.perform(Cmd::Toggle)
|
||||
else {
|
||||
return Some(Msg::None);
|
||||
};
|
||||
|
||||
Some(Msg::Ui(UiMsg::MarkFile(index)))
|
||||
}
|
||||
// -- comp msg
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
@@ -717,7 +737,7 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('p'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
|
||||
}) => Some(Msg::Ui(UiMsg::GoToTransferQueue)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('r') | Key::Function(6),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
|
||||
@@ -60,6 +60,23 @@ impl Browser {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn other_explorer_no_found(&self) -> &FileExplorer {
|
||||
match self.tab {
|
||||
FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge => &self.remote,
|
||||
FileExplorerTab::Remote | FileExplorerTab::FindRemote => &self.host_bridge,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn explorer_mut(&mut self) -> &mut FileExplorer {
|
||||
match self.tab {
|
||||
FileExplorerTab::HostBridge => &mut self.host_bridge,
|
||||
FileExplorerTab::Remote => &mut self.remote,
|
||||
FileExplorerTab::FindHostBridge | FileExplorerTab::FindRemote => {
|
||||
self.found.as_mut().map(|x| &mut x.explorer).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host_bridge(&self) -> &FileExplorer {
|
||||
&self.host_bridge
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use bytesize::ByteSize;
|
||||
use tuirealm::props::{
|
||||
Alignment, AttrValue, Attribute, Color, PropPayload, PropValue, TableBuilder, TextSpan,
|
||||
Alignment, AttrValue, Attribute, Color, PropPayload, PropValue, TableBuilder, TextModifiers,
|
||||
TextSpan,
|
||||
};
|
||||
use tuirealm::{PollStrategy, Update};
|
||||
|
||||
@@ -227,7 +228,7 @@ impl FileTransferActivity {
|
||||
transfer_stats
|
||||
)
|
||||
}
|
||||
TransferPayload::Many(entries) => {
|
||||
TransferPayload::TransferQueue(entries) => {
|
||||
format!(
|
||||
"{} files has been successfully transferred ({})",
|
||||
entries.len(),
|
||||
@@ -240,6 +241,11 @@ impl FileTransferActivity {
|
||||
/// Update host bridge file list
|
||||
pub(super) fn update_host_bridge_filelist(&mut self) {
|
||||
self.reload_host_bridge_dir();
|
||||
self.reload_host_bridge_filelist();
|
||||
}
|
||||
|
||||
/// Update host bridge file list
|
||||
pub(super) fn reload_host_bridge_filelist(&mut self) {
|
||||
// Get width
|
||||
let width = self
|
||||
.context_mut()
|
||||
@@ -261,7 +267,15 @@ impl FileTransferActivity {
|
||||
let files: Vec<Vec<TextSpan>> = self
|
||||
.host_bridge()
|
||||
.iter_files()
|
||||
.map(|x| vec![TextSpan::from(self.host_bridge().fmt_file(x))])
|
||||
.map(|x| {
|
||||
let mut span = TextSpan::from(self.host_bridge().fmt_file(x));
|
||||
if self.host_bridge().enqueued().contains_key(x.path()) {
|
||||
span.modifiers |=
|
||||
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC;
|
||||
}
|
||||
|
||||
vec![span]
|
||||
})
|
||||
.collect();
|
||||
// Update content and title
|
||||
assert!(
|
||||
@@ -287,7 +301,10 @@ impl FileTransferActivity {
|
||||
/// Update remote file list
|
||||
pub(super) fn update_remote_filelist(&mut self) {
|
||||
self.reload_remote_dir();
|
||||
self.reload_remote_filelist();
|
||||
}
|
||||
|
||||
pub(super) fn reload_remote_filelist(&mut self) {
|
||||
let width = self
|
||||
.context_mut()
|
||||
.terminal()
|
||||
@@ -308,7 +325,15 @@ impl FileTransferActivity {
|
||||
let files: Vec<Vec<TextSpan>> = self
|
||||
.remote()
|
||||
.iter_files()
|
||||
.map(|x| vec![TextSpan::from(self.remote().fmt_file(x))])
|
||||
.map(|x| {
|
||||
let mut span = TextSpan::from(self.remote().fmt_file(x));
|
||||
if self.remote().enqueued().contains_key(x.path()) {
|
||||
span.modifiers |=
|
||||
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC;
|
||||
}
|
||||
|
||||
vec![span]
|
||||
})
|
||||
.collect();
|
||||
// Update content and title
|
||||
assert!(
|
||||
@@ -460,7 +485,14 @@ impl FileTransferActivity {
|
||||
.found()
|
||||
.unwrap()
|
||||
.iter_files()
|
||||
.map(|x| vec![TextSpan::from(self.found().unwrap().fmt_file(x))])
|
||||
.map(|x| {
|
||||
let mut span = TextSpan::from(self.found().unwrap().fmt_file(x));
|
||||
if self.found().unwrap().enqueued().contains_key(x.path()) {
|
||||
span.modifiers |=
|
||||
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC;
|
||||
}
|
||||
vec![span]
|
||||
})
|
||||
.collect();
|
||||
assert!(
|
||||
self.app
|
||||
@@ -482,6 +514,15 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn reload_browser_file_list(&mut self) {
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge => {
|
||||
self.reload_host_bridge_filelist()
|
||||
}
|
||||
FileExplorerTab::Remote | FileExplorerTab::FindRemote => self.reload_remote_filelist(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn update_browser_file_list_swapped(&mut self) {
|
||||
match self.browser.tab() {
|
||||
FileExplorerTab::HostBridge | FileExplorerTab::FindHostBridge => {
|
||||
|
||||
@@ -40,6 +40,12 @@ use crate::system::watcher::FsWatcher;
|
||||
|
||||
// -- components
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum MarkQueue {
|
||||
Local,
|
||||
Remote,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
enum Id {
|
||||
ChmodPopup,
|
||||
@@ -74,6 +80,8 @@ enum Id {
|
||||
StatusBarRemote,
|
||||
SymlinkPopup,
|
||||
SyncBrowsingMkdirPopup,
|
||||
TransferQueueHostBridge,
|
||||
TransferQueueRemote,
|
||||
WaitPopup,
|
||||
WatchedPathsList,
|
||||
WatcherPopup,
|
||||
@@ -125,6 +133,8 @@ enum TransferMsg {
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum UiMsg {
|
||||
BottomPanelLeft,
|
||||
BottomPanelRight,
|
||||
ChangeFileSorting(FileSorting),
|
||||
ChangeTransferWindow,
|
||||
CloseChmodPopup,
|
||||
@@ -153,6 +163,13 @@ enum UiMsg {
|
||||
FilterFiles(String),
|
||||
FuzzySearch(String),
|
||||
LogBackTabbed,
|
||||
/// Mark file on the list; usize is the index of the file
|
||||
MarkFile(usize),
|
||||
MarkRemove(MarkQueue, PathBuf),
|
||||
/// Mark all file at tab
|
||||
MarkAll,
|
||||
/// Clear all marks
|
||||
MarkClear,
|
||||
Quit,
|
||||
ReplacePopupTabbed,
|
||||
ShowChmodPopup,
|
||||
@@ -165,7 +182,7 @@ enum UiMsg {
|
||||
ShowFilterPopup,
|
||||
ShowGotoPopup,
|
||||
ShowKeybindingsPopup,
|
||||
ShowLogPanel,
|
||||
GoToTransferQueue,
|
||||
ShowMkdirPopup,
|
||||
ShowNewFilePopup,
|
||||
ShowOpenWithPopup,
|
||||
@@ -307,6 +324,45 @@ impl FileTransferActivity {
|
||||
self.browser.found_mut()
|
||||
}
|
||||
|
||||
/// Enqueue a file to be transferred
|
||||
fn enqueue_file(&mut self, index: usize) {
|
||||
let Some(src) = self
|
||||
.browser
|
||||
.explorer()
|
||||
.get(index)
|
||||
.map(|item| item.path().to_path_buf())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if self.browser.explorer().enqueued().contains_key(&src) {
|
||||
debug!("File already marked, unmarking {}", src.display());
|
||||
self.browser.explorer_mut().dequeue(&src);
|
||||
} else {
|
||||
debug!("Marking file {}", src.display());
|
||||
let dest = self.browser.other_explorer_no_found().wrkdir.clone();
|
||||
self.browser.explorer_mut().enqueue(&src, &dest);
|
||||
}
|
||||
self.reload_browser_file_list();
|
||||
self.refresh_host_bridge_transfer_queue();
|
||||
self.refresh_remote_transfer_queue();
|
||||
}
|
||||
|
||||
fn enqueue_all(&mut self) {
|
||||
let dest = self.browser.other_explorer_no_found().wrkdir.clone();
|
||||
self.browser.explorer_mut().enqueue_all(&dest);
|
||||
self.reload_browser_file_list();
|
||||
self.refresh_host_bridge_transfer_queue();
|
||||
self.refresh_remote_transfer_queue();
|
||||
}
|
||||
|
||||
fn clear_queue(&mut self) {
|
||||
self.browser.explorer_mut().clear_queue();
|
||||
self.reload_browser_file_list();
|
||||
self.refresh_host_bridge_transfer_queue();
|
||||
self.refresh_remote_transfer_queue();
|
||||
}
|
||||
|
||||
/// Get file name for a file in cache
|
||||
fn get_cache_tmp_name(&self, name: &str, file_type: Option<&str>) -> Option<String> {
|
||||
self.cache.as_ref().map(|_| {
|
||||
|
||||
@@ -41,7 +41,8 @@ enum TransferErrorReason {
|
||||
pub(super) enum TransferPayload {
|
||||
File(File),
|
||||
Any(File),
|
||||
Many(Vec<File>),
|
||||
/// List of file with their destination name
|
||||
TransferQueue(Vec<(File, PathBuf)>),
|
||||
}
|
||||
|
||||
impl FileTransferActivity {
|
||||
@@ -264,8 +265,8 @@ impl FileTransferActivity {
|
||||
TransferPayload::File(ref file) => {
|
||||
self.filetransfer_send_file(file, curr_remote_path, dst_name)
|
||||
}
|
||||
TransferPayload::Many(ref entries) => {
|
||||
self.filetransfer_send_many(entries, curr_remote_path)
|
||||
TransferPayload::TransferQueue(ref entries) => {
|
||||
self.filetransfer_send_transfer_queue(entries)
|
||||
}
|
||||
};
|
||||
// Notify
|
||||
@@ -331,18 +332,17 @@ impl FileTransferActivity {
|
||||
result
|
||||
}
|
||||
|
||||
/// Send many entries to remote
|
||||
fn filetransfer_send_many(
|
||||
/// Send transfer queue entries to remote
|
||||
fn filetransfer_send_transfer_queue(
|
||||
&mut self,
|
||||
entries: &[File],
|
||||
curr_remote_path: &Path,
|
||||
entries: &[(File, PathBuf)],
|
||||
) -> Result<(), String> {
|
||||
// Reset states
|
||||
self.transfer.reset();
|
||||
// Calculate total size of transfer
|
||||
let total_transfer_size: usize = entries
|
||||
.iter()
|
||||
.map(|x| self.get_total_transfer_size_host(x))
|
||||
.map(|(x, _)| self.get_total_transfer_size_host(x))
|
||||
.sum();
|
||||
self.transfer.full.init(total_transfer_size);
|
||||
// Mount progress bar
|
||||
@@ -350,7 +350,7 @@ impl FileTransferActivity {
|
||||
// Send recurse
|
||||
let result = entries
|
||||
.iter()
|
||||
.map(|x| self.filetransfer_send_recurse(x, curr_remote_path, None))
|
||||
.map(|(x, remote)| self.filetransfer_send_recurse(x, remote, None))
|
||||
.find(|x| x.is_err())
|
||||
.unwrap_or(Ok(()));
|
||||
// Umount progress bar
|
||||
@@ -704,8 +704,8 @@ impl FileTransferActivity {
|
||||
self.filetransfer_recv_any(entry, host_bridge_path, dst_name)
|
||||
}
|
||||
TransferPayload::File(ref file) => self.filetransfer_recv_file(file, host_bridge_path),
|
||||
TransferPayload::Many(ref entries) => {
|
||||
self.filetransfer_recv_many(entries, host_bridge_path)
|
||||
TransferPayload::TransferQueue(ref entries) => {
|
||||
self.filetransfer_recv_transfer_queue(entries)
|
||||
}
|
||||
};
|
||||
// Notify
|
||||
@@ -764,18 +764,17 @@ impl FileTransferActivity {
|
||||
result.map_err(|x| x.to_string())
|
||||
}
|
||||
|
||||
/// Send many entries to remote
|
||||
fn filetransfer_recv_many(
|
||||
/// Receive transfer queue from remote
|
||||
fn filetransfer_recv_transfer_queue(
|
||||
&mut self,
|
||||
entries: &[File],
|
||||
curr_remote_path: &Path,
|
||||
entries: &[(File, PathBuf)],
|
||||
) -> Result<(), String> {
|
||||
// Reset states
|
||||
self.transfer.reset();
|
||||
// Calculate total size of transfer
|
||||
let total_transfer_size: usize = entries
|
||||
.iter()
|
||||
.map(|x| self.get_total_transfer_size_remote(x))
|
||||
.map(|(x, _)| self.get_total_transfer_size_remote(x))
|
||||
.sum();
|
||||
self.transfer.full.init(total_transfer_size);
|
||||
// Mount progress bar
|
||||
@@ -783,7 +782,7 @@ impl FileTransferActivity {
|
||||
// Send recurse
|
||||
let result = entries
|
||||
.iter()
|
||||
.map(|x| self.filetransfer_recv_recurse(x, curr_remote_path, None))
|
||||
.map(|(x, path)| self.filetransfer_recv_recurse(x, path, None))
|
||||
.find(|x| x.is_err())
|
||||
.unwrap_or(Ok(()));
|
||||
// Umount progress bar
|
||||
|
||||
@@ -11,7 +11,9 @@ use tuirealm::{State, StateValue, Update};
|
||||
use super::actions::SelectedFile;
|
||||
use super::actions::walkdir::WalkdirError;
|
||||
use super::browser::{FileExplorerTab, FoundExplorerTab};
|
||||
use super::{ExitReason, FileTransferActivity, Id, Msg, TransferMsg, TransferOpts, UiMsg};
|
||||
use super::{
|
||||
ExitReason, FileTransferActivity, Id, MarkQueue, Msg, TransferMsg, TransferOpts, UiMsg,
|
||||
};
|
||||
|
||||
impl Update<Msg> for FileTransferActivity {
|
||||
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||
@@ -113,7 +115,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::HostBridge => {
|
||||
if let SelectedFile::One(entry) = self.get_local_selected_entries() {
|
||||
if let Some(entry) = self.get_local_selected_file() {
|
||||
self.action_submit_local(entry);
|
||||
// Update file list if sync
|
||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||
@@ -123,7 +125,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
}
|
||||
TransferMsg::EnterDirectory if self.browser.tab() == FileExplorerTab::Remote => {
|
||||
if let SelectedFile::One(entry) = self.get_remote_selected_entries() {
|
||||
if let Some(entry) = self.get_remote_selected_file() {
|
||||
self.action_submit_remote(entry);
|
||||
// Update file list if sync
|
||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||
@@ -473,12 +475,33 @@ impl FileTransferActivity {
|
||||
self.browser.fuzzy_search(&needle);
|
||||
self.update_find_list();
|
||||
}
|
||||
UiMsg::ShowLogPanel => {
|
||||
assert!(self.app.active(&Id::Log).is_ok());
|
||||
UiMsg::GoToTransferQueue => {
|
||||
assert!(self.app.active(&Id::TransferQueueHostBridge).is_ok());
|
||||
}
|
||||
UiMsg::LogBackTabbed => {
|
||||
assert!(self.app.active(&Id::ExplorerHostBridge).is_ok());
|
||||
}
|
||||
UiMsg::MarkFile(index) => {
|
||||
self.action_mark_file(index);
|
||||
}
|
||||
UiMsg::MarkAll => {
|
||||
self.action_mark_all();
|
||||
}
|
||||
UiMsg::MarkClear => {
|
||||
self.action_mark_clear();
|
||||
}
|
||||
UiMsg::MarkRemove(tab, path) => match tab {
|
||||
MarkQueue::Local => {
|
||||
self.host_bridge_mut().dequeue(&path);
|
||||
self.reload_host_bridge_filelist();
|
||||
self.refresh_host_bridge_transfer_queue();
|
||||
}
|
||||
MarkQueue::Remote => {
|
||||
self.remote_mut().dequeue(&path);
|
||||
self.reload_remote_filelist();
|
||||
self.refresh_remote_transfer_queue();
|
||||
}
|
||||
},
|
||||
UiMsg::Quit => {
|
||||
self.disconnect_and_quit();
|
||||
self.umount_quit();
|
||||
@@ -584,6 +607,31 @@ impl FileTransferActivity {
|
||||
UiMsg::WindowResized => {
|
||||
self.redraw = true;
|
||||
}
|
||||
|
||||
UiMsg::BottomPanelLeft => match self.app.focus() {
|
||||
Some(Id::TransferQueueHostBridge) => {
|
||||
assert!(self.app.active(&Id::Log).is_ok())
|
||||
}
|
||||
Some(Id::TransferQueueRemote) => {
|
||||
assert!(self.app.active(&Id::TransferQueueHostBridge).is_ok())
|
||||
}
|
||||
Some(Id::Log) => {
|
||||
assert!(self.app.active(&Id::TransferQueueRemote).is_ok())
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
UiMsg::BottomPanelRight => match self.app.focus() {
|
||||
Some(Id::TransferQueueHostBridge) => {
|
||||
assert!(self.app.active(&Id::TransferQueueRemote).is_ok())
|
||||
}
|
||||
Some(Id::TransferQueueRemote) => {
|
||||
assert!(self.app.active(&Id::Log).is_ok())
|
||||
}
|
||||
Some(Id::Log) => {
|
||||
assert!(self.app.active(&Id::TransferQueueHostBridge).is_ok())
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use super::browser::{FileExplorerTab, FoundExplorerTab};
|
||||
use super::components::ATTR_FILES;
|
||||
use super::{Context, FileTransferActivity, Id, components};
|
||||
use crate::explorer::FileSorting;
|
||||
use crate::ui::activities::filetransfer::MarkQueue;
|
||||
use crate::utils::ui::{Popup, Size};
|
||||
|
||||
impl FileTransferActivity {
|
||||
@@ -81,6 +82,8 @@ impl FileTransferActivity {
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
self.refresh_host_bridge_transfer_queue();
|
||||
self.refresh_remote_transfer_queue();
|
||||
// Load status bar
|
||||
self.refresh_local_status_bar();
|
||||
self.refresh_remote_status_bar();
|
||||
@@ -138,6 +141,17 @@ impl FileTransferActivity {
|
||||
.direction(Direction::Horizontal)
|
||||
.horizontal_margin(1)
|
||||
.split(bottom_chunks[0]);
|
||||
let bottom_components = Layout::default()
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(50),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.direction(Direction::Horizontal)
|
||||
.split(bottom_chunks[1]);
|
||||
// Draw footer
|
||||
self.app.view(&Id::FooterBar, f, body[1]);
|
||||
// Draw explorers
|
||||
@@ -153,8 +167,13 @@ impl FileTransferActivity {
|
||||
} else {
|
||||
self.app.view(&Id::ExplorerRemote, f, tabs_chunks[1]);
|
||||
}
|
||||
// draw transfer queues
|
||||
self.app
|
||||
.view(&Id::TransferQueueHostBridge, f, bottom_components[0]);
|
||||
self.app
|
||||
.view(&Id::TransferQueueRemote, f, bottom_components[1]);
|
||||
// Draw log box
|
||||
self.app.view(&Id::Log, f, bottom_chunks[1]);
|
||||
self.app.view(&Id::Log, f, bottom_components[2]);
|
||||
// Draw status bar
|
||||
self.app
|
||||
.view(&Id::StatusBarHostBridge, f, status_bar_chunks[0]);
|
||||
@@ -928,6 +947,56 @@ impl FileTransferActivity {
|
||||
let _ = self.app.umount(&Id::FileInfoPopup);
|
||||
}
|
||||
|
||||
pub(super) fn refresh_host_bridge_transfer_queue(&mut self) {
|
||||
let enqueued = self
|
||||
.host_bridge()
|
||||
.enqueued()
|
||||
.iter()
|
||||
.map(|(src, dest)| (src.clone(), dest.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let log_panel = self.theme().transfer_log_window;
|
||||
|
||||
assert!(
|
||||
self.app
|
||||
.remount(
|
||||
Id::TransferQueueHostBridge,
|
||||
Box::new(components::SelectedFilesList::new(
|
||||
&enqueued,
|
||||
MarkQueue::Local,
|
||||
log_panel,
|
||||
"Host Bridge selected files",
|
||||
)),
|
||||
vec![]
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn refresh_remote_transfer_queue(&mut self) {
|
||||
let enqueued = self
|
||||
.remote()
|
||||
.enqueued()
|
||||
.iter()
|
||||
.map(|(src, dest)| (src.clone(), dest.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let log_panel = self.theme().transfer_log_window;
|
||||
|
||||
assert!(
|
||||
self.app
|
||||
.remount(
|
||||
Id::TransferQueueRemote,
|
||||
Box::new(components::SelectedFilesList::new(
|
||||
&enqueued,
|
||||
MarkQueue::Remote,
|
||||
log_panel,
|
||||
"Remote transfer selected files",
|
||||
)),
|
||||
vec![]
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn refresh_local_status_bar(&mut self) {
|
||||
let sorting_color = self.theme().transfer_status_sorting;
|
||||
let hidden_color = self.theme().transfer_status_hidden;
|
||||
|
||||
Reference in New Issue
Block a user