Add download file actions
This commit is contained in:
@@ -12,6 +12,7 @@ ely_domain = { path = "../ely_domain" }
|
||||
gpui.workspace = true
|
||||
gpui-component.workspace = true
|
||||
gpui-component-assets.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod services;
|
||||
mod shell;
|
||||
|
||||
use gpui::{
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
use std::{
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, ExitStatus},
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum DownloadFileAction {
|
||||
Open,
|
||||
Reveal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DownloadFileError {
|
||||
#[error("download file is unavailable: {path:?}")]
|
||||
FileUnavailable { path: PathBuf },
|
||||
|
||||
#[error("failed to launch {action} for download file: {path:?}: {source}")]
|
||||
LaunchFailed { action: &'static str, path: PathBuf, source: io::Error },
|
||||
|
||||
#[error("{action} failed for download file: {path:?} ({status})")]
|
||||
CommandFailed { action: &'static str, path: PathBuf, status: ExitStatus },
|
||||
}
|
||||
|
||||
impl DownloadFileAction {
|
||||
pub fn run(self, path: &Path) -> Result<(), DownloadFileError> {
|
||||
ensure_regular_file(path)?;
|
||||
let status =
|
||||
self.command(path).status().map_err(|source| DownloadFileError::LaunchFailed {
|
||||
action: self.label(),
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
if status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(DownloadFileError::CommandFailed {
|
||||
action: self.label(),
|
||||
path: path.to_path_buf(),
|
||||
status,
|
||||
})
|
||||
}
|
||||
|
||||
fn command(self, path: &Path) -> Command {
|
||||
let mut command = Command::new("/usr/bin/open");
|
||||
match self {
|
||||
Self::Open => {
|
||||
command.arg(path);
|
||||
}
|
||||
Self::Reveal => {
|
||||
command.arg("-R").arg(path);
|
||||
}
|
||||
}
|
||||
command
|
||||
}
|
||||
|
||||
fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Open => "open",
|
||||
Self::Reveal => "reveal",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_regular_file(path: &Path) -> Result<(), DownloadFileError> {
|
||||
match fs::metadata(path) {
|
||||
Ok(metadata) if metadata.is_file() => Ok(()),
|
||||
Ok(_) | Err(_) => Err(DownloadFileError::FileUnavailable { path: path.to_path_buf() }),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod download_files;
|
||||
@@ -75,6 +75,9 @@ impl ElyShell {
|
||||
),
|
||||
),
|
||||
)
|
||||
.when_some(self.download_file_error.clone(), |this, message| {
|
||||
this.child(render_download_file_error(message))
|
||||
})
|
||||
.child(self.render_downloads_list(snapshot, cx)),
|
||||
)
|
||||
}
|
||||
@@ -168,7 +171,7 @@ impl ElyShell {
|
||||
div()
|
||||
.min_w_0()
|
||||
.truncate()
|
||||
.child(download_destination_label(entry.destination())),
|
||||
.child(download_entry_location_label(entry)),
|
||||
)
|
||||
.when(entry.security().requires_prompt(), |this| {
|
||||
this.child(render_security_prompt(entry.security()))
|
||||
@@ -277,6 +280,34 @@ impl ElyShell {
|
||||
)
|
||||
},
|
||||
)
|
||||
.when(
|
||||
matches!(entry.state(), DownloadState::Completed)
|
||||
&& entry.target_file_path().is_some(),
|
||||
|this| {
|
||||
let open_id = entry.id().clone();
|
||||
let reveal_id = entry.id().clone();
|
||||
|
||||
this.child(
|
||||
download_action_button("open", index, IconName::ExternalLink, "Open File")
|
||||
.on_click(cx.listener(move |shell, _, _, cx| {
|
||||
shell.open_download_file(&open_id, cx);
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
download_action_button(
|
||||
"reveal",
|
||||
index,
|
||||
IconName::FolderOpen,
|
||||
"Reveal in Finder",
|
||||
)
|
||||
.on_click(cx.listener(move |shell, _, _, cx| {
|
||||
shell.reveal_download_file(&reveal_id, cx);
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
@@ -320,6 +351,30 @@ fn download_destination_label(destination: &DownloadDestination) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn download_entry_location_label(entry: &DownloadEntry) -> String {
|
||||
match entry.target_file_path() {
|
||||
Some(path) => path.display().to_string(),
|
||||
None => download_destination_label(entry.destination()),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_download_file_error(message: String) -> AnyElement {
|
||||
div()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(rgb(colors::ERROR))
|
||||
.px_3()
|
||||
.py_2()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.text_xs()
|
||||
.text_color(rgb(colors::ERROR))
|
||||
.child(IconName::TriangleAlert)
|
||||
.child(message)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_security_prompt(security: &DownloadSecurity) -> AnyElement {
|
||||
div()
|
||||
.flex()
|
||||
|
||||
@@ -9,7 +9,7 @@ use gpui_component::input::{InputEvent, InputState, SelectAll};
|
||||
use crate::{
|
||||
CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab,
|
||||
OpenSettings, RestoreClosedTab, SelectNextTab, SelectPreviousTab, ToggleFavoriteTab,
|
||||
TogglePinnedTab,
|
||||
TogglePinnedTab, services::download_files::DownloadFileAction,
|
||||
};
|
||||
|
||||
enum ShellState {
|
||||
@@ -22,6 +22,7 @@ pub struct ElyShell {
|
||||
focus_handle: FocusHandle,
|
||||
command_input: Entity<InputState>,
|
||||
last_intent: Option<CommandIntent>,
|
||||
download_file_error: Option<String>,
|
||||
_command_subscription: Subscription,
|
||||
}
|
||||
|
||||
@@ -77,6 +78,7 @@ impl ElyShell {
|
||||
focus_handle: cx.focus_handle(),
|
||||
command_input,
|
||||
last_intent: None,
|
||||
download_file_error: None,
|
||||
_command_subscription: command_subscription,
|
||||
}
|
||||
}
|
||||
@@ -247,6 +249,32 @@ impl ElyShell {
|
||||
}
|
||||
}
|
||||
|
||||
fn open_download_file(&mut self, download_id: &DownloadId, cx: &mut Context<Self>) {
|
||||
self.run_download_file_action(download_id, DownloadFileAction::Open, cx);
|
||||
}
|
||||
|
||||
fn reveal_download_file(&mut self, download_id: &DownloadId, cx: &mut Context<Self>) {
|
||||
self.run_download_file_action(download_id, DownloadFileAction::Reveal, cx);
|
||||
}
|
||||
|
||||
fn run_download_file_action(
|
||||
&mut self,
|
||||
download_id: &DownloadId,
|
||||
action: DownloadFileAction,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let result = match &self.state {
|
||||
ShellState::Ready(core) => core
|
||||
.download_target_file_path(download_id)
|
||||
.map_err(|error| error.to_string())
|
||||
.and_then(|path| action.run(&path).map_err(|error| error.to_string())),
|
||||
ShellState::StartupError(message) => Err(message.clone()),
|
||||
};
|
||||
|
||||
self.download_file_error = result.err();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_close_current_tab(
|
||||
&mut self,
|
||||
_: &CloseCurrentTab,
|
||||
|
||||
Reference in New Issue
Block a user