Skip to content

Commit 646dead

Browse files
Add native save file dialog
1 parent 54add78 commit 646dead

7 files changed

Lines changed: 81 additions & 4 deletions

File tree

desktop/src/app.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::CustomEvent;
22
use crate::WindowSize;
33
use crate::dialogs::dialog_open_graphite_file;
4+
use crate::dialogs::dialog_save_graphite_file;
45
use crate::render::GraphicsState;
56
use crate::render::WgpuContext;
67
use graph_craft::wasm_application_io::WasmApplicationIo;
@@ -80,6 +81,31 @@ impl WinitApp {
8081
});
8182
}
8283

84+
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerSaveDocument { .. })) {
85+
let FrontendMessage::TriggerSaveDocument { document_id, name, path, document } = message else {
86+
unreachable!()
87+
};
88+
if let Some(path) = path {
89+
let _ = std::fs::write(&path, document);
90+
} else {
91+
let event_loop_proxy = self.event_loop_proxy.clone();
92+
let _ = thread::spawn(move || {
93+
let path = futures::executor::block_on(dialog_save_graphite_file(name));
94+
if let Some(path) = path {
95+
if let Err(e) = std::fs::write(&path, document) {
96+
tracing::error!("Failed to save file: {}: {}", path.display(), e);
97+
} else {
98+
let message = Message::Portfolio(PortfolioMessage::DocumentPassMessage {
99+
document_id,
100+
message: DocumentMessage::SavedDocument { path: Some(path) },
101+
});
102+
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message));
103+
}
104+
}
105+
});
106+
}
107+
}
108+
83109
if responses.is_empty() {
84110
return;
85111
}

desktop/src/dialogs.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@ use std::path::PathBuf;
33
use rfd::AsyncFileDialog;
44

55
pub(crate) async fn dialog_open_graphite_file() -> Option<PathBuf> {
6-
let file = AsyncFileDialog::new().add_filter("Graphite", &["graphite"]).set_title("Open Graphite Document").pick_file().await;
7-
file.map(|f| f.path().to_path_buf())
6+
AsyncFileDialog::new()
7+
.add_filter("Graphite", &["graphite"])
8+
.set_title("Open Graphite Document")
9+
.pick_file()
10+
.await
11+
.map(|f| f.path().to_path_buf())
12+
}
13+
14+
pub(crate) async fn dialog_save_graphite_file(name: String) -> Option<PathBuf> {
15+
AsyncFileDialog::new()
16+
.add_filter("Graphite", &["graphite"])
17+
.set_title("Save Graphite Document")
18+
.set_file_name(name)
19+
.save_file()
20+
.await
21+
.map(|f| f.path().to_path_buf())
822
}

editor/src/messages/frontend/frontend_message.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use graph_craft::document::NodeId;
1212
use graphene_std::raster::Image;
1313
use graphene_std::raster::color::Color;
1414
use graphene_std::text::{Font, TextAlign};
15+
use std::path::PathBuf;
1516

1617
#[cfg(not(target_family = "wasm"))]
1718
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
@@ -63,6 +64,12 @@ pub enum FrontendMessage {
6364
#[serde(rename = "commitDate")]
6465
commit_date: String,
6566
},
67+
TriggerSaveDocument {
68+
document_id: DocumentId,
69+
name: String,
70+
path: Option<PathBuf>,
71+
document: String,
72+
},
6673
TriggerDownloadImage {
6774
svg: String,
6875
name: String,

editor/src/messages/portfolio/document/document_message.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::path::PathBuf;
2+
13
use super::utility_types::misc::{GroupFolderType, SnappingState};
24
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
35
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
@@ -105,6 +107,9 @@ pub enum DocumentMessage {
105107
RenderRulers,
106108
RenderScrollbars,
107109
SaveDocument,
110+
SavedDocument {
111+
path: Option<PathBuf>,
112+
},
108113
SelectParentLayer,
109114
SelectAllLayers,
110115
SelectedLayersLower,

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ use graphene_std::table::Table;
3737
use graphene_std::vector::PointId;
3838
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
3939
use graphene_std::vector::style::ViewMode;
40+
use std::path::PathBuf;
4041
use std::time::Duration;
4142

4243
#[derive(ExtractField)]
@@ -115,6 +116,9 @@ pub struct DocumentMessageHandler {
115116
/// Stack of document network snapshots for future history states.
116117
#[serde(skip)]
117118
document_redo_history: VecDeque<NodeNetworkInterface>,
119+
/// The path of the to the document file.
120+
#[serde(skip)]
121+
path: Option<PathBuf>,
118122
/// Hash of the document snapshot that was most recently saved to disk by the user.
119123
#[serde(skip)]
120124
saved_hash: Option<u64>,
@@ -162,6 +166,7 @@ impl Default for DocumentMessageHandler {
162166
selection_network_path: Vec::new(),
163167
document_undo_history: VecDeque::new(),
164168
document_redo_history: VecDeque::new(),
169+
path: None,
165170
saved_hash: None,
166171
auto_saved_hash: None,
167172
layer_range_selection_reference: None,
@@ -996,11 +1001,16 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
9961001
true => self.name.clone(),
9971002
false => self.name.clone() + FILE_SAVE_SUFFIX,
9981003
};
999-
responses.add(FrontendMessage::TriggerDownloadTextFile {
1000-
document: self.serialize_document(),
1004+
responses.add(FrontendMessage::TriggerSaveDocument {
1005+
document_id,
10011006
name,
1007+
path: self.path.clone(),
1008+
document: self.serialize_document(),
10021009
})
10031010
}
1011+
DocumentMessage::SavedDocument { path } => {
1012+
self.path = path;
1013+
}
10041014
DocumentMessage::SelectParentLayer => {
10051015
let selected_nodes = self.network_interface.selected_nodes();
10061016
let selected_layers = selected_nodes.selected_layers(self.metadata());

frontend/src/messages.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,16 @@ export class TriggerImport extends JsMessage {}
791791

792792
export class TriggerPaste extends JsMessage {}
793793

794+
export class TriggerSaveDocument extends JsMessage {
795+
readonly documentId!: bigint;
796+
797+
readonly name!: string;
798+
799+
readonly path!: string | undefined;
800+
801+
readonly document!: string;
802+
}
803+
794804
export class TriggerDownloadImage extends JsMessage {
795805
readonly svg!: string;
796806

@@ -1647,6 +1657,7 @@ export const messageMakers: Record<string, MessageMaker> = {
16471657
DisplayRemoveEditableTextbox,
16481658
SendUIMetadata,
16491659
TriggerAboutGraphiteLocalizedCommitDate,
1660+
TriggerSaveDocument,
16501661
TriggerDownloadImage,
16511662
TriggerDownloadTextFile,
16521663
TriggerFetchAndOpenDocument,

frontend/src/state-providers/portfolio.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { type Editor } from "@graphite/editor";
66
import {
77
type FrontendDocumentDetails,
88
TriggerFetchAndOpenDocument,
9+
TriggerSaveDocument,
910
TriggerDownloadImage,
1011
TriggerDownloadTextFile,
1112
TriggerImport,
@@ -84,6 +85,9 @@ export function createPortfolioState(editor: Editor) {
8485
const imageData = await extractPixelData(new Blob([data.content.data], { type: data.type }));
8586
editor.handle.pasteImage(data.filename, new Uint8Array(imageData.data), imageData.width, imageData.height);
8687
});
88+
editor.subscriptions.subscribeJsMessage(TriggerSaveDocument, (triggerSaveDocument) => {
89+
downloadFileText(triggerSaveDocument.name, triggerSaveDocument.document);
90+
});
8791
editor.subscriptions.subscribeJsMessage(TriggerDownloadTextFile, (triggerFileDownload) => {
8892
downloadFileText(triggerFileDownload.name, triggerFileDownload.document);
8993
});

0 commit comments

Comments
 (0)