Browse Source

Merge pull request #917 from LucasXu0/feat/export

Update example project
Lucas.Xu 2 năm trước cách đây
mục cha
commit
192438fc20

+ 43 - 217
frontend/app_flowy/packages/appflowy_editor/example/assets/example.json

@@ -1,276 +1,102 @@
 {
   "document": {
     "type": "editor",
-    "attributes": {},
     "children": [
       {
         "type": "image",
         "attributes": {
-          "image_src": "https://images.squarespace-cdn.com/content/v1/617f6f16b877c06711e87373/c3f23723-37f4-44d7-9c5d-6e2a53064ae7/Asset+10.png",
+          "image_src": "https://s1.ax1x.com/2022/08/26/v2sSbR.jpg",
           "align": "center"
         }
       },
       {
         "type": "text",
+        "attributes": { "subtype": "heading", "heading": "h1" },
         "delta": [
+          { "insert": "👋 " },
+          { "insert": "Welcome to ", "attributes": { "bold": true } },
           {
-            "insert": "🌶 Read Me"
-          }
-        ],
-        "attributes": {
-          "subtype": "heading",
-          "heading": "h1"
-        }
-      },
-      {
-        "type": "text",
-        "delta": [
-          {
-            "insert": "👋 Welcome to FlowyEditor"
-          }
-        ],
-        "attributes": {
-          "subtype": "heading",
-          "heading": "h2"
-        }
-      },
-      {
-        "type": "text",
-        "delta": [
-          {
-            "insert": "To be honest, we are still in the alpha stage. There are still many functions that need to be completed. And we are developing more features. Please give us a star if the "
-          },
-          {
-            "insert": "FlowyEditor",
+            "insert": "AppFlowy Editor",
             "attributes": {
-              "href": "https://github.com/AppFlowy-IO/AppFlowy"
+              "href": "appflowy.io",
+              "italic": true,
+              "bold": true
             }
-          },
-          {
-            "insert": " helps you. 😊😊😊"
-          }
-        ]
-      },
-      {
-        "type": "text",
-        "delta": [
-          {
-            "insert": "Since the FlowyEditor are a community-driven open source editor, we very welcome and appreciate every pull request submissions from everyone.😄😄😄"
           }
         ]
       },
+      { "type": "text", "delta": [] },
       {
         "type": "text",
         "delta": [
-          {
-            "insert": "Here are the basics:"
-          }
-        ],
-        "attributes": {
-          "subtype": "heading",
-          "heading": "h3"
-        }
-      },
-      {
-        "type": "text",
-        "delta": [
-          { "insert": "Click " },
-          { "insert": "anywhere", "attributes": { "underline": true } },
-          { "insert": " and just typing." }
+          { "insert": "AppFlowy Editor is a " },
+          { "insert": "highly customizable", "attributes": { "bold": true } },
+          { "insert": " " },
+          { "insert": "rich-text editor", "attributes": { "italic": true } },
+          { "insert": " for " },
+          { "insert": "Flutter", "attributes": { "underline": true } }
         ]
       },
       {
         "type": "text",
-        "delta": [
-          {
-            "insert": "Hit"
-          },
-          {
-            "insert": "  /  ",
-            "attributes": { "backgroundColor": "0xFFFFFF00" }
-          },
-          {
-            "insert": "to see all the types of content you can add - headers, bulleted lists, checkboxes, etc."
-          }
-        ]
-      },
-      {
-        "type": "text",
-        "delta": [
-          {
-            "insert": "Highlight",
-            "attributes": { "backgroundColor": "0xFF00BCFB" }
-          },
-          {
-            "insert": " any text, and use the menu that pops up to "
-          },
-          { "insert": "style", "attributes": { "bold": true } },
-          { "insert": " your ", "attributes": { "italic": true } },
-          { "insert": "writing", "attributes": { "strikethrough": true } },
-          { "insert": "." }
-        ]
-      },
-      {
-        "type": "text",
-        "delta": [
-          {
-            "insert": "Here are the plugins:"
-          }
-        ],
-        "attributes": {
-          "subtype": "heading",
-          "heading": "h3"
-        }
-      },
-      {
-        "type": "image",
-        "attributes": {
-          "image_src": "https://s1.ax1x.com/2022/08/24/vgAJED.png",
-          "align": "left",
-          "width": 300
-        }
-      },
-      {
-        "type": "text",
-        "delta": [
-          {
-            "insert": "Hello world"
-          }
-        ],
-        "attributes": {
-          "subtype": "checkbox",
-          "checkbox": false
-        }
-      },
-      {
-        "type": "text",
-        "delta": [
-          {
-            "insert": "Hello world"
-          }
-        ],
-        "attributes": {
-          "subtype": "checkbox",
-          "checkbox": false
-        }
+        "attributes": { "checkbox": true, "subtype": "checkbox" },
+        "delta": [{ "insert": "Customizable" }]
       },
       {
         "type": "text",
-        "delta": [
-          {
-            "insert": "Hello world"
-          }
-        ],
-        "attributes": {
-          "subtype": "checkbox",
-          "checkbox": false
-        }
+        "attributes": { "checkbox": true, "subtype": "checkbox" },
+        "delta": [{ "insert": "Test-covered" }]
       },
       {
         "type": "text",
-        "delta": [
-          {
-            "insert": "Hello world"
-          }
-        ],
-        "attributes": {
-          "subtype": "bulleted-list"
-        }
+        "attributes": { "checkbox": false, "subtype": "checkbox" },
+        "delta": [{ "insert": "more to come!" }]
       },
+      { "type": "text", "delta": [] },
       {
         "type": "text",
-        "delta": [
-          {
-            "insert": "Hello world"
-          }
-        ],
-        "attributes": {
-          "subtype": "bulleted-list"
-        }
+        "attributes": { "subtype": "quote" },
+        "delta": [{ "insert": "Here is an exmaple you can give it a try" }]
       },
+      { "type": "text", "delta": [] },
       {
         "type": "text",
         "delta": [
+          { "insert": "You can also use " },
           {
-            "insert": "Hello "
+            "insert": "AppFlowy Editor",
+            "attributes": {
+              "italic": true,
+              "bold": true,
+              "backgroundColor": "0x6000BCF0"
+            }
           },
-          {
-            "insert": "world",
-            "attributes": { "bold": true }
-          }
-        ],
-        "attributes": {
-          "subtype": "bulleted-list"
-        }
-      },
-      {
-        "type": "text",
-        "delta": [
-          {
-            "insert": "Hello world"
-          }
-        ],
-        "attributes": {
-          "subtype": "quote"
-        }
-      },
-      {
-        "type": "text",
-        "delta": [
-          {
-            "insert": "Hello world"
-          }
-        ],
-        "attributes": {
-          "subtype": "quote"
-        }
-      },
-      {
-        "type": "text",
-        "delta": [
-          {
-            "insert": "Hello world"
-          }
-        ],
-        "attributes": {
-          "subtype": "quote"
-        }
+          { "insert": " as a component to build your own app." }
+        ]
       },
+      { "type": "text", "delta": [] },
       {
         "type": "text",
-        "delta": [
-          {
-            "insert": "Hello world"
-          }
-        ],
-        "attributes": {
-          "subtype": "number-list",
-          "number": 1
-        }
+        "attributes": { "subtype": "bulleted-list" },
+        "delta": [{ "insert": "Use / to insert blocks" }]
       },
       {
         "type": "text",
+        "attributes": { "subtype": "bulleted-list" },
         "delta": [
           {
-            "insert": "Hello world"
+            "insert": "Select text to trigger to the toolbar to format your notes."
           }
-        ],
-        "attributes": {
-          "subtype": "number-list",
-          "number": 2
-        }
+        ]
       },
+      { "type": "text", "delta": [] },
       {
         "type": "text",
         "delta": [
           {
-            "insert": "Hello world"
+            "insert": "If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!"
           }
-        ],
-        "attributes": {
-          "subtype": "number-list",
-          "number": 3
-        }
+        ]
       }
     ]
   }

+ 91 - 104
frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart

@@ -1,14 +1,15 @@
 import 'dart:convert';
+import 'dart:io';
 
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
-import 'expandable_floating_action_button.dart';
-// import 'plugin/image_node_widget.dart';
-import 'plugin/youtube_link_node_widget.dart';
+import 'package:path_provider/path_provider.dart';
 
 import 'package:appflowy_editor/appflowy_editor.dart';
 
+import 'expandable_floating_action_button.dart';
+
 void main() {
   runApp(const MyApp());
 }
@@ -16,20 +17,11 @@ void main() {
 class MyApp extends StatelessWidget {
   const MyApp({Key? key}) : super(key: key);
 
-  // This widget is the root of your application.
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
+      debugShowCheckedModeBanner: false,
       theme: ThemeData(
-        // This is the theme of your application.
-        //
-        // Try running your application with "flutter run". You'll see the
-        // application has a blue toolbar. Then, without quitting the app, try
-        // changing the primarySwatch below to Colors.green and then invoke
-        // "hot reload" (press "r" in the console where you ran "flutter run",
-        // or simply save your changes to "hot reload" in a Flutter IDE).
-        // Notice that the counter didn't reset back to zero; the application
-        // is not restarted.
         primarySwatch: Colors.blue,
       ),
       home: const MyHomePage(title: 'AppFlowyEditor Example'),
@@ -39,16 +31,6 @@ class MyApp extends StatelessWidget {
 
 class MyHomePage extends StatefulWidget {
   const MyHomePage({Key? key, required this.title}) : super(key: key);
-
-  // This widget is the home page of your application. It is stateful, meaning
-  // that it has a State object (defined below) that contains fields that affect
-  // how it looks.
-
-  // This class is the configuration for the state. It holds the values (in this
-  // case the title) provided by the parent (in this case the App widget) and
-  // used by the build method of the State. Fields in a Widget subclass are
-  // always marked "final".
-
   final String title;
 
   @override
@@ -56,72 +38,66 @@ class MyHomePage extends StatefulWidget {
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  final editorKey = GlobalKey();
-  int page = 0;
+  int _pageIndex = 0;
+  late EditorState _editorState;
+  Future<String>? _jsonString;
 
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       body: Container(
         alignment: Alignment.topCenter,
-        child: _buildBody(),
+        child: _buildEditor(context),
       ),
       floatingActionButton: _buildExpandableFab(),
     );
   }
 
-  Widget _buildBody() {
-    if (page == 0) {
-      return _buildAppFlowyEditorWithExample();
-    } else if (page == 1) {
-      return _buildAppFlowyEditorWithEmptyDocument();
-    } else if (page == 2) {
-      return _buildAppFlowyEditorWithBigDocument();
+  Widget _buildEditor(BuildContext context) {
+    if (_jsonString != null) {
+      return _buildEditorWithJsonString(_jsonString!);
     }
-    return Container();
-  }
-
-  Widget _buildAppFlowyEditorWithEmptyDocument() {
-    final editorState = EditorState.empty();
-    final editor = AppFlowyEditor(
-      editorState: editorState,
-      keyEventHandlers: const [],
-      customBuilders: const {},
-    );
-    return editor;
+    if (_pageIndex == 0) {
+      return _buildEditorWithJsonString(
+        rootBundle.loadString('assets/example.json'),
+      );
+    } else if (_pageIndex == 1) {
+      return _buildEditorWithJsonString(
+        rootBundle.loadString('assets/big_document.json'),
+      );
+    } else if (_pageIndex == 2) {
+      return _buildEditorWithJsonString(
+        Future.value(
+          jsonEncode(EditorState.empty().document.toJson()),
+        ),
+      );
+    }
+    throw UnimplementedError();
   }
 
-  Widget _buildAppFlowyEditorWithExample() {
+  Widget _buildEditorWithJsonString(Future<String> jsonString) {
     return FutureBuilder<String>(
-      future: rootBundle.loadString('assets/example.json'),
-      builder: (context, snapshot) {
+      future: jsonString,
+      builder: (_, snapshot) {
         if (snapshot.hasData) {
-          final data = Map<String, Object>.from(json.decode(snapshot.data!));
-          final editorState = EditorState(document: StateTree.fromJson(data));
-          editorState.logConfiguration
+          _editorState = EditorState(
+            document: StateTree.fromJson(
+              Map<String, Object>.from(
+                json.decode(snapshot.data!),
+              ),
+            ),
+          );
+          _editorState.logConfiguration
             ..level = LogLevel.all
             ..handler = (message) {
               debugPrint(message);
             };
-          return _buildAppFlowyEditor(editorState);
-        } else {
-          return const Center(
-            child: CircularProgressIndicator(),
+          return Container(
+            padding: const EdgeInsets.all(20),
+            child: AppFlowyEditor(
+              editorState: _editorState,
+            ),
           );
-        }
-      },
-    );
-  }
-
-  Widget _buildAppFlowyEditorWithBigDocument() {
-    return FutureBuilder<String>(
-      future: rootBundle.loadString('assets/big_document.json'),
-      builder: (context, snapshot) {
-        if (snapshot.hasData) {
-          final data = Map<String, Object>.from(json.decode(snapshot.data!));
-          return _buildAppFlowyEditor(EditorState(
-            document: StateTree.fromJson(data),
-          ));
         } else {
           return const Center(
             child: CircularProgressIndicator(),
@@ -131,53 +107,64 @@ class _MyHomePageState extends State<MyHomePage> {
     );
   }
 
-  Widget _buildAppFlowyEditor(EditorState editorState) {
-    return Container(
-      padding: const EdgeInsets.only(left: 20, right: 20),
-      child: AppFlowyEditor(
-        key: editorKey,
-        editorState: editorState,
-        keyEventHandlers: const [],
-        customBuilders: {
-          // 'image': ImageNodeBuilder(),
-          'youtube_link': YouTubeLinkNodeBuilder()
-        },
-      ),
-    );
-  }
-
   Widget _buildExpandableFab() {
     return ExpandableFab(
       distance: 112.0,
       children: [
         ActionButton(
-          onPressed: () {
-            if (page == 0) return;
-            setState(() {
-              page = 0;
-            });
-          },
-          icon: const Icon(Icons.note_add),
+          icon: const Icon(Icons.abc),
+          onPressed: () => _switchToPage(0),
         ),
         ActionButton(
-          icon: const Icon(Icons.document_scanner),
-          onPressed: () {
-            if (page == 1) return;
-            setState(() {
-              page = 1;
-            });
-          },
+          icon: const Icon(Icons.abc),
+          onPressed: () => _switchToPage(1),
         ),
         ActionButton(
-          onPressed: () {
-            if (page == 2) return;
-            setState(() {
-              page = 2;
-            });
-          },
-          icon: const Icon(Icons.text_fields),
+          icon: const Icon(Icons.abc),
+          onPressed: () => _switchToPage(2),
+        ),
+        ActionButton(
+            icon: const Icon(Icons.print),
+            onPressed: () => {_exportDocument(_editorState)}),
+        ActionButton(
+          icon: const Icon(Icons.import_export),
+          onPressed: () => _importDocument(),
         ),
       ],
     );
   }
+
+  void _exportDocument(EditorState editorState) async {
+    final document = editorState.document.toJson();
+    final json = jsonEncode(document);
+    final directory = await getTemporaryDirectory();
+    final path = directory.path;
+    final file = File('$path/editor.json');
+    await file.writeAsString(json);
+
+    if (mounted) {
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(
+          content: Text('The document is saved to the ${file.path}'),
+        ),
+      );
+    }
+  }
+
+  void _importDocument() async {
+    final directory = await getTemporaryDirectory();
+    final path = directory.path;
+    final file = File('$path/editor.json');
+    setState(() {
+      _jsonString = file.readAsString();
+    });
+  }
+
+  void _switchToPage(int pageIndex) {
+    if (pageIndex != _pageIndex) {
+      setState(() {
+        _pageIndex = pageIndex;
+      });
+    }
+  }
 }

+ 2 - 0
frontend/app_flowy/packages/appflowy_editor/example/macos/Flutter/GeneratedPluginRegistrant.swift

@@ -5,11 +5,13 @@
 import FlutterMacOS
 import Foundation
 
+import path_provider_macos
 import rich_clipboard_macos
 import url_launcher_macos
 import wakelock_macos
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+  PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
   RichClipboardPlugin.register(with: registry.registrar(forPlugin: "RichClipboardPlugin"))
   UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
   WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))

+ 6 - 0
frontend/app_flowy/packages/appflowy_editor/example/macos/Podfile.lock

@@ -1,5 +1,7 @@
 PODS:
   - FlutterMacOS (1.0.0)
+  - path_provider_macos (0.0.1):
+    - FlutterMacOS
   - rich_clipboard_macos (0.0.1):
     - FlutterMacOS
   - url_launcher_macos (0.0.1):
@@ -9,6 +11,7 @@ PODS:
 
 DEPENDENCIES:
   - FlutterMacOS (from `Flutter/ephemeral`)
+  - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
   - rich_clipboard_macos (from `Flutter/ephemeral/.symlinks/plugins/rich_clipboard_macos/macos`)
   - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
   - wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`)
@@ -16,6 +19,8 @@ DEPENDENCIES:
 EXTERNAL SOURCES:
   FlutterMacOS:
     :path: Flutter/ephemeral
+  path_provider_macos:
+    :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
   rich_clipboard_macos:
     :path: Flutter/ephemeral/.symlinks/plugins/rich_clipboard_macos/macos
   url_launcher_macos:
@@ -25,6 +30,7 @@ EXTERNAL SOURCES:
 
 SPEC CHECKSUMS:
   FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
+  path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
   rich_clipboard_macos: 43364b66b9dc69d203eb8dd6d758e2d12e02723c
   url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
   wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9

+ 1 - 0
frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml

@@ -40,6 +40,7 @@ dependencies:
   video_player: ^2.4.5
   pod_player: 0.0.8
   flutter_inappwebview: ^5.4.3+7
+  path_provider: ^2.0.11
 
 dev_dependencies:
   flutter_test:

+ 2 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/document/node.dart

@@ -163,7 +163,8 @@ class Node extends ChangeNotifier with LinkedListEntry<Node> {
       'type': type,
     };
     if (children.isNotEmpty) {
-      map['children'] = children.map((node) => node.toJson());
+      map['children'] =
+          (children.map((node) => node.toJson())).toList(growable: false);
     }
     if (_attributes.isNotEmpty) {
       map['attributes'] = _attributes;

+ 6 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/document/state_tree.dart

@@ -33,6 +33,12 @@ class StateTree {
     return StateTree(root: root);
   }
 
+  Map<String, Object> toJson() {
+    return {
+      'document': root.toJson(),
+    };
+  }
+
   Node? nodeAtPath(Path path) {
     return root.childAtPath(path);
   }