From fe017aa0608870c5d7725bb7cf84d7720938e193 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Wed, 2 Sep 2026 12:00:34 +0800 Subject: [PATCH] project file: format dialog hands to the SYSTEM dialog (extension-limited) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 导出工程文件: the in-app dialog only picks the format (OTIO / OVE / FCPXML); OK asks the system save dialog with the suggested file name carrying the format's extension, then exports - 打开项目: the system open dialog restricts the choosable files to .ove/.ovexml/.otio/.fcpxml (PathPromptOptions.allowed_extensions plumbed through gpui; Linux portal glob filter) --- .github/workflows/cd.yml | 2 +- crates/oak-app/src/app.rs | 67 +++++++++++-------- crates/oak-app/src/dialogs.rs | 4 ++ crates/oak-app/src/panels/project_explorer.rs | 1 + gpui | 2 +- 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 439ae6816..579dc44d5 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,4 +1,4 @@ -name: CD +kiname: CD on: push: diff --git a/crates/oak-app/src/app.rs b/crates/oak-app/src/app.rs index 5f9b40edf..5ffdf4499 100644 --- a/crates/oak-app/src/app.rs +++ b/crates/oak-app/src/app.rs @@ -424,6 +424,8 @@ pub struct OakApp { plugin_progress_rx: Mutex>, /// The library row pending an export save dialog (manager 导出). pending_export: Option, + /// The suggested file stem for the export-project system save dialog. + export_suggested_name: String, /// The panel that most recently took focus (the target of /// [`Route::FocusedPanel`](crate::actions::Route::FocusedPanel) /// commands; `None` before any panel is focused). @@ -843,6 +845,7 @@ impl OakApp { export: None, pending_drop: None, pending_export: None, + export_suggested_name: String::new(), plugin_progress_open: false, plugin_progress_rx: Mutex::new(plugin_progress_rx), focused_panel: None, @@ -2018,6 +2021,17 @@ impl OakApp { directories: false, multiple: action == FileAction::ImportFootage, prompt: Some(prompt.into()), + allowed_extensions: if action == FileAction::Open { + // Project files only: OVE / OTIO / FCPXML. + vec![ + "ove".to_string(), + "ovexml".to_string(), + "otio".to_string(), + "fcpxml".to_string(), + ] + } else { + Vec::new() + }, }); cx.spawn(async move |this, cx| { if let Ok(Ok(Some(paths))) = receiver.await { @@ -2615,8 +2629,9 @@ impl OakApp { } /// Opens the 导出工程文件 dialog: choose the project file format - /// (OTIO / OVE / FCPXML) and the output path; the OK button runs the - /// engine's export_project_path with the picked extension. + /// (OTIO / OVE / FCPXML); the OK button then asks the SYSTEM save + /// dialog for the output path (the suggested file name carries the + /// chosen extension). fn open_export_project_dfd(&mut self, cx: &mut Context) { if !matches!(self.modal, ModalState::None) { return; @@ -2633,15 +2648,11 @@ impl OakApp { .map(|name| name.to_string_lossy().into_owned()) }) .unwrap_or_else(|| "untitled".to_string()); + self.export_suggested_name = suggested; self.spawn_modal(cx, move |window, app| { let content = app.new(|cx| { crate::dialogs::ExportProjectDialogContent::new(window, cx) }); - // Default selection is OTIO: pre-fill the pathway with the - // correct extension, the OK handler applies the chosen one. - content.update(app, |content, cx| { - content.set_path(format!("{suggested}.otio"), cx) - }); let modal = app.new(|cx| { Modal::new( modal_ids::EXPORT_PROJECT_DFD, @@ -3128,35 +3139,33 @@ impl OakApp { modal_ids::EXPORT_PROJECT_DFD => { if let ModalState::ExportProjectDfd { content, .. } = &self.modal { if *button == 0 { - // OK: export with the chosen format — the path - // gets the format's extension appended. + // OK: keep the chosen format; the SYSTEM save + // dialog then picks the output path (the + // suggested name carries the format's + // extension). let format = content.read(cx).format(cx); let ext = match format { crate::dialogs::PROJECT_FORMAT_OVE => "ove", crate::dialogs::PROJECT_FORMAT_FCPXML => "fcpxml", _ => "otio", }; - let path = content.read(cx).path(cx).to_string(); - let path = if path.to_ascii_lowercase().ends_with(".otio") - || path.to_ascii_lowercase().ends_with(".ove") - || path.to_ascii_lowercase().ends_with(".fcpxml") - { - // Stem swap: the entered name already has - // an extension — replace it. - let stem = path - .split('.') - .next() - .unwrap_or(&path) - .to_string(); - format!("{stem}.{ext}") - } else { - format!("{path}.{ext}") - }; - let engine = self.engine.clone(); - engine.update(cx, |engine, cx| { - engine.export_project_path(PathBuf::from(path), cx) - }); + let suggested = self.export_suggested_name.clone(); + let receiver = cx.prompt_for_new_path( + std::path::Path::new("."), + Some(&format!("{suggested}.{ext}")), + ); self.close_modal(cx); + cx.spawn(async move |this, cx| { + if let Ok(Ok(Some(path))) = receiver.await { + this.update(cx, |this, cx| { + let engine = this.engine.clone(); + engine.update(cx, |engine, cx| { + engine.export_project_path(path, cx) + }); + }); + } + }) + .detach(); } else { self.close_modal(cx); } diff --git a/crates/oak-app/src/dialogs.rs b/crates/oak-app/src/dialogs.rs index 87b7818ed..29b96c8dc 100644 --- a/crates/oak-app/src/dialogs.rs +++ b/crates/oak-app/src/dialogs.rs @@ -585,6 +585,7 @@ impl PreferencesContent { directories: false, multiple: false, prompt: Some(i18n::tr("preferences.color.browse").into()), + allowed_extensions: Vec::new(), }); cx.spawn(async move |this, cx| { let Ok(Ok(Some(paths))) = receiver.await else { @@ -623,6 +624,7 @@ impl PreferencesContent { directories: true, multiple: false, prompt: Some(i18n::tr("preferences.cache.browse").into()), + allowed_extensions: Vec::new(), }); cx.spawn(async move |this, cx| { let Ok(Ok(Some(paths))) = receiver.await else { @@ -2095,6 +2097,7 @@ impl Render for ProjectPropertiesContent directories: false, multiple: false, prompt: None, + allowed_extensions: Vec::new(), }); cx.spawn(async move |this, cx| { if let Ok(Ok(Some(paths))) = receiver.await { @@ -2569,6 +2572,7 @@ impl KeyboardTabContent { directories: false, multiple: false, prompt: Some(i18n::tr("preferences.keyboard.import").into()), + allowed_extensions: Vec::new(), }); cx.spawn(async move |this, cx| { let Ok(Ok(Some(paths))) = receiver.await else { diff --git a/crates/oak-app/src/panels/project_explorer.rs b/crates/oak-app/src/panels/project_explorer.rs index c78640482..6a775db20 100644 --- a/crates/oak-app/src/panels/project_explorer.rs +++ b/crates/oak-app/src/panels/project_explorer.rs @@ -142,6 +142,7 @@ impl ProjectExplorerPanel { directories: false, multiple: false, prompt: Some(crate::i18n::tr("project.context.replace_footage").into()), + allowed_extensions: Vec::new(), }); cx.spawn(async move |this, cx| { if let Ok(Ok(Some(paths))) = receiver.await { diff --git a/gpui b/gpui index 209c593da..a934be5bc 160000 --- a/gpui +++ b/gpui @@ -1 +1 @@ -Subproject commit 209c593da605f480565ffb8144edec8edc574c37 +Subproject commit a934be5bc69ba21d2649192852d8ea3502fcd50e