feat: Show count of files affected when 'Replace all' is selected in copy/move operations

This commit is contained in:
Ric's Dev 2026-02-11 21:55:44 +01:00
parent 3ecdb59f3b
commit 14c96a466e
3 changed files with 29 additions and 7 deletions

View file

@ -567,6 +567,7 @@ pub enum DialogPage {
to: tab::Item,
multiple: bool,
apply_to_all: bool,
conflict_count: usize,
tx: mpsc::Sender<ReplaceResult>,
},
SetExecutableAndLaunch {
@ -5715,6 +5716,7 @@ impl Application for App {
to,
multiple,
apply_to_all,
conflict_count,
tx,
} => {
let military_time = self.config.tab.military_time;
@ -5739,13 +5741,18 @@ impl Application for App {
if *multiple {
dialog
.control(
widget::checkbox(fl!("apply-to-all"), *apply_to_all).on_toggle(
widget::checkbox(
format!("{} ({})" ,fl!("apply-to-all"), *conflict_count),
*apply_to_all,
)
.on_toggle(
|apply_to_all| {
Message::DialogUpdate(DialogPage::Replace {
from: from.clone(),
to: to.clone(),
multiple: *multiple,
apply_to_all,
conflict_count: *conflict_count,
tx: tx.clone(),
})
},

View file

@ -32,6 +32,7 @@ async fn handle_replace(
file_from: PathBuf,
file_to: PathBuf,
multiple: bool,
conflict_count: usize,
) -> ReplaceResult {
let item_from = match tab::item_from_path(file_from, IconSizes::default()) {
Ok(ok) => ok,
@ -59,6 +60,7 @@ async fn handle_replace(
to: item_to,
multiple,
apply_to_all: false,
conflict_count,
tx,
},
Some(REPLACE_BUTTON_ID.clone()),
@ -180,9 +182,9 @@ async fn copy_or_move(
{
let msg_tx = msg_tx.clone();
context = context.on_replace(move |op| {
context = context.on_replace(move |op, conflict_count| {
let msg_tx = msg_tx.clone();
Box::pin(handle_replace(msg_tx, op.from.clone(), op.to.clone(), true))
Box::pin(handle_replace(msg_tx, op.from.clone(), op.to.clone(), true, conflict_count))
});
}

View file

@ -23,17 +23,18 @@ pub struct Context {
on_replace: Pin<Box<dyn OnReplace>>,
pub(crate) op_sel: OperationSelection,
replace_result_opt: Option<ReplaceResult>,
remaining_conflicts: usize,
}
pub trait OnProgress: Fn(&Op, &Progress) + 'static {}
impl<F> OnProgress for F where F: Fn(&Op, &Progress) + 'static {}
pub trait OnReplace:
for<'a> Fn(&'a Op) -> Pin<Box<dyn Future<Output = ReplaceResult> + 'a>> + 'static
for<'a> Fn(&'a Op, usize) -> Pin<Box<dyn Future<Output = ReplaceResult> + 'a>> + 'static
{
}
impl<F> OnReplace for F where
F: for<'a> Fn(&'a Op) -> Pin<Box<dyn Future<Output = ReplaceResult> + 'a>> + 'static
F: for<'a> Fn(&'a Op, usize) -> Pin<Box<dyn Future<Output = ReplaceResult> + 'a>> + 'static
{
}
@ -44,9 +45,10 @@ impl Context {
buf: vec![0u8; 128 * 1024],
controller,
on_progress: Box::new(|_op, _progress| {}),
on_replace: Box::pin(|_op| Box::pin(async { ReplaceResult::Cancel })),
on_replace: Box::pin(|_op, _count| Box::pin(async { ReplaceResult::Cancel })),
op_sel: OperationSelection::default(),
replace_result_opt: None,
remaining_conflicts: 0,
}
}
@ -156,6 +158,17 @@ impl Context {
cleanup_ops.reverse();
ops.append(&mut cleanup_ops);
// Count potential conflicts (files that would need replacement)
self.remaining_conflicts = ops
.iter()
.filter(|op| {
matches!(
op.kind,
OpKind::Copy | OpKind::Move { .. } | OpKind::Symlink { .. }
) && op.to.is_file()
})
.count();
let total_ops = ops.len();
for (current_ops, mut op) in ops.into_iter().enumerate() {
self.controller
@ -221,7 +234,7 @@ impl Context {
async fn replace(&mut self, op: &Op) -> Result<ControlFlow<bool, PathBuf>, Box<dyn Error>> {
let replace_result = match self.replace_result_opt {
Some(result) => result,
None => (self.on_replace)(op).await,
None => (self.on_replace)(op, self.remaining_conflicts).await,
};
match replace_result {