Improve UX of importing vs. opening files (#3661)

* wip

* fix drag and drop

* fix

* fix tests

* fix tests

* fix warning

* Partial code review

* add dialog

* fix web

* fix web

* push back release candidate expiry

* Code review

* Reduce code duplication for pasting files in frontend

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Timon
2026-01-22 10:37:49 +01:00
committed by GitHub
parent 781fa7ae95
commit 2be7790d4d
23 changed files with 301 additions and 354 deletions

View File

@@ -1,5 +1,3 @@
use graphene_std::Color;
use graphene_std::raster::Image;
use graphite_editor::messages::clipboard::utility_types::ClipboardContentRaw;
use graphite_editor::messages::prelude::*;
@@ -14,9 +12,9 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
DesktopWrapperMessage::Input(message) => {
dispatcher.queue_editor_message(EditorMessage::InputPreprocessor(message));
}
DesktopWrapperMessage::OpenFileDialogResult { path, content, context } => match context {
OpenFileDialogContext::Document => {
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::OpenDocument { path, content });
DesktopWrapperMessage::FileDialogResult { path, content, context } => match context {
OpenFileDialogContext::Open => {
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::OpenFile { path, content });
}
OpenFileDialogContext::Import => {
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::ImportFile { path, content });
@@ -35,78 +33,11 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
}
},
DesktopWrapperMessage::OpenFile { path, content } => {
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or_default().to_lowercase();
match extension.as_str() {
"graphite" => {
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::OpenDocument { path, content });
}
_ => {
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::ImportFile { path, content });
}
}
}
DesktopWrapperMessage::OpenDocument { path, content } => {
let Ok(content) = String::from_utf8(content) else {
tracing::warn!("Document file is invalid: {}", path.display());
return;
};
let message = PortfolioMessage::OpenDocumentFile {
document_name: None,
document_path: Some(path),
document_serialized_content: content,
};
let message = PortfolioMessage::OpenFile { path, content };
dispatcher.queue_editor_message(message);
}
DesktopWrapperMessage::ImportFile { path, content } => {
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or_default().to_lowercase();
match extension.as_str() {
"svg" => {
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::ImportSvg { path, content });
}
_ => {
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::ImportImage { path, content });
}
}
}
DesktopWrapperMessage::ImportSvg { path, content } => {
let Ok(content) = String::from_utf8(content) else {
tracing::warn!("Svg file is invalid: {}", path.display());
return;
};
let message = PortfolioMessage::PasteSvg {
name: path.file_stem().map(|s| s.to_string_lossy().to_string()),
svg: content,
mouse: None,
parent_and_insert_index: None,
};
dispatcher.queue_editor_message(message);
}
DesktopWrapperMessage::ImportImage { path, content } => {
let name = path.file_stem().and_then(|s| s.to_str()).map(|s| s.to_string());
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or_default().to_lowercase();
let Some(image_format) = image::ImageFormat::from_extension(&extension) else {
tracing::warn!("Unsupported file type: {}", path.display());
return;
};
let reader = image::ImageReader::with_format(std::io::Cursor::new(content), image_format);
let Ok(image) = reader.decode() else {
tracing::error!("Failed to decode image: {}", path.display());
return;
};
let width = image.width();
let height = image.height();
// TODO: Handle Image formats with more than 8 bits per channel
let image_data = image.to_rgba8();
let image = Image::<Color>::from_image_data(image_data.as_raw(), width, height);
let message = PortfolioMessage::PasteImage {
name,
image,
mouse: None,
parent_and_insert_index: None,
};
let message = PortfolioMessage::ImportFile { path, content };
dispatcher.queue_editor_message(message);
}
DesktopWrapperMessage::PollNodeGraphEvaluation => dispatcher.poll_node_graph_evaluation(),

View File

@@ -10,29 +10,17 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
FrontendMessage::RenderOverlays { context } => {
dispatcher.respond(DesktopFrontendMessage::UpdateOverlays(context.take_scene()));
}
FrontendMessage::TriggerOpenDocument => {
FrontendMessage::TriggerOpen => {
dispatcher.respond(DesktopFrontendMessage::OpenFileDialog {
title: "Open Document".to_string(),
filters: vec![FileFilter {
name: "Graphite".to_string(),
extensions: vec!["graphite".to_string()],
}],
context: OpenFileDialogContext::Document,
filters: vec![],
context: OpenFileDialogContext::Open,
});
}
FrontendMessage::TriggerImport => {
dispatcher.respond(DesktopFrontendMessage::OpenFileDialog {
title: "Import File".to_string(),
filters: vec![
FileFilter {
name: "Svg".to_string(),
extensions: vec!["svg".to_string()],
},
FileFilter {
name: "Image".to_string(),
extensions: vec!["png".to_string(), "jpg".to_string(), "jpeg".to_string(), "bmp".to_string()],
},
],
filters: vec![],
context: OpenFileDialogContext::Import,
});
}

View File

@@ -2,6 +2,7 @@ use graph_craft::wasm_application_io::WasmApplicationIo;
use graphite_editor::application::{Editor, Environment, Host, Platform};
use graphite_editor::messages::prelude::{FrontendMessage, Message};
pub use graphite_editor::consts::FILE_EXTENSION;
// TODO: Remove usage of this reexport in desktop create and remove this line
pub use graphene_std::Color;

View File

@@ -79,7 +79,7 @@ pub enum DesktopFrontendMessage {
pub enum DesktopWrapperMessage {
FromWeb(Box<EditorMessage>),
Input(InputMessage),
OpenFileDialogResult {
FileDialogResult {
path: PathBuf,
content: Vec<u8>,
context: OpenFileDialogContext,
@@ -88,10 +88,6 @@ pub enum DesktopWrapperMessage {
path: PathBuf,
context: SaveFileDialogContext,
},
OpenDocument {
path: PathBuf,
content: Vec<u8>,
},
OpenFile {
path: PathBuf,
content: Vec<u8>,
@@ -100,14 +96,6 @@ pub enum DesktopWrapperMessage {
path: PathBuf,
content: Vec<u8>,
},
ImportSvg {
path: PathBuf,
content: Vec<u8>,
},
ImportImage {
path: PathBuf,
content: Vec<u8>,
},
PollNodeGraphEvaluation,
UpdateMaximized {
maximized: bool,
@@ -153,7 +141,7 @@ pub struct FileFilter {
}
pub enum OpenFileDialogContext {
Document,
Open,
Import,
}