Desktop: Unify save file handling and add file dialog for export (#3008)
* Prepare save file unification * Desktop add save file dialog
This commit is contained in:
parent
5f2432cacf
commit
96a1b12a05
|
|
@ -2,6 +2,7 @@ use crate::CustomEvent;
|
|||
use crate::WindowSize;
|
||||
use crate::consts::APP_NAME;
|
||||
use crate::dialogs::dialog_open_graphite_file;
|
||||
use crate::dialogs::dialog_save_file;
|
||||
use crate::dialogs::dialog_save_graphite_file;
|
||||
use crate::render::GraphicsState;
|
||||
use crate::render::WgpuContext;
|
||||
|
|
@ -83,17 +84,17 @@ impl WinitApp {
|
|||
}
|
||||
|
||||
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerSaveDocument { .. })) {
|
||||
let FrontendMessage::TriggerSaveDocument { document_id, name, path, document } = message else {
|
||||
let FrontendMessage::TriggerSaveDocument { document_id, name, path, content } = message else {
|
||||
unreachable!()
|
||||
};
|
||||
if let Some(path) = path {
|
||||
let _ = std::fs::write(&path, document);
|
||||
let _ = std::fs::write(&path, content);
|
||||
} else {
|
||||
let event_loop_proxy = self.event_loop_proxy.clone();
|
||||
let _ = thread::spawn(move || {
|
||||
let path = futures::executor::block_on(dialog_save_graphite_file(name));
|
||||
if let Some(path) = path {
|
||||
if let Err(e) = std::fs::write(&path, document) {
|
||||
if let Err(e) = std::fs::write(&path, content) {
|
||||
tracing::error!("Failed to save file: {}: {}", path.display(), e);
|
||||
} else {
|
||||
let message = Message::Portfolio(PortfolioMessage::DocumentPassMessage {
|
||||
|
|
@ -107,6 +108,18 @@ impl WinitApp {
|
|||
}
|
||||
}
|
||||
|
||||
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerSaveFile { .. })) {
|
||||
let FrontendMessage::TriggerSaveFile { name, content } = message else { unreachable!() };
|
||||
let _ = thread::spawn(move || {
|
||||
let path = futures::executor::block_on(dialog_save_file(name));
|
||||
if let Some(path) = path {
|
||||
if let Err(e) = std::fs::write(&path, content) {
|
||||
tracing::error!("Failed to save file: {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerVisitLink { .. })) {
|
||||
let _ = thread::spawn(move || {
|
||||
let FrontendMessage::TriggerVisitLink { url } = message else { unreachable!() };
|
||||
|
|
|
|||
|
|
@ -20,3 +20,7 @@ pub(crate) async fn dialog_save_graphite_file(name: String) -> Option<PathBuf> {
|
|||
.await
|
||||
.map(|f| f.path().to_path_buf())
|
||||
}
|
||||
|
||||
pub(crate) async fn dialog_save_file(name: String) -> Option<PathBuf> {
|
||||
AsyncFileDialog::new().set_title("Save File").set_file_name(name).save_file().await.map(|f| f.path().to_path_buf())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,18 +68,18 @@ pub enum FrontendMessage {
|
|||
document_id: DocumentId,
|
||||
name: String,
|
||||
path: Option<PathBuf>,
|
||||
document: String,
|
||||
content: Vec<u8>,
|
||||
},
|
||||
TriggerDownloadImage {
|
||||
TriggerSaveFile {
|
||||
name: String,
|
||||
content: Vec<u8>,
|
||||
},
|
||||
TriggerExportImage {
|
||||
svg: String,
|
||||
name: String,
|
||||
mime: String,
|
||||
size: (f64, f64),
|
||||
},
|
||||
TriggerDownloadTextFile {
|
||||
document: String,
|
||||
name: String,
|
||||
},
|
||||
TriggerFetchAndOpenDocument {
|
||||
name: String,
|
||||
filename: String,
|
||||
|
|
|
|||
|
|
@ -1005,7 +1005,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
document_id,
|
||||
name,
|
||||
path: self.path.clone(),
|
||||
document: self.serialize_document(),
|
||||
content: self.serialize_document().into_bytes(),
|
||||
})
|
||||
}
|
||||
DocumentMessage::SavedDocument { path } => {
|
||||
|
|
|
|||
|
|
@ -234,11 +234,11 @@ impl NodeGraphExecutor {
|
|||
};
|
||||
|
||||
if file_type == FileType::Svg {
|
||||
responses.add(FrontendMessage::TriggerDownloadTextFile { document: svg, name });
|
||||
responses.add(FrontendMessage::TriggerSaveFile { name, content: svg.into_bytes() });
|
||||
} else {
|
||||
let mime = file_type.to_mime().to_string();
|
||||
let size = (size * scale_factor).into();
|
||||
responses.add(FrontendMessage::TriggerDownloadImage { svg, name, mime, size });
|
||||
responses.add(FrontendMessage::TriggerExportImage { svg, name, mime, size });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -798,10 +798,10 @@ export class TriggerSaveDocument extends JsMessage {
|
|||
|
||||
readonly path!: string | undefined;
|
||||
|
||||
readonly document!: string;
|
||||
readonly content!: Uint8Array;
|
||||
}
|
||||
|
||||
export class TriggerDownloadImage extends JsMessage {
|
||||
export class TriggerExportImage extends JsMessage {
|
||||
readonly svg!: string;
|
||||
|
||||
readonly name!: string;
|
||||
|
|
@ -812,10 +812,10 @@ export class TriggerDownloadImage extends JsMessage {
|
|||
readonly size!: XY;
|
||||
}
|
||||
|
||||
export class TriggerDownloadTextFile extends JsMessage {
|
||||
readonly document!: string;
|
||||
|
||||
export class TriggerSaveFile extends JsMessage {
|
||||
readonly name!: string;
|
||||
|
||||
readonly content!: Uint8Array;
|
||||
}
|
||||
|
||||
export class TriggerSavePreferences extends JsMessage {
|
||||
|
|
@ -1658,8 +1658,8 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||
SendUIMetadata,
|
||||
TriggerAboutGraphiteLocalizedCommitDate,
|
||||
TriggerSaveDocument,
|
||||
TriggerDownloadImage,
|
||||
TriggerDownloadTextFile,
|
||||
TriggerSaveFile,
|
||||
TriggerExportImage,
|
||||
TriggerFetchAndOpenDocument,
|
||||
TriggerFontLoad,
|
||||
TriggerImport,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import {
|
|||
type FrontendDocumentDetails,
|
||||
TriggerFetchAndOpenDocument,
|
||||
TriggerSaveDocument,
|
||||
TriggerDownloadImage,
|
||||
TriggerDownloadTextFile,
|
||||
TriggerExportImage,
|
||||
TriggerSaveFile,
|
||||
TriggerImport,
|
||||
TriggerOpenDocument,
|
||||
UpdateActiveDocument,
|
||||
|
|
@ -18,7 +18,7 @@ import {
|
|||
patchWidgetLayout,
|
||||
UpdateSpreadsheetLayout,
|
||||
} from "@graphite/messages";
|
||||
import { downloadFileText, downloadFileBlob, upload } from "@graphite/utility-functions/files";
|
||||
import { downloadFile, downloadFileBlob, upload } from "@graphite/utility-functions/files";
|
||||
import { extractPixelData, rasterizeSVG } from "@graphite/utility-functions/rasterization";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
|
|
@ -86,13 +86,13 @@ export function createPortfolioState(editor: Editor) {
|
|||
editor.handle.pasteImage(data.filename, new Uint8Array(imageData.data), imageData.width, imageData.height);
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerSaveDocument, (triggerSaveDocument) => {
|
||||
downloadFileText(triggerSaveDocument.name, triggerSaveDocument.document);
|
||||
downloadFile(triggerSaveDocument.name, triggerSaveDocument.content);
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerDownloadTextFile, (triggerFileDownload) => {
|
||||
downloadFileText(triggerFileDownload.name, triggerFileDownload.document);
|
||||
editor.subscriptions.subscribeJsMessage(TriggerSaveFile, (triggerFileDownload) => {
|
||||
downloadFile(triggerFileDownload.name, triggerFileDownload.content);
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerDownloadImage, async (triggerDownloadImage) => {
|
||||
const { svg, name, mime, size } = triggerDownloadImage;
|
||||
editor.subscriptions.subscribeJsMessage(TriggerExportImage, async (TriggerExportImage) => {
|
||||
const { svg, name, mime, size } = TriggerExportImage;
|
||||
|
||||
// Fill the canvas with white if it'll be a JPEG (which does not support transparency and defaults to black)
|
||||
const backgroundColor = mime.endsWith("jpeg") ? "white" : undefined;
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ export function downloadFileBlob(filename: string, blob: Blob) {
|
|||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export function downloadFileText(filename: string, text: string) {
|
||||
const type = filename.endsWith(".svg") ? "image/svg+xml;charset=utf-8" : "text/plain;charset=utf-8";
|
||||
export function downloadFile(filename: string, content: Uint8Array) {
|
||||
const type = filename.endsWith(".svg") ? "image/svg+xml;charset=utf-8" : "application/octet-stream";
|
||||
|
||||
const blob = new Blob([text], { type });
|
||||
const blob = new Blob([content], { type });
|
||||
downloadFileBlob(filename, blob);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue