diff --git a/Cargo.lock b/Cargo.lock index 74dee62c16..4c6c34043d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -688,6 +688,7 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-http", "tauri-plugin-log", + "tauri-plugin-opener", "tauri-plugin-upload", "tempdir", "tempfile", @@ -805,6 +806,30 @@ dependencies = [ "zstd-safe 7.1.0", ] +[[package]] +name = "async-executor" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.3.0", + "futures-lite 2.6.0", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite 2.6.0", +] + [[package]] name = "async-io" version = "2.4.0" @@ -5780,6 +5805,25 @@ dependencies = [ "serde", ] +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -7561,6 +7605,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "open" +version = "5.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "openai_sora_client" version = "0.0.1" @@ -7935,6 +7991,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "pbkdf2" version = "0.11.0" @@ -11645,6 +11707,28 @@ dependencies = [ "time", ] +[[package]] +name = "tauri-plugin-opener" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d75fdc9ba0391eafb3337aac9d1b34fd353601dd5d4e7498820733f738991e7" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "open", + "schemars", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "url", + "windows 0.58.0", + "zbus", +] + [[package]] name = "tauri-plugin-upload" version = "2.3.1" @@ -13465,8 +13549,8 @@ dependencies = [ "webview2-com-sys", "windows 0.61.3", "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "windows-implement 0.60.0", + "windows-interface 0.59.1", ] [[package]] @@ -13575,6 +13659,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.61.3" @@ -13615,17 +13709,30 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.60.0", + "windows-interface 0.59.1", "windows-link", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -13639,6 +13746,17 @@ dependencies = [ "windows-threading", ] +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "windows-implement" version = "0.60.0" @@ -13650,6 +13768,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "windows-interface" version = "0.59.1" @@ -13684,8 +13813,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" dependencies = [ "windows-link", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -13697,6 +13835,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -14409,9 +14557,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9ff46f2a25abd690ed072054733e0bc3157e3d4c45f41bd183dce09c2ff8ab9" dependencies = [ "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", "async-process", "async-recursion", + "async-task", "async-trait", + "blocking", "derivative", "enumflags2", "event-listener 5.2.0", diff --git a/crates/desktop/artcraft/Cargo.toml b/crates/desktop/artcraft/Cargo.toml index 59f8741d92..bb48f67f6c 100644 --- a/crates/desktop/artcraft/Cargo.toml +++ b/crates/desktop/artcraft/Cargo.toml @@ -81,6 +81,7 @@ tauri-plugin-http = { path = "../../vendor/tauri-plugin-http" } # NB: The version of the `discord-rich-presence` crate (0.2.5) last published has non-Send/Sync error # types and can't do threading. We need to wait for a new version to unpin from Git. discord-rich-presence = { git = "https://github.com/vionya/discord-rich-presence" } +tauri-plugin-opener = "2" # # Transparent title bar on MacOS # # NB: See the notes here: diff --git a/crates/desktop/artcraft/capabilities/default.json b/crates/desktop/artcraft/capabilities/default.json index a7b3380d5f..e0a5a74fd1 100644 --- a/crates/desktop/artcraft/capabilities/default.json +++ b/crates/desktop/artcraft/capabilities/default.json @@ -2,7 +2,9 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "enables the default permissions", - "windows": ["main"], + "windows": [ + "main" + ], "permissions": [ "core:default", "core:window:allow-close", @@ -25,6 +27,7 @@ "url": "http://localhost:12345" } ] - } + }, + "opener:default" ] -} +} \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/artcraft_error.rs b/crates/desktop/artcraft/src/core/artcraft_error.rs index 065b786e78..779cc334f4 100644 --- a/crates/desktop/artcraft/src/core/artcraft_error.rs +++ b/crates/desktop/artcraft/src/core/artcraft_error.rs @@ -1,6 +1,6 @@ use grok_client::error::grok_error::GrokError; use std::error::Error; -use std::fmt::{write, Display, Formatter}; +use std::fmt::{Display, Formatter}; use std::path::PathBuf; use storyteller_client::error::storyteller_error::StorytellerError; diff --git a/crates/desktop/artcraft/src/core/commands/download/download_directory_reveal_command.rs b/crates/desktop/artcraft/src/core/commands/download/download_directory_reveal_command.rs new file mode 100644 index 0000000000..750bdc5553 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/download/download_directory_reveal_command.rs @@ -0,0 +1,70 @@ +use crate::core::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio; +use crate::core::artcraft_error::ArtcraftError; +use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; +use crate::core::commands::response::shorthand::{Response, ResponseOrErrorMessage, ResponseOrErrorType}; +use crate::core::commands::response::success_response_wrapper::SerializeMarker; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::core::state::app_preferences::app_preferences::AppPreferences; +use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager; +use crate::core::state::data_dir::app_data_root::AppDataRoot; +use crate::core::utils::download_url_to_temp_dir::download_url_to_temp_dir; +use crate::core::utils::download_url_to_user_download_dir::download_url_to_user_download_dir; +use anyhow::anyhow; +use log::{error, info}; +use serde_derive::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::str::FromStr; +use storyteller_client::endpoints::media_files::get_media_file::get_media_file; +use tauri::{AppHandle, State}; +use tauri_plugin_opener::OpenerExt; +use tokens::tokens::media_files::MediaFileToken; + +#[derive(Serialize)] +pub struct DownloadDirectoryRevealSuccessResponse { +} + +impl SerializeMarker for DownloadDirectoryRevealSuccessResponse {} + +#[tauri::command] +pub async fn download_directory_reveal_command( + app: AppHandle, + app_prefs: State<'_, AppPreferencesManager>, + app_data_root: State<'_, AppDataRoot>, +) -> ResponseOrErrorMessage { + + info!("download_directory_reveal_command called"); + + let result = handle_request( + &app, + &app_prefs, + &app_data_root, + ).await; + + if let Err(err) = result { + format!("Error revealing download dir: {:?}", err); + return Err("error revealing download dir".into()) + } + + Ok(DownloadDirectoryRevealSuccessResponse {}.into()) +} + + +pub async fn handle_request( + app: &AppHandle, + app_prefs: &AppPreferencesManager, + app_data_root: &AppDataRoot, +) -> Result<(), ArtcraftError> { + + let app_prefs = app_prefs.get_clone()?; + + let download_directory = app_prefs + .preferred_download_directory + .download_directory(app_data_root); + + info!("Revealing item in directory: {:?}", download_directory); + + app.opener().reveal_item_in_dir(download_directory) + .map_err(|err| anyhow!("Failed to open directory: {:?}", err))?; + + Ok(()) +} diff --git a/crates/desktop/artcraft/src/core/commands/download/mod.rs b/crates/desktop/artcraft/src/core/commands/download/mod.rs index 281551f501..ad9c6330d3 100644 --- a/crates/desktop/artcraft/src/core/commands/download/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/download/mod.rs @@ -1,2 +1,3 @@ +pub mod download_directory_reveal_command; pub mod download_media_file_command; pub mod download_url_command; diff --git a/crates/desktop/artcraft/src/lib.rs b/crates/desktop/artcraft/src/lib.rs index 8845367e4f..87125cf8ef 100644 --- a/crates/desktop/artcraft/src/lib.rs +++ b/crates/desktop/artcraft/src/lib.rs @@ -5,6 +5,7 @@ use tauri::Manager; use crate::core::commands::app_preferences::get_app_preferences_command::get_app_preferences_command; use crate::core::commands::app_preferences::update_app_preference_command::update_app_preferences_command; +use crate::core::commands::download::download_directory_reveal_command::download_directory_reveal_command; use crate::core::commands::download::download_media_file_command::download_media_file_command; use crate::core::commands::download::download_url_command::download_url_command; use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::enqueue_image_bg_removal_command; @@ -129,6 +130,7 @@ pub fn run() { let builder = tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_http::init()) + .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_upload::init()) .setup(move |app| { // TODO(bt): This is broken on windows @@ -192,6 +194,7 @@ pub fn run() { // My first attempt at naively doing this didn't work because the macros can't find their codegen'd targets. let builder = builder.invoke_handler(tauri::generate_handler![ check_sora_session_command, + download_directory_reveal_command, download_media_file_command, download_url_command, enqueue_edit_image_command, @@ -226,10 +229,10 @@ pub fn run() { storyteller_open_customer_portal_cancel_plan_command, storyteller_open_customer_portal_manage_plan_command, storyteller_open_customer_portal_switch_plan_command, - tasks_nuke_all_command, storyteller_open_customer_portal_update_payment_method_command, storyteller_open_subscription_purchase_command, storyteller_purge_credentials_command, + tasks_nuke_all_command, update_app_preferences_command, worldlabs_clear_credentials_command, worldlabs_get_credential_info_command, diff --git a/frontend/libs/components/settings-modal/src/lib/panes/MiscSettingsPane.tsx b/frontend/libs/components/settings-modal/src/lib/panes/MiscSettingsPane.tsx index eb3cdaf95c..d6873046ad 100644 --- a/frontend/libs/components/settings-modal/src/lib/panes/MiscSettingsPane.tsx +++ b/frontend/libs/components/settings-modal/src/lib/panes/MiscSettingsPane.tsx @@ -9,6 +9,9 @@ import { import { PreferenceName, UpdateAppPreferences } from "@storyteller/tauri-api"; import { open } from "@tauri-apps/plugin-dialog"; import { Label } from "@storyteller/ui-label"; +import { DownloadDirectoryReveal } from "@storyteller/tauri-api"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFolder, faMagnifyingGlass, faRotateLeft } from "@fortawesome/pro-solid-svg-icons"; interface MiscSettingsPaneProps {} @@ -71,6 +74,10 @@ export const MiscSettingsPane = (args: MiscSettingsPaneProps) => { await reloadPreferences(); }; + const showDirectory = async () => { + await DownloadDirectoryReveal(); + }; + return (
@@ -85,11 +92,17 @@ export const MiscSettingsPane = (args: MiscSettingsPaneProps) => {
- +
); diff --git a/frontend/libs/tauri-api/src/index.ts b/frontend/libs/tauri-api/src/index.ts index ddbfa114f1..0e90142e0e 100644 --- a/frontend/libs/tauri-api/src/index.ts +++ b/frontend/libs/tauri-api/src/index.ts @@ -5,6 +5,7 @@ export * from "./lib/artcraft/task_queue/GetTaskQueue.js"; export * from "./lib/artcraft/task_queue/MarkTaskAsDismissed.js"; export * from "./lib/artcraft/task_queue/TasksNukeAll.js"; export * from "./lib/common/CommandStatus.js"; +export * from "./lib/download/DownloadDirectoryReveal.js"; export * from "./lib/download/DownloadUrl.js"; export * from "./lib/enqueue/EnqueueEditImage.js"; export * from "./lib/enqueue/EnqueueImageBgRemovalCommand.js"; diff --git a/frontend/libs/tauri-api/src/lib/download/DownloadDirectoryReveal.ts b/frontend/libs/tauri-api/src/lib/download/DownloadDirectoryReveal.ts new file mode 100644 index 0000000000..d794374fed --- /dev/null +++ b/frontend/libs/tauri-api/src/lib/download/DownloadDirectoryReveal.ts @@ -0,0 +1,17 @@ +import { invoke } from "@tauri-apps/api/core"; +import { CommandResult } from "../common/CommandStatus"; + +export interface DownloadDirectoryRevealSuccess extends CommandResult { + payload: DownloadDirectoryRevealPayload; +} + +export interface DownloadDirectoryRevealPayload { +} + +export const DownloadDirectoryReveal = async () : Promise => { + try { + return await invoke("download_directory_reveal_command") as DownloadDirectoryRevealSuccess; + } catch (error) { + throw error; + } +}