Skip to content

Commit

Permalink
feat: support exporting json from database and document
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasXu0 committed Aug 28, 2024
1 parent 5e785d1 commit fd06a1d
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 3 deletions.
49 changes: 49 additions & 0 deletions frontend/appflowy_flutter/lib/plugins/shared/share/export_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

Expand Down Expand Up @@ -51,6 +52,14 @@ class ExportTab extends StatelessWidget {
svg: FlowySvgs.duplicate_s,
onTap: () => _exportToClipboard(context),
),
if (kDebugMode) ...[
const VSpace(10),
_ExportButton(
title: 'JSON (Debug Mode)',
svg: FlowySvgs.duplicate_s,
onTap: () => _exportJSON(context),
),
],
],
);
}
Expand All @@ -64,6 +73,14 @@ class ExportTab extends StatelessWidget {
svg: FlowySvgs.database_layout_m,
onTap: () => _exportCSV(context),
),
if (kDebugMode) ...[
const VSpace(10),
_ExportButton(
title: 'Raw Database Data (Debug Mode)',
svg: FlowySvgs.duplicate_s,
onTap: () => _exportRawDatabaseData(context),
),
],
],
);
}
Expand Down Expand Up @@ -100,6 +117,22 @@ class ExportTab extends StatelessWidget {
}
}

Future<void> _exportJSON(BuildContext context) async {
final viewName = context.read<ShareBloc>().state.viewName;
final exportPath = await getIt<FilePickerService>().saveFile(
dialogTitle: '',
fileName: '${viewName.toFileName()}.json',
);
if (context.mounted && exportPath != null) {
context.read<ShareBloc>().add(
ShareEvent.share(
ShareType.json,
exportPath,
),
);
}
}

Future<void> _exportCSV(BuildContext context) async {
final viewName = context.read<ShareBloc>().state.viewName;
final exportPath = await getIt<FilePickerService>().saveFile(
Expand All @@ -116,6 +149,22 @@ class ExportTab extends StatelessWidget {
}
}

Future<void> _exportRawDatabaseData(BuildContext context) async {
final viewName = context.read<ShareBloc>().state.viewName;
final exportPath = await getIt<FilePickerService>().saveFile(
dialogTitle: '',
fileName: '${viewName.toFileName()}.json',
);
if (context.mounted && exportPath != null) {
context.read<ShareBloc>().add(
ShareEvent.share(
ShareType.rawDatabaseData,
exportPath,
),
);
}
}

Future<void> _exportToClipboard(BuildContext context) async {
final documentExporter = DocumentExporter(context.read<ShareBloc>().view);
final result = await documentExporter.export(DocumentExportType.markdown);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ class ShareBloc extends Bloc<ShareEvent, ShareState> {
(s) => FlowyResult.success(s.data),
(f) => FlowyResult.failure(f),
);
} else if (type == ShareType.rawDatabaseData) {
final exportResult = await BackendExportService.exportDatabaseAsRawData(
view.id,
);
result = exportResult.fold(
(s) => FlowyResult.success(s.data),
(f) => FlowyResult.failure(f),
);
} else {
result = await documentExporter.export(type.documentExportType);
}
Expand All @@ -189,6 +197,8 @@ class ShareBloc extends Bloc<ShareEvent, ShareState> {
case ShareType.markdown:
case ShareType.html:
case ShareType.csv:
case ShareType.json:
case ShareType.rawDatabaseData:
File(path).writeAsStringSync(s);
return FlowyResult.success(type);
default:
Expand All @@ -208,9 +218,11 @@ enum ShareType {
html,
text,
link,
json,

// only available in database
csv;
csv,
rawDatabaseData;

static List<ShareType> get unimplemented => [link];

Expand All @@ -222,10 +234,16 @@ enum ShareType {
return DocumentExportType.html;
case ShareType.text:
return DocumentExportType.text;
case ShareType.json:
return DocumentExportType.json;
case ShareType.csv:
throw UnsupportedError('DocumentShareType.csv is not supported');
case ShareType.link:
throw UnsupportedError('DocumentShareType.link is not supported');
case ShareType.rawDatabaseData:
throw UnsupportedError(
'DocumentShareType.rawDatabaseData is not supported',
);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:appflowy_backend/log.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../startup.dart';

Expand All @@ -17,6 +19,23 @@ class PlatformErrorCatcherTask extends LaunchTask {
return true;
};
}

ErrorWidget.builder = (details) {
if (kDebugMode) {
return Container(
width: double.infinity,
height: 30,
color: Colors.red,
child: FlowyText(
'ERROR: ${details.exceptionAsString()}',
color: Colors.white,
),
);
}

// hide the error widget in release mode
return const SizedBox.shrink();
};
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,12 @@ class BackendExportService {
final payload = DatabaseViewIdPB.create()..value = viewId;
return DatabaseEventExportCSV(payload).send();
}

static Future<FlowyResult<DatabaseExportDataPB, FlowyError>>
exportDatabaseAsRawData(
String viewId,
) async {
final payload = DatabaseViewIdPB.create()..value = viewId;
return DatabaseEventExportRawDatabaseData(payload).send();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
}

async fn duplicate_view(&self, view_id: &str) -> Result<Bytes, FlowyError> {
let delta_bytes = self.0.duplicate_database(view_id).await?;
let delta_bytes = self.0.get_database_json_bytes(view_id).await?;
Ok(Bytes::from(delta_bytes))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
pub enum DatabaseExportDataType {
#[default]
CSV = 0,

// DatabaseData
RawDatabaseData = 1,
}

#[derive(Debug, ProtoBuf, Default, Clone)]
Expand Down
14 changes: 14 additions & 0 deletions frontend/rust-lib/flowy-database2/src/event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,20 @@ pub(crate) async fn export_csv_handler(
})
}

#[tracing::instrument(level = "debug", skip_all, err)]
pub(crate) async fn export_raw_database_data_handler(
data: AFPluginData<DatabaseViewIdPB>,
manager: AFPluginState<Weak<DatabaseManager>>,
) -> DataResult<DatabaseExportDataPB, FlowyError> {
let manager = upgrade_manager(manager)?;
let view_id = data.into_inner().value;
let data = manager.get_database_json_string(&view_id).await?;
data_result_ok(DatabaseExportDataPB {
export_type: DatabaseExportDataType::RawDatabaseData,
data,
})
}

#[tracing::instrument(level = "debug", skip_all, err)]
pub(crate) async fn get_snapshots_handler(
data: AFPluginData<DatabaseViewIdPB>,
Expand Down
4 changes: 4 additions & 0 deletions frontend/rust-lib/flowy-database2/src/event_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
.event(DatabaseEvent::CreateDatabaseView, create_database_view)
// Export
.event(DatabaseEvent::ExportCSV, export_csv_handler)
.event(DatabaseEvent::ExportRawDatabaseData, export_raw_database_data_handler)
.event(DatabaseEvent::GetDatabaseSnapshots, get_snapshots_handler)
// Field settings
.event(DatabaseEvent::GetFieldSettings, get_field_settings_handler)
Expand Down Expand Up @@ -385,4 +386,7 @@ pub enum DatabaseEvent {

#[event(input = "DatabaseViewIdPB", output = "RepeatedRowMetaPB")]
GetAllRows = 177,

#[event(input = "DatabaseViewIdPB", output = "DatabaseExportDataPB")]
ExportRawDatabaseData = 178,
}
10 changes: 9 additions & 1 deletion frontend/rust-lib/flowy-database2/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,14 +340,22 @@ impl DatabaseManager {
Ok(())
}

pub async fn duplicate_database(&self, view_id: &str) -> FlowyResult<Vec<u8>> {
pub async fn get_database_json_bytes(&self, view_id: &str) -> FlowyResult<Vec<u8>> {
let lock = self.workspace_database()?;
let wdb = lock.read().await;
let data = wdb.get_database_data(view_id).await?;
let json_bytes = data.to_json_bytes()?;
Ok(json_bytes)
}

pub async fn get_database_json_string(&self, view_id: &str) -> FlowyResult<String> {
let lock = self.workspace_database()?;
let wdb = lock.read().await;
let data = wdb.get_database_data(view_id).await?;
let json_string = serde_json::to_string(&data)?;
Ok(json_string)
}

/// Create a new database with the given data that can be deserialized to [DatabaseData].
#[tracing::instrument(level = "trace", skip_all, err)]
pub async fn create_database_with_database_data(
Expand Down

0 comments on commit fd06a1d

Please sign in to comment.