Display Asterisk on Unsaved Documents (#392)

* ability to mark an open document as unsaved

* unsaved detection now being triggered based on layer tree height

* - rust implementation of unsaved markers
- upgraded eslint

* updated eslint in package.json

* - Renamed GetOpenDocumentsList -> UpdateOpenDocumentsList
- is not -> was not

* changed hash to current identifier to better reflect its meaning

* resolve some merge conflicts

* removed console.log statement leftover from debuging
This commit is contained in:
mfish33
2021-11-30 10:06:07 -08:00
committed by Keavon Chambers
parent c3ec2f76e9
commit fc0b951ab6
9 changed files with 88 additions and 52 deletions
+55 -25
View File
@@ -59,8 +59,9 @@ pub struct VectorManipulatorShape {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DocumentMessageHandler { pub struct DocumentMessageHandler {
pub graphene_document: GrapheneDocument, pub graphene_document: GrapheneDocument,
pub document_history: Vec<DocumentSave>, pub document_undo_history: Vec<DocumentSave>,
pub document_redo_history: Vec<DocumentSave>, pub document_redo_history: Vec<DocumentSave>,
pub saved_document_identifier: u64,
pub name: String, pub name: String,
pub layer_data: HashMap<Vec<LayerId>, LayerData>, pub layer_data: HashMap<Vec<LayerId>, LayerData>,
movement_handler: MovementMessageHandler, movement_handler: MovementMessageHandler,
@@ -72,9 +73,10 @@ impl Default for DocumentMessageHandler {
fn default() -> Self { fn default() -> Self {
Self { Self {
graphene_document: GrapheneDocument::default(), graphene_document: GrapheneDocument::default(),
document_history: Vec::new(), document_undo_history: Vec::new(),
document_redo_history: Vec::new(), document_redo_history: Vec::new(),
name: String::from("Untitled Document"), name: String::from("Untitled Document"),
saved_document_identifier: 0,
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(), layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
movement_handler: MovementMessageHandler::default(), movement_handler: MovementMessageHandler::default(),
transform_layer_handler: TransformLayerMessageHandler::default(), transform_layer_handler: TransformLayerMessageHandler::default(),
@@ -305,8 +307,9 @@ impl DocumentMessageHandler {
pub fn with_name(name: String) -> Self { pub fn with_name(name: String) -> Self {
Self { Self {
graphene_document: GrapheneDocument::default(), graphene_document: GrapheneDocument::default(),
document_history: Vec::new(), document_undo_history: Vec::new(),
document_redo_history: Vec::new(), document_redo_history: Vec::new(),
saved_document_identifier: 0,
name, name,
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(), layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
movement_handler: MovementMessageHandler::default(), movement_handler: MovementMessageHandler::default(),
@@ -332,23 +335,30 @@ impl DocumentMessageHandler {
layer_data(&mut self.layer_data, path) layer_data(&mut self.layer_data, path)
} }
pub fn backup(&mut self) { pub fn backup(&mut self, responses: &mut VecDeque<Message>) {
self.document_redo_history.clear(); self.document_redo_history.clear();
let new_layer_data = self let new_layer_data = self
.layer_data .layer_data
.iter() .iter()
.filter_map(|(key, value)| (!self.graphene_document.layer(key).unwrap().overlay).then(|| (key.clone(), *value))) .filter_map(|(key, value)| (!self.graphene_document.layer(key).unwrap().overlay).then(|| (key.clone(), *value)))
.collect(); .collect();
self.document_history.push((self.graphene_document.clone_without_overlays(), new_layer_data)) self.document_undo_history.push((self.graphene_document.clone_without_overlays(), new_layer_data));
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.push_back(DocumentsMessage::UpdateOpenDocumentsList.into());
} }
pub fn rollback(&mut self) -> Result<(), EditorError> { pub fn rollback(&mut self, responses: &mut VecDeque<Message>) -> Result<(), EditorError> {
self.backup(); self.backup(responses);
self.undo() self.undo(responses)
// TODO: Consider if we should check if the document is saved
} }
pub fn undo(&mut self) -> Result<(), EditorError> { pub fn undo(&mut self, responses: &mut VecDeque<Message>) -> Result<(), EditorError> {
match self.document_history.pop() { // Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.push_back(DocumentsMessage::UpdateOpenDocumentsList.into());
match self.document_undo_history.pop() {
Some((document, layer_data)) => { Some((document, layer_data)) => {
let document = std::mem::replace(&mut self.graphene_document, document); let document = std::mem::replace(&mut self.graphene_document, document);
let layer_data = std::mem::replace(&mut self.layer_data, layer_data); let layer_data = std::mem::replace(&mut self.layer_data, layer_data);
@@ -359,7 +369,10 @@ impl DocumentMessageHandler {
} }
} }
pub fn redo(&mut self) -> Result<(), EditorError> { pub fn redo(&mut self, responses: &mut VecDeque<Message>) -> Result<(), EditorError> {
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.push_back(DocumentsMessage::UpdateOpenDocumentsList.into());
match self.document_redo_history.pop() { match self.document_redo_history.pop() {
Some((document, layer_data)) => { Some((document, layer_data)) => {
let document = std::mem::replace(&mut self.graphene_document, document); let document = std::mem::replace(&mut self.graphene_document, document);
@@ -368,13 +381,26 @@ impl DocumentMessageHandler {
.iter() .iter()
.filter_map(|(key, value)| (!self.graphene_document.layer(key).unwrap().overlay).then(|| (key.clone(), *value))) .filter_map(|(key, value)| (!self.graphene_document.layer(key).unwrap().overlay).then(|| (key.clone(), *value)))
.collect(); .collect();
self.document_history.push((document.clone_without_overlays(), new_layer_data)); self.document_undo_history.push((document.clone_without_overlays(), new_layer_data));
Ok(()) Ok(())
} }
None => Err(EditorError::NoTransactionInProgress), None => Err(EditorError::NoTransactionInProgress),
} }
} }
pub fn current_identifier(&self) -> u64 {
// We can use the last state of the document to serve as the identifier to compare against
// This is useful since when the document is empty the identifier will be 0
self.document_undo_history
.last()
.map(|(graphene_document, _)| graphene_document.current_state_identifier())
.unwrap_or(0)
}
pub fn is_saved(&self) -> bool {
self.current_identifier() == self.saved_document_identifier
}
pub fn layer_panel_entry(&mut self, path: Vec<LayerId>) -> Result<LayerPanelEntry, EditorError> { pub fn layer_panel_entry(&mut self, path: Vec<LayerId>) -> Result<LayerPanelEntry, EditorError> {
let data: LayerData = *layer_data(&mut self.layer_data, &path); let data: LayerData = *layer_data(&mut self.layer_data, &path);
let layer = self.graphene_document.layer(&path)?; let layer = self.graphene_document.layer(&path)?;
@@ -421,13 +447,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
.transform_layer_handler .transform_layer_handler
.process_action(message, (&mut self.layer_data, &mut self.graphene_document, ipp), responses), .process_action(message, (&mut self.layer_data, &mut self.graphene_document, ipp), responses),
DeleteLayer(path) => responses.push_back(DocumentOperation::DeleteLayer { path }.into()), DeleteLayer(path) => responses.push_back(DocumentOperation::DeleteLayer { path }.into()),
StartTransaction => self.backup(), StartTransaction => self.backup(responses),
RollbackTransaction => { RollbackTransaction => {
self.rollback().unwrap_or_else(|e| log::warn!("{}", e)); self.rollback(responses).unwrap_or_else(|e| log::warn!("{}", e));
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]); responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
} }
AbortTransaction => { AbortTransaction => {
self.undo().unwrap_or_else(|e| log::warn!("{}", e)); self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e));
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]); responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
} }
CommitTransaction => (), CommitTransaction => (),
@@ -455,6 +481,10 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
) )
} }
SaveDocument => { SaveDocument => {
self.saved_document_identifier = self.current_identifier();
// Update the save status of the just saved document
responses.push_back(DocumentsMessage::UpdateOpenDocumentsList.into());
let name = match self.name.ends_with(FILE_SAVE_SUFFIX) { let name = match self.name.ends_with(FILE_SAVE_SUFFIX) {
true => self.name.clone(), true => self.name.clone(),
false => self.name.clone() + FILE_SAVE_SUFFIX, false => self.name.clone() + FILE_SAVE_SUFFIX,
@@ -494,13 +524,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
responses.push_back(DocumentMessage::SetSelectedLayers(vec![new_folder_path]).into()); responses.push_back(DocumentMessage::SetSelectedLayers(vec![new_folder_path]).into());
} }
SetBlendModeForSelectedLayers(blend_mode) => { SetBlendModeForSelectedLayers(blend_mode) => {
self.backup(); self.backup(responses);
for path in self.layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) { for path in self.layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) {
responses.push_back(DocumentOperation::SetLayerBlendMode { path, blend_mode }.into()); responses.push_back(DocumentOperation::SetLayerBlendMode { path, blend_mode }.into());
} }
} }
SetOpacityForSelectedLayers(opacity) => { SetOpacityForSelectedLayers(opacity) => {
self.backup(); self.backup(responses);
let opacity = opacity.clamp(0., 1.); let opacity = opacity.clamp(0., 1.);
for path in self.selected_layers().map(|path| path.to_vec()) { for path in self.selected_layers().map(|path| path.to_vec()) {
@@ -520,7 +550,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
responses.push_back(ToolMessage::SelectedLayersChanged.into()); responses.push_back(ToolMessage::SelectedLayersChanged.into());
} }
DeleteSelectedLayers => { DeleteSelectedLayers => {
self.backup(); self.backup(responses);
responses.push_front(ToolMessage::SelectedLayersChanged.into()); responses.push_front(ToolMessage::SelectedLayersChanged.into());
for path in self.selected_layers().map(|path| path.to_vec()) { for path in self.selected_layers().map(|path| path.to_vec()) {
responses.push_front(DocumentOperation::DeleteLayer { path }.into()); responses.push_front(DocumentOperation::DeleteLayer { path }.into());
@@ -533,7 +563,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
} }
} }
DuplicateSelectedLayers => { DuplicateSelectedLayers => {
self.backup(); self.backup(responses);
for path in self.selected_layers_sorted() { for path in self.selected_layers_sorted() {
responses.push_back(DocumentOperation::DuplicateLayer { path }.into()); responses.push_back(DocumentOperation::DuplicateLayer { path }.into());
} }
@@ -564,8 +594,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
responses.push_front(SetSelectedLayers(all_layer_paths).into()); responses.push_front(SetSelectedLayers(all_layer_paths).into());
} }
DeselectAllLayers => responses.push_front(SetSelectedLayers(vec![]).into()), DeselectAllLayers => responses.push_front(SetSelectedLayers(vec![]).into()),
DocumentHistoryBackward => self.undo().unwrap_or_else(|e| log::warn!("{}", e)), DocumentHistoryBackward => self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e)),
DocumentHistoryForward => self.redo().unwrap_or_else(|e| log::warn!("{}", e)), DocumentHistoryForward => self.redo(responses).unwrap_or_else(|e| log::warn!("{}", e)),
Undo => { Undo => {
responses.push_back(SelectMessage::Abort.into()); responses.push_back(SelectMessage::Abort.into());
responses.push_back(DocumentHistoryBackward.into()); responses.push_back(DocumentHistoryBackward.into());
@@ -666,7 +696,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
} }
NudgeSelectedLayers(x, y) => { NudgeSelectedLayers(x, y) => {
self.backup(); self.backup(responses);
for path in self.selected_layers().map(|path| path.to_vec()) { for path in self.selected_layers().map(|path| path.to_vec()) {
let operation = DocumentOperation::TransformLayerInViewport { let operation = DocumentOperation::TransformLayerInViewport {
path, path,
@@ -682,7 +712,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
responses.push_back(DocumentsMessage::PasteIntoFolder { path, insert_index }.into()); responses.push_back(DocumentsMessage::PasteIntoFolder { path, insert_index }.into());
} }
ReorderSelectedLayers(relative_position) => { ReorderSelectedLayers(relative_position) => {
self.backup(); self.backup(responses);
let all_layer_paths = self.all_layers_sorted(); let all_layer_paths = self.all_layers_sorted();
let selected_layers = self.selected_layers_sorted(); let selected_layers = self.selected_layers_sorted();
if let Some(pivot) = match relative_position.signum() { if let Some(pivot) = match relative_position.signum() {
@@ -717,7 +747,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
} }
} }
FlipSelectedLayers(axis) => { FlipSelectedLayers(axis) => {
self.backup(); self.backup(responses);
let scale = match axis { let scale = match axis {
FlipAxis::X => DVec2::new(-1., 1.), FlipAxis::X => DVec2::new(-1., 1.),
FlipAxis::Y => DVec2::new(1., -1.), FlipAxis::Y => DVec2::new(1., -1.),
@@ -739,7 +769,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
} }
} }
AlignSelectedLayers(axis, aggregate) => { AlignSelectedLayers(axis, aggregate) => {
self.backup(); self.backup(responses);
let (paths, boxes): (Vec<_>, Vec<_>) = self let (paths, boxes): (Vec<_>, Vec<_>) = self
.selected_layers() .selected_layers()
.filter_map(|path| self.graphene_document.viewport_bounding_box(path).ok()?.map(|b| (path, b))) .filter_map(|path| self.graphene_document.viewport_bounding_box(path).ok()?.map(|b| (path, b)))
@@ -28,7 +28,7 @@ pub enum DocumentsMessage {
NewDocument, NewDocument,
OpenDocument, OpenDocument,
OpenDocumentFile(String, String), OpenDocumentFile(String, String),
GetOpenDocumentsList, UpdateOpenDocumentsList,
NextDocument, NextDocument,
PrevDocument, PrevDocument,
} }
@@ -55,9 +55,7 @@ impl DocumentsMessageHandler {
fn generate_new_document_name(&self) -> String { fn generate_new_document_name(&self) -> String {
let mut doc_title_numbers = self let mut doc_title_numbers = self
.document_ids .ordered_document_iterator()
.iter()
.filter_map(|id| self.documents.get(&id))
.map(|doc| { .map(|doc| {
doc.name doc.name
.rsplit_once(DEFAULT_DOCUMENT_NAME) .rsplit_once(DEFAULT_DOCUMENT_NAME)
@@ -85,7 +83,11 @@ impl DocumentsMessageHandler {
self.documents.insert(self.document_id_counter, new_document); self.documents.insert(self.document_id_counter, new_document);
// Send the new list of document tab names // Send the new list of document tab names
let open_documents = self.document_ids.iter().filter_map(|id| self.documents.get(&id).map(|doc| doc.name.clone())).collect::<Vec<String>>(); let open_documents = self
.document_ids
.iter()
.filter_map(|id| self.documents.get(&id).map(|doc| (doc.name.clone(), doc.is_saved())))
.collect::<Vec<_>>();
responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into()); responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into());
@@ -96,6 +98,11 @@ impl DocumentsMessageHandler {
responses.push_back(DocumentMessage::LayerChanged(layer.clone()).into()); responses.push_back(DocumentMessage::LayerChanged(layer.clone()).into());
} }
} }
// Returns an iterator over the open documents in order
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"))
}
} }
impl Default for DocumentsMessageHandler { impl Default for DocumentsMessageHandler {
@@ -170,7 +177,7 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
}; };
// Send the new list of document tab names // Send the new list of document tab names
let open_documents = self.document_ids.iter().filter_map(|id| self.documents.get(&id).map(|doc| doc.name.clone())).collect(); let open_documents = self.ordered_document_iterator().map(|doc| (doc.name.clone(), doc.is_saved())).collect();
// 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());
@@ -209,9 +216,9 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
), ),
} }
} }
GetOpenDocumentsList => { UpdateOpenDocumentsList => {
// Send the list of document tab names // Send the list of document tab names
let open_documents = self.documents.values().map(|doc| doc.name.clone()).collect(); let open_documents = self.ordered_document_iterator().map(|doc| (doc.name.clone(), doc.is_saved())).collect();
responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into()); responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into());
} }
NextDocument => { NextDocument => {
@@ -10,7 +10,7 @@ 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_index: usize },
UpdateOpenDocumentsList { open_documents: Vec<String> }, UpdateOpenDocumentsList { open_documents: Vec<(String, bool)> },
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_index: usize },
@@ -393,7 +393,6 @@ export default defineComponent({
registerResponseHandler(ResponseType.DisplayFolderTreeStructure, (responseData: Response) => { registerResponseHandler(ResponseType.DisplayFolderTreeStructure, (responseData: Response) => {
const expandData = responseData as DisplayFolderTreeStructure; const expandData = responseData as DisplayFolderTreeStructure;
if (!expandData) return; if (!expandData) return;
console.log(expandData);
const path = [] as Array<bigint>; const path = [] as Array<bigint>;
this.layers = [] as Array<LayerPanelEntry>; this.layers = [] as Array<LayerPanelEntry>;
@@ -4,7 +4,7 @@
<MenuBarInput v-if="platform !== ApplicationPlatform.Mac" /> <MenuBarInput v-if="platform !== ApplicationPlatform.Mac" />
</div> </div>
<div class="header-third"> <div class="header-third">
<WindowTitle :title="`${documents.title}${documents.unsaved ? '*' : ''} - Graphite`" /> <WindowTitle :title="`${documents.activeDocument} - Graphite`" />
</div> </div>
<div class="header-third"> <div class="header-third">
<WindowButtonsWindows :maximized="maximized" v-if="platform === ApplicationPlatform.Windows || platform === ApplicationPlatform.Linux" /> <WindowButtonsWindows :maximized="maximized" v-if="platform === ApplicationPlatform.Windows || platform === ApplicationPlatform.Linux" />
+5 -8
View File
@@ -17,10 +17,11 @@ import { panicProxy } from "@/utilities/panic-proxy";
const wasm = import("@/../wasm/pkg").then(panicProxy); const wasm = import("@/../wasm/pkg").then(panicProxy);
const state = reactive({ const state = reactive({
title: "", documents: [] as string[],
unsaved: false,
documents: [] as Array<string>,
activeDocumentIndex: 0, activeDocumentIndex: 0,
get activeDocument() {
return this.documents[this.activeDocumentIndex];
},
}); });
export async function selectDocument(tabIndex: number) { export async function selectDocument(tabIndex: number) {
@@ -83,17 +84,13 @@ export default readonly(state);
registerResponseHandler(ResponseType.UpdateOpenDocumentsList, (responseData: Response) => { registerResponseHandler(ResponseType.UpdateOpenDocumentsList, (responseData: Response) => {
const documentListData = responseData as UpdateOpenDocumentsList; const documentListData = responseData as UpdateOpenDocumentsList;
if (documentListData) { state.documents = documentListData.open_documents.map(({ name, isSaved }) => `${name}${isSaved ? "" : "*"}`);
state.documents = documentListData.open_documents;
state.title = state.documents[state.activeDocumentIndex];
}
}); });
registerResponseHandler(ResponseType.SetActiveDocument, (responseData: Response) => { registerResponseHandler(ResponseType.SetActiveDocument, (responseData: Response) => {
const documentData = responseData as SetActiveDocument; const documentData = responseData as SetActiveDocument;
if (documentData) { if (documentData) {
state.activeDocumentIndex = documentData.document_index; state.activeDocumentIndex = documentData.document_index;
state.title = state.documents[state.activeDocumentIndex];
} }
}); });
+3 -2
View File
@@ -119,10 +119,11 @@ export type Response =
| DisplayConfirmationToCloseAllDocuments; | DisplayConfirmationToCloseAllDocuments;
export interface UpdateOpenDocumentsList { export interface UpdateOpenDocumentsList {
open_documents: Array<string>; open_documents: { name: string; isSaved: boolean }[];
} }
function newUpdateOpenDocumentsList(input: any): UpdateOpenDocumentsList { function newUpdateOpenDocumentsList(input: any): UpdateOpenDocumentsList {
return { open_documents: input.open_documents }; const openDocuments = input.open_documents.map((docData: [string, boolean]) => ({ name: docData[0], isSaved: docData[1] }));
return { open_documents: openDocuments };
} }
export interface Color { export interface Color {
+1 -1
View File
@@ -88,7 +88,7 @@ pub fn select_document(document: usize) {
#[wasm_bindgen] #[wasm_bindgen]
pub fn get_open_documents_list() { pub fn get_open_documents_list() {
let message = DocumentsMessage::GetOpenDocumentsList; let message = DocumentsMessage::UpdateOpenDocumentsList;
dispatch(message); dispatch(message);
} }
+7 -5
View File
@@ -14,15 +14,17 @@ use crate::{
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Document { pub struct Document {
pub root: Layer, pub root: Layer,
/// The state_identifier serves to provide a way to uniquely identify a particular state that the document is in.
/// This identifier is not a hash and is not guaranteed to be equal for equivalent documents.
#[serde(skip)] #[serde(skip)]
pub hasher: DefaultHasher, pub state_identifier: DefaultHasher,
} }
impl Default for Document { impl Default for Document {
fn default() -> Self { fn default() -> Self {
Self { Self {
root: Layer::new(LayerDataType::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array()), root: Layer::new(LayerDataType::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array()),
hasher: DefaultHasher::new(), state_identifier: DefaultHasher::new(),
} }
} }
} }
@@ -38,8 +40,8 @@ impl Document {
self.root.cache.clone() self.root.cache.clone()
} }
pub fn hash(&self) -> u64 { pub fn current_state_identifier(&self) -> u64 {
self.hasher.finish() self.state_identifier.finish()
} }
pub fn serialize_document(&self) -> String { pub fn serialize_document(&self) -> String {
@@ -309,7 +311,7 @@ impl Document {
/// Mutate the document by applying the `operation` to it. If the operation necessitates a /// Mutate the document by applying the `operation` to it. If the operation necessitates a
/// reaction from the frontend, responses may be returned. /// reaction from the frontend, responses may be returned.
pub fn handle_operation(&mut self, operation: &Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> { pub fn handle_operation(&mut self, operation: &Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
operation.pseudo_hash().hash(&mut self.hasher); operation.pseudo_hash().hash(&mut self.state_identifier);
use DocumentResponse::*; use DocumentResponse::*;
let responses = match &operation { let responses = match &operation {