fix: add eject button for context menus on mount point

This commit is contained in:
Ashley Wulber 2025-06-30 19:23:40 -04:00 committed by Jeremy Soller
parent aba90279e6
commit 6a2bd1faf1
6 changed files with 90 additions and 16 deletions

View file

@ -291,6 +291,7 @@ type-to-search-enter-path = Enters the path to the directory or file
add-to-sidebar = Add to sidebar add-to-sidebar = Add to sidebar
compress = Compress compress = Compress
delete-permanently = Delete permanently delete-permanently = Delete permanently
eject = Eject
extract-here = Extract extract-here = Extract
new-file = New file... new-file = New file...
new-folder = New folder... new-folder = New folder...

View file

@ -117,6 +117,7 @@ pub enum Action {
Delete, Delete,
EditHistory, EditHistory,
EditLocation, EditLocation,
Eject,
EmptyTrash, EmptyTrash,
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
ExecEntryAction(usize), ExecEntryAction(usize),
@ -184,6 +185,7 @@ impl Action {
Action::EditLocation => { Action::EditLocation => {
Message::TabMessage(entity_opt, tab::Message::EditLocationEnable) Message::TabMessage(entity_opt, tab::Message::EditLocationEnable)
} }
Action::Eject => Message::Eject,
Action::EmptyTrash => Message::TabMessage(None, tab::Message::EmptyTrash), Action::EmptyTrash => Message::TabMessage(None, tab::Message::EmptyTrash),
Action::ExtractHere => Message::ExtractHere(entity_opt), Action::ExtractHere => Message::ExtractHere(entity_opt),
Action::ExtractTo => Message::ExtractTo(entity_opt), Action::ExtractTo => Message::ExtractTo(entity_opt),
@ -304,6 +306,7 @@ pub enum Message {
DesktopViewOptions, DesktopViewOptions,
DialogCancel, DialogCancel,
DialogComplete, DialogComplete,
Eject,
FileDialogMessage(DialogMessage), FileDialogMessage(DialogMessage),
DialogPush(DialogPage), DialogPush(DialogPage),
DialogUpdate(DialogPage), DialogUpdate(DialogPage),
@ -1044,17 +1047,36 @@ impl App {
) -> Task<Message> { ) -> Task<Message> {
log::info!("rescan_tab {entity:?} {location:?} {selection_paths:?}"); log::info!("rescan_tab {entity:?} {location:?} {selection_paths:?}");
let icon_sizes = self.config.tab.icon_sizes; let icon_sizes = self.config.tab.icon_sizes;
let mounter_items = self.mounter_items.clone();
Task::perform( Task::perform(
async move { async move {
let location2 = location.clone(); let location2 = location.clone();
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
Ok((parent_item_opt, items)) => cosmic::action::app(Message::TabRescan( Ok((parent_item_opt, mut items)) => {
entity, #[cfg(feature = "gvfs")]
location, {
parent_item_opt, let mounter_paths: Vec<_> = mounter_items
items, .iter()
selection_paths, .flat_map(|item| item.1.iter())
)), .filter_map(|item| item.path())
.collect();
if !mounter_paths.is_empty() {
for item in &mut items {
item.is_mount_point =
item.path_opt().is_some_and(|p| mounter_paths.contains(p));
}
}
}
cosmic::action::app(Message::TabRescan(
entity,
location,
parent_item_opt,
items,
selection_paths,
))
}
Err(err) => { Err(err) => {
log::warn!("failed to rescan: {}", err); log::warn!("failed to rescan: {}", err);
cosmic::action::none() cosmic::action::none()
@ -4136,6 +4158,28 @@ impl Application for App {
self.size = Some(size); self.size = Some(size);
self.handle_overlap(); self.handle_overlap();
} }
Message::Eject => {
#[cfg(feature = "gvfs")]
{
let paths = self.selected_paths(None);
if let Some(p) = paths.first() {
{
for (k, mounter_items) in &self.mounter_items {
if let Some(mounter) = MOUNTERS.get(&k) {
if let Some(item) = mounter_items
.iter()
.find(|item| item.path().is_some_and(|path| path == *p))
{
return mounter
.unmount(item.clone())
.map(|_| cosmic::action::none());
}
}
}
}
}
}
}
#[cfg(all(feature = "wayland", feature = "desktop-applet"))] #[cfg(all(feature = "wayland", feature = "desktop-applet"))]
Message::Focused(id) => { Message::Focused(id) => {
if let Some(w) = self.windows.get(&id) { if let Some(w) = self.windows.get(&id) {

View file

@ -650,11 +650,26 @@ impl App {
fn rescan_tab(&self) -> Task<Message> { fn rescan_tab(&self) -> Task<Message> {
let location = self.tab.location.clone(); let location = self.tab.location.clone();
let icon_sizes = self.tab.config.icon_sizes; let icon_sizes = self.tab.config.icon_sizes;
let mounter_items = self.mounter_items.clone();
Task::perform( Task::perform(
async move { async move {
let location2 = location.clone(); let location2 = location.clone();
match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await {
Ok((parent_item_opt, items)) => { Ok((parent_item_opt, mut items)) => {
#[cfg(feature = "gvfs")]
{
let mounter_paths: Vec<_> = mounter_items
.iter()
.flat_map(|item| item.1.iter())
.filter_map(|item| item.path())
.collect();
if !mounter_paths.is_empty() {
for item in &mut items {
item.is_mount_point =
item.path_opt().is_some_and(|p| mounter_paths.contains(p));
}
}
}
cosmic::action::app(Message::TabRescan(location, parent_item_opt, items)) cosmic::action::app(Message::TabRescan(location, parent_item_opt, items))
} }
Err(err) => { Err(err) => {

View file

@ -108,11 +108,13 @@ pub fn context_menu<'a>(
let mut selected_trash_only = false; let mut selected_trash_only = false;
let mut selected_desktop_entry = None; let mut selected_desktop_entry = None;
let mut selected_types: Vec<Mime> = vec![]; let mut selected_types: Vec<Mime> = vec![];
let mut selected_mount_point = 0;
if let Some(items) = tab.items_opt() { if let Some(items) = tab.items_opt() {
for item in items.iter() { for item in items.iter() {
if item.selected { if item.selected {
selected += 1; selected += 1;
if item.metadata.is_dir() { if item.metadata.is_dir() {
selected_mount_point += item.is_mount_point as i32;
selected_dir += 1; selected_dir += 1;
} }
match &item.location_opt { match &item.location_opt {
@ -194,8 +196,10 @@ pub fn context_menu<'a>(
.push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into()); .push(menu_item(fl!("open-in-new-window"), Action::OpenInNewWindow).into());
} }
children.push(divider::horizontal::light().into()); children.push(divider::horizontal::light().into());
children.push(menu_item(fl!("rename"), Action::Rename).into()); if selected_mount_point == 0 {
children.push(menu_item(fl!("cut"), Action::Cut).into()); children.push(menu_item(fl!("rename"), Action::Rename).into());
children.push(menu_item(fl!("cut"), Action::Cut).into());
}
children.push(menu_item(fl!("copy"), Action::Copy).into()); children.push(menu_item(fl!("copy"), Action::Copy).into());
children.push(divider::horizontal::light().into()); children.push(divider::horizontal::light().into());
@ -235,12 +239,16 @@ pub fn context_menu<'a>(
children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into()); children.push(menu_item(fl!("add-to-sidebar"), Action::AddToSidebar).into());
} }
children.push(divider::horizontal::light().into()); children.push(divider::horizontal::light().into());
if modifiers.shift() && !modifiers.control() { if selected_mount_point == 0 {
children.push( if modifiers.shift() && !modifiers.control() {
menu_item(fl!("delete-permanently"), Action::PermanentlyDelete).into(), children.push(
); menu_item(fl!("delete-permanently"), Action::PermanentlyDelete).into(),
} else { );
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into()); } else {
children.push(menu_item(fl!("move-to-trash"), Action::Delete).into());
}
} else if selected == 1 {
children.push(menu_item(fl!("eject"), Action::Eject).into());
} }
} else { } else {
//TODO: need better designs for menu with no selection //TODO: need better designs for menu with no selection

View file

@ -109,6 +109,7 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result<Vec<tab::Item>, String> {
items.push(tab::Item { items.push(tab::Item {
name, name,
is_mount_point: false,
display_name, display_name,
metadata, metadata,
hidden: false, hidden: false,

View file

@ -668,6 +668,7 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS
Item { Item {
name: file_name.clone().to_string(), name: file_name.clone().to_string(),
display_name, display_name,
is_mount_point: false,
metadata: ItemMetadata::GvfsPath { metadata: ItemMetadata::GvfsPath {
mtime, mtime,
size_opt, size_opt,
@ -809,6 +810,7 @@ pub fn item_from_entry(
Item { Item {
name, name,
display_name, display_name,
is_mount_point: false,
metadata: ItemMetadata::Path { metadata: ItemMetadata::Path {
metadata, metadata,
children_opt, children_opt,
@ -1090,6 +1092,7 @@ pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
items.push(Item { items.push(Item {
name, name,
display_name, display_name,
is_mount_point: false,
metadata: ItemMetadata::Trash { metadata, entry }, metadata: ItemMetadata::Trash { metadata, entry },
hidden: false, hidden: false,
location_opt: None, location_opt: None,
@ -1270,6 +1273,7 @@ pub fn scan_desktop(
items.push(Item { items.push(Item {
name, name,
display_name, display_name,
is_mount_point: false,
metadata, metadata,
hidden: false, hidden: false,
location_opt: Some(Location::Trash), location_opt: Some(Location::Trash),
@ -1821,6 +1825,7 @@ impl ItemThumbnail {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Item { pub struct Item {
pub name: String, pub name: String,
pub is_mount_point: bool,
pub display_name: String, pub display_name: String,
pub metadata: ItemMetadata, pub metadata: ItemMetadata,
pub hidden: bool, pub hidden: bool,