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
commit 92a751b32a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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_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

View File

@ -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;
// 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,
),
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,
),
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(

View File

@ -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;
}

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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)?;

View File

@ -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);

View File

@ -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;
}

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) {
// 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;
}

View File

@ -92,17 +92,33 @@ where
})
}
/// Returns the groups without the default group
pub(crate) fn concrete_groups(&self) -> Vec<&Group> {
self.groups_map.values().collect()
}
pub(crate) fn default_group(&self) -> &Group {
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 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.
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();

View File

@ -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()],
)])
}
}

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> {
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);
}

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> {
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);
}

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> {
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);
}

View File

@ -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,
}

View File

@ -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),
}
}
}

View File

@ -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,

View File

@ -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)]

View File

@ -14,7 +14,9 @@ pub struct WorkspaceRevision {
pub apps: Vec<AppRevision>,
#[serde(default)]
pub modified_time: i64,
#[serde(default)]
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_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();

View File

@ -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> {