feat: allow setting permissions in multi preview
This adds a Message::ShiftPermissions to handle setting permissions for user, group or other for either 1 or more items and a Command::SetMultiplePermissions to set permissions on multiple items at the same time. The permission dropdown will only have a selection if all selected items have the same permission, otherwise it will be empty but still allow changing. Up to 5 owners and groups will be displayed for all selected items with an ellipses if there are more. The OperationSelection for setting permissions now also returns the path as selected such that the tab will be re-scanned and update the dropdown to correctly.
This commit is contained in:
parent
17325a5f5a
commit
41cdf89604
3 changed files with 152 additions and 2 deletions
|
|
@ -4478,6 +4478,13 @@ impl Application for App {
|
|||
tab::Command::SetPermissions(path, mode) => {
|
||||
commands.push(self.operation(Operation::SetPermissions { path, mode }));
|
||||
}
|
||||
tab::Command::SetMultiplePermissions(permissions) => {
|
||||
commands.push(Task::batch(permissions.into_iter().map(
|
||||
|(path, mode)| {
|
||||
self.operation(Operation::SetPermissions { path, mode })
|
||||
},
|
||||
)));
|
||||
}
|
||||
tab::Command::WindowDrag => {
|
||||
if let Some(window_id) = self.core.main_window_id() {
|
||||
commands.push(window::drag(window_id));
|
||||
|
|
|
|||
|
|
@ -1180,8 +1180,10 @@ impl Operation {
|
|||
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||
|
||||
let controller_clone = controller.clone();
|
||||
let path_clone = path.clone();
|
||||
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
||||
let controller = controller_clone;
|
||||
let path = path_clone;
|
||||
//TODO: what to do on non-Unix systems?
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
|
@ -1196,7 +1198,10 @@ impl Operation {
|
|||
.await
|
||||
.map_err(wrap_compio_spawn_error)?
|
||||
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||
Ok(OperationSelection::default())
|
||||
Ok(OperationSelection {
|
||||
ignored: Vec::new(),
|
||||
selected: vec![path],
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
140
src/tab.rs
140
src/tab.rs
|
|
@ -46,7 +46,7 @@ use std::{
|
|||
borrow::Cow,
|
||||
cell::Cell,
|
||||
cmp::{Ordering, Reverse},
|
||||
collections::{BTreeMap, HashMap},
|
||||
collections::{BTreeMap, BTreeSet, HashMap},
|
||||
error::Error,
|
||||
fmt::{self, Display},
|
||||
fs::{self, File, Metadata},
|
||||
|
|
@ -1796,6 +1796,7 @@ pub enum Command {
|
|||
Preview(PreviewKind),
|
||||
SetOpenWith(Mime, String),
|
||||
SetPermissions(PathBuf, u32),
|
||||
SetMultiplePermissions(Vec<(PathBuf, u32)>),
|
||||
SetSort(String, HeadingOptions, bool),
|
||||
WindowDrag,
|
||||
WindowToggleMaximize,
|
||||
|
|
@ -1852,6 +1853,7 @@ pub enum Message {
|
|||
SelectLast,
|
||||
SetOpenWith(Mime, String),
|
||||
SetPermissions(PathBuf, u32),
|
||||
ShiftPermissions(Option<(PathBuf, u32)>, u32, u32),
|
||||
SetSort(HeadingOptions, bool),
|
||||
TabComplete(PathBuf, Vec<(String, PathBuf)>),
|
||||
Thumbnail(PathBuf, ItemThumbnail),
|
||||
|
|
@ -4335,6 +4337,31 @@ impl Tab {
|
|||
Message::SetPermissions(path, mode) => {
|
||||
commands.push(Command::SetPermissions(path, mode));
|
||||
}
|
||||
Message::ShiftPermissions(path_mode_opt, shift, bits) => match path_mode_opt {
|
||||
Some((path, mode)) => commands.push(Command::SetPermissions(
|
||||
path,
|
||||
set_mode_part(mode, shift, bits.try_into().unwrap()),
|
||||
)),
|
||||
// Shift permissions on all selected items
|
||||
None => {
|
||||
let mut permissions = Vec::new();
|
||||
for item in self.items_opt().map_or(Vec::new(), |items| {
|
||||
items.iter().filter(|item| item.selected).collect()
|
||||
}) {
|
||||
if let (Some(path), Some(mode)) = (
|
||||
item.path_opt(),
|
||||
item.file_metadata()
|
||||
.and_then(|metadata| Some(metadata.mode())),
|
||||
) {
|
||||
permissions.push((
|
||||
path.clone(),
|
||||
set_mode_part(mode, shift, bits.try_into().unwrap()),
|
||||
));
|
||||
}
|
||||
}
|
||||
commands.push(Command::SetMultiplePermissions(permissions));
|
||||
}
|
||||
},
|
||||
Message::SetSort(heading_option, dir) => {
|
||||
if !matches!(self.location, Location::Search(..)) {
|
||||
self.sort_name = heading_option;
|
||||
|
|
@ -6304,6 +6331,11 @@ impl Tab {
|
|||
|
||||
let mut total_size: u64 = 0;
|
||||
let mut mime_type_counts: BTreeMap<String, u64> = BTreeMap::new();
|
||||
let mut user_name: BTreeSet<String> = BTreeSet::new();
|
||||
let mut mode_user: BTreeSet<u32> = BTreeSet::new();
|
||||
let mut group_name: BTreeSet<String> = BTreeSet::new();
|
||||
let mut mode_group: BTreeSet<u32> = BTreeSet::new();
|
||||
let mut mode_other: BTreeSet<u32> = BTreeSet::new();
|
||||
let mut calculating_dir_size = false;
|
||||
let mut dir_size_error: Option<String> = None;
|
||||
|
||||
|
|
@ -6327,6 +6359,20 @@ impl Tab {
|
|||
} else {
|
||||
total_size = total_size.saturating_add(metadata.len());
|
||||
}
|
||||
let mode = metadata.mode();
|
||||
user_name.insert(
|
||||
get_user_by_uid(metadata.uid())
|
||||
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
mode_user.insert(get_mode_part(mode, MODE_SHIFT_USER));
|
||||
group_name.insert(
|
||||
get_group_by_gid(metadata.gid())
|
||||
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
mode_group.insert(get_mode_part(mode, MODE_SHIFT_GROUP));
|
||||
mode_other.insert(get_mode_part(mode, MODE_SHIFT_OTHER));
|
||||
}
|
||||
}
|
||||
let mut mime_types: Vec<(String, u64)> = mime_type_counts.into_iter().collect();
|
||||
|
|
@ -6366,6 +6412,7 @@ impl Tab {
|
|||
column = column.push(widget::button::standard(fl!("open")).on_press(Message::Open(None)));
|
||||
|
||||
let mut settings = Vec::new();
|
||||
// Only allow modifying open-with if all mime types are the same
|
||||
if mime_types.len() == 1 {
|
||||
if let Some(mime) = mime_types
|
||||
.get(0)
|
||||
|
|
@ -6396,6 +6443,97 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// The following is a bit of a hack to make the dropdown selector
|
||||
// fill the available space when selected is None so it can be
|
||||
// clicked on easier.
|
||||
fn dropdown_width(use_default: bool) -> Length {
|
||||
if use_default {
|
||||
Length::Shrink
|
||||
} else {
|
||||
Length::Fill
|
||||
}
|
||||
}
|
||||
|
||||
// Only return mode part if it's the only one
|
||||
fn selected_mode_part(mut modes: BTreeSet<u32>) -> Option<usize> {
|
||||
match (modes.pop_first(), modes.pop_first()) {
|
||||
(Some(mode), None) => Some(mode.try_into().unwrap()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a limited number of values from a set into a comma separated list
|
||||
fn join_set(set: BTreeSet<String>) -> String {
|
||||
let limit = 5;
|
||||
let mut title = set.into_iter().collect::<Vec<String>>();
|
||||
if title.len() > limit {
|
||||
title.truncate(limit);
|
||||
title.push("...".to_string());
|
||||
}
|
||||
title.join(", ")
|
||||
}
|
||||
|
||||
let mode_part_user = selected_mode_part(mode_user);
|
||||
settings.push(
|
||||
widget::settings::item::builder(join_set(user_name))
|
||||
.description(fl!("owner"))
|
||||
.control(
|
||||
widget::dropdown(
|
||||
Cow::Borrowed(MODE_NAMES.as_slice()),
|
||||
mode_part_user,
|
||||
move |selected| {
|
||||
Message::ShiftPermissions(
|
||||
None,
|
||||
MODE_SHIFT_USER,
|
||||
selected.try_into().unwrap(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.width(dropdown_width(mode_part_user.is_some())),
|
||||
),
|
||||
);
|
||||
|
||||
let mode_part_group = selected_mode_part(mode_group);
|
||||
settings.push(
|
||||
widget::settings::item::builder(join_set(group_name))
|
||||
.description(fl!("group"))
|
||||
.control(
|
||||
widget::dropdown(
|
||||
Cow::Borrowed(MODE_NAMES.as_slice()),
|
||||
mode_part_group,
|
||||
move |selected| {
|
||||
Message::ShiftPermissions(
|
||||
None,
|
||||
MODE_SHIFT_GROUP,
|
||||
selected.try_into().unwrap(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.width(dropdown_width(mode_part_group.is_some())),
|
||||
),
|
||||
);
|
||||
|
||||
let mode_part_other = selected_mode_part(mode_other);
|
||||
settings.push(
|
||||
widget::settings::item::builder(fl!("other")).control(
|
||||
widget::dropdown(
|
||||
Cow::Borrowed(MODE_NAMES.as_slice()),
|
||||
mode_part_other,
|
||||
move |selected| {
|
||||
Message::ShiftPermissions(
|
||||
None,
|
||||
MODE_SHIFT_OTHER,
|
||||
selected.try_into().unwrap(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.width(dropdown_width(mode_part_other.is_some())),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if !settings.is_empty() {
|
||||
let mut section = widget::settings::section();
|
||||
section = section.extend(settings);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue