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 prefix = _referencedDatabasePrefix(viewPB.layout);
|
||||||
final ref = await AppBackendService().createView(
|
final ref = await AppBackendService().createView(
|
||||||
appId: appPB.id,
|
parentViewId: appPB.id,
|
||||||
name: "$prefix ${viewPB.name}",
|
name: "$prefix ${viewPB.name}",
|
||||||
layoutType: viewPB.layout,
|
layoutType: viewPB.layout,
|
||||||
ext: {
|
ext: {
|
||||||
|
@ -25,7 +25,7 @@ SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) =>
|
|||||||
final service = AppBackendService();
|
final service = AppBackendService();
|
||||||
|
|
||||||
final result = (await service.createView(
|
final result = (await service.createView(
|
||||||
appId: appId,
|
parentViewId: appId,
|
||||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
layoutType: ViewLayoutPB.Board,
|
layoutType: ViewLayoutPB.Board,
|
||||||
))
|
))
|
||||||
|
@ -25,7 +25,7 @@ SelectionMenuItem gridViewMenuItem(DocumentBloc documentBloc) =>
|
|||||||
final service = AppBackendService();
|
final service = AppBackendService();
|
||||||
|
|
||||||
final result = (await service.createView(
|
final result = (await service.createView(
|
||||||
appId: appId,
|
parentViewId: appId,
|
||||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
layoutType: ViewLayoutPB.Grid,
|
layoutType: ViewLayoutPB.Grid,
|
||||||
))
|
))
|
||||||
|
@ -104,7 +104,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
|||||||
|
|
||||||
Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
|
Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
|
||||||
final result = await appService.createView(
|
final result = await appService.createView(
|
||||||
appId: state.view.id,
|
parentViewId: state.view.id,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
desc: value.desc ?? "",
|
desc: value.desc ?? "",
|
||||||
layoutType: value.pluginBuilder.layoutType!,
|
layoutType: value.pluginBuilder.layoutType!,
|
||||||
|
@ -8,7 +8,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
|||||||
|
|
||||||
class AppBackendService {
|
class AppBackendService {
|
||||||
Future<Either<ViewPB, FlowyError>> createView({
|
Future<Either<ViewPB, FlowyError>> createView({
|
||||||
required String appId,
|
required String parentViewId,
|
||||||
required String name,
|
required String name,
|
||||||
String? desc,
|
String? desc,
|
||||||
required ViewLayoutPB layoutType,
|
required ViewLayoutPB layoutType,
|
||||||
@ -25,7 +25,7 @@ class AppBackendService {
|
|||||||
Map<String, String> ext = const {},
|
Map<String, String> ext = const {},
|
||||||
}) {
|
}) {
|
||||||
final payload = CreateViewPayloadPB.create()
|
final payload = CreateViewPayloadPB.create()
|
||||||
..belongToId = appId
|
..parentViewId = parentViewId
|
||||||
..name = name
|
..name = name
|
||||||
..desc = desc ?? ""
|
..desc = desc ?? ""
|
||||||
..layout = layoutType
|
..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,
|
String? desc,
|
||||||
}) {
|
}) {
|
||||||
final payload = CreateViewPayloadPB.create()
|
final payload = CreateViewPayloadPB.create()
|
||||||
..belongToId = workspaceId
|
..parentViewId = workspaceId
|
||||||
..name = name
|
..name = name
|
||||||
..desc = desc ?? ""
|
..desc = desc ?? ""
|
||||||
..layout = ViewLayoutPB.Document;
|
..layout = ViewLayoutPB.Document;
|
||||||
|
@ -12,6 +12,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
class AddButton extends StatelessWidget {
|
class AddButton extends StatelessWidget {
|
||||||
|
final String parentViewId;
|
||||||
final Function(
|
final Function(
|
||||||
PluginBuilder,
|
PluginBuilder,
|
||||||
String? name,
|
String? name,
|
||||||
@ -20,6 +21,7 @@ class AddButton extends StatelessWidget {
|
|||||||
) onSelected;
|
) onSelected;
|
||||||
|
|
||||||
const AddButton({
|
const AddButton({
|
||||||
|
required this.parentViewId,
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -75,20 +77,34 @@ class AddButton extends StatelessWidget {
|
|||||||
onSelected(action.pluginBuilder, null, null, true);
|
onSelected(action.pluginBuilder, null, null, true);
|
||||||
}
|
}
|
||||||
if (action is ImportActionWrapper) {
|
if (action is ImportActionWrapper) {
|
||||||
showImportPanel(context, (type, name, initialDataBytes) {
|
showImportPanel(
|
||||||
if (initialDataBytes == null) {
|
parentViewId,
|
||||||
return;
|
context,
|
||||||
}
|
(type, name, initialDataBytes) {
|
||||||
switch (type) {
|
if (initialDataBytes == null) {
|
||||||
case ImportType.historyDocument:
|
return;
|
||||||
case ImportType.historyDatabase:
|
}
|
||||||
onSelected(action.pluginBuilder, name, initialDataBytes, false);
|
switch (type) {
|
||||||
break;
|
case ImportType.historyDocument:
|
||||||
case ImportType.markdownOrText:
|
case ImportType.historyDatabase:
|
||||||
onSelected(action.pluginBuilder, name, initialDataBytes, true);
|
onSelected(
|
||||||
break;
|
action.pluginBuilder,
|
||||||
}
|
name,
|
||||||
});
|
initialDataBytes,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ImportType.markdownOrText:
|
||||||
|
onSelected(
|
||||||
|
action.pluginBuilder,
|
||||||
|
name,
|
||||||
|
initialDataBytes,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
controller.close();
|
controller.close();
|
||||||
},
|
},
|
||||||
|
@ -17,9 +17,9 @@ import '../menu_app.dart';
|
|||||||
import 'add_button.dart';
|
import 'add_button.dart';
|
||||||
|
|
||||||
class MenuAppHeader extends StatelessWidget {
|
class MenuAppHeader extends StatelessWidget {
|
||||||
final ViewPB app;
|
final ViewPB parentView;
|
||||||
const MenuAppHeader(
|
const MenuAppHeader(
|
||||||
this.app, {
|
this.parentView, {
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -108,6 +108,7 @@ class MenuAppHeader extends StatelessWidget {
|
|||||||
return Tooltip(
|
return Tooltip(
|
||||||
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
|
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
|
||||||
child: AddButton(
|
child: AddButton(
|
||||||
|
parentViewId: parentView.id,
|
||||||
onSelected: (pluginBuilder, name, initialDataBytes, openAfterCreated) {
|
onSelected: (pluginBuilder, name, initialDataBytes, openAfterCreated) {
|
||||||
context.read<AppBloc>().add(
|
context.read<AppBloc>().add(
|
||||||
AppEvent.createView(
|
AppEvent.createView(
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
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/plugins/document/presentation/editor_plugins/migration/editor_migration.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/util/file_picker/file_picker_service.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:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
@ -21,6 +23,7 @@ typedef ImportCallback = void Function(
|
|||||||
);
|
);
|
||||||
|
|
||||||
Future<void> showImportPanel(
|
Future<void> showImportPanel(
|
||||||
|
String parentViewId,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
ImportCallback callback,
|
ImportCallback callback,
|
||||||
) async {
|
) async {
|
||||||
@ -33,7 +36,10 @@ Future<void> showImportPanel(
|
|||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
color: Theme.of(context).colorScheme.tertiary,
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
),
|
),
|
||||||
content: _ImportPanel(importCallback: callback),
|
content: _ImportPanel(
|
||||||
|
parentViewId: parentViewId,
|
||||||
|
importCallback: callback,
|
||||||
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
vertical: 10.0,
|
vertical: 10.0,
|
||||||
horizontal: 20.0,
|
horizontal: 20.0,
|
||||||
@ -112,9 +118,11 @@ enum ImportType {
|
|||||||
|
|
||||||
class _ImportPanel extends StatefulWidget {
|
class _ImportPanel extends StatefulWidget {
|
||||||
const _ImportPanel({
|
const _ImportPanel({
|
||||||
|
required this.parentViewId,
|
||||||
required this.importCallback,
|
required this.importCallback,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final String parentViewId;
|
||||||
final ImportCallback importCallback;
|
final ImportCallback importCallback;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -145,7 +153,7 @@ class _ImportPanelState extends State<_ImportPanel> {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await _importFile(e);
|
await _importFile(widget.parentViewId, e);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.of(context).pop();
|
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(
|
final result = await getIt<FilePickerService>().pickFiles(
|
||||||
allowMultiple: importType.allowMultiSelect,
|
allowMultiple: importType.allowMultiSelect,
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
@ -173,26 +181,45 @@ class _ImportPanelState extends State<_ImportPanel> {
|
|||||||
if (path == null) {
|
if (path == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final plainText = await File(path).readAsString();
|
final data = await File(path).readAsString();
|
||||||
Document? document;
|
final name = p.basenameWithoutExtension(path);
|
||||||
|
|
||||||
switch (importType) {
|
switch (importType) {
|
||||||
case ImportType.markdownOrText:
|
case ImportType.markdownOrText:
|
||||||
document = markdownToDocument(plainText);
|
|
||||||
break;
|
|
||||||
case ImportType.historyDocument:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false, 'Unsupported Type $importType');
|
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();
|
final builder = BoardPluginBuilder();
|
||||||
return AppBackendService()
|
return AppBackendService()
|
||||||
.createView(
|
.createView(
|
||||||
appId: app.id,
|
parentViewId: app.id,
|
||||||
name: "Test Board",
|
name: "Test Board",
|
||||||
layoutType: builder.layoutType!,
|
layoutType: builder.layoutType!,
|
||||||
)
|
)
|
||||||
|
@ -10,7 +10,7 @@ Future<GridTestContext> createTestFilterGrid(AppFlowyGridTest gridTest) async {
|
|||||||
final builder = GridPluginBuilder();
|
final builder = GridPluginBuilder();
|
||||||
final context = await AppBackendService()
|
final context = await AppBackendService()
|
||||||
.createView(
|
.createView(
|
||||||
appId: app.id,
|
parentViewId: app.id,
|
||||||
name: "Filter Grid",
|
name: "Filter Grid",
|
||||||
layoutType: builder.layoutType!,
|
layoutType: builder.layoutType!,
|
||||||
)
|
)
|
||||||
|
@ -172,7 +172,7 @@ class AppFlowyGridTest {
|
|||||||
final builder = GridPluginBuilder();
|
final builder = GridPluginBuilder();
|
||||||
final context = await AppBackendService()
|
final context = await AppBackendService()
|
||||||
.createView(
|
.createView(
|
||||||
appId: app.id,
|
parentViewId: app.id,
|
||||||
name: "Test Grid",
|
name: "Test Grid",
|
||||||
layoutType: builder.layoutType!,
|
layoutType: builder.layoutType!,
|
||||||
)
|
)
|
||||||
|
@ -36,7 +36,7 @@ export class AppBackendService {
|
|||||||
}) => {
|
}) => {
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const payload = CreateViewPayloadPB.fromObject({
|
const payload = CreateViewPayloadPB.fromObject({
|
||||||
belong_to_id: this.appId,
|
parent_view_id: this.appId,
|
||||||
name: params.name,
|
name: params.name,
|
||||||
desc: params.desc || '',
|
desc: params.desc || '',
|
||||||
layout: params.layoutType,
|
layout: params.layoutType,
|
||||||
|
@ -19,7 +19,7 @@ export class WorkspaceBackendService {
|
|||||||
|
|
||||||
createApp = async (params: { name: string; desc?: string }) => {
|
createApp = async (params: { name: string; desc?: string }) => {
|
||||||
const payload = CreateViewPayloadPB.fromObject({
|
const payload = CreateViewPayloadPB.fromObject({
|
||||||
belong_to_id: this.workspaceId,
|
parent_view_id: this.workspaceId,
|
||||||
name: params.name,
|
name: params.name,
|
||||||
desc: params.desc || '',
|
desc: params.desc || '',
|
||||||
layout: ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
|
@ -7,6 +7,7 @@ use appflowy_integrate::RocksCollabDB;
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
use flowy_database2::entities::DatabaseLayoutPB;
|
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::template::{make_default_board, make_default_calendar, make_default_grid};
|
||||||
use flowy_database2::DatabaseManager2;
|
use flowy_database2::DatabaseManager2;
|
||||||
use flowy_document2::document_data::DocumentDataWrapper;
|
use flowy_document2::document_data::DocumentDataWrapper;
|
||||||
@ -16,7 +17,7 @@ use flowy_error::FlowyError;
|
|||||||
use flowy_folder2::deps::{FolderCloudService, FolderUser};
|
use flowy_folder2::deps::{FolderCloudService, FolderUser};
|
||||||
use flowy_folder2::entities::ViewLayoutPB;
|
use flowy_folder2::entities::ViewLayoutPB;
|
||||||
use flowy_folder2::manager::Folder2Manager;
|
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_folder2::ViewLayout;
|
||||||
use flowy_user::services::UserSession;
|
use flowy_user::services::UserSession;
|
||||||
use lib_dispatch::prelude::ToBytes;
|
use lib_dispatch::prelude::ToBytes;
|
||||||
@ -33,29 +34,28 @@ impl Folder2DepsResolver {
|
|||||||
) -> Arc<Folder2Manager> {
|
) -> Arc<Folder2Manager> {
|
||||||
let user: Arc<dyn FolderUser> = Arc::new(FolderUserImpl(user_session.clone()));
|
let user: Arc<dyn FolderUser> = Arc::new(FolderUserImpl(user_session.clone()));
|
||||||
|
|
||||||
let view_processors =
|
let handlers = folder_operation_handlers(document_manager.clone(), database_manager.clone());
|
||||||
make_view_data_processor(document_manager.clone(), database_manager.clone());
|
|
||||||
Arc::new(
|
Arc::new(
|
||||||
Folder2Manager::new(user.clone(), collab_builder, view_processors, folder_cloud)
|
Folder2Manager::new(user.clone(), collab_builder, handlers, folder_cloud)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_view_data_processor(
|
fn folder_operation_handlers(
|
||||||
document_manager: Arc<DocumentManager>,
|
document_manager: Arc<DocumentManager>,
|
||||||
database_manager: Arc<DatabaseManager2>,
|
database_manager: Arc<DatabaseManager2>,
|
||||||
) -> ViewDataProcessorMap {
|
) -> FolderOperationHandlers {
|
||||||
let mut map: HashMap<ViewLayout, Arc<dyn ViewDataProcessor + Send + Sync>> = HashMap::new();
|
let mut map: HashMap<ViewLayout, Arc<dyn FolderOperationHandler + Send + Sync>> = HashMap::new();
|
||||||
|
|
||||||
let document_processor = Arc::new(DocumentViewDataProcessor(document_manager));
|
let document_folder_operation = Arc::new(DocumentFolderOperation(document_manager));
|
||||||
map.insert(ViewLayout::Document, document_processor);
|
map.insert(ViewLayout::Document, document_folder_operation);
|
||||||
|
|
||||||
let database_processor = Arc::new(DatabaseViewDataProcessor(database_manager));
|
let database_folder_operation = Arc::new(DatabaseFolderOperation(database_manager));
|
||||||
map.insert(ViewLayout::Board, database_processor.clone());
|
map.insert(ViewLayout::Board, database_folder_operation.clone());
|
||||||
map.insert(ViewLayout::Grid, database_processor.clone());
|
map.insert(ViewLayout::Grid, database_folder_operation.clone());
|
||||||
map.insert(ViewLayout::Calendar, database_processor);
|
map.insert(ViewLayout::Calendar, database_folder_operation);
|
||||||
Arc::new(map)
|
Arc::new(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,8 +80,8 @@ impl FolderUser for FolderUserImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DocumentViewDataProcessor(Arc<DocumentManager>);
|
struct DocumentFolderOperation(Arc<DocumentManager>);
|
||||||
impl ViewDataProcessor for DocumentViewDataProcessor {
|
impl FolderOperationHandler for DocumentFolderOperation {
|
||||||
/// Close the document view.
|
/// Close the document view.
|
||||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
@ -92,10 +92,7 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the view data.
|
fn duplicate_view(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
||||||
///
|
|
||||||
/// only use in the duplicate view.
|
|
||||||
fn get_view_data(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
|
||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
let view_id = view_id.to_string();
|
let view_id = view_id.to_string();
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
@ -106,27 +103,7 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a view with built-in data.
|
fn create_view_with_view_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(
|
|
||||||
&self,
|
&self,
|
||||||
_user_id: i64,
|
_user_id: i64,
|
||||||
view_id: &str,
|
view_id: &str,
|
||||||
@ -145,10 +122,55 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
|
|||||||
Ok(())
|
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>);
|
struct DatabaseFolderOperation(Arc<DatabaseManager2>);
|
||||||
impl ViewDataProcessor for DatabaseViewDataProcessor {
|
impl FolderOperationHandler for DatabaseFolderOperation {
|
||||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
|
||||||
let database_manager = self.0.clone();
|
let database_manager = self.0.clone();
|
||||||
let view_id = view_id.to_string();
|
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 database_manager = self.0.clone();
|
||||||
let view_id = view_id.to_owned();
|
let view_id = view_id.to_owned();
|
||||||
FutureResult::new(async move {
|
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.
|
/// Create a database view with duplicated data.
|
||||||
/// If the ext contains the {"database_id": "xx"}, then it will link
|
/// If the ext contains the {"database_id": "xx"}, then it will link
|
||||||
/// to the existing database.
|
/// to the existing database.
|
||||||
fn create_view_with_custom_data(
|
fn create_view_with_view_data(
|
||||||
&self,
|
&self,
|
||||||
_user_id: i64,
|
_user_id: i64,
|
||||||
view_id: &str,
|
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)]
|
#[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]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
mod share_entities;
|
|
||||||
mod type_option_entities;
|
mod type_option_entities;
|
||||||
|
|
||||||
pub use calendar_entities::*;
|
pub use calendar_entities::*;
|
||||||
@ -23,7 +22,6 @@ pub use filter_entities::*;
|
|||||||
pub use group_entities::*;
|
pub use group_entities::*;
|
||||||
pub use row_entities::*;
|
pub use row_entities::*;
|
||||||
pub use setting_entities::*;
|
pub use setting_entities::*;
|
||||||
pub use share_entities::*;
|
|
||||||
pub use sort_entities::*;
|
pub use sort_entities::*;
|
||||||
pub use type_option_entities::*;
|
pub use type_option_entities::*;
|
||||||
pub use view_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 collab_database::views::DatabaseLayout;
|
||||||
use lib_infra::util::timestamp;
|
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 lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||||
|
|
||||||
use crate::entities::*;
|
use crate::entities::*;
|
||||||
@ -17,7 +17,6 @@ use crate::services::field::{
|
|||||||
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
|
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
|
||||||
};
|
};
|
||||||
use crate::services::group::{GroupChangeset, GroupSettingChangeset};
|
use crate::services::group::{GroupChangeset, GroupSettingChangeset};
|
||||||
use crate::services::share::csv::CSVFormat;
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||||
pub(crate) async fn get_database_data_handler(
|
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),
|
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;
|
use crate::manager::DatabaseManager2;
|
||||||
|
|
||||||
pub fn init(database_manager: Arc<DatabaseManager2>) -> AFPlugin {
|
pub fn init(database_manager: Arc<DatabaseManager2>) -> AFPlugin {
|
||||||
let mut plugin = AFPlugin::new()
|
let plugin = AFPlugin::new()
|
||||||
.name(env!("CARGO_PKG_NAME"))
|
.name(env!("CARGO_PKG_NAME"))
|
||||||
.state(database_manager);
|
.state(database_manager);
|
||||||
plugin = plugin
|
plugin
|
||||||
.event(DatabaseEvent::GetDatabase, get_database_data_handler)
|
.event(DatabaseEvent::GetDatabase, get_database_data_handler)
|
||||||
.event(DatabaseEvent::GetDatabaseSetting, get_database_setting_handler)
|
.event(DatabaseEvent::GetDatabaseSetting, get_database_setting_handler)
|
||||||
.event(DatabaseEvent::UpdateDatabaseSetting, update_database_setting_handler)
|
.event(DatabaseEvent::UpdateDatabaseSetting, update_database_setting_handler)
|
||||||
@ -64,10 +64,7 @@ pub fn init(database_manager: Arc<DatabaseManager2>) -> AFPlugin {
|
|||||||
// Layout setting
|
// Layout setting
|
||||||
.event(DatabaseEvent::SetLayoutSetting, set_layout_setting_handler)
|
.event(DatabaseEvent::SetLayoutSetting, set_layout_setting_handler)
|
||||||
.event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler)
|
.event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler)
|
||||||
// import
|
// import
|
||||||
.event(DatabaseEvent::ImportCSV, import_data_handler);
|
|
||||||
|
|
||||||
plugin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [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)
|
/// [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")]
|
#[event(input = "MoveCalendarEventPB")]
|
||||||
MoveCalendarEvent = 125,
|
MoveCalendarEvent = 125,
|
||||||
|
|
||||||
#[event(input = "DatabaseImportPB")]
|
|
||||||
ImportCSV = 130,
|
|
||||||
}
|
}
|
||||||
|
@ -195,11 +195,17 @@ impl DatabaseManager2 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn import_csv(&self, content: String, format: CSVFormat) -> FlowyResult<ImportResult> {
|
pub async fn import_csv(
|
||||||
let params =
|
&self,
|
||||||
tokio::task::spawn_blocking(move || CSVImporter.import_csv_from_string(content, format))
|
view_id: String,
|
||||||
.await
|
content: String,
|
||||||
.map_err(internal_error)??;
|
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 {
|
let result = ImportResult {
|
||||||
database_id: params.database_id.clone(),
|
database_id: params.database_id.clone(),
|
||||||
view_id: params.view_id.clone(),
|
view_id: params.view_id.clone(),
|
||||||
@ -208,7 +214,12 @@ impl DatabaseManager2 {
|
|||||||
Ok(result)
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use crate::entities::FieldType;
|
|||||||
|
|
||||||
use crate::services::field::{default_type_option_data_from_type, CELL_DATA};
|
use crate::services::field::{default_type_option_data_from_type, CELL_DATA};
|
||||||
use crate::services::share::csv::CSVFormat;
|
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::fields::Field;
|
||||||
use collab_database::rows::{new_cell_builder, Cell, CreateRowParams};
|
use collab_database::rows::{new_cell_builder, Cell, CreateRowParams};
|
||||||
use collab_database::views::{CreateDatabaseParams, DatabaseLayout};
|
use collab_database::views::{CreateDatabaseParams, DatabaseLayout};
|
||||||
@ -15,6 +15,7 @@ pub struct CSVImporter;
|
|||||||
impl CSVImporter {
|
impl CSVImporter {
|
||||||
pub fn import_csv_from_file(
|
pub fn import_csv_from_file(
|
||||||
&self,
|
&self,
|
||||||
|
view_id: &str,
|
||||||
path: &str,
|
path: &str,
|
||||||
style: CSVFormat,
|
style: CSVFormat,
|
||||||
) -> FlowyResult<CreateDatabaseParams> {
|
) -> FlowyResult<CreateDatabaseParams> {
|
||||||
@ -22,17 +23,18 @@ impl CSVImporter {
|
|||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
file.read_to_string(&mut content)?;
|
file.read_to_string(&mut content)?;
|
||||||
let fields_with_rows = self.get_fields_and_rows(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)
|
Ok(database_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn import_csv_from_string(
|
pub fn import_csv_from_string(
|
||||||
&self,
|
&self,
|
||||||
|
view_id: String,
|
||||||
content: String,
|
content: String,
|
||||||
format: CSVFormat,
|
format: CSVFormat,
|
||||||
) -> FlowyResult<CreateDatabaseParams> {
|
) -> FlowyResult<CreateDatabaseParams> {
|
||||||
let fields_with_rows = self.get_fields_and_rows(content)?;
|
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)
|
Ok(database_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,11 +70,11 @@ impl CSVImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn database_from_fields_and_rows(
|
fn database_from_fields_and_rows(
|
||||||
|
view_id: &str,
|
||||||
fields_and_rows: FieldsRows,
|
fields_and_rows: FieldsRows,
|
||||||
format: &CSVFormat,
|
format: &CSVFormat,
|
||||||
) -> CreateDatabaseParams {
|
) -> CreateDatabaseParams {
|
||||||
let (fields, rows) = fields_and_rows.split();
|
let (fields, rows) = fields_and_rows.split();
|
||||||
let view_id = gen_database_view_id();
|
|
||||||
let database_id = gen_database_id();
|
let database_id = gen_database_id();
|
||||||
|
|
||||||
let fields = fields
|
let fields = fields
|
||||||
@ -125,7 +127,7 @@ fn database_from_fields_and_rows(
|
|||||||
|
|
||||||
CreateDatabaseParams {
|
CreateDatabaseParams {
|
||||||
database_id,
|
database_id,
|
||||||
view_id,
|
view_id: view_id.to_string(),
|
||||||
name: "".to_string(),
|
name: "".to_string(),
|
||||||
layout: DatabaseLayout::Grid,
|
layout: DatabaseLayout::Grid,
|
||||||
layout_settings: Default::default(),
|
layout_settings: Default::default(),
|
||||||
@ -167,6 +169,7 @@ pub struct ImportResult {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::services::share::csv::{CSVFormat, CSVImporter};
|
use crate::services::share::csv::{CSVFormat, CSVImporter};
|
||||||
|
use collab_database::database::gen_database_view_id;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_import_csv_from_str() {
|
fn test_import_csv_from_str() {
|
||||||
@ -176,7 +179,7 @@ mod tests {
|
|||||||
,,,,Yes,"#;
|
,,,,Yes,"#;
|
||||||
let importer = CSVImporter;
|
let importer = CSVImporter;
|
||||||
let result = importer
|
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();
|
.unwrap();
|
||||||
assert_eq!(result.created_rows.len(), 3);
|
assert_eq!(result.created_rows.len(), 3);
|
||||||
assert_eq!(result.fields.len(), 6);
|
assert_eq!(result.fields.len(), 6);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use collab_database::database::gen_database_view_id;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -262,7 +263,7 @@ impl DatabaseEditorTest {
|
|||||||
self
|
self
|
||||||
.sdk
|
.sdk
|
||||||
.database_manager
|
.database_manager
|
||||||
.import_csv(s, format)
|
.import_csv(gen_database_view_id(), s, format)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.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;
|
mod parser;
|
||||||
pub mod trash;
|
pub mod trash;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
|
|
||||||
|
pub use import::*;
|
||||||
pub use trash::*;
|
pub use trash::*;
|
||||||
pub use view::*;
|
pub use view::*;
|
||||||
pub use workspace::*;
|
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 app;
|
||||||
|
pub mod empty_str;
|
||||||
pub mod trash;
|
pub mod trash;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
|
@ -123,7 +123,7 @@ pub struct RepeatedViewIdPB {
|
|||||||
#[derive(Default, ProtoBuf)]
|
#[derive(Default, ProtoBuf)]
|
||||||
pub struct CreateViewPayloadPB {
|
pub struct CreateViewPayloadPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
pub belong_to_id: String,
|
pub parent_view_id: String,
|
||||||
|
|
||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -146,13 +146,13 @@ pub struct CreateViewPayloadPB {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CreateViewParams {
|
pub struct CreateViewParams {
|
||||||
pub belong_to_id: String,
|
pub parent_view_id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub desc: String,
|
pub desc: String,
|
||||||
pub layout: ViewLayoutPB,
|
pub layout: ViewLayoutPB,
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
pub initial_data: Vec<u8>,
|
pub initial_data: Vec<u8>,
|
||||||
pub ext: HashMap<String, String>,
|
pub meta: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryInto<CreateViewParams> for CreateViewPayloadPB {
|
impl TryInto<CreateViewParams> for CreateViewPayloadPB {
|
||||||
@ -160,17 +160,17 @@ impl TryInto<CreateViewParams> for CreateViewPayloadPB {
|
|||||||
|
|
||||||
fn try_into(self) -> Result<CreateViewParams, Self::Error> {
|
fn try_into(self) -> Result<CreateViewParams, Self::Error> {
|
||||||
let name = ViewName::parse(self.name)?.0;
|
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();
|
let view_id = gen_view_id();
|
||||||
|
|
||||||
Ok(CreateViewParams {
|
Ok(CreateViewParams {
|
||||||
belong_to_id,
|
parent_view_id: belong_to_id,
|
||||||
name,
|
name,
|
||||||
desc: self.desc,
|
desc: self.desc,
|
||||||
layout: self.layout,
|
layout: self.layout,
|
||||||
view_id,
|
view_id,
|
||||||
initial_data: self.initial_data,
|
initial_data: self.initial_data,
|
||||||
ext: self.ext,
|
meta: self.ext,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
view_pb_without_child_views, CreateViewParams, CreateViewPayloadPB, CreateWorkspaceParams,
|
view_pb_without_child_views, CreateViewParams, CreateViewPayloadPB, CreateWorkspaceParams,
|
||||||
CreateWorkspacePayloadPB, MoveFolderItemPayloadPB, MoveViewParams, RepeatedTrashIdPB,
|
CreateWorkspacePayloadPB, ImportPB, MoveFolderItemPayloadPB, MoveViewParams, RepeatedTrashIdPB,
|
||||||
RepeatedTrashPB, RepeatedViewIdPB, RepeatedViewPB, RepeatedWorkspacePB, TrashIdPB,
|
RepeatedTrashPB, RepeatedViewIdPB, RepeatedViewPB, RepeatedWorkspacePB, TrashIdPB,
|
||||||
UpdateViewParams, UpdateViewPayloadPB, ViewIdPB, ViewPB, WorkspaceIdPB, WorkspacePB,
|
UpdateViewParams, UpdateViewPayloadPB, ViewIdPB, ViewPB, WorkspaceIdPB, WorkspacePB,
|
||||||
WorkspaceSettingPB,
|
WorkspaceSettingPB,
|
||||||
};
|
};
|
||||||
use crate::manager::Folder2Manager;
|
use crate::manager::Folder2Manager;
|
||||||
|
|
||||||
|
use crate::share::ImportParams;
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::FlowyError;
|
||||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -203,3 +204,13 @@ pub(crate) async fn delete_all_trash_handler(
|
|||||||
folder.delete_all_trash().await;
|
folder.delete_all_trash().await;
|
||||||
Ok(())
|
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::DeleteTrash, delete_trash_handler)
|
||||||
.event(FolderEvent::RestoreAllTrash, restore_all_trash_handler)
|
.event(FolderEvent::RestoreAllTrash, restore_all_trash_handler)
|
||||||
.event(FolderEvent::DeleteAllTrash, delete_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)]
|
#[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
|
/// Create a new view in the corresponding app
|
||||||
#[event(input = "CreateViewPayloadPB", output = "ViewPB")]
|
#[event(input = "CreateViewPayloadPB", output = "ViewPB")]
|
||||||
CreateView = 201,
|
CreateView = 10,
|
||||||
|
|
||||||
/// Return the view info
|
/// Return the view info
|
||||||
#[event(input = "ViewIdPB", output = "ViewPB")]
|
#[event(input = "ViewIdPB", output = "ViewPB")]
|
||||||
ReadView = 202,
|
ReadView = 11,
|
||||||
|
|
||||||
/// Update the view's properties including the name,description, etc.
|
/// Update the view's properties including the name,description, etc.
|
||||||
#[event(input = "UpdateViewPayloadPB", output = "ViewPB")]
|
#[event(input = "UpdateViewPayloadPB", output = "ViewPB")]
|
||||||
UpdateView = 203,
|
UpdateView = 12,
|
||||||
|
|
||||||
/// Move the view to the trash folder
|
/// Move the view to the trash folder
|
||||||
#[event(input = "RepeatedViewIdPB")]
|
#[event(input = "RepeatedViewIdPB")]
|
||||||
DeleteView = 204,
|
DeleteView = 13,
|
||||||
|
|
||||||
/// Duplicate the view
|
/// Duplicate the view
|
||||||
#[event(input = "ViewPB")]
|
#[event(input = "ViewPB")]
|
||||||
DuplicateView = 205,
|
DuplicateView = 14,
|
||||||
|
|
||||||
/// Close and release the resources that are used by this view.
|
/// Close and release the resources that are used by this view.
|
||||||
/// It should get called when the 'View' page get destroy
|
/// It should get called when the 'View' page get destroy
|
||||||
#[event(input = "ViewIdPB")]
|
#[event(input = "ViewIdPB")]
|
||||||
CloseView = 206,
|
CloseView = 15,
|
||||||
|
|
||||||
#[event()]
|
#[event()]
|
||||||
CopyLink = 220,
|
CopyLink = 20,
|
||||||
|
|
||||||
/// Set the current visiting view
|
/// Set the current visiting view
|
||||||
#[event(input = "ViewIdPB")]
|
#[event(input = "ViewIdPB")]
|
||||||
SetLatestView = 221,
|
SetLatestView = 21,
|
||||||
|
|
||||||
/// Move the view or app to another place
|
/// Move the view or app to another place
|
||||||
#[event(input = "MoveFolderItemPayloadPB")]
|
#[event(input = "MoveFolderItemPayloadPB")]
|
||||||
MoveItem = 230,
|
MoveItem = 22,
|
||||||
|
|
||||||
/// Read the trash that was deleted by the user
|
/// Read the trash that was deleted by the user
|
||||||
#[event(output = "RepeatedTrashPB")]
|
#[event(output = "RepeatedTrashPB")]
|
||||||
ReadTrash = 300,
|
ReadTrash = 23,
|
||||||
|
|
||||||
/// Put back the trash to the origin folder
|
/// Put back the trash to the origin folder
|
||||||
#[event(input = "TrashIdPB")]
|
#[event(input = "TrashIdPB")]
|
||||||
PutbackTrash = 301,
|
PutbackTrash = 24,
|
||||||
|
|
||||||
/// Delete the trash from the disk
|
/// Delete the trash from the disk
|
||||||
#[event(input = "RepeatedTrashIdPB")]
|
#[event(input = "RepeatedTrashIdPB")]
|
||||||
DeleteTrash = 302,
|
DeleteTrash = 25,
|
||||||
|
|
||||||
/// Put back all the trash to its original folder
|
/// Put back all the trash to its original folder
|
||||||
#[event()]
|
#[event()]
|
||||||
RestoreAllTrash = 303,
|
RestoreAllTrash = 26,
|
||||||
|
|
||||||
/// Delete all the trash from the disk
|
/// Delete all the trash from the disk
|
||||||
#[event()]
|
#[event()]
|
||||||
DeleteAllTrash = 304,
|
DeleteAllTrash = 27,
|
||||||
|
|
||||||
|
#[event(input = "ImportPB")]
|
||||||
|
ImportData = 30,
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ mod user_default;
|
|||||||
pub mod view_ext;
|
pub mod view_ext;
|
||||||
|
|
||||||
pub mod deps;
|
pub mod deps;
|
||||||
|
mod share;
|
||||||
#[cfg(feature = "test_helper")]
|
#[cfg(feature = "test_helper")]
|
||||||
mod test_helper;
|
mod test_helper;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ use parking_lot::Mutex;
|
|||||||
use tracing::{event, Level};
|
use tracing::{event, Level};
|
||||||
|
|
||||||
use crate::deps::{FolderCloudService, FolderUser};
|
use crate::deps::{FolderCloudService, FolderUser};
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||||
use lib_infra::util::timestamp;
|
use lib_infra::util::timestamp;
|
||||||
|
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
@ -23,16 +24,15 @@ use crate::notification::{
|
|||||||
send_notification, send_workspace_notification, send_workspace_setting_notification,
|
send_notification, send_workspace_notification, send_workspace_setting_notification,
|
||||||
FolderNotification,
|
FolderNotification,
|
||||||
};
|
};
|
||||||
|
use crate::share::ImportParams;
|
||||||
use crate::user_default::DefaultFolderBuilder;
|
use crate::user_default::DefaultFolderBuilder;
|
||||||
use crate::view_ext::{
|
use crate::view_ext::{create_view, gen_view_id, FolderOperationHandler, FolderOperationHandlers};
|
||||||
gen_view_id, view_from_create_view_params, ViewDataProcessor, ViewDataProcessorMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Folder2Manager {
|
pub struct Folder2Manager {
|
||||||
folder: Folder,
|
folder: Folder,
|
||||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||||
user: Arc<dyn FolderUser>,
|
user: Arc<dyn FolderUser>,
|
||||||
view_processors: ViewDataProcessorMap,
|
operation_handlers: FolderOperationHandlers,
|
||||||
cloud_service: Arc<dyn FolderCloudService>,
|
cloud_service: Arc<dyn FolderCloudService>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ impl Folder2Manager {
|
|||||||
pub async fn new(
|
pub async fn new(
|
||||||
user: Arc<dyn FolderUser>,
|
user: Arc<dyn FolderUser>,
|
||||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||||
view_processors: ViewDataProcessorMap,
|
operation_handlers: FolderOperationHandlers,
|
||||||
cloud_service: Arc<dyn FolderCloudService>,
|
cloud_service: Arc<dyn FolderCloudService>,
|
||||||
) -> FlowyResult<Self> {
|
) -> FlowyResult<Self> {
|
||||||
let folder = Folder::default();
|
let folder = Folder::default();
|
||||||
@ -51,7 +51,7 @@ impl Folder2Manager {
|
|||||||
user,
|
user,
|
||||||
folder,
|
folder,
|
||||||
collab_builder,
|
collab_builder,
|
||||||
view_processors,
|
operation_handlers,
|
||||||
cloud_service,
|
cloud_service,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ impl Folder2Manager {
|
|||||||
let (folder_data, workspace_pb) = DefaultFolderBuilder::build(
|
let (folder_data, workspace_pb) = DefaultFolderBuilder::build(
|
||||||
self.user.user_id()?,
|
self.user.user_id()?,
|
||||||
workspace_id.to_string(),
|
workspace_id.to_string(),
|
||||||
&self.view_processors,
|
&self.operation_handlers,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
self.with_folder((), |folder| {
|
self.with_folder((), |folder| {
|
||||||
@ -187,14 +187,14 @@ impl Folder2Manager {
|
|||||||
|
|
||||||
pub async fn create_view_with_params(&self, params: CreateViewParams) -> FlowyResult<View> {
|
pub async fn create_view_with_params(&self, params: CreateViewParams) -> FlowyResult<View> {
|
||||||
let view_layout: ViewLayout = params.layout.clone().into();
|
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 user_id = self.user.user_id()?;
|
||||||
let ext = params.ext.clone();
|
let ext = params.meta.clone();
|
||||||
match params.initial_data.is_empty() {
|
match params.initial_data.is_empty() {
|
||||||
true => {
|
true => {
|
||||||
tracing::trace!("Create view with build-in data");
|
tracing::trace!("Create view with build-in data");
|
||||||
processor
|
handler
|
||||||
.create_view_with_built_in_data(
|
.create_built_in_view(
|
||||||
user_id,
|
user_id,
|
||||||
¶ms.view_id,
|
¶ms.view_id,
|
||||||
¶ms.name,
|
¶ms.name,
|
||||||
@ -205,8 +205,8 @@ impl Folder2Manager {
|
|||||||
},
|
},
|
||||||
false => {
|
false => {
|
||||||
tracing::trace!("Create view with view data");
|
tracing::trace!("Create view with view data");
|
||||||
processor
|
handler
|
||||||
.create_view_with_custom_data(
|
.create_view_with_view_data(
|
||||||
user_id,
|
user_id,
|
||||||
¶ms.view_id,
|
¶ms.view_id,
|
||||||
¶ms.name,
|
¶ms.name,
|
||||||
@ -217,7 +217,7 @@ impl Folder2Manager {
|
|||||||
.await?;
|
.await?;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
let view = view_from_create_view_params(params, view_layout);
|
let view = create_view(params, view_layout);
|
||||||
self.with_folder((), |folder| {
|
self.with_folder((), |folder| {
|
||||||
folder.insert_view(view.clone());
|
folder.insert_view(view.clone());
|
||||||
});
|
});
|
||||||
@ -233,12 +233,12 @@ impl Folder2Manager {
|
|||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
FlowyError::record_not_found().context("Can't find the view when closing the view")
|
FlowyError::record_not_found().context("Can't find the view when closing the view")
|
||||||
})?;
|
})?;
|
||||||
let processor = self.get_data_processor(&view.layout)?;
|
let handler = self.get_handler(&view.layout)?;
|
||||||
processor.close_view(view_id).await?;
|
handler.close_view(view_id).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_view_data(
|
pub async fn create_view_with_data(
|
||||||
&self,
|
&self,
|
||||||
view_id: &str,
|
view_id: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
@ -246,9 +246,9 @@ impl Folder2Manager {
|
|||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
) -> FlowyResult<()> {
|
) -> FlowyResult<()> {
|
||||||
let user_id = self.user.user_id()?;
|
let user_id = self.user.user_id()?;
|
||||||
let processor = self.get_data_processor(&view_layout)?;
|
let handler = self.get_handler(&view_layout)?;
|
||||||
processor
|
handler
|
||||||
.create_view_with_custom_data(
|
.create_view_with_view_data(
|
||||||
user_id,
|
user_id,
|
||||||
view_id,
|
view_id,
|
||||||
name,
|
name,
|
||||||
@ -367,20 +367,20 @@ impl Folder2Manager {
|
|||||||
.with_folder(None, |folder| folder.views.get_view(view_id))
|
.with_folder(None, |folder| folder.views.get_view(view_id))
|
||||||
.ok_or_else(|| FlowyError::record_not_found().context("Can't duplicate the view"))?;
|
.ok_or_else(|| FlowyError::record_not_found().context("Can't duplicate the view"))?;
|
||||||
|
|
||||||
let processor = self.get_data_processor(&view.layout)?;
|
let handler = self.get_handler(&view.layout)?;
|
||||||
let view_data = processor.get_view_data(&view.id).await?;
|
let view_data = handler.duplicate_view(&view.id).await?;
|
||||||
let mut ext = HashMap::new();
|
let mut ext = HashMap::new();
|
||||||
if let Some(database_id) = view.database_id {
|
if let Some(database_id) = view.database_id {
|
||||||
ext.insert("database_id".to_string(), database_id);
|
ext.insert("database_id".to_string(), database_id);
|
||||||
}
|
}
|
||||||
let duplicate_params = CreateViewParams {
|
let duplicate_params = CreateViewParams {
|
||||||
belong_to_id: view.bid.clone(),
|
parent_view_id: view.bid.clone(),
|
||||||
name: format!("{} (copy)", &view.name),
|
name: format!("{} (copy)", &view.name),
|
||||||
desc: view.desc,
|
desc: view.desc,
|
||||||
layout: view.layout.into(),
|
layout: view.layout.into(),
|
||||||
initial_data: view_data.to_vec(),
|
initial_data: view_data.to_vec(),
|
||||||
view_id: gen_view_id(),
|
view_id: gen_view_id(),
|
||||||
ext,
|
meta: ext,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = self.create_view_with_params(duplicate_params).await?;
|
let _ = self.create_view_with_params(duplicate_params).await?;
|
||||||
@ -451,11 +451,52 @@ impl Folder2Manager {
|
|||||||
.send();
|
.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,
|
&self,
|
||||||
view_layout: &ViewLayout,
|
view_layout: &ViewLayout,
|
||||||
) -> FlowyResult<Arc<dyn ViewDataProcessor + Send + Sync>> {
|
) -> FlowyResult<Arc<dyn FolderOperationHandler + Send + Sync>> {
|
||||||
match self.view_processors.get(view_layout) {
|
match self.operation_handlers.get(view_layout) {
|
||||||
None => Err(FlowyError::internal().context(format!(
|
None => Err(FlowyError::internal().context(format!(
|
||||||
"Get data processor failed. Unknown layout type: {:?}",
|
"Get data processor failed. Unknown layout type: {:?}",
|
||||||
view_layout
|
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 {
|
) -> String {
|
||||||
let view_id = gen_view_id();
|
let view_id = gen_view_id();
|
||||||
let params = CreateViewParams {
|
let params = CreateViewParams {
|
||||||
belong_to_id: app_id.to_string(),
|
parent_view_id: app_id.to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
desc: "".to_string(),
|
desc: "".to_string(),
|
||||||
layout,
|
layout,
|
||||||
view_id: view_id.clone(),
|
view_id: view_id.clone(),
|
||||||
initial_data: vec![],
|
initial_data: vec![],
|
||||||
ext,
|
meta: ext,
|
||||||
};
|
};
|
||||||
self.create_view_with_params(params).await.unwrap();
|
self.create_view_with_params(params).await.unwrap();
|
||||||
view_id
|
view_id
|
||||||
|
@ -5,14 +5,14 @@ use collab_folder::core::{Belonging, Belongings, FolderData, View, ViewLayout, W
|
|||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
|
|
||||||
use crate::entities::{view_pb_with_child_views, WorkspacePB};
|
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();
|
pub struct DefaultFolderBuilder();
|
||||||
impl DefaultFolderBuilder {
|
impl DefaultFolderBuilder {
|
||||||
pub async fn build(
|
pub async fn build(
|
||||||
uid: i64,
|
uid: i64,
|
||||||
workspace_id: String,
|
workspace_id: String,
|
||||||
view_processors: &ViewDataProcessorMap,
|
handlers: &FolderOperationHandlers,
|
||||||
) -> (FolderData, WorkspacePB) {
|
) -> (FolderData, WorkspacePB) {
|
||||||
let time = Utc::now().timestamp();
|
let time = Utc::now().timestamp();
|
||||||
let view_id = gen_view_id();
|
let view_id = gen_view_id();
|
||||||
@ -33,9 +33,9 @@ impl DefaultFolderBuilder {
|
|||||||
// create the document
|
// create the document
|
||||||
// TODO: use the initial data from the view processor
|
// TODO: use the initial data from the view processor
|
||||||
// let data = initial_read_me().into_bytes();
|
// let data = initial_read_me().into_bytes();
|
||||||
let processor = view_processors.get(&child_view_layout).unwrap();
|
let handler = handlers.get(&child_view_layout).unwrap();
|
||||||
processor
|
handler
|
||||||
.create_view_with_built_in_data(
|
.create_built_in_view(
|
||||||
uid,
|
uid,
|
||||||
&child_view.id,
|
&child_view.id,
|
||||||
&child_view.name,
|
&child_view.name,
|
||||||
|
@ -8,29 +8,22 @@ use lib_infra::util::timestamp;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
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
|
/// Closes the view and releases the resources that this view has in
|
||||||
/// the backend
|
/// the backend
|
||||||
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>;
|
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>;
|
||||||
|
|
||||||
/// Gets the data of the this view.
|
/// Returns the [ViewData] that can be used to create the same view.
|
||||||
/// For example, the data can be used to duplicate the view.
|
fn duplicate_view(&self, view_id: &str) -> FutureResult<ViewData, FlowyError>;
|
||||||
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>;
|
|
||||||
|
|
||||||
/// Create a view with custom data
|
/// Create a view with custom data
|
||||||
fn create_view_with_custom_data(
|
fn create_view_with_view_data(
|
||||||
&self,
|
&self,
|
||||||
user_id: i64,
|
user_id: i64,
|
||||||
view_id: &str,
|
view_id: &str,
|
||||||
@ -39,9 +32,38 @@ pub trait ViewDataProcessor {
|
|||||||
layout: ViewLayout,
|
layout: ViewLayout,
|
||||||
ext: HashMap<String, String>,
|
ext: HashMap<String, String>,
|
||||||
) -> FutureResult<(), FlowyError>;
|
) -> 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 {
|
impl From<ViewLayoutPB> for ViewLayout {
|
||||||
fn from(pb: ViewLayoutPB) -> Self {
|
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();
|
let time = timestamp();
|
||||||
View {
|
View {
|
||||||
id: params.view_id,
|
id: params.view_id,
|
||||||
bid: params.belong_to_id,
|
bid: params.parent_view_id,
|
||||||
name: params.name,
|
name: params.name,
|
||||||
desc: params.desc,
|
desc: params.desc,
|
||||||
belongings: Default::default(),
|
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 {
|
pub async fn create_app(sdk: &FlowyCoreTest, workspace_id: &str, name: &str, desc: &str) -> ViewPB {
|
||||||
let create_view_request = CreateViewPayloadPB {
|
let create_view_request = CreateViewPayloadPB {
|
||||||
belong_to_id: workspace_id.to_owned(),
|
parent_view_id: workspace_id.to_owned(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
desc: desc.to_string(),
|
desc: desc.to_string(),
|
||||||
thumbnail: None,
|
thumbnail: None,
|
||||||
@ -237,7 +237,7 @@ pub async fn create_view(
|
|||||||
layout: ViewLayout,
|
layout: ViewLayout,
|
||||||
) -> ViewPB {
|
) -> ViewPB {
|
||||||
let request = CreateViewPayloadPB {
|
let request = CreateViewPayloadPB {
|
||||||
belong_to_id: app_id.to_string(),
|
parent_view_id: app_id.to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
desc: desc.to_string(),
|
desc: desc.to_string(),
|
||||||
thumbnail: None,
|
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 {
|
async fn create_app(sdk: &FlowyCoreTest, name: &str, desc: &str, workspace_id: &str) -> ViewPB {
|
||||||
let create_app_request = CreateViewPayloadPB {
|
let create_app_request = CreateViewPayloadPB {
|
||||||
belong_to_id: workspace_id.to_owned(),
|
parent_view_id: workspace_id.to_owned(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
desc: desc.to_string(),
|
desc: desc.to_string(),
|
||||||
thumbnail: None,
|
thumbnail: None,
|
||||||
@ -93,7 +93,7 @@ async fn create_view(
|
|||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
) -> ViewPB {
|
) -> ViewPB {
|
||||||
let payload = CreateViewPayloadPB {
|
let payload = CreateViewPayloadPB {
|
||||||
belong_to_id: app_id.to_string(),
|
parent_view_id: app_id.to_string(),
|
||||||
name: "View A".to_string(),
|
name: "View A".to_string(),
|
||||||
desc: "".to_string(),
|
desc: "".to_string(),
|
||||||
thumbnail: Some("http://1.png".to_string()),
|
thumbnail: Some("http://1.png".to_string()),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user