mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-03 01:58:11 +08:00
Desktop: Move autosave persistence to native (#3134)
* Move autosave persistence to native 1 * Move autosave persistence to native 2 * Reimplement quirky behavior of the web frontend * Code revew * Use select_after_open * fix fmt --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Generated
+2
@@ -2271,6 +2271,7 @@ dependencies = [
|
|||||||
"open",
|
"open",
|
||||||
"rfd",
|
"rfd",
|
||||||
"ron",
|
"ron",
|
||||||
|
"serde",
|
||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -2298,6 +2299,7 @@ dependencies = [
|
|||||||
"graphite-editor",
|
"graphite-editor",
|
||||||
"image",
|
"image",
|
||||||
"ron",
|
"ron",
|
||||||
|
"serde",
|
||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
"tracing",
|
"tracing",
|
||||||
"vello",
|
"vello",
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ vello = { workspace = true }
|
|||||||
derivative = { workspace = true }
|
derivative = { workspace = true }
|
||||||
rfd = { workspace = true }
|
rfd = { workspace = true }
|
||||||
open = { workspace = true }
|
open = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
|
||||||
# Hardware acceleration dependencies
|
# Hardware acceleration dependencies
|
||||||
ash = { version = "0.38", optional = true }
|
ash = { version = "0.38", optional = true }
|
||||||
|
|||||||
+54
-1
@@ -1,6 +1,7 @@
|
|||||||
use crate::CustomEvent;
|
use crate::CustomEvent;
|
||||||
use crate::cef::WindowSize;
|
use crate::cef::WindowSize;
|
||||||
use crate::consts::{APP_NAME, CEF_MESSAGE_LOOP_MAX_ITERATIONS};
|
use crate::consts::{APP_NAME, CEF_MESSAGE_LOOP_MAX_ITERATIONS};
|
||||||
|
use crate::persist::PersistentData;
|
||||||
use crate::render::GraphicsState;
|
use crate::render::GraphicsState;
|
||||||
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform};
|
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform};
|
||||||
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
||||||
@@ -37,6 +38,7 @@ pub(crate) struct WinitApp {
|
|||||||
start_render_sender: SyncSender<()>,
|
start_render_sender: SyncSender<()>,
|
||||||
web_communication_initialized: bool,
|
web_communication_initialized: bool,
|
||||||
web_communication_startup_buffer: Vec<Vec<u8>>,
|
web_communication_startup_buffer: Vec<Vec<u8>>,
|
||||||
|
persistent_data: PersistentData,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WinitApp {
|
impl WinitApp {
|
||||||
@@ -51,6 +53,9 @@ impl WinitApp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mut persistent_data = PersistentData::default();
|
||||||
|
persistent_data.load_from_disk();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
cef_context,
|
cef_context,
|
||||||
window: None,
|
window: None,
|
||||||
@@ -65,6 +70,7 @@ impl WinitApp {
|
|||||||
start_render_sender,
|
start_render_sender,
|
||||||
web_communication_initialized: false,
|
web_communication_initialized: false,
|
||||||
web_communication_startup_buffer: Vec::new(),
|
web_communication_startup_buffer: Vec::new(),
|
||||||
|
persistent_data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +167,53 @@ impl WinitApp {
|
|||||||
DesktopFrontendMessage::CloseWindow => {
|
DesktopFrontendMessage::CloseWindow => {
|
||||||
let _ = self.event_loop_proxy.send_event(CustomEvent::CloseWindow);
|
let _ = self.event_loop_proxy.send_event(CustomEvent::CloseWindow);
|
||||||
}
|
}
|
||||||
|
DesktopFrontendMessage::PersistenceWriteDocument { id, document } => {
|
||||||
|
self.persistent_data.write_document(id, document);
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::PersistenceDeleteDocument { id } => {
|
||||||
|
self.persistent_data.delete_document(&id);
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::PersistenceUpdateCurrentDocument { id } => {
|
||||||
|
self.persistent_data.set_current_document(id);
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::PersistenceUpdateDocumentsList { ids } => {
|
||||||
|
self.persistent_data.set_document_order(ids);
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::PersistenceLoadCurrentDocument => {
|
||||||
|
if let Some((id, document)) = self.persistent_data.current_document() {
|
||||||
|
let message = DesktopWrapperMessage::LoadDocument {
|
||||||
|
id,
|
||||||
|
document,
|
||||||
|
to_front: false,
|
||||||
|
select_after_open: true,
|
||||||
|
};
|
||||||
|
self.dispatch_desktop_wrapper_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::PersistenceLoadRemainingDocuments => {
|
||||||
|
for (id, document) in self.persistent_data.documents_before_current().into_iter().rev() {
|
||||||
|
let message = DesktopWrapperMessage::LoadDocument {
|
||||||
|
id,
|
||||||
|
document,
|
||||||
|
to_front: true,
|
||||||
|
select_after_open: false,
|
||||||
|
};
|
||||||
|
self.dispatch_desktop_wrapper_message(message);
|
||||||
|
}
|
||||||
|
for (id, document) in self.persistent_data.documents_after_current() {
|
||||||
|
let message = DesktopWrapperMessage::LoadDocument {
|
||||||
|
id,
|
||||||
|
document,
|
||||||
|
to_front: false,
|
||||||
|
select_after_open: false,
|
||||||
|
};
|
||||||
|
self.dispatch_desktop_wrapper_message(message);
|
||||||
|
}
|
||||||
|
if let Some(id) = self.persistent_data.current_document_id() {
|
||||||
|
let message = DesktopWrapperMessage::SelectDocument { id };
|
||||||
|
self.dispatch_desktop_wrapper_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +360,7 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
|||||||
}
|
}
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
let Some(ref mut graphics_state) = self.graphics_state else { return };
|
let Some(ref mut graphics_state) = self.graphics_state else { return };
|
||||||
// Only rerender once we have a new ui texture to display
|
// Only rerender once we have a new UI texture to display
|
||||||
if let Some(window) = &self.window {
|
if let Some(window) = &self.window {
|
||||||
match graphics_state.render(window.as_ref()) {
|
match graphics_state.render(window.as_ref()) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub(crate) static APP_NAME: &str = "Graphite";
|
pub(crate) static APP_NAME: &str = "Graphite";
|
||||||
pub(crate) static APP_ID: &str = "rs.graphite.GraphiteEditor";
|
pub(crate) static APP_ID: &str = "rs.graphite.GraphiteEditor";
|
||||||
pub(crate) static APP_DIRECTORY_NAME: &str = "graphite-editor";
|
pub(crate) static APP_DIRECTORY_NAME: &str = "graphite-editor";
|
||||||
|
pub(crate) static APP_AUTOSAVE_DIRECTORY_NAME: &str = "documents";
|
||||||
|
|
||||||
// CEF configuration constants
|
// CEF configuration constants
|
||||||
pub(crate) const CEF_WINDOWLESS_FRAME_RATE: i32 = 60;
|
pub(crate) const CEF_WINDOWLESS_FRAME_RATE: i32 = 60;
|
||||||
|
|||||||
+7
-1
@@ -1,7 +1,7 @@
|
|||||||
use std::fs::create_dir_all;
|
use std::fs::create_dir_all;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::consts::APP_DIRECTORY_NAME;
|
use crate::consts::{APP_AUTOSAVE_DIRECTORY_NAME, APP_DIRECTORY_NAME};
|
||||||
|
|
||||||
pub(crate) fn ensure_dir_exists(path: &PathBuf) {
|
pub(crate) fn ensure_dir_exists(path: &PathBuf) {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
@@ -14,3 +14,9 @@ pub(crate) fn graphite_data_dir() -> PathBuf {
|
|||||||
ensure_dir_exists(&path);
|
ensure_dir_exists(&path);
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn graphite_autosave_documents_dir() -> PathBuf {
|
||||||
|
let path = graphite_data_dir().join(APP_AUTOSAVE_DIRECTORY_NAME);
|
||||||
|
ensure_dir_exists(&path);
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ mod app;
|
|||||||
use app::WinitApp;
|
use app::WinitApp;
|
||||||
|
|
||||||
mod dirs;
|
mod dirs;
|
||||||
|
mod persist;
|
||||||
|
|
||||||
use graphite_desktop_wrapper::messages::DesktopWrapperMessage;
|
use graphite_desktop_wrapper::messages::DesktopWrapperMessage;
|
||||||
use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext};
|
use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext};
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
use graphite_desktop_wrapper::messages::{Document, DocumentId};
|
||||||
|
|
||||||
|
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub(crate) struct PersistentData {
|
||||||
|
documents: DocumentStore,
|
||||||
|
current_document: Option<DocumentId>,
|
||||||
|
#[serde(skip)]
|
||||||
|
document_order: Option<Vec<DocumentId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PersistentData {
|
||||||
|
pub(crate) fn write_document(&mut self, id: DocumentId, document: Document) {
|
||||||
|
self.documents.write(id, document);
|
||||||
|
if let Some(order) = &self.document_order {
|
||||||
|
self.documents.force_order(order.clone());
|
||||||
|
}
|
||||||
|
self.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn delete_document(&mut self, id: &DocumentId) {
|
||||||
|
if Some(*id) == self.current_document {
|
||||||
|
self.current_document = None;
|
||||||
|
}
|
||||||
|
self.documents.delete(id);
|
||||||
|
self.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn current_document_id(&self) -> Option<DocumentId> {
|
||||||
|
match self.current_document {
|
||||||
|
Some(id) => Some(id),
|
||||||
|
None => Some(*self.documents.document_ids().first()?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn current_document(&self) -> Option<(DocumentId, Document)> {
|
||||||
|
let current_id = self.current_document_id()?;
|
||||||
|
Some((current_id, self.documents.read(¤t_id)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn documents_before_current(&self) -> Vec<(DocumentId, Document)> {
|
||||||
|
let Some(current_id) = self.current_document_id() else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
self.documents
|
||||||
|
.document_ids()
|
||||||
|
.into_iter()
|
||||||
|
.take_while(|id| *id != current_id)
|
||||||
|
.filter_map(|id| Some((id, self.documents.read(&id)?)))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn documents_after_current(&self) -> Vec<(DocumentId, Document)> {
|
||||||
|
let Some(current_id) = self.current_document_id() else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
self.documents
|
||||||
|
.document_ids()
|
||||||
|
.into_iter()
|
||||||
|
.skip_while(|id| *id != current_id)
|
||||||
|
.skip(1)
|
||||||
|
.filter_map(|id| Some((id, self.documents.read(&id)?)))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_current_document(&mut self, id: DocumentId) {
|
||||||
|
self.current_document = Some(id);
|
||||||
|
self.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_document_order(&mut self, order: Vec<DocumentId>) {
|
||||||
|
self.document_order = Some(order);
|
||||||
|
self.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {
|
||||||
|
let data = match ron::to_string(self) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to serialize persistent data: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(e) = std::fs::write(Self::persistence_file_path(), data) {
|
||||||
|
tracing::error!("Failed to write persistent data to disk: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_from_disk(&mut self) {
|
||||||
|
let path = Self::persistence_file_path();
|
||||||
|
let data = match std::fs::read_to_string(&path) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
tracing::info!("No persistent data file found at {path:?}, starting fresh");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to read persistent data from disk: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let loaded = match ron::from_str(&data) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to deserialize persistent data: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*self = loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn persistence_file_path() -> std::path::PathBuf {
|
||||||
|
let mut path = crate::dirs::graphite_data_dir();
|
||||||
|
path.push(format!("{}.ron", crate::consts::APP_AUTOSAVE_DIRECTORY_NAME));
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||||
|
struct DocumentStore(Vec<DocumentInfo>);
|
||||||
|
impl DocumentStore {
|
||||||
|
fn write(&mut self, id: DocumentId, document: Document) {
|
||||||
|
let meta = DocumentInfo::new(id, &document);
|
||||||
|
if let Some(existing) = self.0.iter_mut().find(|meta| meta.id == id) {
|
||||||
|
*existing = meta;
|
||||||
|
} else {
|
||||||
|
self.0.push(meta);
|
||||||
|
}
|
||||||
|
if let Err(e) = std::fs::write(Self::document_path(&id), document.content) {
|
||||||
|
tracing::error!("Failed to write document {id:?} to disk: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(&mut self, id: &DocumentId) {
|
||||||
|
self.0.retain(|meta| meta.id != *id);
|
||||||
|
if let Err(e) = std::fs::remove_file(Self::document_path(id)) {
|
||||||
|
tracing::error!("Failed to delete document {id:?} from disk: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self, id: &DocumentId) -> Option<Document> {
|
||||||
|
let meta = self.0.iter().find(|meta| meta.id == *id)?;
|
||||||
|
let content = std::fs::read_to_string(Self::document_path(id)).ok()?;
|
||||||
|
Some(Document {
|
||||||
|
content,
|
||||||
|
name: meta.name.clone(),
|
||||||
|
path: meta.path.clone(),
|
||||||
|
is_saved: meta.is_saved,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn force_order(&mut self, desired_order: Vec<DocumentId>) {
|
||||||
|
let mut ordered_prefix_len = 0;
|
||||||
|
for id in desired_order {
|
||||||
|
if let Some(offset) = self.0[ordered_prefix_len..].iter().position(|meta| meta.id == id) {
|
||||||
|
let found_index = ordered_prefix_len + offset;
|
||||||
|
if found_index != ordered_prefix_len {
|
||||||
|
self.0[ordered_prefix_len..=found_index].rotate_right(1);
|
||||||
|
}
|
||||||
|
ordered_prefix_len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.0.truncate(ordered_prefix_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn document_ids(&self) -> Vec<DocumentId> {
|
||||||
|
self.0.iter().map(|meta| meta.id).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn document_path(id: &DocumentId) -> std::path::PathBuf {
|
||||||
|
let mut path = crate::dirs::graphite_autosave_documents_dir();
|
||||||
|
path.push(format!("{:x}.graphite", id.0));
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
struct DocumentInfo {
|
||||||
|
id: DocumentId,
|
||||||
|
name: String,
|
||||||
|
path: Option<std::path::PathBuf>,
|
||||||
|
is_saved: bool,
|
||||||
|
}
|
||||||
|
impl DocumentInfo {
|
||||||
|
fn new(id: DocumentId, Document { name, path, is_saved, .. }: &Document) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
name: name.clone(),
|
||||||
|
path: path.clone(),
|
||||||
|
is_saved: *is_saved,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,3 +30,4 @@ dirs = { workspace = true }
|
|||||||
ron = { workspace = true}
|
ron = { workspace = true}
|
||||||
vello = { workspace = true }
|
vello = { workspace = true }
|
||||||
image = { workspace = true }
|
image = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
|||||||
@@ -118,5 +118,27 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
|
|||||||
let message = AppWindowMessage::AppWindowUpdatePlatform { platform };
|
let message = AppWindowMessage::AppWindowUpdatePlatform { platform };
|
||||||
dispatcher.queue_editor_message(message.into());
|
dispatcher.queue_editor_message(message.into());
|
||||||
}
|
}
|
||||||
|
DesktopWrapperMessage::LoadDocument {
|
||||||
|
id,
|
||||||
|
document,
|
||||||
|
to_front,
|
||||||
|
select_after_open,
|
||||||
|
} => {
|
||||||
|
let message = PortfolioMessage::OpenDocumentFileWithId {
|
||||||
|
document_id: id,
|
||||||
|
document_name: Some(document.name),
|
||||||
|
document_path: document.path,
|
||||||
|
document_serialized_content: document.content,
|
||||||
|
document_is_auto_saved: true,
|
||||||
|
document_is_saved: document.is_saved,
|
||||||
|
to_front,
|
||||||
|
select_after_open,
|
||||||
|
};
|
||||||
|
dispatcher.queue_editor_message(message.into());
|
||||||
|
}
|
||||||
|
DesktopWrapperMessage::SelectDocument { id } => {
|
||||||
|
let message = PortfolioMessage::SelectDocument { document_id: id };
|
||||||
|
dispatcher.queue_editor_message(message.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::path::PathBuf;
|
|||||||
use graphite_editor::messages::prelude::FrontendMessage;
|
use graphite_editor::messages::prelude::FrontendMessage;
|
||||||
|
|
||||||
use super::DesktopWrapperMessageDispatcher;
|
use super::DesktopWrapperMessageDispatcher;
|
||||||
use super::messages::{DesktopFrontendMessage, FileFilter, OpenFileDialogContext, SaveFileDialogContext};
|
use super::messages::{DesktopFrontendMessage, Document, FileFilter, OpenFileDialogContext, SaveFileDialogContext};
|
||||||
|
|
||||||
pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageDispatcher, message: FrontendMessage) -> Option<FrontendMessage> {
|
pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageDispatcher, message: FrontendMessage) -> Option<FrontendMessage> {
|
||||||
match message {
|
match message {
|
||||||
@@ -67,12 +67,46 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
|||||||
FrontendMessage::UpdateWindowState { maximized, minimized } => {
|
FrontendMessage::UpdateWindowState { maximized, minimized } => {
|
||||||
dispatcher.respond(DesktopFrontendMessage::UpdateWindowState { maximized, minimized });
|
dispatcher.respond(DesktopFrontendMessage::UpdateWindowState { maximized, minimized });
|
||||||
|
|
||||||
// Forward this to update the ui
|
// Forward this to update the UI
|
||||||
return Some(message);
|
return Some(message);
|
||||||
}
|
}
|
||||||
FrontendMessage::CloseWindow => {
|
FrontendMessage::CloseWindow => {
|
||||||
dispatcher.respond(DesktopFrontendMessage::CloseWindow);
|
dispatcher.respond(DesktopFrontendMessage::CloseWindow);
|
||||||
}
|
}
|
||||||
|
FrontendMessage::TriggerPersistenceWriteDocument { document_id, document, details } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::PersistenceWriteDocument {
|
||||||
|
id: document_id,
|
||||||
|
document: Document {
|
||||||
|
name: details.name,
|
||||||
|
path: None,
|
||||||
|
content: document,
|
||||||
|
is_saved: details.is_saved,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
FrontendMessage::TriggerPersistenceRemoveDocument { document_id } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::PersistenceDeleteDocument { id: document_id });
|
||||||
|
}
|
||||||
|
FrontendMessage::UpdateActiveDocument { document_id } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::PersistenceUpdateCurrentDocument { id: document_id });
|
||||||
|
|
||||||
|
// Forward this to update the UI
|
||||||
|
return Some(FrontendMessage::UpdateActiveDocument { document_id });
|
||||||
|
}
|
||||||
|
FrontendMessage::UpdateOpenDocumentsList { open_documents } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::PersistenceUpdateDocumentsList {
|
||||||
|
ids: open_documents.iter().map(|document| document.id).collect(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Forward this to update the UI
|
||||||
|
return Some(FrontendMessage::UpdateOpenDocumentsList { open_documents });
|
||||||
|
}
|
||||||
|
FrontendMessage::TriggerLoadFirstAutoSaveDocument => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::PersistenceLoadCurrentDocument);
|
||||||
|
}
|
||||||
|
FrontendMessage::TriggerLoadRestAutoSaveDocuments => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::PersistenceLoadRemainingDocuments);
|
||||||
|
}
|
||||||
m => return Some(m),
|
m => return Some(m),
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::path::PathBuf;
|
pub use graphite_editor::messages::prelude::DocumentId;
|
||||||
|
use graphite_editor::messages::prelude::FrontendMessage;
|
||||||
use graphite_editor::messages::prelude::{DocumentId, FrontendMessage};
|
|
||||||
|
|
||||||
pub(crate) use graphite_editor::messages::prelude::Message as EditorMessage;
|
pub(crate) use graphite_editor::messages::prelude::Message as EditorMessage;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub enum DesktopFrontendMessage {
|
pub enum DesktopFrontendMessage {
|
||||||
ToWeb(Vec<FrontendMessage>),
|
ToWeb(Vec<FrontendMessage>),
|
||||||
@@ -34,27 +33,81 @@ pub enum DesktopFrontendMessage {
|
|||||||
maximized: bool,
|
maximized: bool,
|
||||||
minimized: bool,
|
minimized: bool,
|
||||||
},
|
},
|
||||||
|
PersistenceWriteDocument {
|
||||||
|
id: DocumentId,
|
||||||
|
document: Document,
|
||||||
|
},
|
||||||
|
PersistenceDeleteDocument {
|
||||||
|
id: DocumentId,
|
||||||
|
},
|
||||||
|
PersistenceUpdateCurrentDocument {
|
||||||
|
id: DocumentId,
|
||||||
|
},
|
||||||
|
PersistenceLoadCurrentDocument,
|
||||||
|
PersistenceLoadRemainingDocuments,
|
||||||
|
PersistenceUpdateDocumentsList {
|
||||||
|
ids: Vec<DocumentId>,
|
||||||
|
},
|
||||||
CloseWindow,
|
CloseWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum DesktopWrapperMessage {
|
||||||
|
FromWeb(Box<EditorMessage>),
|
||||||
|
OpenFileDialogResult {
|
||||||
|
path: PathBuf,
|
||||||
|
content: Vec<u8>,
|
||||||
|
context: OpenFileDialogContext,
|
||||||
|
},
|
||||||
|
SaveFileDialogResult {
|
||||||
|
path: PathBuf,
|
||||||
|
context: SaveFileDialogContext,
|
||||||
|
},
|
||||||
|
OpenDocument {
|
||||||
|
path: PathBuf,
|
||||||
|
content: Vec<u8>,
|
||||||
|
},
|
||||||
|
OpenFile {
|
||||||
|
path: PathBuf,
|
||||||
|
content: Vec<u8>,
|
||||||
|
},
|
||||||
|
ImportFile {
|
||||||
|
path: PathBuf,
|
||||||
|
content: Vec<u8>,
|
||||||
|
},
|
||||||
|
ImportSvg {
|
||||||
|
path: PathBuf,
|
||||||
|
content: Vec<u8>,
|
||||||
|
},
|
||||||
|
ImportImage {
|
||||||
|
path: PathBuf,
|
||||||
|
content: Vec<u8>,
|
||||||
|
},
|
||||||
|
PollNodeGraphEvaluation,
|
||||||
|
UpdatePlatform(Platform),
|
||||||
|
LoadDocument {
|
||||||
|
id: DocumentId,
|
||||||
|
document: Document,
|
||||||
|
to_front: bool,
|
||||||
|
select_after_open: bool,
|
||||||
|
},
|
||||||
|
SelectDocument {
|
||||||
|
id: DocumentId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]
|
||||||
|
pub struct Document {
|
||||||
|
pub content: String,
|
||||||
|
pub name: String,
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
|
pub is_saved: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FileFilter {
|
pub struct FileFilter {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub extensions: Vec<String>,
|
pub extensions: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum DesktopWrapperMessage {
|
|
||||||
FromWeb(Box<EditorMessage>),
|
|
||||||
OpenFileDialogResult { path: PathBuf, content: Vec<u8>, context: OpenFileDialogContext },
|
|
||||||
SaveFileDialogResult { path: PathBuf, context: SaveFileDialogContext },
|
|
||||||
OpenDocument { path: PathBuf, content: Vec<u8> },
|
|
||||||
OpenFile { path: PathBuf, content: Vec<u8> },
|
|
||||||
ImportFile { path: PathBuf, content: Vec<u8> },
|
|
||||||
ImportSvg { path: PathBuf, content: Vec<u8> },
|
|
||||||
ImportImage { path: PathBuf, content: Vec<u8> },
|
|
||||||
PollNodeGraphEvaluation,
|
|
||||||
UpdatePlatform(Platform),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum OpenFileDialogContext {
|
pub enum OpenFileDialogContext {
|
||||||
Document,
|
Document,
|
||||||
Import,
|
Import,
|
||||||
|
|||||||
Reference in New Issue
Block a user