Remove all use of document indices (#406)
* removed all use of document indicies * -add u64 support for wasm bridge * fixed rust formating * Cleaned up FrontendDocumentState in js-messages * Tiny tweaks from code review * - moved more of closeDocumentWithConfirmation to rust - updated serde_wasm_bindgen to add feature flag * changed to upsteam version of serde_wasm_bindgen * cargo fmt * -fix event propigation on delete - Js message change class extention to typedef * changed another typedef * cargo fmt Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
1594b9c61d
commit
04c1b2ed03
|
|
@ -69,6 +69,12 @@ dependencies = [
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glam"
|
name = "glam"
|
||||||
version = "0.17.3"
|
version = "0.17.3"
|
||||||
|
|
@ -129,6 +135,7 @@ dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-test",
|
"wasm-bindgen-test",
|
||||||
]
|
]
|
||||||
|
|
@ -292,6 +299,18 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-wasm-bindgen"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d5cefe81e058ce25d1acbd79160e110d2eb4b9459024d46818d7553e4be6ff7e"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.130"
|
version = "1.0.130"
|
||||||
|
|
@ -376,8 +395,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,15 @@ pub struct Dispatcher {
|
||||||
pub responses: Vec<FrontendMessage>,
|
pub responses: Vec<FrontendMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GROUP_MESSAGES: &[MessageDiscriminant] = &[
|
// For optimization, these are messages guaranteed to be redundant when repeated
|
||||||
|
// The last occurrence of the message in the message queue is sufficient to ensure correctness
|
||||||
|
// In addition, these messages do not change any state in the backend (aside from caches)
|
||||||
|
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||||
MessageDiscriminant::Documents(DocumentsMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)),
|
MessageDiscriminant::Documents(DocumentsMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)),
|
||||||
MessageDiscriminant::Documents(DocumentsMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
|
MessageDiscriminant::Documents(DocumentsMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
|
||||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateLayer),
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateLayer),
|
||||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::DisplayFolderTreeStructure),
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::DisplayFolderTreeStructure),
|
||||||
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateOpenDocumentsList),
|
||||||
MessageDiscriminant::Tool(ToolMessageDiscriminant::SelectedLayersChanged),
|
MessageDiscriminant::Tool(ToolMessageDiscriminant::SelectedLayersChanged),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -31,7 +35,8 @@ impl Dispatcher {
|
||||||
|
|
||||||
use Message::*;
|
use Message::*;
|
||||||
while let Some(message) = self.messages.pop_front() {
|
while let Some(message) = self.messages.pop_front() {
|
||||||
if GROUP_MESSAGES.contains(&message.to_discriminant()) && self.messages.contains(&message) {
|
// Skip processing of this message if it will be processed later
|
||||||
|
if SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) && self.messages.contains(&message) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
self.log_message(&message);
|
self.log_message(&message);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::frontend::frontend_message_handler::FrontendDocumentDetails;
|
||||||
use crate::input::InputPreprocessor;
|
use crate::input::InputPreprocessor;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use graphene::layers::Layer;
|
use graphene::layers::Layer;
|
||||||
|
|
@ -18,11 +19,12 @@ pub enum DocumentsMessage {
|
||||||
insert_index: isize,
|
insert_index: isize,
|
||||||
},
|
},
|
||||||
Paste,
|
Paste,
|
||||||
SelectDocument(usize),
|
SelectDocument(u64),
|
||||||
CloseDocument(usize),
|
CloseDocument(u64),
|
||||||
#[child]
|
#[child]
|
||||||
Document(DocumentMessage),
|
Document(DocumentMessage),
|
||||||
CloseActiveDocumentWithConfirmation,
|
CloseActiveDocumentWithConfirmation,
|
||||||
|
CloseDocumentWithConfirmation(u64),
|
||||||
CloseAllDocumentsWithConfirmation,
|
CloseAllDocumentsWithConfirmation,
|
||||||
CloseAllDocuments,
|
CloseAllDocuments,
|
||||||
RequestAboutGraphiteDialog,
|
RequestAboutGraphiteDialog,
|
||||||
|
|
@ -38,20 +40,17 @@ pub enum DocumentsMessage {
|
||||||
pub struct DocumentsMessageHandler {
|
pub struct DocumentsMessageHandler {
|
||||||
documents: HashMap<u64, DocumentMessageHandler>,
|
documents: HashMap<u64, DocumentMessageHandler>,
|
||||||
document_ids: Vec<u64>,
|
document_ids: Vec<u64>,
|
||||||
document_id_counter: u64,
|
active_document_id: u64,
|
||||||
active_document_index: usize,
|
|
||||||
copy_buffer: Vec<Layer>,
|
copy_buffer: Vec<Layer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentsMessageHandler {
|
impl DocumentsMessageHandler {
|
||||||
pub fn active_document(&self) -> &DocumentMessageHandler {
|
pub fn active_document(&self) -> &DocumentMessageHandler {
|
||||||
let id = self.document_ids[self.active_document_index];
|
self.documents.get(&self.active_document_id).unwrap()
|
||||||
self.documents.get(&id).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_document_mut(&mut self) -> &mut DocumentMessageHandler {
|
pub fn active_document_mut(&mut self) -> &mut DocumentMessageHandler {
|
||||||
let id = self.document_ids[self.active_document_index];
|
self.documents.get_mut(&self.active_document_id).unwrap()
|
||||||
self.documents.get_mut(&id).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_new_document_name(&self) -> String {
|
fn generate_new_document_name(&self) -> String {
|
||||||
|
|
@ -78,21 +77,27 @@ impl DocumentsMessageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_document(&mut self, new_document: DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
fn load_document(&mut self, new_document: DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
self.document_id_counter += 1;
|
let new_id = generate_uuid();
|
||||||
self.active_document_index = self.document_ids.len();
|
self.active_document_id = new_id;
|
||||||
self.document_ids.push(self.document_id_counter);
|
self.document_ids.push(new_id);
|
||||||
self.documents.insert(self.document_id_counter, new_document);
|
self.documents.insert(new_id, new_document);
|
||||||
|
|
||||||
// Send the new list of document tab names
|
// Send the new list of document tab names
|
||||||
let open_documents = self
|
let open_documents = self
|
||||||
.document_ids
|
.document_ids
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|id| self.documents.get(&id).map(|doc| (doc.name.clone(), doc.is_saved())))
|
.filter_map(|id| {
|
||||||
|
self.documents.get(&id).map(|doc| FrontendDocumentDetails {
|
||||||
|
is_saved: doc.is_saved(),
|
||||||
|
id: *id,
|
||||||
|
name: doc.name.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into());
|
responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into());
|
||||||
|
|
||||||
responses.push_back(DocumentsMessage::SelectDocument(self.active_document_index).into());
|
responses.push_back(DocumentsMessage::SelectDocument(self.active_document_id).into());
|
||||||
responses.push_back(DocumentMessage::RenderDocument.into());
|
responses.push_back(DocumentMessage::RenderDocument.into());
|
||||||
responses.push_back(DocumentMessage::DocumentStructureChanged.into());
|
responses.push_back(DocumentMessage::DocumentStructureChanged.into());
|
||||||
for layer in self.active_document().layer_data.keys() {
|
for layer in self.active_document().layer_data.keys() {
|
||||||
|
|
@ -104,18 +109,23 @@ impl DocumentsMessageHandler {
|
||||||
pub fn ordered_document_iterator(&self) -> impl Iterator<Item = &DocumentMessageHandler> {
|
pub fn ordered_document_iterator(&self) -> impl Iterator<Item = &DocumentMessageHandler> {
|
||||||
self.document_ids.iter().map(|id| self.documents.get(id).expect("document id was not found in the document hashmap"))
|
self.document_ids.iter().map(|id| self.documents.get(id).expect("document id was not found in the document hashmap"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn document_index(&self, document_id: u64) -> usize {
|
||||||
|
self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DocumentsMessageHandler {
|
impl Default for DocumentsMessageHandler {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let mut documents_map: HashMap<u64, DocumentMessageHandler> = HashMap::with_capacity(1);
|
let mut documents_map: HashMap<u64, DocumentMessageHandler> = HashMap::with_capacity(1);
|
||||||
documents_map.insert(0, DocumentMessageHandler::default());
|
let starting_key = generate_uuid();
|
||||||
|
documents_map.insert(starting_key, DocumentMessageHandler::default());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
documents: documents_map,
|
documents: documents_map,
|
||||||
document_ids: vec![0],
|
document_ids: vec![starting_key],
|
||||||
copy_buffer: vec![],
|
copy_buffer: vec![],
|
||||||
active_document_index: 0,
|
active_document_id: starting_key,
|
||||||
document_id_counter: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -129,11 +139,9 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
|
||||||
responses.push_back(FrontendMessage::DisplayAboutGraphiteDialog.into());
|
responses.push_back(FrontendMessage::DisplayAboutGraphiteDialog.into());
|
||||||
}
|
}
|
||||||
Document(message) => self.active_document_mut().process_action(message, ipp, responses),
|
Document(message) => self.active_document_mut().process_action(message, ipp, responses),
|
||||||
SelectDocument(index) => {
|
SelectDocument(id) => {
|
||||||
// NOTE: Potentially this will break if we ever exceed 56 bit values due to how the message parsing system works.
|
self.active_document_id = id;
|
||||||
assert!(index < self.documents.len(), "Tried to select a document that was not initialized");
|
responses.push_back(FrontendMessage::SetActiveDocument { document_id: id }.into());
|
||||||
self.active_document_index = index;
|
|
||||||
responses.push_back(FrontendMessage::SetActiveDocument { document_index: index }.into());
|
|
||||||
responses.push_back(RenderDocument.into());
|
responses.push_back(RenderDocument.into());
|
||||||
responses.push_back(DocumentMessage::DocumentStructureChanged.into());
|
responses.push_back(DocumentMessage::DocumentStructureChanged.into());
|
||||||
for layer in self.active_document().layer_data.keys() {
|
for layer in self.active_document().layer_data.keys() {
|
||||||
|
|
@ -141,12 +149,17 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CloseActiveDocumentWithConfirmation => {
|
CloseActiveDocumentWithConfirmation => {
|
||||||
responses.push_back(
|
responses.push_back(DocumentsMessage::CloseDocumentWithConfirmation(self.active_document_id).into());
|
||||||
FrontendMessage::DisplayConfirmationToCloseDocument {
|
}
|
||||||
document_index: self.active_document_index,
|
CloseDocumentWithConfirmation(id) => {
|
||||||
}
|
let target_document = self.documents.get(&id).unwrap();
|
||||||
.into(),
|
if target_document.is_saved() {
|
||||||
);
|
responses.push_back(DocumentsMessage::CloseDocument(id).into());
|
||||||
|
} else {
|
||||||
|
responses.push_back(FrontendMessage::DisplayConfirmationToCloseDocument { document_id: id }.into());
|
||||||
|
// Select the document being closed
|
||||||
|
responses.push_back(DocumentsMessage::SelectDocument(id).into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CloseAllDocumentsWithConfirmation => {
|
CloseAllDocumentsWithConfirmation => {
|
||||||
responses.push_back(FrontendMessage::DisplayConfirmationToCloseAllDocuments.into());
|
responses.push_back(FrontendMessage::DisplayConfirmationToCloseAllDocuments.into());
|
||||||
|
|
@ -159,38 +172,44 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
|
||||||
// Create a new blank document
|
// Create a new blank document
|
||||||
responses.push_back(NewDocument.into());
|
responses.push_back(NewDocument.into());
|
||||||
}
|
}
|
||||||
CloseDocument(index) => {
|
CloseDocument(id) => {
|
||||||
assert!(index < self.documents.len(), "Tried to close a document that was not initialized");
|
let document_index = self.document_index(id);
|
||||||
// Get the ID based on the current collection of the documents.
|
|
||||||
let id = self.document_ids[index];
|
|
||||||
// Map the ID to an index and remove the document
|
|
||||||
self.documents.remove(&id);
|
self.documents.remove(&id);
|
||||||
self.document_ids.remove(index);
|
self.document_ids.remove(document_index);
|
||||||
|
|
||||||
// Last tab was closed, so create a new blank tab
|
// Last tab was closed, so create a new blank tab
|
||||||
if self.document_ids.is_empty() {
|
if self.document_ids.is_empty() {
|
||||||
self.document_id_counter += 1;
|
let new_id = generate_uuid();
|
||||||
self.document_ids.push(self.document_id_counter);
|
self.document_ids.push(new_id);
|
||||||
self.documents.insert(self.document_id_counter, DocumentMessageHandler::default());
|
self.documents.insert(new_id, DocumentMessageHandler::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.active_document_index = if self.active_document_index >= self.document_ids.len() {
|
self.active_document_id = if id != self.active_document_id {
|
||||||
self.document_ids.len() - 1
|
// If we are not closing the active document, stay on it
|
||||||
|
self.active_document_id
|
||||||
|
} else if document_index >= self.document_ids.len() {
|
||||||
|
// If we closed the last document take the one previous (same as last)
|
||||||
|
*self.document_ids.last().unwrap()
|
||||||
} else {
|
} else {
|
||||||
index
|
// Move to the next tab
|
||||||
|
self.document_ids[document_index]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send the new list of document tab names
|
// Send the new list of document tab names
|
||||||
let open_documents = self.ordered_document_iterator().map(|doc| (doc.name.clone(), doc.is_saved())).collect();
|
let open_documents = self
|
||||||
|
.document_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| {
|
||||||
|
self.documents.get(&id).map(|doc| FrontendDocumentDetails {
|
||||||
|
is_saved: doc.is_saved(),
|
||||||
|
id: *id,
|
||||||
|
name: doc.name.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
// Update the list of new documents on the front end, active tab, and ensure that document renders
|
// Update the list of new documents on the front end, active tab, and ensure that document renders
|
||||||
responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into());
|
responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into());
|
||||||
responses.push_back(
|
responses.push_back(FrontendMessage::SetActiveDocument { document_id: self.active_document_id }.into());
|
||||||
FrontendMessage::SetActiveDocument {
|
|
||||||
document_index: self.active_document_index,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
responses.push_back(RenderDocument.into());
|
responses.push_back(RenderDocument.into());
|
||||||
responses.push_back(DocumentMessage::DocumentStructureChanged.into());
|
responses.push_back(DocumentMessage::DocumentStructureChanged.into());
|
||||||
for layer in self.active_document().layer_data.keys() {
|
for layer in self.active_document().layer_data.keys() {
|
||||||
|
|
@ -222,17 +241,31 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
|
||||||
}
|
}
|
||||||
UpdateOpenDocumentsList => {
|
UpdateOpenDocumentsList => {
|
||||||
// Send the list of document tab names
|
// Send the list of document tab names
|
||||||
let open_documents = self.ordered_document_iterator().map(|doc| (doc.name.clone(), doc.is_saved())).collect();
|
let open_documents = self
|
||||||
|
.document_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| {
|
||||||
|
self.documents.get(&id).map(|doc| FrontendDocumentDetails {
|
||||||
|
is_saved: doc.is_saved(),
|
||||||
|
id: *id,
|
||||||
|
name: doc.name.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into());
|
responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into());
|
||||||
}
|
}
|
||||||
NextDocument => {
|
NextDocument => {
|
||||||
let next = (self.active_document_index + 1) % self.document_ids.len();
|
let current_index = self.document_index(self.active_document_id);
|
||||||
responses.push_back(SelectDocument(next).into());
|
let next_index = (current_index + 1) % self.document_ids.len();
|
||||||
|
let next_id = self.document_ids[next_index];
|
||||||
|
responses.push_back(SelectDocument(next_id).into());
|
||||||
}
|
}
|
||||||
PrevDocument => {
|
PrevDocument => {
|
||||||
let len = self.document_ids.len();
|
let len = self.document_ids.len();
|
||||||
let prev = (self.active_document_index + len - 1) % len;
|
let current_index = self.document_index(self.active_document_id);
|
||||||
responses.push_back(SelectDocument(prev).into());
|
let prev_index = (current_index + len - 1) % len;
|
||||||
|
let prev_id = self.document_ids[prev_index];
|
||||||
|
responses.push_back(SelectDocument(prev_id).into());
|
||||||
}
|
}
|
||||||
Copy => {
|
Copy => {
|
||||||
let paths = self.active_document().selected_layers_sorted();
|
let paths = self.active_document().selected_layers_sorted();
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@ use crate::consts::VIEWPORT_ROTATE_SNAP_INTERVAL;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene::layers::{style::ViewMode, BlendMode, Layer, LayerData as DocumentLayerData, LayerDataType};
|
use graphene::layers::{style::ViewMode, BlendMode, Layer, LayerData as DocumentLayerData, LayerDataType};
|
||||||
use graphene::LayerId;
|
use graphene::LayerId;
|
||||||
use serde::{
|
use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
||||||
ser::{SerializeSeq, SerializeStruct},
|
|
||||||
Deserialize, Serialize,
|
|
||||||
};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
|
@ -84,39 +81,11 @@ pub fn layer_panel_entry(layer_data: &LayerData, transform: DAffine2, layer: &La
|
||||||
opacity: layer.opacity,
|
opacity: layer.opacity,
|
||||||
layer_type: (&layer.data).into(),
|
layer_type: (&layer.data).into(),
|
||||||
layer_data: *layer_data,
|
layer_data: *layer_data,
|
||||||
path: path.into(),
|
path,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
|
||||||
pub struct Path(Vec<LayerId>);
|
|
||||||
|
|
||||||
impl From<Vec<LayerId>> for Path {
|
|
||||||
fn from(iter: Vec<LayerId>) -> Self {
|
|
||||||
Self(iter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Serialize for Path {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
|
|
||||||
for e in self.0.iter() {
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
{
|
|
||||||
// LayerIds are sent as (u32, u32) because json does not support u64s
|
|
||||||
let id = ((e >> 32) as u32, (e << 32 >> 32) as u32);
|
|
||||||
seq.serialize_element(&id)?;
|
|
||||||
}
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
seq.serialize_element(e)?;
|
|
||||||
}
|
|
||||||
seq.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||||
pub struct RawBuffer(Vec<u8>);
|
pub struct RawBuffer(Vec<u8>);
|
||||||
|
|
||||||
|
|
@ -152,7 +121,7 @@ pub struct LayerPanelEntry {
|
||||||
pub opacity: f64,
|
pub opacity: f64,
|
||||||
pub layer_type: LayerType,
|
pub layer_type: LayerType,
|
||||||
pub layer_data: LayerData,
|
pub layer_data: LayerData,
|
||||||
pub path: crate::document::layer_panel::Path,
|
pub path: Vec<LayerId>,
|
||||||
pub thumbnail: String,
|
pub thumbnail: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,24 @@ use crate::tool::tool_options::ToolOptions;
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||||
|
pub struct FrontendDocumentDetails {
|
||||||
|
pub is_saved: bool,
|
||||||
|
pub name: String,
|
||||||
|
pub id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
#[impl_message(Message, Frontend)]
|
#[impl_message(Message, Frontend)]
|
||||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||||
pub enum FrontendMessage {
|
pub enum FrontendMessage {
|
||||||
DisplayFolderTreeStructure { data_buffer: RawBuffer },
|
DisplayFolderTreeStructure { data_buffer: RawBuffer },
|
||||||
SetActiveTool { tool_name: String, tool_options: Option<ToolOptions> },
|
SetActiveTool { tool_name: String, tool_options: Option<ToolOptions> },
|
||||||
SetActiveDocument { document_index: usize },
|
SetActiveDocument { document_id: u64 },
|
||||||
UpdateOpenDocumentsList { open_documents: Vec<(String, bool)> },
|
UpdateOpenDocumentsList { open_documents: Vec<FrontendDocumentDetails> },
|
||||||
UpdateInputHints { hint_data: HintData },
|
UpdateInputHints { hint_data: HintData },
|
||||||
DisplayError { title: String, description: String },
|
DisplayError { title: String, description: String },
|
||||||
DisplayPanic { panic_info: String, title: String, description: String },
|
DisplayPanic { panic_info: String, title: String, description: String },
|
||||||
DisplayConfirmationToCloseDocument { document_index: usize },
|
DisplayConfirmationToCloseDocument { document_id: u64 },
|
||||||
DisplayConfirmationToCloseAllDocuments,
|
DisplayConfirmationToCloseAllDocuments,
|
||||||
DisplayAboutGraphiteDialog,
|
DisplayAboutGraphiteDialog,
|
||||||
UpdateLayer { data: LayerPanelEntry },
|
UpdateLayer { data: LayerPanelEntry },
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<button class="icon-button" :class="`size-${String(size)}`" @click="action">
|
<button class="icon-button" :class="`size-${String(size)}`" @click="(e) => action(e)">
|
||||||
<IconLabel :icon="icon" />
|
<IconLabel :icon="icon" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -7,26 +7,11 @@
|
||||||
:class="{ active: tabIndex === tabActiveIndex }"
|
:class="{ active: tabIndex === tabActiveIndex }"
|
||||||
v-for="(tabLabel, tabIndex) in tabLabels"
|
v-for="(tabLabel, tabIndex) in tabLabels"
|
||||||
:key="tabIndex"
|
:key="tabIndex"
|
||||||
@click.middle="
|
@click="(e) => e.stopPropagation() || (clickAction && clickAction(tabIndex))"
|
||||||
(e) => {
|
@click.middle="(e) => e.stopPropagation() || (closeAction && closeAction(tabIndex))"
|
||||||
e.stopPropagation();
|
|
||||||
documents.closeDocumentWithConfirmation(tabIndex);
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@click="panelType === 'Document' && documents.selectDocument(tabIndex)"
|
|
||||||
>
|
>
|
||||||
<span>{{ tabLabel }}</span>
|
<span>{{ tabLabel }}</span>
|
||||||
<IconButton
|
<IconButton :action="(e) => e.stopPropagation() || (closeAction && closeAction(tabIndex))" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
|
||||||
:action="
|
|
||||||
(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
documents.closeDocumentWithConfirmation(tabIndex);
|
|
||||||
}
|
|
||||||
"
|
|
||||||
:icon="'CloseX'"
|
|
||||||
:size="16"
|
|
||||||
v-if="tabCloseButtons"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PopoverButton :icon="PopoverButtonIcon.VerticalEllipsis">
|
<PopoverButton :icon="PopoverButtonIcon.VerticalEllipsis">
|
||||||
|
|
@ -193,6 +178,8 @@ export default defineComponent({
|
||||||
tabLabels: { type: Array as PropType<string[]>, required: true },
|
tabLabels: { type: Array as PropType<string[]>, required: true },
|
||||||
tabActiveIndex: { type: Number, required: true },
|
tabActiveIndex: { type: Number, required: true },
|
||||||
panelType: { type: String, required: true },
|
panelType: { type: String, required: true },
|
||||||
|
clickAction: { type: Function as PropType<(index: number) => void>, required: false },
|
||||||
|
closeAction: { type: Function as PropType<(index: number) => void>, required: false },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,18 @@
|
||||||
:tabCloseButtons="true"
|
:tabCloseButtons="true"
|
||||||
:tabMinWidths="true"
|
:tabMinWidths="true"
|
||||||
:tabLabels="documents.state.documents.map((doc) => doc.displayName)"
|
:tabLabels="documents.state.documents.map((doc) => doc.displayName)"
|
||||||
|
:clickAction="
|
||||||
|
(tabIndex) => {
|
||||||
|
const targetId = documents.state.documents[tabIndex].id;
|
||||||
|
editor.instance.select_document(targetId);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
:closeAction="
|
||||||
|
(tabIndex) => {
|
||||||
|
const targetId = documents.state.documents[tabIndex].id;
|
||||||
|
editor.instance.close_document_with_confirmation(targetId);
|
||||||
|
}
|
||||||
|
"
|
||||||
:tabActiveIndex="documents.state.activeDocumentIndex"
|
:tabActiveIndex="documents.state.activeDocumentIndex"
|
||||||
ref="documentsPanel"
|
ref="documentsPanel"
|
||||||
/>
|
/>
|
||||||
|
|
@ -61,16 +73,13 @@ import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
import DialogModal from "@/components/widgets/floating-menus/DialogModal.vue";
|
import DialogModal from "@/components/widgets/floating-menus/DialogModal.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ["documents", "dialog"],
|
inject: ["documents", "dialog", "editor"],
|
||||||
components: {
|
components: {
|
||||||
LayoutRow,
|
LayoutRow,
|
||||||
LayoutCol,
|
LayoutCol,
|
||||||
Panel,
|
Panel,
|
||||||
DialogModal,
|
DialogModal,
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
activeDocumentIndex() {
|
activeDocumentIndex() {
|
||||||
return this.documents.state.activeDocumentIndex;
|
return this.documents.state.activeDocumentIndex;
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,31 @@ export class JsMessage {
|
||||||
// for details about how to transform the JSON from wasm-bindgen into classes.
|
// for details about how to transform the JSON from wasm-bindgen into classes.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
export class UpdateOpenDocumentsList extends JsMessage {
|
export class FrontendDocumentDetails {
|
||||||
@Transform(({ value }) => value.map((tuple: [string, boolean]) => ({ name: tuple[0], isSaved: tuple[1] })))
|
readonly name!: string;
|
||||||
readonly open_documents!: { name: string; isSaved: boolean }[];
|
|
||||||
|
readonly is_saved!: boolean;
|
||||||
|
|
||||||
|
readonly id!: BigInt;
|
||||||
|
|
||||||
|
get displayName() {
|
||||||
|
return `${this.name}${this.is_saved ? "" : "*"}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateOpenDocumentsList extends JsMessage {
|
||||||
|
@Type(() => FrontendDocumentDetails)
|
||||||
|
readonly open_documents!: FrontendDocumentDetails[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HintData = HintInfo[][];
|
||||||
|
|
||||||
export class UpdateInputHints extends JsMessage {
|
export class UpdateInputHints extends JsMessage {
|
||||||
@Type(() => HintInfo)
|
@Type(() => HintInfo)
|
||||||
readonly hint_data!: HintData;
|
readonly hint_data!: HintData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HintGroup extends Array<HintInfo> {}
|
export type KeysGroup = string[];
|
||||||
|
|
||||||
export class HintData extends Array<HintGroup> {}
|
|
||||||
|
|
||||||
export class HintInfo {
|
export class HintInfo {
|
||||||
readonly keys!: string[];
|
readonly keys!: string[];
|
||||||
|
|
@ -43,8 +55,6 @@ export class HintInfo {
|
||||||
readonly plus!: boolean;
|
readonly plus!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KeysGroup extends Array<string> {}
|
|
||||||
|
|
||||||
const To255Scale = Transform(({ value }) => value * 255);
|
const To255Scale = Transform(({ value }) => value * 255);
|
||||||
export class Color {
|
export class Color {
|
||||||
@To255Scale
|
@To255Scale
|
||||||
|
|
@ -83,7 +93,7 @@ export class SetActiveTool extends JsMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SetActiveDocument extends JsMessage {
|
export class SetActiveDocument extends JsMessage {
|
||||||
readonly document_index!: number;
|
readonly document_id!: BigInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DisplayError extends JsMessage {
|
export class DisplayError extends JsMessage {
|
||||||
|
|
@ -101,7 +111,7 @@ export class DisplayPanic extends JsMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DisplayConfirmationToCloseDocument extends JsMessage {
|
export class DisplayConfirmationToCloseDocument extends JsMessage {
|
||||||
readonly document_index!: number;
|
readonly document_id!: BigInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DisplayConfirmationToCloseAllDocuments extends JsMessage {}
|
export class DisplayConfirmationToCloseAllDocuments extends JsMessage {}
|
||||||
|
|
@ -157,23 +167,25 @@ export class DisplayFolderTreeStructure extends JsMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DataBuffer {
|
interface DataBuffer {
|
||||||
pointer: number;
|
pointer: BigInt;
|
||||||
length: number;
|
length: BigInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function newDisplayFolderTreeStructure(input: { data_buffer: DataBuffer }, wasm: WasmInstance): DisplayFolderTreeStructure {
|
export function newDisplayFolderTreeStructure(input: { data_buffer: DataBuffer }, wasm: WasmInstance): DisplayFolderTreeStructure {
|
||||||
const { pointer, length } = input.data_buffer;
|
const { pointer, length } = input.data_buffer;
|
||||||
|
const pointerNum = Number(pointer);
|
||||||
|
const lengthNum = Number(length);
|
||||||
const wasmMemoryBuffer = wasm.wasm_memory().buffer;
|
const wasmMemoryBuffer = wasm.wasm_memory().buffer;
|
||||||
|
|
||||||
// Decode the folder structure encoding
|
// Decode the folder structure encoding
|
||||||
const encoding = new DataView(wasmMemoryBuffer, pointer, length);
|
const encoding = new DataView(wasmMemoryBuffer, pointerNum, lengthNum);
|
||||||
|
|
||||||
// The structure section indicates how to read through the upcoming layer list and assign depths to each layer
|
// The structure section indicates how to read through the upcoming layer list and assign depths to each layer
|
||||||
const structureSectionLength = Number(encoding.getBigUint64(0, true));
|
const structureSectionLength = Number(encoding.getBigUint64(0, true));
|
||||||
const structureSectionMsbSigned = new DataView(wasmMemoryBuffer, pointer + 8, structureSectionLength * 8);
|
const structureSectionMsbSigned = new DataView(wasmMemoryBuffer, pointerNum + 8, structureSectionLength * 8);
|
||||||
|
|
||||||
// The layer IDs section lists each layer ID sequentially in the tree, as it will show up in the panel
|
// The layer IDs section lists each layer ID sequentially in the tree, as it will show up in the panel
|
||||||
const layerIdsSection = new DataView(wasmMemoryBuffer, pointer + 8 + structureSectionLength * 8);
|
const layerIdsSection = new DataView(wasmMemoryBuffer, pointerNum + 8 + structureSectionLength * 8);
|
||||||
|
|
||||||
let layersEncountered = 0;
|
let layersEncountered = 0;
|
||||||
let currentFolder = new DisplayFolderTreeStructure(BigInt(-1), []);
|
let currentFolder = new DisplayFolderTreeStructure(BigInt(-1), []);
|
||||||
|
|
@ -226,12 +238,6 @@ export class SetCanvasRotation extends JsMessage {
|
||||||
readonly new_radians!: number;
|
readonly new_radians!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function newPath(input: number[][]): BigUint64Array {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const u32CombinedPairs = input.map((n: number[]) => BigInt((BigInt(n[0]) << BigInt(32)) | BigInt(n[1])));
|
|
||||||
return new BigUint64Array(u32CombinedPairs);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BlendMode =
|
export type BlendMode =
|
||||||
| "Normal"
|
| "Normal"
|
||||||
| "Multiply"
|
| "Multiply"
|
||||||
|
|
@ -263,7 +269,7 @@ export class LayerPanelEntry {
|
||||||
|
|
||||||
layer_type!: LayerType;
|
layer_type!: LayerType;
|
||||||
|
|
||||||
@Transform(({ value }) => newPath(value))
|
@Transform(({ value }) => new BigUint64Array(value))
|
||||||
path!: BigUint64Array;
|
path!: BigUint64Array;
|
||||||
|
|
||||||
@Type(() => LayerData)
|
@Type(() => LayerData)
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
|
||||||
// Skip the message during development, since it's annoying when testing
|
// Skip the message during development, since it's annoying when testing
|
||||||
if (process.env.NODE_ENV === "development") return;
|
if (process.env.NODE_ENV === "development") return;
|
||||||
|
|
||||||
const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.isSaved, true);
|
const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.is_saved, true);
|
||||||
if (!allDocumentsSaved) {
|
if (!allDocumentsSaved) {
|
||||||
e.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?";
|
e.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?";
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
||||||
|
|
@ -8,47 +8,30 @@ import {
|
||||||
DisplayConfirmationToCloseAllDocuments,
|
DisplayConfirmationToCloseAllDocuments,
|
||||||
DisplayConfirmationToCloseDocument,
|
DisplayConfirmationToCloseDocument,
|
||||||
ExportDocument,
|
ExportDocument,
|
||||||
|
FrontendDocumentDetails,
|
||||||
OpenDocumentBrowse,
|
OpenDocumentBrowse,
|
||||||
SaveDocument,
|
SaveDocument,
|
||||||
SetActiveDocument,
|
SetActiveDocument,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
} from "@/dispatcher/js-messages";
|
} from "@/dispatcher/js-messages";
|
||||||
|
|
||||||
class DocumentSaveState {
|
|
||||||
readonly displayName: string;
|
|
||||||
|
|
||||||
constructor(readonly name: string, readonly isSaved: boolean) {
|
|
||||||
this.displayName = `${name}${isSaved ? "" : "*"}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDocumentsState(editor: EditorState, dialogState: DialogState) {
|
export function createDocumentsState(editor: EditorState, dialogState: DialogState) {
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
unsaved: false,
|
unsaved: false,
|
||||||
documents: [] as DocumentSaveState[],
|
documents: [] as FrontendDocumentDetails[],
|
||||||
activeDocumentIndex: 0,
|
activeDocumentIndex: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectDocument = (tabIndex: number) => {
|
const closeDocumentWithConfirmation = async (documentId: BigInt) => {
|
||||||
editor.instance.select_document(tabIndex);
|
// Assume we receive a correct document_id
|
||||||
};
|
const targetDocument = state.documents.find((doc) => doc.id === documentId) as FrontendDocumentDetails;
|
||||||
|
const tabLabel = targetDocument.displayName;
|
||||||
const closeDocumentWithConfirmation = (tabIndex: number) => {
|
|
||||||
// Close automatically if it's already saved, no confirmation is needed
|
|
||||||
const targetDocument = state.documents[tabIndex];
|
|
||||||
if (targetDocument.isSaved) {
|
|
||||||
editor.instance.close_document(tabIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to the document that's being prompted to close
|
|
||||||
selectDocument(tabIndex);
|
|
||||||
|
|
||||||
// Show the close confirmation prompt
|
// Show the close confirmation prompt
|
||||||
dialogState.createDialog("File", "Save changes before closing?", targetDocument.displayName, [
|
dialogState.createDialog("File", "Save changes before closing?", tabLabel, [
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: () => {
|
callback: async () => {
|
||||||
editor.instance.save_document();
|
editor.instance.save_document();
|
||||||
dialogState.dismissDialog();
|
dialogState.dismissDialog();
|
||||||
},
|
},
|
||||||
|
|
@ -56,15 +39,15 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: () => {
|
callback: async () => {
|
||||||
editor.instance.close_document(tabIndex);
|
editor.instance.close_document(targetDocument.id);
|
||||||
dialogState.dismissDialog();
|
dialogState.dismissDialog();
|
||||||
},
|
},
|
||||||
props: { label: "Discard", minWidth: 96 },
|
props: { label: "Discard", minWidth: 96 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: () => {
|
callback: async () => {
|
||||||
dialogState.dismissDialog();
|
dialogState.dismissDialog();
|
||||||
},
|
},
|
||||||
props: { label: "Cancel", minWidth: 96 },
|
props: { label: "Cancel", minWidth: 96 },
|
||||||
|
|
@ -94,15 +77,17 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
||||||
|
|
||||||
// Set up message subscriptions on creation
|
// Set up message subscriptions on creation
|
||||||
editor.dispatcher.subscribeJsMessage(UpdateOpenDocumentsList, (updateOpenDocumentList) => {
|
editor.dispatcher.subscribeJsMessage(UpdateOpenDocumentsList, (updateOpenDocumentList) => {
|
||||||
state.documents = updateOpenDocumentList.open_documents.map(({ name, isSaved }) => new DocumentSaveState(name, isSaved));
|
state.documents = updateOpenDocumentList.open_documents;
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.dispatcher.subscribeJsMessage(SetActiveDocument, (setActiveDocument) => {
|
editor.dispatcher.subscribeJsMessage(SetActiveDocument, (setActiveDocument) => {
|
||||||
state.activeDocumentIndex = setActiveDocument.document_index;
|
// Assume we receive a correct document id
|
||||||
|
const activeId = state.documents.findIndex((doc) => doc.id === setActiveDocument.document_id);
|
||||||
|
state.activeDocumentIndex = activeId;
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.dispatcher.subscribeJsMessage(DisplayConfirmationToCloseDocument, (displayConfirmationToCloseDocument) => {
|
editor.dispatcher.subscribeJsMessage(DisplayConfirmationToCloseDocument, (displayConfirmationToCloseDocument) => {
|
||||||
closeDocumentWithConfirmation(displayConfirmationToCloseDocument.document_index);
|
closeDocumentWithConfirmation(displayConfirmationToCloseDocument.document_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.dispatcher.subscribeJsMessage(DisplayConfirmationToCloseAllDocuments, () => {
|
editor.dispatcher.subscribeJsMessage(DisplayConfirmationToCloseAllDocuments, () => {
|
||||||
|
|
@ -128,8 +113,6 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: readonly(state),
|
state: readonly(state),
|
||||||
selectDocument,
|
|
||||||
closeDocumentWithConfirmation,
|
|
||||||
closeAllDocumentsWithConfirmation,
|
closeAllDocumentsWithConfirmation,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ editor = { path = "../../editor", package = "graphite-editor" }
|
||||||
graphene = { path = "../../graphene", package = "graphite-graphene" }
|
graphene = { path = "../../graphene", package = "graphite-graphene" }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
wasm-bindgen = { version = "0.2.73", features = ["serde-serialize"] }
|
wasm-bindgen = { version = "0.2.73" }
|
||||||
|
serde-wasm-bindgen = "0.4.1"
|
||||||
js-sys = "0.3.55"
|
js-sys = "0.3.55"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,12 @@ use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
|
||||||
use editor::message_prelude::*;
|
use editor::message_prelude::*;
|
||||||
use editor::misc::EditorError;
|
use editor::misc::EditorError;
|
||||||
use editor::tool::{tool_options::ToolOptions, tools, ToolType};
|
use editor::tool::{tool_options::ToolOptions, tools, ToolType};
|
||||||
use editor::{Color, Editor, LayerId};
|
use editor::Color;
|
||||||
|
use editor::LayerId;
|
||||||
|
|
||||||
|
use editor::Editor;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_wasm_bindgen;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
// To avoid wasm-bindgen from checking mutable reference issues using WasmRefCell
|
// To avoid wasm-bindgen from checking mutable reference issues using WasmRefCell
|
||||||
|
|
@ -29,7 +34,7 @@ pub struct JsEditorHandle {
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl JsEditorHandle {
|
impl JsEditorHandle {
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(handle_response: js_sys::Function) -> JsEditorHandle {
|
pub fn new(handle_response: js_sys::Function) -> Self {
|
||||||
let editor_id = generate_uuid();
|
let editor_id = generate_uuid();
|
||||||
let editor = Editor::new();
|
let editor = Editor::new();
|
||||||
EDITOR_INSTANCES.with(|instances| instances.borrow_mut().insert(editor_id, editor));
|
EDITOR_INSTANCES.with(|instances| instances.borrow_mut().insert(editor_id, editor));
|
||||||
|
|
@ -68,7 +73,9 @@ impl JsEditorHandle {
|
||||||
// Sends a FrontendMessage to JavaScript
|
// Sends a FrontendMessage to JavaScript
|
||||||
fn handle_response(&self, message: FrontendMessage) {
|
fn handle_response(&self, message: FrontendMessage) {
|
||||||
let message_type = message.to_discriminant().local_name();
|
let message_type = message.to_discriminant().local_name();
|
||||||
let message_data = JsValue::from_serde(&message).expect("Failed to serialize FrontendMessage");
|
|
||||||
|
let serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);
|
||||||
|
let message_data = message.serialize(&serializer).expect("Failed to serialize FrontendMessage");
|
||||||
|
|
||||||
let js_return_value = self.handle_response.call2(&JsValue::null(), &JsValue::from(message_type), &message_data);
|
let js_return_value = self.handle_response.call2(&JsValue::null(), &JsValue::from(message_type), &message_data);
|
||||||
|
|
||||||
|
|
@ -106,7 +113,7 @@ impl JsEditorHandle {
|
||||||
|
|
||||||
/// Update the options for a given tool
|
/// Update the options for a given tool
|
||||||
pub fn set_tool_options(&self, tool: String, options: &JsValue) -> Result<(), JsValue> {
|
pub fn set_tool_options(&self, tool: String, options: &JsValue) -> Result<(), JsValue> {
|
||||||
match options.into_serde::<ToolOptions>() {
|
match serde_wasm_bindgen::from_value::<ToolOptions>(options.clone()) {
|
||||||
Ok(options) => match translate_tool_type(&tool) {
|
Ok(options) => match translate_tool_type(&tool) {
|
||||||
Some(tool) => {
|
Some(tool) => {
|
||||||
let message = ToolMessage::SetToolOptions(tool, options);
|
let message = ToolMessage::SetToolOptions(tool, options);
|
||||||
|
|
@ -124,7 +131,7 @@ impl JsEditorHandle {
|
||||||
pub fn send_tool_message(&self, tool: String, message: &JsValue) -> Result<(), JsValue> {
|
pub fn send_tool_message(&self, tool: String, message: &JsValue) -> Result<(), JsValue> {
|
||||||
let tool_message = match translate_tool_type(&tool) {
|
let tool_message = match translate_tool_type(&tool) {
|
||||||
Some(tool) => match tool {
|
Some(tool) => match tool {
|
||||||
ToolType::Select => match message.into_serde::<tools::select::SelectMessage>() {
|
ToolType::Select => match serde_wasm_bindgen::from_value::<tools::select::SelectMessage>(message.clone()) {
|
||||||
Ok(select_message) => Ok(ToolMessage::Select(select_message)),
|
Ok(select_message) => Ok(ToolMessage::Select(select_message)),
|
||||||
Err(err) => Err(Error::new(&format!("Invalid message for {}: {}", tool, err)).into()),
|
Err(err) => Err(Error::new(&format!("Invalid message for {}: {}", tool, err)).into()),
|
||||||
},
|
},
|
||||||
|
|
@ -143,8 +150,8 @@ impl JsEditorHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_document(&self, document: usize) {
|
pub fn select_document(&self, document_id: u64) {
|
||||||
let message = DocumentsMessage::SelectDocument(document);
|
let message = DocumentsMessage::SelectDocument(document_id);
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,8 +180,8 @@ impl JsEditorHandle {
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_document(&self, document: usize) {
|
pub fn close_document(&self, document_id: u64) {
|
||||||
let message = DocumentsMessage::CloseDocument(document);
|
let message = DocumentsMessage::CloseDocument(document_id);
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,6 +195,11 @@ impl JsEditorHandle {
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn close_document_with_confirmation(&self, document_id: u64) {
|
||||||
|
let message = DocumentsMessage::CloseDocumentWithConfirmation(document_id);
|
||||||
|
self.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn close_all_documents_with_confirmation(&self) {
|
pub fn close_all_documents_with_confirmation(&self) {
|
||||||
let message = DocumentsMessage::CloseAllDocumentsWithConfirmation;
|
let message = DocumentsMessage::CloseAllDocumentsWithConfirmation;
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue