Преглед на файлове

Merge pull request #1073 from AppFlowy-IO/feat/merge_release_0052

Feat/merge release 0052
Nathan.fooo преди 2 години
родител
ревизия
92a751b32a
променени са 21 файла, в които са добавени 182 реда и са изтрити 86 реда
  1. 1 1
      frontend/Makefile.toml
  2. 24 28
      frontend/app_flowy/lib/plugins/board/presentation/board_page.dart
  3. 3 9
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart
  4. 3 4
      frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs
  5. 1 2
      frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs
  6. 1 0
      frontend/rust-lib/flowy-grid/src/services/block_manager.rs
  7. 1 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs
  8. 1 0
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  9. 2 0
      frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs
  10. 20 20
      frontend/rust-lib/flowy-grid/src/services/group/configuration.rs
  11. 10 5
      frontend/rust-lib/flowy-grid/src/services/group/controller.rs
  12. 3 3
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs
  13. 3 3
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs
  14. 3 3
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs
  15. 3 0
      shared-lib/flowy-folder-data-model/src/revision/app_rev.rs
  16. 69 2
      shared-lib/flowy-folder-data-model/src/revision/folder_rev.rs
  17. 2 0
      shared-lib/flowy-folder-data-model/src/revision/trash_rev.rs
  18. 2 1
      shared-lib/flowy-folder-data-model/src/revision/view_rev.rs
  19. 2 0
      shared-lib/flowy-folder-data-model/src/revision/workspace_rev.rs
  20. 19 1
      shared-lib/flowy-sync/src/client_folder/folder_pad.rs
  21. 9 4
      shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs

+ 1 - 1
frontend/Makefile.toml

@@ -22,7 +22,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
 CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
 CARGO_MAKE_CRATE_NAME = "dart-ffi"
 LIB_NAME = "dart_ffi"
-CURRENT_APP_VERSION = "0.0.5.1"
+CURRENT_APP_VERSION = "0.0.5.2"
 FEATURES = "flutter"
 PRODUCT_NAME = "AppFlowy"
 # CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html

+ 24 - 28
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -187,35 +187,31 @@ class _BoardContentState extends State<BoardContent> {
   }
 
   Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) {
-    final boardCustomData = columnData.customData as BoardCustomData;
-    final group = boardCustomData.group;
-
-    if (group.isDefault) {
-      return const SizedBox();
-    } else {
-      return AppFlowyGroupFooter(
-        icon: SizedBox(
-          height: 20,
-          width: 20,
-          child: svgWidget(
-            "home/add",
-            color: context.read<AppTheme>().iconColor,
-          ),
-        ),
-        title: FlowyText.medium(
-          LocaleKeys.board_column_create_new_card.tr(),
-          fontSize: 14,
-          color: context.read<AppTheme>().textColor,
+    // final boardCustomData = columnData.customData as BoardCustomData;
+    // final group = boardCustomData.group;
+
+    return AppFlowyGroupFooter(
+      icon: SizedBox(
+        height: 20,
+        width: 20,
+        child: svgWidget(
+          "home/add",
+          color: context.read<AppTheme>().iconColor,
         ),
-        height: 50,
-        margin: config.footerPadding,
-        onAddButtonClick: () {
-          context.read<BoardBloc>().add(
-                BoardEvent.createBottomRow(columnData.id),
-              );
-        },
-      );
-    }
+      ),
+      title: FlowyText.medium(
+        LocaleKeys.board_column_create_new_card.tr(),
+        fontSize: 14,
+        color: context.read<AppTheme>().textColor,
+      ),
+      height: 50,
+      margin: config.footerPadding,
+      onAddButtonClick: () {
+        context.read<BoardBloc>().add(
+              BoardEvent.createBottomRow(columnData.id),
+            );
+      },
+    );
   }
 
   Widget _buildCard(

+ 3 - 9
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart

@@ -1,5 +1,4 @@
 import 'package:app_flowy/plugins/grid/application/row/row_action_sheet_bloc.dart';
-import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:flowy_infra/image.dart';
@@ -151,14 +150,9 @@ extension _RowActionExtension on _RowAction {
             .add(const RowActionSheetEvent.duplicateRow());
         break;
       case _RowAction.delete:
-        NavigatorAlertDialog(
-          title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
-          confirm: () {
-            context
-                .read<RowActionSheetBloc>()
-                .add(const RowActionSheetEvent.deleteRow());
-          },
-        ).show(context);
+        context
+            .read<RowActionSheetBloc>()
+            .add(const RowActionSheetEvent.deleteRow());
 
         break;
     }

+ 3 - 4
frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs

@@ -78,7 +78,7 @@ impl FolderMigration {
 
         let folder = FolderPad::new(workspaces, trash)?;
         KV::set_bool(&key, true);
-        tracing::trace!("Run folder v1 migration");
+        tracing::info!("Run folder v1 migration");
         Ok(Some(folder))
     }
 
@@ -89,11 +89,10 @@ impl FolderMigration {
         }
         let _ = self.migration_folder_rev_struct(folder_id).await?;
         KV::set_bool(&key, true);
-        tracing::trace!("Run folder v2 migration");
+        // tracing::info!("Run folder v2 migration");
         Ok(())
     }
 
-    #[allow(dead_code)]
     pub async fn run_v3_migration(&self, folder_id: &FolderId) -> FlowyResult<()> {
         let key = migration_flag_key(&self.user_id, V3_MIGRATION);
         if KV::get_bool(&key) {
@@ -101,7 +100,7 @@ impl FolderMigration {
         }
         let _ = self.migration_folder_rev_struct(folder_id).await?;
         KV::set_bool(&key, true);
-        tracing::trace!("Run folder v3 migration");
+        tracing::info!("Run folder v3 migration");
         Ok(())
     }
 

+ 1 - 2
frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs

@@ -101,8 +101,7 @@ impl FolderPersistence {
         }
 
         let _ = migrations.run_v2_migration(folder_id).await?;
-
-        // let _ = migrations.run_v3_migration(folder_id).await?;
+        let _ = migrations.run_v3_migration(folder_id).await?;
         Ok(())
     }
 

+ 1 - 0
frontend/rust-lib/flowy-grid/src/services/block_manager.rs

@@ -57,6 +57,7 @@ impl GridBlockManager {
         Ok(self.get_block_editor(&block_id).await?)
     }
 
+    #[tracing::instrument(level = "trace", skip(self, start_row_id), err)]
     pub(crate) async fn create_row(&self, row_rev: RowRevision, start_row_id: Option<String>) -> FlowyResult<i32> {
         let block_id = row_rev.block_id.clone();
         let _ = self.persistence.insert(&row_rev.block_id, &row_rev.id)?;

+ 1 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs

@@ -29,6 +29,7 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder {
 #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct RichTextTypeOptionPB {
     #[pb(index = 1)]
+    #[serde(default)]
     data: String, //It's not used yet
 }
 impl_type_option!(RichTextTypeOptionPB, FieldType::RichText);

+ 1 - 0
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -420,6 +420,7 @@ impl GridRevisionEditor {
 
     pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> {
         let row_rev = self.block_manager.delete_row(row_id).await?;
+        tracing::trace!("Did delete row:{:?}", row_rev);
         if let Some(row_rev) = row_rev {
             self.view_manager.did_delete_row(row_rev).await;
         }

+ 2 - 0
frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs

@@ -109,6 +109,7 @@ impl GridViewRevisionEditor {
         }
     }
 
+    #[tracing::instrument(level = "trace", skip_all)]
     pub(crate) async fn did_delete_row(&self, row_rev: &RowRevision) {
         // Send the group notification if the current view has groups;
         let changesets = self
@@ -116,6 +117,7 @@ impl GridViewRevisionEditor {
             .await;
 
         if let Some(changesets) = changesets {
+            tracing::trace!("{:?}", changesets);
             for changeset in changesets {
                 self.notify_did_update_group(changeset).await;
             }

+ 20 - 20
frontend/rust-lib/flowy-grid/src/services/group/configuration.rs

@@ -92,17 +92,33 @@ where
         })
     }
 
+    pub(crate) fn get_default_group(&self) -> &Group {
+        &self.default_group
+    }
+
+    pub(crate) fn get_mut_default_group(&mut self) -> &mut Group {
+        &mut self.default_group
+    }
+
     /// Returns the groups without the default group
-    pub(crate) fn concrete_groups(&self) -> Vec<&Group> {
+    pub(crate) fn groups(&self) -> Vec<&Group> {
         self.groups_map.values().collect()
     }
 
-    pub(crate) fn default_group(&self) -> &Group {
-        &self.default_group
+    pub(crate) fn get_mut_group(&mut self, group_id: &str) -> Option<&mut Group> {
+        self.groups_map.get_mut(group_id)
+    }
+
+    // Returns the index and group specified by the group_id
+    pub(crate) fn get_group(&self, group_id: &str) -> Option<(usize, &Group)> {
+        match (self.groups_map.get_index_of(group_id), self.groups_map.get(group_id)) {
+            (Some(index), Some(group)) => Some((index, group)),
+            _ => None,
+        }
     }
 
     /// Iterate mut the groups. The default group will be the last one that get mutated.
-    pub(crate) fn iter_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) {
+    pub(crate) fn iter_mut_all_groups(&mut self, mut each: impl FnMut(&mut Group)) {
         self.groups_map.iter_mut().for_each(|(_, group)| {
             each(group);
         });
@@ -253,22 +269,6 @@ where
         Ok(())
     }
 
-    pub(crate) fn get_mut_default_group(&mut self) -> &mut Group {
-        &mut self.default_group
-    }
-
-    pub(crate) fn get_mut_group(&mut self, group_id: &str) -> Option<&mut Group> {
-        self.groups_map.get_mut(group_id)
-    }
-
-    // Returns the index and group specified by the group_id
-    pub(crate) fn get_group(&self, group_id: &str) -> Option<(usize, &Group)> {
-        match (self.groups_map.get_index_of(group_id), self.groups_map.get(group_id)) {
-            (Some(index), Some(group)) => Some((index, group)),
-            _ => None,
-        }
-    }
-
     pub fn save_configuration(&self) -> FlowyResult<()> {
         let configuration = (&*self.configuration).clone();
         let writer = self.writer.clone();

+ 10 - 5
frontend/rust-lib/flowy-grid/src/services/group/controller.rs

@@ -183,11 +183,11 @@ where
 
     fn groups(&self) -> Vec<Group> {
         if self.use_default_group() {
-            let mut groups: Vec<Group> = self.group_ctx.concrete_groups().into_iter().cloned().collect();
-            groups.push(self.group_ctx.default_group().clone());
+            let mut groups: Vec<Group> = self.group_ctx.groups().into_iter().cloned().collect();
+            groups.push(self.group_ctx.get_default_group().clone());
             groups
         } else {
-            self.group_ctx.concrete_groups().into_iter().cloned().collect()
+            self.group_ctx.groups().into_iter().cloned().collect()
         }
     }
 
@@ -208,7 +208,7 @@ where
                 let mut grouped_rows: Vec<GroupedRow> = vec![];
                 let cell_bytes = decode_any_cell_data(cell_rev.data, field_rev);
                 let cell_data = cell_bytes.parser::<P>()?;
-                for group in self.group_ctx.concrete_groups() {
+                for group in self.group_ctx.groups() {
                     if self.can_group(&group.filter_content, &cell_data) {
                         grouped_rows.push(GroupedRow {
                             row: row_rev.into(),
@@ -264,12 +264,17 @@ where
         row_rev: &RowRevision,
         field_rev: &FieldRevision,
     ) -> FlowyResult<Vec<GroupChangesetPB>> {
+        // if the cell_rev is none, then the row must be crated from the default group.
         if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
             let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
             let cell_data = cell_bytes.parser::<P>()?;
             Ok(self.remove_row_if_match(row_rev, &cell_data))
         } else {
-            Ok(vec![])
+            let group = self.group_ctx.get_default_group();
+            Ok(vec![GroupChangesetPB::delete(
+                group.id.clone(),
+                vec![row_rev.id.clone()],
+            )])
         }
     }
 

+ 3 - 3
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs

@@ -41,7 +41,7 @@ impl GroupAction for CheckboxGroupController {
 
     fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
         let mut changesets = vec![];
-        self.group_ctx.iter_mut_groups(|group| {
+        self.group_ctx.iter_mut_all_groups(|group| {
             let mut changeset = GroupChangesetPB::new(group.id.clone());
             let is_contained = group.contains_row(&row_rev.id);
             if group.id == CHECK && cell_data.is_check() {
@@ -63,7 +63,7 @@ impl GroupAction for CheckboxGroupController {
 
     fn remove_row_if_match(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
         let mut changesets = vec![];
-        self.group_ctx.iter_mut_groups(|group| {
+        self.group_ctx.iter_mut_all_groups(|group| {
             let mut changeset = GroupChangesetPB::new(group.id.clone());
             if group.contains_row(&row_rev.id) {
                 changeset.deleted_rows.push(row_rev.id.clone());
@@ -79,7 +79,7 @@ impl GroupAction for CheckboxGroupController {
 
     fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
         let mut group_changeset = vec![];
-        self.group_ctx.iter_mut_groups(|group| {
+        self.group_ctx.iter_mut_all_groups(|group| {
             if let Some(changeset) = move_group_row(group, &mut context) {
                 group_changeset.push(changeset);
             }

+ 3 - 3
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs

@@ -28,7 +28,7 @@ impl GroupAction for MultiSelectGroupController {
 
     fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
         let mut changesets = vec![];
-        self.group_ctx.iter_mut_groups(|group| {
+        self.group_ctx.iter_mut_all_groups(|group| {
             if let Some(changeset) = add_select_option_row(group, cell_data, row_rev) {
                 changesets.push(changeset);
             }
@@ -38,7 +38,7 @@ impl GroupAction for MultiSelectGroupController {
 
     fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
         let mut changesets = vec![];
-        self.group_ctx.iter_mut_groups(|group| {
+        self.group_ctx.iter_mut_all_groups(|group| {
             if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
                 changesets.push(changeset);
             }
@@ -48,7 +48,7 @@ impl GroupAction for MultiSelectGroupController {
 
     fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
         let mut group_changeset = vec![];
-        self.group_ctx.iter_mut_groups(|group| {
+        self.group_ctx.iter_mut_all_groups(|group| {
             if let Some(changeset) = move_group_row(group, &mut context) {
                 group_changeset.push(changeset);
             }

+ 3 - 3
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs

@@ -28,7 +28,7 @@ impl GroupAction for SingleSelectGroupController {
 
     fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
         let mut changesets = vec![];
-        self.group_ctx.iter_mut_groups(|group| {
+        self.group_ctx.iter_mut_all_groups(|group| {
             if let Some(changeset) = add_select_option_row(group, cell_data, row_rev) {
                 changesets.push(changeset);
             }
@@ -38,7 +38,7 @@ impl GroupAction for SingleSelectGroupController {
 
     fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
         let mut changesets = vec![];
-        self.group_ctx.iter_mut_groups(|group| {
+        self.group_ctx.iter_mut_all_groups(|group| {
             if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
                 changesets.push(changeset);
             }
@@ -48,7 +48,7 @@ impl GroupAction for SingleSelectGroupController {
 
     fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
         let mut group_changeset = vec![];
-        self.group_ctx.iter_mut_groups(|group| {
+        self.group_ctx.iter_mut_all_groups(|group| {
             if let Some(changeset) = move_group_row(group, &mut context) {
                 group_changeset.push(changeset);
             }

+ 3 - 0
shared-lib/flowy-folder-data-model/src/revision/app_rev.rs

@@ -17,10 +17,13 @@ pub struct AppRevision {
 
     pub belongings: Vec<ViewRevision>,
 
+    #[serde(default)]
     pub version: i64,
 
+    #[serde(default)]
     pub modified_time: i64,
 
+    #[serde(default)]
     pub create_time: i64,
 }
 

+ 69 - 2
shared-lib/flowy-folder-data-model/src/revision/folder_rev.rs

@@ -1,9 +1,76 @@
 use crate::revision::{TrashRevision, WorkspaceRevision};
-use serde::{Deserialize, Serialize};
+use serde::de::{MapAccess, Visitor};
+use serde::{de, Deserialize, Deserializer, Serialize};
+use std::fmt;
 use std::sync::Arc;
 
-#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)]
+#[derive(Debug, Default, Serialize, Clone, Eq, PartialEq)]
 pub struct FolderRevision {
     pub workspaces: Vec<Arc<WorkspaceRevision>>,
     pub trash: Vec<Arc<TrashRevision>>,
 }
+
+impl<'de> Deserialize<'de> for FolderRevision {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct FolderVisitor<'a>(&'a mut Option<FolderRevision>);
+        impl<'de, 'a> Visitor<'de> for FolderVisitor<'a> {
+            type Value = ();
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str("Expect struct FolderRevision")
+            }
+
+            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+            where
+                A: MapAccess<'de>,
+            {
+                let f = |map: &mut A,
+                         workspaces: &mut Option<Vec<WorkspaceRevision>>,
+                         trash: &mut Option<Vec<TrashRevision>>| match map.next_key::<String>()
+                {
+                    Ok(Some(key)) => {
+                        if key == "workspaces" && workspaces.is_none() {
+                            *workspaces = Some(map.next_value::<Vec<WorkspaceRevision>>().ok()?);
+                        }
+                        if key == "trash" && trash.is_none() {
+                            *trash = Some(map.next_value::<Vec<TrashRevision>>().ok()?);
+                        }
+                        Some(())
+                    }
+                    Ok(None) => None,
+                    Err(_e) => None,
+                };
+
+                let mut workspaces: Option<Vec<WorkspaceRevision>> = None;
+                let mut trash: Option<Vec<TrashRevision>> = None;
+                while f(&mut map, &mut workspaces, &mut trash).is_some() {
+                    if workspaces.is_some() && trash.is_some() {
+                        break;
+                    }
+                }
+
+                *self.0 = Some(FolderRevision {
+                    workspaces: workspaces.unwrap_or_default().into_iter().map(Arc::new).collect(),
+                    trash: trash.unwrap_or_default().into_iter().map(Arc::new).collect(),
+                });
+                Ok(())
+            }
+        }
+
+        let mut folder_rev: Option<FolderRevision> = None;
+        const FIELDS: &[&str] = &["workspaces", "trash"];
+        let _ = serde::Deserializer::deserialize_struct(
+            deserializer,
+            "FolderRevision",
+            FIELDS,
+            FolderVisitor(&mut folder_rev),
+        );
+
+        match folder_rev {
+            None => Err(de::Error::missing_field("workspaces or trash")),
+            Some(folder_rev) => Ok(folder_rev),
+        }
+    }
+}

+ 2 - 0
shared-lib/flowy-folder-data-model/src/revision/trash_rev.rs

@@ -8,8 +8,10 @@ pub struct TrashRevision {
 
     pub name: String,
 
+    #[serde(default)]
     pub modified_time: i64,
 
+    #[serde(default)]
     pub create_time: i64,
 
     pub ty: TrashTypeRevision,

+ 2 - 1
shared-lib/flowy-folder-data-model/src/revision/view_rev.rs

@@ -9,7 +9,6 @@ pub fn gen_view_id() -> String {
 pub struct ViewRevision {
     pub id: String,
 
-    // Maybe app_id or vi
     #[serde(rename = "belong_to_id")]
     pub app_id: String,
 
@@ -24,8 +23,10 @@ pub struct ViewRevision {
 
     pub belongings: Vec<ViewRevision>,
 
+    #[serde(default)]
     pub modified_time: i64,
 
+    #[serde(default)]
     pub create_time: i64,
 
     #[serde(default)]

+ 2 - 0
shared-lib/flowy-folder-data-model/src/revision/workspace_rev.rs

@@ -14,7 +14,9 @@ pub struct WorkspaceRevision {
 
     pub apps: Vec<AppRevision>,
 
+    #[serde(default)]
     pub modified_time: i64,
 
+    #[serde(default)]
     pub create_time: i64,
 }

+ 19 - 1
shared-lib/flowy-sync/src/client_folder/folder_pad.rs

@@ -12,6 +12,7 @@ use flowy_folder_data_model::revision::{AppRevision, FolderRevision, TrashRevisi
 use lib_infra::util::move_vec_element;
 use lib_ot::core::*;
 
+use serde::Deserialize;
 use std::sync::Arc;
 
 #[derive(Debug, Clone, Eq, PartialEq)]
@@ -44,7 +45,9 @@ impl FolderPad {
     pub fn from_delta(delta: FolderDelta) -> CollaborateResult<Self> {
         // TODO: Reconvert from history if delta.to_str() failed.
         let content = delta.content()?;
-        let folder_rev: FolderRevision = serde_json::from_str(&content).map_err(|e| {
+        let mut deserializer = serde_json::Deserializer::from_reader(content.as_bytes());
+
+        let folder_rev = FolderRevision::deserialize(&mut deserializer).map_err(|e| {
             tracing::error!("Deserialize folder from {} failed", content);
             return CollaborateError::internal().context(format!("Deserialize delta to folder failed: {}", e));
         })?;
@@ -455,6 +458,7 @@ mod tests {
     #![allow(clippy::all)]
     use crate::{client_folder::folder_pad::FolderPad, entities::folder::FolderDelta};
     use chrono::Utc;
+    use serde::Deserialize;
 
     use flowy_folder_data_model::revision::{
         AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision,
@@ -478,6 +482,20 @@ mod tests {
         assert_eq!(folder, folder_from_delta);
     }
 
+    #[test]
+    fn folder_deserialize_invalid_json_test() {
+        for json in vec![
+            // No timestamp
+            r#"{"workspaces":[{"id":"1","name":"first workspace","desc":"","apps":[]}],"trash":[]}"#,
+            // Trailing characters
+            r#"{"workspaces":[{"id":"1","name":"first workspace","desc":"","apps":[]}],"trash":[]}123"#,
+        ] {
+            let mut deserializer = serde_json::Deserializer::from_reader(json.as_bytes());
+            let folder_rev = FolderRevision::deserialize(&mut deserializer).unwrap();
+            assert_eq!(folder_rev.workspaces.first().as_ref().unwrap().name, "first workspace");
+        }
+    }
+
     #[test]
     fn folder_update_workspace() {
         let (mut folder, initial_delta, workspace) = test_folder();

+ 9 - 4
shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs

@@ -23,6 +23,8 @@ impl std::ops::Deref for GridViewRevisionPad {
 }
 
 impl GridViewRevisionPad {
+    // For the moment, the view_id is equal to grid_id. The grid_id represents the database id.
+    // A database can be referenced by multiple views.
     pub fn new(grid_id: String, view_id: String) -> Self {
         let view = Arc::new(GridViewRevision::new(grid_id, view_id));
         let json = serde_json::to_string(&view).unwrap();
@@ -30,11 +32,14 @@ impl GridViewRevisionPad {
         Self { view, delta }
     }
 
-    pub fn from_delta(delta: Delta) -> CollaborateResult<Self> {
+    pub fn from_delta(view_id: &str, delta: Delta) -> CollaborateResult<Self> {
+        if delta.is_empty() {
+            return Ok(GridViewRevisionPad::new(view_id.to_owned(), view_id.to_owned()));
+        }
         let s = delta.content()?;
         let view: GridViewRevision = serde_json::from_str(&s).map_err(|e| {
             let msg = format!("Deserialize delta to GridViewRevision failed: {}", e);
-            tracing::error!("{}", s);
+            tracing::error!("parsing json: {}", s);
             CollaborateError::internal().context(msg)
         })?;
         Ok(Self {
@@ -43,9 +48,9 @@ impl GridViewRevisionPad {
         })
     }
 
-    pub fn from_revisions(_grid_id: &str, revisions: Vec<Revision>) -> CollaborateResult<Self> {
+    pub fn from_revisions(view_id: &str, revisions: Vec<Revision>) -> CollaborateResult<Self> {
         let delta: Delta = make_text_delta_from_revisions(revisions)?;
-        Self::from_delta(delta)
+        Self::from_delta(view_id, delta)
     }
 
     pub fn get_groups_by_field_revs(&self, field_revs: &[Arc<FieldRevision>]) -> Option<GroupConfigurationsByFieldId> {