mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: import cvs from file (#2667)
* refactor: import file * chore: fix tarui build
This commit is contained in:
parent
2247fa8edb
commit
188b36cae6
@ -37,7 +37,7 @@ extension InsertDatabase on EditorState {
|
||||
|
||||
final prefix = _referencedDatabasePrefix(viewPB.layout);
|
||||
final ref = await AppBackendService().createView(
|
||||
appId: appPB.id,
|
||||
parentViewId: appPB.id,
|
||||
name: "$prefix ${viewPB.name}",
|
||||
layoutType: viewPB.layout,
|
||||
ext: {
|
||||
|
@ -25,7 +25,7 @@ SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) =>
|
||||
final service = AppBackendService();
|
||||
|
||||
final result = (await service.createView(
|
||||
appId: appId,
|
||||
parentViewId: appId,
|
||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
layoutType: ViewLayoutPB.Board,
|
||||
))
|
||||
|
@ -25,7 +25,7 @@ SelectionMenuItem gridViewMenuItem(DocumentBloc documentBloc) =>
|
||||
final service = AppBackendService();
|
||||
|
||||
final result = (await service.createView(
|
||||
appId: appId,
|
||||
parentViewId: appId,
|
||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
layoutType: ViewLayoutPB.Grid,
|
||||
))
|
||||
|
@ -104,7 +104,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
||||
|
||||
Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
|
||||
final result = await appService.createView(
|
||||
appId: state.view.id,
|
||||
parentViewId: state.view.id,
|
||||
name: value.name,
|
||||
desc: value.desc ?? "",
|
||||
layoutType: value.pluginBuilder.layoutType!,
|
||||
|
@ -8,7 +8,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
|
||||
class AppBackendService {
|
||||
Future<Either<ViewPB, FlowyError>> createView({
|
||||
required String appId,
|
||||
required String parentViewId,
|
||||
required String name,
|
||||
String? desc,
|
||||
required ViewLayoutPB layoutType,
|
||||
@ -25,7 +25,7 @@ class AppBackendService {
|
||||
Map<String, String> ext = const {},
|
||||
}) {
|
||||
final payload = CreateViewPayloadPB.create()
|
||||
..belongToId = appId
|
||||
..parentViewId = parentViewId
|
||||
..name = name
|
||||
..desc = desc ?? ""
|
||||
..layout = layoutType
|
||||
|
@ -0,0 +1,40 @@
|
||||
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';
|
||||
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,
|
||||
String name,
|
||||
String parentViewId,
|
||||
) async {
|
||||
final payload = ImportPB.create()
|
||||
..data = utf8.encode(data)
|
||||
..parentViewId = parentViewId
|
||||
..viewLayout = ViewLayoutPB.Grid
|
||||
..name = name
|
||||
..importType = ImportTypePB.HistoryDatabase;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ class WorkspaceService {
|
||||
String? desc,
|
||||
}) {
|
||||
final payload = CreateViewPayloadPB.create()
|
||||
..belongToId = workspaceId
|
||||
..parentViewId = workspaceId
|
||||
..name = name
|
||||
..desc = desc ?? ""
|
||||
..layout = ViewLayoutPB.Document;
|
||||
|
@ -12,6 +12,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
class AddButton extends StatelessWidget {
|
||||
final String parentViewId;
|
||||
final Function(
|
||||
PluginBuilder,
|
||||
String? name,
|
||||
@ -20,6 +21,7 @@ class AddButton extends StatelessWidget {
|
||||
) onSelected;
|
||||
|
||||
const AddButton({
|
||||
required this.parentViewId,
|
||||
Key? key,
|
||||
required this.onSelected,
|
||||
}) : super(key: key);
|
||||
@ -75,20 +77,34 @@ class AddButton extends StatelessWidget {
|
||||
onSelected(action.pluginBuilder, null, null, true);
|
||||
}
|
||||
if (action is ImportActionWrapper) {
|
||||
showImportPanel(context, (type, name, initialDataBytes) {
|
||||
if (initialDataBytes == null) {
|
||||
return;
|
||||
}
|
||||
switch (type) {
|
||||
case ImportType.historyDocument:
|
||||
case ImportType.historyDatabase:
|
||||
onSelected(action.pluginBuilder, name, initialDataBytes, false);
|
||||
break;
|
||||
case ImportType.markdownOrText:
|
||||
onSelected(action.pluginBuilder, name, initialDataBytes, true);
|
||||
break;
|
||||
}
|
||||
});
|
||||
showImportPanel(
|
||||
parentViewId,
|
||||
context,
|
||||
(type, name, initialDataBytes) {
|
||||
if (initialDataBytes == null) {
|
||||
return;
|
||||
}
|
||||
switch (type) {
|
||||
case ImportType.historyDocument:
|
||||
case ImportType.historyDatabase:
|
||||
onSelected(
|
||||
action.pluginBuilder,
|
||||
name,
|
||||
initialDataBytes,
|
||||
false,
|
||||
);
|
||||
break;
|
||||
case ImportType.markdownOrText:
|
||||
onSelected(
|
||||
action.pluginBuilder,
|
||||
name,
|
||||
initialDataBytes,
|
||||
true,
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
|
@ -17,9 +17,9 @@ import '../menu_app.dart';
|
||||
import 'add_button.dart';
|
||||
|
||||
class MenuAppHeader extends StatelessWidget {
|
||||
final ViewPB app;
|
||||
final ViewPB parentView;
|
||||
const MenuAppHeader(
|
||||
this.app, {
|
||||
this.parentView, {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -108,6 +108,7 @@ class MenuAppHeader extends StatelessWidget {
|
||||
return Tooltip(
|
||||
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
|
||||
child: AddButton(
|
||||
parentViewId: parentView.id,
|
||||
onSelected: (pluginBuilder, name, initialDataBytes, openAfterCreated) {
|
||||
context.read<AppBloc>().add(
|
||||
AppEvent.createView(
|
||||
|
@ -1,9 +1,11 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/migration/editor_migration.dart';
|
||||
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_editor/appflowy_editor.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@ -21,6 +23,7 @@ typedef ImportCallback = void Function(
|
||||
);
|
||||
|
||||
Future<void> showImportPanel(
|
||||
String parentViewId,
|
||||
BuildContext context,
|
||||
ImportCallback callback,
|
||||
) async {
|
||||
@ -33,7 +36,10 @@ Future<void> showImportPanel(
|
||||
fontSize: 20,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
content: _ImportPanel(importCallback: callback),
|
||||
content: _ImportPanel(
|
||||
parentViewId: parentViewId,
|
||||
importCallback: callback,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0,
|
||||
horizontal: 20.0,
|
||||
@ -112,9 +118,11 @@ enum ImportType {
|
||||
|
||||
class _ImportPanel extends StatefulWidget {
|
||||
const _ImportPanel({
|
||||
required this.parentViewId,
|
||||
required this.importCallback,
|
||||
});
|
||||
|
||||
final String parentViewId;
|
||||
final ImportCallback importCallback;
|
||||
|
||||
@override
|
||||
@ -145,7 +153,7 @@ class _ImportPanelState extends State<_ImportPanel> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onTap: () async {
|
||||
await _importFile(e);
|
||||
await _importFile(widget.parentViewId, e);
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
@ -158,7 +166,7 @@ class _ImportPanelState extends State<_ImportPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _importFile(ImportType importType) async {
|
||||
Future<void> _importFile(String parentViewId, ImportType importType) async {
|
||||
final result = await getIt<FilePickerService>().pickFiles(
|
||||
allowMultiple: importType.allowMultiSelect,
|
||||
type: FileType.custom,
|
||||
@ -173,26 +181,45 @@ class _ImportPanelState extends State<_ImportPanel> {
|
||||
if (path == null) {
|
||||
continue;
|
||||
}
|
||||
final plainText = await File(path).readAsString();
|
||||
Document? document;
|
||||
final data = await File(path).readAsString();
|
||||
final name = p.basenameWithoutExtension(path);
|
||||
|
||||
switch (importType) {
|
||||
case ImportType.markdownOrText:
|
||||
document = markdownToDocument(plainText);
|
||||
break;
|
||||
case ImportType.historyDocument:
|
||||
document = EditorMigration.migrateDocument(plainText);
|
||||
final bytes = _documentDataFrom(importType, data);
|
||||
if (bytes != null) {
|
||||
await ImportBackendService.importHistoryDocument(
|
||||
bytes,
|
||||
name,
|
||||
parentViewId,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ImportType.historyDatabase:
|
||||
await ImportBackendService.importHistoryDatabase(
|
||||
data,
|
||||
name,
|
||||
parentViewId,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
assert(false, 'Unsupported Type $importType');
|
||||
}
|
||||
if (document != null) {
|
||||
final data = DocumentDataPBFromTo.fromDocument(document);
|
||||
widget.importCallback(
|
||||
importType,
|
||||
p.basenameWithoutExtension(path),
|
||||
data?.writeToBuffer(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List? _documentDataFrom(ImportType importType, String data) {
|
||||
switch (importType) {
|
||||
case ImportType.markdownOrText:
|
||||
final document = markdownToDocument(data);
|
||||
return DocumentDataPBFromTo.fromDocument(document)?.writeToBuffer();
|
||||
case ImportType.historyDocument:
|
||||
final document = EditorMigration.migrateDocument(data);
|
||||
return DocumentDataPBFromTo.fromDocument(document)?.writeToBuffer();
|
||||
default:
|
||||
assert(false, 'Unsupported Type $importType');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ class AppFlowyBoardTest {
|
||||
final builder = BoardPluginBuilder();
|
||||
return AppBackendService()
|
||||
.createView(
|
||||
appId: app.id,
|
||||
parentViewId: app.id,
|
||||
name: "Test Board",
|
||||
layoutType: builder.layoutType!,
|
||||
)
|
||||
|
@ -10,7 +10,7 @@ Future<GridTestContext> createTestFilterGrid(AppFlowyGridTest gridTest) async {
|
||||
final builder = GridPluginBuilder();
|
||||
final context = await AppBackendService()
|
||||
.createView(
|
||||
appId: app.id,
|
||||
parentViewId: app.id,
|
||||
name: "Filter Grid",
|
||||
layoutType: builder.layoutType!,
|
||||
)
|
||||
|
@ -172,7 +172,7 @@ class AppFlowyGridTest {
|
||||
final builder = GridPluginBuilder();
|
||||
final context = await AppBackendService()
|
||||
.createView(
|
||||
appId: app.id,
|
||||
parentViewId: app.id,
|
||||
name: "Test Grid",
|
||||
layoutType: builder.layoutType!,
|
||||
)
|
||||
|
@ -36,7 +36,7 @@ export class AppBackendService {
|
||||
}) => {
|
||||
const encoder = new TextEncoder();
|
||||
const payload = CreateViewPayloadPB.fromObject({
|
||||
belong_to_id: this.appId,
|
||||
parent_view_id: this.appId,
|
||||
name: params.name,
|
||||
desc: params.desc || '',
|
||||
layout: params.layoutType,
|
||||
|
@ -19,7 +19,7 @@ export class WorkspaceBackendService {
|
||||
|
||||
createApp = async (params: { name: string; desc?: string }) => {
|
||||
const payload = CreateViewPayloadPB.fromObject({
|
||||
belong_to_id: this.workspaceId,
|
||||
parent_view_id: this.workspaceId,
|
||||
name: params.name,
|
||||
desc: params.desc || '',
|
||||
layout: ViewLayoutPB.Document,
|
||||
|
@ -7,6 +7,7 @@ use appflowy_integrate::RocksCollabDB;
|
||||
use bytes::Bytes;
|
||||
|
||||
use flowy_database2::entities::DatabaseLayoutPB;
|
||||
use flowy_database2::services::share::csv::CSVFormat;
|
||||
use flowy_database2::template::{make_default_board, make_default_calendar, make_default_grid};
|
||||
use flowy_database2::DatabaseManager2;
|
||||
use flowy_document2::document_data::DocumentDataWrapper;
|
||||
@ -16,7 +17,7 @@ use flowy_error::FlowyError;
|
||||
use flowy_folder2::deps::{FolderCloudService, FolderUser};
|
||||
use flowy_folder2::entities::ViewLayoutPB;
|
||||
use flowy_folder2::manager::Folder2Manager;
|
||||
use flowy_folder2::view_ext::{ViewDataProcessor, ViewDataProcessorMap};
|
||||
use flowy_folder2::view_ext::{FolderOperationHandler, FolderOperationHandlers};
|
||||
use flowy_folder2::ViewLayout;
|
||||
use flowy_user::services::UserSession;
|
||||
use lib_dispatch::prelude::ToBytes;
|
||||
@ -33,29 +34,28 @@ impl Folder2DepsResolver {
|
||||
) -> Arc<Folder2Manager> {
|
||||
let user: Arc<dyn FolderUser> = Arc::new(FolderUserImpl(user_session.clone()));
|
||||
|
||||
let view_processors =
|
||||
make_view_data_processor(document_manager.clone(), database_manager.clone());
|
||||
let handlers = folder_operation_handlers(document_manager.clone(), database_manager.clone());
|
||||
Arc::new(
|
||||
Folder2Manager::new(user.clone(), collab_builder, view_processors, folder_cloud)
|
||||
Folder2Manager::new(user.clone(), collab_builder, handlers, folder_cloud)
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_view_data_processor(
|
||||
fn folder_operation_handlers(
|
||||
document_manager: Arc<DocumentManager>,
|
||||
database_manager: Arc<DatabaseManager2>,
|
||||
) -> ViewDataProcessorMap {
|
||||
let mut map: HashMap<ViewLayout, Arc<dyn ViewDataProcessor + Send + Sync>> = HashMap::new();
|
||||
) -> FolderOperationHandlers {
|
||||
let mut map: HashMap<ViewLayout, Arc<dyn FolderOperationHandler + Send + Sync>> = HashMap::new();
|
||||
|
||||
let document_processor = Arc::new(DocumentViewDataProcessor(document_manager));
|
||||
map.insert(ViewLayout::Document, document_processor);
|
||||
let document_folder_operation = Arc::new(DocumentFolderOperation(document_manager));
|
||||
map.insert(ViewLayout::Document, document_folder_operation);
|
||||
|
||||
let database_processor = Arc::new(DatabaseViewDataProcessor(database_manager));
|
||||
map.insert(ViewLayout::Board, database_processor.clone());
|
||||
map.insert(ViewLayout::Grid, database_processor.clone());
|
||||
map.insert(ViewLayout::Calendar, database_processor);
|
||||
let database_folder_operation = Arc::new(DatabaseFolderOperation(database_manager));
|
||||
map.insert(ViewLayout::Board, database_folder_operation.clone());
|
||||
map.insert(ViewLayout::Grid, database_folder_operation.clone());
|
||||
map.insert(ViewLayout::Calendar, database_folder_operation);
|
||||
Arc::new(map)
|
||||
}
|
||||
|
||||
@ -80,8 +80,8 @@ impl FolderUser for FolderUserImpl {
|
||||
}
|
||||
}
|
||||
|
||||
struct DocumentViewDataProcessor(Arc<DocumentManager>);
|
||||
impl ViewDataProcessor for DocumentViewDataProcessor {
|
||||
struct DocumentFolderOperation(Arc<DocumentManager>);
|
||||
impl FolderOperationHandler for DocumentFolderOperation {
|
||||
/// Close the document view.
|
||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
@ -92,10 +92,7 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the view data.
|
||||
///
|
||||
/// only use in the duplicate view.
|
||||
fn get_view_data(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
||||
fn duplicate_view(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
@ -106,27 +103,7 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a view with built-in data.
|
||||
fn create_view_with_built_in_data(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
layout: ViewLayout,
|
||||
_ext: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
debug_assert_eq!(layout, ViewLayout::Document);
|
||||
|
||||
let view_id = view_id.to_string();
|
||||
let manager = self.0.clone();
|
||||
// TODO: implement read the document data from json.
|
||||
FutureResult::new(async move {
|
||||
manager.create_document(view_id, DocumentDataWrapper::default())?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn create_view_with_custom_data(
|
||||
fn create_view_with_view_data(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
view_id: &str,
|
||||
@ -145,10 +122,55 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a view with built-in data.
|
||||
fn create_built_in_view(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
layout: ViewLayout,
|
||||
_ext: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
debug_assert_eq!(layout, ViewLayout::Document);
|
||||
|
||||
let view_id = view_id.to_string();
|
||||
let manager = self.0.clone();
|
||||
// TODO: implement read the document data from json.
|
||||
FutureResult::new(async move {
|
||||
manager.create_document(view_id, DocumentDataWrapper::default())?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn import_from_bytes(
|
||||
&self,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
bytes: Vec<u8>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let view_id = view_id.to_string();
|
||||
let manager = self.0.clone();
|
||||
FutureResult::new(async move {
|
||||
let data = DocumentDataPB::try_from(Bytes::from(bytes))?;
|
||||
manager.create_document(view_id, data.into())?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
// will implement soon
|
||||
fn import_from_file_path(
|
||||
&self,
|
||||
_view_id: &str,
|
||||
_name: &str,
|
||||
_path: String,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async move { Ok(()) })
|
||||
}
|
||||
}
|
||||
|
||||
struct DatabaseViewDataProcessor(Arc<DatabaseManager2>);
|
||||
impl ViewDataProcessor for DatabaseViewDataProcessor {
|
||||
struct DatabaseFolderOperation(Arc<DatabaseManager2>);
|
||||
impl FolderOperationHandler for DatabaseFolderOperation {
|
||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
@ -158,7 +180,7 @@ impl ViewDataProcessor for DatabaseViewDataProcessor {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_view_data(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
||||
fn duplicate_view(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = view_id.to_owned();
|
||||
FutureResult::new(async move {
|
||||
@ -167,40 +189,10 @@ impl ViewDataProcessor for DatabaseViewDataProcessor {
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a database view with build-in data.
|
||||
/// If the ext contains the {"database_id": "xx"}, then it will link to
|
||||
/// the existing database. The data of the database will be shared within
|
||||
/// these references views.
|
||||
fn create_view_with_built_in_data(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
layout: ViewLayout,
|
||||
_ext: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let name = name.to_string();
|
||||
let database_manager = self.0.clone();
|
||||
let data = match layout {
|
||||
ViewLayout::Grid => make_default_grid(view_id, &name),
|
||||
ViewLayout::Board => make_default_board(view_id, &name),
|
||||
ViewLayout::Calendar => make_default_calendar(view_id, &name),
|
||||
ViewLayout::Document => {
|
||||
return FutureResult::new(async move {
|
||||
Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout)))
|
||||
});
|
||||
},
|
||||
};
|
||||
FutureResult::new(async move {
|
||||
database_manager.create_database_with_params(data).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a database view with duplicated data.
|
||||
/// If the ext contains the {"database_id": "xx"}, then it will link
|
||||
/// to the existing database.
|
||||
fn create_view_with_custom_data(
|
||||
fn create_view_with_view_data(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
view_id: &str,
|
||||
@ -241,6 +233,68 @@ impl ViewDataProcessor for DatabaseViewDataProcessor {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a database view with build-in data.
|
||||
/// If the ext contains the {"database_id": "xx"}, then it will link to
|
||||
/// the existing database. The data of the database will be shared within
|
||||
/// these references views.
|
||||
fn create_built_in_view(
|
||||
&self,
|
||||
_user_id: i64,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
layout: ViewLayout,
|
||||
_meta: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let name = name.to_string();
|
||||
let database_manager = self.0.clone();
|
||||
let data = match layout {
|
||||
ViewLayout::Grid => make_default_grid(view_id, &name),
|
||||
ViewLayout::Board => make_default_board(view_id, &name),
|
||||
ViewLayout::Calendar => make_default_calendar(view_id, &name),
|
||||
ViewLayout::Document => {
|
||||
return FutureResult::new(async move {
|
||||
Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout)))
|
||||
});
|
||||
},
|
||||
};
|
||||
FutureResult::new(async move {
|
||||
database_manager.create_database_with_params(data).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn import_from_bytes(
|
||||
&self,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
bytes: Vec<u8>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
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)
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn import_from_file_path(
|
||||
&self,
|
||||
_view_id: &str,
|
||||
_name: &str,
|
||||
path: String,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
FutureResult::new(async move {
|
||||
database_manager
|
||||
.import_csv_from_file(path, CSVFormat::META)
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
|
@ -1,361 +0,0 @@
|
||||
use bytes::Bytes;
|
||||
use flowy_sqlite::ConnectionPool;
|
||||
|
||||
use database_model::BuildDatabaseContext;
|
||||
use flowy_client_ws::FlowyWebSocketConnect;
|
||||
use flowy_database::entities::LayoutTypePB;
|
||||
use flowy_database::manager::{create_new_database, link_existing_database, DatabaseManager};
|
||||
use flowy_database::util::{make_default_board, make_default_calendar, make_default_grid};
|
||||
use flowy_document::editor::make_transaction_from_document_content;
|
||||
use flowy_document::DocumentManager;
|
||||
|
||||
use flowy_folder::entities::{ViewDataFormatPB, ViewLayoutTypePB, ViewPB};
|
||||
use flowy_folder::manager::{ViewDataProcessor, ViewDataProcessorMap};
|
||||
use flowy_folder::{
|
||||
errors::{internal_error, FlowyError},
|
||||
event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser},
|
||||
manager::FolderManager,
|
||||
};
|
||||
use flowy_net::ClientServerConfiguration;
|
||||
use flowy_net::{http_server::folder::FolderHttpCloudService, local_server::LocalServer};
|
||||
use flowy_revision::{RevisionWebSocket, WSStateReceiver};
|
||||
use flowy_user::services::UserSession;
|
||||
use futures_core::future::BoxFuture;
|
||||
use lib_infra::future::{BoxResultFuture, FutureResult};
|
||||
use lib_ws::{WSChannel, WSMessageReceiver, WebSocketRawMessage};
|
||||
use revision_model::Revision;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::{convert::TryInto, sync::Arc};
|
||||
use ws_model::ws_revision::ClientRevisionWSData;
|
||||
|
||||
pub struct FolderDepsResolver();
|
||||
impl FolderDepsResolver {
|
||||
pub async fn resolve(
|
||||
local_server: Option<Arc<LocalServer>>,
|
||||
user_session: Arc<UserSession>,
|
||||
server_config: &ClientServerConfiguration,
|
||||
ws_conn: &Arc<FlowyWebSocketConnect>,
|
||||
text_block_manager: &Arc<DocumentManager>,
|
||||
database_manager: &Arc<DatabaseManager>,
|
||||
) -> Arc<FolderManager> {
|
||||
let user: Arc<dyn WorkspaceUser> = Arc::new(WorkspaceUserImpl(user_session.clone()));
|
||||
let database: Arc<dyn WorkspaceDatabase> = Arc::new(WorkspaceDatabaseImpl(user_session));
|
||||
let web_socket = Arc::new(FolderRevisionWebSocket(ws_conn.clone()));
|
||||
let cloud_service: Arc<dyn FolderCouldServiceV1> = match local_server {
|
||||
None => Arc::new(FolderHttpCloudService::new(server_config.clone())),
|
||||
Some(local_server) => local_server,
|
||||
};
|
||||
|
||||
let view_data_processor =
|
||||
make_view_data_processor(text_block_manager.clone(), database_manager.clone());
|
||||
let folder_manager = Arc::new(
|
||||
FolderManager::new(
|
||||
user.clone(),
|
||||
cloud_service,
|
||||
database,
|
||||
view_data_processor,
|
||||
web_socket,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
// if let (Ok(user_id), Ok(token)) = (user.user_id(), user.token()) {
|
||||
// match folder_manager.initialize(&user_id, &token).await {
|
||||
// Ok(_) => {},
|
||||
// Err(e) => tracing::error!("Initialize folder manager failed: {}", e),
|
||||
// }
|
||||
// }
|
||||
|
||||
let receiver = Arc::new(FolderWSMessageReceiverImpl(folder_manager.clone()));
|
||||
ws_conn.add_ws_message_receiver(receiver).unwrap();
|
||||
folder_manager
|
||||
}
|
||||
}
|
||||
|
||||
fn make_view_data_processor(
|
||||
document_manager: Arc<DocumentManager>,
|
||||
database_manager: Arc<DatabaseManager>,
|
||||
) -> ViewDataProcessorMap {
|
||||
let mut map: HashMap<ViewDataFormatPB, Arc<dyn ViewDataProcessor + Send + Sync>> = HashMap::new();
|
||||
|
||||
let document_processor = Arc::new(DocumentViewDataProcessor(document_manager));
|
||||
document_processor
|
||||
.data_types()
|
||||
.into_iter()
|
||||
.for_each(|data_type| {
|
||||
map.insert(data_type, document_processor.clone());
|
||||
});
|
||||
|
||||
let grid_data_impl = Arc::new(DatabaseViewDataProcessor(database_manager));
|
||||
grid_data_impl
|
||||
.data_types()
|
||||
.into_iter()
|
||||
.for_each(|data_type| {
|
||||
map.insert(data_type, grid_data_impl.clone());
|
||||
});
|
||||
|
||||
Arc::new(map)
|
||||
}
|
||||
|
||||
struct WorkspaceDatabaseImpl(Arc<UserSession>);
|
||||
impl WorkspaceDatabase for WorkspaceDatabaseImpl {
|
||||
fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError> {
|
||||
self
|
||||
.0
|
||||
.db_pool()
|
||||
.map_err(|e| FlowyError::internal().context(e))
|
||||
}
|
||||
}
|
||||
|
||||
struct WorkspaceUserImpl(Arc<UserSession>);
|
||||
impl WorkspaceUser for WorkspaceUserImpl {
|
||||
fn user_id(&self) -> Result<String, FlowyError> {
|
||||
self
|
||||
.0
|
||||
.user_id()
|
||||
.map_err(|e| FlowyError::internal().context(e))
|
||||
}
|
||||
|
||||
fn token(&self) -> Result<Option<String>, FlowyError> {
|
||||
self
|
||||
.0
|
||||
.token()
|
||||
.map_err(|e| FlowyError::internal().context(e))
|
||||
}
|
||||
}
|
||||
|
||||
struct FolderRevisionWebSocket(Arc<FlowyWebSocketConnect>);
|
||||
impl RevisionWebSocket for FolderRevisionWebSocket {
|
||||
fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError> {
|
||||
let bytes: Bytes = data.try_into().unwrap();
|
||||
let msg = WebSocketRawMessage {
|
||||
channel: WSChannel::Folder,
|
||||
data: bytes.to_vec(),
|
||||
};
|
||||
|
||||
let ws_conn = self.0.clone();
|
||||
Box::pin(async move {
|
||||
match ws_conn.web_socket().await? {
|
||||
None => {},
|
||||
Some(sender) => {
|
||||
sender.send(msg).map_err(internal_error)?;
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn subscribe_state_changed(&self) -> BoxFuture<WSStateReceiver> {
|
||||
let ws_conn = self.0.clone();
|
||||
Box::pin(async move { ws_conn.subscribe_websocket_state().await })
|
||||
}
|
||||
}
|
||||
|
||||
struct FolderWSMessageReceiverImpl(Arc<FolderManager>);
|
||||
impl WSMessageReceiver for FolderWSMessageReceiverImpl {
|
||||
fn source(&self) -> WSChannel {
|
||||
WSChannel::Folder
|
||||
}
|
||||
fn receive_message(&self, msg: WebSocketRawMessage) {
|
||||
let handler = self.0.clone();
|
||||
tokio::spawn(async move {
|
||||
handler.did_receive_ws_data(Bytes::from(msg.data)).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct DocumentViewDataProcessor(Arc<DocumentManager>);
|
||||
impl ViewDataProcessor for DocumentViewDataProcessor {
|
||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
manager.close_document_editor(view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_view_data(&self, view: &ViewPB) -> FutureResult<Bytes, FlowyError> {
|
||||
let view_id = view.id.clone();
|
||||
let manager = self.0.clone();
|
||||
FutureResult::new(async move {
|
||||
let editor = manager.open_document_editor(view_id).await?;
|
||||
let document_data = Bytes::from(editor.duplicate().await?);
|
||||
Ok(document_data)
|
||||
})
|
||||
}
|
||||
|
||||
fn create_view_with_built_in_data(
|
||||
&self,
|
||||
user_id: &str,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
layout: ViewLayoutTypePB,
|
||||
_data_format: ViewDataFormatPB,
|
||||
_ext: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
debug_assert_eq!(layout, ViewLayoutTypePB::Document);
|
||||
let _user_id = user_id.to_string();
|
||||
let view_id = view_id.to_string();
|
||||
let manager = self.0.clone();
|
||||
// todo: implement the default content
|
||||
FutureResult::new(async move {
|
||||
let delta_data = Bytes::from(document_content);
|
||||
let revision = Revision::initial_revision(&view_id, delta_data);
|
||||
manager.create_document(view_id, vec![revision]).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn create_view_with_custom_data(
|
||||
&self,
|
||||
_user_id: &str,
|
||||
view_id: &str,
|
||||
_name: &str,
|
||||
data: Vec<u8>,
|
||||
layout: ViewLayoutTypePB,
|
||||
_ext: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
debug_assert_eq!(layout, ViewLayoutTypePB::Document);
|
||||
let view_data = match String::from_utf8(data) {
|
||||
Ok(content) => match make_transaction_from_document_content(&content) {
|
||||
Ok(transaction) => transaction.to_bytes().unwrap_or_else(|_| vec![]),
|
||||
Err(_) => vec![],
|
||||
},
|
||||
Err(_) => vec![],
|
||||
};
|
||||
|
||||
let revision = Revision::initial_revision(view_id, Bytes::from(view_data));
|
||||
let view_id = view_id.to_string();
|
||||
let manager = self.0.clone();
|
||||
|
||||
FutureResult::new(async move {
|
||||
manager.create_document(view_id, vec![revision]).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn data_types(&self) -> Vec<ViewDataFormatPB> {
|
||||
vec![ViewDataFormatPB::DeltaFormat, ViewDataFormatPB::NodeFormat]
|
||||
}
|
||||
}
|
||||
|
||||
struct DatabaseViewDataProcessor(Arc<DatabaseManager>);
|
||||
impl ViewDataProcessor for DatabaseViewDataProcessor {
|
||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
database_manager.close_database_view(view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_view_data(&self, view: &ViewPB) -> FutureResult<Bytes, FlowyError> {
|
||||
let database_manager = self.0.clone();
|
||||
let view_id = view.id.clone();
|
||||
FutureResult::new(async move {
|
||||
let editor = database_manager.open_database_view(&view_id).await?;
|
||||
let delta_bytes = editor.duplicate_database(&view_id).await?;
|
||||
Ok(delta_bytes.into())
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a database view with build-in data.
|
||||
/// If the ext contains the {"database_id": "xx"}, then it will link to
|
||||
/// the existing database. The data of the database will be shared within
|
||||
/// these references views.
|
||||
fn create_view_with_built_in_data(
|
||||
&self,
|
||||
_user_id: &str,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
layout: ViewLayoutTypePB,
|
||||
data_format: ViewDataFormatPB,
|
||||
ext: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
debug_assert_eq!(data_format, ViewDataFormatPB::DatabaseFormat);
|
||||
let view_id = view_id.to_string();
|
||||
let name = name.to_string();
|
||||
let database_manager = self.0.clone();
|
||||
match DatabaseExtParams::from_map(ext).map(|params| params.database_id) {
|
||||
None => {
|
||||
let (build_context, layout) = match layout {
|
||||
ViewLayoutTypePB::Grid => (make_default_grid(), LayoutTypePB::Grid),
|
||||
ViewLayoutTypePB::Board => (make_default_board(), LayoutTypePB::Board),
|
||||
ViewLayoutTypePB::Calendar => (make_default_calendar(), LayoutTypePB::Calendar),
|
||||
ViewLayoutTypePB::Document => {
|
||||
return FutureResult::new(async move {
|
||||
Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout)))
|
||||
});
|
||||
},
|
||||
};
|
||||
FutureResult::new(async move {
|
||||
create_new_database(&view_id, name, layout, database_manager, build_context).await
|
||||
})
|
||||
},
|
||||
Some(database_id) => {
|
||||
let layout = layout_type_from_view_layout(layout);
|
||||
FutureResult::new(async move {
|
||||
link_existing_database(&view_id, name, &database_id, layout, database_manager).await
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a database view with custom data.
|
||||
/// If the ext contains the {"database_id": "xx"}, then it will link
|
||||
/// to the existing database. The data of the database will be shared
|
||||
/// within these references views.
|
||||
fn create_view_with_custom_data(
|
||||
&self,
|
||||
_user_id: &str,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
data: Vec<u8>,
|
||||
layout: ViewLayoutTypePB,
|
||||
ext: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let view_id = view_id.to_string();
|
||||
let database_manager = self.0.clone();
|
||||
let layout = layout_type_from_view_layout(layout);
|
||||
let name = name.to_string();
|
||||
match DatabaseExtParams::from_map(ext).map(|params| params.database_id) {
|
||||
None => FutureResult::new(async move {
|
||||
let bytes = Bytes::from(data);
|
||||
let build_context = BuildDatabaseContext::try_from(bytes)?;
|
||||
let _ = create_new_database(&view_id, name, layout, database_manager, build_context).await;
|
||||
Ok(())
|
||||
}),
|
||||
Some(database_id) => FutureResult::new(async move {
|
||||
link_existing_database(&view_id, name, &database_id, layout, database_manager).await
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn data_types(&self) -> Vec<ViewDataFormatPB> {
|
||||
vec![ViewDataFormatPB::DatabaseFormat]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout_type_from_view_layout(layout: ViewLayoutTypePB) -> LayoutTypePB {
|
||||
match layout {
|
||||
ViewLayoutTypePB::Grid => LayoutTypePB::Grid,
|
||||
ViewLayoutTypePB::Board => LayoutTypePB::Board,
|
||||
ViewLayoutTypePB::Calendar => LayoutTypePB::Calendar,
|
||||
ViewLayoutTypePB::Document => LayoutTypePB::Grid,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct DatabaseExtParams {
|
||||
database_id: String,
|
||||
}
|
||||
|
||||
impl DatabaseExtParams {
|
||||
pub fn from_map(map: HashMap<String, String>) -> Option<Self> {
|
||||
let value = serde_json::to_value(map).ok()?;
|
||||
serde_json::from_value::<Self>(value).ok()
|
||||
}
|
||||
}
|
@ -12,7 +12,6 @@ mod view_entities;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod share_entities;
|
||||
mod type_option_entities;
|
||||
|
||||
pub use calendar_entities::*;
|
||||
@ -23,7 +22,6 @@ pub use filter_entities::*;
|
||||
pub use group_entities::*;
|
||||
pub use row_entities::*;
|
||||
pub use setting_entities::*;
|
||||
pub use share_entities::*;
|
||||
pub use sort_entities::*;
|
||||
pub use type_option_entities::*;
|
||||
pub use view_entities::*;
|
||||
|
@ -1,24 +0,0 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
|
||||
#[derive(Clone, Debug, ProtoBuf_Enum)]
|
||||
pub enum ImportTypePB {
|
||||
CSV = 0,
|
||||
}
|
||||
|
||||
impl Default for ImportTypePB {
|
||||
fn default() -> Self {
|
||||
Self::CSV
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, ProtoBuf, Default)]
|
||||
pub struct DatabaseImportPB {
|
||||
#[pb(index = 1, one_of)]
|
||||
pub data: Option<String>,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub uri: Option<String>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub import_type: ImportTypePB,
|
||||
}
|
@ -5,7 +5,7 @@ use collab_database::rows::RowId;
|
||||
use collab_database::views::DatabaseLayout;
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||
|
||||
use crate::entities::*;
|
||||
@ -17,7 +17,6 @@ use crate::services::field::{
|
||||
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
|
||||
};
|
||||
use crate::services::group::{GroupChangeset, GroupSettingChangeset};
|
||||
use crate::services::share::csv::CSVFormat;
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn get_database_data_handler(
|
||||
@ -678,27 +677,3 @@ pub(crate) async fn get_calendar_event_handler(
|
||||
Some(event) => data_result_ok(event),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn import_data_handler(
|
||||
data: AFPluginData<DatabaseImportPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager2>>,
|
||||
) -> FlowyResult<()> {
|
||||
let params = data.into_inner();
|
||||
|
||||
match params.import_type {
|
||||
ImportTypePB::CSV => {
|
||||
if let Some(data) = params.data {
|
||||
manager.import_csv(data, CSVFormat::META).await?;
|
||||
} else if let Some(uri) = params.uri {
|
||||
manager.import_csv_from_uri(uri, CSVFormat::META).await?;
|
||||
} else {
|
||||
return Err(FlowyError::new(
|
||||
ErrorCode::InvalidData,
|
||||
"No data or uri provided",
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ use crate::event_handler::*;
|
||||
use crate::manager::DatabaseManager2;
|
||||
|
||||
pub fn init(database_manager: Arc<DatabaseManager2>) -> AFPlugin {
|
||||
let mut plugin = AFPlugin::new()
|
||||
let plugin = AFPlugin::new()
|
||||
.name(env!("CARGO_PKG_NAME"))
|
||||
.state(database_manager);
|
||||
plugin = plugin
|
||||
plugin
|
||||
.event(DatabaseEvent::GetDatabase, get_database_data_handler)
|
||||
.event(DatabaseEvent::GetDatabaseSetting, get_database_setting_handler)
|
||||
.event(DatabaseEvent::UpdateDatabaseSetting, update_database_setting_handler)
|
||||
@ -64,10 +64,7 @@ pub fn init(database_manager: Arc<DatabaseManager2>) -> AFPlugin {
|
||||
// Layout setting
|
||||
.event(DatabaseEvent::SetLayoutSetting, set_layout_setting_handler)
|
||||
.event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler)
|
||||
// import
|
||||
.event(DatabaseEvent::ImportCSV, import_data_handler);
|
||||
|
||||
plugin
|
||||
// import
|
||||
}
|
||||
|
||||
/// [DatabaseEvent] defines events that are used to interact with the Grid. You could check [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/backend/protobuf)
|
||||
@ -278,7 +275,4 @@ pub enum DatabaseEvent {
|
||||
|
||||
#[event(input = "MoveCalendarEventPB")]
|
||||
MoveCalendarEvent = 125,
|
||||
|
||||
#[event(input = "DatabaseImportPB")]
|
||||
ImportCSV = 130,
|
||||
}
|
||||
|
@ -195,11 +195,17 @@ impl DatabaseManager2 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn import_csv(&self, content: String, format: CSVFormat) -> FlowyResult<ImportResult> {
|
||||
let params =
|
||||
tokio::task::spawn_blocking(move || CSVImporter.import_csv_from_string(content, format))
|
||||
.await
|
||||
.map_err(internal_error)??;
|
||||
pub async fn import_csv(
|
||||
&self,
|
||||
view_id: String,
|
||||
content: String,
|
||||
format: CSVFormat,
|
||||
) -> FlowyResult<ImportResult> {
|
||||
let params = tokio::task::spawn_blocking(move || {
|
||||
CSVImporter.import_csv_from_string(view_id, content, format)
|
||||
})
|
||||
.await
|
||||
.map_err(internal_error)??;
|
||||
let result = ImportResult {
|
||||
database_id: params.database_id.clone(),
|
||||
view_id: params.view_id.clone(),
|
||||
@ -208,7 +214,12 @@ impl DatabaseManager2 {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn import_csv_from_uri(&self, _uri: String, _format: CSVFormat) -> FlowyResult<()> {
|
||||
// will implement soon
|
||||
pub async fn import_csv_from_file(
|
||||
&self,
|
||||
_file_path: String,
|
||||
_format: CSVFormat,
|
||||
) -> FlowyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ use crate::entities::FieldType;
|
||||
|
||||
use crate::services::field::{default_type_option_data_from_type, CELL_DATA};
|
||||
use crate::services::share::csv::CSVFormat;
|
||||
use collab_database::database::{gen_database_id, gen_database_view_id, gen_field_id, gen_row_id};
|
||||
use collab_database::database::{gen_database_id, gen_field_id, gen_row_id};
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::{new_cell_builder, Cell, CreateRowParams};
|
||||
use collab_database::views::{CreateDatabaseParams, DatabaseLayout};
|
||||
@ -15,6 +15,7 @@ pub struct CSVImporter;
|
||||
impl CSVImporter {
|
||||
pub fn import_csv_from_file(
|
||||
&self,
|
||||
view_id: &str,
|
||||
path: &str,
|
||||
style: CSVFormat,
|
||||
) -> FlowyResult<CreateDatabaseParams> {
|
||||
@ -22,17 +23,18 @@ impl CSVImporter {
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content)?;
|
||||
let fields_with_rows = self.get_fields_and_rows(content)?;
|
||||
let database_data = database_from_fields_and_rows(fields_with_rows, &style);
|
||||
let database_data = database_from_fields_and_rows(view_id, fields_with_rows, &style);
|
||||
Ok(database_data)
|
||||
}
|
||||
|
||||
pub fn import_csv_from_string(
|
||||
&self,
|
||||
view_id: String,
|
||||
content: String,
|
||||
format: CSVFormat,
|
||||
) -> FlowyResult<CreateDatabaseParams> {
|
||||
let fields_with_rows = self.get_fields_and_rows(content)?;
|
||||
let database_data = database_from_fields_and_rows(fields_with_rows, &format);
|
||||
let database_data = database_from_fields_and_rows(&view_id, fields_with_rows, &format);
|
||||
Ok(database_data)
|
||||
}
|
||||
|
||||
@ -68,11 +70,11 @@ impl CSVImporter {
|
||||
}
|
||||
|
||||
fn database_from_fields_and_rows(
|
||||
view_id: &str,
|
||||
fields_and_rows: FieldsRows,
|
||||
format: &CSVFormat,
|
||||
) -> CreateDatabaseParams {
|
||||
let (fields, rows) = fields_and_rows.split();
|
||||
let view_id = gen_database_view_id();
|
||||
let database_id = gen_database_id();
|
||||
|
||||
let fields = fields
|
||||
@ -125,7 +127,7 @@ fn database_from_fields_and_rows(
|
||||
|
||||
CreateDatabaseParams {
|
||||
database_id,
|
||||
view_id,
|
||||
view_id: view_id.to_string(),
|
||||
name: "".to_string(),
|
||||
layout: DatabaseLayout::Grid,
|
||||
layout_settings: Default::default(),
|
||||
@ -167,6 +169,7 @@ pub struct ImportResult {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::services::share::csv::{CSVFormat, CSVImporter};
|
||||
use collab_database::database::gen_database_view_id;
|
||||
|
||||
#[test]
|
||||
fn test_import_csv_from_str() {
|
||||
@ -176,7 +179,7 @@ mod tests {
|
||||
,,,,Yes,"#;
|
||||
let importer = CSVImporter;
|
||||
let result = importer
|
||||
.import_csv_from_string(s.to_string(), CSVFormat::Original)
|
||||
.import_csv_from_string(gen_database_view_id(), s.to_string(), CSVFormat::Original)
|
||||
.unwrap();
|
||||
assert_eq!(result.created_rows.len(), 3);
|
||||
assert_eq!(result.fields.len(), 6);
|
||||
|
@ -1,3 +1,4 @@
|
||||
use collab_database::database::gen_database_view_id;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -262,7 +263,7 @@ impl DatabaseEditorTest {
|
||||
self
|
||||
.sdk
|
||||
.database_manager
|
||||
.import_csv(s, format)
|
||||
.import_csv(gen_database_view_id(), s, format)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
82
frontend/rust-lib/flowy-folder2/src/entities/import.rs
Normal file
82
frontend/rust-lib/flowy-folder2/src/entities/import.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use crate::entities::parser::empty_str::NotEmptyStr;
|
||||
use crate::entities::ViewLayoutPB;
|
||||
use crate::share::{ImportParams, ImportType};
|
||||
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::FlowyError;
|
||||
|
||||
#[derive(Clone, Debug, ProtoBuf_Enum)]
|
||||
pub enum ImportTypePB {
|
||||
HistoryDocument = 0,
|
||||
HistoryDatabase = 1,
|
||||
}
|
||||
|
||||
impl From<ImportTypePB> for ImportType {
|
||||
fn from(pb: ImportTypePB) -> Self {
|
||||
match pb {
|
||||
ImportTypePB::HistoryDocument => ImportType::HistoryDocument,
|
||||
ImportTypePB::HistoryDatabase => ImportType::HistoryDatabase,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ImportTypePB {
|
||||
fn default() -> Self {
|
||||
Self::HistoryDocument
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, ProtoBuf, Default)]
|
||||
pub struct ImportPB {
|
||||
#[pb(index = 1)]
|
||||
pub parent_view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub name: String,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub data: Option<Vec<u8>>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub file_path: Option<String>,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub view_layout: ViewLayoutPB,
|
||||
|
||||
#[pb(index = 6)]
|
||||
pub import_type: ImportTypePB,
|
||||
}
|
||||
|
||||
impl TryInto<ImportParams> for ImportPB {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_into(self) -> Result<ImportParams, Self::Error> {
|
||||
let parent_view_id = NotEmptyStr::parse(self.parent_view_id)
|
||||
.map_err(|_| FlowyError::invalid_view_id())?
|
||||
.0;
|
||||
|
||||
let name = if self.name.is_empty() {
|
||||
"Untitled".to_string()
|
||||
} else {
|
||||
self.name
|
||||
};
|
||||
|
||||
let file_path = match self.file_path {
|
||||
None => None,
|
||||
Some(file_path) => Some(
|
||||
NotEmptyStr::parse(file_path)
|
||||
.map_err(|_| FlowyError::invalid_data().context("The import file path is empty"))?
|
||||
.0,
|
||||
),
|
||||
};
|
||||
|
||||
Ok(ImportParams {
|
||||
parent_view_id,
|
||||
name,
|
||||
data: self.data,
|
||||
file_path,
|
||||
view_layout: self.view_layout.into(),
|
||||
import_type: self.import_type.into(),
|
||||
})
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
mod import;
|
||||
mod parser;
|
||||
pub mod trash;
|
||||
pub mod view;
|
||||
pub mod workspace;
|
||||
|
||||
pub use import::*;
|
||||
pub use trash::*;
|
||||
pub use view::*;
|
||||
pub use workspace::*;
|
||||
|
@ -0,0 +1,17 @@
|
||||
#[derive(Debug)]
|
||||
pub struct NotEmptyStr(pub String);
|
||||
|
||||
impl NotEmptyStr {
|
||||
pub fn parse(s: String) -> Result<Self, String> {
|
||||
if s.trim().is_empty() {
|
||||
return Err("Input string is empty".to_owned());
|
||||
}
|
||||
Ok(Self(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for NotEmptyStr {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
// pub mod app;
|
||||
pub mod empty_str;
|
||||
pub mod trash;
|
||||
pub mod view;
|
||||
pub mod workspace;
|
||||
|
@ -123,7 +123,7 @@ pub struct RepeatedViewIdPB {
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct CreateViewPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub belong_to_id: String,
|
||||
pub parent_view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub name: String,
|
||||
@ -146,13 +146,13 @@ pub struct CreateViewPayloadPB {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CreateViewParams {
|
||||
pub belong_to_id: String,
|
||||
pub parent_view_id: String,
|
||||
pub name: String,
|
||||
pub desc: String,
|
||||
pub layout: ViewLayoutPB,
|
||||
pub view_id: String,
|
||||
pub initial_data: Vec<u8>,
|
||||
pub ext: HashMap<String, String>,
|
||||
pub meta: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl TryInto<CreateViewParams> for CreateViewPayloadPB {
|
||||
@ -160,17 +160,17 @@ impl TryInto<CreateViewParams> for CreateViewPayloadPB {
|
||||
|
||||
fn try_into(self) -> Result<CreateViewParams, Self::Error> {
|
||||
let name = ViewName::parse(self.name)?.0;
|
||||
let belong_to_id = ViewIdentify::parse(self.belong_to_id)?.0;
|
||||
let belong_to_id = ViewIdentify::parse(self.parent_view_id)?.0;
|
||||
let view_id = gen_view_id();
|
||||
|
||||
Ok(CreateViewParams {
|
||||
belong_to_id,
|
||||
parent_view_id: belong_to_id,
|
||||
name,
|
||||
desc: self.desc,
|
||||
layout: self.layout,
|
||||
view_id,
|
||||
initial_data: self.initial_data,
|
||||
ext: self.ext,
|
||||
meta: self.ext,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
use crate::entities::{
|
||||
view_pb_without_child_views, CreateViewParams, CreateViewPayloadPB, CreateWorkspaceParams,
|
||||
CreateWorkspacePayloadPB, MoveFolderItemPayloadPB, MoveViewParams, RepeatedTrashIdPB,
|
||||
CreateWorkspacePayloadPB, ImportPB, MoveFolderItemPayloadPB, MoveViewParams, RepeatedTrashIdPB,
|
||||
RepeatedTrashPB, RepeatedViewIdPB, RepeatedViewPB, RepeatedWorkspacePB, TrashIdPB,
|
||||
UpdateViewParams, UpdateViewPayloadPB, ViewIdPB, ViewPB, WorkspaceIdPB, WorkspacePB,
|
||||
WorkspaceSettingPB,
|
||||
};
|
||||
use crate::manager::Folder2Manager;
|
||||
|
||||
use crate::share::ImportParams;
|
||||
use flowy_error::FlowyError;
|
||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||
use std::sync::Arc;
|
||||
@ -203,3 +204,13 @@ pub(crate) async fn delete_all_trash_handler(
|
||||
folder.delete_all_trash().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, folder), err)]
|
||||
pub(crate) async fn import_data_handler(
|
||||
data: AFPluginData<ImportPB>,
|
||||
folder: AFPluginState<Arc<Folder2Manager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: ImportParams = data.into_inner().try_into()?;
|
||||
folder.import(params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ pub fn init(folder: Arc<Folder2Manager>) -> AFPlugin {
|
||||
.event(FolderEvent::DeleteTrash, delete_trash_handler)
|
||||
.event(FolderEvent::RestoreAllTrash, restore_all_trash_handler)
|
||||
.event(FolderEvent::DeleteAllTrash, delete_all_trash_handler)
|
||||
.event(FolderEvent::ImportData, import_data_handler)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||
@ -64,57 +65,60 @@ pub enum FolderEvent {
|
||||
|
||||
/// Create a new view in the corresponding app
|
||||
#[event(input = "CreateViewPayloadPB", output = "ViewPB")]
|
||||
CreateView = 201,
|
||||
CreateView = 10,
|
||||
|
||||
/// Return the view info
|
||||
#[event(input = "ViewIdPB", output = "ViewPB")]
|
||||
ReadView = 202,
|
||||
ReadView = 11,
|
||||
|
||||
/// Update the view's properties including the name,description, etc.
|
||||
#[event(input = "UpdateViewPayloadPB", output = "ViewPB")]
|
||||
UpdateView = 203,
|
||||
UpdateView = 12,
|
||||
|
||||
/// Move the view to the trash folder
|
||||
#[event(input = "RepeatedViewIdPB")]
|
||||
DeleteView = 204,
|
||||
DeleteView = 13,
|
||||
|
||||
/// Duplicate the view
|
||||
#[event(input = "ViewPB")]
|
||||
DuplicateView = 205,
|
||||
DuplicateView = 14,
|
||||
|
||||
/// Close and release the resources that are used by this view.
|
||||
/// It should get called when the 'View' page get destroy
|
||||
#[event(input = "ViewIdPB")]
|
||||
CloseView = 206,
|
||||
CloseView = 15,
|
||||
|
||||
#[event()]
|
||||
CopyLink = 220,
|
||||
CopyLink = 20,
|
||||
|
||||
/// Set the current visiting view
|
||||
#[event(input = "ViewIdPB")]
|
||||
SetLatestView = 221,
|
||||
SetLatestView = 21,
|
||||
|
||||
/// Move the view or app to another place
|
||||
#[event(input = "MoveFolderItemPayloadPB")]
|
||||
MoveItem = 230,
|
||||
MoveItem = 22,
|
||||
|
||||
/// Read the trash that was deleted by the user
|
||||
#[event(output = "RepeatedTrashPB")]
|
||||
ReadTrash = 300,
|
||||
ReadTrash = 23,
|
||||
|
||||
/// Put back the trash to the origin folder
|
||||
#[event(input = "TrashIdPB")]
|
||||
PutbackTrash = 301,
|
||||
PutbackTrash = 24,
|
||||
|
||||
/// Delete the trash from the disk
|
||||
#[event(input = "RepeatedTrashIdPB")]
|
||||
DeleteTrash = 302,
|
||||
DeleteTrash = 25,
|
||||
|
||||
/// Put back all the trash to its original folder
|
||||
#[event()]
|
||||
RestoreAllTrash = 303,
|
||||
RestoreAllTrash = 26,
|
||||
|
||||
/// Delete all the trash from the disk
|
||||
#[event()]
|
||||
DeleteAllTrash = 304,
|
||||
DeleteAllTrash = 27,
|
||||
|
||||
#[event(input = "ImportPB")]
|
||||
ImportData = 30,
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ mod user_default;
|
||||
pub mod view_ext;
|
||||
|
||||
pub mod deps;
|
||||
mod share;
|
||||
#[cfg(feature = "test_helper")]
|
||||
mod test_helper;
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -12,7 +13,7 @@ use parking_lot::Mutex;
|
||||
use tracing::{event, Level};
|
||||
|
||||
use crate::deps::{FolderCloudService, FolderUser};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
use crate::entities::{
|
||||
@ -23,16 +24,15 @@ use crate::notification::{
|
||||
send_notification, send_workspace_notification, send_workspace_setting_notification,
|
||||
FolderNotification,
|
||||
};
|
||||
use crate::share::ImportParams;
|
||||
use crate::user_default::DefaultFolderBuilder;
|
||||
use crate::view_ext::{
|
||||
gen_view_id, view_from_create_view_params, ViewDataProcessor, ViewDataProcessorMap,
|
||||
};
|
||||
use crate::view_ext::{create_view, gen_view_id, FolderOperationHandler, FolderOperationHandlers};
|
||||
|
||||
pub struct Folder2Manager {
|
||||
folder: Folder,
|
||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||
user: Arc<dyn FolderUser>,
|
||||
view_processors: ViewDataProcessorMap,
|
||||
operation_handlers: FolderOperationHandlers,
|
||||
cloud_service: Arc<dyn FolderCloudService>,
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ impl Folder2Manager {
|
||||
pub async fn new(
|
||||
user: Arc<dyn FolderUser>,
|
||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||
view_processors: ViewDataProcessorMap,
|
||||
operation_handlers: FolderOperationHandlers,
|
||||
cloud_service: Arc<dyn FolderCloudService>,
|
||||
) -> FlowyResult<Self> {
|
||||
let folder = Folder::default();
|
||||
@ -51,7 +51,7 @@ impl Folder2Manager {
|
||||
user,
|
||||
folder,
|
||||
collab_builder,
|
||||
view_processors,
|
||||
operation_handlers,
|
||||
cloud_service,
|
||||
};
|
||||
|
||||
@ -117,7 +117,7 @@ impl Folder2Manager {
|
||||
let (folder_data, workspace_pb) = DefaultFolderBuilder::build(
|
||||
self.user.user_id()?,
|
||||
workspace_id.to_string(),
|
||||
&self.view_processors,
|
||||
&self.operation_handlers,
|
||||
)
|
||||
.await;
|
||||
self.with_folder((), |folder| {
|
||||
@ -187,14 +187,14 @@ impl Folder2Manager {
|
||||
|
||||
pub async fn create_view_with_params(&self, params: CreateViewParams) -> FlowyResult<View> {
|
||||
let view_layout: ViewLayout = params.layout.clone().into();
|
||||
let processor = self.get_data_processor(&view_layout)?;
|
||||
let handler = self.get_handler(&view_layout)?;
|
||||
let user_id = self.user.user_id()?;
|
||||
let ext = params.ext.clone();
|
||||
let ext = params.meta.clone();
|
||||
match params.initial_data.is_empty() {
|
||||
true => {
|
||||
tracing::trace!("Create view with build-in data");
|
||||
processor
|
||||
.create_view_with_built_in_data(
|
||||
handler
|
||||
.create_built_in_view(
|
||||
user_id,
|
||||
¶ms.view_id,
|
||||
¶ms.name,
|
||||
@ -205,8 +205,8 @@ impl Folder2Manager {
|
||||
},
|
||||
false => {
|
||||
tracing::trace!("Create view with view data");
|
||||
processor
|
||||
.create_view_with_custom_data(
|
||||
handler
|
||||
.create_view_with_view_data(
|
||||
user_id,
|
||||
¶ms.view_id,
|
||||
¶ms.name,
|
||||
@ -217,7 +217,7 @@ impl Folder2Manager {
|
||||
.await?;
|
||||
},
|
||||
}
|
||||
let view = view_from_create_view_params(params, view_layout);
|
||||
let view = create_view(params, view_layout);
|
||||
self.with_folder((), |folder| {
|
||||
folder.insert_view(view.clone());
|
||||
});
|
||||
@ -233,12 +233,12 @@ impl Folder2Manager {
|
||||
.ok_or_else(|| {
|
||||
FlowyError::record_not_found().context("Can't find the view when closing the view")
|
||||
})?;
|
||||
let processor = self.get_data_processor(&view.layout)?;
|
||||
processor.close_view(view_id).await?;
|
||||
let handler = self.get_handler(&view.layout)?;
|
||||
handler.close_view(view_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_view_data(
|
||||
pub async fn create_view_with_data(
|
||||
&self,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
@ -246,9 +246,9 @@ impl Folder2Manager {
|
||||
data: Vec<u8>,
|
||||
) -> FlowyResult<()> {
|
||||
let user_id = self.user.user_id()?;
|
||||
let processor = self.get_data_processor(&view_layout)?;
|
||||
processor
|
||||
.create_view_with_custom_data(
|
||||
let handler = self.get_handler(&view_layout)?;
|
||||
handler
|
||||
.create_view_with_view_data(
|
||||
user_id,
|
||||
view_id,
|
||||
name,
|
||||
@ -367,20 +367,20 @@ impl Folder2Manager {
|
||||
.with_folder(None, |folder| folder.views.get_view(view_id))
|
||||
.ok_or_else(|| FlowyError::record_not_found().context("Can't duplicate the view"))?;
|
||||
|
||||
let processor = self.get_data_processor(&view.layout)?;
|
||||
let view_data = processor.get_view_data(&view.id).await?;
|
||||
let handler = self.get_handler(&view.layout)?;
|
||||
let view_data = handler.duplicate_view(&view.id).await?;
|
||||
let mut ext = HashMap::new();
|
||||
if let Some(database_id) = view.database_id {
|
||||
ext.insert("database_id".to_string(), database_id);
|
||||
}
|
||||
let duplicate_params = CreateViewParams {
|
||||
belong_to_id: view.bid.clone(),
|
||||
parent_view_id: view.bid.clone(),
|
||||
name: format!("{} (copy)", &view.name),
|
||||
desc: view.desc,
|
||||
layout: view.layout.into(),
|
||||
initial_data: view_data.to_vec(),
|
||||
view_id: gen_view_id(),
|
||||
ext,
|
||||
meta: ext,
|
||||
};
|
||||
|
||||
let _ = self.create_view_with_params(duplicate_params).await?;
|
||||
@ -451,11 +451,52 @@ impl Folder2Manager {
|
||||
.send();
|
||||
}
|
||||
|
||||
fn get_data_processor(
|
||||
pub(crate) async fn import(&self, import_data: ImportParams) -> FlowyResult<View> {
|
||||
if import_data.data.is_none() && import_data.file_path.is_none() {
|
||||
return Err(FlowyError::new(
|
||||
ErrorCode::InvalidData,
|
||||
"data or file_path is required",
|
||||
));
|
||||
}
|
||||
|
||||
let handler = self.get_handler(&import_data.view_layout)?;
|
||||
let view_id = gen_view_id();
|
||||
if let Some(data) = import_data.data {
|
||||
handler
|
||||
.import_from_bytes(&view_id, &import_data.name, data)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(file_path) = import_data.file_path {
|
||||
handler
|
||||
.import_from_file_path(&view_id, &import_data.name, file_path)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let params = CreateViewParams {
|
||||
parent_view_id: import_data.parent_view_id,
|
||||
name: import_data.name,
|
||||
desc: "".to_string(),
|
||||
layout: import_data.view_layout.clone().into(),
|
||||
initial_data: vec![],
|
||||
view_id,
|
||||
meta: Default::default(),
|
||||
};
|
||||
|
||||
let view = create_view(params, import_data.view_layout);
|
||||
self.with_folder((), |folder| {
|
||||
folder.insert_view(view.clone());
|
||||
});
|
||||
notify_parent_view_did_change(self.folder.clone(), vec![view.bid.clone()]);
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
/// Returns a handler that implements the [FolderOperationHandler] trait
|
||||
fn get_handler(
|
||||
&self,
|
||||
view_layout: &ViewLayout,
|
||||
) -> FlowyResult<Arc<dyn ViewDataProcessor + Send + Sync>> {
|
||||
match self.view_processors.get(view_layout) {
|
||||
) -> FlowyResult<Arc<dyn FolderOperationHandler + Send + Sync>> {
|
||||
match self.operation_handlers.get(view_layout) {
|
||||
None => Err(FlowyError::internal().context(format!(
|
||||
"Get data processor failed. Unknown layout type: {:?}",
|
||||
view_layout
|
||||
|
17
frontend/rust-lib/flowy-folder2/src/share/import.rs
Normal file
17
frontend/rust-lib/flowy-folder2/src/share/import.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use collab_folder::core::ViewLayout;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ImportType {
|
||||
HistoryDocument = 0,
|
||||
HistoryDatabase = 1,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImportParams {
|
||||
pub parent_view_id: String,
|
||||
pub name: String,
|
||||
pub data: Option<Vec<u8>>,
|
||||
pub file_path: Option<String>,
|
||||
pub view_layout: ViewLayout,
|
||||
pub import_type: ImportType,
|
||||
}
|
3
frontend/rust-lib/flowy-folder2/src/share/mod.rs
Normal file
3
frontend/rust-lib/flowy-folder2/src/share/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod import;
|
||||
|
||||
pub use import::*;
|
@ -36,13 +36,13 @@ impl Folder2Manager {
|
||||
) -> String {
|
||||
let view_id = gen_view_id();
|
||||
let params = CreateViewParams {
|
||||
belong_to_id: app_id.to_string(),
|
||||
parent_view_id: app_id.to_string(),
|
||||
name: name.to_string(),
|
||||
desc: "".to_string(),
|
||||
layout,
|
||||
view_id: view_id.clone(),
|
||||
initial_data: vec![],
|
||||
ext,
|
||||
meta: ext,
|
||||
};
|
||||
self.create_view_with_params(params).await.unwrap();
|
||||
view_id
|
||||
|
@ -5,14 +5,14 @@ use collab_folder::core::{Belonging, Belongings, FolderData, View, ViewLayout, W
|
||||
use nanoid::nanoid;
|
||||
|
||||
use crate::entities::{view_pb_with_child_views, WorkspacePB};
|
||||
use crate::view_ext::{gen_view_id, ViewDataProcessorMap};
|
||||
use crate::view_ext::{gen_view_id, FolderOperationHandlers};
|
||||
|
||||
pub struct DefaultFolderBuilder();
|
||||
impl DefaultFolderBuilder {
|
||||
pub async fn build(
|
||||
uid: i64,
|
||||
workspace_id: String,
|
||||
view_processors: &ViewDataProcessorMap,
|
||||
handlers: &FolderOperationHandlers,
|
||||
) -> (FolderData, WorkspacePB) {
|
||||
let time = Utc::now().timestamp();
|
||||
let view_id = gen_view_id();
|
||||
@ -33,9 +33,9 @@ impl DefaultFolderBuilder {
|
||||
// create the document
|
||||
// TODO: use the initial data from the view processor
|
||||
// let data = initial_read_me().into_bytes();
|
||||
let processor = view_processors.get(&child_view_layout).unwrap();
|
||||
processor
|
||||
.create_view_with_built_in_data(
|
||||
let handler = handlers.get(&child_view_layout).unwrap();
|
||||
handler
|
||||
.create_built_in_view(
|
||||
uid,
|
||||
&child_view.id,
|
||||
&child_view.name,
|
||||
|
@ -8,29 +8,22 @@ use lib_infra::util::timestamp;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait ViewDataProcessor {
|
||||
pub type ViewData = Bytes;
|
||||
|
||||
/// The handler will be used to handler the folder operation for a specific
|
||||
/// view layout. Each [ViewLayout] will have a handler. So when creating a new
|
||||
/// view, the [ViewLayout] will be used to get the handler.
|
||||
///
|
||||
pub trait FolderOperationHandler {
|
||||
/// Closes the view and releases the resources that this view has in
|
||||
/// the backend
|
||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>;
|
||||
|
||||
/// Gets the data of the this view.
|
||||
/// For example, the data can be used to duplicate the view.
|
||||
fn get_view_data(&self, view_id: &str) -> FutureResult<Bytes, FlowyError>;
|
||||
|
||||
/// Create a view with the pre-defined data.
|
||||
/// For example, the initial data of the grid/calendar/kanban board when
|
||||
/// you create a new view.
|
||||
fn create_view_with_built_in_data(
|
||||
&self,
|
||||
user_id: i64,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
layout: ViewLayout,
|
||||
ext: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError>;
|
||||
/// Returns the [ViewData] that can be used to create the same view.
|
||||
fn duplicate_view(&self, view_id: &str) -> FutureResult<ViewData, FlowyError>;
|
||||
|
||||
/// Create a view with custom data
|
||||
fn create_view_with_custom_data(
|
||||
fn create_view_with_view_data(
|
||||
&self,
|
||||
user_id: i64,
|
||||
view_id: &str,
|
||||
@ -39,9 +32,38 @@ pub trait ViewDataProcessor {
|
||||
layout: ViewLayout,
|
||||
ext: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError>;
|
||||
|
||||
/// Create a view with the pre-defined data.
|
||||
/// For example, the initial data of the grid/calendar/kanban board when
|
||||
/// you create a new view.
|
||||
fn create_built_in_view(
|
||||
&self,
|
||||
user_id: i64,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
layout: ViewLayout,
|
||||
meta: HashMap<String, String>,
|
||||
) -> FutureResult<(), FlowyError>;
|
||||
|
||||
/// Create a view by importing data
|
||||
fn import_from_bytes(
|
||||
&self,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
bytes: Vec<u8>,
|
||||
) -> FutureResult<(), FlowyError>;
|
||||
|
||||
/// Create a view by importing data from a file
|
||||
fn import_from_file_path(
|
||||
&self,
|
||||
view_id: &str,
|
||||
name: &str,
|
||||
path: String,
|
||||
) -> FutureResult<(), FlowyError>;
|
||||
}
|
||||
|
||||
pub type ViewDataProcessorMap = Arc<HashMap<ViewLayout, Arc<dyn ViewDataProcessor + Send + Sync>>>;
|
||||
pub type FolderOperationHandlers =
|
||||
Arc<HashMap<ViewLayout, Arc<dyn FolderOperationHandler + Send + Sync>>>;
|
||||
|
||||
impl From<ViewLayoutPB> for ViewLayout {
|
||||
fn from(pb: ViewLayoutPB) -> Self {
|
||||
@ -54,11 +76,11 @@ impl From<ViewLayoutPB> for ViewLayout {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_from_create_view_params(params: CreateViewParams, layout: ViewLayout) -> View {
|
||||
pub(crate) fn create_view(params: CreateViewParams, layout: ViewLayout) -> View {
|
||||
let time = timestamp();
|
||||
View {
|
||||
id: params.view_id,
|
||||
bid: params.belong_to_id,
|
||||
bid: params.parent_view_id,
|
||||
name: params.name,
|
||||
desc: params.desc,
|
||||
belongings: Default::default(),
|
||||
|
@ -212,7 +212,7 @@ pub async fn read_workspace(sdk: &FlowyCoreTest, workspace_id: Option<String>) -
|
||||
|
||||
pub async fn create_app(sdk: &FlowyCoreTest, workspace_id: &str, name: &str, desc: &str) -> ViewPB {
|
||||
let create_view_request = CreateViewPayloadPB {
|
||||
belong_to_id: workspace_id.to_owned(),
|
||||
parent_view_id: workspace_id.to_owned(),
|
||||
name: name.to_string(),
|
||||
desc: desc.to_string(),
|
||||
thumbnail: None,
|
||||
@ -237,7 +237,7 @@ pub async fn create_view(
|
||||
layout: ViewLayout,
|
||||
) -> ViewPB {
|
||||
let request = CreateViewPayloadPB {
|
||||
belong_to_id: app_id.to_string(),
|
||||
parent_view_id: app_id.to_string(),
|
||||
name: name.to_string(),
|
||||
desc: desc.to_string(),
|
||||
thumbnail: None,
|
||||
|
@ -69,7 +69,7 @@ async fn open_workspace(sdk: &FlowyCoreTest, workspace_id: &str) {
|
||||
|
||||
async fn create_app(sdk: &FlowyCoreTest, name: &str, desc: &str, workspace_id: &str) -> ViewPB {
|
||||
let create_app_request = CreateViewPayloadPB {
|
||||
belong_to_id: workspace_id.to_owned(),
|
||||
parent_view_id: workspace_id.to_owned(),
|
||||
name: name.to_string(),
|
||||
desc: desc.to_string(),
|
||||
thumbnail: None,
|
||||
@ -93,7 +93,7 @@ async fn create_view(
|
||||
data: Vec<u8>,
|
||||
) -> ViewPB {
|
||||
let payload = CreateViewPayloadPB {
|
||||
belong_to_id: app_id.to_string(),
|
||||
parent_view_id: app_id.to_string(),
|
||||
name: "View A".to_string(),
|
||||
desc: "".to_string(),
|
||||
thumbnail: Some("http://1.png".to_string()),
|
||||
|
Loading…
Reference in New Issue
Block a user