mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Code review fixes
This commit is contained in:
@@ -31,10 +31,15 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
|
||||
SaveFileDialogContext::File { content } => {
|
||||
dispatcher.respond(DesktopFrontendMessage::WriteFile { path, content });
|
||||
}
|
||||
SaveFileDialogContext::MultipleFiles { files } => {
|
||||
// Treat the chosen path as the folder name; strip any extension the user typed (e.g. "MyAnim.png" → "MyAnim").
|
||||
SaveFileDialogContext::MultipleFiles { files, expected_extension } => {
|
||||
// Treat the chosen path as the folder name. Strip only the export's expected extension (e.g. ".png" for a
|
||||
// PNG animation export) so that arbitrary dotted folder names like `v1.0` are preserved as-is, while a user
|
||||
// who typed `MyAnim.png` still gets a `MyAnim/` folder rather than a `MyAnim.png/` folder.
|
||||
// The `WriteFile` handler creates parent directories if they don't exist, so the folder is materialized on first write.
|
||||
let folder = path.with_extension("");
|
||||
let folder = match path.extension().and_then(|e| e.to_str()) {
|
||||
Some(ext) if ext.eq_ignore_ascii_case(&expected_extension) => path.with_extension(""),
|
||||
_ => path,
|
||||
};
|
||||
for (filename, content) in files {
|
||||
let file_path = folder.join(&filename);
|
||||
dispatcher.respond(DesktopFrontendMessage::WriteFile { path: file_path, content });
|
||||
|
||||
@@ -71,10 +71,16 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
||||
// Materialize each frame to bytes; SVG strings are encoded as UTF-8.
|
||||
// Raster-needs-canvas-rasterize frames can't be encoded here without a Rust SVG rasterizer,
|
||||
// so fall through to the frontend zip path in that case.
|
||||
// TODO: This fallback is inconsistent with the desktop folder-save flow for the rest of the animation
|
||||
// export — desktop users will see a .zip download instead of a folder. Once SVG rasterization moves to
|
||||
// Rust (resvg), this fallback can be removed and all frame paths can save into the chosen folder.
|
||||
let mut needs_frontend_rasterization = false;
|
||||
let mut materialized = Vec::with_capacity(frames.len());
|
||||
// Dynamic zero-pad width so files keep sorting in playback order beyond 9,999 frames.
|
||||
let pad_width = frames.len().to_string().len().max(4);
|
||||
let safe_base = graphite_editor::messages::frontend::utility_types::sanitize_filename_component(&name);
|
||||
for (index, frame) in frames.iter().enumerate() {
|
||||
let filename = format!("{name}_{:04}.{extension}", index + 1);
|
||||
let filename = format!("{safe_base}_{:0pad$}.{extension}", index + 1, pad = pad_width);
|
||||
let bytes = match frame {
|
||||
ExportAnimationFrame::Svg(svg) if extension == "svg" => svg.as_bytes().to_vec(),
|
||||
ExportAnimationFrame::Bytes(bytes) => bytes.to_vec(),
|
||||
@@ -100,10 +106,13 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
||||
// The dialog name is the folder the frames go into (analogous to the .zip on web).
|
||||
dispatcher.respond(DesktopFrontendMessage::SaveFileDialog {
|
||||
title: "Save Animation Frames Folder As".to_string(),
|
||||
default_filename: name.clone(),
|
||||
default_filename: safe_base,
|
||||
default_folder: folder,
|
||||
filters: Vec::new(),
|
||||
context: SaveFileDialogContext::MultipleFiles { files: materialized },
|
||||
context: SaveFileDialogContext::MultipleFiles {
|
||||
files: materialized,
|
||||
expected_extension: extension,
|
||||
},
|
||||
});
|
||||
}
|
||||
FrontendMessage::TriggerVisitLink { url } => {
|
||||
|
||||
@@ -118,11 +118,13 @@ pub enum SaveFileDialogContext {
|
||||
File {
|
||||
content: Vec<u8>,
|
||||
},
|
||||
/// Multiple files written into a folder whose path is the user-chosen path with any extension stripped
|
||||
/// (e.g. picking `MyAnim.png` yields a `MyAnim/` folder). Each `(filename, content)` entry is written
|
||||
/// Multiple files written into a folder whose path is the user-chosen path with the matching `expected_extension`
|
||||
/// stripped (e.g. for a PNG animation export, picking `MyAnim.png` yields a `MyAnim/` folder, while picking `v1.0`
|
||||
/// is preserved as `v1.0/` because `.0` isn't the expected extension). Each `(filename, content)` entry is written
|
||||
/// inside that folder, which is created if it doesn't exist.
|
||||
MultipleFiles {
|
||||
files: Vec<(String, Vec<u8>)>,
|
||||
expected_extension: String,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user