From 1fc443d9acc7687b123c457eebcd817ce00c5f7f Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Mon, 15 Aug 2022 17:22:55 +0800 Subject: [PATCH 1/6] feat: place holder --- .../example/lib/plugin/image_node_widget.dart | 71 +++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart index 7a47802163..4270897397 100644 --- a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart +++ b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart @@ -34,6 +34,8 @@ class ImageNodeBuilder extends NodeWidgetBuilder { }); } +const double placeholderHeight = 132; + class ImageNodeWidget extends StatefulWidget { final Node node; final EditorState editorState; @@ -49,6 +51,7 @@ class ImageNodeWidget extends StatefulWidget { } class _ImageNodeWidgetState extends State with Selectable { + bool isHovered = false; Node get node => widget.node; EditorState get editorState => widget.editorState; String get src => widget.node.attributes['image_src'] as String; @@ -88,13 +91,73 @@ class _ImageNodeWidgetState extends State with Selectable { return _build(context); } + Widget _loadingBuilder( + BuildContext context, Widget widget, ImageChunkEvent? evt) { + if (evt == null) { + return widget; + } + return Container( + alignment: Alignment.center, + height: placeholderHeight, + child: const Text("Loading..."), + ); + } + + Widget _errorBuilder( + BuildContext context, Object obj, StackTrace? stackTrace) { + return Container( + alignment: Alignment.center, + height: placeholderHeight, + child: const Text("Error..."), + ); + } + + Widget _frameBuilder( + BuildContext context, + Widget child, + int? frame, + bool wasSynchronouslyLoaded, + ) { + if (frame == null) { + return Container( + alignment: Alignment.center, + height: placeholderHeight, + child: const Text("Loading..."), + ); + } + + return child; + } + Widget _build(BuildContext context) { return Column( children: [ - Image.network( - src, - width: MediaQuery.of(context).size.width, - ) + MouseRegion( + onEnter: (event) { + setState(() { + isHovered = true; + }); + }, + onExit: (event) { + setState(() { + isHovered = false; + }); + }, + child: Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + border: Border.all( + color: isHovered ? Colors.blue : Colors.grey, + ), + borderRadius: const BorderRadius.all(Radius.circular(20))), + child: Image.network( + src, + width: MediaQuery.of(context).size.width, + frameBuilder: _frameBuilder, + loadingBuilder: _loadingBuilder, + errorBuilder: _errorBuilder, + ), + )), ], ); } From 9456fbd57388f53ad84afdf2d6608997867909dc Mon Sep 17 00:00:00 2001 From: appflowy Date: Tue, 16 Aug 2022 20:34:12 +0800 Subject: [PATCH 2/6] chore: fix some bugs --- .../plugins/board/application/board_bloc.dart | 4 ++- .../card/board_select_option_cell.dart | 8 ++++- .../presentation/card/board_text_cell.dart | 11 +++++-- .../presentation/card/board_url_cell.dart | 32 ++++++++++--------- .../plugins/board/presentation/card/card.dart | 2 +- .../cell/cell_service/context_builder.dart | 2 +- .../widgets/cell/date_cell/date_editor.dart | 2 +- .../widgets/cell/number_cell.dart | 11 +++++-- .../flowy-grid/src/services/grid_editor.rs | 7 ++-- .../src/services/grid_view_editor.rs | 2 +- .../src/services/grid_view_manager.rs | 8 +++-- 11 files changed, 58 insertions(+), 31 deletions(-) diff --git a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart index ed15c9703c..c837e4346f 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart @@ -253,5 +253,7 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate { } @override - void updateRow(String groupId, RowPB row) {} + void updateRow(String groupId, RowPB row) { + // + } } diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart index d430f869c1..373bb3c850 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart @@ -42,7 +42,13 @@ class _BoardSelectOptionCellState extends State { .toList(); return Align( alignment: Alignment.centerLeft, - child: Wrap(children: children, spacing: 4, runSpacing: 2), + child: AbsorbPointer( + child: Wrap( + children: children, + spacing: 4, + runSpacing: 2, + ), + ), ); }, ), diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart index 8cb5c4987e..2da156ded8 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart @@ -37,9 +37,14 @@ class _BoardTextCellState extends State { } else { return Align( alignment: Alignment.centerLeft, - child: FlowyText.regular( - state.content, - fontSize: 14, + child: ConstrainedBox( + constraints: BoxConstraints.loose( + const Size(double.infinity, 100), + ), + child: FlowyText.regular( + state.content, + fontSize: 14, + ), ), ); } diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart index 5493b0d45b..31cca41e6a 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart @@ -35,22 +35,24 @@ class _BoardUrlCellState extends State { value: _cellBloc, child: BlocBuilder( builder: (context, state) { - final richText = RichText( - textAlign: TextAlign.left, - text: TextSpan( - text: state.content, - style: TextStyle( - color: theme.main2, - fontSize: 14, - decoration: TextDecoration.underline, + if (state.content.isEmpty) { + return const SizedBox(); + } else { + return Align( + alignment: Alignment.centerLeft, + child: RichText( + textAlign: TextAlign.left, + text: TextSpan( + text: state.content, + style: TextStyle( + color: theme.main2, + fontSize: 14, + decoration: TextDecoration.underline, + ), + ), ), - ), - ); - - return Align( - alignment: Alignment.centerLeft, - child: richText, - ); + ); + } }, ), ); diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart index 20640f5601..a5c7b7ba2c 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart @@ -73,7 +73,7 @@ class _BoardCardState extends State { (cellId) { final child = widget.cellBuilder.buildCell(cellId); return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), child: child, ); }, diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart index c8e92809d7..1068cbf36b 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart @@ -242,7 +242,7 @@ class IGridCellController extends Equatable { .getFieldTypeOptionData(fieldType: fieldType) .then((result) { return result.fold( - (data) => parser.fromBuffer(data.typeOptionData), + (data) => left(parser.fromBuffer(data.typeOptionData)), (err) => right(err), ); }); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart index 4ab3ff352d..f6ddf42fba 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart @@ -15,7 +15,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:table_calendar/table_calendar.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; - import '../../../layout/sizes.dart'; import '../../header/type_option/date.dart'; @@ -39,6 +38,7 @@ class DateCellEditor with FlowyOverlayDelegate { final result = await cellController.getFieldTypeOption(DateTypeOptionDataParser()); + result.fold( (dateTypeOptionPB) { final calendar = _CellCalendarWidget( diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart index 004c34b657..2926972f95 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart @@ -49,8 +49,10 @@ class _NumberCellState extends GridFocusNodeCellState { controller: _controller, focusNode: focusNode, onEditingComplete: () => focusNode.unfocus(), - maxLines: null, + onSubmitted: (_) => focusNode.unfocus(), + maxLines: 1, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + textInputAction: TextInputAction.done, decoration: const InputDecoration( contentPadding: EdgeInsets.zero, border: InputBorder.none, @@ -63,8 +65,6 @@ class _NumberCellState extends GridFocusNodeCellState { @override Future dispose() async { - _delayOperation?.cancel(); - _cellBloc.close(); super.dispose(); } @@ -76,6 +76,11 @@ class _NumberCellState extends GridFocusNodeCellState { if (_cellBloc.isClosed == false && _controller.text != contentFromState(_cellBloc.state)) { _cellBloc.add(NumberCellEvent.updateCell(_controller.text)); + + if (!mounted) { + _delayOperation = null; + _cellBloc.close(); + } } }); } diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index c39bb9e929..215cd3c467 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -287,7 +287,7 @@ impl GridRevisionEditor { pub async fn create_row(&self, params: CreateRowParams) -> FlowyResult { let mut row_rev = self.create_row_rev().await?; - self.view_manager.update_row(&mut row_rev, ¶ms).await; + self.view_manager.fill_row(&mut row_rev, ¶ms).await; let row_pb = self.create_row_pb(row_rev, params.start_row_id.clone()).await?; @@ -314,7 +314,10 @@ impl GridRevisionEditor { } pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> { - self.block_manager.update_row(changeset, make_row_from_row_rev).await + let row_id = changeset.row_id.clone(); + let _ = self.block_manager.update_row(changeset, make_row_from_row_rev).await?; + self.view_manager.did_update_row(&row_id).await; + Ok(()) } pub async fn get_rows(&self, block_id: &str) -> FlowyResult { diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs index adb016ed41..6d1c231480 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs @@ -76,7 +76,7 @@ impl GridViewRevisionEditor { }) } - pub(crate) async fn update_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { + pub(crate) async fn fill_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { match params.layout { GridLayout::Table => { // Table can be grouped too diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs index edae70ba63..b5f8d41691 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs @@ -47,12 +47,16 @@ impl GridViewManager { }) } - pub(crate) async fn update_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { + pub(crate) async fn fill_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { for view_editor in self.view_editors.iter() { - view_editor.update_row(row_rev, params).await; + view_editor.fill_row(row_rev, params).await; } } + pub(crate) async fn did_update_row(&self, row_id: &str) { + let row = self.block_manager.get_row_rev(row_id).await; + } + pub(crate) async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) { for view_editor in self.view_editors.iter() { view_editor.did_create_row(row_pb, params).await; From 9922645e59486d602d38d8c065432468bb278d15 Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 17 Aug 2022 08:14:25 +0800 Subject: [PATCH 3/6] chore: group the row after updated the row --- .../src/services/block_manager_trait_impl.rs | 45 +++++++ .../flowy-grid/src/services/grid_editor.rs | 8 +- .../src/services/grid_editor_trait_impl.rs | 32 +++++ .../src/services/grid_view_editor.rs | 96 +++++++------- .../src/services/grid_view_manager.rs | 119 ++++++++---------- .../group/group_generator/checkbox_group.rs | 2 +- .../group/group_generator/generator.rs | 3 +- .../group_generator/select_option_group.rs | 4 +- .../src/services/group/group_service.rs | 46 +++---- .../rust-lib/flowy-grid/src/services/mod.rs | 2 + .../flowy-revision/src/cache/reset.rs | 1 - 11 files changed, 211 insertions(+), 147 deletions(-) create mode 100644 frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs create mode 100644 frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs diff --git a/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs b/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs new file mode 100644 index 0000000000..2696ef0b2c --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs @@ -0,0 +1,45 @@ +use crate::services::block_manager::GridBlockManager; +use crate::services::grid_view_manager::{GridViewRowDelegate, GridViewRowOperation}; +use flowy_error::FlowyResult; +use flowy_grid_data_model::revision::RowRevision; +use lib_infra::future::{wrap_future, AFFuture}; +use std::sync::Arc; + +impl GridViewRowDelegate for Arc { + fn gv_index_of_row(&self, row_id: &str) -> AFFuture> { + let block_manager = self.clone(); + let row_id = row_id.to_owned(); + wrap_future(async move { block_manager.index_of_row(&row_id).await }) + } + + fn gv_get_row_rev(&self, row_id: &str) -> AFFuture>> { + let block_manager = self.clone(); + let row_id = row_id.to_owned(); + wrap_future(async move { + match block_manager.get_row_rev(&row_id).await { + Ok(row_rev) => row_rev, + Err(_) => None, + } + }) + } + + fn gv_row_revs(&self) -> AFFuture>> { + let block_manager = self.clone(); + + wrap_future(async move { + let blocks = block_manager.get_block_snapshots(None).await.unwrap(); + blocks + .into_iter() + .map(|block| block.row_revs) + .flatten() + .collect::>>() + }) + } +} + +impl GridViewRowOperation for Arc { + fn gv_move_row(&self, row_rev: Arc, from: usize, to: usize) -> AFFuture> { + let block_manager = self.clone(); + wrap_future(async move { block_manager.move_row(row_rev, from, to).await }) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 215cd3c467..4e078bfa0e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -3,6 +3,7 @@ use crate::entities::GridCellIdParams; use crate::entities::*; use crate::manager::{GridTaskSchedulerRwLock, GridUser}; use crate::services::block_manager::GridBlockManager; +use crate::services::block_manager_trait_impl::*; use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data, CellBytes}; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder}; use crate::services::filter::GridFilterService; @@ -11,7 +12,6 @@ use crate::services::persistence::block_index::BlockIndexCache; use crate::services::row::{ make_grid_blocks, make_row_from_row_rev, make_rows_from_row_revs, GridBlockSnapshot, RowRevisionBuilder, }; - use bytes::Bytes; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::revision::*; @@ -68,9 +68,11 @@ impl GridRevisionEditor { // View manager let view_manager = Arc::new( GridViewManager::new( + grid_id.to_owned(), user.clone(), - grid_pad.clone(), - block_manager.clone(), + Arc::new(grid_pad.clone()), + Arc::new(block_manager.clone()), + Arc::new(block_manager.clone()), Arc::new(task_scheduler.clone()), ) .await?, diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs new file mode 100644 index 0000000000..43bc74bf12 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs @@ -0,0 +1,32 @@ +use crate::services::grid_view_manager::GridViewFieldDelegate; +use flowy_grid_data_model::revision::FieldRevision; +use flowy_sync::client_grid::GridRevisionPad; +use lib_infra::future::{wrap_future, AFFuture}; +use std::sync::Arc; +use tokio::sync::RwLock; + +impl GridViewFieldDelegate for Arc> { + fn get_field_revs(&self) -> AFFuture>> { + let pad = self.clone(); + wrap_future(async move { + match pad.read().await.get_field_revs(None) { + Ok(field_revs) => field_revs, + Err(e) => { + tracing::error!("[GridViewRevisionDelegate] get field revisions failed: {}", e); + vec![] + } + } + }) + } + + fn get_field_rev(&self, field_id: &str) -> AFFuture>> { + let pad = self.clone(); + let field_id = field_id.to_owned(); + wrap_future(async move { + pad.read() + .await + .get_field_rev(&field_id) + .map(|(_, field_rev)| field_rev.clone()) + }) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs index 6d1c231480..68d61bbd35 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs @@ -1,59 +1,44 @@ -use flowy_error::{FlowyError, FlowyResult}; - +use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::entities::{ CreateRowParams, GridFilterConfiguration, GridLayout, GridSettingPB, GroupPB, GroupRowsChangesetPB, InsertedRowPB, RowPB, }; use crate::services::grid_editor_task::GridServiceTaskScheduler; +use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate}; use crate::services::group::{default_group_configuration, Group, GroupConfigurationDelegate, GroupService}; +use crate::services::setting::make_grid_setting; +use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{FieldRevision, GroupConfigurationRevision, RowRevision}; use flowy_revision::{RevisionCloudService, RevisionManager, RevisionObjectBuilder}; use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad}; -use flowy_sync::entities::revision::Revision; - -use crate::dart_notification::{send_dart_notification, GridNotification}; -use crate::services::setting::make_grid_setting; use flowy_sync::entities::grid::GridSettingChangesetParams; +use flowy_sync::entities::revision::Revision; use lib_infra::future::{wrap_future, AFFuture, FutureResult}; use std::sync::Arc; use tokio::sync::RwLock; -pub trait GridViewRevisionDelegate: Send + Sync + 'static { - fn get_field_revs(&self) -> AFFuture>>; - fn get_field_rev(&self, field_id: &str) -> AFFuture>>; -} - -pub trait GridViewRevisionRowDataSource: Send + Sync + 'static { - fn row_revs(&self) -> AFFuture>>; -} - #[allow(dead_code)] pub struct GridViewRevisionEditor { user_id: String, view_id: String, pad: Arc>, rev_manager: Arc, - delegate: Arc, - data_source: Arc, + field_delegate: Arc, + row_delegate: Arc, group_service: Arc>, - groups: Arc>>, scheduler: Arc, } impl GridViewRevisionEditor { - pub(crate) async fn new( + pub(crate) async fn new( user_id: &str, token: &str, view_id: String, - delegate: Delegate, - data_source: DataSource, + field_delegate: Arc, + row_delegate: Arc, scheduler: Arc, mut rev_manager: RevisionManager, - ) -> FlowyResult - where - Delegate: GridViewRevisionDelegate, - DataSource: GridViewRevisionRowDataSource, - { + ) -> FlowyResult { let cloud = Arc::new(GridViewRevisionCloudService { token: token.to_owned(), }); @@ -62,16 +47,14 @@ impl GridViewRevisionEditor { let rev_manager = Arc::new(rev_manager); let group_service = GroupService::new(Box::new(pad.clone())).await; let user_id = user_id.to_owned(); - let groups = Arc::new(RwLock::new(vec![])); Ok(Self { pad, user_id, view_id, rev_manager, scheduler, - groups, - delegate: Arc::new(delegate), - data_source: Arc::new(data_source), + field_delegate, + row_delegate, group_service: Arc::new(RwLock::new(group_service)), }) } @@ -87,7 +70,9 @@ impl GridViewRevisionEditor { self.group_service .read() .await - .update_row(row_rev, group_id, |field_id| self.delegate.get_field_rev(&field_id)) + .fill_row(row_rev, group_id, |field_id| { + self.field_delegate.get_field_rev(&field_id) + }) .await; } }, @@ -120,34 +105,55 @@ impl GridViewRevisionEditor { } } + pub(crate) async fn did_update_row(&self, row_rev: Arc) { + match self.group_id_of_row(&row_rev.id).await { + None => {} + Some(group_id) => { + // self.get_mut_group(&group_id, |group| Ok(())); + } + } + } + async fn group_id_of_row(&self, row_id: &str) -> Option { - let read_guard = self.groups.read().await; + let read_guard = &self.group_service.read().await.groups; for group in read_guard.iter() { if group.rows.iter().any(|row| row.id == row_id) { return Some(group.id.clone()); } } - None } - pub(crate) async fn load_groups(&self) -> FlowyResult> { - let field_revs = self.delegate.get_field_revs().await; - let row_revs = self.data_source.row_revs().await; + // async fn get_mut_group(&self, group_id: &str, f: F) -> FlowyResult<()> + // where + // F: Fn(&mut Group) -> FlowyResult<()>, + // { + // for group in self.groups.write().await.iter_mut() { + // if group.id == group_id { + // let _ = f(group)?; + // } + // } + // Ok(()) + // } - // - let mut write_guard = self.group_service.write().await; - match write_guard.load_groups(&field_revs, row_revs).await { + pub(crate) async fn load_groups(&self) -> FlowyResult> { + let field_revs = self.field_delegate.get_field_revs().await; + let row_revs = self.row_delegate.gv_row_revs().await; + + match self + .group_service + .write() + .await + .load_groups(&field_revs, row_revs) + .await + { None => Ok(vec![]), - Some(groups) => { - *self.groups.write().await = groups.clone(); - Ok(groups.into_iter().map(GroupPB::from).collect()) - } + Some(groups) => Ok(groups.into_iter().map(GroupPB::from).collect()), } } pub(crate) async fn get_setting(&self) -> GridSettingPB { - let field_revs = self.delegate.get_field_revs().await; + let field_revs = self.field_delegate.get_field_revs().await; let grid_setting = make_grid_setting(self.pad.read().await.get_setting_rev(), &field_revs); grid_setting } @@ -158,7 +164,7 @@ impl GridViewRevisionEditor { } pub(crate) async fn get_filters(&self) -> Vec { - let field_revs = self.delegate.get_field_revs().await; + let field_revs = self.field_delegate.get_field_revs().await; match self.pad.read().await.get_setting_rev().get_all_filters(&field_revs) { None => vec![], Some(filters) => filters diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs index b5f8d41691..c7d6c6711e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs @@ -4,9 +4,7 @@ use crate::entities::{ use crate::manager::GridUser; use crate::services::block_manager::GridBlockManager; use crate::services::grid_editor_task::GridServiceTaskScheduler; -use crate::services::grid_view_editor::{ - GridViewRevisionDelegate, GridViewRevisionEditor, GridViewRevisionRowDataSource, -}; +use crate::services::grid_view_editor::GridViewRevisionEditor; use bytes::Bytes; use dashmap::DashMap; use flowy_error::FlowyResult; @@ -23,26 +21,48 @@ use tokio::sync::RwLock; type ViewId = String; +pub trait GridViewFieldDelegate: Send + Sync + 'static { + fn get_field_revs(&self) -> AFFuture>>; + fn get_field_rev(&self, field_id: &str) -> AFFuture>>; +} + +pub trait GridViewRowDelegate: Send + Sync + 'static { + fn gv_index_of_row(&self, row_id: &str) -> AFFuture>; + fn gv_get_row_rev(&self, row_id: &str) -> AFFuture>>; + fn gv_row_revs(&self) -> AFFuture>>; +} + +pub trait GridViewRowOperation: Send + Sync + 'static { + // Will be removed in the future. + fn gv_move_row(&self, row_rev: Arc, from: usize, to: usize) -> AFFuture>; +} + pub(crate) struct GridViewManager { + grid_id: String, user: Arc, - grid_pad: Arc>, - block_manager: Arc, + field_delegate: Arc, + row_delegate: Arc, + row_operation: Arc, view_editors: DashMap>, scheduler: Arc, } impl GridViewManager { pub(crate) async fn new( + grid_id: String, user: Arc, - grid_pad: Arc>, - block_manager: Arc, + field_delegate: Arc, + row_delegate: Arc, + row_operation: Arc, scheduler: Arc, ) -> FlowyResult { Ok(Self { + grid_id, user, - grid_pad, scheduler, - block_manager, + field_delegate, + row_delegate, + row_operation, view_editors: DashMap::default(), }) } @@ -54,7 +74,7 @@ impl GridViewManager { } pub(crate) async fn did_update_row(&self, row_id: &str) { - let row = self.block_manager.get_row_rev(row_id).await; + let row = self.row_delegate.gv_get_row_rev(row_id).await; } pub(crate) async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) { @@ -103,19 +123,19 @@ impl GridViewManager { let from_index = from_index as usize; - match self.block_manager.get_row_rev(&row_id).await? { + match self.row_delegate.gv_get_row_rev(&row_id).await { None => tracing::warn!("Move row failed, can not find the row:{}", row_id), Some(row_rev) => match layout { GridLayout::Table => { tracing::trace!("Move row from {} to {}", from_index, to_index); let to_index = to_index as usize; - let _ = self.block_manager.move_row(row_rev, from_index, to_index).await?; + let _ = self.row_operation.gv_move_row(row_rev, from_index, to_index).await?; } GridLayout::Board => { if let Some(upper_row_id) = upper_row_id { - if let Some(to_index) = self.block_manager.index_of_row(&upper_row_id).await { + if let Some(to_index) = self.row_delegate.gv_index_of_row(&upper_row_id).await { tracing::trace!("Move row from {} to {}", from_index, to_index); - let _ = self.block_manager.move_row(row_rev, from_index, to_index).await?; + let _ = self.row_operation.gv_move_row(row_rev, from_index, to_index).await?; } } } @@ -132,8 +152,8 @@ impl GridViewManager { make_view_editor( &self.user, view_id, - self.grid_pad.clone(), - self.block_manager.clone(), + self.field_delegate.clone(), + self.row_delegate.clone(), self.scheduler.clone(), ) .await?, @@ -146,29 +166,33 @@ impl GridViewManager { } async fn get_default_view_editor(&self) -> FlowyResult> { - let grid_id = self.grid_pad.read().await.grid_id(); - self.get_view_editor(&grid_id).await + self.get_view_editor(&self.grid_id).await } } -async fn make_view_editor( +async fn make_view_editor( user: &Arc, view_id: &str, - delegate: Delegate, - data_source: DataSource, + field_delegate: Arc, + row_delegate: Arc, scheduler: Arc, -) -> FlowyResult -where - Delegate: GridViewRevisionDelegate, - DataSource: GridViewRevisionRowDataSource, -{ +) -> FlowyResult { tracing::trace!("Open view:{} editor", view_id); let rev_manager = make_grid_view_rev_manager(user, view_id).await?; let user_id = user.user_id()?; let token = user.token()?; let view_id = view_id.to_owned(); - GridViewRevisionEditor::new(&user_id, &token, view_id, delegate, data_source, scheduler, rev_manager).await + GridViewRevisionEditor::new( + &user_id, + &token, + view_id, + field_delegate, + row_delegate, + scheduler, + rev_manager, + ) + .await } pub async fn make_grid_view_rev_manager(user: &Arc, view_id: &str) -> FlowyResult { @@ -197,44 +221,3 @@ impl RevisionCompactor for GridViewRevisionCompactor { Ok(delta.json_bytes()) } } - -impl GridViewRevisionRowDataSource for Arc { - fn row_revs(&self) -> AFFuture>> { - let block_manager = self.clone(); - - wrap_future(async move { - let blocks = block_manager.get_block_snapshots(None).await.unwrap(); - blocks - .into_iter() - .map(|block| block.row_revs) - .flatten() - .collect::>>() - }) - } -} - -impl GridViewRevisionDelegate for Arc> { - fn get_field_revs(&self) -> AFFuture>> { - let pad = self.clone(); - wrap_future(async move { - match pad.read().await.get_field_revs(None) { - Ok(field_revs) => field_revs, - Err(e) => { - tracing::error!("[GridViewRevisionDelegate] get field revisions failed: {}", e); - vec![] - } - } - }) - } - - fn get_field_rev(&self, field_id: &str) -> AFFuture>> { - let pad = self.clone(); - let field_id = field_id.to_owned(); - wrap_future(async move { - pad.read() - .await - .get_field_rev(&field_id) - .map(|(_, field_rev)| field_rev.clone()) - }) - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs index 01c2e7a2d9..ccc264edc2 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs @@ -30,7 +30,7 @@ impl GroupActionHandler for CheckboxGroupController { self.handle_rows(row_revs, field_rev) } - fn update_card(&self, _row_rev: &mut RowRevision, _field_rev: &FieldRevision, _group_id: &str) { + fn fill_row(&self, _row_rev: &mut RowRevision, _field_rev: &FieldRevision, _group_id: &str) { todo!() } } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs index 11144184a8..201eb5eba2 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs @@ -27,10 +27,11 @@ pub trait Groupable { } pub trait GroupActionHandler: Send + Sync { + // The field that is used for grouping the rows fn field_id(&self) -> &str; fn build_groups(&self) -> Vec; fn group_rows(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()>; - fn update_card(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str); + fn fill_row(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str); } pub trait GroupActionHandler2: Send + Sync { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs index eb9ce69c9d..94260309b1 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs @@ -38,7 +38,7 @@ impl GroupActionHandler for SingleSelectGroupController { self.handle_rows(row_revs, field_rev) } - fn update_card(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { + fn fill_row(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { let group: Option<&Group> = self.groups_map.get(group_id); match group { None => {} @@ -102,7 +102,7 @@ impl GroupActionHandler for MultiSelectGroupController { self.handle_rows(row_revs, field_rev) } - fn update_card(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { + fn fill_row(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { let group: Option<&Group> = self.groups_map.get(group_id); match group { None => tracing::warn!("Can not find the group: {}", group_id), diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs index 2fd4bed686..2be0a9b7bf 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs @@ -18,15 +18,17 @@ pub trait GroupConfigurationDelegate: Send + Sync + 'static { } pub(crate) struct GroupService { + pub groups: Vec, delegate: Box, - action_handler: Option>>, + group_action: Option>>, } impl GroupService { pub(crate) async fn new(delegate: Box) -> Self { Self { + groups: vec![], delegate, - action_handler: None, + group_action: None, } } @@ -35,47 +37,39 @@ impl GroupService { field_revs: &[Arc], row_revs: Vec>, ) -> Option> { - let field_rev = find_group_field(field_revs).unwrap(); + let field_rev = find_group_field(field_revs)?; let field_type: FieldType = field_rev.field_type_rev.into(); let configuration = self.delegate.get_group_configuration(field_rev.clone()).await; - match self .build_groups(&field_type, &field_rev, row_revs, configuration) .await { - Ok(groups) => Some(groups), + Ok(groups) => { + self.groups = groups.clone(); + Some(groups) + } Err(_) => None, } } - pub(crate) async fn update_row(&self, row_rev: &mut RowRevision, group_id: &str, f: F) + pub(crate) async fn fill_row(&self, row_rev: &mut RowRevision, group_id: &str, f: F) where F: FnOnce(String) -> O, O: Future>> + Send + Sync + 'static, { - if let Some(group_action_handler) = self.action_handler.as_ref() { - let field_id = group_action_handler.read().await.field_id().to_owned(); + if let Some(group_action) = self.group_action.as_ref() { + let field_id = group_action.read().await.field_id().to_owned(); match f(field_id).await { None => {} Some(field_rev) => { - group_action_handler - .write() - .await - .update_card(row_rev, &field_rev, group_id); + group_action.write().await.fill_row(row_rev, &field_rev, group_id); } } } } - #[allow(dead_code)] - pub async fn move_card(&self, _group_id: &str, _from: i32, _to: i32) { - // BoardCardChangesetPB { - // group_id: "".to_string(), - // inserted_cards: vec![], - // deleted_cards: vec![], - // updated_cards: vec![] - // } - // let row_pb = make_row_from_row_rev(row_rev); - todo!() + + pub(crate) async fn did_update_row(&self, row_rev: Arc) { + if let Some(group_action) = self.group_action.as_ref() {} } #[tracing::instrument(level = "trace", skip_all, err)] @@ -98,15 +92,15 @@ impl GroupService { } FieldType::SingleSelect => { let controller = SingleSelectGroupController::new(field_rev, configuration)?; - self.action_handler = Some(Arc::new(RwLock::new(controller))); + self.group_action = Some(Arc::new(RwLock::new(controller))); } FieldType::MultiSelect => { let controller = MultiSelectGroupController::new(field_rev, configuration)?; - self.action_handler = Some(Arc::new(RwLock::new(controller))); + self.group_action = Some(Arc::new(RwLock::new(controller))); } FieldType::Checkbox => { let controller = CheckboxGroupController::new(field_rev, configuration)?; - self.action_handler = Some(Arc::new(RwLock::new(controller))); + self.group_action = Some(Arc::new(RwLock::new(controller))); } FieldType::URL => { // let generator = GroupGenerator::::from_configuration(configuration); @@ -114,7 +108,7 @@ impl GroupService { }; let mut groups = vec![]; - if let Some(group_action_handler) = self.action_handler.as_ref() { + if let Some(group_action_handler) = self.group_action.as_ref() { let mut write_guard = group_action_handler.write().await; let _ = write_guard.group_rows(&row_revs, field_rev)?; groups = write_guard.build_groups(); diff --git a/frontend/rust-lib/flowy-grid/src/services/mod.rs b/frontend/rust-lib/flowy-grid/src/services/mod.rs index 2addb16686..ab864c544a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/mod.rs @@ -2,11 +2,13 @@ mod util; pub mod block_editor; mod block_manager; +mod block_manager_trait_impl; pub mod cell; pub mod field; mod filter; pub mod grid_editor; mod grid_editor_task; +mod grid_editor_trait_impl; pub mod grid_view_editor; pub mod grid_view_manager; pub mod group; diff --git a/frontend/rust-lib/flowy-revision/src/cache/reset.rs b/frontend/rust-lib/flowy-revision/src/cache/reset.rs index cc9228e9ff..bd1ef4ba58 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/reset.rs +++ b/frontend/rust-lib/flowy-revision/src/cache/reset.rs @@ -38,7 +38,6 @@ where pub async fn run(&self) -> FlowyResult<()> { match KV::get_str(self.target.target_id()) { None => { - tracing::trace!("😁 reset object"); let _ = self.reset_object().await?; let _ = self.save_migrate_record()?; } From 0ecb15cfa322ca155e54e5b9e2ae04c058c40360 Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 17 Aug 2022 14:33:45 +0800 Subject: [PATCH 4/6] chore: fix some bugs --- .../flowy-grid/src/entities/block_entities.rs | 6 + .../group_entities/group_changeset.rs | 3 + .../flowy-grid/src/services/grid_editor.rs | 6 +- .../src/services/grid_view_editor.rs | 19 +- .../src/services/grid_view_manager.rs | 18 +- .../group/group_generator/checkbox_group.rs | 49 ++---- .../group/group_generator/generator.rs | 163 ++++++++++++++---- .../group_generator/select_option_group.rs | 98 ++++++----- .../src/services/group/group_service.rs | 51 ++++-- frontend/rust-lib/flowy-sdk/src/lib.rs | 2 +- 10 files changed, 270 insertions(+), 145 deletions(-) diff --git a/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs index 4d4fcc78c1..05750e7283 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs @@ -112,6 +112,12 @@ pub struct InsertedRowPB { pub index: Option, } +impl InsertedRowPB { + pub fn new(row: RowPB) -> Self { + Self { row, index: None } + } +} + impl std::convert::From for InsertedRowPB { fn from(row: RowPB) -> Self { Self { row, index: None } diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs index 1feb0debe2..f4602b9a50 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs @@ -17,6 +17,9 @@ pub struct GroupRowsChangesetPB { } impl GroupRowsChangesetPB { + pub fn is_empty(&self) -> bool { + self.inserted_rows.is_empty() && self.deleted_rows.is_empty() && self.updated_rows.is_empty() + } pub fn insert(group_id: String, inserted_rows: Vec) -> Self { Self { group_id, diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 4e078bfa0e..7c1d706e33 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -3,7 +3,7 @@ use crate::entities::GridCellIdParams; use crate::entities::*; use crate::manager::{GridTaskSchedulerRwLock, GridUser}; use crate::services::block_manager::GridBlockManager; -use crate::services::block_manager_trait_impl::*; + use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data, CellBytes}; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder}; use crate::services::filter::GridFilterService; @@ -405,7 +405,7 @@ impl GridRevisionEditor { content = Some(apply_cell_data_changeset(content.unwrap(), cell_rev, field_rev)?); let cell_changeset = CellChangesetPB { grid_id, - row_id, + row_id: row_id.clone(), field_id, content, }; @@ -413,6 +413,8 @@ impl GridRevisionEditor { .block_manager .update_cell(cell_changeset, make_row_from_row_rev) .await?; + + self.view_manager.did_update_row(&row_id).await; Ok(()) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs index 68d61bbd35..212febacf0 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs @@ -5,7 +5,7 @@ use crate::entities::{ }; use crate::services::grid_editor_task::GridServiceTaskScheduler; use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate}; -use crate::services::group::{default_group_configuration, Group, GroupConfigurationDelegate, GroupService}; +use crate::services::group::{default_group_configuration, GroupConfigurationDelegate, GroupService}; use crate::services::setting::make_grid_setting; use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{FieldRevision, GroupConfigurationRevision, RowRevision}; @@ -105,11 +105,16 @@ impl GridViewRevisionEditor { } } - pub(crate) async fn did_update_row(&self, row_rev: Arc) { - match self.group_id_of_row(&row_rev.id).await { - None => {} - Some(group_id) => { - // self.get_mut_group(&group_id, |group| Ok(())); + pub(crate) async fn did_update_row(&self, row_rev: &RowRevision) { + if let Some(changesets) = self + .group_service + .write() + .await + .did_update_row(row_rev, |field_id| self.field_delegate.get_field_rev(&field_id)) + .await + { + for changeset in changesets { + self.notify_did_update_group(changeset).await; } } } @@ -117,7 +122,7 @@ impl GridViewRevisionEditor { async fn group_id_of_row(&self, row_id: &str) -> Option { let read_guard = &self.group_service.read().await.groups; for group in read_guard.iter() { - if group.rows.iter().any(|row| row.id == row_id) { + if group.contains_row(row_id) { return Some(group.id.clone()); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs index c7d6c6711e..c2e13c1089 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs @@ -2,7 +2,7 @@ use crate::entities::{ CreateRowParams, GridFilterConfiguration, GridLayout, GridSettingPB, MoveRowParams, RepeatedGridGroupPB, RowPB, }; use crate::manager::GridUser; -use crate::services::block_manager::GridBlockManager; + use crate::services::grid_editor_task::GridServiceTaskScheduler; use crate::services::grid_view_editor::GridViewRevisionEditor; use bytes::Bytes; @@ -11,13 +11,12 @@ use flowy_error::FlowyResult; use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; use flowy_revision::disk::SQLiteGridViewRevisionPersistence; use flowy_revision::{RevisionCompactor, RevisionManager, RevisionPersistence, SQLiteRevisionSnapshotPersistence}; -use flowy_sync::client_grid::GridRevisionPad; + use flowy_sync::entities::grid::GridSettingChangesetParams; use flowy_sync::entities::revision::Revision; use flowy_sync::util::make_text_delta_from_revisions; -use lib_infra::future::{wrap_future, AFFuture}; +use lib_infra::future::AFFuture; use std::sync::Arc; -use tokio::sync::RwLock; type ViewId = String; @@ -74,7 +73,16 @@ impl GridViewManager { } pub(crate) async fn did_update_row(&self, row_id: &str) { - let row = self.row_delegate.gv_get_row_rev(row_id).await; + match self.row_delegate.gv_get_row_rev(row_id).await { + None => { + tracing::warn!("Can not find the row in grid view"); + } + Some(row_rev) => { + for view_editor in self.view_editors.iter() { + view_editor.did_update_row(&row_rev).await; + } + } + } } pub(crate) async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs index ccc264edc2..020a97e847 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs @@ -1,13 +1,16 @@ -use crate::entities::CheckboxGroupConfigurationPB; -use flowy_error::FlowyResult; +use crate::entities::{CheckboxGroupConfigurationPB, GroupRowsChangesetPB}; + use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; -use std::sync::Arc; use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK}; -use crate::services::group::{Group, GroupActionHandler, GroupController, GroupGenerator, Groupable}; +use crate::services::group::{GenericGroupController, Group, GroupController, GroupGenerator, Groupable}; -pub type CheckboxGroupController = - GroupController; +pub type CheckboxGroupController = GenericGroupController< + CheckboxGroupConfigurationPB, + CheckboxTypeOptionPB, + CheckboxGroupGenerator, + CheckboxCellDataParser, +>; impl Groupable for CheckboxGroupController { type CellDataType = CheckboxCellData; @@ -15,21 +18,13 @@ impl Groupable for CheckboxGroupController { fn can_group(&self, _content: &str, _cell_data: &Self::CellDataType) -> bool { false } + + fn group_row(&mut self, _row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec { + todo!() + } } -impl GroupActionHandler for CheckboxGroupController { - fn field_id(&self) -> &str { - &self.field_id - } - - fn build_groups(&self) -> Vec { - self.make_groups() - } - - fn group_rows(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { - self.handle_rows(row_revs, field_rev) - } - +impl GroupController for CheckboxGroupController { fn fill_row(&self, _row_rev: &mut RowRevision, _field_rev: &FieldRevision, _group_id: &str) { todo!() } @@ -44,20 +39,8 @@ impl GroupGenerator for CheckboxGroupGenerator { _configuration: &Option, _type_option: &Option, ) -> Vec { - let check_group = Group { - id: "true".to_string(), - desc: "".to_string(), - rows: vec![], - content: CHECK.to_string(), - }; - - let uncheck_group = Group { - id: "false".to_string(), - desc: "".to_string(), - rows: vec![], - content: UNCHECK.to_string(), - }; - + let check_group = Group::new("true".to_string(), "".to_string(), CHECK.to_string()); + let uncheck_group = Group::new("false".to_string(), "".to_string(), UNCHECK.to_string()); vec![check_group, uncheck_group] } } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs index 201eb5eba2..becef0d784 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs @@ -1,13 +1,11 @@ -use crate::entities::{GroupPB, RowPB}; +use crate::entities::{GroupPB, GroupRowsChangesetPB, RowPB}; use crate::services::cell::{decode_any_cell_data, CellBytesParser}; use bytes::Bytes; use flowy_error::FlowyResult; use flowy_grid_data_model::revision::{ FieldRevision, GroupConfigurationRevision, RowRevision, TypeOptionDataDeserializer, }; - use indexmap::IndexMap; - use std::marker::PhantomData; use std::sync::Arc; @@ -21,30 +19,35 @@ pub trait GroupGenerator { ) -> Vec; } -pub trait Groupable { +pub trait Groupable: Send + Sync { type CellDataType; fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool; + fn group_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec; } -pub trait GroupActionHandler: Send + Sync { +pub trait GroupController: GroupControllerSharedAction + Send + Sync { + fn fill_row(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str); +} + +pub trait GroupControllerSharedAction: Send + Sync { // The field that is used for grouping the rows fn field_id(&self) -> &str; fn build_groups(&self) -> Vec; fn group_rows(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()>; - fn fill_row(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str); -} - -pub trait GroupActionHandler2: Send + Sync { - fn create_card(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str); + fn did_update_row( + &mut self, + row_rev: &RowRevision, + field_rev: &FieldRevision, + ) -> FlowyResult>; } const DEFAULT_GROUP_ID: &str = "default_group"; /// C: represents the group configuration structure /// T: the type option data deserializer that impl [TypeOptionDataDeserializer] -/// G: the group container generator +/// G: the group generator, [GroupGenerator] /// P: the parser that impl [CellBytesParser] for the CellBytes -pub struct GroupController { +pub struct GenericGroupController { pub field_id: String, pub groups_map: IndexMap, default_group: Group, @@ -58,7 +61,7 @@ pub struct GroupController { pub struct Group { pub id: String, pub desc: String, - pub rows: Vec, + rows: Vec, pub content: String, } @@ -72,7 +75,40 @@ impl std::convert::From for GroupPB { } } -impl GroupController +impl Group { + pub fn new(id: String, desc: String, content: String) -> Self { + Self { + id, + desc, + rows: vec![], + content, + } + } + + pub fn contains_row(&self, row_id: &str) -> bool { + self.rows.iter().any(|row| row.id == row_id) + } + + pub fn remove_row(&mut self, row_id: &str) { + match self.rows.iter().position(|row| row.id == row_id) { + None => {} + Some(pos) => { + self.rows.remove(pos); + } + } + } + + pub fn add_row(&mut self, row_pb: RowPB) { + match self.rows.iter().find(|row| row.id == row_pb.id) { + None => { + self.rows.push(row_pb); + } + Some(_) => {} + } + } +} + +impl GenericGroupController where C: TryFrom, T: TypeOptionDataDeserializer, @@ -87,12 +123,11 @@ where let type_option = field_rev.get_type_option_entry::(field_type_rev); let groups = G::generate_groups(&configuration, &type_option); - let default_group = Group { - id: DEFAULT_GROUP_ID.to_owned(), - desc: format!("No {}", field_rev.name), - rows: vec![], - content: "".to_string(), - }; + let default_group = Group::new( + DEFAULT_GROUP_ID.to_owned(), + format!("No {}", field_rev.name), + "".to_string(), + ); Ok(Self { field_id: field_rev.id.clone(), @@ -104,8 +139,18 @@ where cell_parser_phantom: PhantomData, }) } +} - pub fn make_groups(&self) -> Vec { +impl GroupControllerSharedAction for GenericGroupController +where + P: CellBytesParser, + Self: Groupable, +{ + fn field_id(&self) -> &str { + &self.field_id + } + + fn build_groups(&self) -> Vec { let default_group = self.default_group.clone(); let mut groups: Vec = self.groups_map.values().cloned().collect(); if !default_group.rows.is_empty() { @@ -113,35 +158,28 @@ where } groups } -} -impl GroupController -where - P: CellBytesParser, - Self: Groupable, -{ - pub fn handle_rows(&mut self, rows: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { - // The field_rev might be None if corresponding field_rev is deleted. + fn group_rows(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { if self.configuration.is_none() { return Ok(()); } - for row in rows { - if let Some(cell_rev) = row.cells.get(&self.field_id) { + for row_rev in row_revs { + if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { let mut records: Vec = vec![]; let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); let cell_data = cell_bytes.parser::

()?; for group in self.groups_map.values() { if self.can_group(&group.content, &cell_data) { records.push(GroupRecord { - row: row.into(), + row: row_rev.into(), group_id: group.id.clone(), }); } } if records.is_empty() { - self.default_group.rows.push(row.into()); + self.default_group.rows.push(row_rev.into()); } else { for record in records { if let Some(group) = self.groups_map.get_mut(&record.group_id) { @@ -150,14 +188,71 @@ where } } } else { - self.default_group.rows.push(row.into()); + self.default_group.rows.push(row_rev.into()); } } Ok(()) } + + fn did_update_row( + &mut self, + row_rev: &RowRevision, + field_rev: &FieldRevision, + ) -> FlowyResult> { + 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::

()?; + Ok(self.group_row(row_rev, &cell_data)) + } else { + Ok(vec![]) + } + } } +// impl GroupController +// where +// P: CellBytesParser, +// Self: Groupable, +// { +// pub fn handle_rows(&mut self, rows: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { +// // The field_rev might be None if corresponding field_rev is deleted. +// if self.configuration.is_none() { +// return Ok(()); +// } +// +// for row in rows { +// if let Some(cell_rev) = row.cells.get(&self.field_id) { +// let mut records: Vec = vec![]; +// let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); +// let cell_data = cell_bytes.parser::

()?; +// for group in self.groups_map.values() { +// if self.can_group(&group.content, &cell_data) { +// records.push(GroupRecord { +// row: row.into(), +// group_id: group.id.clone(), +// }); +// } +// } +// +// if records.is_empty() { +// self.default_group.rows.push(row.into()); +// } else { +// for record in records { +// if let Some(group) = self.groups_map.get_mut(&record.group_id) { +// group.rows.push(record.row); +// } +// } +// } +// } else { +// self.default_group.rows.push(row.into()); +// } +// } +// +// Ok(()) +// } +// } + struct GroupRecord { row: RowPB, group_id: String, diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs index 94260309b1..ce39e59955 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs @@ -1,17 +1,14 @@ -use crate::entities::SelectOptionGroupConfigurationPB; +use crate::entities::{GroupRowsChangesetPB, InsertedRowPB, RowPB, SelectOptionGroupConfigurationPB}; use crate::services::cell::insert_select_option_cell; -use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; - -use std::sync::Arc; - use crate::services::field::{ MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB, }; -use crate::services::group::{Group, GroupActionHandler, GroupController, GroupGenerator, Groupable}; +use crate::services::group::{GenericGroupController, Group, GroupController, GroupGenerator, Groupable}; + +use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; // SingleSelect -pub type SingleSelectGroupController = GroupController< +pub type SingleSelectGroupController = GenericGroupController< SelectOptionGroupConfigurationPB, SingleSelectTypeOptionPB, SingleSelectGroupGenerator, @@ -23,21 +20,17 @@ impl Groupable for SingleSelectGroupController { fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { cell_data.select_options.iter().any(|option| option.id == content) } + + fn group_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { + let mut changesets = vec![]; + self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| { + group_select_option_row(group, &mut changesets, cell_data, row_rev); + }); + changesets + } } -impl GroupActionHandler for SingleSelectGroupController { - fn field_id(&self) -> &str { - &self.field_id - } - - fn build_groups(&self) -> Vec { - self.make_groups() - } - - fn group_rows(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { - self.handle_rows(row_revs, field_rev) - } - +impl GroupController for SingleSelectGroupController { fn fill_row(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { let group: Option<&Group> = self.groups_map.get(group_id); match group { @@ -63,19 +56,14 @@ impl GroupGenerator for SingleSelectGroupGenerator { Some(type_option) => type_option .options .iter() - .map(|option| Group { - id: option.id.clone(), - desc: option.name.clone(), - rows: vec![], - content: option.id.clone(), - }) + .map(|option| Group::new(option.id.clone(), option.name.clone(), option.id.clone())) .collect(), } } } // MultiSelect -pub type MultiSelectGroupController = GroupController< +pub type MultiSelectGroupController = GenericGroupController< SelectOptionGroupConfigurationPB, MultiSelectTypeOptionPB, MultiSelectGroupGenerator, @@ -84,24 +72,22 @@ pub type MultiSelectGroupController = GroupController< impl Groupable for MultiSelectGroupController { type CellDataType = SelectOptionCellDataPB; + fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { cell_data.select_options.iter().any(|option| option.id == content) } + + fn group_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { + let mut changesets = vec![]; + + self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| { + group_select_option_row(group, &mut changesets, cell_data, row_rev); + }); + changesets + } } -impl GroupActionHandler for MultiSelectGroupController { - fn field_id(&self) -> &str { - &self.field_id - } - - fn build_groups(&self) -> Vec { - self.make_groups() - } - - fn group_rows(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { - self.handle_rows(row_revs, field_rev) - } - +impl GroupController for MultiSelectGroupController { fn fill_row(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { let group: Option<&Group> = self.groups_map.get(group_id); match group { @@ -128,13 +114,31 @@ impl GroupGenerator for MultiSelectGroupGenerator { Some(type_option) => type_option .options .iter() - .map(|option| Group { - id: option.id.clone(), - desc: option.name.clone(), - rows: vec![], - content: option.id.clone(), - }) + .map(|option| Group::new(option.id.clone(), option.name.clone(), option.id.clone())) .collect(), } } } + +fn group_select_option_row( + group: &mut Group, + changesets: &mut Vec, + cell_data: &SelectOptionCellDataPB, + row_rev: &RowRevision, +) { + cell_data.select_options.iter().for_each(|option| { + if option.id == group.id { + if !group.contains_row(&row_rev.id) { + let row_pb = RowPB::from(row_rev); + changesets.push(GroupRowsChangesetPB::insert( + group.id.clone(), + vec![InsertedRowPB::new(row_pb.clone())], + )); + group.add_row(row_pb); + } + } else if group.contains_row(&row_rev.id) { + group.remove_row(&row_rev.id); + changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()])); + } + }); +} diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs index 2be0a9b7bf..d2e713cf9d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs @@ -1,9 +1,9 @@ use crate::entities::{ - CheckboxGroupConfigurationPB, DateGroupConfigurationPB, FieldType, NumberGroupConfigurationPB, - SelectOptionGroupConfigurationPB, TextGroupConfigurationPB, UrlGroupConfigurationPB, + CheckboxGroupConfigurationPB, DateGroupConfigurationPB, FieldType, GroupRowsChangesetPB, + NumberGroupConfigurationPB, SelectOptionGroupConfigurationPB, TextGroupConfigurationPB, UrlGroupConfigurationPB, }; use crate::services::group::{ - CheckboxGroupController, Group, GroupActionHandler, MultiSelectGroupController, SingleSelectGroupController, + CheckboxGroupController, Group, GroupController, MultiSelectGroupController, SingleSelectGroupController, }; use bytes::Bytes; use flowy_error::FlowyResult; @@ -20,7 +20,7 @@ pub trait GroupConfigurationDelegate: Send + Sync + 'static { pub(crate) struct GroupService { pub groups: Vec, delegate: Box, - group_action: Option>>, + group_controller: Option>>, } impl GroupService { @@ -28,7 +28,7 @@ impl GroupService { Self { groups: vec![], delegate, - group_action: None, + group_controller: None, } } @@ -52,24 +52,43 @@ impl GroupService { } } - pub(crate) async fn fill_row(&self, row_rev: &mut RowRevision, group_id: &str, f: F) + pub(crate) async fn fill_row(&self, row_rev: &mut RowRevision, group_id: &str, get_field_fn: F) where F: FnOnce(String) -> O, O: Future>> + Send + Sync + 'static, { - if let Some(group_action) = self.group_action.as_ref() { - let field_id = group_action.read().await.field_id().to_owned(); - match f(field_id).await { + if let Some(group_controller) = self.group_controller.as_ref() { + let field_id = group_controller.read().await.field_id().to_owned(); + match get_field_fn(field_id).await { None => {} Some(field_rev) => { - group_action.write().await.fill_row(row_rev, &field_rev, group_id); + group_controller.write().await.fill_row(row_rev, &field_rev, group_id); } } } } - pub(crate) async fn did_update_row(&self, row_rev: Arc) { - if let Some(group_action) = self.group_action.as_ref() {} + #[tracing::instrument(level = "trace", skip_all)] + pub(crate) async fn did_update_row( + &self, + row_rev: &RowRevision, + get_field_fn: F, + ) -> Option> + where + F: FnOnce(String) -> O, + O: Future>> + Send + Sync + 'static, + { + let group_controller = self.group_controller.as_ref()?; + let field_id = group_controller.read().await.field_id().to_owned(); + let field_rev = get_field_fn(field_id).await?; + + match group_controller.write().await.did_update_row(row_rev, &field_rev) { + Ok(changeset) => Some(changeset), + Err(e) => { + tracing::error!("Update group data failed, {:?}", e); + None + } + } } #[tracing::instrument(level = "trace", skip_all, err)] @@ -92,15 +111,15 @@ impl GroupService { } FieldType::SingleSelect => { let controller = SingleSelectGroupController::new(field_rev, configuration)?; - self.group_action = Some(Arc::new(RwLock::new(controller))); + self.group_controller = Some(Arc::new(RwLock::new(controller))); } FieldType::MultiSelect => { let controller = MultiSelectGroupController::new(field_rev, configuration)?; - self.group_action = Some(Arc::new(RwLock::new(controller))); + self.group_controller = Some(Arc::new(RwLock::new(controller))); } FieldType::Checkbox => { let controller = CheckboxGroupController::new(field_rev, configuration)?; - self.group_action = Some(Arc::new(RwLock::new(controller))); + self.group_controller = Some(Arc::new(RwLock::new(controller))); } FieldType::URL => { // let generator = GroupGenerator::::from_configuration(configuration); @@ -108,7 +127,7 @@ impl GroupService { }; let mut groups = vec![]; - if let Some(group_action_handler) = self.group_action.as_ref() { + if let Some(group_action_handler) = self.group_controller.as_ref() { let mut write_guard = group_action_handler.write().await; let _ = write_guard.group_rows(&row_revs, field_rev)?; groups = write_guard.build_groups(); diff --git a/frontend/rust-lib/flowy-sdk/src/lib.rs b/frontend/rust-lib/flowy-sdk/src/lib.rs index 602e1d2092..e0bd601987 100644 --- a/frontend/rust-lib/flowy-sdk/src/lib.rs +++ b/frontend/rust-lib/flowy-sdk/src/lib.rs @@ -75,7 +75,7 @@ fn crate_log_filter(level: String) -> String { filters.push(format!("lib_ws={}", level)); filters.push(format!("lib_infra={}", level)); filters.push(format!("flowy_sync={}", level)); - filters.push(format!("flowy_revision={}", level)); + // filters.push(format!("flowy_revision={}", level)); // filters.push(format!("lib_dispatch={}", level)); filters.push(format!("dart_ffi={}", "info")); From 6c2dda16670767e4dab2dd20c5a820bcc382b066 Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 17 Aug 2022 15:50:19 +0800 Subject: [PATCH 5/6] chore: add shortcut to exit the FlowyOverlay --- .../widgets/cell/cell_shortcuts.dart | 18 ++-- .../example/lib/overlay/overlay_screen.dart | 95 ++++++++++++++----- .../lib/src/flowy_overlay/flowy_overlay.dart | 65 +++++++++++-- 3 files changed, 135 insertions(+), 43 deletions(-) diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_shortcuts.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_shortcuts.dart index f4f222f219..f44a4b5663 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_shortcuts.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_shortcuts.dart @@ -24,14 +24,16 @@ class GridCellShortcuts extends StatelessWidget { return Shortcuts( shortcuts: { LogicalKeySet(LogicalKeyboardKey.enter): const GridCellEnterIdent(), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC): const GridCellCopyIntent(), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyV): const GridCellInsertIntent(), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC): + const GridCellCopyIntent(), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyV): + const GridCellPasteIntent(), }, child: Actions( actions: { GridCellEnterIdent: GridCellEnterAction(child: child), GridCellCopyIntent: GridCellCopyAction(child: child), - GridCellInsertIntent: GridCellInsertAction(child: child), + GridCellPasteIntent: GridCellPasteAction(child: child), }, child: child, ), @@ -78,16 +80,16 @@ class GridCellCopyAction extends Action { } } -class GridCellInsertIntent extends Intent { - const GridCellInsertIntent(); +class GridCellPasteIntent extends Intent { + const GridCellPasteIntent(); } -class GridCellInsertAction extends Action { +class GridCellPasteAction extends Action { final CellShortcuts child; - GridCellInsertAction({required this.child}); + GridCellPasteAction({required this.child}); @override - void invoke(covariant GridCellInsertIntent intent) { + void invoke(covariant GridCellPasteIntent intent) { final callback = child.shortcutHandlers[CellKeyboardKey.onInsert]; if (callback != null) { callback(); diff --git a/frontend/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart b/frontend/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart index 24274999db..cea870a017 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart @@ -53,7 +53,8 @@ class OverlayScreen extends StatelessWidget { title: const Text('Overlay Demo'), ), body: ChangeNotifierProvider( - create: (context) => OverlayDemoConfiguration(AnchorDirection.rightWithTopAligned, OverlapBehaviour.stretch), + create: (context) => OverlayDemoConfiguration( + AnchorDirection.rightWithTopAligned, OverlapBehaviour.stretch), child: Builder(builder: (providerContext) { return Center( child: ConstrainedBox( @@ -77,7 +78,8 @@ class OverlayScreen extends StatelessWidget { child: GestureDetector( // ignore: avoid_print onTapDown: (_) => print('Hello Flutter'), - child: const Center(child: FlutterLogo(size: 100)), + child: + const Center(child: FlutterLogo(size: 100)), ), ), ), @@ -90,26 +92,38 @@ class OverlayScreen extends StatelessWidget { ), const SizedBox(height: 24.0), DropdownButton( - value: providerContext.watch().anchorDirection, + value: providerContext + .watch() + .anchorDirection, onChanged: (AnchorDirection? newValue) { if (newValue != null) { - providerContext.read().anchorDirection = newValue; + providerContext + .read() + .anchorDirection = newValue; } }, - items: AnchorDirection.values.map((AnchorDirection classType) { - return DropdownMenuItem(value: classType, child: Text(classType.toString())); + items: + AnchorDirection.values.map((AnchorDirection classType) { + return DropdownMenuItem( + value: classType, child: Text(classType.toString())); }).toList(), ), const SizedBox(height: 24.0), DropdownButton( - value: providerContext.watch().overlapBehaviour, + value: providerContext + .watch() + .overlapBehaviour, onChanged: (OverlapBehaviour? newValue) { if (newValue != null) { - providerContext.read().overlapBehaviour = newValue; + providerContext + .read() + .overlapBehaviour = newValue; } }, - items: OverlapBehaviour.values.map((OverlapBehaviour classType) { - return DropdownMenuItem(value: classType, child: Text(classType.toString())); + items: OverlapBehaviour.values + .map((OverlapBehaviour classType) { + return DropdownMenuItem( + value: classType, child: Text(classType.toString())); }).toList(), ), const SizedBox(height: 24.0), @@ -127,15 +141,20 @@ class OverlayScreen extends StatelessWidget { child: GestureDetector( // ignore: avoid_print onTapDown: (_) => print('Hello Flutter'), - child: const Center(child: FlutterLogo(size: 50)), + child: const Center( + child: FlutterLogo(size: 50)), ), ), ), identifier: 'overlay_anchored_card', delegate: null, anchorContext: buttonContext, - anchorDirection: providerContext.read().anchorDirection, - overlapBehaviour: providerContext.read().overlapBehaviour, + anchorDirection: providerContext + .read() + .anchorDirection, + overlapBehaviour: providerContext + .read() + .overlapBehaviour, ); }, child: const Text('Show Anchored Overlay'), @@ -155,7 +174,8 @@ class OverlayScreen extends StatelessWidget { child: GestureDetector( // ignore: avoid_print onTapDown: (_) => debugPrint('Hello Flutter'), - child: const Center(child: FlutterLogo(size: 100)), + child: + const Center(child: FlutterLogo(size: 100)), ), ), ), @@ -163,8 +183,12 @@ class OverlayScreen extends StatelessWidget { delegate: null, anchorPosition: Offset(0, windowSize.height - 200), anchorSize: Size.zero, - anchorDirection: providerContext.read().anchorDirection, - overlapBehaviour: providerContext.read().overlapBehaviour, + anchorDirection: providerContext + .read() + .anchorDirection, + overlapBehaviour: providerContext + .read() + .overlapBehaviour, ); }, child: const Text('Show Positioned Overlay'), @@ -176,18 +200,24 @@ class OverlayScreen extends StatelessWidget { ListOverlay.showWithAnchor( context, itemBuilder: (_, index) => Card( - margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), + margin: const EdgeInsets.symmetric( + vertical: 8.0, horizontal: 12.0), elevation: 0, child: Text( 'Option $index', - style: const TextStyle(fontSize: 20.0, color: Colors.black), + style: const TextStyle( + fontSize: 20.0, color: Colors.black), ), ), itemCount: 10, identifier: 'overlay_list_menu', anchorContext: buttonContext, - anchorDirection: providerContext.read().anchorDirection, - overlapBehaviour: providerContext.read().overlapBehaviour, + anchorDirection: providerContext + .read() + .anchorDirection, + overlapBehaviour: providerContext + .read() + .overlapBehaviour, width: 200.0, height: 200.0, ); @@ -201,13 +231,28 @@ class OverlayScreen extends StatelessWidget { onPressed: () { OptionOverlay.showWithAnchor( context, - items: ['Alpha', 'Beta', 'Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf', 'Hotel'], - onHover: (value, index) => debugPrint('Did hover option $index, value $value'), - onTap: (value, index) => debugPrint('Did tap option $index, value $value'), + items: [ + 'Alpha', + 'Beta', + 'Charlie', + 'Delta', + 'Echo', + 'Foxtrot', + 'Golf', + 'Hotel' + ], + onHover: (value, index) => debugPrint( + 'Did hover option $index, value $value'), + onTap: (value, index) => + debugPrint('Did tap option $index, value $value'), identifier: 'overlay_options', anchorContext: buttonContext, - anchorDirection: providerContext.read().anchorDirection, - overlapBehaviour: providerContext.read().overlapBehaviour, + anchorDirection: providerContext + .read() + .anchorDirection, + overlapBehaviour: providerContext + .read() + .overlapBehaviour, ); }, child: const Text('Show Options Overlay'), diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart index 7ed3c04673..ad04dc25c2 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:flowy_infra_ui/src/flowy_overlay/layout.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; export './overlay_container.dart'; /// Specifies how overlay are anchored to the SourceWidget @@ -59,7 +60,8 @@ class FlowyOverlayStyle { final Color barrierColor; bool blur; - FlowyOverlayStyle({this.barrierColor = Colors.transparent, this.blur = false}); + FlowyOverlayStyle( + {this.barrierColor = Colors.transparent, this.blur = false}); } final GlobalKey _key = GlobalKey(); @@ -82,7 +84,8 @@ class FlowyOverlay extends StatefulWidget { final Widget child; - static FlowyOverlayState of(BuildContext context, {bool rootOverlay = false}) { + static FlowyOverlayState of(BuildContext context, + {bool rootOverlay = false}) { FlowyOverlayState? state = maybeOf(context, rootOverlay: rootOverlay); assert(() { if (state == null) { @@ -95,7 +98,8 @@ class FlowyOverlay extends StatefulWidget { return state!; } - static FlowyOverlayState? maybeOf(BuildContext context, {bool rootOverlay = false}) { + static FlowyOverlayState? maybeOf(BuildContext context, + {bool rootOverlay = false}) { FlowyOverlayState? state; if (rootOverlay) { state = context.findRootAncestorStateOfType(); @@ -113,20 +117,29 @@ class OverlayItem { Widget widget; String identifier; FlowyOverlayDelegate? delegate; + FocusNode focusNode; OverlayItem({ required this.widget, required this.identifier, + required this.focusNode, this.delegate, }); + + void dispose() { + focusNode.dispose(); + } } class FlowyOverlayState extends State { final List _overlayList = []; FlowyOverlayStyle style = FlowyOverlayStyle(); + final Map + _keyboardShortcutBindings = {}; + /// Insert a overlay widget which frame is set by the widget, not the component. - /// Be sure to specify the offset and size using a anchorable widget (like `Postition`, `CompositedTransformFollower`) + /// Be sure to specify the offset and size using a anchorable widget (like `Position`, `CompositedTransformFollower`) void insertCustom({ required Widget widget, required String identifier, @@ -192,9 +205,12 @@ class FlowyOverlayState extends State { void remove(String identifier) { setState(() { - final index = _overlayList.indexWhere((item) => item.identifier == identifier); + final index = + _overlayList.indexWhere((item) => item.identifier == identifier); if (index != -1) { - _overlayList.removeAt(index).delegate?.didRemove(); + final OverlayItem item = _overlayList.removeAt(index); + item.delegate?.didRemove(); + item.dispose(); } }); } @@ -210,6 +226,7 @@ class FlowyOverlayState extends State { _overlayList.remove(firstItem); if (firstItem.delegate != null) { firstItem.delegate!.didRemove(); + firstItem.dispose(); if (firstItem.delegate!.asBarrier()) { return; } @@ -220,6 +237,7 @@ class FlowyOverlayState extends State { return; } else { element.delegate?.didRemove(); + element.dispose(); _overlayList.remove(element); } } @@ -247,7 +265,7 @@ class FlowyOverlayState extends State { debugPrint("Show overlay: $identifier"); Widget overlay = widget; final offset = anchorOffset ?? Offset.zero; - + final focusNode = FocusNode(); if (shouldAnchor) { assert( anchorPosition != null || anchorContext != null, @@ -259,7 +277,7 @@ class FlowyOverlayState extends State { RenderObject renderObject = anchorContext.findRenderObject()!; assert( renderObject is RenderBox, - 'Unexpect non-RenderBox render object caught.', + 'Unexpected non-RenderBox render object caught.', ); final renderBox = renderObject as RenderBox; targetAnchorPosition = renderBox.localToGlobal(Offset.zero); @@ -271,13 +289,28 @@ class FlowyOverlayState extends State { targetAnchorSize.width, targetAnchorSize.height, ); + overlay = CustomSingleChildLayout( delegate: OverlayLayoutDelegate( anchorRect: anchorRect, - anchorDirection: anchorDirection ?? AnchorDirection.rightWithTopAligned, + anchorDirection: + anchorDirection ?? AnchorDirection.rightWithTopAligned, overlapBehaviour: overlapBehaviour ?? OverlapBehaviour.stretch, ), - child: widget, + child: Focus( + focusNode: focusNode, + onKey: (node, event) { + KeyEventResult result = KeyEventResult.ignored; + for (final ShortcutActivator activator + in _keyboardShortcutBindings.keys) { + if (activator.accepts(event, RawKeyboard.instance)) { + _keyboardShortcutBindings[activator]!.call(identifier); + result = KeyEventResult.handled; + } + } + return result; + }, + child: widget), ); } @@ -285,15 +318,27 @@ class FlowyOverlayState extends State { _overlayList.add(OverlayItem( widget: overlay, identifier: identifier, + focusNode: focusNode, delegate: delegate, )); }); } + @override + void initState() { + _keyboardShortcutBindings.addAll({ + LogicalKeySet(LogicalKeyboardKey.escape): (identifier) { + remove(identifier); + }, + }); + super.initState(); + } + @override Widget build(BuildContext context) { final overlays = _overlayList.map((item) { var widget = item.widget; + item.focusNode.requestFocus(); if (item.delegate?.asBarrier() ?? false) { widget = Container( color: style.barrierColor, From 7361172c8994f9a6ce2ce778dfba34b413d7d307 Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 18 Aug 2022 08:37:45 +0800 Subject: [PATCH 6/6] fix: require write lock to make sure exclusive accest to sync_seq --- .../flowy-revision/src/rev_persistence.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/rust-lib/flowy-revision/src/rev_persistence.rs b/frontend/rust-lib/flowy-revision/src/rev_persistence.rs index 01f4a4189d..cf672368ed 100644 --- a/frontend/rust-lib/flowy-revision/src/rev_persistence.rs +++ b/frontend/rust-lib/flowy-revision/src/rev_persistence.rs @@ -73,12 +73,13 @@ impl RevisionPersistence { revision: &'a Revision, compactor: &Arc, ) -> FlowyResult { - let result = self.sync_seq.read().await.compact(); + let mut sync_seq_write_guard = self.sync_seq.write().await; + let result = sync_seq_write_guard.compact(); match result { None => { tracing::Span::current().record("rev_id", &revision.rev_id); self.add(revision.clone(), RevisionState::Sync, true).await?; - self.sync_seq.write().await.add(revision.rev_id)?; + sync_seq_write_guard.add(revision.rev_id)?; Ok(revision.rev_id) } Some((range, mut compact_seq)) => { @@ -101,8 +102,10 @@ impl RevisionPersistence { // replace the revisions in range with compact revision self.compact(&range, compact_revision).await?; - debug_assert_eq!(self.sync_seq.read().await.len(), compact_seq.len()); - self.sync_seq.write().await.reset(compact_seq); + // + debug_assert_eq!(compact_seq.len(), 2); + debug_assert_eq!(sync_seq_write_guard.len(), compact_seq.len()); + sync_seq_write_guard.reset(compact_seq); Ok(rev_id) } } @@ -315,7 +318,11 @@ impl RevisionSyncSequence { // Compact the rev_ids into one except the current synchronizing rev_id. fn compact(&self) -> Option<(RevisionRange, VecDeque)> { - self.next_rev_id()?; + // Make sure there are two rev_id going to sync. No need to compact if there is only + // one rev_id in queue. + if self.next_rev_id().is_none() { + return None; + } let mut new_seq = self.0.clone(); let mut drained = new_seq.drain(1..).collect::>();