chore: share database via csv (#3285)

* chore: share database via csv

* fix: flutter test
This commit is contained in:
Nathan.fooo 2023-08-28 23:20:56 +08:00 committed by GitHub
parent 1205f0ebf7
commit 0806436c19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 288 additions and 27 deletions

View File

@ -0,0 +1,66 @@
import 'dart:io';
import 'package:appflowy/workspace/application/settings/share/export_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:dartz/dartz.dart';
part 'share_bloc.freezed.dart';
class DatabaseShareBloc extends Bloc<DatabaseShareEvent, DatabaseShareState> {
DatabaseShareBloc({
required this.view,
}) : super(const DatabaseShareState.initial()) {
on<ShareCSV>(_onShareCSV);
}
final ViewPB view;
Future<void> _onShareCSV(
ShareCSV event,
Emitter<DatabaseShareState> emit,
) async {
emit(const DatabaseShareState.loading());
final result = await BackendExportService.exportDatabaseAsCSV(view.id);
result.fold(
(l) => _saveCSVToPath(l.data, event.path),
(r) => Log.error(r),
);
emit(
DatabaseShareState.finish(
result.fold(
(l) {
_saveCSVToPath(l.data, event.path);
return left(unit);
},
(r) => right(r),
),
),
);
}
ExportDataPB _saveCSVToPath(String markdown, String path) {
File(path).writeAsStringSync(markdown);
return ExportDataPB()
..data = markdown
..exportType = ExportType.Markdown;
}
}
@freezed
class DatabaseShareEvent with _$DatabaseShareEvent {
const factory DatabaseShareEvent.shareCSV(String path) = ShareCSV;
}
@freezed
class DatabaseShareState with _$DatabaseShareState {
const factory DatabaseShareState.initial() = _Initial;
const factory DatabaseShareState.loading() = _Loading;
const factory DatabaseShareState.finish(
Either<Unit, FlowyError> successOrFail,
) = _Finish;
}

View File

@ -1,4 +1,5 @@
import 'package:appflowy/plugins/database_view/application/tar_bar_bloc.dart';
import 'package:appflowy/plugins/database_view/widgets/share_button.dart';
import 'package:appflowy/plugins/util.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
@ -212,4 +213,16 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
@override
List<NavigationItem> get navigationItems => [this];
@override
Widget? get rightBarItem {
return Row(
children: [
DatabaseShareButton(
key: ValueKey(notifier.view.id),
view: notifier.view,
),
],
);
}
}

View File

@ -0,0 +1,154 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/share_bloc.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/view/view_listener.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DatabaseShareButton extends StatelessWidget {
const DatabaseShareButton({
super.key,
required this.view,
});
final ViewPB view;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DatabaseShareBloc(view: view),
child: BlocListener<DatabaseShareBloc, DatabaseShareState>(
listener: (context, state) {
state.mapOrNull(
finish: (state) {
state.successOrFail.fold(
(data) => _handleExportData(context),
_handleExportError,
);
},
);
},
child: BlocBuilder<DatabaseShareBloc, DatabaseShareState>(
builder: (context, state) => ConstrainedBox(
constraints: const BoxConstraints.expand(
height: 30,
width: 100,
),
child: DatabaseShareActionList(view: view),
),
),
),
);
}
void _handleExportData(BuildContext context) {
showSnackBarMessage(
context,
LocaleKeys.settings_files_exportFileSuccess.tr(),
);
}
void _handleExportError(FlowyError error) {
showMessageToast(error.msg);
}
}
class DatabaseShareActionList extends StatefulWidget {
const DatabaseShareActionList({
super.key,
required this.view,
});
final ViewPB view;
@override
State<DatabaseShareActionList> createState() =>
DatabaseShareActionListState();
}
@visibleForTesting
class DatabaseShareActionListState extends State<DatabaseShareActionList> {
late String name;
late final ViewListener viewListener = ViewListener(viewId: widget.view.id);
@override
void initState() {
super.initState();
listenOnViewUpdated();
}
@override
void dispose() {
viewListener.stop();
super.dispose();
}
@override
Widget build(BuildContext context) {
final databaseShareBloc = context.read<DatabaseShareBloc>();
return PopoverActionList<ShareActionWrapper>(
direction: PopoverDirection.bottomWithCenterAligned,
offset: const Offset(0, 8),
actions: ShareAction.values
.map((action) => ShareActionWrapper(action))
.toList(),
buildChild: (controller) {
return RoundedTextButton(
title: LocaleKeys.shareAction_buttonText.tr(),
onPressed: () => controller.show(),
);
},
onSelected: (action, controller) async {
switch (action.inner) {
case ShareAction.csv:
final exportPath = await getIt<FilePickerService>().saveFile(
dialogTitle: '',
fileName: '${Uri.encodeComponent(name)}.csv',
);
if (exportPath != null) {
databaseShareBloc.add(DatabaseShareEvent.shareCSV(exportPath));
}
break;
}
controller.close();
},
);
}
void listenOnViewUpdated() {
name = widget.view.name;
viewListener.start(
onViewUpdated: (view) {
name = view.name;
},
);
}
}
enum ShareAction {
csv,
}
class ShareActionWrapper extends ActionCell {
final ShareAction inner;
ShareActionWrapper(this.inner);
Widget? icon(Color iconColor) => null;
@override
String get name {
switch (inner) {
case ShareAction.csv:
return LocaleKeys.shareAction_csv.tr();
}
}
}

View File

@ -140,7 +140,7 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "appflowy-integrate"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"anyhow",
"collab",
@ -728,7 +728,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"anyhow",
"bytes",
@ -746,7 +746,7 @@ dependencies = [
[[package]]
name = "collab-client-ws"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"bytes",
"collab-sync",
@ -764,7 +764,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"anyhow",
"async-trait",
@ -781,6 +781,8 @@ dependencies = [
"serde",
"serde_json",
"serde_repr",
"strum",
"strum_macros 0.25.2",
"thiserror",
"tokio",
"tokio-stream",
@ -791,7 +793,7 @@ dependencies = [
[[package]]
name = "collab-define"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"uuid",
]
@ -799,7 +801,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"proc-macro2",
"quote",
@ -811,7 +813,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"anyhow",
"collab",
@ -830,7 +832,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"anyhow",
"chrono",
@ -850,7 +852,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"bincode",
"chrono",
@ -870,7 +872,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"anyhow",
"async-trait",
@ -899,7 +901,7 @@ dependencies = [
[[package]]
name = "collab-sync"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"bytes",
"collab",
@ -921,7 +923,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=1b297c#1b297c2ed75aa33b964f0da546d771b00805be62"
dependencies = [
"anyhow",
"collab",
@ -1544,7 +1546,7 @@ dependencies = [
"flowy-sqlite",
"lib-dispatch",
"protobuf",
"strum_macros",
"strum_macros 0.21.1",
]
[[package]]
@ -1629,7 +1631,7 @@ dependencies = [
"serde_json",
"serde_repr",
"strum",
"strum_macros",
"strum_macros 0.25.2",
"tokio",
"tracing",
"url",
@ -1683,7 +1685,7 @@ dependencies = [
"protobuf",
"serde",
"serde_json",
"strum_macros",
"strum_macros 0.21.1",
"tokio",
"tokio-stream",
"tracing",
@ -1756,7 +1758,7 @@ dependencies = [
"nanoid",
"parking_lot 0.12.1",
"protobuf",
"strum_macros",
"strum_macros 0.21.1",
"tokio",
"tokio-stream",
"tracing",
@ -1868,11 +1870,13 @@ dependencies = [
name = "flowy-user"
version = "0.1.0"
dependencies = [
"anyhow",
"appflowy-integrate",
"base64 0.21.2",
"bytes",
"chrono",
"collab",
"collab-database",
"collab-document",
"collab-folder",
"collab-user",
@ -1883,6 +1887,7 @@ dependencies = [
"flowy-derive",
"flowy-encrypt",
"flowy-error",
"flowy-folder-deps",
"flowy-notification",
"flowy-server-config",
"flowy-sqlite",
@ -1897,7 +1902,7 @@ dependencies = [
"serde",
"serde_json",
"serde_repr",
"strum_macros",
"strum_macros 0.21.1",
"tokio",
"tracing",
"unicode-segmentation",
@ -3464,6 +3469,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "111.27.0+1.1.1v"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.90"
@ -3472,6 +3486,7 @@ checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
@ -5041,9 +5056,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.21.0"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
[[package]]
name = "strum_macros"
@ -5057,6 +5072,19 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "strum_macros"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.22",
]
[[package]]
name = "subtle"
version = "2.5.0"

View File

@ -55,6 +55,7 @@
"buttonText": "Share",
"workInProgress": "Coming soon",
"markdown": "Markdown",
"csv": "CSV",
"copyLink": "Copy Link"
},
"moreAction": {

View File

@ -64,15 +64,14 @@ impl DocumentManager {
data: Option<DocumentData>,
) -> FlowyResult<Arc<MutexDocument>> {
tracing::trace!("create a document: {:?}", doc_id);
let collab = self.collab_for_document(uid, doc_id, vec![])?;
match self.get_document(doc_id).await {
Ok(document) => Ok(document),
Err(_) => {
let data = data.unwrap_or_else(default_document_data);
let document = Arc::new(MutexDocument::create_with_data(collab, data)?);
Ok(document)
},
if self.is_doc_exist(doc_id).unwrap_or(false) {
self.get_document(doc_id).await
} else {
let collab = self.collab_for_document(uid, doc_id, vec![])?;
let data = data.unwrap_or_else(default_document_data);
let document = Arc::new(MutexDocument::create_with_data(collab, data)?);
Ok(document)
}
}