## Key Features
-* Allow you to build rich, intuitive editors
-* Design and modify it your way by customizing components, shortcut events, and many more coming soon including menu options and themes
-* [Test-covered](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md) and maintained by AppFlowy's core team along with a community of more than 1,000 builders
+* Build rich, intuitive editors
+* Design and modify an ever expanding list of customizable features including
+ * components (such as form input controls, numbered lists, and rich text widgets)
+ * shortcut events
+ * menu options (**coming soon!**)
+ * themes (**coming soon!**)
+* [Test-coverage](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md) and on-going maintenance by AppFlowy's core team and community of more than 1,000 builders
+## Getting Started
-## Getting started
+Add the AppFlowy editor [Flutter package](https://docs.flutter.dev/development/packages-and-plugins/using-packages) to your environment.
```shell
flutter pub add appflowy_editor
flutter pub get
```
-## How to use
+## Creating Your First Editor
+
+Start by creating a new empty AppFlowyEditor object.
-Let's create a new AppFlowyEditor object
```dart
final editorState = EditorState.empty(); // an empty state
final editor = AppFlowyEditor(
@@ -50,7 +62,8 @@ final editor = AppFlowyEditor(
);
```
-You can also create an editor from a JSON file
+You can also create an editor from a JSON object in order to configure your initial state.
+
```dart
final json = ...;
final editorState = EditorState(StateTree.fromJson(data));
@@ -61,37 +74,43 @@ final editor = AppFlowyEditor(
);
```
-To get a sense for how you might use it, run this example:
+To get a sense for how the AppFlowy Editor works, run our example:
+
```shell
git clone https://github.com/AppFlowy-IO/AppFlowy.git
cd frontend/app_flowy/packages/appflowy_editor/example
flutter run
```
+## Customizing Your Editor
-## How to customize
-### Customize a component
-Please refer to [customizing a component](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md#customize-a-component) for more details.
+### Customizing Components
+Please refer to our documentation on customizing AppFlowy for a detailed discussion about [customizing components](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md#customize-a-component).
-### Customize a shortcut event
-Please refer to [customizing a shortcut event](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md#customize-a-shortcut-event) for more details.
+Below are some examples of component customizations:
-## More Examples
-* Customize a component
- * [Checkbox Text](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart) shows you how to extend new styles based on existing rich text components
- * [Image](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) teaches you how to extend a new node and render it
- * And more examples on [rich-text plugins](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text)
-* Customize a shortcut event
- * [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart) shows you how to make text bold/italic/underline/strikethrough through shortcut keys
- * [Paste HTML](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart) gives you an idea on how to handle pasted styles through shortcut keys
- * Need more examples? Check out [Internal key event handlers](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers)
+ * [Checkbox Text](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart) demonstrates how to extend new styles based on existing rich text components
+ * [Image](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) demonstrates how to extend a new node and render it
+ * See further examples of [rich-text plugins](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text)
+
+### Customizing Shortcut Events
+
+Please refer to our documentation on customizing AppFlowy for a detailed discussion about [customizing shortcut events](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md#customize-a-shortcut-event).
+
+Below are some examples of shortcut event customizations:
+
+ * [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
+ * [Paste HTML](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart) gives you an idea on how to handle pasted styles through shortcut keys
+ * Need more examples? Check out [Internal key event handlers](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers)
## Glossary
Please refer to the API documentation.
## Contributing
-Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated. Please look at [CONTRIBUTING.md](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/contributing-to-appflowy) for details.
+Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.
+
+Please look at [CONTRIBUTING.md](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/contributing-to-appflowy) for details.
## License
-Distributed under the AGPLv3 License. See LICENSE for more information.
+Distributed under the AGPLv3 License. See [LICENSE](https://github.com/AppFlowy-IO/AppFlowy-Docs/blob/main/LICENSE) for more information.
diff --git a/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md b/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md
index 85fa195c96..ed0f257ad0 100644
--- a/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md
+++ b/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md
@@ -1,12 +1,12 @@
-# How to customize ...
+# Customizing Editor Features
-## Customize a shortcut event
+## Customizing a Shortcut Event
We will use a simple example to illustrate how to quickly add a shortcut event.
-For example, typing `_xxx_` will be converted into _xxx_.
+In this example, text that starts and ends with an underscore ( \_ ) character will be rendered in italics for emphasis. So typing `_xxx_` will automatically be converted into _xxx_.
-Let's start with a blank document.
+Let's start with a blank document:
```dart
@override
@@ -27,7 +27,7 @@ At this point, nothing magic will happen after typing `_xxx_`.

-Next, we will create a function to handle an underscore input.
+To implement our shortcut event we will create a function to handle an underscore input.
```dart
import 'package:appflowy_editor/appflowy_editor.dart';
@@ -35,23 +35,25 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
FlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
- // Since we only need to handler the input of `underscore`.
- // All inputs except `underscore` will be ignored directly.
+ // Since we only need to handle the input of an 'underscore' character,
+ // all inputs except `underscore` will be ignored immediately.
if (event.logicalKey != LogicalKeyboardKey.underscore) {
return KeyEventResult.ignored;
}
};
```
-Then, we need to determine if the currently selected node is `TextNode` and the selection is collapsed.
+Then, we need to determine if the currently selected node is a `TextNode` and if the selection is collapsed.
+
+If so, we will continue.
```dart
// ...
FlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
// ...
- // Obtaining the selection and selected nodes of the current document through `selectionService`.
- // And determine whether the selection is collapsed and whether the selected node is a text node.
+ // Obtain the selection and selected nodes of the current document through the 'selectionService'
+ // to determine whether the selection is collapsed and whether the selected node is a text node.
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType();
@@ -60,11 +62,11 @@ FlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
}
```
-Now, we start dealing with underscore.
+Now, we deal with handling the underscore.
Look for the position of the previous underscore and
-1. return, if not found.
-2. if found, the text wrapped in between two underscores will be displayed in italic.
+1. if one is _not_ found, return without doing anything.
+2. if one is found, the text enclosed within the two underscores will be formatted to display in italics.
```dart
// ...
@@ -73,14 +75,14 @@ FlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
final textNode = textNodes.first;
final text = textNode.toRawString();
- // Determine if `underscore` already exists in the text node
+ // Determine if an 'underscore' already exists in the text node
final previousUnderscore = text.indexOf('_');
if (previousUnderscore == -1) {
return KeyEventResult.ignored;
}
- // Delete the previous `underscore`,
- // update the style of the text surrounded by two underscores to `italic`,
+ // Delete the previous 'underscore',
+ // update the style of the text surrounded by the two underscores to 'italic',
// and update the cursor position.
TransactionBuilder(editorState)
..deleteText(textNode, previousUnderscore, 1)
@@ -99,7 +101,7 @@ FlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
};
```
-So far, the 'underscore handler' function is done and the only task left is to inject it into the AppFlowyEditor.
+Now our 'underscore handler' function is done and the only task left is to inject it into the AppFlowyEditor.
```dart
@override
@@ -120,14 +122,15 @@ Widget build(BuildContext context) {

-[Complete code example]()
+Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic_key_event_handler.dart) file of this example.
-## Customize a component
-We will use a simple example to showcase how to quickly add a custom component.
-For example, we want to render an image from the network.
+## Customizing a Component
+We will use a simple example to show how to quickly add a custom component.
-To start with, let's create an empty document by running commands as follows:
+In this example we will render an image from the network.
+
+Let's start with a blank document:
```dart
@override
@@ -144,9 +147,9 @@ Widget build(BuildContext context) {
}
```
-Next, we choose a unique string for your custom node's type. We use `network_image` in this case. And we add `network_image_src` to the `attributes` to describe the link of the image.
+Next, we will choose a unique string for your custom node's type.
-> For the definition of the [Node](), please refer to this [link]().
+We'll use `network_image` in this case. And we add `network_image_src` to the `attributes` to describe the link of the image.
```JSON
{
@@ -157,9 +160,9 @@ Next, we choose a unique string for your custom node's type. We use `network_ima
}
```
-Then, we create a class that inherits [NodeWidgetBuilder](). As shown in the autoprompt, we need to implement two functions:
+Then, we create a class that inherits [NodeWidgetBuilder](../lib/src/service/render_plugin_service.dart). As shown in the autoprompt, we need to implement two functions:
1. one returns a widget
-2. the other verifies the correctness of the [Node]().
+2. the other verifies the correctness of the [Node](../lib/src/document/node.dart).
```dart
@@ -179,9 +182,7 @@ class NetworkImageNodeWidgetBuilder extends NodeWidgetBuilder {
Now, let's implement a simple image widget based on `Image`.
-**It is important to note that the `State` of the returned `Widget` must be with [Selectable]().**
-
-> For the definition of the [Selectable](), please refer to this [link]().
+Note that the `State` object that is returned by the `Widget` must implement [Selectable](../lib/src/render/selection/selectable.dart) using the `with` keyword.
```dart
class _NetworkImageNodeWidget extends StatefulWidget {
@@ -236,7 +237,7 @@ class __NetworkImageNodeWidgetState extends State<_NetworkImageNodeWidget>
}
```
-Finally, we return `_NetworkImageNodeWidget` in the `build` function of `NetworkImageNodeWidgetBuilder` and register `NetworkImageNodeWidgetBuilder` into `AppFlowyEditor`.
+Finally, we return `_NetworkImageNodeWidget` in the `build` function of `NetworkImageNodeWidgetBuilder`...
```dart
class NetworkImageNodeWidgetBuilder extends NodeWidgetBuilder {
@@ -256,6 +257,8 @@ class NetworkImageNodeWidgetBuilder extends NodeWidgetBuilder {
}
```
+... and register `NetworkImageNodeWidgetBuilder` in the `AppFlowyEditor`.
+
```dart
final editorState = EditorState(
document: StateTree.empty()
@@ -281,6 +284,6 @@ return AppFlowyEditor(
);
```
-
+
-[Here you can check out the complete code file of this example]()
+Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) file of this example.
diff --git a/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif b/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif
new file mode 100644
index 0000000000..a849562052
Binary files /dev/null and b/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif differ
diff --git a/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md b/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md
index 25d918c4b5..d51583a9c5 100644
--- a/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md
+++ b/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md
@@ -1,24 +1,33 @@
# Testing
-> The directory structure of test files is consistent with the code files, making it easy for us to map a file with the corresponding test and check if the test is updated
+The directory structure of test files mirrors that of the code files, making it easy for us to map a file with the corresponding test and check if the test is updated.
-## Testing Functions
+For an overview of testing best practices in Flutter applications, please refer to Flutter's [introduction to widget testing](https://docs.flutter.dev/cookbook/testing/widget/introduction) as well as their [introduction to unit testing](https://docs.flutter.dev/cookbook/testing/unit/introduction).
+There you will learn how to do such things as such as simulate a click as well as leverage the `test` and `expect` functions.
+
+## Testing Basic Editor Functions
+
+The example code below shows how to construct a document that will be used in our testing.
-**Construct a document for testing**
```dart
const text = 'Welcome to Appflowy 😁';
-// Get the instance of editor.
+// Get the instance of the editor.
final editor = tester.editor;
-// Insert empty text node.
+
+// Insert an empty text node.
editor.insertEmptyTextNode();
-// Insert text node with string.
+
+// Insert a text node with the text string we defined earlier.
editor.insertTextNode(text);
-// Insert text node with heading style.
+
+// Insert the same text, but with the heading style.
editor.insertTextNode(text, attributes: {
StyleKey.subtype: StyleKey.heading,
StyleKey.heading: StyleKey.h1,
});
-// Insert text node with bulleted list style and bold style.
+
+// Insert our text with the bulleted list style and the bold style.
+// If you want to modify the style of the inserted text, you need to use the Delta parameter.
editor.insertTextNode(
'',
attributes: {
@@ -30,66 +39,76 @@ editor.insertTextNode(
);
```
-**The `startTesting` function must be called before testing**.
+The `startTesting` function of the editor must be called before you begin your test.
+
```dart
await editor.startTesting();
```
-**Get the number of nodes in the document**
+Get the number of nodes in the document.
+
```dart
final length = editor.documentLength;
print(length);
```
-**Get the node of a defined path**
+Get the node of a defined path. In this case we are getting the first node of the document which is the text "Welcome to Appflowy 😁".
+
```dart
final firstTextNode = editor.nodeAtPath([0]) as TextNode;
```
-**Update selection**
+Update the [Selection](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/document/selection.dart) so that our text "Welcome to Appflowy 😁" is selected. We will start our selection from the beginning of the string.
+
```dart
await editor.updateSelection(
Selection.single(path: firstTextNode.path, startOffset: 0),
);
```
-**Get the selection**
+Get the current selection.
+
```dart
final selection = editor.documentSelection;
print(selection);
```
-**Simulate shortcut event inputs**
+Next we will simulate the input of a shortcut key being pressed that will select all the text.
+
```dart
-// Command + A.
+// Meta + A.
await editor.pressLogicKey(LogicalKeyboardKey.keyA, isMetaPressed: true);
-// Command + shift + S.
+// Meta + shift + S.
await editor.pressLogicKey(
- LogicalKeyboardKey.keyS,
- isMetaPressed: true,
+ LogicalKeyboardKey.keyS,
+ isMetaPressed: true,
isShiftPressed: true,
);
```
-**Simulate a text input**
+We will then simulate text input.
+
```dart
// Insert 'Hello World' at the beginning of the first node.
editor.insertText(firstTextNode, 'Hello World', 0);
```
-**Get information about the text node**
+Once the text has been added, we can get information about the text node.
+
```dart
-// Get plain text.
+// Get the text of the first text node as plain text
final textAfterInserted = firstTextNode.toRawString();
print(textAfterInserted);
-// Get attributes.
+// Get the attributes of the text node
final attributes = firstTextNode.attributes;
print(attributes);
```
-## Example
-For example, we are going to test `select_all_handler.dart`
+## A Complete Code Example
+In the example code below we are going to test `select_all_handler.dart` by inserting 100 lines of text that read "Welcome to Appflowy 😁" and then simulating the "selectAll" shortcut key being pressed.
+
+Afterwards, we will `expect` that the current selection of the editor is equal to the selection of all the lines that were generated.
```dart
import 'package:appflowy_editor/appflowy_editor.dart';
@@ -124,5 +143,3 @@ void main() async {
});
}
```
-
-For more information about testing, such as simulating a click, please refer to [An introduction to widget testing](https://docs.flutter.dev/cookbook/testing/widget/introduction)
diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/image_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/image_node_widget.dart
index c535bc6295..d76ce8d6a2 100644
--- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/image_node_widget.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/image_node_widget.dart
@@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
/// 2. create a class extends [NodeWidgetBuilder]
/// 3. override the function `Widget build(NodeWidgetContext context)`
/// and return a widget to render. The returned widget should be
-/// a StatefulWidget and mixin with [Selectable].
+/// a StatefulWidget and mixin with [SelectableMixin].
///
/// 4. override the getter `nodeValidator`
/// to verify the data structure in [Node].
@@ -50,7 +50,8 @@ class ImageNodeWidget extends StatefulWidget {
State createState() => _ImageNodeWidgetState();
}
-class _ImageNodeWidgetState extends State with Selectable {
+class _ImageNodeWidgetState extends State
+ with SelectableMixin {
bool isHovered = false;
Node get node => widget.node;
EditorState get editorState => widget.editorState;
diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart
index 84d2e72918..395fc175f4 100644
--- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart
@@ -31,7 +31,7 @@ class _NetworkImageNodeWidget extends StatefulWidget {
}
class __NetworkImageNodeWidgetState extends State<_NetworkImageNodeWidget>
- with Selectable {
+ with SelectableMixin {
RenderBox get _renderBox => context.findRenderObject() as RenderBox;
@override
diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/youtube_link_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/youtube_link_node_widget.dart
index 27bbe922e3..3923c60c45 100644
--- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/youtube_link_node_widget.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/youtube_link_node_widget.dart
@@ -33,7 +33,7 @@ class LinkNodeWidget extends StatefulWidget {
}
class _YouTubeLinkNodeWidgetState extends State
- with Selectable {
+ with SelectableMixin {
Node get node => widget.node;
EditorState get editorState => widget.editorState;
String get src => widget.node.attributes['youtube_link'] as String;
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart
index 12b3a29252..a77f3fe7eb 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart
@@ -20,5 +20,6 @@ export 'src/service/render_plugin_service.dart';
export 'src/service/service.dart';
export 'src/service/selection_service.dart';
export 'src/service/scroll_service.dart';
+export 'src/service/toolbar_service.dart';
export 'src/service/keyboard_service.dart';
export 'src/service/input_service.dart';
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/document/node_iterator.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/document/node_iterator.dart
index 9c666bdfea..ccae0f43d1 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/document/node_iterator.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/document/node_iterator.dart
@@ -1,7 +1,6 @@
import 'package:appflowy_editor/src/document/node.dart';
import './state_tree.dart';
-import './node.dart';
/// [NodeIterator] is used to traverse the nodes in visual order.
class NodeIterator implements Iterator {
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/document/position.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/document/position.dart
index a87064d85a..cea065fdea 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/document/position.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/document/position.dart
@@ -1,5 +1,3 @@
-import 'package:flutter/material.dart';
-
import './path.dart';
class Position {
@@ -21,7 +19,7 @@ class Position {
@override
int get hashCode {
- final pathHash = hashList(path);
+ final pathHash = Object.hashAll(path);
return Object.hash(pathHash, offset);
}
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/document/text_delta.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/document/text_delta.dart
index 2e6fa82437..3ca877cc98 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/document/text_delta.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/document/text_delta.dart
@@ -3,8 +3,6 @@ import 'dart:math';
import 'package:appflowy_editor/src/document/attributes.dart';
import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import './attributes.dart';
// constant number: 2^53 - 1
const int _maxInt = 9007199254740991;
@@ -463,7 +461,7 @@ class Delta extends Iterable {
@override
int get hashCode {
- return hashList(_operations);
+ return Object.hashAll(_operations);
}
/// Returned an inverted delta that has the opposite effect of against a base document delta.
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/node_extensions.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/node_extensions.dart
index 5df8665a6b..dffca3eaf0 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/node_extensions.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/node_extensions.dart
@@ -10,7 +10,8 @@ extension NodeExtensions on Node {
key?.currentContext?.findRenderObject()?.unwrapOrNull();
BuildContext? get context => key?.currentContext;
- Selectable? get selectable => key?.currentState?.unwrapOrNull();
+ SelectableMixin? get selectable =>
+ key?.currentState?.unwrapOrNull();
bool inSelection(Selection selection) {
if (selection.start.path <= selection.end.path) {
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_widget.dart
index a65df11541..c720231f6e 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_widget.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_widget.dart
@@ -32,7 +32,8 @@ class ImageNodeWidget extends StatefulWidget {
State createState() => _ImageNodeWidgetState();
}
-class _ImageNodeWidgetState extends State with Selectable {
+class _ImageNodeWidgetState extends State
+ with SelectableMixin {
final _imageKey = GlobalKey();
double? _imageWidth;
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart
index 7f0f0363f8..58c86e7670 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart
@@ -42,7 +42,7 @@ class BulletedListTextNodeWidget extends StatefulWidget {
// customize
class _BulletedListTextNodeWidgetState extends State
- with Selectable, DefaultSelectable {
+ with SelectableMixin, DefaultSelectable {
@override
final iconKey = GlobalKey();
@@ -51,8 +51,8 @@ class _BulletedListTextNodeWidgetState extends State
final _iconRightPadding = 5.0;
@override
- Selectable get forward =>
- _richTextKey.currentState as Selectable;
+ SelectableMixin get forward =>
+ _richTextKey.currentState as SelectableMixin;
@override
Widget build(BuildContext context) {
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart
index ed6748a43e..d6458304d6 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart
@@ -40,7 +40,7 @@ class CheckboxNodeWidget extends StatefulWidget {
}
class _CheckboxNodeWidgetState extends State
- with Selectable, DefaultSelectable {
+ with SelectableMixin, DefaultSelectable {
@override
final iconKey = GlobalKey();
@@ -49,8 +49,8 @@ class _CheckboxNodeWidgetState extends State
final _iconRightPadding = 5.0;
@override
- Selectable get forward =>
- _richTextKey.currentState as Selectable;
+ SelectableMixin get forward =>
+ _richTextKey.currentState as SelectableMixin;
@override
Widget build(BuildContext context) {
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/default_selectable.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/default_selectable.dart
index c477478deb..fd86f84831 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/default_selectable.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/default_selectable.dart
@@ -4,7 +4,7 @@ import 'package:appflowy_editor/src/render/selection/selectable.dart';
import 'package:flutter/material.dart';
mixin DefaultSelectable {
- Selectable get forward;
+ SelectableMixin get forward;
GlobalKey? get iconKey;
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart
index 9d1b7f119e..9292c1e5cd 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart
@@ -42,7 +42,7 @@ class FlowyRichText extends StatefulWidget {
State createState() => _FlowyRichTextState();
}
-class _FlowyRichTextState extends State with Selectable {
+class _FlowyRichTextState extends State with SelectableMixin {
var _textKey = GlobalKey();
final _placeholderTextKey = GlobalKey();
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart
index 93defaae8e..d58030ef15 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart
@@ -39,7 +39,7 @@ class HeadingTextNodeWidget extends StatefulWidget {
// customize
class _HeadingTextNodeWidgetState extends State
- with Selectable, DefaultSelectable {
+ with SelectableMixin, DefaultSelectable {
@override
GlobalKey? get iconKey => null;
@@ -47,8 +47,8 @@ class _HeadingTextNodeWidgetState extends State
final _topPadding = 5.0;
@override
- Selectable get forward =>
- _richTextKey.currentState as Selectable;
+ SelectableMixin get forward =>
+ _richTextKey.currentState as SelectableMixin;
@override
Offset get baseOffset {
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart
index 36cf91bdce..36d3d93ed9 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart
@@ -42,7 +42,7 @@ class NumberListTextNodeWidget extends StatefulWidget {
// customize
class _NumberListTextNodeWidgetState extends State
- with Selectable, DefaultSelectable {
+ with SelectableMixin, DefaultSelectable {
@override
final iconKey = GlobalKey();
@@ -51,8 +51,8 @@ class _NumberListTextNodeWidgetState extends State
final _iconRightPadding = 5.0;
@override
- Selectable get forward =>
- _richTextKey.currentState as Selectable;
+ SelectableMixin get forward =>
+ _richTextKey.currentState as SelectableMixin;
@override
Widget build(BuildContext context) {
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart
index 9c2366d1cb..9e36acfad0 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart
@@ -41,7 +41,7 @@ class QuotedTextNodeWidget extends StatefulWidget {
// customize
class _QuotedTextNodeWidgetState extends State
- with Selectable, DefaultSelectable {
+ with SelectableMixin, DefaultSelectable {
@override
final iconKey = GlobalKey();
@@ -50,8 +50,8 @@ class _QuotedTextNodeWidgetState extends State
final _iconRightPadding = 5.0;
@override
- Selectable get forward =>
- _richTextKey.currentState as Selectable;
+ SelectableMixin get forward =>
+ _richTextKey.currentState as SelectableMixin;
@override
Widget build(BuildContext context) {
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart
index b9a3e2f314..a00e1d7c14 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart
@@ -40,15 +40,15 @@ class RichTextNodeWidget extends StatefulWidget {
// customize
class _RichTextNodeWidgetState extends State
- with Selectable, DefaultSelectable {
+ with SelectableMixin, DefaultSelectable {
@override
GlobalKey? get iconKey => null;
final _richTextKey = GlobalKey(debugLabel: 'rich_text');
@override
- Selectable get forward =>
- _richTextKey.currentState as Selectable;
+ SelectableMixin get forward =>
+ _richTextKey.currentState as SelectableMixin;
@override
Widget build(BuildContext context) {
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/selectable.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/selectable.dart
index 434b4b67b1..372dbd7067 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/selectable.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/selectable.dart
@@ -2,12 +2,12 @@ import 'package:appflowy_editor/src/document/position.dart';
import 'package:appflowy_editor/src/document/selection.dart';
import 'package:flutter/material.dart';
-/// [Selectable] is used for the editor to calculate the position
+/// [SelectableMixin] is used for the editor to calculate the position
/// and size of the selection.
///
-/// The widget returned by NodeWidgetBuilder must be with [Selectable],
+/// The widget returned by NodeWidgetBuilder must be with [SelectableMixin],
/// otherwise the [AppFlowySelectionService] will not work properly.
-mixin Selectable on State {
+mixin SelectableMixin on State {
/// Returns the [Selection] surrounded by start and end
/// in current widget.
///
diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart
index ca442f4ff9..ef3b501a5b 100644
--- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart
@@ -510,7 +510,7 @@ class _AppFlowySelectionState extends State
editorState.service.scrollService?.enable();
}
- Rect _transformRectToGlobal(Selectable selectable, Rect r) {
+ Rect _transformRectToGlobal(SelectableMixin selectable, Rect r) {
final Offset topLeft = selectable.localToGlobal(Offset(r.left, r.top));
return Rect.fromLTWH(topLeft.dx, topLeft.dy, r.width, r.height);
}
diff --git a/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml b/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml
index 295a45ad8c..17f75aea6a 100644
--- a/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml
+++ b/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml
@@ -1,8 +1,13 @@
name: appflowy_editor
description: A highly customizable rich-text editor for Flutter
-version: 0.0.3
+version: 0.0.4
homepage: https://github.com/AppFlowy-IO/AppFlowy
+platforms:
+ linux:
+ macos:
+ windows:
+
environment:
sdk: ">=2.17.0 <3.0.0"
flutter: ">=1.17.0"
diff --git a/frontend/app_flowy/packages/appflowy_editor/test/legacy/flowy_editor_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/legacy/flowy_editor_test.dart
index ab37cfec0c..df586b3ac0 100644
--- a/frontend/app_flowy/packages/appflowy_editor/test/legacy/flowy_editor_test.dart
+++ b/frontend/app_flowy/packages/appflowy_editor/test/legacy/flowy_editor_test.dart
@@ -1,7 +1,6 @@
import 'package:appflowy_editor/src/document/path.dart';
import 'package:appflowy_editor/src/document/position.dart';
import 'package:appflowy_editor/src/document/selection.dart';
-import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
@@ -75,7 +74,7 @@ void main() {
final path2 = [1];
expect(pathEquals(path1, path2), true);
- expect(hashList(path1), hashList(path2));
+ expect(Object.hashAll(path1), Object.hashAll(path2));
});
test('test path utils 2', () {
@@ -83,7 +82,7 @@ void main() {
final path2 = [2];
expect(pathEquals(path1, path2), false);
- expect(hashList(path1) != hashList(path2), true);
+ expect(Object.hashAll(path1) != Object.hashAll(path2), true);
});
test('test position comparator', () {
diff --git a/frontend/app_flowy/pubspec.lock b/frontend/app_flowy/pubspec.lock
index 76d489c915..3e2bdfe2be 100644
--- a/frontend/app_flowy/pubspec.lock
+++ b/frontend/app_flowy/pubspec.lock
@@ -35,7 +35,7 @@ packages:
path: "packages/appflowy_editor"
relative: true
source: path
- version: "0.0.3"
+ version: "0.0.4"
appflowy_popover:
dependency: "direct main"
description:
diff --git a/frontend/rust-lib/flowy-grid/src/dart_notification.rs b/frontend/rust-lib/flowy-grid/src/dart_notification.rs
index a0030c6773..8d7ec2ba36 100644
--- a/frontend/rust-lib/flowy-grid/src/dart_notification.rs
+++ b/frontend/rust-lib/flowy-grid/src/dart_notification.rs
@@ -13,6 +13,8 @@ pub enum GridNotification {
DidUpdateField = 50,
DidUpdateGroupView = 60,
DidUpdateGroup = 61,
+ DidGroupByNewField = 62,
+ DidUpdateGridSetting = 70,
}
impl std::default::Default for GridNotification {
diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
index 7dff00bf56..c6737e8e76 100644
--- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
+++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
@@ -10,33 +10,33 @@ use std::convert::TryInto;
use std::sync::Arc;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct GridFilterConfiguration {
+pub struct GridFilterConfigurationPB {
#[pb(index = 1)]
pub id: String,
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct RepeatedGridConfigurationFilterPB {
+pub struct RepeatedGridFilterConfigurationPB {
#[pb(index = 1)]
- pub items: Vec,
+ pub items: Vec,
}
-impl std::convert::From<&FilterConfigurationRevision> for GridFilterConfiguration {
+impl std::convert::From<&FilterConfigurationRevision> for GridFilterConfigurationPB {
fn from(rev: &FilterConfigurationRevision) -> Self {
Self { id: rev.id.clone() }
}
}
-impl std::convert::From>> for RepeatedGridConfigurationFilterPB {
+impl std::convert::From>> for RepeatedGridFilterConfigurationPB {
fn from(revs: Vec>) -> Self {
- RepeatedGridConfigurationFilterPB {
+ RepeatedGridFilterConfigurationPB {
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
}
}
}
-impl std::convert::From> for RepeatedGridConfigurationFilterPB {
- fn from(items: Vec) -> Self {
+impl std::convert::From> for RepeatedGridFilterConfigurationPB {
+ fn from(items: Vec) -> Self {
Self { items }
}
}
@@ -78,7 +78,7 @@ pub struct DeleteFilterParams {
}
#[derive(ProtoBuf, Debug, Default, Clone)]
-pub struct CreateGridFilterPayloadPB {
+pub struct InsertFilterPayloadPB {
#[pb(index = 1)]
pub field_id: String,
@@ -92,7 +92,7 @@ pub struct CreateGridFilterPayloadPB {
pub content: Option,
}
-impl CreateGridFilterPayloadPB {
+impl InsertFilterPayloadPB {
#[allow(dead_code)]
pub fn new>(field_rev: &FieldRevision, condition: T, content: Option) -> Self {
Self {
@@ -104,10 +104,10 @@ impl CreateGridFilterPayloadPB {
}
}
-impl TryInto for CreateGridFilterPayloadPB {
+impl TryInto for InsertFilterPayloadPB {
type Error = ErrorCode;
- fn try_into(self) -> Result {
+ fn try_into(self) -> Result {
let field_id = NotEmptyStr::parse(self.field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0;
@@ -130,7 +130,7 @@ impl TryInto for CreateGridFilterPayloadPB {
}
}
- Ok(CreateFilterParams {
+ Ok(InsertFilterParams {
field_id,
field_type_rev: self.field_type.into(),
condition,
@@ -139,7 +139,7 @@ impl TryInto for CreateGridFilterPayloadPB {
}
}
-pub struct CreateFilterParams {
+pub struct InsertFilterParams {
pub field_id: String,
pub field_type_rev: FieldTypeRevision,
pub condition: u8,
diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs
index 002cb73c6d..de6f920341 100644
--- a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs
+++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs
@@ -91,6 +91,9 @@ pub struct GroupPB {
#[pb(index = 5)]
pub is_default: bool,
+
+ #[pb(index = 6)]
+ pub is_visible: bool,
}
impl std::convert::From for GroupPB {
@@ -101,6 +104,7 @@ impl std::convert::From for GroupPB {
desc: group.name,
rows: group.rows,
is_default: group.is_default,
+ is_visible: group.is_visible,
}
}
}
@@ -126,7 +130,7 @@ impl std::convert::From>> for RepeatedGridGr
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct CreateGridGroupPayloadPB {
+pub struct InsertGroupPayloadPB {
#[pb(index = 1)]
pub field_id: String,
@@ -134,22 +138,22 @@ pub struct CreateGridGroupPayloadPB {
pub field_type: FieldType,
}
-impl TryInto for CreateGridGroupPayloadPB {
+impl TryInto for InsertGroupPayloadPB {
type Error = ErrorCode;
- fn try_into(self) -> Result {
+ fn try_into(self) -> Result {
let field_id = NotEmptyStr::parse(self.field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0;
- Ok(CreatGroupParams {
+ Ok(InsertGroupParams {
field_id,
field_type_rev: self.field_type.into(),
})
}
}
-pub struct CreatGroupParams {
+pub struct InsertGroupParams {
pub field_id: String,
pub field_type_rev: FieldTypeRevision,
}
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 21f39775f6..63b883d570 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
@@ -134,15 +134,21 @@ pub struct GroupViewChangesetPB {
pub inserted_groups: Vec,
#[pb(index = 3)]
- pub deleted_groups: Vec,
+ pub new_groups: Vec,
#[pb(index = 4)]
+ pub deleted_groups: Vec,
+
+ #[pb(index = 5)]
pub update_groups: Vec,
}
impl GroupViewChangesetPB {
pub fn is_empty(&self) -> bool {
- self.inserted_groups.is_empty() && self.deleted_groups.is_empty() && self.update_groups.is_empty()
+ self.new_groups.is_empty()
+ && self.inserted_groups.is_empty()
+ && self.deleted_groups.is_empty()
+ && self.update_groups.is_empty()
}
}
diff --git a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs
index 9c02a2c692..bbf5af831f 100644
--- a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs
+++ b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs
@@ -1,13 +1,12 @@
use crate::entities::{
- CreatGroupParams, CreateFilterParams, CreateGridFilterPayloadPB, CreateGridGroupPayloadPB, DeleteFilterParams,
- DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, RepeatedGridConfigurationFilterPB,
+ DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, InsertFilterParams,
+ InsertFilterPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedGridFilterConfigurationPB,
RepeatedGridGroupConfigurationPB,
};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
use flowy_grid_data_model::revision::LayoutRevision;
-use std::collections::HashMap;
use std::convert::TryInto;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
@@ -19,13 +18,13 @@ pub struct GridSettingPB {
pub layouts: Vec,
#[pb(index = 2)]
- pub current_layout_type: GridLayout,
+ pub layout_type: GridLayout,
#[pb(index = 3)]
- pub filter_configuration_by_field_id: HashMap,
+ pub filter_configurations: RepeatedGridFilterConfigurationPB,
#[pb(index = 4)]
- pub group_configuration_by_field_id: HashMap,
+ pub group_configurations: RepeatedGridGroupConfigurationPB,
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
@@ -85,13 +84,13 @@ pub struct GridSettingChangesetPayloadPB {
pub layout_type: GridLayout,
#[pb(index = 3, one_of)]
- pub insert_filter: Option,
+ pub insert_filter: Option,
#[pb(index = 4, one_of)]
pub delete_filter: Option,
#[pb(index = 5, one_of)]
- pub insert_group: Option,
+ pub insert_group: Option,
#[pb(index = 6, one_of)]
pub delete_group: Option,
@@ -102,7 +101,7 @@ impl TryInto for GridSettingChangesetPayloadPB {
fn try_into(self) -> Result {
let view_id = NotEmptyStr::parse(self.grid_id)
- .map_err(|_| ErrorCode::FieldIdIsEmpty)?
+ .map_err(|_| ErrorCode::ViewIdInvalid)?
.0;
let insert_filter = match self.insert_filter {
@@ -139,9 +138,9 @@ impl TryInto for GridSettingChangesetPayloadPB {
pub struct GridSettingChangesetParams {
pub grid_id: String,
pub layout_type: LayoutRevision,
- pub insert_filter: Option,
+ pub insert_filter: Option,
pub delete_filter: Option,
- pub insert_group: Option,
+ pub insert_group: Option,
pub delete_group: Option,
}
diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs
index 4b525c233f..c9b6cf4662 100644
--- a/frontend/rust-lib/flowy-grid/src/event_handler.rs
+++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs
@@ -35,6 +35,32 @@ pub(crate) async fn get_grid_setting_handler(
data_result(grid_setting)
}
+#[tracing::instrument(level = "trace", skip(data, manager), err)]
+pub(crate) async fn update_grid_setting_handler(
+ data: Data,
+ manager: AppData>,
+) -> Result<(), FlowyError> {
+ let params: GridSettingChangesetParams = data.into_inner().try_into()?;
+
+ let editor = manager.get_grid_editor(¶ms.grid_id)?;
+ if let Some(insert_params) = params.insert_group {
+ let _ = editor.create_group(insert_params).await?;
+ }
+
+ if let Some(delete_params) = params.delete_group {
+ let _ = editor.delete_group(delete_params).await?;
+ }
+
+ if let Some(create_filter) = params.insert_filter {
+ let _ = editor.create_filter(create_filter).await?;
+ }
+
+ if let Some(delete_filter) = params.delete_filter {
+ let _ = editor.delete_filter(delete_filter).await?;
+ }
+ Ok(())
+}
+
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn get_grid_blocks_handler(
data: Data,
@@ -203,12 +229,14 @@ pub(crate) async fn move_field_handler(
/// The FieldMeta contains multiple data, each of them belongs to a specific FieldType.
async fn get_type_option_data(field_rev: &FieldRevision, field_type: &FieldType) -> FlowyResult> {
- let s = field_rev
- .get_type_option_str(field_type)
- .unwrap_or_else(|| default_type_option_builder_from_type(field_type).entry().json_str());
+ let s = field_rev.get_type_option_str(field_type).unwrap_or_else(|| {
+ default_type_option_builder_from_type(field_type)
+ .data_format()
+ .json_str()
+ });
let field_type: FieldType = field_rev.ty.into();
let builder = type_option_builder_from_json_str(&s, &field_type);
- let type_option_data = builder.entry().protobuf_bytes().to_vec();
+ let type_option_data = builder.data_format().protobuf_bytes().to_vec();
Ok(type_option_data)
}
@@ -337,7 +365,7 @@ pub(crate) async fn update_select_option_handler(
type_option.delete_option(option);
}
- mut_field_rev.insert_type_option_entry(&*type_option);
+ mut_field_rev.insert_type_option(&*type_option);
let _ = editor.replace_field(field_rev).await?;
if let Some(cell_content_changeset) = cell_content_changeset {
diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs
index a78bcb5ed3..e0a5ffcf1b 100644
--- a/frontend/rust-lib/flowy-grid/src/event_map.rs
+++ b/frontend/rust-lib/flowy-grid/src/event_map.rs
@@ -11,7 +11,7 @@ pub fn create(grid_manager: Arc) -> Module {
.event(GridEvent::GetGrid, get_grid_handler)
.event(GridEvent::GetGridBlocks, get_grid_blocks_handler)
.event(GridEvent::GetGridSetting, get_grid_setting_handler)
- // .event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
+ .event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
// Field
.event(GridEvent::GetFields, get_fields_handler)
.event(GridEvent::UpdateField, update_field_handler)
@@ -75,8 +75,8 @@ pub enum GridEvent {
/// [UpdateGridSetting] event is used to update the grid's settings.
///
- /// The event handler accepts [GridIdPB] and return errors if failed to modify the grid's settings.
- #[event(input = "GridIdPB", input = "GridSettingChangesetPayloadPB")]
+ /// The event handler accepts [GridSettingChangesetPayloadPB] and return errors if failed to modify the grid's settings.
+ #[event(input = "GridSettingChangesetPayloadPB")]
UpdateGridSetting = 3,
/// [GetFields] event is used to get the grid's settings.
@@ -225,4 +225,7 @@ pub enum GridEvent {
#[event(input = "MoveGroupRowPayloadPB")]
MoveGroupRow = 112,
+
+ #[event(input = "MoveGroupRowPayloadPB")]
+ GroupByField = 113,
}
diff --git a/frontend/rust-lib/flowy-grid/src/macros.rs b/frontend/rust-lib/flowy-grid/src/macros.rs
index aee8ca2b68..bee2e95e21 100644
--- a/frontend/rust-lib/flowy-grid/src/macros.rs
+++ b/frontend/rust-lib/flowy-grid/src/macros.rs
@@ -30,7 +30,7 @@ macro_rules! impl_type_option {
($target: ident, $field_type:expr) => {
impl std::convert::From<&FieldRevision> for $target {
fn from(field_rev: &FieldRevision) -> $target {
- match field_rev.get_type_option_entry::<$target>($field_type.into()) {
+ match field_rev.get_type_option::<$target>($field_type.into()) {
None => $target::default(),
Some(target) => target,
}
@@ -39,7 +39,7 @@ macro_rules! impl_type_option {
impl std::convert::From<&std::sync::Arc> for $target {
fn from(field_rev: &std::sync::Arc) -> $target {
- match field_rev.get_type_option_entry::<$target>($field_type.into()) {
+ match field_rev.get_type_option::<$target>($field_type.into()) {
None => $target::default(),
Some(target) => target,
}
@@ -52,7 +52,7 @@ macro_rules! impl_type_option {
}
}
- impl TypeOptionDataEntry for $target {
+ impl TypeOptionDataFormat for $target {
fn json_str(&self) -> String {
match serde_json::to_string(&self) {
Ok(s) => s,
diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs
index ecff3b951b..17c52dd3a2 100644
--- a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs
@@ -101,25 +101,25 @@ pub fn try_decode_cell_data(
let field_type: FieldTypeRevision = t_field_type.into();
let data = match t_field_type {
FieldType::RichText => field_rev
- .get_type_option_entry::(field_type)?
+ .get_type_option::(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::Number => field_rev
- .get_type_option_entry::(field_type)?
+ .get_type_option::(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::DateTime => field_rev
- .get_type_option_entry::(field_type)?
+ .get_type_option::(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::SingleSelect => field_rev
- .get_type_option_entry::(field_type)?
+ .get_type_option::(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::MultiSelect => field_rev
- .get_type_option_entry::(field_type)?
+ .get_type_option::(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::Checkbox => field_rev
- .get_type_option_entry::(field_type)?
+ .get_type_option::(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::URL => field_rev
- .get_type_option_entry::(field_type)?
+ .get_type_option::(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
};
Some(data)
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs
index 0242a5ca43..4a1d57c28f 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs
@@ -1,7 +1,7 @@
use crate::entities::{FieldPB, FieldType};
use crate::services::field::type_options::*;
use bytes::Bytes;
-use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry};
+use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataFormat};
use indexmap::IndexMap;
pub struct FieldBuilder {
@@ -78,14 +78,14 @@ impl FieldBuilder {
pub fn build(self) -> FieldRevision {
let mut field_rev = self.field_rev;
- field_rev.insert_type_option_entry(self.type_option_builder.entry());
+ field_rev.insert_type_option(self.type_option_builder.data_format());
field_rev
}
}
pub trait TypeOptionBuilder {
fn field_type(&self) -> FieldType;
- fn entry(&self) -> &dyn TypeOptionDataEntry;
+ fn data_format(&self) -> &dyn TypeOptionDataFormat;
}
pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box {
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs
new file mode 100644
index 0000000000..3a2ce89c21
--- /dev/null
+++ b/frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs
@@ -0,0 +1,45 @@
+use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
+use crate::services::grid_editor::GridRevisionEditor;
+use flowy_error::FlowyResult;
+use flowy_grid_data_model::revision::{TypeOptionDataDeserializer, TypeOptionDataFormat};
+use std::sync::Arc;
+
+pub async fn edit_field_type_option(
+ field_id: &str,
+ editor: Arc,
+ action: impl FnOnce(&mut T),
+) -> FlowyResult<()>
+where
+ T: TypeOptionDataDeserializer + TypeOptionDataFormat,
+{
+ let get_type_option = async {
+ let field_rev = editor.get_field_rev(field_id).await?;
+ field_rev.get_type_option::(field_rev.ty)
+ };
+
+ if let Some(mut type_option) = get_type_option.await {
+ action(&mut type_option);
+ let bytes = type_option.protobuf_bytes().to_vec();
+ let _ = editor
+ .update_field_type_option(&editor.grid_id, field_id, bytes)
+ .await?;
+ }
+
+ Ok(())
+}
+
+pub async fn edit_single_select_type_option(
+ field_id: &str,
+ editor: Arc,
+ action: impl FnOnce(&mut SingleSelectTypeOptionPB),
+) -> FlowyResult<()> {
+ edit_field_type_option(field_id, editor, action).await
+}
+
+pub async fn edit_multi_select_type_option(
+ field_id: &str,
+ editor: Arc,
+ action: impl FnOnce(&mut MultiSelectTypeOptionPB),
+) -> FlowyResult<()> {
+ edit_field_type_option(field_id, editor, action).await
+}
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/mod.rs
index c1b689fbf4..5e8a461541 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/mod.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/mod.rs
@@ -1,5 +1,7 @@
mod field_builder;
+mod field_operation;
pub(crate) mod type_options;
pub use field_builder::*;
+pub use field_operation::*;
pub use type_options::*;
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs
index cf668fcc1e..fbb1ba64e8 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs
@@ -5,7 +5,7 @@ use crate::services::field::{BoxTypeOptionBuilder, CheckboxCellData, TypeOptionB
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
@@ -26,7 +26,7 @@ impl TypeOptionBuilder for CheckboxTypeOptionBuilder {
FieldType::Checkbox
}
- fn entry(&self) -> &dyn TypeOptionDataEntry {
+ fn data_format(&self) -> &dyn TypeOptionDataFormat {
&self.0
}
}
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs
index 729ae1958a..e4ac22f299 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs
@@ -9,7 +9,7 @@ use chrono::format::strftime::StrftimeItems;
use chrono::{NaiveDateTime, Timelike};
use flowy_derive::ProtoBuf;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use serde::{Deserialize, Serialize};
// Date
@@ -189,7 +189,7 @@ impl TypeOptionBuilder for DateTypeOptionBuilder {
FieldType::DateTime
}
- fn entry(&self) -> &dyn TypeOptionDataEntry {
+ fn data_format(&self) -> &dyn TypeOptionDataFormat {
&self.0
}
}
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs
index cdb1118385..fe4ddcf5fa 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs
@@ -6,7 +6,7 @@ use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBui
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use rust_decimal::Decimal;
@@ -45,7 +45,7 @@ impl TypeOptionBuilder for NumberTypeOptionBuilder {
FieldType::Number
}
- fn entry(&self) -> &dyn TypeOptionDataEntry {
+ fn data_format(&self) -> &dyn TypeOptionDataFormat {
&self.0
}
}
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
index 75f654507d..967a65e60e 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
@@ -9,7 +9,7 @@ use crate::services::field::{
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use serde::{Deserialize, Serialize};
// Multiple select
@@ -108,7 +108,7 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
FieldType::MultiSelect
}
- fn entry(&self) -> &dyn TypeOptionDataEntry {
+ fn data_format(&self) -> &dyn TypeOptionDataFormat {
&self.0
}
}
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs
index dcec7772a7..ff579f8172 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs
@@ -5,7 +5,7 @@ use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::{internal_error, ErrorCode, FlowyResult};
use flowy_grid_data_model::parser::NotEmptyStr;
-use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry};
+use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataFormat};
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
@@ -75,7 +75,7 @@ pub fn make_selected_select_options(
}
}
-pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
+pub trait SelectOptionOperation: TypeOptionDataFormat + Send + Sync {
fn insert_option(&mut self, new_option: SelectOptionPB) {
let options = self.mut_options();
if let Some(index) = options
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs
index 287d0c3217..23d3c02b69 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs
@@ -9,7 +9,7 @@ use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use serde::{Deserialize, Serialize};
// Single select
@@ -91,7 +91,7 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
FieldType::SingleSelect
}
- fn entry(&self) -> &dyn TypeOptionDataEntry {
+ fn data_format(&self) -> &dyn TypeOptionDataFormat {
&self.0
}
}
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs
index 6c50ce8da3..33c1f7eb9e 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs
@@ -8,7 +8,7 @@ use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use bytes::Bytes;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use serde::{Deserialize, Serialize};
#[derive(Default)]
@@ -21,7 +21,7 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder {
FieldType::RichText
}
- fn entry(&self) -> &dyn TypeOptionDataEntry {
+ fn data_format(&self) -> &dyn TypeOptionDataFormat {
&self.0
}
}
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs
index adbc91b4f0..d34dfc12d3 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs
@@ -6,7 +6,7 @@ use bytes::Bytes;
use fancy_regex::Regex;
use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult};
-use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
+use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
@@ -20,7 +20,7 @@ impl TypeOptionBuilder for URLTypeOptionBuilder {
FieldType::URL
}
- fn entry(&self) -> &dyn TypeOptionDataEntry {
+ fn data_format(&self) -> &dyn TypeOptionDataFormat {
&self.0
}
}
diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs
index 8335a36fb2..173dc8ffea 100644
--- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs
@@ -188,7 +188,7 @@ fn filter_cell(
FieldType::RichText => filter_cache.text_filter.get(&filter_id).and_then(|filter| {
Some(
field_rev
- .get_type_option_entry::(field_type_rev)?
+ .get_type_option::(field_type_rev)?
.apply_filter(any_cell_data, filter.value())
.ok(),
)
@@ -196,7 +196,7 @@ fn filter_cell(
FieldType::Number => filter_cache.number_filter.get(&filter_id).and_then(|filter| {
Some(
field_rev
- .get_type_option_entry::(field_type_rev)?
+ .get_type_option::(field_type_rev)?
.apply_filter(any_cell_data, filter.value())
.ok(),
)
@@ -204,7 +204,7 @@ fn filter_cell(
FieldType::DateTime => filter_cache.date_filter.get(&filter_id).and_then(|filter| {
Some(
field_rev
- .get_type_option_entry::(field_type_rev)?
+ .get_type_option::(field_type_rev)?
.apply_filter(any_cell_data, filter.value())
.ok(),
)
@@ -212,7 +212,7 @@ fn filter_cell(
FieldType::SingleSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| {
Some(
field_rev
- .get_type_option_entry::(field_type_rev)?
+ .get_type_option::(field_type_rev)?
.apply_filter(any_cell_data, filter.value())
.ok(),
)
@@ -220,7 +220,7 @@ fn filter_cell(
FieldType::MultiSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| {
Some(
field_rev
- .get_type_option_entry::(field_type_rev)?
+ .get_type_option::(field_type_rev)?
.apply_filter(any_cell_data, filter.value())
.ok(),
)
@@ -228,7 +228,7 @@ fn filter_cell(
FieldType::Checkbox => filter_cache.checkbox_filter.get(&filter_id).and_then(|filter| {
Some(
field_rev
- .get_type_option_entry::(field_type_rev)?
+ .get_type_option::(field_type_rev)?
.apply_filter(any_cell_data, filter.value())
.ok(),
)
@@ -236,7 +236,7 @@ fn filter_cell(
FieldType::URL => filter_cache.url_filter.get(&filter_id).and_then(|filter| {
Some(
field_rev
- .get_type_option_entry::(field_type_rev)?
+ .get_type_option::(field_type_rev)?
.apply_filter(any_cell_data, filter.value())
.ok(),
)
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 9ae7918955..320d41cbff 100644
--- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
@@ -179,6 +179,10 @@ impl GridRevisionEditor {
None => Err(ErrorCode::FieldDoesNotExist.into()),
Some(field_type) => {
let _ = self.update_field_rev(params, field_type).await?;
+ match self.view_manager.did_update_field(&field_id).await {
+ Ok(_) => {}
+ Err(e) => tracing::error!("View manager update field failed: {:?}", e),
+ }
let _ = self.notify_did_update_grid_field(&field_id).await?;
Ok(())
}
@@ -207,6 +211,11 @@ impl GridRevisionEditor {
Ok(())
}
+ pub async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> {
+ let _ = self.view_manager.group_by_field(field_id).await?;
+ Ok(())
+ }
+
pub async fn switch_to_field_type(&self, field_id: &str, field_type: &FieldType) -> FlowyResult<()> {
// let block_ids = self
// .get_block_metas()
@@ -221,7 +230,9 @@ impl GridRevisionEditor {
let type_option_json_builder = |field_type: &FieldTypeRevision| -> String {
let field_type: FieldType = field_type.into();
- return default_type_option_builder_from_type(&field_type).entry().json_str();
+ return default_type_option_builder_from_type(&field_type)
+ .data_format()
+ .json_str();
};
let _ = self
@@ -521,12 +532,20 @@ impl GridRevisionEditor {
self.view_manager.get_setting().await
}
- pub async fn get_grid_filter(&self) -> FlowyResult> {
+ pub async fn get_grid_filter(&self) -> FlowyResult> {
self.view_manager.get_filters().await
}
- pub async fn update_filter(&self, params: CreateFilterParams) -> FlowyResult<()> {
- let _ = self.view_manager.update_filter(params).await?;
+ pub async fn create_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
+ self.view_manager.insert_or_update_group(params).await
+ }
+
+ pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
+ self.view_manager.delete_group(params).await
+ }
+
+ pub async fn create_filter(&self, params: InsertFilterParams) -> FlowyResult<()> {
+ let _ = self.view_manager.insert_or_update_filter(params).await?;
Ok(())
}
@@ -824,7 +843,7 @@ impl JsonDeserializer for TypeOptionJsonDeserializer {
fn deserialize(&self, type_option_data: Vec) -> CollaborateResult {
// The type_option_data sent from Dart is serialized by protobuf.
let builder = type_option_builder_from_bytes(type_option_data, &self.0);
- let json = builder.entry().json_str();
+ let json = builder.data_format().json_str();
tracing::trace!("Deserialize type option data to: {}", json);
Ok(json)
}
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 688a844707..ab8e8bd41f 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,12 +1,16 @@
use crate::dart_notification::{send_dart_notification, GridNotification};
use crate::entities::{
- CreateFilterParams, CreateRowParams, DeleteFilterParams, GridFilterConfiguration, GridLayout, GridLayoutPB,
- GridSettingPB, GroupChangesetPB, GroupPB, GroupViewChangesetPB, InsertedGroupPB, InsertedRowPB, MoveGroupParams,
- RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB, RowPB,
+ CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridFilterConfigurationPB, GridGroupConfigurationPB,
+ GridLayout, GridLayoutPB, GridSettingPB, GroupChangesetPB, GroupPB, GroupViewChangesetPB, InsertFilterParams,
+ InsertGroupParams, InsertedGroupPB, InsertedRowPB, MoveGroupParams, RepeatedGridFilterConfigurationPB,
+ RepeatedGridGroupConfigurationPB, RowPB,
};
use crate::services::grid_editor_task::GridServiceTaskScheduler;
use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate};
-use crate::services::group::{GroupConfigurationReader, GroupConfigurationWriter, GroupService};
+use crate::services::group::{
+ default_group_configuration, find_group_field, make_group_controller, GroupConfigurationReader,
+ GroupConfigurationWriter, GroupController, MoveGroupRowContext,
+};
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{
gen_grid_filter_id, FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision,
@@ -16,9 +20,7 @@ use flowy_revision::{RevisionCloudService, RevisionManager, RevisionObjectBuilde
use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad};
use flowy_sync::entities::revision::Revision;
use lib_infra::future::{wrap_future, AFFuture, FutureResult};
-use std::collections::HashMap;
-
-use std::sync::atomic::{AtomicBool, Ordering};
+use std::future::Future;
use std::sync::Arc;
use tokio::sync::RwLock;
@@ -30,11 +32,9 @@ pub struct GridViewRevisionEditor {
rev_manager: Arc,
field_delegate: Arc,
row_delegate: Arc,
- group_service: Arc>,
+ group_controller: Arc>>,
scheduler: Arc,
- did_load_group: AtomicBool,
}
-
impl GridViewRevisionEditor {
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn new(
@@ -52,16 +52,16 @@ impl GridViewRevisionEditor {
let view_revision_pad = rev_manager.load::(Some(cloud)).await?;
let pad = Arc::new(RwLock::new(view_revision_pad));
let rev_manager = Arc::new(rev_manager);
-
- let configuration_reader = GroupConfigurationReaderImpl(pad.clone());
- let configuration_writer = GroupConfigurationWriterImpl {
- user_id: user_id.to_owned(),
- rev_manager: rev_manager.clone(),
- view_pad: pad.clone(),
- };
- let group_service = GroupService::new(view_id.clone(), configuration_reader, configuration_writer).await;
+ let group_controller = new_group_controller(
+ user_id.to_owned(),
+ view_id.clone(),
+ pad.clone(),
+ rev_manager.clone(),
+ field_delegate.clone(),
+ row_delegate.clone(),
+ )
+ .await?;
let user_id = user_id.to_owned();
- let did_load_group = AtomicBool::new(false);
Ok(Self {
pad,
user_id,
@@ -70,24 +70,21 @@ impl GridViewRevisionEditor {
scheduler,
field_delegate,
row_delegate,
- group_service: Arc::new(RwLock::new(group_service)),
- did_load_group,
+ group_controller: Arc::new(RwLock::new(group_controller)),
})
}
pub(crate) async fn will_create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) {
- match params.group_id.as_ref() {
- None => {}
- Some(group_id) => {
- self.group_service
- .write()
- .await
- .will_create_row(row_rev, group_id, |field_id| {
- self.field_delegate.get_field_rev(&field_id)
- })
- .await;
- }
+ if params.group_id.is_none() {
+ return;
}
+ let group_id = params.group_id.as_ref().unwrap();
+ let _ = self
+ .mut_group_controller(|group_controller, field_rev| {
+ group_controller.will_create_row(row_rev, &field_rev, group_id);
+ Ok(())
+ })
+ .await;
}
pub(crate) async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) {
@@ -112,13 +109,11 @@ impl GridViewRevisionEditor {
pub(crate) async fn did_delete_row(&self, row_rev: &RowRevision) {
// Send the group notification if the current view has groups;
- if let Some(changesets) = self
- .group_service
- .write()
- .await
- .did_delete_row(row_rev, |field_id| self.field_delegate.get_field_rev(&field_id))
- .await
- {
+ let changesets = self
+ .mut_group_controller(|group_controller, field_rev| group_controller.did_delete_row(row_rev, &field_rev))
+ .await;
+
+ if let Some(changesets) = changesets {
for changeset in changesets {
self.notify_did_update_group(changeset).await;
}
@@ -126,13 +121,11 @@ impl GridViewRevisionEditor {
}
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
- {
+ let changesets = self
+ .mut_group_controller(|group_controller, field_rev| group_controller.did_update_row(row_rev, &field_rev))
+ .await;
+
+ if let Some(changesets) = changesets {
for changeset in changesets {
self.notify_did_update_group(changeset).await;
}
@@ -146,54 +139,38 @@ impl GridViewRevisionEditor {
to_group_id: &str,
to_row_id: Option,
) -> Vec {
- match self
- .group_service
- .write()
- .await
- .move_group_row(row_rev, row_changeset, to_group_id, to_row_id, |field_id| {
- self.field_delegate.get_field_rev(&field_id)
+ let changesets = self
+ .mut_group_controller(|group_controller, field_rev| {
+ let move_row_context = MoveGroupRowContext {
+ row_rev,
+ row_changeset,
+ field_rev: field_rev.as_ref(),
+ to_group_id,
+ to_row_id,
+ };
+
+ let changesets = group_controller.move_group_row(move_row_context)?;
+ Ok(changesets)
})
- .await
- {
- None => vec![],
- Some(changesets) => changesets,
- }
+ .await;
+
+ changesets.unwrap_or_default()
}
/// Only call once after grid view editor initialized
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) async fn load_groups(&self) -> FlowyResult> {
- let groups = if !self.did_load_group.load(Ordering::SeqCst) {
- self.did_load_group.store(true, Ordering::SeqCst);
- 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 => vec![],
- Some(groups) => groups,
- }
- } else {
- self.group_service.read().await.groups().await
- };
-
+ let groups = self.group_controller.read().await.groups();
tracing::trace!("Number of groups: {}", groups.len());
Ok(groups.into_iter().map(GroupPB::from).collect())
}
pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
let _ = self
- .group_service
+ .group_controller
.write()
.await
- .move_group(¶ms.from_group_id, ¶ms.to_group_id)
- .await?;
-
- match self.group_service.read().await.get_group(¶ms.from_group_id).await {
+ .move_group(¶ms.from_group_id, ¶ms.to_group_id)?;
+ match self.group_controller.read().await.get_group(¶ms.from_group_id) {
None => {}
Some((index, group)) => {
let inserted_group = InsertedGroupPB {
@@ -206,6 +183,7 @@ impl GridViewRevisionEditor {
inserted_groups: vec![inserted_group],
deleted_groups: vec![params.from_group_id.clone()],
update_groups: vec![],
+ new_groups: vec![],
};
self.notify_did_update_view(changeset).await;
@@ -220,27 +198,52 @@ impl GridViewRevisionEditor {
grid_setting
}
- pub(crate) async fn get_filters(&self) -> Vec {
+ pub(crate) async fn get_filters(&self) -> Vec {
let field_revs = self.field_delegate.get_field_revs().await;
match self.pad.read().await.get_all_filters(&field_revs) {
None => vec![],
Some(filters) => filters
.into_values()
.flatten()
- .map(|filter| GridFilterConfiguration::from(filter.as_ref()))
+ .map(|filter| GridFilterConfigurationPB::from(filter.as_ref()))
.collect(),
}
}
- pub(crate) async fn insert_filter(&self, insert_filter: CreateFilterParams) -> FlowyResult<()> {
+ pub(crate) async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> {
+ if let Some(field_rev) = self.field_delegate.get_field_rev(¶ms.field_id).await {
+ let _ = self
+ .modify(|pad| {
+ let configuration = default_group_configuration(&field_rev);
+ let changeset = pad.insert_group(¶ms.field_id, ¶ms.field_type_rev, configuration)?;
+ Ok(changeset)
+ })
+ .await?;
+ }
+ if self.group_controller.read().await.field_id() != params.field_id {
+ let _ = self.group_by_field(¶ms.field_id).await?;
+ self.notify_did_update_setting().await;
+ }
+ Ok(())
+ }
+
+ pub(crate) async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
+ self.modify(|pad| {
+ let changeset = pad.delete_filter(¶ms.field_id, ¶ms.field_type_rev, ¶ms.group_id)?;
+ Ok(changeset)
+ })
+ .await
+ }
+
+ pub(crate) async fn insert_filter(&self, params: InsertFilterParams) -> FlowyResult<()> {
self.modify(|pad| {
let filter_rev = FilterConfigurationRevision {
id: gen_grid_filter_id(),
- field_id: insert_filter.field_id.clone(),
- condition: insert_filter.condition,
- content: insert_filter.content,
+ field_id: params.field_id.clone(),
+ condition: params.condition,
+ content: params.content,
};
- let changeset = pad.insert_filter(&insert_filter.field_id, &insert_filter.field_type_rev, filter_rev)?;
+ let changeset = pad.insert_filter(¶ms.field_id, ¶ms.field_type_rev, filter_rev)?;
Ok(changeset)
})
.await
@@ -260,7 +263,7 @@ impl GridViewRevisionEditor {
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn did_update_field(&self, field_id: &str) -> FlowyResult<()> {
if let Some(field_rev) = self.field_delegate.get_field_rev(field_id).await {
- match self.group_service.write().await.did_update_field(&field_rev).await? {
+ match self.group_controller.write().await.did_update_field(&field_rev)? {
None => {}
Some(changeset) => {
self.notify_did_update_view(changeset).await;
@@ -270,6 +273,44 @@ impl GridViewRevisionEditor {
Ok(())
}
+ pub(crate) async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> {
+ if let Some(field_rev) = self.field_delegate.get_field_rev(field_id).await {
+ let new_group_controller = new_group_controller_with_field_rev(
+ self.user_id.clone(),
+ self.view_id.clone(),
+ self.pad.clone(),
+ self.rev_manager.clone(),
+ field_rev,
+ self.row_delegate.clone(),
+ )
+ .await?;
+
+ let new_groups = new_group_controller.groups().into_iter().map(GroupPB::from).collect();
+
+ *self.group_controller.write().await = new_group_controller;
+ let changeset = GroupViewChangesetPB {
+ view_id: self.view_id.clone(),
+ new_groups,
+ ..Default::default()
+ };
+
+ debug_assert!(!changeset.is_empty());
+ if !changeset.is_empty() {
+ send_dart_notification(&changeset.view_id, GridNotification::DidGroupByNewField)
+ .payload(changeset)
+ .send();
+ }
+ }
+ Ok(())
+ }
+
+ async fn notify_did_update_setting(&self) {
+ let setting = self.get_setting().await;
+ send_dart_notification(&self.view_id, GridNotification::DidUpdateGridSetting)
+ .payload(setting)
+ .send();
+ }
+
pub async fn notify_did_update_group(&self, changeset: GroupChangesetPB) {
send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup)
.payload(changeset)
@@ -295,6 +336,78 @@ impl GridViewRevisionEditor {
}
Ok(())
}
+
+ async fn mut_group_controller(&self, f: F) -> Option
+ where
+ F: FnOnce(&mut Box, Arc) -> FlowyResult,
+ {
+ let group_field_id = self.group_controller.read().await.field_id().to_owned();
+ match self.field_delegate.get_field_rev(&group_field_id).await {
+ None => None,
+ Some(field_rev) => {
+ let mut write_guard = self.group_controller.write().await;
+ f(&mut write_guard, field_rev).ok()
+ }
+ }
+ }
+
+ #[allow(dead_code)]
+ async fn async_mut_group_controller(&self, f: F) -> Option
+ where
+ F: FnOnce(Arc>>, Arc) -> O,
+ O: Future