Move file filter creation from frontend to editor (#4351)

This commit is contained in:
Timon
2026-08-28 11:19:44 +02:00
committed by GitHub
parent c8f38059f8
commit c2afc07a04
11 changed files with 98 additions and 50 deletions

View File

@@ -1,7 +1,7 @@
use super::IconName;
use super::utility_types::{MouseCursorIcon, PersistedState};
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
use crate::messages::frontend::utility_types::{DocumentInfo, EyedropperPreviewImage, RasterizedImage};
use crate::messages::frontend::utility_types::{DocumentInfo, EyedropperPreviewImage, FileFilter, RasterizedImage};
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{
@@ -87,16 +87,24 @@ pub enum FrontendMessage {
commit_date: String,
},
TriggerDisplayThirdPartyLicensesDialog,
TriggerOpen {
filters: Vec<FileFilter>,
},
TriggerImport {
filters: Vec<FileFilter>,
},
TriggerSaveDocument {
document_id: DocumentId,
name: String,
path: Option<PathBuf>,
folder: Option<PathBuf>,
filters: Vec<FileFilter>,
content: serde_bytes::ByteBuf,
},
TriggerSaveFile {
name: String,
folder: Option<PathBuf>,
filters: Vec<FileFilter>,
content: serde_bytes::ByteBuf,
},
TriggerExportImage {
@@ -128,8 +136,6 @@ pub enum FrontendMessage {
},
TriggerOpenLaunchDocuments,
TriggerLoadPreferences,
TriggerOpen,
TriggerImport,
TriggerSavePreferences {
#[cfg_attr(feature = "wasm", tsify(type = "unknown"))]
preferences: PreferencesMessageHandler,

View File

@@ -64,6 +64,26 @@ impl FileType {
FileType::Svg => "image/svg+xml",
}
}
pub fn extension(self) -> &'static str {
match self {
FileType::Png => "png",
FileType::Jpg => "jpg",
FileType::Svg => "svg",
}
}
pub fn file_filter(self) -> FileFilter {
let name = match self {
FileType::Png => "PNG Image",
FileType::Jpg => "JPEG Image",
FileType::Svg => "SVG Image",
};
FileFilter {
name: name.into(),
extensions: vec![self.extension().into()],
}
}
}
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
@@ -91,3 +111,10 @@ pub struct RasterizedImage {
pub height: u32,
pub pixels: serde_bytes::ByteBuf,
}
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct FileFilter {
pub name: String,
pub extensions: Vec<String>,
}

View File

@@ -10,6 +10,7 @@ use crate::consts::{
ASYMPTOTIC_EFFECT, BLEND_COUNT_PER_LAYER, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, GDD_FILE_EXTENSION, LAYER_INDENT_OFFSET, NODE_CHAIN_WIDTH, SCALE_EFFECT, SCROLLBAR_SPACING,
VIEWPORT_ROTATE_SNAP_INTERVAL,
};
use crate::messages::frontend::utility_types::FileFilter;
use crate::messages::input_mapper::utility_types::macros::action_shortcut;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::data_panel::{DataPanelMessageContext, DataPanelMessageHandler};
@@ -1089,6 +1090,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
name,
path,
folder,
filters: vec![FileFilter {
name: "Graphite Document".into(),
extensions: vec![extension.into()],
}],
content: content.into(),
})
});

View File

@@ -5,7 +5,7 @@ use crate::application::{Editor, generate_uuid};
use crate::consts::{DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, GDD_FILE_EXTENSION};
use crate::messages::animation::TimingInformation;
use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::{DocumentInfo, PersistedState};
use crate::messages::frontend::utility_types::{DocumentInfo, FileFilter, PersistedState};
use crate::messages::input_mapper::utility_types::keyboard::Key;
use crate::messages::input_mapper::utility_types::macros::{action_shortcut, action_shortcut_manual};
use crate::messages::layout::utility_types::widget_prelude::*;
@@ -501,6 +501,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(FrontendMessage::TriggerSaveFile {
name: filename,
folder: None,
filters: vec![FileFilter {
name: "Graphite Document".into(),
extensions: vec![FILE_EXTENSION.into()],
}],
content: serde_bytes::ByteBuf::from(content),
});
} else {
@@ -508,6 +512,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
Ok(zip_bytes) => responses.add(FrontendMessage::TriggerSaveFile {
name: format!("{FOLDER_NAME}.zip"),
folder: None,
filters: vec![FileFilter {
name: "Zip Archive".into(),
extensions: vec!["zip".into()],
}],
content: serde_bytes::ByteBuf::from(zip_bytes),
}),
Err(e) => {
@@ -652,11 +660,28 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
PortfolioMessage::Open => {
// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages
responses.add(FrontendMessage::TriggerOpen);
responses.add(FrontendMessage::TriggerOpen {
filters: vec![
FileFilter {
name: "Graphite Document".into(),
extensions: vec![FILE_EXTENSION.into(), GDD_FILE_EXTENSION.into()],
},
FileFilter {
name: "Image".into(),
extensions: vec!["svg".into(), "png".into(), "jpg".into(), "jpeg".into(), "bmp".into(), "gif".into()],
},
],
});
}
PortfolioMessage::Import => {
// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages
responses.add(FrontendMessage::TriggerImport);
// TODO: Also offer the Graphite document filter once importing Graphite documents as nodes is supported
responses.add(FrontendMessage::TriggerImport {
filters: vec![FileFilter {
name: "Image".into(),
extensions: vec!["svg".into(), "png".into(), "jpg".into(), "jpeg".into(), "bmp".into(), "gif".into()],
}],
});
}
PortfolioMessage::OpenFile { path, content } => {
let name = path.file_stem().map(|n| n.to_string_lossy().to_string());

View File

@@ -730,17 +730,13 @@ impl NodeGraphExecutor {
..
} = export_config;
let file_extension = match file_type {
FileType::Svg => "svg",
FileType::Png => "png",
FileType::Jpg => "jpg",
};
let base_name = match (artboard_name, artboard_count) {
(Some(artboard_name), count) if count > 1 => format!("{name} - {artboard_name}"),
_ => name,
};
let name = format!("{base_name}.{file_extension}");
let name = format!("{base_name}.{}", file_type.extension());
let folder = document.path.as_ref().and_then(|path| path.parent()).map(|parent| parent.to_path_buf());
let filters = vec![file_type.file_filter()];
match node_graph_output {
TaggedValue::RenderOutput(RenderOutput {
@@ -751,6 +747,7 @@ impl NodeGraphExecutor {
responses.add(FrontendMessage::TriggerSaveFile {
name,
folder,
filters,
content: svg.into_bytes().into(),
});
} else {
@@ -804,6 +801,7 @@ impl NodeGraphExecutor {
responses.add(FrontendMessage::TriggerSaveFile {
name,
folder,
filters,
content: encoded.into(),
});
}