Browse Source

chore: implement import csv ui (#2710)

* chore: implement import csv ui

* feat: support importing CSV

---------

Co-authored-by: Lucas.Xu <[email protected]>
Nathan.fooo 1 year ago
parent
commit
e24a8aabeb

+ 17 - 22
frontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dart

@@ -1,6 +1,3 @@
-import 'dart:convert';
-import 'dart:typed_data';
-
 import 'package:appflowy_backend/dispatch/dispatch.dart';
 import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/import.pb.dart';
@@ -8,33 +5,31 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart';
 import 'package:dartz/dartz.dart';
 
 class ImportBackendService {
-  static Future<Either<Unit, FlowyError>> importHistoryDatabase(
-    String data,
+  static Future<Either<Unit, FlowyError>> importData(
+    List<int> data,
     String name,
     String parentViewId,
+    ImportTypePB importType,
   ) async {
     final payload = ImportPB.create()
-      ..data = utf8.encode(data)
+      ..data = data
       ..parentViewId = parentViewId
-      ..viewLayout = ViewLayoutPB.Grid
+      ..viewLayout = importType.toLayout()
       ..name = name
-      ..importType = ImportTypePB.HistoryDatabase;
-
+      ..importType = importType;
     return await FolderEventImportData(payload).send();
   }
+}
 
-  static Future<Either<Unit, FlowyError>> importHistoryDocument(
-    Uint8List data,
-    String name,
-    String parentViewId,
-  ) async {
-    final payload = ImportPB.create()
-      ..data = data
-      ..parentViewId = parentViewId
-      ..viewLayout = ViewLayoutPB.Document
-      ..name = name
-      ..importType = ImportTypePB.HistoryDocument;
-
-    return await FolderEventImportData(payload).send();
+extension on ImportTypePB {
+  ViewLayoutPB toLayout() {
+    switch (this) {
+      case ImportTypePB.HistoryDocument:
+        return ViewLayoutPB.Document;
+      case ImportTypePB.HistoryDatabase || ImportTypePB.CSV:
+        return ViewLayoutPB.Grid;
+      default:
+        throw UnimplementedError('Unsupported import type $this');
+    }
   }
 }

+ 1 - 0
frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/add_button.dart

@@ -87,6 +87,7 @@ class AddButton extends StatelessWidget {
               switch (type) {
                 case ImportType.historyDocument:
                 case ImportType.historyDatabase:
+                case ImportType.databaseCSV:
                   onSelected(
                     action.pluginBuilder,
                     name,

+ 32 - 12
frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_panel.dart

@@ -1,3 +1,4 @@
+import 'dart:convert';
 import 'dart:io';
 import 'dart:typed_data';
 
@@ -6,6 +7,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/migration/
 import 'package:appflowy/startup/startup.dart';
 import 'package:appflowy/util/file_picker/file_picker_service.dart';
 import 'package:appflowy/workspace/application/settings/share/import_service.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:file_picker/file_picker.dart';
 import 'package:flowy_infra/image.dart';
@@ -52,7 +54,8 @@ Future<void> showImportPanel(
 enum ImportType {
   historyDocument,
   historyDatabase,
-  markdownOrText;
+  markdownOrText,
+  databaseCSV;
 
   @override
   String toString() {
@@ -63,6 +66,8 @@ enum ImportType {
         return 'Database from v0.1';
       case ImportType.markdownOrText:
         return 'Text & Markdown';
+      case ImportType.databaseCSV:
+        return 'CSV';
       default:
         assert(false, 'Unsupported Type $this');
         return '';
@@ -70,22 +75,24 @@ enum ImportType {
   }
 
   Widget? Function(BuildContext context) get icon => (context) {
+        var name = '';
         switch (this) {
           case ImportType.historyDocument:
+            name = 'editor/board';
           case ImportType.historyDatabase:
-            return svgWidget(
-              'editor/documents',
-              color: Theme.of(context).iconTheme.color,
-            );
+            name = 'editor/documents';
+          case ImportType.databaseCSV:
+            name = 'editor/board';
           case ImportType.markdownOrText:
-            return svgWidget(
-              'editor/documents',
-              color: Theme.of(context).iconTheme.color,
-            );
+            name = 'editor/text';
           default:
             assert(false, 'Unsupported Type $this');
             return null;
         }
+        return svgWidget(
+          name,
+          color: Theme.of(context).iconTheme.color,
+        );
       };
 
   List<String> get allowedExtensions {
@@ -96,6 +103,8 @@ enum ImportType {
         return ['afdb'];
       case ImportType.markdownOrText:
         return ['md', 'txt'];
+      case ImportType.databaseCSV:
+        return ['csv'];
       default:
         assert(false, 'Unsupported Type $this');
         return [];
@@ -105,6 +114,7 @@ enum ImportType {
   bool get allowMultiSelect {
     switch (this) {
       case ImportType.historyDocument:
+      case ImportType.databaseCSV:
         return true;
       case ImportType.historyDatabase:
       case ImportType.markdownOrText:
@@ -189,18 +199,28 @@ class _ImportPanelState extends State<_ImportPanel> {
         case ImportType.historyDocument:
           final bytes = _documentDataFrom(importType, data);
           if (bytes != null) {
-            await ImportBackendService.importHistoryDocument(
+            await ImportBackendService.importData(
               bytes,
               name,
               parentViewId,
+              ImportTypePB.HistoryDocument,
             );
           }
           break;
         case ImportType.historyDatabase:
-          await ImportBackendService.importHistoryDatabase(
-            data,
+          await ImportBackendService.importData(
+            utf8.encode(data),
+            name,
+            parentViewId,
+            ImportTypePB.HistoryDatabase,
+          );
+          break;
+        case ImportType.databaseCSV:
+          await ImportBackendService.importData(
+            utf8.encode(data),
             name,
             parentViewId,
+            ImportTypePB.CSV,
           );
           break;
         default:

+ 9 - 1
frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs

@@ -18,6 +18,7 @@ use flowy_error::FlowyError;
 use flowy_folder2::deps::{FolderCloudService, FolderUser};
 use flowy_folder2::entities::ViewLayoutPB;
 use flowy_folder2::manager::Folder2Manager;
+use flowy_folder2::share::ImportType;
 use flowy_folder2::view_operation::{
   FolderOperationHandler, FolderOperationHandlers, View, WorkspaceViewBuilder,
 };
@@ -189,6 +190,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
     &self,
     view_id: &str,
     _name: &str,
+    _import_type: ImportType,
     bytes: Vec<u8>,
   ) -> FutureResult<(), FlowyError> {
     let view_id = view_id.to_string();
@@ -315,14 +317,20 @@ impl FolderOperationHandler for DatabaseFolderOperation {
     &self,
     view_id: &str,
     _name: &str,
+    import_type: ImportType,
     bytes: Vec<u8>,
   ) -> FutureResult<(), FlowyError> {
     let database_manager = self.0.clone();
     let view_id = view_id.to_string();
+    let format = match import_type {
+      ImportType::CSV => CSVFormat::Original,
+      ImportType::HistoryDatabase => CSVFormat::META,
+      _ => CSVFormat::Original,
+    };
     FutureResult::new(async move {
       let content = String::from_utf8(bytes).map_err(|err| FlowyError::internal().context(err))?;
       database_manager
-        .import_csv(view_id, content, CSVFormat::META)
+        .import_csv(view_id, content, format)
         .await?;
       Ok(())
     })

+ 10 - 4
frontend/rust-lib/flowy-database2/src/services/share/csv/import.rs

@@ -85,10 +85,7 @@ fn database_from_fields_and_rows(
       CSVFormat::META => {
         //
         match serde_json::from_str(&field_meta) {
-          Ok(field) => {
-            //
-            field
-          },
+          Ok(field) => field,
           Err(e) => {
             dbg!(e);
             default_field(field_meta, index == 0)
@@ -197,4 +194,13 @@ mod tests {
 
     println!("{:?}", result);
   }
+
+  #[test]
+  fn import_empty_csv_data_test() {
+    let s = r#""#;
+    let importer = CSVImporter;
+    let result =
+      importer.import_csv_from_string(gen_database_view_id(), s.to_string(), CSVFormat::Original);
+    assert!(result.is_err());
+  }
 }

+ 2 - 0
frontend/rust-lib/flowy-folder2/src/entities/import.rs

@@ -9,6 +9,7 @@ use flowy_error::FlowyError;
 pub enum ImportTypePB {
   HistoryDocument = 0,
   HistoryDatabase = 1,
+  CSV = 2,
 }
 
 impl From<ImportTypePB> for ImportType {
@@ -16,6 +17,7 @@ impl From<ImportTypePB> for ImportType {
     match pb {
       ImportTypePB::HistoryDocument => ImportType::HistoryDocument,
       ImportTypePB::HistoryDatabase => ImportType::HistoryDatabase,
+      ImportTypePB::CSV => ImportType::CSV,
     }
   }
 }

+ 1 - 1
frontend/rust-lib/flowy-folder2/src/lib.rs

@@ -8,7 +8,7 @@ mod user_default;
 pub mod view_operation;
 
 pub mod deps;
-mod share;
+pub mod share;
 #[cfg(feature = "test_helper")]
 mod test_helper;
 

+ 1 - 1
frontend/rust-lib/flowy-folder2/src/manager.rs

@@ -497,7 +497,7 @@ impl Folder2Manager {
     let view_id = gen_view_id();
     if let Some(data) = import_data.data {
       handler
-        .import_from_bytes(&view_id, &import_data.name, data)
+        .import_from_bytes(&view_id, &import_data.name, import_data.import_type, data)
         .await?;
     }
 

+ 1 - 0
frontend/rust-lib/flowy-folder2/src/share/import.rs

@@ -4,6 +4,7 @@ use collab_folder::core::ViewLayout;
 pub enum ImportType {
   HistoryDocument = 0,
   HistoryDatabase = 1,
+  CSV = 2,
 }
 
 #[derive(Clone, Debug)]

+ 2 - 0
frontend/rust-lib/flowy-folder2/src/view_operation.rs

@@ -12,6 +12,7 @@ use lib_infra::future::FutureResult;
 use lib_infra::util::timestamp;
 
 use crate::entities::{CreateViewParams, ViewLayoutPB};
+use crate::share::ImportType;
 
 pub type ViewData = Bytes;
 
@@ -204,6 +205,7 @@ pub trait FolderOperationHandler {
     &self,
     view_id: &str,
     name: &str,
+    import_type: ImportType,
     bytes: Vec<u8>,
   ) -> FutureResult<(), FlowyError>;