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

Feat/merge release 0052
This commit is contained in:
Nathan.fooo
2022-09-16 21:47:12 +08:00
committed by GitHub
21 changed files with 184 additions and 88 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -101,8 +101,7 @@ impl FolderPersistence {
} }
let _ = migrations.run_v2_migration(folder_id).await?; 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(()) Ok(())
} }

View File

@ -57,6 +57,7 @@ impl GridBlockManager {
Ok(self.get_block_editor(&block_id).await?) 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> { 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 block_id = row_rev.block_id.clone();
let _ = self.persistence.insert(&row_rev.block_id, &row_rev.id)?; let _ = self.persistence.insert(&row_rev.block_id, &row_rev.id)?;

View File

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

View File

@ -420,6 +420,7 @@ impl GridRevisionEditor {
pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> { pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> {
let row_rev = self.block_manager.delete_row(row_id).await?; 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 { if let Some(row_rev) = row_rev {
self.view_manager.did_delete_row(row_rev).await; self.view_manager.did_delete_row(row_rev).await;
} }

View File

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

View File

@ -92,17 +92,33 @@ where
}) })
} }
/// Returns the groups without the default group pub(crate) fn get_default_group(&self) -> &Group {
pub(crate) fn concrete_groups(&self) -> Vec<&Group> {
self.groups_map.values().collect()
}
pub(crate) fn default_group(&self) -> &Group {
&self.default_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 groups(&self) -> Vec<&Group> {
self.groups_map.values().collect()
}
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. /// 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)| { self.groups_map.iter_mut().for_each(|(_, group)| {
each(group); each(group);
}); });
@ -253,22 +269,6 @@ where
Ok(()) 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<()> { pub fn save_configuration(&self) -> FlowyResult<()> {
let configuration = (&*self.configuration).clone(); let configuration = (&*self.configuration).clone();
let writer = self.writer.clone(); let writer = self.writer.clone();

View File

@ -183,11 +183,11 @@ where
fn groups(&self) -> Vec<Group> { fn groups(&self) -> Vec<Group> {
if self.use_default_group() { if self.use_default_group() {
let mut groups: Vec<Group> = self.group_ctx.concrete_groups().into_iter().cloned().collect(); let mut groups: Vec<Group> = self.group_ctx.groups().into_iter().cloned().collect();
groups.push(self.group_ctx.default_group().clone()); groups.push(self.group_ctx.get_default_group().clone());
groups groups
} else { } 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 mut grouped_rows: Vec<GroupedRow> = vec![];
let cell_bytes = decode_any_cell_data(cell_rev.data, field_rev); let cell_bytes = decode_any_cell_data(cell_rev.data, field_rev);
let cell_data = cell_bytes.parser::<P>()?; 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) { if self.can_group(&group.filter_content, &cell_data) {
grouped_rows.push(GroupedRow { grouped_rows.push(GroupedRow {
row: row_rev.into(), row: row_rev.into(),
@ -264,12 +264,17 @@ where
row_rev: &RowRevision, row_rev: &RowRevision,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>> { ) -> 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) { 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_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
Ok(self.remove_row_if_match(row_rev, &cell_data)) Ok(self.remove_row_if_match(row_rev, &cell_data))
} else { } else {
Ok(vec![]) let group = self.group_ctx.get_default_group();
Ok(vec![GroupChangesetPB::delete(
group.id.clone(),
vec![row_rev.id.clone()],
)])
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,76 @@
use crate::revision::{TrashRevision, WorkspaceRevision}; 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; use std::sync::Arc;
#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] #[derive(Debug, Default, Serialize, Clone, Eq, PartialEq)]
pub struct FolderRevision { pub struct FolderRevision {
pub workspaces: Vec<Arc<WorkspaceRevision>>, pub workspaces: Vec<Arc<WorkspaceRevision>>,
pub trash: Vec<Arc<TrashRevision>>, 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),
}
}
}

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ use flowy_folder_data_model::revision::{AppRevision, FolderRevision, TrashRevisi
use lib_infra::util::move_vec_element; use lib_infra::util::move_vec_element;
use lib_ot::core::*; use lib_ot::core::*;
use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -44,7 +45,9 @@ impl FolderPad {
pub fn from_delta(delta: FolderDelta) -> CollaborateResult<Self> { pub fn from_delta(delta: FolderDelta) -> CollaborateResult<Self> {
// TODO: Reconvert from history if delta.to_str() failed. // TODO: Reconvert from history if delta.to_str() failed.
let content = delta.content()?; 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); tracing::error!("Deserialize folder from {} failed", content);
return CollaborateError::internal().context(format!("Deserialize delta to folder failed: {}", e)); return CollaborateError::internal().context(format!("Deserialize delta to folder failed: {}", e));
})?; })?;
@ -455,6 +458,7 @@ mod tests {
#![allow(clippy::all)] #![allow(clippy::all)]
use crate::{client_folder::folder_pad::FolderPad, entities::folder::FolderDelta}; use crate::{client_folder::folder_pad::FolderPad, entities::folder::FolderDelta};
use chrono::Utc; use chrono::Utc;
use serde::Deserialize;
use flowy_folder_data_model::revision::{ use flowy_folder_data_model::revision::{
AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision, AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision,
@ -478,6 +482,20 @@ mod tests {
assert_eq!(folder, folder_from_delta); 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] #[test]
fn folder_update_workspace() { fn folder_update_workspace() {
let (mut folder, initial_delta, workspace) = test_folder(); let (mut folder, initial_delta, workspace) = test_folder();

View File

@ -23,6 +23,8 @@ impl std::ops::Deref for GridViewRevisionPad {
} }
impl 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 { pub fn new(grid_id: String, view_id: String) -> Self {
let view = Arc::new(GridViewRevision::new(grid_id, view_id)); let view = Arc::new(GridViewRevision::new(grid_id, view_id));
let json = serde_json::to_string(&view).unwrap(); let json = serde_json::to_string(&view).unwrap();
@ -30,11 +32,14 @@ impl GridViewRevisionPad {
Self { view, delta } 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 s = delta.content()?;
let view: GridViewRevision = serde_json::from_str(&s).map_err(|e| { let view: GridViewRevision = serde_json::from_str(&s).map_err(|e| {
let msg = format!("Deserialize delta to GridViewRevision failed: {}", e); let msg = format!("Deserialize delta to GridViewRevision failed: {}", e);
tracing::error!("{}", s); tracing::error!("parsing json: {}", s);
CollaborateError::internal().context(msg) CollaborateError::internal().context(msg)
})?; })?;
Ok(Self { 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)?; 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> { pub fn get_groups_by_field_revs(&self, field_revs: &[Arc<FieldRevision>]) -> Option<GroupConfigurationsByFieldId> {