|
1 |
| -# migraiton to visual studio |
| 1 | +# Migrating `flutter-quill` to `visual-editor` |
| 2 | + |
| 3 | +> [!WARNING] |
| 4 | +> |
| 5 | +> At the time of writing, |
| 6 | +> `visual-editor` is still in development and **does not offer all the features `flutter-quill` offers**. |
| 7 | +> There are a few bugs that are noticeable (more specifically, text selection) |
| 8 | +> that do not work properly on mobile devices. |
| 9 | +> |
| 10 | +> We will make a migration to a **fork of `visual-editor`** - https://github.com/LuchoTurtle/visual-editor. |
| 11 | +> This is because the PR that was opened to `visual-editor` has need yet been merged |
| 12 | +> (https://github.com/visual-space/visual-editor/pull/237). |
| 13 | +> Once it is merged and this document has yet to be updated, |
| 14 | +> [please open an issue](https://github.com/dwyl/flutter-wysiwyg-editor-tutorial/issues/). |
| 15 | +
|
| 16 | + |
| 17 | +[`visual-editor`](https://github.com/visual-space/visual-editor) is a fork of `flutter-quill` that, |
| 18 | +unlike the latter, |
| 19 | +is actively maintained. |
| 20 | +They've done a complete refactor of `flutter-quill`, |
| 21 | +documented the code and made it easier to contribute to. |
| 22 | +Because it offers some new features that can be easily integrated into your app, |
| 23 | +we've created this small migration guide so you can leverage this library |
| 24 | +from the app you've just implemented with `flutter-quill`. |
| 25 | + |
| 26 | +> [!NOTE] |
| 27 | +> |
| 28 | +> `visual-editor` provides a migration guide. |
| 29 | +> Check it in https://github.com/visual-space/visual-editor. |
| 30 | +
|
| 31 | + |
| 32 | +# 0. Pre-requisites |
| 33 | + |
| 34 | +This guide builds upon the app that was created in |
| 35 | +[`README.md`](../README.md). |
| 36 | +Make sure you have completed the tutorial first |
| 37 | +and head back here so the code is in the same state. |
| 38 | + |
| 39 | + |
| 40 | +# 1. Install dependencies |
| 41 | + |
| 42 | +Head over to `pubspec.yaml` |
| 43 | +and change the `dependencies` section to the following. |
| 44 | + |
| 45 | +```yaml |
| 46 | +dependencies: |
| 47 | + flutter: |
| 48 | + sdk: flutter |
| 49 | + |
| 50 | + file_picker: ^5.3.3 |
| 51 | + universal_io: ^2.2.2 |
| 52 | + responsive_framework: ^1.1.0 |
| 53 | + universal_html: ^2.2.3 |
| 54 | + path: ^1.8.3 |
| 55 | + path_provider: ^2.1.0 |
| 56 | + http: ^1.1.0 |
| 57 | + mime: ^1.0.4 |
| 58 | + http_parser: ^4.0.2 |
| 59 | + |
| 60 | + visual_editor: |
| 61 | + git: |
| 62 | + url: https://github.com/LuchoTurtle/visual-editor.git |
| 63 | + ref: update_dependencies#236 |
| 64 | +``` |
| 65 | +
|
| 66 | +- we've removed `flutter_quill` and `flutter_quill_extensions`. |
| 67 | +- we've upgraded `http` to version `1.1.0`. |
| 68 | +- we've installed `visual_editor` through the aforementioned fork. |
| 69 | +Normally you'll follow the guidelines in [`visual_editor`](https://github.com/visual-space/visual-editor#getting-started). |
| 70 | + |
| 71 | + |
| 72 | +# 2. Delete `web_embeds` |
| 73 | + |
| 74 | +With `visual-editor`, we do not need to use separate web embeds |
| 75 | +to make it work on the web. |
| 76 | +So you can safely delete 🗑️ |
| 77 | +`mobile_platform_registry.dart`, |
| 78 | +`web_embeds.dart` |
| 79 | +and `web_platform_registry.dart`, |
| 80 | +as they no longer will be needed. |
| 81 | + |
| 82 | + |
| 83 | +# 3. Update import, rename classes and delete unnecessary code |
| 84 | + |
| 85 | +The classes from `visual_editor`, |
| 86 | +although similar to `flutter-quill`, |
| 87 | +have different constructors and some will differ, |
| 88 | +so renaming classes won't work sometimes. |
| 89 | + |
| 90 | +But let's do that first! |
| 91 | +We'll deal with each issue along the way. |
| 92 | +Let's start with replacing the imports. |
| 93 | +Change the imports like so: |
| 94 | + |
| 95 | +`import 'package:flutter_quill/flutter_quill.dart';` -> `import 'package:visual_editor/visual-editor.dart';` |
| 96 | + |
| 97 | +Add `import 'package:visual_editor/document/models/attributes/attributes.model.dart';`, as well. |
| 98 | +We are going to need it. |
| 99 | + |
| 100 | +You can now delete any `flutter_quill` and `flutter_quill_extensions` import. |
| 101 | +You can also delete `import 'web_embeds/web_embeds.dart';`, |
| 102 | +since it no longer exists. |
| 103 | + |
| 104 | +Now, it's time to rename some classes! |
| 105 | +Follow the next steps. |
| 106 | + |
| 107 | +`QuillEditor` -> `VisualEditor` |
| 108 | +`QuillController` -> `EditorController` |
| 109 | +`QuillToolbar` -> `EditorToolbar` |
| 110 | +`DefaultTextBlockStyle` -> `TextBlockStyleM` |
| 111 | +`DefaultStyles` -> `EditorStylesM` |
| 112 | +`Document` -> `DeltaDocM` |
| 113 | + |
| 114 | +Awesome! |
| 115 | + |
| 116 | +The last thing we need to do is |
| 117 | +deleting our `_SelectionType` methods and classes. |
| 118 | +We have used these to handle the triple click selection behaviour |
| 119 | +on tap up. |
| 120 | +We don't need this any more. |
| 121 | + |
| 122 | +Therefore: |
| 123 | +- delete the `_SelectionType` enum. |
| 124 | +- delete `_SelectionType _selectionType = _SelectionType.none;` field from `HomePageState`. |
| 125 | +- delete the `_onTripleClickSelection()` function inside `HomePageState`. |
| 126 | + |
| 127 | +Great job! 🥳 |
| 128 | + |
| 129 | +We are now ready to |
| 130 | +change how our `visual-editor` classes are constructed! |
| 131 | + |
| 132 | + |
| 133 | +# 4. Update `visual-editor` classes invocations |
| 134 | + |
| 135 | +**From now on, we'll only be working inside the `HomePageState` class.** |
| 136 | + |
| 137 | +Let's start by changing our `_initializeText` function. |
| 138 | +`EditorController` now only receives one argument, |
| 139 | +which is the `document`. |
| 140 | +Change it to look like so: |
| 141 | + |
| 142 | +```dart |
| 143 | + Future<void> _initializeText() async { |
| 144 | + final doc = DeltaDocM(); |
| 145 | + setState(() { |
| 146 | + _controller = EditorController( |
| 147 | + document: doc, |
| 148 | + ); |
| 149 | + }); |
| 150 | + } |
| 151 | +``` |
| 152 | + |
| 153 | + |
| 154 | +## 4.1 `VisualEditor` |
| 155 | + |
| 156 | +Let's move to the `VisualEditor`. |
| 157 | +We previously used `QuillEditor`, |
| 158 | +where we had a myriad of parameters we set. |
| 159 | +These parameters *are now more organised*, |
| 160 | +and will be changed like so. |
| 161 | + |
| 162 | +```dart |
| 163 | + Widget quillEditor = VisualEditor( |
| 164 | + controller: _controller!, |
| 165 | + scrollController: ScrollController(), |
| 166 | + focusNode: _focusNode, |
| 167 | + config: EditorConfigM( |
| 168 | + scrollable: true, |
| 169 | + autoFocus: false, |
| 170 | + readOnly: false, |
| 171 | + placeholder: 'Write what\'s on your mind.', |
| 172 | + enableInteractiveSelection: true, |
| 173 | + expands: false, |
| 174 | + padding: EdgeInsets.zero, |
| 175 | + customStyles: const EditorStylesM( |
| 176 | + h1: TextBlockStyleM( |
| 177 | + TextStyle( |
| 178 | + fontSize: 32, |
| 179 | + color: Colors.black, |
| 180 | + height: 1.15, |
| 181 | + fontWeight: FontWeight.w300, |
| 182 | + ), |
| 183 | + VerticalSpacing(top: 16, bottom: 0), |
| 184 | + VerticalSpacing(top: 0, bottom: 0), |
| 185 | + VerticalSpacing(top: 16, bottom: 0), |
| 186 | + null, |
| 187 | + ), |
| 188 | + sizeSmall: TextStyle(fontSize: 9), |
| 189 | + ), |
| 190 | + ), |
| 191 | + ); |
| 192 | +``` |
| 193 | + |
| 194 | +As you can see, most of the configuration |
| 195 | +is now done under the `config` parameter, |
| 196 | +which receives an `EditorConfigM`. |
| 197 | + |
| 198 | +- `enableSelectionToolbar` becomes `enableInteractiveSelection`. |
| 199 | +- we define the `customStyles` with the `EditorStyleM` class. |
| 200 | +In this case, we are defining the `h1` field with `TextBlockStyleM` class, |
| 201 | +which has also changed. |
| 202 | +- `TextBlockStyleM` has an additional parameter, where you will need to define 4 arguments, instead of 3. |
| 203 | +The added argument pertains to `lastLineSpacing`, the spacing at the end of the text block. |
| 204 | +We've just added a `VerticalSpacing(top: 16, bottom: 0)` |
| 205 | +- we can't set the `subscript` and `superscript` fields in `EditorStylesM` (previously `DefaultStyles`), |
| 206 | +as they're not yet available. |
| 207 | +- we've also removed the `onTapUp` callback field, |
| 208 | +as we no longer need it. |
| 209 | + |
| 210 | +In the same file, |
| 211 | +we've re-defined `quillEditor` if it was in a web platform. |
| 212 | +Let's update that as well. |
| 213 | +It now becomes the following: |
| 214 | + |
| 215 | +```dart |
| 216 | + // Alternatively, the web editor version is shown (with the web embeds) |
| 217 | + if (widget.platformService.isWebPlatform()) { |
| 218 | + quillEditor = VisualEditor( |
| 219 | + controller: _controller!, |
| 220 | + scrollController: ScrollController(), |
| 221 | + focusNode: _focusNode, |
| 222 | + config: EditorConfigM( |
| 223 | + scrollable: true, |
| 224 | + enableInteractiveSelection: false, |
| 225 | + autoFocus: false, |
| 226 | + readOnly: false, |
| 227 | + placeholder: 'Add content', |
| 228 | + expands: false, |
| 229 | + padding: EdgeInsets.zero, |
| 230 | + customStyles: const EditorStylesM( |
| 231 | + h1: TextBlockStyleM( |
| 232 | + TextStyle( |
| 233 | + fontSize: 32, |
| 234 | + color: Colors.black, |
| 235 | + height: 1.15, |
| 236 | + fontWeight: FontWeight.w300, |
| 237 | + ), |
| 238 | + VerticalSpacing(top: 16, bottom: 0), |
| 239 | + VerticalSpacing(top: 0, bottom: 0), |
| 240 | + VerticalSpacing(top: 16, bottom: 0), |
| 241 | + null, |
| 242 | + ), |
| 243 | + sizeSmall: TextStyle(fontSize: 9), |
| 244 | + ), |
| 245 | + ), |
| 246 | + ); |
| 247 | + } |
| 248 | +``` |
| 249 | + |
| 250 | +## 4.2 `EditorToolbar` |
| 251 | + |
| 252 | +`QuillToolbar` now becomes `EditorToolbar`. |
| 253 | +With `flutter-quill`, |
| 254 | +we used `FlutterQuillEmbeds.buttons` and appended these custom embed buttons |
| 255 | +this to the toolbar's children. |
| 256 | + |
| 257 | +**This is not the case in `visual-editor`.** |
| 258 | + |
| 259 | +You will simply list the buttons in the `customButton` field |
| 260 | +and add the necessary callbacks (like `webImagePickImpl` or `onImagePickCallback`, for example) to the relevant buttons. |
| 261 | +You can also use the `children` field to enforce a custom order, |
| 262 | +feeding the list straight to `EditorToolbar`'s constructor. |
| 263 | +However, in this case, it's almost pointless to use this field as it does not provide much functionality on top of the customs buttons set. |
| 264 | + |
| 265 | +Let's add our buttons to the toolbar, then! |
| 266 | +Locate the `toolbar` variable, |
| 267 | +and change it. |
| 268 | + |
| 269 | +```dart |
| 270 | +// Toolbar definitions |
| 271 | + const toolbarIconSize = 18.0; |
| 272 | + const toolbarButtonSpacing = 2.5; |
| 273 | +
|
| 274 | + // Instantiating the toolbar |
| 275 | + final toolbar = EditorToolbar( |
| 276 | + children: [ |
| 277 | + HistoryButton( |
| 278 | + buttonsSpacing: toolbarButtonSpacing, |
| 279 | + icon: Icons.undo_outlined, |
| 280 | + iconSize: toolbarIconSize, |
| 281 | + controller: _controller!, |
| 282 | + isUndo: true, |
| 283 | + ), |
| 284 | + HistoryButton( |
| 285 | + buttonsSpacing: toolbarButtonSpacing, |
| 286 | + icon: Icons.redo_outlined, |
| 287 | + iconSize: toolbarIconSize, |
| 288 | + controller: _controller!, |
| 289 | + isUndo: false, |
| 290 | + ), |
| 291 | + ToggleStyleButton( |
| 292 | + buttonsSpacing: toolbarButtonSpacing, |
| 293 | + attribute: AttributesM.bold, |
| 294 | + icon: Icons.format_bold, |
| 295 | + iconSize: toolbarIconSize, |
| 296 | + controller: _controller!, |
| 297 | + ), |
| 298 | + ToggleStyleButton( |
| 299 | + buttonsSpacing: toolbarButtonSpacing, |
| 300 | + attribute: AttributesM.italic, |
| 301 | + icon: Icons.format_italic, |
| 302 | + iconSize: toolbarIconSize, |
| 303 | + controller: _controller!, |
| 304 | + ), |
| 305 | + ToggleStyleButton( |
| 306 | + buttonsSpacing: toolbarButtonSpacing, |
| 307 | + attribute: AttributesM.underline, |
| 308 | + icon: Icons.format_underline, |
| 309 | + iconSize: toolbarIconSize, |
| 310 | + controller: _controller!, |
| 311 | + ), |
| 312 | + ToggleStyleButton( |
| 313 | + buttonsSpacing: toolbarButtonSpacing, |
| 314 | + attribute: AttributesM.strikeThrough, |
| 315 | + icon: Icons.format_strikethrough, |
| 316 | + iconSize: toolbarIconSize, |
| 317 | + controller: _controller!, |
| 318 | + ), |
| 319 | +
|
| 320 | + // Our embed buttons |
| 321 | + ImageButton( |
| 322 | + icon: Icons.image, |
| 323 | + iconSize: toolbarIconSize, |
| 324 | + buttonsSpacing: toolbarButtonSpacing, |
| 325 | + controller: _controller!, |
| 326 | + onImagePickCallback: _onImagePickCallback, |
| 327 | + webImagePickImpl: _webImagePickImpl, |
| 328 | + mediaPickSettingSelector: (context) { |
| 329 | + return Future.value(MediaPickSettingE.Gallery); |
| 330 | + }, |
| 331 | + ), |
| 332 | + ], |
| 333 | + ); |
| 334 | +``` |
| 335 | + |
| 336 | +Here's a list of relevant changes we've made: |
| 337 | + |
| 338 | +- removed `FlutterQuillEmbeds.buttons` - we don't need to use `FlutterQuillEmbeds` to create embed buttons any more. |
| 339 | +We simply create them normally in the children field. |
| 340 | +- removed the `afterButtonPressed` field from `EditorToolbar` constructor. |
| 341 | +- added `buttonsSpacing` field to all buttons, as it's required. |
| 342 | +- `Attribute` is now renamed to `AttributesM` |
| 343 | +(which is why we imported it in the beginning of this guide). |
| 344 | +- `MediaPickSetting` is now renamed to `MediaPickSettingE`. |
| 345 | + |
| 346 | + |
| 347 | +Each button we add needs to have the `buttonsSpacing` field defined. |
| 348 | +But now can simply define our array of buttons, |
| 349 | +display them in the order we like, |
| 350 | +and not have to worry about web embeds or anything like that! |
| 351 | +All the necessary callbacks (like `onImagePickCallback`) |
| 352 | +are related to relevant buttons (`CameraButton`, for example). |
| 353 | +So we don't add this behaviour to the definition of the `toolbar`, |
| 354 | +but **to the adequate button**. |
| 355 | +Much simpler, right? |
| 356 | + |
| 357 | + |
| 358 | +# 5. Changing tests |
| 359 | + |
| 360 | +Let's fix the compiling errors on our tests. |
| 361 | +Luckily, it's simple. |
| 362 | +Just change the imports like we've done at the beginning of this guide, |
| 363 | +and replace `QuillEditor` with `VisualEditor` |
| 364 | +(and other necessary classes). |
| 365 | + |
| 366 | +The method `tester.quillEnterText()` no longer exists, |
| 367 | +so you can safely delete it. |
| 368 | + |
| 369 | + |
| 370 | +# 6. You're done! 🎉 |
| 371 | + |
| 372 | +Congratulations, you've successfully migrated |
| 373 | +the app from `flutter-quill` to `visual-editor`! |
| 374 | + |
| 375 | +Give yourself a pat on the back! 👏 |
0 commit comments