mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #403 from AppFlowy-IO/refactor_document_data_layer
Refactor document data layer
This commit is contained in:
commit
e7033aa6e8
77
frontend/app_flowy/lib/plugin/plugin.dart
Normal file
77
frontend/app_flowy/lib/plugin/plugin.dart
Normal file
@ -0,0 +1,77 @@
|
||||
library flowy_plugin;
|
||||
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
export "./src/sandbox.dart";
|
||||
|
||||
typedef PluginType = int;
|
||||
|
||||
typedef PluginDataType = ViewDataType;
|
||||
|
||||
typedef PluginId = String;
|
||||
|
||||
abstract class Plugin {
|
||||
PluginId get pluginId;
|
||||
|
||||
PluginDisplay get pluginDisplay;
|
||||
|
||||
PluginType get pluginType;
|
||||
|
||||
ChangeNotifier? get displayNotifier => null;
|
||||
|
||||
void dispose();
|
||||
}
|
||||
|
||||
abstract class PluginBuilder {
|
||||
Plugin build(dynamic data);
|
||||
|
||||
String get menuName;
|
||||
|
||||
PluginType get pluginType;
|
||||
|
||||
ViewDataType get dataType => ViewDataType.PlainText;
|
||||
}
|
||||
|
||||
abstract class PluginConfig {
|
||||
bool get creatable => true;
|
||||
}
|
||||
|
||||
abstract class PluginDisplay with NavigationItem {
|
||||
@override
|
||||
Widget get leftBarItem;
|
||||
|
||||
@override
|
||||
Widget? get rightBarItem;
|
||||
|
||||
List<NavigationItem> get navigationItems;
|
||||
|
||||
Widget buildWidget();
|
||||
}
|
||||
|
||||
void registerPlugin({required PluginBuilder builder, PluginConfig? config}) {
|
||||
getIt<PluginSandbox>().registerPlugin(builder.pluginType, builder, config: config);
|
||||
}
|
||||
|
||||
Plugin makePlugin({required PluginType pluginType, dynamic data}) {
|
||||
final plugin = getIt<PluginSandbox>().buildPlugin(pluginType, data);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
List<PluginBuilder> pluginBuilders() {
|
||||
final pluginBuilders = getIt<PluginSandbox>().builders;
|
||||
final pluginConfigs = getIt<PluginSandbox>().pluginConfigs;
|
||||
return pluginBuilders.where(
|
||||
(builder) {
|
||||
final config = pluginConfigs[builder.pluginType]?.creatable;
|
||||
return config ?? true;
|
||||
},
|
||||
).toList();
|
||||
}
|
||||
|
||||
enum FlowyPluginException {
|
||||
invalidData,
|
||||
}
|
1
frontend/app_flowy/lib/plugin/src/runner.dart
Normal file
1
frontend/app_flowy/lib/plugin/src/runner.dart
Normal file
@ -0,0 +1 @@
|
||||
class PluginRunner {}
|
46
frontend/app_flowy/lib/plugin/src/sandbox.dart
Normal file
46
frontend/app_flowy/lib/plugin/src/sandbox.dart
Normal file
@ -0,0 +1,46 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../plugin.dart';
|
||||
import 'runner.dart';
|
||||
|
||||
class PluginSandbox {
|
||||
final LinkedHashMap<PluginType, PluginBuilder> _pluginBuilders = LinkedHashMap();
|
||||
final Map<PluginType, PluginConfig> _pluginConfigs = <PluginType, PluginConfig>{};
|
||||
late PluginRunner pluginRunner;
|
||||
|
||||
PluginSandbox() {
|
||||
pluginRunner = PluginRunner();
|
||||
}
|
||||
|
||||
int indexOf(PluginType pluginType) {
|
||||
final index = _pluginBuilders.keys.toList().indexWhere((ty) => ty == pluginType);
|
||||
if (index == -1) {
|
||||
throw PlatformException(code: '-1', message: "Can't find the flowy plugin type: $pluginType");
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
Plugin buildPlugin(PluginType pluginType, dynamic data) {
|
||||
final plugin = _pluginBuilders[pluginType]!.build(data);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
void registerPlugin(PluginType pluginType, PluginBuilder builder, {PluginConfig? config}) {
|
||||
if (_pluginBuilders.containsKey(pluginType)) {
|
||||
throw PlatformException(code: '-1', message: "$pluginType was registered before");
|
||||
}
|
||||
_pluginBuilders[pluginType] = builder;
|
||||
|
||||
if (config != null) {
|
||||
_pluginConfigs[pluginType] = config;
|
||||
}
|
||||
}
|
||||
|
||||
List<int> get supportPluginTypes => _pluginBuilders.keys.toList();
|
||||
|
||||
List<PluginBuilder> get builders => _pluginBuilders.values.toList();
|
||||
|
||||
Map<PluginType, PluginConfig> get pluginConfigs => _pluginConfigs;
|
||||
}
|
@ -19,7 +19,7 @@ import 'package:app_flowy/workspace/application/view/view_service.dart';
|
||||
import 'package:app_flowy/workspace/application/workspace/welcome_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/workspace/workspace_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/workspace/workspace_service.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
|
||||
@ -55,7 +55,7 @@ class HomeDepsResolver {
|
||||
|
||||
getIt.registerFactoryParam<ViewBloc, View, void>(
|
||||
(view, _) => ViewBloc(
|
||||
view: view,
|
||||
view: view,
|
||||
service: ViewService(),
|
||||
listener: getIt<ViewListener>(param1: view),
|
||||
),
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/tasks/prelude.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -41,6 +42,7 @@ class FlowyRunner {
|
||||
getIt<AppLauncher>().addTask(InitRustSDKTask());
|
||||
|
||||
if (!env.isTest()) {
|
||||
getIt<AppLauncher>().addTask(PluginLoadTask());
|
||||
getIt<AppLauncher>().addTask(InitAppWidgetTask());
|
||||
getIt<AppLauncher>().addTask(InitPlatformServiceTask());
|
||||
}
|
||||
@ -58,6 +60,7 @@ Future<void> initGetIt(
|
||||
getIt.registerFactory<EntryPoint>(() => f);
|
||||
getIt.registerLazySingleton<FlowySDK>(() => const FlowySDK());
|
||||
getIt.registerLazySingleton<AppLauncher>(() => AppLauncher(env, getIt));
|
||||
getIt.registerSingleton<PluginSandbox>(PluginSandbox());
|
||||
|
||||
await UserDepsResolver.resolve(getIt);
|
||||
await HomeDepsResolver.resolve(getIt);
|
||||
|
36
frontend/app_flowy/lib/startup/tasks/load_plugin.dart
Normal file
36
frontend/app_flowy/lib/startup/tasks/load_plugin.dart
Normal file
@ -0,0 +1,36 @@
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/doc/document.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/trash/trash.dart';
|
||||
|
||||
enum DefaultPlugin {
|
||||
quillEditor,
|
||||
blank,
|
||||
trash,
|
||||
}
|
||||
|
||||
extension FlowyDefaultPluginExt on DefaultPlugin {
|
||||
int type() {
|
||||
switch (this) {
|
||||
case DefaultPlugin.quillEditor:
|
||||
return 0;
|
||||
case DefaultPlugin.blank:
|
||||
return 1;
|
||||
case DefaultPlugin.trash:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PluginLoadTask extends LaunchTask {
|
||||
@override
|
||||
LaunchTaskType get type => LaunchTaskType.dataProcessing;
|
||||
|
||||
@override
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
registerPlugin(builder: BlankPluginBuilder(), config: BlankPluginConfig());
|
||||
registerPlugin(builder: TrashPluginBuilder(), config: TrashPluginConfig());
|
||||
registerPlugin(builder: DocumentPluginBuilder());
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export 'app_widget.dart';
|
||||
export 'init_sdk.dart';
|
||||
export 'rust_sdk.dart';
|
||||
export 'platform_service.dart';
|
||||
export 'load_plugin.dart';
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/workspace/application/app/app_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/app/app_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
@ -15,8 +16,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
||||
final AppService service;
|
||||
final AppListener listener;
|
||||
|
||||
AppBloc({required this.app, required this.service, required this.listener})
|
||||
: super(AppState.initial(app)) {
|
||||
AppBloc({required this.app, required this.service, required this.listener}) : super(AppState.initial(app)) {
|
||||
on<AppEvent>((event, emit) async {
|
||||
await event.map(initial: (e) async {
|
||||
listener.startListening(
|
||||
@ -25,8 +25,13 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
||||
);
|
||||
await _fetchViews(emit);
|
||||
}, createView: (CreateView value) async {
|
||||
final viewOrFailed =
|
||||
await service.createView(appId: app.id, name: value.name, desc: value.desc, viewType: value.viewType);
|
||||
final viewOrFailed = await service.createView(
|
||||
appId: app.id,
|
||||
name: value.name,
|
||||
desc: value.desc,
|
||||
dataType: value.dataType,
|
||||
pluginType: value.pluginType,
|
||||
);
|
||||
viewOrFailed.fold(
|
||||
(view) => emit(state.copyWith(
|
||||
latestCreatedView: view,
|
||||
@ -100,7 +105,12 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
||||
@freezed
|
||||
class AppEvent with _$AppEvent {
|
||||
const factory AppEvent.initial() = Initial;
|
||||
const factory AppEvent.createView(String name, String desc, ViewType viewType) = CreateView;
|
||||
const factory AppEvent.createView(
|
||||
String name,
|
||||
String desc,
|
||||
PluginDataType dataType,
|
||||
PluginType pluginType,
|
||||
) = CreateView;
|
||||
const factory AppEvent.delete() = Delete;
|
||||
const factory AppEvent.rename(String newName) = Rename;
|
||||
const factory AppEvent.didReceiveViews(List<View> views) = ReceiveViews;
|
||||
|
@ -4,9 +4,9 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
|
||||
class AppService {
|
||||
|
||||
Future<Either<App, FlowyError>> getAppDesc({required String appId}) {
|
||||
final request = AppId.create()..value = appId;
|
||||
|
||||
@ -17,13 +17,15 @@ class AppService {
|
||||
required String appId,
|
||||
required String name,
|
||||
required String desc,
|
||||
required ViewType viewType,
|
||||
required PluginDataType dataType,
|
||||
required PluginType pluginType,
|
||||
}) {
|
||||
final request = CreateViewPayload.create()
|
||||
..belongToId = appId
|
||||
..name = name
|
||||
..desc = desc
|
||||
..viewType = viewType;
|
||||
..dataType = dataType
|
||||
..pluginType = pluginType;
|
||||
|
||||
return FolderEventCreateView(request).send();
|
||||
}
|
||||
@ -53,5 +55,3 @@ class AppService {
|
||||
return FolderEventUpdateApp(request).send();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -48,7 +48,7 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
|
||||
void setLocale(BuildContext context, Locale newLocale) {
|
||||
if (_locale != newLocale) {
|
||||
if (!context.supportedLocales.contains(newLocale)) {
|
||||
Log.error("Unsupported locale: $newLocale");
|
||||
Log.warn("Unsupported locale: $newLocale");
|
||||
newLocale = const Locale('en');
|
||||
Log.debug("Fallback to locale: $newLocale");
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ import 'package:app_flowy/workspace/application/doc/doc_service.dart';
|
||||
import 'package:app_flowy/workspace/application/trash/trash_service.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_listener.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/trash.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart' show Document, Delta;
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -19,6 +19,7 @@ typedef FlutterQuillDocument = Document;
|
||||
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
final View view;
|
||||
final DocumentService service;
|
||||
|
||||
final ViewListener listener;
|
||||
final TrashService trashService;
|
||||
late FlutterQuillDocument document;
|
||||
@ -43,6 +44,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
},
|
||||
deletePermanently: (DeletePermanently value) async {
|
||||
final result = await trashService.deleteViews([Tuple2(view.id, TrashType.TrashView)]);
|
||||
|
||||
final newState = result.fold((l) => state.copyWith(forceClose: true), (r) => state);
|
||||
emit(newState);
|
||||
},
|
||||
@ -85,8 +87,8 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
listener.start();
|
||||
final result = await service.openDocument(docId: view.id);
|
||||
result.fold(
|
||||
(doc) {
|
||||
document = _decodeJsonToDocument(doc.deltaJson);
|
||||
(block) {
|
||||
document = _decodeJsonToDocument(block.deltaJson);
|
||||
_subscription = document.changes.listen((event) {
|
||||
final delta = event.item2;
|
||||
final documentDelta = document.toDelta();
|
||||
|
@ -5,14 +5,14 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
|
||||
class DocumentService {
|
||||
Future<Either<DocumentDelta, FlowyError>> openDocument({required String docId}) {
|
||||
Future<Either<BlockDelta, FlowyError>> openDocument({required String docId}) {
|
||||
final request = ViewId(value: docId);
|
||||
return FolderEventOpenView(request).send();
|
||||
}
|
||||
|
||||
Future<Either<DocumentDelta, FlowyError>> composeDelta({required String docId, required String data}) {
|
||||
final request = DocumentDelta.create()
|
||||
..docId = docId
|
||||
Future<Either<BlockDelta, FlowyError>> composeDelta({required String docId, required String data}) {
|
||||
final request = BlockDelta.create()
|
||||
..blockId = docId
|
||||
..deltaJson = data;
|
||||
return FolderEventApplyDocDelta(request).send();
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
class ShareService {
|
||||
Future<Either<ExportData, FlowyError>> export(String docId, ExportType type) {
|
||||
final request = ExportPayload.create()
|
||||
..docId = docId
|
||||
..viewId = docId
|
||||
..exportType = type;
|
||||
|
||||
return FolderEventExportDocument(request).send();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:app_flowy/workspace/domain/edit_context.dart';
|
||||
import 'package:app_flowy/workspace/application/edit_pannel/edit_context.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
// ignore: import_of_legacy_library_into_null_safe
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:app_flowy/workspace/domain/edit_context.dart';
|
||||
import 'package:app_flowy/workspace/application/edit_pannel/edit_context.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/tasks/load_plugin.dart';
|
||||
import 'package:app_flowy/workspace/application/workspace/workspace_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/workspace/workspace_service.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/blank/blank_page.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
||||
@ -29,7 +29,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
||||
emit(state.copyWith(isCollapse: !isCollapse));
|
||||
},
|
||||
openPage: (e) async {
|
||||
emit(state.copyWith(stackContext: e.context));
|
||||
emit(state.copyWith(plugin: e.plugin));
|
||||
},
|
||||
createApp: (CreateApp event) async {
|
||||
await _performActionOnCreateApp(event, emit);
|
||||
@ -85,7 +85,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
||||
class MenuEvent with _$MenuEvent {
|
||||
const factory MenuEvent.initial() = _Initial;
|
||||
const factory MenuEvent.collapse() = Collapse;
|
||||
const factory MenuEvent.openPage(HomeStackContext context) = OpenPage;
|
||||
const factory MenuEvent.openPage(Plugin plugin) = OpenPage;
|
||||
const factory MenuEvent.createApp(String name, {String? desc}) = CreateApp;
|
||||
const factory MenuEvent.didReceiveApps(Either<List<App>, FlowyError> appsOrFail) = ReceiveApps;
|
||||
}
|
||||
@ -96,13 +96,13 @@ class MenuState with _$MenuState {
|
||||
required bool isCollapse,
|
||||
required Option<List<App>> apps,
|
||||
required Either<Unit, FlowyError> successOrFailure,
|
||||
required HomeStackContext stackContext,
|
||||
required Plugin plugin,
|
||||
}) = _MenuState;
|
||||
|
||||
factory MenuState.initial() => MenuState(
|
||||
isCollapse: false,
|
||||
apps: none(),
|
||||
successOrFailure: left(unit),
|
||||
stackContext: BlankStackContext(),
|
||||
plugin: makePlugin(pluginType: DefaultPlugin.blank.type()),
|
||||
);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,50 @@
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum FlowyPlugin {
|
||||
editor,
|
||||
kanban,
|
||||
}
|
||||
|
||||
extension FlowyPluginExtension on FlowyPlugin {
|
||||
String displayName() {
|
||||
switch (this) {
|
||||
case FlowyPlugin.editor:
|
||||
return "Doc";
|
||||
case FlowyPlugin.kanban:
|
||||
return "Kanban";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
bool enable() {
|
||||
switch (this) {
|
||||
case FlowyPlugin.editor:
|
||||
return true;
|
||||
case FlowyPlugin.kanban:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewExtension on View {
|
||||
Widget renderThumbnail({Color? iconColor}) {
|
||||
String thumbnail = this.thumbnail;
|
||||
if (thumbnail.isEmpty) {
|
||||
thumbnail = "file_icon";
|
||||
}
|
||||
|
||||
final Widget widget = svg(thumbnail, color: iconColor);
|
||||
return widget;
|
||||
}
|
||||
|
||||
Plugin plugin() {
|
||||
final plugin = makePlugin(pluginType: pluginType, data: this);
|
||||
return plugin;
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
|
||||
enum AppDisclosureAction {
|
||||
rename,
|
||||
delete,
|
||||
}
|
||||
|
||||
extension AppDisclosureExtension on AppDisclosureAction {
|
||||
String get name {
|
||||
switch (this) {
|
||||
case AppDisclosureAction.rename:
|
||||
return LocaleKeys.disclosureAction_rename.tr();
|
||||
case AppDisclosureAction.delete:
|
||||
return LocaleKeys.disclosureAction_delete.tr();
|
||||
}
|
||||
}
|
||||
|
||||
Widget get icon {
|
||||
switch (this) {
|
||||
case AppDisclosureAction.rename:
|
||||
return svg('editor/edit', color: const Color(0xffe5e5e5));
|
||||
case AppDisclosureAction.delete:
|
||||
return svg('editor/delete', color: const Color(0xffe5e5e5));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
|
||||
enum ViewDisclosureAction {
|
||||
rename,
|
||||
delete,
|
||||
duplicate,
|
||||
}
|
||||
|
||||
extension ViewDisclosureExtension on ViewDisclosureAction {
|
||||
String get name {
|
||||
switch (this) {
|
||||
case ViewDisclosureAction.rename:
|
||||
return LocaleKeys.disclosureAction_rename.tr();
|
||||
case ViewDisclosureAction.delete:
|
||||
return LocaleKeys.disclosureAction_delete.tr();
|
||||
case ViewDisclosureAction.duplicate:
|
||||
return LocaleKeys.disclosureAction_duplicate.tr();
|
||||
}
|
||||
}
|
||||
|
||||
Widget get icon {
|
||||
switch (this) {
|
||||
case ViewDisclosureAction.rename:
|
||||
return svg('editor/edit', color: const Color(0xff999999));
|
||||
case ViewDisclosureAction.delete:
|
||||
return svg('editor/delete', color: const Color(0xff999999));
|
||||
case ViewDisclosureAction.duplicate:
|
||||
return svg('editor/copy', color: const Color(0xff999999));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
|
||||
AssetImage assetImageForViewType(ViewType type) {
|
||||
final imageName = _imageNameForViewType(type);
|
||||
return AssetImage('assets/images/$imageName');
|
||||
}
|
||||
|
||||
extension SvgViewType on View {
|
||||
Widget thumbnail({Color? iconColor}) {
|
||||
final imageName = _imageNameForViewType(viewType);
|
||||
final Widget widget = svg(imageName, color: iconColor);
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
|
||||
String _imageNameForViewType(ViewType type) {
|
||||
switch (type) {
|
||||
case ViewType.Doc:
|
||||
return "file_icon";
|
||||
default:
|
||||
return "file_icon";
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/home_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/prelude.dart';
|
||||
|
||||
typedef NavigationCallback = void Function(String id);
|
||||
|
||||
abstract class NavigationItem {
|
||||
Widget get leftBarItem;
|
||||
Widget? get rightBarItem => null;
|
||||
String get identifier;
|
||||
|
||||
NavigationCallback get action => (id) {
|
||||
getIt<HomeStackManager>().setStackWithId(id);
|
||||
};
|
||||
}
|
||||
|
||||
enum HomeStackType {
|
||||
blank,
|
||||
doc,
|
||||
trash,
|
||||
}
|
||||
|
||||
List<HomeStackType> pages = HomeStackType.values.toList();
|
||||
|
||||
abstract class HomeStackContext<T, S> with NavigationItem {
|
||||
List<NavigationItem> get navigationItems;
|
||||
|
||||
@override
|
||||
Widget get leftBarItem;
|
||||
|
||||
@override
|
||||
Widget? get rightBarItem;
|
||||
|
||||
@override
|
||||
String get identifier;
|
||||
|
||||
ValueNotifier<T> get isUpdated;
|
||||
|
||||
HomeStackType get type;
|
||||
|
||||
Widget buildWidget();
|
||||
|
||||
void dispose();
|
||||
}
|
||||
|
||||
class HomeStackNotifier extends ChangeNotifier {
|
||||
HomeStackContext stackContext;
|
||||
PublishNotifier<bool> collapsedNotifier = PublishNotifier();
|
||||
|
||||
Widget get titleWidget => stackContext.leftBarItem;
|
||||
|
||||
HomeStackNotifier({HomeStackContext? context}) : stackContext = context ?? BlankStackContext();
|
||||
|
||||
set context(HomeStackContext context) {
|
||||
if (stackContext.identifier == context.identifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
stackContext.isUpdated.removeListener(notifyListeners);
|
||||
stackContext.dispose();
|
||||
|
||||
stackContext = context;
|
||||
stackContext.isUpdated.addListener(notifyListeners);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
HomeStackContext get context => stackContext;
|
||||
}
|
||||
|
||||
// HomeStack is initialized as singleton to control the page stack.
|
||||
class HomeStackManager {
|
||||
final HomeStackNotifier _notifier = HomeStackNotifier();
|
||||
HomeStackManager();
|
||||
|
||||
Widget title() {
|
||||
return _notifier.context.leftBarItem;
|
||||
}
|
||||
|
||||
PublishNotifier<bool> get collapsedNotifier => _notifier.collapsedNotifier;
|
||||
|
||||
void setStack(HomeStackContext context) {
|
||||
_notifier.context = context;
|
||||
}
|
||||
|
||||
void setStackWithId(String id) {}
|
||||
|
||||
Widget stackTopBar() {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: _notifier),
|
||||
],
|
||||
child: Selector<HomeStackNotifier, Widget>(
|
||||
selector: (context, notifier) => notifier.titleWidget,
|
||||
builder: (context, widget, child) {
|
||||
return const HomeTopBar();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget stackWidget() {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: _notifier),
|
||||
],
|
||||
child: Consumer(builder: (ctx, HomeStackNotifier notifier, child) {
|
||||
return FadingIndexedStack(
|
||||
index: pages.indexOf(notifier.context.type),
|
||||
children: HomeStackType.values.map((viewType) {
|
||||
if (viewType == notifier.context.type) {
|
||||
return notifier.context.buildWidget();
|
||||
} else {
|
||||
return const BlankStackPage();
|
||||
}
|
||||
}).toList(),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/blank/blank_page.dart';
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/doc/doc_stack_page.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
|
||||
extension ToHomeStackContext on View {
|
||||
HomeStackContext stackContext() {
|
||||
switch (viewType) {
|
||||
case ViewType.Blank:
|
||||
return BlankStackContext();
|
||||
case ViewType.Doc:
|
||||
return DocStackContext(view: this);
|
||||
default:
|
||||
return BlankStackContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ToHomeStackType on View {
|
||||
HomeStackType stackType() {
|
||||
switch (viewType) {
|
||||
case ViewType.Blank:
|
||||
return HomeStackType.blank;
|
||||
case ViewType.Doc:
|
||||
return HomeStackType.doc;
|
||||
default:
|
||||
return HomeStackType.blank;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/home/home_listen_bloc.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/home_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/edit_pannel/pannel_animation.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/prelude.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/container.dart';
|
||||
@ -12,9 +11,12 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:app_flowy/workspace/domain/view_ext.dart';
|
||||
|
||||
import '../widgets/edit_pannel/edit_pannel.dart';
|
||||
|
||||
import 'home_layout.dart';
|
||||
import 'home_stack.dart';
|
||||
import 'menu/menu.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
||||
@ -109,7 +111,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
Widget _buildHomeMenu({required HomeLayout layout, required BuildContext context}) {
|
||||
if (initialView == null && widget.workspaceSetting.hasLatestView()) {
|
||||
initialView = widget.workspaceSetting.latestView;
|
||||
getIt<HomeStackManager>().setStack(initialView!.stackContext());
|
||||
final plugin = makePlugin(pluginType: initialView!.pluginType, data: initialView);
|
||||
getIt<HomeStackManager>().setPlugin(plugin);
|
||||
}
|
||||
|
||||
HomeMenu homeMenu = HomeMenu(
|
||||
|
@ -0,0 +1,213 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:time/time.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/tasks/load_plugin.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/navigation.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/extension.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
|
||||
typedef NavigationCallback = void Function(String id);
|
||||
|
||||
late FToast fToast;
|
||||
|
||||
class HomeStack extends StatelessWidget {
|
||||
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
||||
// final Size size;
|
||||
const HomeStack({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Log.info('HomePage build');
|
||||
final theme = context.watch<AppTheme>();
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
getIt<HomeStackManager>().stackTopBar(),
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: theme.surface,
|
||||
child: FocusTraversalGroup(
|
||||
child: getIt<HomeStackManager>().stackWidget(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FadingIndexedStack extends StatefulWidget {
|
||||
final int index;
|
||||
final List<Widget> children;
|
||||
final Duration duration;
|
||||
|
||||
const FadingIndexedStack({
|
||||
Key? key,
|
||||
required this.index,
|
||||
required this.children,
|
||||
this.duration = const Duration(
|
||||
milliseconds: 250,
|
||||
),
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_FadingIndexedStackState createState() => _FadingIndexedStackState();
|
||||
}
|
||||
|
||||
class _FadingIndexedStackState extends State<FadingIndexedStack> {
|
||||
double _targetOpacity = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
fToast = FToast();
|
||||
fToast.init(HomeScreen.scaffoldKey.currentState!.context);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(FadingIndexedStack oldWidget) {
|
||||
if (oldWidget.index == widget.index) return;
|
||||
setState(() => _targetOpacity = 0);
|
||||
Future.delayed(1.milliseconds, () => setState(() => _targetOpacity = 1));
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TweenAnimationBuilder<double>(
|
||||
duration: _targetOpacity > 0 ? widget.duration : 0.milliseconds,
|
||||
tween: Tween(begin: 0, end: _targetOpacity),
|
||||
builder: (_, value, child) {
|
||||
return Opacity(opacity: value, child: child);
|
||||
},
|
||||
child: IndexedStack(index: widget.index, children: widget.children),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class NavigationItem {
|
||||
Widget get leftBarItem;
|
||||
Widget? get rightBarItem => null;
|
||||
|
||||
NavigationCallback get action => (id) {
|
||||
getIt<HomeStackManager>().setStackWithId(id);
|
||||
};
|
||||
}
|
||||
|
||||
class HomeStackNotifier extends ChangeNotifier {
|
||||
Plugin _plugin;
|
||||
PublishNotifier<bool> collapsedNotifier = PublishNotifier();
|
||||
|
||||
Widget get titleWidget => _plugin.pluginDisplay.leftBarItem;
|
||||
|
||||
HomeStackNotifier({Plugin? plugin}) : _plugin = plugin ?? makePlugin(pluginType: DefaultPlugin.blank.type());
|
||||
|
||||
set plugin(Plugin newPlugin) {
|
||||
if (newPlugin.pluginId == _plugin.pluginId) {
|
||||
return;
|
||||
}
|
||||
|
||||
_plugin.displayNotifier?.removeListener(notifyListeners);
|
||||
_plugin.dispose();
|
||||
|
||||
_plugin = newPlugin;
|
||||
_plugin.displayNotifier?.addListener(notifyListeners);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Plugin get plugin => _plugin;
|
||||
}
|
||||
|
||||
// HomeStack is initialized as singleton to controll the page stack.
|
||||
class HomeStackManager {
|
||||
final HomeStackNotifier _notifier = HomeStackNotifier();
|
||||
HomeStackManager();
|
||||
|
||||
Widget title() {
|
||||
return _notifier.plugin.pluginDisplay.leftBarItem;
|
||||
}
|
||||
|
||||
PublishNotifier<bool> get collapsedNotifier => _notifier.collapsedNotifier;
|
||||
|
||||
void setPlugin(Plugin newPlugin) {
|
||||
_notifier.plugin = newPlugin;
|
||||
}
|
||||
|
||||
void setStackWithId(String id) {}
|
||||
|
||||
Widget stackTopBar() {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: _notifier),
|
||||
],
|
||||
child: Selector<HomeStackNotifier, Widget>(
|
||||
selector: (context, notifier) => notifier.titleWidget,
|
||||
builder: (context, widget, child) {
|
||||
return const HomeTopBar();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget stackWidget() {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: _notifier),
|
||||
],
|
||||
child: Consumer(builder: (ctx, HomeStackNotifier notifier, child) {
|
||||
return FadingIndexedStack(
|
||||
index: getIt<PluginSandbox>().indexOf(notifier.plugin.pluginType),
|
||||
children: getIt<PluginSandbox>().supportPluginTypes.map((pluginType) {
|
||||
if (pluginType == notifier.plugin.pluginType) {
|
||||
return notifier.plugin.pluginDisplay.buildWidget();
|
||||
} else {
|
||||
return const BlankStackPage();
|
||||
}
|
||||
}).toList(),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomeTopBar extends StatelessWidget {
|
||||
const HomeTopBar({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return Container(
|
||||
color: theme.surface,
|
||||
height: HomeSizes.topBarHeight,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const FlowyNavigation(),
|
||||
const HSpace(16),
|
||||
ChangeNotifierProvider.value(
|
||||
value: Provider.of<HomeStackNotifier>(context, listen: false),
|
||||
child: Consumer(
|
||||
builder: (BuildContext context, HomeStackNotifier notifier, Widget? child) {
|
||||
return notifier.plugin.pluginDisplay.rightBarItem ?? const SizedBox();
|
||||
},
|
||||
),
|
||||
) // _renderMoreButton(),
|
||||
],
|
||||
)
|
||||
.padding(
|
||||
horizontal: HomeInsets.topBarTitlePadding,
|
||||
)
|
||||
.bottomBorder(color: Colors.grey.shade300),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class AddButton extends StatelessWidget {
|
||||
final Function(ViewType) onSelected;
|
||||
final Function(PluginBuilder) onSelected;
|
||||
const AddButton({
|
||||
Key? key,
|
||||
required this.onSelected,
|
||||
@ -34,21 +34,24 @@ class AddButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
class ActionList {
|
||||
final Function(ViewType) onSelected;
|
||||
final Function(PluginBuilder) onSelected;
|
||||
final BuildContext anchorContext;
|
||||
final String _identifier = 'DisclosureButtonActionList';
|
||||
|
||||
const ActionList({required this.anchorContext, required this.onSelected});
|
||||
|
||||
void show(BuildContext buildContext) {
|
||||
final items = ViewType.values.where((element) => element != ViewType.Blank).map((ty) {
|
||||
return CreateItem(
|
||||
viewType: ty,
|
||||
onSelected: (viewType) {
|
||||
final items = pluginBuilders().map(
|
||||
(pluginBuilder) {
|
||||
return CreateItem(
|
||||
pluginBuilder: pluginBuilder,
|
||||
onSelected: (builder) {
|
||||
FlowyOverlay.of(buildContext).remove(_identifier);
|
||||
onSelected(viewType);
|
||||
});
|
||||
}).toList();
|
||||
onSelected(builder);
|
||||
},
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
|
||||
ListOverlay.showWithAnchor(
|
||||
buildContext,
|
||||
@ -64,11 +67,11 @@ class ActionList {
|
||||
}
|
||||
|
||||
class CreateItem extends StatelessWidget {
|
||||
final ViewType viewType;
|
||||
final Function(ViewType) onSelected;
|
||||
final PluginBuilder pluginBuilder;
|
||||
final Function(PluginBuilder) onSelected;
|
||||
const CreateItem({
|
||||
Key? key,
|
||||
required this.viewType,
|
||||
required this.pluginBuilder,
|
||||
required this.onSelected,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -81,9 +84,9 @@ class CreateItem extends StatelessWidget {
|
||||
config: config,
|
||||
builder: (context, onHover) {
|
||||
return GestureDetector(
|
||||
onTap: () => onSelected(viewType),
|
||||
onTap: () => onSelected(pluginBuilder),
|
||||
child: FlowyText.medium(
|
||||
viewType.name,
|
||||
pluginBuilder.menuName,
|
||||
color: theme.textColor,
|
||||
fontSize: 12,
|
||||
).padding(horizontal: 10, vertical: 6),
|
@ -1,4 +1,3 @@
|
||||
import 'package:app_flowy/workspace/domain/edit_action/app_edit.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
@ -13,6 +12,8 @@ import 'package:app_flowy/workspace/application/app/app_bloc.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
|
||||
import '../menu_app.dart';
|
||||
import 'add_button.dart';
|
||||
import 'right_click_action.dart';
|
||||
@ -102,10 +103,13 @@ class MenuAppHeader extends StatelessWidget {
|
||||
return Tooltip(
|
||||
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
|
||||
child: AddButton(
|
||||
onSelected: (viewType) {
|
||||
context
|
||||
.read<AppBloc>()
|
||||
.add(AppEvent.createView(LocaleKeys.menuAppHeader_defaultNewPageName.tr(), "", viewType));
|
||||
onSelected: (pluginBuilder) {
|
||||
context.read<AppBloc>().add(AppEvent.createView(
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
"",
|
||||
pluginBuilder.dataType,
|
||||
pluginBuilder.pluginType,
|
||||
));
|
||||
},
|
||||
).padding(right: MenuAppSizes.headerPadding),
|
||||
);
|
||||
@ -131,3 +135,28 @@ class MenuAppHeader extends StatelessWidget {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum AppDisclosureAction {
|
||||
rename,
|
||||
delete,
|
||||
}
|
||||
|
||||
extension AppDisclosureExtension on AppDisclosureAction {
|
||||
String get name {
|
||||
switch (this) {
|
||||
case AppDisclosureAction.rename:
|
||||
return LocaleKeys.disclosureAction_rename.tr();
|
||||
case AppDisclosureAction.delete:
|
||||
return LocaleKeys.disclosureAction_delete.tr();
|
||||
}
|
||||
}
|
||||
|
||||
Widget get icon {
|
||||
switch (this) {
|
||||
case AppDisclosureAction.rename:
|
||||
return svg('editor/edit', color: const Color(0xffe5e5e5));
|
||||
case AppDisclosureAction.delete:
|
||||
return svg('editor/delete', color: const Color(0xffe5e5e5));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
import 'package:app_flowy/workspace/domain/edit_action/app_edit.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:dartz/dartz.dart' as dartz;
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'header.dart';
|
||||
|
||||
class AppDisclosureActionSheet with ActionList<DisclosureActionWrapper> implements FlowyOverlayDelegate {
|
||||
final Function(dartz.Option<AppDisclosureAction>) onSelected;
|
||||
final _items = AppDisclosureAction.values.map((action) => DisclosureActionWrapper(action)).toList();
|
@ -1,6 +1,5 @@
|
||||
import 'package:app_flowy/workspace/application/appearance.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/header/header.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
@ -1,4 +1,3 @@
|
||||
import 'package:app_flowy/workspace/domain/edit_action/view_edit.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:dartz/dartz.dart' as dartz;
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@ -8,6 +7,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'item.dart';
|
||||
|
||||
// [[Widget: LifeCycle]]
|
||||
// https://flutterbyexample.com/lesson/stateful-widget-lifecycle
|
||||
class ViewDisclosureButton extends StatelessWidget
|
@ -1,6 +1,7 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:app_flowy/workspace/domain/edit_action/view_edit.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:dartz/dartz.dart' as dartz;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -12,9 +13,8 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:app_flowy/workspace/domain/image.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/menu_app.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
|
||||
import 'disclosure_action.dart';
|
||||
|
||||
@ -55,7 +55,7 @@ class ViewSectionItem extends StatelessWidget {
|
||||
|
||||
Widget _render(BuildContext context, bool onHover, ViewState state, Color iconColor) {
|
||||
List<Widget> children = [
|
||||
SizedBox(width: 16, height: 16, child: state.view.thumbnail(iconColor: iconColor)),
|
||||
SizedBox(width: 16, height: 16, child: state.view.renderThumbnail(iconColor: iconColor)),
|
||||
const HSpace(2),
|
||||
Expanded(child: FlowyText.regular(state.view.name, fontSize: 12, overflow: TextOverflow.clip)),
|
||||
];
|
||||
@ -104,3 +104,33 @@ class ViewSectionItem extends StatelessWidget {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum ViewDisclosureAction {
|
||||
rename,
|
||||
delete,
|
||||
duplicate,
|
||||
}
|
||||
|
||||
extension ViewDisclosureExtension on ViewDisclosureAction {
|
||||
String get name {
|
||||
switch (this) {
|
||||
case ViewDisclosureAction.rename:
|
||||
return LocaleKeys.disclosureAction_rename.tr();
|
||||
case ViewDisclosureAction.delete:
|
||||
return LocaleKeys.disclosureAction_delete.tr();
|
||||
case ViewDisclosureAction.duplicate:
|
||||
return LocaleKeys.disclosureAction_duplicate.tr();
|
||||
}
|
||||
}
|
||||
|
||||
Widget get icon {
|
||||
switch (this) {
|
||||
case ViewDisclosureAction.rename:
|
||||
return svg('editor/edit', color: const Color(0xff999999));
|
||||
case ViewDisclosureAction.delete:
|
||||
return svg('editor/delete', color: const Color(0xff999999));
|
||||
case ViewDisclosureAction.duplicate:
|
||||
return svg('editor/copy', color: const Color(0xff999999));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/domain/view_ext.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/menu_app.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -106,7 +105,7 @@ class ViewSectionNotifier with ChangeNotifier {
|
||||
|
||||
if (view != null) {
|
||||
WidgetsBinding.instance?.addPostFrameCallback((_) {
|
||||
getIt<HomeStackManager>().setStack(view.stackContext());
|
||||
getIt<HomeStackManager>().setPlugin(view.plugin());
|
||||
});
|
||||
} else {
|
||||
// do nothing
|
@ -1,4 +1,8 @@
|
||||
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/top_bar.dart';
|
||||
export './app/header/header.dart';
|
||||
export './app/menu_app.dart';
|
||||
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/trash/menu.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -15,32 +19,13 @@ import 'package:expandable/expandable.dart';
|
||||
import 'package:flowy_infra/time/duration.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/menu_user.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
|
||||
import 'widget/app/menu_app.dart';
|
||||
import 'widget/app/create_button.dart';
|
||||
import 'widget/menu_trash.dart';
|
||||
|
||||
// [[diagram: HomeMenu's widget structure]]
|
||||
// get user profile or modify user
|
||||
// ┌──────┐
|
||||
// ┌──────────┐ ┌──▶│IUser │
|
||||
// ┌─▶│MenuTopBar│ ┌────────┐ ┌─────────────┐ │ └──────┘
|
||||
// │ └──────────┘ ┌───│MenuUser│─▶│MenuUserBloc │──┤
|
||||
// ┌──────────┐ │ │ └────────┘ └─────────────┘ │ ┌─────────────┐
|
||||
// │ HomeMenu │─┤ │ └──▶│IUserListener│
|
||||
// └──────────┘ │ │ └─────────────┘
|
||||
// │ │ listen workspace changes or user
|
||||
// │ impl │ profile changes
|
||||
// │ ┌──────────┐ ┌─────────┐ │
|
||||
// └─▶│ MenuList │───▶│MenuItem │◀─┤
|
||||
// └──────────┘ └─────────┘ │ ┌────────┐
|
||||
// │ ┌─▶│AppBloc │ fetch app's views or modify view
|
||||
// │ │ └────────┘
|
||||
// │ ┌────────┐ │
|
||||
// └───│MenuApp │──┤
|
||||
// └────────┘
|
||||
import 'app/menu_app.dart';
|
||||
import 'app/create_button.dart';
|
||||
import 'menu_user.dart';
|
||||
|
||||
class HomeMenu extends StatelessWidget {
|
||||
final PublishNotifier<bool> _collapsedNotifier;
|
||||
@ -70,9 +55,9 @@ class HomeMenu extends StatelessWidget {
|
||||
child: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<MenuBloc, MenuState>(
|
||||
listenWhen: (p, c) => p.stackContext != c.stackContext,
|
||||
listenWhen: (p, c) => p.plugin.pluginId != c.plugin.pluginId,
|
||||
listener: (context, state) {
|
||||
getIt<HomeStackManager>().setStack(state.stackContext);
|
||||
getIt<HomeStackManager>().setPlugin(state.plugin);
|
||||
},
|
||||
),
|
||||
BlocListener<MenuBloc, MenuState>(
|
||||
@ -181,3 +166,32 @@ class MenuSharedState extends ChangeNotifier {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class MenuTopBar extends StatelessWidget {
|
||||
const MenuTopBar({Key? key}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return BlocBuilder<MenuBloc, MenuState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: HomeSizes.topBarHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
(theme.isDark
|
||||
? svgWithSize("flowy_logo_dark_mode", const Size(92, 17))
|
||||
: svgWithSize("flowy_logo_with_text", const Size(92, 17))),
|
||||
const Spacer(),
|
||||
FlowyIconButton(
|
||||
width: 28,
|
||||
onPressed: () => context.read<MenuBloc>().add(const MenuEvent.collapse()),
|
||||
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||
icon: svg("home/hide_menu", color: theme.iconColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -17,8 +17,8 @@ class NavigationNotifier with ChangeNotifier {
|
||||
|
||||
void update(HomeStackNotifier notifier) {
|
||||
bool shouldNotify = false;
|
||||
if (navigationItems != notifier.context.navigationItems) {
|
||||
navigationItems = notifier.context.navigationItems;
|
||||
if (navigationItems != notifier.plugin.pluginDisplay.navigationItems) {
|
||||
navigationItems = notifier.plugin.pluginDisplay.navigationItems;
|
||||
shouldNotify = true;
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ class FlowyNavigation extends StatelessWidget {
|
||||
create: (_) {
|
||||
final notifier = Provider.of<HomeStackNotifier>(context, listen: false);
|
||||
return NavigationNotifier(
|
||||
navigationItems: notifier.context.navigationItems,
|
||||
navigationItems: notifier.plugin.pluginDisplay.navigationItems,
|
||||
collapasedNotifier: notifier.collapsedNotifier,
|
||||
);
|
||||
},
|
||||
@ -179,7 +179,4 @@ class EllipsisNaviItem extends NavigationItem {
|
||||
|
||||
@override
|
||||
NavigationCallback get action => (id) {};
|
||||
|
||||
@override
|
||||
String get identifier => "Ellipsis";
|
||||
}
|
||||
|
@ -1,37 +1,58 @@
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/startup/tasks/load_plugin.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
|
||||
class BlankStackContext extends HomeStackContext {
|
||||
final ValueNotifier<bool> _isUpdated = ValueNotifier<bool>(false);
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
|
||||
class BlankPluginBuilder extends PluginBuilder {
|
||||
@override
|
||||
Plugin build(dynamic data) {
|
||||
return BlankPagePlugin(pluginType: pluginType);
|
||||
}
|
||||
|
||||
@override
|
||||
String get identifier => "1";
|
||||
String get menuName => "Blank";
|
||||
|
||||
@override
|
||||
PluginType get pluginType => DefaultPlugin.blank.type();
|
||||
}
|
||||
|
||||
class BlankPluginConfig implements PluginConfig {
|
||||
@override
|
||||
bool get creatable => false;
|
||||
}
|
||||
|
||||
class BlankPagePlugin extends Plugin {
|
||||
final PluginType _pluginType;
|
||||
BlankPagePlugin({
|
||||
required PluginType pluginType,
|
||||
}) : _pluginType = pluginType;
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
|
||||
@override
|
||||
PluginDisplay get pluginDisplay => BlankPagePluginDisplay();
|
||||
|
||||
@override
|
||||
PluginId get pluginId => "BlankStack";
|
||||
|
||||
@override
|
||||
PluginType get pluginType => _pluginType;
|
||||
}
|
||||
|
||||
class BlankPagePluginDisplay extends PluginDisplay {
|
||||
@override
|
||||
Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr(), fontSize: 12);
|
||||
|
||||
@override
|
||||
Widget? get rightBarItem => null;
|
||||
|
||||
@override
|
||||
HomeStackType get type => HomeStackType.blank;
|
||||
|
||||
@override
|
||||
Widget buildWidget() {
|
||||
return const BlankStackPage();
|
||||
}
|
||||
Widget buildWidget() => const BlankStackPage();
|
||||
|
||||
@override
|
||||
List<NavigationItem> get navigationItems => [this];
|
||||
|
||||
@override
|
||||
ValueNotifier<bool> get isUpdated => _isUpdated;
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
class BlankStackPage extends StatefulWidget {
|
@ -1,10 +1,18 @@
|
||||
library docuemnt_plugin;
|
||||
|
||||
export './src/document_page.dart';
|
||||
export './src/widget/toolbar/history_button.dart';
|
||||
export './src/widget/toolbar/toolbar_icon_button.dart';
|
||||
export './src/widget/toolbar/tool_bar.dart';
|
||||
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/startup/tasks/load_plugin.dart';
|
||||
import 'package:app_flowy/workspace/application/appearance.dart';
|
||||
import 'package:app_flowy/workspace/application/doc/share_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_service.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/domain/view_ext.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -23,62 +31,90 @@ import 'package:clipboard/clipboard.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'document_page.dart';
|
||||
import 'src/document_page.dart';
|
||||
|
||||
class DocStackContext extends HomeStackContext<int, ShareActionWrapper> {
|
||||
View _view;
|
||||
late ViewListener _listener;
|
||||
final ValueNotifier<int> _isUpdated = ValueNotifier<int>(0);
|
||||
class DocumentPluginBuilder extends PluginBuilder {
|
||||
@override
|
||||
Plugin build(dynamic data) {
|
||||
if (data is View) {
|
||||
return DocumentPlugin(pluginType: pluginType, view: data);
|
||||
} else {
|
||||
throw FlowyPluginException.invalidData;
|
||||
}
|
||||
}
|
||||
|
||||
DocStackContext({required View view, Key? key}) : _view = view {
|
||||
@override
|
||||
String get menuName => "Doc";
|
||||
|
||||
@override
|
||||
PluginType get pluginType => DefaultPlugin.quillEditor.type();
|
||||
|
||||
@override
|
||||
ViewDataType get dataType => ViewDataType.RichText;
|
||||
}
|
||||
|
||||
class DocumentPlugin implements Plugin {
|
||||
late View _view;
|
||||
ViewListener? _listener;
|
||||
final ValueNotifier<int> _displayNotifier = ValueNotifier<int>(0);
|
||||
late PluginType _pluginType;
|
||||
|
||||
DocumentPlugin({required PluginType pluginType, required View view, Key? key}) : _view = view {
|
||||
_pluginType = pluginType;
|
||||
_listener = getIt<ViewListener>(param1: view);
|
||||
_listener.updatedNotifier.addPublishListener((result) {
|
||||
_listener?.updatedNotifier.addPublishListener((result) {
|
||||
result.fold(
|
||||
(newView) {
|
||||
_view = newView;
|
||||
_isUpdated.value = _view.hashCode;
|
||||
_displayNotifier.value = _view.hashCode;
|
||||
},
|
||||
(error) {},
|
||||
);
|
||||
});
|
||||
_listener.start();
|
||||
_listener?.start();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_listener?.close();
|
||||
_listener = null;
|
||||
}
|
||||
|
||||
@override
|
||||
PluginDisplay get pluginDisplay => DocumentPluginDisplay(view: _view);
|
||||
|
||||
@override
|
||||
PluginType get pluginType => _pluginType;
|
||||
|
||||
@override
|
||||
PluginId get pluginId => _view.id;
|
||||
|
||||
@override
|
||||
ChangeNotifier? get displayNotifier => _displayNotifier;
|
||||
}
|
||||
|
||||
class DocumentPluginDisplay extends PluginDisplay {
|
||||
final View _view;
|
||||
|
||||
DocumentPluginDisplay({required View view, Key? key}) : _view = view;
|
||||
|
||||
@override
|
||||
Widget buildWidget() => DocumentPage(view: _view, key: ValueKey(_view.id));
|
||||
|
||||
@override
|
||||
Widget get leftBarItem => DocumentLeftBarItem(view: _view);
|
||||
|
||||
@override
|
||||
Widget? get rightBarItem => DocumentShareButton(view: _view);
|
||||
|
||||
@override
|
||||
String get identifier => _view.id;
|
||||
|
||||
@override
|
||||
HomeStackType get type => _view.stackType();
|
||||
|
||||
@override
|
||||
Widget buildWidget() => DocumentPage(view: _view, key: ValueKey(_view.id));
|
||||
|
||||
@override
|
||||
List<NavigationItem> get navigationItems => _makeNavigationItems();
|
||||
|
||||
@override
|
||||
ValueNotifier<int> get isUpdated => _isUpdated;
|
||||
|
||||
// List<NavigationItem> get navigationItems => naviStacks.map((stack) {
|
||||
// return NavigationItemImpl(context: stack);
|
||||
// }).toList();
|
||||
|
||||
List<NavigationItem> _makeNavigationItems() {
|
||||
return [
|
||||
this,
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_listener.close();
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentLeftBarItem extends StatefulWidget {
|
@ -1,6 +1,7 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/appearance.dart';
|
||||
import 'package:app_flowy/workspace/application/doc/doc_bloc.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/doc/document.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart' as quill;
|
||||
@ -12,7 +13,6 @@ import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'styles.dart';
|
||||
import 'widget/banner.dart';
|
||||
import 'widget/toolbar/tool_bar.dart';
|
||||
|
||||
class DocumentPage extends StatefulWidget {
|
||||
final View view;
|
@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
|
||||
import 'widget/style_widgets/style_widgets.dart';
|
||||
import 'widget/style_widgets.dart';
|
||||
|
||||
DefaultStyles customStyles(BuildContext context) {
|
||||
const baseSpacing = Tuple2<double, double>(6, 0);
|
@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/doc/widget/toolbar/history_button.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
@ -10,6 +9,7 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
import 'check_button.dart';
|
||||
import 'color_picker.dart';
|
||||
import 'header_button.dart';
|
||||
import 'history_button.dart';
|
||||
import 'link_button.dart';
|
||||
import 'toggle_button.dart';
|
||||
import 'toolbar_icon_button.dart';
|
@ -1,8 +1,9 @@
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/startup/tasks/load_plugin.dart';
|
||||
import 'package:app_flowy/workspace/application/appearance.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/trash/trash_page.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
@ -22,7 +23,7 @@ class MenuTrash extends StatelessWidget {
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Provider.of<MenuSharedState>(context, listen: false).selectedView.value = null;
|
||||
getIt<HomeStackManager>().setStack(TrashStackContext());
|
||||
getIt<HomeStackManager>().setPlugin(makePlugin(pluginType: DefaultPlugin.trash.type()));
|
||||
},
|
||||
child: _render(context),
|
||||
),
|
@ -1,8 +1,12 @@
|
||||
export "./src/sizes.dart";
|
||||
export "./src/trash_cell.dart";
|
||||
export "./src/trash_header.dart";
|
||||
|
||||
import 'package:app_flowy/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/startup/tasks/load_plugin.dart';
|
||||
import 'package:app_flowy/workspace/application/trash/trash_bloc.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/trash/widget/sizes.dart';
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/trash/widget/trash_cell.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -17,14 +21,47 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
|
||||
import 'widget/trash_header.dart';
|
||||
import 'src/sizes.dart';
|
||||
import 'src/trash_cell.dart';
|
||||
import 'src/trash_header.dart';
|
||||
|
||||
class TrashStackContext extends HomeStackContext {
|
||||
final ValueNotifier<bool> _isUpdated = ValueNotifier<bool>(false);
|
||||
class TrashPluginBuilder extends PluginBuilder {
|
||||
@override
|
||||
Plugin build(dynamic data) {
|
||||
return TrashPlugin(pluginType: pluginType);
|
||||
}
|
||||
|
||||
@override
|
||||
String get identifier => "TrashStackContext";
|
||||
String get menuName => "Trash";
|
||||
|
||||
@override
|
||||
PluginType get pluginType => DefaultPlugin.trash.type();
|
||||
}
|
||||
|
||||
class TrashPluginConfig implements PluginConfig {
|
||||
@override
|
||||
bool get creatable => false;
|
||||
}
|
||||
|
||||
class TrashPlugin extends Plugin {
|
||||
final PluginType _pluginType;
|
||||
|
||||
TrashPlugin({required PluginType pluginType}) : _pluginType = pluginType;
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
|
||||
@override
|
||||
PluginDisplay get pluginDisplay => TrashPluginDisplay();
|
||||
|
||||
@override
|
||||
PluginId get pluginId => "TrashStack";
|
||||
|
||||
@override
|
||||
PluginType get pluginType => _pluginType;
|
||||
}
|
||||
|
||||
class TrashPluginDisplay extends PluginDisplay {
|
||||
@override
|
||||
Widget get leftBarItem => FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12);
|
||||
|
||||
@ -32,21 +69,10 @@ class TrashStackContext extends HomeStackContext {
|
||||
Widget? get rightBarItem => null;
|
||||
|
||||
@override
|
||||
HomeStackType get type => HomeStackType.trash;
|
||||
|
||||
@override
|
||||
Widget buildWidget() {
|
||||
return const TrashStackPage(key: ValueKey('TrashStackPage'));
|
||||
}
|
||||
Widget buildWidget() => const TrashStackPage(key: ValueKey('TrashStackPage'));
|
||||
|
||||
@override
|
||||
List<NavigationItem> get navigationItems => [this];
|
||||
|
||||
@override
|
||||
ValueNotifier<bool> get isUpdated => _isUpdated;
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
class TrashStackPage extends StatefulWidget {
|
@ -1,97 +0,0 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:time/time.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
late FToast fToast;
|
||||
|
||||
// [[diagram: HomeStack's widget structure]]
|
||||
//
|
||||
// ┌──────────────────┐ ┌───────────────┐
|
||||
// ┌──│BlankStackContext │──▶│BlankStackPage │
|
||||
// ┌──────────┐ ┌───────────────────┐ ┌─────────────────┐ │ └──────────────────┘ └───────────────┘
|
||||
// │HomeStack │─▶│ HomeStackManager │──▶│HomeStackContext │◀─┤
|
||||
// └──────────┘ └───────────────────┘ └─────────────────┘ │ ┌─────────────────┐ ┌────────────┐
|
||||
// └──│ DocStackContext │───▶│DocStackPage│
|
||||
// └─────────────────┘ └────────────┘
|
||||
//
|
||||
//
|
||||
class HomeStack extends StatelessWidget {
|
||||
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
||||
// final Size size;
|
||||
const HomeStack({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Log.info('HomePage build');
|
||||
final theme = context.watch<AppTheme>();
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
getIt<HomeStackManager>().stackTopBar(),
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: theme.surface,
|
||||
child: FocusTraversalGroup(
|
||||
child: getIt<HomeStackManager>().stackWidget(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FadingIndexedStack extends StatefulWidget {
|
||||
final int index;
|
||||
final List<Widget> children;
|
||||
final Duration duration;
|
||||
|
||||
const FadingIndexedStack({
|
||||
Key? key,
|
||||
required this.index,
|
||||
required this.children,
|
||||
this.duration = const Duration(
|
||||
milliseconds: 250,
|
||||
),
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_FadingIndexedStackState createState() => _FadingIndexedStackState();
|
||||
}
|
||||
|
||||
class _FadingIndexedStackState extends State<FadingIndexedStack> {
|
||||
double _targetOpacity = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
fToast = FToast();
|
||||
fToast.init(HomeScreen.scaffoldKey.currentState!.context);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(FadingIndexedStack oldWidget) {
|
||||
if (oldWidget.index == widget.index) return;
|
||||
setState(() => _targetOpacity = 0);
|
||||
Future.delayed(1.milliseconds, () => setState(() => _targetOpacity = 1));
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TweenAnimationBuilder<double>(
|
||||
duration: _targetOpacity > 0 ? widget.duration : 0.milliseconds,
|
||||
tween: Tween(begin: 0, end: _targetOpacity),
|
||||
builder: (_, value, child) {
|
||||
return Opacity(opacity: value, child: child);
|
||||
},
|
||||
child: IndexedStack(index: widget.index, children: widget.children),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import 'package:app_flowy/workspace/application/edit_pannel/edit_pannel_bloc.dart';
|
||||
import 'package:app_flowy/workspace/domain/edit_context.dart';
|
||||
import 'package:app_flowy/workspace/application/edit_pannel/edit_context.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:app_flowy/workspace/presentation/plugins/doc/document.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/doc/widget/toolbar/toolbar_icon_button.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
|
||||
|
||||
class FlowyEmojiStyleButton extends StatefulWidget {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -16,7 +17,6 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:app_flowy/workspace/presentation/stack_page/home_stack.dart';
|
||||
|
||||
class QuestionBubble extends StatelessWidget {
|
||||
const QuestionBubble({Key? key}) : super(key: key);
|
||||
|
@ -1,67 +0,0 @@
|
||||
import 'package:app_flowy/workspace/domain/image.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/navigation.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class HomeTopBar extends StatelessWidget {
|
||||
const HomeTopBar({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return Container(
|
||||
color: theme.surface,
|
||||
height: HomeSizes.topBarHeight,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const FlowyNavigation(),
|
||||
const HSpace(16),
|
||||
ChangeNotifierProvider.value(
|
||||
value: Provider.of<HomeStackNotifier>(context, listen: false),
|
||||
child: Consumer(
|
||||
builder: (BuildContext context, HomeStackNotifier notifier, Widget? child) {
|
||||
return notifier.stackContext.rightBarItem ?? const SizedBox();
|
||||
},
|
||||
),
|
||||
) // _renderMoreButton(),
|
||||
],
|
||||
)
|
||||
.padding(
|
||||
horizontal: HomeInsets.topBarTitlePadding,
|
||||
)
|
||||
.bottomBorder(color: Colors.grey.shade300),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomeTitle extends StatelessWidget {
|
||||
final String title;
|
||||
final ViewType type;
|
||||
|
||||
const HomeTitle({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.type,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Flexible(
|
||||
child: Row(
|
||||
children: [
|
||||
Image(fit: BoxFit.scaleDown, width: 15, height: 15, image: assetImageForViewType(type)),
|
||||
const HSpace(6),
|
||||
FlowyText(title, fontSize: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export 'menu.dart';
|
@ -1,36 +0,0 @@
|
||||
import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
|
||||
class MenuTopBar extends StatelessWidget {
|
||||
const MenuTopBar({Key? key}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return BlocBuilder<MenuBloc, MenuState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: HomeSizes.topBarHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
(theme.isDark
|
||||
? svgWithSize("flowy_logo_dark_mode", const Size(92, 17))
|
||||
: svgWithSize("flowy_logo_with_text", const Size(92, 17))),
|
||||
const Spacer(),
|
||||
FlowyIconButton(
|
||||
width: 28,
|
||||
onPressed: () => context.read<MenuBloc>().add(const MenuEvent.collapse()),
|
||||
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||
icon: svg("home/hide_menu", color: theme.iconColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export '../stack_page/blank/blank_page.dart';
|
||||
export './edit_pannel/edit_pannel.dart';
|
||||
export './edit_pannel/pannel_animation.dart';
|
||||
export './home_top_bar.dart';
|
||||
export 'menu/menu.dart';
|
@ -271,14 +271,14 @@ class FolderEventOpenView {
|
||||
ViewId request;
|
||||
FolderEventOpenView(this.request);
|
||||
|
||||
Future<Either<DocumentDelta, FlowyError>> send() {
|
||||
Future<Either<BlockDelta, FlowyError>> send() {
|
||||
final request = FFIRequest.create()
|
||||
..event = FolderEvent.OpenView.toString()
|
||||
..payload = requestToBytes(this.request);
|
||||
|
||||
return Dispatch.asyncRequest(request)
|
||||
.then((bytesResult) => bytesResult.fold(
|
||||
(okBytes) => left(DocumentDelta.fromBuffer(okBytes)),
|
||||
(okBytes) => left(BlockDelta.fromBuffer(okBytes)),
|
||||
(errBytes) => right(FlowyError.fromBuffer(errBytes)),
|
||||
));
|
||||
}
|
||||
@ -378,17 +378,17 @@ class FolderEventDeleteAllTrash {
|
||||
}
|
||||
|
||||
class FolderEventApplyDocDelta {
|
||||
DocumentDelta request;
|
||||
BlockDelta request;
|
||||
FolderEventApplyDocDelta(this.request);
|
||||
|
||||
Future<Either<DocumentDelta, FlowyError>> send() {
|
||||
Future<Either<BlockDelta, FlowyError>> send() {
|
||||
final request = FFIRequest.create()
|
||||
..event = FolderEvent.ApplyDocDelta.toString()
|
||||
..payload = requestToBytes(this.request);
|
||||
|
||||
return Dispatch.asyncRequest(request)
|
||||
.then((bytesResult) => bytesResult.fold(
|
||||
(okBytes) => left(DocumentDelta.fromBuffer(okBytes)),
|
||||
(okBytes) => left(BlockDelta.fromBuffer(okBytes)),
|
||||
(errBytes) => right(FlowyError.fromBuffer(errBytes)),
|
||||
));
|
||||
}
|
||||
|
@ -12,15 +12,15 @@ import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'revision.pb.dart' as $0;
|
||||
|
||||
class CreateDocParams extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateDocParams', createEmptyInstance: create)
|
||||
class CreateBlockParams extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateBlockParams', createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
|
||||
..aOM<$0.RepeatedRevision>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'revisions', subBuilder: $0.RepeatedRevision.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
CreateDocParams._() : super();
|
||||
factory CreateDocParams({
|
||||
CreateBlockParams._() : super();
|
||||
factory CreateBlockParams({
|
||||
$core.String? id,
|
||||
$0.RepeatedRevision? revisions,
|
||||
}) {
|
||||
@ -33,26 +33,26 @@ class CreateDocParams extends $pb.GeneratedMessage {
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory CreateDocParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory CreateDocParams.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory CreateBlockParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory CreateBlockParams.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
CreateDocParams clone() => CreateDocParams()..mergeFromMessage(this);
|
||||
CreateBlockParams clone() => CreateBlockParams()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
CreateDocParams copyWith(void Function(CreateDocParams) updates) => super.copyWith((message) => updates(message as CreateDocParams)) as CreateDocParams; // ignore: deprecated_member_use
|
||||
CreateBlockParams copyWith(void Function(CreateBlockParams) updates) => super.copyWith((message) => updates(message as CreateBlockParams)) as CreateBlockParams; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static CreateDocParams create() => CreateDocParams._();
|
||||
CreateDocParams createEmptyInstance() => create();
|
||||
static $pb.PbList<CreateDocParams> createRepeated() => $pb.PbList<CreateDocParams>();
|
||||
static CreateBlockParams create() => CreateBlockParams._();
|
||||
CreateBlockParams createEmptyInstance() => create();
|
||||
static $pb.PbList<CreateBlockParams> createRepeated() => $pb.PbList<CreateBlockParams>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static CreateDocParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateDocParams>(create);
|
||||
static CreateDocParams? _defaultInstance;
|
||||
static CreateBlockParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateBlockParams>(create);
|
||||
static CreateBlockParams? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get id => $_getSZ(0);
|
||||
@ -75,8 +75,8 @@ class CreateDocParams extends $pb.GeneratedMessage {
|
||||
$0.RepeatedRevision ensureRevisions() => $_ensure(1);
|
||||
}
|
||||
|
||||
class DocumentInfo extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocumentInfo', createEmptyInstance: create)
|
||||
class BlockInfo extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'BlockInfo', createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'text')
|
||||
..aInt64(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'revId')
|
||||
@ -84,8 +84,8 @@ class DocumentInfo extends $pb.GeneratedMessage {
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
DocumentInfo._() : super();
|
||||
factory DocumentInfo({
|
||||
BlockInfo._() : super();
|
||||
factory BlockInfo({
|
||||
$core.String? docId,
|
||||
$core.String? text,
|
||||
$fixnum.Int64? revId,
|
||||
@ -106,26 +106,26 @@ class DocumentInfo extends $pb.GeneratedMessage {
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory DocumentInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory DocumentInfo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory BlockInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory BlockInfo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
DocumentInfo clone() => DocumentInfo()..mergeFromMessage(this);
|
||||
BlockInfo clone() => BlockInfo()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
DocumentInfo copyWith(void Function(DocumentInfo) updates) => super.copyWith((message) => updates(message as DocumentInfo)) as DocumentInfo; // ignore: deprecated_member_use
|
||||
BlockInfo copyWith(void Function(BlockInfo) updates) => super.copyWith((message) => updates(message as BlockInfo)) as BlockInfo; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DocumentInfo create() => DocumentInfo._();
|
||||
DocumentInfo createEmptyInstance() => create();
|
||||
static $pb.PbList<DocumentInfo> createRepeated() => $pb.PbList<DocumentInfo>();
|
||||
static BlockInfo create() => BlockInfo._();
|
||||
BlockInfo createEmptyInstance() => create();
|
||||
static $pb.PbList<BlockInfo> createRepeated() => $pb.PbList<BlockInfo>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DocumentInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocumentInfo>(create);
|
||||
static DocumentInfo? _defaultInstance;
|
||||
static BlockInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BlockInfo>(create);
|
||||
static BlockInfo? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get docId => $_getSZ(0);
|
||||
@ -227,56 +227,56 @@ class ResetDocumentParams extends $pb.GeneratedMessage {
|
||||
$0.RepeatedRevision ensureRevisions() => $_ensure(1);
|
||||
}
|
||||
|
||||
class DocumentDelta extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocumentDelta', createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
|
||||
class BlockDelta extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'BlockDelta', createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deltaJson')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
DocumentDelta._() : super();
|
||||
factory DocumentDelta({
|
||||
$core.String? docId,
|
||||
BlockDelta._() : super();
|
||||
factory BlockDelta({
|
||||
$core.String? blockId,
|
||||
$core.String? deltaJson,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (docId != null) {
|
||||
_result.docId = docId;
|
||||
if (blockId != null) {
|
||||
_result.blockId = blockId;
|
||||
}
|
||||
if (deltaJson != null) {
|
||||
_result.deltaJson = deltaJson;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory DocumentDelta.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory DocumentDelta.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory BlockDelta.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory BlockDelta.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
DocumentDelta clone() => DocumentDelta()..mergeFromMessage(this);
|
||||
BlockDelta clone() => BlockDelta()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
DocumentDelta copyWith(void Function(DocumentDelta) updates) => super.copyWith((message) => updates(message as DocumentDelta)) as DocumentDelta; // ignore: deprecated_member_use
|
||||
BlockDelta copyWith(void Function(BlockDelta) updates) => super.copyWith((message) => updates(message as BlockDelta)) as BlockDelta; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DocumentDelta create() => DocumentDelta._();
|
||||
DocumentDelta createEmptyInstance() => create();
|
||||
static $pb.PbList<DocumentDelta> createRepeated() => $pb.PbList<DocumentDelta>();
|
||||
static BlockDelta create() => BlockDelta._();
|
||||
BlockDelta createEmptyInstance() => create();
|
||||
static $pb.PbList<BlockDelta> createRepeated() => $pb.PbList<BlockDelta>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DocumentDelta getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocumentDelta>(create);
|
||||
static DocumentDelta? _defaultInstance;
|
||||
static BlockDelta getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BlockDelta>(create);
|
||||
static BlockDelta? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get docId => $_getSZ(0);
|
||||
$core.String get blockId => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set docId($core.String v) { $_setString(0, v); }
|
||||
set blockId($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasDocId() => $_has(0);
|
||||
$core.bool hasBlockId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearDocId() => clearField(1);
|
||||
void clearBlockId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get deltaJson => $_getSZ(1);
|
||||
@ -363,14 +363,14 @@ class NewDocUser extends $pb.GeneratedMessage {
|
||||
void clearDocId() => clearField(3);
|
||||
}
|
||||
|
||||
class DocumentId extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocumentId', createEmptyInstance: create)
|
||||
class BlockId extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'BlockId', createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'value')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
DocumentId._() : super();
|
||||
factory DocumentId({
|
||||
BlockId._() : super();
|
||||
factory BlockId({
|
||||
$core.String? value,
|
||||
}) {
|
||||
final _result = create();
|
||||
@ -379,26 +379,26 @@ class DocumentId extends $pb.GeneratedMessage {
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory DocumentId.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory DocumentId.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory BlockId.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory BlockId.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
DocumentId clone() => DocumentId()..mergeFromMessage(this);
|
||||
BlockId clone() => BlockId()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
DocumentId copyWith(void Function(DocumentId) updates) => super.copyWith((message) => updates(message as DocumentId)) as DocumentId; // ignore: deprecated_member_use
|
||||
BlockId copyWith(void Function(BlockId) updates) => super.copyWith((message) => updates(message as BlockId)) as BlockId; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DocumentId create() => DocumentId._();
|
||||
DocumentId createEmptyInstance() => create();
|
||||
static $pb.PbList<DocumentId> createRepeated() => $pb.PbList<DocumentId>();
|
||||
static BlockId create() => BlockId._();
|
||||
BlockId createEmptyInstance() => create();
|
||||
static $pb.PbList<BlockId> createRepeated() => $pb.PbList<BlockId>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DocumentId getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocumentId>(create);
|
||||
static DocumentId? _defaultInstance;
|
||||
static BlockId getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BlockId>(create);
|
||||
static BlockId? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get value => $_getSZ(0);
|
||||
|
@ -8,20 +8,20 @@
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
@$core.Deprecated('Use createDocParamsDescriptor instead')
|
||||
const CreateDocParams$json = const {
|
||||
'1': 'CreateDocParams',
|
||||
@$core.Deprecated('Use createBlockParamsDescriptor instead')
|
||||
const CreateBlockParams$json = const {
|
||||
'1': 'CreateBlockParams',
|
||||
'2': const [
|
||||
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
|
||||
const {'1': 'revisions', '3': 2, '4': 1, '5': 11, '6': '.RepeatedRevision', '10': 'revisions'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `CreateDocParams`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List createDocParamsDescriptor = $convert.base64Decode('Cg9DcmVhdGVEb2NQYXJhbXMSDgoCaWQYASABKAlSAmlkEi8KCXJldmlzaW9ucxgCIAEoCzIRLlJlcGVhdGVkUmV2aXNpb25SCXJldmlzaW9ucw==');
|
||||
@$core.Deprecated('Use documentInfoDescriptor instead')
|
||||
const DocumentInfo$json = const {
|
||||
'1': 'DocumentInfo',
|
||||
/// Descriptor for `CreateBlockParams`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List createBlockParamsDescriptor = $convert.base64Decode('ChFDcmVhdGVCbG9ja1BhcmFtcxIOCgJpZBgBIAEoCVICaWQSLwoJcmV2aXNpb25zGAIgASgLMhEuUmVwZWF0ZWRSZXZpc2lvblIJcmV2aXNpb25z');
|
||||
@$core.Deprecated('Use blockInfoDescriptor instead')
|
||||
const BlockInfo$json = const {
|
||||
'1': 'BlockInfo',
|
||||
'2': const [
|
||||
const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'},
|
||||
const {'1': 'text', '3': 2, '4': 1, '5': 9, '10': 'text'},
|
||||
@ -30,8 +30,8 @@ const DocumentInfo$json = const {
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `DocumentInfo`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List documentInfoDescriptor = $convert.base64Decode('CgxEb2N1bWVudEluZm8SFQoGZG9jX2lkGAEgASgJUgVkb2NJZBISCgR0ZXh0GAIgASgJUgR0ZXh0EhUKBnJldl9pZBgDIAEoA1IFcmV2SWQSHgoLYmFzZV9yZXZfaWQYBCABKANSCWJhc2VSZXZJZA==');
|
||||
/// Descriptor for `BlockInfo`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List blockInfoDescriptor = $convert.base64Decode('CglCbG9ja0luZm8SFQoGZG9jX2lkGAEgASgJUgVkb2NJZBISCgR0ZXh0GAIgASgJUgR0ZXh0EhUKBnJldl9pZBgDIAEoA1IFcmV2SWQSHgoLYmFzZV9yZXZfaWQYBCABKANSCWJhc2VSZXZJZA==');
|
||||
@$core.Deprecated('Use resetDocumentParamsDescriptor instead')
|
||||
const ResetDocumentParams$json = const {
|
||||
'1': 'ResetDocumentParams',
|
||||
@ -43,17 +43,17 @@ const ResetDocumentParams$json = const {
|
||||
|
||||
/// Descriptor for `ResetDocumentParams`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List resetDocumentParamsDescriptor = $convert.base64Decode('ChNSZXNldERvY3VtZW50UGFyYW1zEhUKBmRvY19pZBgBIAEoCVIFZG9jSWQSLwoJcmV2aXNpb25zGAIgASgLMhEuUmVwZWF0ZWRSZXZpc2lvblIJcmV2aXNpb25z');
|
||||
@$core.Deprecated('Use documentDeltaDescriptor instead')
|
||||
const DocumentDelta$json = const {
|
||||
'1': 'DocumentDelta',
|
||||
@$core.Deprecated('Use blockDeltaDescriptor instead')
|
||||
const BlockDelta$json = const {
|
||||
'1': 'BlockDelta',
|
||||
'2': const [
|
||||
const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'},
|
||||
const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
|
||||
const {'1': 'delta_json', '3': 2, '4': 1, '5': 9, '10': 'deltaJson'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `DocumentDelta`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List documentDeltaDescriptor = $convert.base64Decode('Cg1Eb2N1bWVudERlbHRhEhUKBmRvY19pZBgBIAEoCVIFZG9jSWQSHQoKZGVsdGFfanNvbhgCIAEoCVIJZGVsdGFKc29u');
|
||||
/// Descriptor for `BlockDelta`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List blockDeltaDescriptor = $convert.base64Decode('CgpCbG9ja0RlbHRhEhkKCGJsb2NrX2lkGAEgASgJUgdibG9ja0lkEh0KCmRlbHRhX2pzb24YAiABKAlSCWRlbHRhSnNvbg==');
|
||||
@$core.Deprecated('Use newDocUserDescriptor instead')
|
||||
const NewDocUser$json = const {
|
||||
'1': 'NewDocUser',
|
||||
@ -66,13 +66,13 @@ const NewDocUser$json = const {
|
||||
|
||||
/// Descriptor for `NewDocUser`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List newDocUserDescriptor = $convert.base64Decode('CgpOZXdEb2NVc2VyEhcKB3VzZXJfaWQYASABKAlSBnVzZXJJZBIVCgZyZXZfaWQYAiABKANSBXJldklkEhUKBmRvY19pZBgDIAEoCVIFZG9jSWQ=');
|
||||
@$core.Deprecated('Use documentIdDescriptor instead')
|
||||
const DocumentId$json = const {
|
||||
'1': 'DocumentId',
|
||||
@$core.Deprecated('Use blockIdDescriptor instead')
|
||||
const BlockId$json = const {
|
||||
'1': 'BlockId',
|
||||
'2': const [
|
||||
const {'1': 'value', '3': 1, '4': 1, '5': 9, '10': 'value'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `DocumentId`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List documentIdDescriptor = $convert.base64Decode('CgpEb2N1bWVudElkEhQKBXZhbHVlGAEgASgJUgV2YWx1ZQ==');
|
||||
/// Descriptor for `BlockId`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List blockIdDescriptor = $convert.base64Decode('CgdCbG9ja0lkEhQKBXZhbHVlGAEgASgJUgV2YWx1ZQ==');
|
||||
|
@ -15,19 +15,19 @@ export 'share.pbenum.dart';
|
||||
|
||||
class ExportPayload extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ExportPayload', createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewId')
|
||||
..e<ExportType>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'exportType', $pb.PbFieldType.OE, defaultOrMaker: ExportType.Text, valueOf: ExportType.valueOf, enumValues: ExportType.values)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
ExportPayload._() : super();
|
||||
factory ExportPayload({
|
||||
$core.String? docId,
|
||||
$core.String? viewId,
|
||||
ExportType? exportType,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (docId != null) {
|
||||
_result.docId = docId;
|
||||
if (viewId != null) {
|
||||
_result.viewId = viewId;
|
||||
}
|
||||
if (exportType != null) {
|
||||
_result.exportType = exportType;
|
||||
@ -56,13 +56,13 @@ class ExportPayload extends $pb.GeneratedMessage {
|
||||
static ExportPayload? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get docId => $_getSZ(0);
|
||||
$core.String get viewId => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set docId($core.String v) { $_setString(0, v); }
|
||||
set viewId($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasDocId() => $_has(0);
|
||||
$core.bool hasViewId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearDocId() => clearField(1);
|
||||
void clearViewId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
ExportType get exportType => $_getN(1);
|
||||
|
@ -24,13 +24,13 @@ final $typed_data.Uint8List exportTypeDescriptor = $convert.base64Decode('CgpFeH
|
||||
const ExportPayload$json = const {
|
||||
'1': 'ExportPayload',
|
||||
'2': const [
|
||||
const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'},
|
||||
const {'1': 'view_id', '3': 1, '4': 1, '5': 9, '10': 'viewId'},
|
||||
const {'1': 'export_type', '3': 2, '4': 1, '5': 14, '6': '.ExportType', '10': 'exportType'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ExportPayload`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List exportPayloadDescriptor = $convert.base64Decode('Cg1FeHBvcnRQYXlsb2FkEhUKBmRvY19pZBgBIAEoCVIFZG9jSWQSLAoLZXhwb3J0X3R5cGUYAiABKA4yCy5FeHBvcnRUeXBlUgpleHBvcnRUeXBl');
|
||||
final $typed_data.Uint8List exportPayloadDescriptor = $convert.base64Decode('Cg1FeHBvcnRQYXlsb2FkEhcKB3ZpZXdfaWQYASABKAlSBnZpZXdJZBIsCgtleHBvcnRfdHlwZRgCIAEoDjILLkV4cG9ydFR5cGVSCmV4cG9ydFR5cGU=');
|
||||
@$core.Deprecated('Use exportDataDescriptor instead')
|
||||
const ExportData$json = const {
|
||||
'1': 'ExportData',
|
||||
|
@ -20,11 +20,14 @@ class View extends $pb.GeneratedMessage {
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongToId')
|
||||
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
|
||||
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
|
||||
..e<ViewType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewType', $pb.PbFieldType.OE, defaultOrMaker: ViewType.Blank, valueOf: ViewType.valueOf, enumValues: ViewType.values)
|
||||
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.RichText, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
|
||||
..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'version')
|
||||
..aOM<RepeatedView>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongings', subBuilder: RepeatedView.create)
|
||||
..aInt64(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
|
||||
..aInt64(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createTime')
|
||||
..aOS(10, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
|
||||
..aOS(11, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
|
||||
..a<$core.int>(12, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pluginType', $pb.PbFieldType.O3)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@ -34,11 +37,14 @@ class View extends $pb.GeneratedMessage {
|
||||
$core.String? belongToId,
|
||||
$core.String? name,
|
||||
$core.String? desc,
|
||||
ViewType? viewType,
|
||||
ViewDataType? dataType,
|
||||
$fixnum.Int64? version,
|
||||
RepeatedView? belongings,
|
||||
$fixnum.Int64? modifiedTime,
|
||||
$fixnum.Int64? createTime,
|
||||
$core.String? extData,
|
||||
$core.String? thumbnail,
|
||||
$core.int? pluginType,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (id != null) {
|
||||
@ -53,8 +59,8 @@ class View extends $pb.GeneratedMessage {
|
||||
if (desc != null) {
|
||||
_result.desc = desc;
|
||||
}
|
||||
if (viewType != null) {
|
||||
_result.viewType = viewType;
|
||||
if (dataType != null) {
|
||||
_result.dataType = dataType;
|
||||
}
|
||||
if (version != null) {
|
||||
_result.version = version;
|
||||
@ -68,6 +74,15 @@ class View extends $pb.GeneratedMessage {
|
||||
if (createTime != null) {
|
||||
_result.createTime = createTime;
|
||||
}
|
||||
if (extData != null) {
|
||||
_result.extData = extData;
|
||||
}
|
||||
if (thumbnail != null) {
|
||||
_result.thumbnail = thumbnail;
|
||||
}
|
||||
if (pluginType != null) {
|
||||
_result.pluginType = pluginType;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory View.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
@ -128,13 +143,13 @@ class View extends $pb.GeneratedMessage {
|
||||
void clearDesc() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
ViewType get viewType => $_getN(4);
|
||||
ViewDataType get dataType => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set viewType(ViewType v) { setField(5, v); }
|
||||
set dataType(ViewDataType v) { setField(5, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasViewType() => $_has(4);
|
||||
$core.bool hasDataType() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearViewType() => clearField(5);
|
||||
void clearDataType() => clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$fixnum.Int64 get version => $_getI64(5);
|
||||
@ -173,6 +188,33 @@ class View extends $pb.GeneratedMessage {
|
||||
$core.bool hasCreateTime() => $_has(8);
|
||||
@$pb.TagNumber(9)
|
||||
void clearCreateTime() => clearField(9);
|
||||
|
||||
@$pb.TagNumber(10)
|
||||
$core.String get extData => $_getSZ(9);
|
||||
@$pb.TagNumber(10)
|
||||
set extData($core.String v) { $_setString(9, v); }
|
||||
@$pb.TagNumber(10)
|
||||
$core.bool hasExtData() => $_has(9);
|
||||
@$pb.TagNumber(10)
|
||||
void clearExtData() => clearField(10);
|
||||
|
||||
@$pb.TagNumber(11)
|
||||
$core.String get thumbnail => $_getSZ(10);
|
||||
@$pb.TagNumber(11)
|
||||
set thumbnail($core.String v) { $_setString(10, v); }
|
||||
@$pb.TagNumber(11)
|
||||
$core.bool hasThumbnail() => $_has(10);
|
||||
@$pb.TagNumber(11)
|
||||
void clearThumbnail() => clearField(11);
|
||||
|
||||
@$pb.TagNumber(12)
|
||||
$core.int get pluginType => $_getIZ(11);
|
||||
@$pb.TagNumber(12)
|
||||
set pluginType($core.int v) { $_setSignedInt32(11, v); }
|
||||
@$pb.TagNumber(12)
|
||||
$core.bool hasPluginType() => $_has(11);
|
||||
@$pb.TagNumber(12)
|
||||
void clearPluginType() => clearField(12);
|
||||
}
|
||||
|
||||
class RepeatedView extends $pb.GeneratedMessage {
|
||||
@ -232,7 +274,9 @@ class CreateViewPayload extends $pb.GeneratedMessage {
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
|
||||
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
|
||||
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
|
||||
..e<ViewType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewType', $pb.PbFieldType.OE, defaultOrMaker: ViewType.Blank, valueOf: ViewType.valueOf, enumValues: ViewType.values)
|
||||
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.RichText, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
|
||||
..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
|
||||
..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pluginType', $pb.PbFieldType.O3)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@ -242,7 +286,9 @@ class CreateViewPayload extends $pb.GeneratedMessage {
|
||||
$core.String? name,
|
||||
$core.String? desc,
|
||||
$core.String? thumbnail,
|
||||
ViewType? viewType,
|
||||
ViewDataType? dataType,
|
||||
$core.String? extData,
|
||||
$core.int? pluginType,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (belongToId != null) {
|
||||
@ -257,8 +303,14 @@ class CreateViewPayload extends $pb.GeneratedMessage {
|
||||
if (thumbnail != null) {
|
||||
_result.thumbnail = thumbnail;
|
||||
}
|
||||
if (viewType != null) {
|
||||
_result.viewType = viewType;
|
||||
if (dataType != null) {
|
||||
_result.dataType = dataType;
|
||||
}
|
||||
if (extData != null) {
|
||||
_result.extData = extData;
|
||||
}
|
||||
if (pluginType != null) {
|
||||
_result.pluginType = pluginType;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
@ -323,13 +375,31 @@ class CreateViewPayload extends $pb.GeneratedMessage {
|
||||
void clearThumbnail() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
ViewType get viewType => $_getN(4);
|
||||
ViewDataType get dataType => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set viewType(ViewType v) { setField(5, v); }
|
||||
set dataType(ViewDataType v) { setField(5, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasViewType() => $_has(4);
|
||||
$core.bool hasDataType() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearViewType() => clearField(5);
|
||||
void clearDataType() => clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.String get extData => $_getSZ(5);
|
||||
@$pb.TagNumber(6)
|
||||
set extData($core.String v) { $_setString(5, v); }
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasExtData() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearExtData() => clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$core.int get pluginType => $_getIZ(6);
|
||||
@$pb.TagNumber(7)
|
||||
set pluginType($core.int v) { $_setSignedInt32(6, v); }
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasPluginType() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearPluginType() => clearField(7);
|
||||
}
|
||||
|
||||
class CreateViewParams extends $pb.GeneratedMessage {
|
||||
@ -338,9 +408,11 @@ class CreateViewParams extends $pb.GeneratedMessage {
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
|
||||
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
|
||||
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
|
||||
..e<ViewType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewType', $pb.PbFieldType.OE, defaultOrMaker: ViewType.Blank, valueOf: ViewType.valueOf, enumValues: ViewType.values)
|
||||
..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewData')
|
||||
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.RichText, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
|
||||
..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
|
||||
..aOS(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewId')
|
||||
..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
|
||||
..a<$core.int>(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pluginType', $pb.PbFieldType.O3)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@ -350,9 +422,11 @@ class CreateViewParams extends $pb.GeneratedMessage {
|
||||
$core.String? name,
|
||||
$core.String? desc,
|
||||
$core.String? thumbnail,
|
||||
ViewType? viewType,
|
||||
$core.String? viewData,
|
||||
ViewDataType? dataType,
|
||||
$core.String? extData,
|
||||
$core.String? viewId,
|
||||
$core.String? data,
|
||||
$core.int? pluginType,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (belongToId != null) {
|
||||
@ -367,15 +441,21 @@ class CreateViewParams extends $pb.GeneratedMessage {
|
||||
if (thumbnail != null) {
|
||||
_result.thumbnail = thumbnail;
|
||||
}
|
||||
if (viewType != null) {
|
||||
_result.viewType = viewType;
|
||||
if (dataType != null) {
|
||||
_result.dataType = dataType;
|
||||
}
|
||||
if (viewData != null) {
|
||||
_result.viewData = viewData;
|
||||
if (extData != null) {
|
||||
_result.extData = extData;
|
||||
}
|
||||
if (viewId != null) {
|
||||
_result.viewId = viewId;
|
||||
}
|
||||
if (data != null) {
|
||||
_result.data = data;
|
||||
}
|
||||
if (pluginType != null) {
|
||||
_result.pluginType = pluginType;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory CreateViewParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
@ -436,22 +516,22 @@ class CreateViewParams extends $pb.GeneratedMessage {
|
||||
void clearThumbnail() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
ViewType get viewType => $_getN(4);
|
||||
ViewDataType get dataType => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set viewType(ViewType v) { setField(5, v); }
|
||||
set dataType(ViewDataType v) { setField(5, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasViewType() => $_has(4);
|
||||
$core.bool hasDataType() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearViewType() => clearField(5);
|
||||
void clearDataType() => clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.String get viewData => $_getSZ(5);
|
||||
$core.String get extData => $_getSZ(5);
|
||||
@$pb.TagNumber(6)
|
||||
set viewData($core.String v) { $_setString(5, v); }
|
||||
set extData($core.String v) { $_setString(5, v); }
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasViewData() => $_has(5);
|
||||
$core.bool hasExtData() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearViewData() => clearField(6);
|
||||
void clearExtData() => clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$core.String get viewId => $_getSZ(6);
|
||||
@ -461,6 +541,24 @@ class CreateViewParams extends $pb.GeneratedMessage {
|
||||
$core.bool hasViewId() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearViewId() => clearField(7);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.String get data => $_getSZ(7);
|
||||
@$pb.TagNumber(8)
|
||||
set data($core.String v) { $_setString(7, v); }
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool hasData() => $_has(7);
|
||||
@$pb.TagNumber(8)
|
||||
void clearData() => clearField(8);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
$core.int get pluginType => $_getIZ(8);
|
||||
@$pb.TagNumber(9)
|
||||
set pluginType($core.int v) { $_setSignedInt32(8, v); }
|
||||
@$pb.TagNumber(9)
|
||||
$core.bool hasPluginType() => $_has(8);
|
||||
@$pb.TagNumber(9)
|
||||
void clearPluginType() => clearField(9);
|
||||
}
|
||||
|
||||
class ViewId extends $pb.GeneratedMessage {
|
||||
|
@ -9,18 +9,18 @@
|
||||
import 'dart:core' as $core;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class ViewType extends $pb.ProtobufEnum {
|
||||
static const ViewType Blank = ViewType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Blank');
|
||||
static const ViewType Doc = ViewType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Doc');
|
||||
class ViewDataType extends $pb.ProtobufEnum {
|
||||
static const ViewDataType RichText = ViewDataType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RichText');
|
||||
static const ViewDataType PlainText = ViewDataType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PlainText');
|
||||
|
||||
static const $core.List<ViewType> values = <ViewType> [
|
||||
Blank,
|
||||
Doc,
|
||||
static const $core.List<ViewDataType> values = <ViewDataType> [
|
||||
RichText,
|
||||
PlainText,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, ViewType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static ViewType? valueOf($core.int value) => _byValue[value];
|
||||
static final $core.Map<$core.int, ViewDataType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static ViewDataType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const ViewType._($core.int v, $core.String n) : super(v, n);
|
||||
const ViewDataType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
|
@ -8,17 +8,17 @@
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
@$core.Deprecated('Use viewTypeDescriptor instead')
|
||||
const ViewType$json = const {
|
||||
'1': 'ViewType',
|
||||
@$core.Deprecated('Use viewDataTypeDescriptor instead')
|
||||
const ViewDataType$json = const {
|
||||
'1': 'ViewDataType',
|
||||
'2': const [
|
||||
const {'1': 'Blank', '2': 0},
|
||||
const {'1': 'Doc', '2': 1},
|
||||
const {'1': 'RichText', '2': 0},
|
||||
const {'1': 'PlainText', '2': 1},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ViewType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List viewTypeDescriptor = $convert.base64Decode('CghWaWV3VHlwZRIJCgVCbGFuaxAAEgcKA0RvYxAB');
|
||||
/// Descriptor for `ViewDataType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List viewDataTypeDescriptor = $convert.base64Decode('CgxWaWV3RGF0YVR5cGUSDAoIUmljaFRleHQQABINCglQbGFpblRleHQQAQ==');
|
||||
@$core.Deprecated('Use viewDescriptor instead')
|
||||
const View$json = const {
|
||||
'1': 'View',
|
||||
@ -27,16 +27,19 @@ const View$json = const {
|
||||
const {'1': 'belong_to_id', '3': 2, '4': 1, '5': 9, '10': 'belongToId'},
|
||||
const {'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'},
|
||||
const {'1': 'desc', '3': 4, '4': 1, '5': 9, '10': 'desc'},
|
||||
const {'1': 'view_type', '3': 5, '4': 1, '5': 14, '6': '.ViewType', '10': 'viewType'},
|
||||
const {'1': 'data_type', '3': 5, '4': 1, '5': 14, '6': '.ViewDataType', '10': 'dataType'},
|
||||
const {'1': 'version', '3': 6, '4': 1, '5': 3, '10': 'version'},
|
||||
const {'1': 'belongings', '3': 7, '4': 1, '5': 11, '6': '.RepeatedView', '10': 'belongings'},
|
||||
const {'1': 'modified_time', '3': 8, '4': 1, '5': 3, '10': 'modifiedTime'},
|
||||
const {'1': 'create_time', '3': 9, '4': 1, '5': 3, '10': 'createTime'},
|
||||
const {'1': 'ext_data', '3': 10, '4': 1, '5': 9, '10': 'extData'},
|
||||
const {'1': 'thumbnail', '3': 11, '4': 1, '5': 9, '10': 'thumbnail'},
|
||||
const {'1': 'plugin_type', '3': 12, '4': 1, '5': 5, '10': 'pluginType'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `View`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List viewDescriptor = $convert.base64Decode('CgRWaWV3Eg4KAmlkGAEgASgJUgJpZBIgCgxiZWxvbmdfdG9faWQYAiABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNjEiYKCXZpZXdfdHlwZRgFIAEoDjIJLlZpZXdUeXBlUgh2aWV3VHlwZRIYCgd2ZXJzaW9uGAYgASgDUgd2ZXJzaW9uEi0KCmJlbG9uZ2luZ3MYByABKAsyDS5SZXBlYXRlZFZpZXdSCmJlbG9uZ2luZ3MSIwoNbW9kaWZpZWRfdGltZRgIIAEoA1IMbW9kaWZpZWRUaW1lEh8KC2NyZWF0ZV90aW1lGAkgASgDUgpjcmVhdGVUaW1l');
|
||||
final $typed_data.Uint8List viewDescriptor = $convert.base64Decode('CgRWaWV3Eg4KAmlkGAEgASgJUgJpZBIgCgxiZWxvbmdfdG9faWQYAiABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNjEioKCWRhdGFfdHlwZRgFIAEoDjINLlZpZXdEYXRhVHlwZVIIZGF0YVR5cGUSGAoHdmVyc2lvbhgGIAEoA1IHdmVyc2lvbhItCgpiZWxvbmdpbmdzGAcgASgLMg0uUmVwZWF0ZWRWaWV3UgpiZWxvbmdpbmdzEiMKDW1vZGlmaWVkX3RpbWUYCCABKANSDG1vZGlmaWVkVGltZRIfCgtjcmVhdGVfdGltZRgJIAEoA1IKY3JlYXRlVGltZRIZCghleHRfZGF0YRgKIAEoCVIHZXh0RGF0YRIcCgl0aHVtYm5haWwYCyABKAlSCXRodW1ibmFpbBIfCgtwbHVnaW5fdHlwZRgMIAEoBVIKcGx1Z2luVHlwZQ==');
|
||||
@$core.Deprecated('Use repeatedViewDescriptor instead')
|
||||
const RepeatedView$json = const {
|
||||
'1': 'RepeatedView',
|
||||
@ -55,7 +58,9 @@ const CreateViewPayload$json = const {
|
||||
const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
|
||||
const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
|
||||
const {'1': 'thumbnail', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'thumbnail'},
|
||||
const {'1': 'view_type', '3': 5, '4': 1, '5': 14, '6': '.ViewType', '10': 'viewType'},
|
||||
const {'1': 'data_type', '3': 5, '4': 1, '5': 14, '6': '.ViewDataType', '10': 'dataType'},
|
||||
const {'1': 'ext_data', '3': 6, '4': 1, '5': 9, '10': 'extData'},
|
||||
const {'1': 'plugin_type', '3': 7, '4': 1, '5': 5, '10': 'pluginType'},
|
||||
],
|
||||
'8': const [
|
||||
const {'1': 'one_of_thumbnail'},
|
||||
@ -63,7 +68,7 @@ const CreateViewPayload$json = const {
|
||||
};
|
||||
|
||||
/// Descriptor for `CreateViewPayload`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List createViewPayloadDescriptor = $convert.base64Decode('ChFDcmVhdGVWaWV3UGF5bG9hZBIgCgxiZWxvbmdfdG9faWQYASABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEh4KCXRodW1ibmFpbBgEIAEoCUgAUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBlQhIKEG9uZV9vZl90aHVtYm5haWw=');
|
||||
final $typed_data.Uint8List createViewPayloadDescriptor = $convert.base64Decode('ChFDcmVhdGVWaWV3UGF5bG9hZBIgCgxiZWxvbmdfdG9faWQYASABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEh4KCXRodW1ibmFpbBgEIAEoCUgAUgl0aHVtYm5haWwSKgoJZGF0YV90eXBlGAUgASgOMg0uVmlld0RhdGFUeXBlUghkYXRhVHlwZRIZCghleHRfZGF0YRgGIAEoCVIHZXh0RGF0YRIfCgtwbHVnaW5fdHlwZRgHIAEoBVIKcGx1Z2luVHlwZUISChBvbmVfb2ZfdGh1bWJuYWls');
|
||||
@$core.Deprecated('Use createViewParamsDescriptor instead')
|
||||
const CreateViewParams$json = const {
|
||||
'1': 'CreateViewParams',
|
||||
@ -72,14 +77,16 @@ const CreateViewParams$json = const {
|
||||
const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
|
||||
const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
|
||||
const {'1': 'thumbnail', '3': 4, '4': 1, '5': 9, '10': 'thumbnail'},
|
||||
const {'1': 'view_type', '3': 5, '4': 1, '5': 14, '6': '.ViewType', '10': 'viewType'},
|
||||
const {'1': 'view_data', '3': 6, '4': 1, '5': 9, '10': 'viewData'},
|
||||
const {'1': 'data_type', '3': 5, '4': 1, '5': 14, '6': '.ViewDataType', '10': 'dataType'},
|
||||
const {'1': 'ext_data', '3': 6, '4': 1, '5': 9, '10': 'extData'},
|
||||
const {'1': 'view_id', '3': 7, '4': 1, '5': 9, '10': 'viewId'},
|
||||
const {'1': 'data', '3': 8, '4': 1, '5': 9, '10': 'data'},
|
||||
const {'1': 'plugin_type', '3': 9, '4': 1, '5': 5, '10': 'pluginType'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `CreateViewParams`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBlEhsKCXZpZXdfZGF0YRgGIAEoCVIIdmlld0RhdGESFwoHdmlld19pZBgHIAEoCVIGdmlld0lk');
|
||||
final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSKgoJZGF0YV90eXBlGAUgASgOMg0uVmlld0RhdGFUeXBlUghkYXRhVHlwZRIZCghleHRfZGF0YRgGIAEoCVIHZXh0RGF0YRIXCgd2aWV3X2lkGAcgASgJUgZ2aWV3SWQSEgoEZGF0YRgIIAEoCVIEZGF0YRIfCgtwbHVnaW5fdHlwZRgJIAEoBVIKcGx1Z2luVHlwZQ==');
|
||||
@$core.Deprecated('Use viewIdDescriptor instead')
|
||||
const ViewId$json = const {
|
||||
'1': 'ViewId',
|
||||
|
@ -645,7 +645,7 @@ packages:
|
||||
name: js
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.3"
|
||||
version: "0.6.4"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -799,7 +799,7 @@ packages:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
version: "1.8.1"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1133,21 +1133,21 @@ packages:
|
||||
name: test
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.19.5"
|
||||
version: "1.20.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.8"
|
||||
version: "0.4.9"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
version: "0.4.11"
|
||||
textstyle_extensions:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1354,5 +1354,5 @@ packages:
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
sdks:
|
||||
dart: ">=2.15.0-116.0.dev <3.0.0"
|
||||
dart: ">=2.16.0-100.0.dev <3.0.0"
|
||||
flutter: ">=2.5.0"
|
||||
|
2
frontend/rust-lib/Cargo.lock
generated
2
frontend/rust-lib/Cargo.lock
generated
@ -1031,6 +1031,7 @@ dependencies = [
|
||||
"protobuf",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"unicode-segmentation",
|
||||
@ -1108,6 +1109,7 @@ name = "flowy-sync"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"dashmap",
|
||||
"diesel",
|
||||
|
@ -1,13 +1,14 @@
|
||||
use crate::queue::DocumentRevisionCompact;
|
||||
use crate::web_socket::{make_document_ws_manager, EditorCommandSender};
|
||||
use crate::queue::BlockRevisionCompact;
|
||||
use crate::web_socket::{make_block_ws_manager, EditorCommandSender};
|
||||
use crate::{
|
||||
errors::FlowyError,
|
||||
queue::{EditorCommand, EditorCommandQueue},
|
||||
DocumentUser, DocumentWSReceiver,
|
||||
queue::{EditBlockQueue, EditorCommand},
|
||||
BlockUser,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_collaboration::entities::ws_data::ServerRevisionWSData;
|
||||
use flowy_collaboration::{
|
||||
entities::{document_info::DocumentInfo, revision::Revision},
|
||||
entities::{document_info::BlockInfo, revision::Revision},
|
||||
errors::CollaborateResult,
|
||||
util::make_delta_from_revisions,
|
||||
};
|
||||
@ -19,10 +20,11 @@ use lib_ot::{
|
||||
core::{Interval, Operation},
|
||||
rich_text::{RichTextAttribute, RichTextDelta},
|
||||
};
|
||||
use lib_ws::WSConnectState;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
pub struct ClientDocumentEditor {
|
||||
pub struct ClientBlockEditor {
|
||||
pub doc_id: String,
|
||||
#[allow(dead_code)]
|
||||
rev_manager: Arc<RevisionManager>,
|
||||
@ -30,16 +32,16 @@ pub struct ClientDocumentEditor {
|
||||
edit_cmd_tx: EditorCommandSender,
|
||||
}
|
||||
|
||||
impl ClientDocumentEditor {
|
||||
impl ClientBlockEditor {
|
||||
pub(crate) async fn new(
|
||||
doc_id: &str,
|
||||
user: Arc<dyn DocumentUser>,
|
||||
user: Arc<dyn BlockUser>,
|
||||
mut rev_manager: RevisionManager,
|
||||
rev_web_socket: Arc<dyn RevisionWebSocket>,
|
||||
cloud_service: Arc<dyn RevisionCloudService>,
|
||||
) -> FlowyResult<Arc<Self>> {
|
||||
let document_info = rev_manager
|
||||
.load::<DocumentInfoBuilder, DocumentRevisionCompact>(cloud_service)
|
||||
.load::<BlockInfoBuilder, BlockRevisionCompact>(cloud_service)
|
||||
.await?;
|
||||
let delta = document_info.delta()?;
|
||||
let rev_manager = Arc::new(rev_manager);
|
||||
@ -47,7 +49,7 @@ impl ClientDocumentEditor {
|
||||
let user_id = user.user_id()?;
|
||||
|
||||
let edit_cmd_tx = spawn_edit_queue(user, rev_manager.clone(), delta);
|
||||
let ws_manager = make_document_ws_manager(
|
||||
let ws_manager = make_block_ws_manager(
|
||||
doc_id.clone(),
|
||||
user_id.clone(),
|
||||
edit_cmd_tx.clone(),
|
||||
@ -138,9 +140,9 @@ impl ClientDocumentEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn document_json(&self) -> FlowyResult<String> {
|
||||
pub async fn block_json(&self) -> FlowyResult<String> {
|
||||
let (ret, rx) = oneshot::channel::<CollaborateResult<String>>();
|
||||
let msg = EditorCommand::ReadDocumentAsJson { ret };
|
||||
let msg = EditorCommand::ReadBlockJson { ret };
|
||||
let _ = self.edit_cmd_tx.send(msg).await;
|
||||
let json = rx.await.map_err(internal_error)??;
|
||||
Ok(json)
|
||||
@ -163,34 +165,38 @@ impl ClientDocumentEditor {
|
||||
self.ws_manager.stop();
|
||||
}
|
||||
|
||||
pub(crate) fn ws_handler(&self) -> Arc<dyn DocumentWSReceiver> {
|
||||
self.ws_manager.clone()
|
||||
pub(crate) async fn receive_ws_data(&self, data: ServerRevisionWSData) -> Result<(), FlowyError> {
|
||||
self.ws_manager.receive_ws_data(data).await
|
||||
}
|
||||
|
||||
pub(crate) fn receive_ws_state(&self, state: &WSConnectState) {
|
||||
self.ws_manager.connect_state_changed(state.clone());
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Drop for ClientDocumentEditor {
|
||||
impl std::ops::Drop for ClientBlockEditor {
|
||||
fn drop(&mut self) {
|
||||
tracing::trace!("{} ClientDocumentEditor was dropped", self.doc_id)
|
||||
tracing::trace!("{} ClientBlockEditor was dropped", self.doc_id)
|
||||
}
|
||||
}
|
||||
|
||||
// The edit queue will exit after the EditorCommandSender was dropped.
|
||||
fn spawn_edit_queue(
|
||||
user: Arc<dyn DocumentUser>,
|
||||
user: Arc<dyn BlockUser>,
|
||||
rev_manager: Arc<RevisionManager>,
|
||||
delta: RichTextDelta,
|
||||
) -> EditorCommandSender {
|
||||
let (sender, receiver) = mpsc::channel(1000);
|
||||
let actor = EditorCommandQueue::new(user, rev_manager, delta, receiver);
|
||||
tokio::spawn(actor.run());
|
||||
let edit_queue = EditBlockQueue::new(user, rev_manager, delta, receiver);
|
||||
tokio::spawn(edit_queue.run());
|
||||
sender
|
||||
}
|
||||
|
||||
#[cfg(feature = "flowy_unit_test")]
|
||||
impl ClientDocumentEditor {
|
||||
impl ClientBlockEditor {
|
||||
pub async fn doc_json(&self) -> FlowyResult<String> {
|
||||
let (ret, rx) = oneshot::channel::<CollaborateResult<String>>();
|
||||
let msg = EditorCommand::ReadDocumentAsJson { ret };
|
||||
let msg = EditorCommand::ReadBlockJson { ret };
|
||||
let _ = self.edit_cmd_tx.send(msg).await;
|
||||
let s = rx.await.map_err(internal_error)??;
|
||||
Ok(s)
|
||||
@ -198,7 +204,7 @@ impl ClientDocumentEditor {
|
||||
|
||||
pub async fn doc_delta(&self) -> FlowyResult<RichTextDelta> {
|
||||
let (ret, rx) = oneshot::channel::<CollaborateResult<RichTextDelta>>();
|
||||
let msg = EditorCommand::ReadDocumentAsDelta { ret };
|
||||
let msg = EditorCommand::ReadBlockDelta { ret };
|
||||
let _ = self.edit_cmd_tx.send(msg).await;
|
||||
let delta = rx.await.map_err(internal_error)??;
|
||||
Ok(delta)
|
||||
@ -209,18 +215,18 @@ impl ClientDocumentEditor {
|
||||
}
|
||||
}
|
||||
|
||||
struct DocumentInfoBuilder();
|
||||
impl RevisionObjectBuilder for DocumentInfoBuilder {
|
||||
type Output = DocumentInfo;
|
||||
struct BlockInfoBuilder();
|
||||
impl RevisionObjectBuilder for BlockInfoBuilder {
|
||||
type Output = BlockInfo;
|
||||
|
||||
fn build_object(object_id: &str, revisions: Vec<Revision>) -> FlowyResult<Self::Output> {
|
||||
let (base_rev_id, rev_id) = revisions.last().unwrap().pair_rev_id();
|
||||
let mut delta = make_delta_from_revisions(revisions)?;
|
||||
correct_delta(&mut delta);
|
||||
|
||||
Result::<DocumentInfo, FlowyError>::Ok(DocumentInfo {
|
||||
Result::<BlockInfo, FlowyError>::Ok(BlockInfo {
|
||||
doc_id: object_id.to_owned(),
|
||||
text: delta.to_json(),
|
||||
text: delta.to_delta_json(),
|
||||
rev_id,
|
||||
base_rev_id,
|
||||
})
|
@ -1,4 +1,4 @@
|
||||
pub mod editor;
|
||||
pub mod block_editor;
|
||||
pub mod manager;
|
||||
mod queue;
|
||||
mod web_socket;
|
||||
@ -11,13 +11,13 @@ pub mod errors {
|
||||
pub const DOCUMENT_SYNC_INTERVAL_IN_MILLIS: u64 = 1000;
|
||||
|
||||
use crate::errors::FlowyError;
|
||||
use flowy_collaboration::entities::document_info::{CreateDocParams, DocumentId, DocumentInfo, ResetDocumentParams};
|
||||
use flowy_collaboration::entities::document_info::{BlockId, BlockInfo, CreateBlockParams, ResetDocumentParams};
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
pub trait DocumentCloudService: Send + Sync {
|
||||
fn create_document(&self, token: &str, params: CreateDocParams) -> FutureResult<(), FlowyError>;
|
||||
pub trait BlockCloudService: Send + Sync {
|
||||
fn create_block(&self, token: &str, params: CreateBlockParams) -> FutureResult<(), FlowyError>;
|
||||
|
||||
fn read_document(&self, token: &str, params: DocumentId) -> FutureResult<Option<DocumentInfo>, FlowyError>;
|
||||
fn read_block(&self, token: &str, params: BlockId) -> FutureResult<Option<BlockInfo>, FlowyError>;
|
||||
|
||||
fn update_document(&self, token: &str, params: ResetDocumentParams) -> FutureResult<(), FlowyError>;
|
||||
fn update_block(&self, token: &str, params: ResetDocumentParams) -> FutureResult<(), FlowyError>;
|
||||
}
|
||||
|
@ -1,76 +1,64 @@
|
||||
use crate::{editor::ClientDocumentEditor, errors::FlowyError, DocumentCloudService};
|
||||
use async_trait::async_trait;
|
||||
use crate::{block_editor::ClientBlockEditor, errors::FlowyError, BlockCloudService};
|
||||
use bytes::Bytes;
|
||||
use dashmap::DashMap;
|
||||
use flowy_collaboration::entities::{
|
||||
document_info::{DocumentDelta, DocumentId},
|
||||
document_info::{BlockDelta, BlockId},
|
||||
revision::{md5, RepeatedRevision, Revision},
|
||||
ws_data::ServerRevisionWSData,
|
||||
};
|
||||
use flowy_database::ConnectionPool;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_sync::{RevisionCache, RevisionCloudService, RevisionManager, RevisionWebSocket};
|
||||
use flowy_sync::{RevisionCloudService, RevisionManager, RevisionPersistence, RevisionWebSocket};
|
||||
use lib_infra::future::FutureResult;
|
||||
use lib_ws::WSConnectState;
|
||||
use std::{convert::TryInto, sync::Arc};
|
||||
|
||||
pub trait DocumentUser: Send + Sync {
|
||||
pub trait BlockUser: Send + Sync {
|
||||
fn user_dir(&self) -> Result<String, FlowyError>;
|
||||
fn user_id(&self) -> Result<String, FlowyError>;
|
||||
fn token(&self) -> Result<String, FlowyError>;
|
||||
fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub(crate) trait DocumentWSReceiver: Send + Sync {
|
||||
async fn receive_ws_data(&self, data: ServerRevisionWSData) -> Result<(), FlowyError>;
|
||||
fn connect_state_changed(&self, state: WSConnectState);
|
||||
}
|
||||
type WebSocketDataReceivers = Arc<DashMap<String, Arc<dyn DocumentWSReceiver>>>;
|
||||
pub struct FlowyDocumentManager {
|
||||
cloud_service: Arc<dyn DocumentCloudService>,
|
||||
ws_data_receivers: WebSocketDataReceivers,
|
||||
pub struct BlockManager {
|
||||
cloud_service: Arc<dyn BlockCloudService>,
|
||||
rev_web_socket: Arc<dyn RevisionWebSocket>,
|
||||
document_handlers: Arc<DocumentEditorHandlers>,
|
||||
document_user: Arc<dyn DocumentUser>,
|
||||
block_editors: Arc<BlockEditors>,
|
||||
block_user: Arc<dyn BlockUser>,
|
||||
}
|
||||
|
||||
impl FlowyDocumentManager {
|
||||
impl BlockManager {
|
||||
pub fn new(
|
||||
cloud_service: Arc<dyn DocumentCloudService>,
|
||||
document_user: Arc<dyn DocumentUser>,
|
||||
cloud_service: Arc<dyn BlockCloudService>,
|
||||
block_user: Arc<dyn BlockUser>,
|
||||
rev_web_socket: Arc<dyn RevisionWebSocket>,
|
||||
) -> Self {
|
||||
let ws_data_receivers = Arc::new(DashMap::new());
|
||||
let document_handlers = Arc::new(DocumentEditorHandlers::new());
|
||||
let block_handlers = Arc::new(BlockEditors::new());
|
||||
Self {
|
||||
cloud_service,
|
||||
ws_data_receivers,
|
||||
rev_web_socket,
|
||||
document_handlers,
|
||||
document_user,
|
||||
block_editors: block_handlers,
|
||||
block_user,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&self) -> FlowyResult<()> {
|
||||
listen_ws_state_changed(self.rev_web_socket.clone(), self.ws_data_receivers.clone());
|
||||
listen_ws_state_changed(self.rev_web_socket.clone(), self.block_editors.clone());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self, doc_id), fields(doc_id), err)]
|
||||
pub async fn open_document<T: AsRef<str>>(&self, doc_id: T) -> Result<Arc<ClientDocumentEditor>, FlowyError> {
|
||||
let doc_id = doc_id.as_ref();
|
||||
tracing::Span::current().record("doc_id", &doc_id);
|
||||
self.get_editor(doc_id).await
|
||||
#[tracing::instrument(level = "debug", skip(self, block_id), fields(block_id), err)]
|
||||
pub async fn open_block<T: AsRef<str>>(&self, block_id: T) -> Result<Arc<ClientBlockEditor>, FlowyError> {
|
||||
let block_id = block_id.as_ref();
|
||||
tracing::Span::current().record("block_id", &block_id);
|
||||
self.get_block_editor(block_id).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self, doc_id), fields(doc_id), err)]
|
||||
pub fn close_document<T: AsRef<str>>(&self, doc_id: T) -> Result<(), FlowyError> {
|
||||
let doc_id = doc_id.as_ref();
|
||||
tracing::Span::current().record("doc_id", &doc_id);
|
||||
self.document_handlers.remove(doc_id);
|
||||
self.ws_data_receivers.remove(doc_id);
|
||||
#[tracing::instrument(level = "trace", skip(self, block_id), fields(block_id), err)]
|
||||
pub fn close_block<T: AsRef<str>>(&self, block_id: T) -> Result<(), FlowyError> {
|
||||
let block_id = block_id.as_ref();
|
||||
tracing::Span::current().record("block_id", &block_id);
|
||||
self.block_editors.remove(block_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -78,25 +66,25 @@ impl FlowyDocumentManager {
|
||||
pub fn delete<T: AsRef<str>>(&self, doc_id: T) -> Result<(), FlowyError> {
|
||||
let doc_id = doc_id.as_ref();
|
||||
tracing::Span::current().record("doc_id", &doc_id);
|
||||
self.document_handlers.remove(doc_id);
|
||||
self.ws_data_receivers.remove(doc_id);
|
||||
self.block_editors.remove(doc_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self, delta), fields(doc_id = %delta.doc_id), err)]
|
||||
pub async fn receive_local_delta(&self, delta: DocumentDelta) -> Result<DocumentDelta, FlowyError> {
|
||||
let editor = self.get_editor(&delta.doc_id).await?;
|
||||
#[tracing::instrument(level = "debug", skip(self, delta), fields(doc_id = %delta.block_id), err)]
|
||||
pub async fn receive_local_delta(&self, delta: BlockDelta) -> Result<BlockDelta, FlowyError> {
|
||||
let editor = self.get_block_editor(&delta.block_id).await?;
|
||||
let _ = editor.compose_local_delta(Bytes::from(delta.delta_json)).await?;
|
||||
let document_json = editor.document_json().await?;
|
||||
Ok(DocumentDelta {
|
||||
doc_id: delta.doc_id.clone(),
|
||||
let document_json = editor.block_json().await?;
|
||||
Ok(BlockDelta {
|
||||
block_id: delta.block_id.clone(),
|
||||
delta_json: document_json,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn reset_with_revisions<T: AsRef<str>>(&self, doc_id: T, revisions: RepeatedRevision) -> FlowyResult<()> {
|
||||
pub async fn create_block<T: AsRef<str>>(&self, doc_id: T, revisions: RepeatedRevision) -> FlowyResult<()> {
|
||||
let doc_id = doc_id.as_ref().to_owned();
|
||||
let db_pool = self.document_user.db_pool()?;
|
||||
let db_pool = self.block_user.db_pool()?;
|
||||
// Maybe we could save the block to disk without creating the RevisionManager
|
||||
let rev_manager = self.make_rev_manager(&doc_id, db_pool)?;
|
||||
let _ = rev_manager.reset_object(revisions).await?;
|
||||
Ok(())
|
||||
@ -105,9 +93,9 @@ impl FlowyDocumentManager {
|
||||
pub async fn receive_ws_data(&self, data: Bytes) {
|
||||
let result: Result<ServerRevisionWSData, protobuf::ProtobufError> = data.try_into();
|
||||
match result {
|
||||
Ok(data) => match self.ws_data_receivers.get(&data.object_id) {
|
||||
Ok(data) => match self.block_editors.get(&data.object_id) {
|
||||
None => tracing::error!("Can't find any source handler for {:?}-{:?}", data.object_id, data.ty),
|
||||
Some(handler) => match handler.receive_ws_data(data).await {
|
||||
Some(block_editor) => match block_editor.receive_ws_data(data).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => tracing::error!("{}", e),
|
||||
},
|
||||
@ -119,59 +107,57 @@ impl FlowyDocumentManager {
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowyDocumentManager {
|
||||
async fn get_editor(&self, doc_id: &str) -> FlowyResult<Arc<ClientDocumentEditor>> {
|
||||
match self.document_handlers.get(doc_id) {
|
||||
impl BlockManager {
|
||||
async fn get_block_editor(&self, block_id: &str) -> FlowyResult<Arc<ClientBlockEditor>> {
|
||||
match self.block_editors.get(block_id) {
|
||||
None => {
|
||||
let db_pool = self.document_user.db_pool()?;
|
||||
self.make_editor(doc_id, db_pool).await
|
||||
let db_pool = self.block_user.db_pool()?;
|
||||
self.make_block_editor(block_id, db_pool).await
|
||||
}
|
||||
Some(editor) => Ok(editor),
|
||||
}
|
||||
}
|
||||
|
||||
async fn make_editor(
|
||||
async fn make_block_editor(
|
||||
&self,
|
||||
doc_id: &str,
|
||||
block_id: &str,
|
||||
pool: Arc<ConnectionPool>,
|
||||
) -> Result<Arc<ClientDocumentEditor>, FlowyError> {
|
||||
let user = self.document_user.clone();
|
||||
let token = self.document_user.token()?;
|
||||
let rev_manager = self.make_rev_manager(doc_id, pool.clone())?;
|
||||
let cloud_service = Arc::new(DocumentRevisionCloudServiceImpl {
|
||||
) -> Result<Arc<ClientBlockEditor>, FlowyError> {
|
||||
let user = self.block_user.clone();
|
||||
let token = self.block_user.token()?;
|
||||
let rev_manager = self.make_rev_manager(block_id, pool.clone())?;
|
||||
let cloud_service = Arc::new(BlockRevisionCloudService {
|
||||
token,
|
||||
server: self.cloud_service.clone(),
|
||||
});
|
||||
let doc_editor =
|
||||
ClientDocumentEditor::new(doc_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?;
|
||||
self.ws_data_receivers
|
||||
.insert(doc_id.to_string(), doc_editor.ws_handler());
|
||||
self.document_handlers.insert(doc_id, &doc_editor);
|
||||
ClientBlockEditor::new(block_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?;
|
||||
self.block_editors.insert(block_id, &doc_editor);
|
||||
Ok(doc_editor)
|
||||
}
|
||||
|
||||
fn make_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> {
|
||||
let user_id = self.document_user.user_id()?;
|
||||
let cache = Arc::new(RevisionCache::new(&user_id, doc_id, pool));
|
||||
Ok(RevisionManager::new(&user_id, doc_id, cache))
|
||||
let user_id = self.block_user.user_id()?;
|
||||
let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, doc_id, pool));
|
||||
Ok(RevisionManager::new(&user_id, doc_id, rev_persistence))
|
||||
}
|
||||
}
|
||||
|
||||
struct DocumentRevisionCloudServiceImpl {
|
||||
struct BlockRevisionCloudService {
|
||||
token: String,
|
||||
server: Arc<dyn DocumentCloudService>,
|
||||
server: Arc<dyn BlockCloudService>,
|
||||
}
|
||||
|
||||
impl RevisionCloudService for DocumentRevisionCloudServiceImpl {
|
||||
impl RevisionCloudService for BlockRevisionCloudService {
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
fn fetch_object(&self, user_id: &str, object_id: &str) -> FutureResult<Vec<Revision>, FlowyError> {
|
||||
let params: DocumentId = object_id.to_string().into();
|
||||
let params: BlockId = object_id.to_string().into();
|
||||
let server = self.server.clone();
|
||||
let token = self.token.clone();
|
||||
let user_id = user_id.to_string();
|
||||
|
||||
FutureResult::new(async move {
|
||||
match server.read_document(&token, params).await? {
|
||||
match server.read_block(&token, params).await? {
|
||||
None => Err(FlowyError::record_not_found().context("Remote doesn't have this document")),
|
||||
Some(doc) => {
|
||||
let delta_data = Bytes::from(doc.text.clone());
|
||||
@ -185,51 +171,50 @@ impl RevisionCloudService for DocumentRevisionCloudServiceImpl {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DocumentEditorHandlers {
|
||||
inner: DashMap<String, Arc<ClientDocumentEditor>>,
|
||||
pub struct BlockEditors {
|
||||
inner: DashMap<String, Arc<ClientBlockEditor>>,
|
||||
}
|
||||
|
||||
impl DocumentEditorHandlers {
|
||||
impl BlockEditors {
|
||||
fn new() -> Self {
|
||||
Self { inner: DashMap::new() }
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&self, doc_id: &str, doc: &Arc<ClientDocumentEditor>) {
|
||||
if self.inner.contains_key(doc_id) {
|
||||
log::warn!("Doc:{} already exists in cache", doc_id);
|
||||
pub(crate) fn insert(&self, block_id: &str, doc: &Arc<ClientBlockEditor>) {
|
||||
if self.inner.contains_key(block_id) {
|
||||
log::warn!("Doc:{} already exists in cache", block_id);
|
||||
}
|
||||
self.inner.insert(doc_id.to_string(), doc.clone());
|
||||
self.inner.insert(block_id.to_string(), doc.clone());
|
||||
}
|
||||
|
||||
pub(crate) fn contains(&self, doc_id: &str) -> bool {
|
||||
self.inner.get(doc_id).is_some()
|
||||
pub(crate) fn contains(&self, block_id: &str) -> bool {
|
||||
self.inner.get(block_id).is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, doc_id: &str) -> Option<Arc<ClientDocumentEditor>> {
|
||||
if !self.contains(doc_id) {
|
||||
pub(crate) fn get(&self, block_id: &str) -> Option<Arc<ClientBlockEditor>> {
|
||||
if !self.contains(block_id) {
|
||||
return None;
|
||||
}
|
||||
let opened_doc = self.inner.get(doc_id).unwrap();
|
||||
let opened_doc = self.inner.get(block_id).unwrap();
|
||||
Some(opened_doc.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&self, id: &str) {
|
||||
let doc_id = id.to_string();
|
||||
if let Some(editor) = self.get(id) {
|
||||
pub(crate) fn remove(&self, block_id: &str) {
|
||||
if let Some(editor) = self.get(block_id) {
|
||||
editor.stop()
|
||||
}
|
||||
self.inner.remove(&doc_id);
|
||||
self.inner.remove(block_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(web_socket, receivers))]
|
||||
fn listen_ws_state_changed(web_socket: Arc<dyn RevisionWebSocket>, receivers: WebSocketDataReceivers) {
|
||||
#[tracing::instrument(level = "trace", skip(web_socket, handlers))]
|
||||
fn listen_ws_state_changed(web_socket: Arc<dyn RevisionWebSocket>, handlers: Arc<BlockEditors>) {
|
||||
tokio::spawn(async move {
|
||||
let mut notify = web_socket.subscribe_state_changed().await;
|
||||
while let Ok(state) = notify.recv().await {
|
||||
for receiver in receivers.iter() {
|
||||
receiver.value().connect_state_changed(state.clone());
|
||||
}
|
||||
handlers.inner.iter().for_each(|handler| {
|
||||
handler.receive_ws_state(&state);
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::web_socket::EditorCommandReceiver;
|
||||
use crate::DocumentUser;
|
||||
use crate::BlockUser;
|
||||
use async_stream::stream;
|
||||
use flowy_collaboration::util::make_delta_from_revisions;
|
||||
use flowy_collaboration::{
|
||||
@ -8,7 +8,7 @@ use flowy_collaboration::{
|
||||
errors::CollaborateError,
|
||||
};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_sync::{DeltaMD5, RevisionCompact, RevisionManager, TransformDeltas};
|
||||
use flowy_sync::{DeltaMD5, RevisionCompact, RevisionManager, RichTextTransformDeltas, TransformDeltas};
|
||||
use futures::stream::StreamExt;
|
||||
use lib_ot::{
|
||||
core::{Interval, OperationTransformable},
|
||||
@ -19,16 +19,16 @@ use tokio::sync::{oneshot, RwLock};
|
||||
|
||||
// The EditorCommandQueue executes each command that will alter the document in
|
||||
// serial.
|
||||
pub(crate) struct EditorCommandQueue {
|
||||
pub(crate) struct EditBlockQueue {
|
||||
document: Arc<RwLock<ClientDocument>>,
|
||||
user: Arc<dyn DocumentUser>,
|
||||
user: Arc<dyn BlockUser>,
|
||||
rev_manager: Arc<RevisionManager>,
|
||||
receiver: Option<EditorCommandReceiver>,
|
||||
}
|
||||
|
||||
impl EditorCommandQueue {
|
||||
impl EditBlockQueue {
|
||||
pub(crate) fn new(
|
||||
user: Arc<dyn DocumentUser>,
|
||||
user: Arc<dyn BlockUser>,
|
||||
rev_manager: Arc<RevisionManager>,
|
||||
delta: RichTextDelta,
|
||||
receiver: EditorCommandReceiver,
|
||||
@ -102,7 +102,7 @@ impl EditorCommandQueue {
|
||||
server_prime = Some(s_prime);
|
||||
}
|
||||
drop(read_guard);
|
||||
Ok::<TransformDeltas<RichTextAttributes>, CollaborateError>(TransformDeltas {
|
||||
Ok::<RichTextTransformDeltas, CollaborateError>(TransformDeltas {
|
||||
client_prime,
|
||||
server_prime,
|
||||
})
|
||||
@ -161,11 +161,11 @@ impl EditorCommandQueue {
|
||||
let _ = self.save_local_delta(delta, md5).await?;
|
||||
let _ = ret.send(Ok(()));
|
||||
}
|
||||
EditorCommand::ReadDocumentAsJson { ret } => {
|
||||
EditorCommand::ReadBlockJson { ret } => {
|
||||
let data = self.document.read().await.to_json();
|
||||
let _ = ret.send(Ok(data));
|
||||
}
|
||||
EditorCommand::ReadDocumentAsDelta { ret } => {
|
||||
EditorCommand::ReadBlockDelta { ret } => {
|
||||
let delta = self.document.read().await.delta().clone();
|
||||
let _ = ret.send(Ok(delta));
|
||||
}
|
||||
@ -187,17 +187,17 @@ impl EditorCommandQueue {
|
||||
);
|
||||
let _ = self
|
||||
.rev_manager
|
||||
.add_local_revision::<DocumentRevisionCompact>(&revision)
|
||||
.add_local_revision::<BlockRevisionCompact>(&revision)
|
||||
.await?;
|
||||
Ok(rev_id.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DocumentRevisionCompact();
|
||||
impl RevisionCompact for DocumentRevisionCompact {
|
||||
pub(crate) struct BlockRevisionCompact();
|
||||
impl RevisionCompact for BlockRevisionCompact {
|
||||
fn compact_revisions(user_id: &str, object_id: &str, mut revisions: Vec<Revision>) -> FlowyResult<Revision> {
|
||||
if revisions.is_empty() {
|
||||
return Err(FlowyError::internal().context("Can't compact the empty document's revisions"));
|
||||
return Err(FlowyError::internal().context("Can't compact the empty block's revisions"));
|
||||
}
|
||||
|
||||
if revisions.len() == 1 {
|
||||
@ -232,7 +232,7 @@ pub(crate) enum EditorCommand {
|
||||
},
|
||||
TransformDelta {
|
||||
delta: RichTextDelta,
|
||||
ret: Ret<TransformDeltas<RichTextAttributes>>,
|
||||
ret: Ret<RichTextTransformDeltas>,
|
||||
},
|
||||
Insert {
|
||||
index: usize,
|
||||
@ -265,11 +265,11 @@ pub(crate) enum EditorCommand {
|
||||
Redo {
|
||||
ret: Ret<()>,
|
||||
},
|
||||
ReadDocumentAsJson {
|
||||
ReadBlockJson {
|
||||
ret: Ret<String>,
|
||||
},
|
||||
#[allow(dead_code)]
|
||||
ReadDocumentAsDelta {
|
||||
ReadBlockDelta {
|
||||
ret: Ret<RichTextDelta>,
|
||||
},
|
||||
}
|
||||
@ -289,8 +289,8 @@ impl std::fmt::Debug for EditorCommand {
|
||||
EditorCommand::CanRedo { .. } => "CanRedo",
|
||||
EditorCommand::Undo { .. } => "Undo",
|
||||
EditorCommand::Redo { .. } => "Redo",
|
||||
EditorCommand::ReadDocumentAsJson { .. } => "ReadDocumentAsJson",
|
||||
EditorCommand::ReadDocumentAsDelta { .. } => "ReadDocumentAsDelta",
|
||||
EditorCommand::ReadBlockJson { .. } => "ReadDocumentAsJson",
|
||||
EditorCommand::ReadBlockDelta { .. } => "ReadDocumentAsDelta",
|
||||
};
|
||||
f.write_str(s)
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
use crate::{queue::EditorCommand, DocumentWSReceiver, DOCUMENT_SYNC_INTERVAL_IN_MILLIS};
|
||||
use async_trait::async_trait;
|
||||
use crate::{queue::EditorCommand, DOCUMENT_SYNC_INTERVAL_IN_MILLIS};
|
||||
use bytes::Bytes;
|
||||
use flowy_collaboration::{
|
||||
entities::{
|
||||
revision::RevisionRange,
|
||||
ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSData, ServerRevisionWSDataType},
|
||||
ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSDataType},
|
||||
},
|
||||
errors::CollaborateResult,
|
||||
};
|
||||
use flowy_error::{internal_error, FlowyError};
|
||||
use flowy_sync::*;
|
||||
use lib_infra::future::{BoxResultFuture, FutureResult};
|
||||
use lib_ot::{core::Delta, rich_text::RichTextAttributes};
|
||||
use lib_ot::rich_text::RichTextAttributes;
|
||||
use lib_ot::rich_text::RichTextDelta;
|
||||
use lib_ws::WSConnectState;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tokio::sync::{
|
||||
@ -23,33 +23,26 @@ use tokio::sync::{
|
||||
pub(crate) type EditorCommandSender = Sender<EditorCommand>;
|
||||
pub(crate) type EditorCommandReceiver = Receiver<EditorCommand>;
|
||||
|
||||
pub(crate) async fn make_document_ws_manager(
|
||||
pub(crate) async fn make_block_ws_manager(
|
||||
doc_id: String,
|
||||
user_id: String,
|
||||
edit_cmd_tx: EditorCommandSender,
|
||||
rev_manager: Arc<RevisionManager>,
|
||||
rev_web_socket: Arc<dyn RevisionWebSocket>,
|
||||
) -> Arc<RevisionWebSocketManager> {
|
||||
let composite_sink_provider = Arc::new(CompositeWSSinkDataProvider::new(&doc_id, rev_manager.clone()));
|
||||
let resolve_target = Arc::new(DocumentRevisionResolveTarget { edit_cmd_tx });
|
||||
let resolver = RevisionConflictResolver::<RichTextAttributes>::new(
|
||||
&user_id,
|
||||
resolve_target,
|
||||
Arc::new(composite_sink_provider.clone()),
|
||||
rev_manager,
|
||||
);
|
||||
let ws_stream_consumer = Arc::new(DocumentWSSteamConsumerAdapter {
|
||||
resolver: Arc::new(resolver),
|
||||
});
|
||||
|
||||
let sink_provider = Arc::new(DocumentWSSinkDataProviderAdapter(composite_sink_provider));
|
||||
let ws_data_provider = Arc::new(WSDataProvider::new(&doc_id, Arc::new(rev_manager.clone())));
|
||||
let resolver = Arc::new(BlockConflictResolver { edit_cmd_tx });
|
||||
let conflict_controller =
|
||||
RichTextConflictController::new(&user_id, resolver, Arc::new(ws_data_provider.clone()), rev_manager);
|
||||
let ws_data_stream = Arc::new(BlockRevisionWSDataStream::new(conflict_controller));
|
||||
let ws_data_sink = Arc::new(BlockWSDataSink(ws_data_provider));
|
||||
let ping_duration = Duration::from_millis(DOCUMENT_SYNC_INTERVAL_IN_MILLIS);
|
||||
let ws_manager = Arc::new(RevisionWebSocketManager::new(
|
||||
"Document",
|
||||
"Block",
|
||||
&doc_id,
|
||||
rev_web_socket,
|
||||
sink_provider,
|
||||
ws_stream_consumer,
|
||||
ws_data_sink,
|
||||
ws_data_stream,
|
||||
ping_duration,
|
||||
));
|
||||
listen_document_ws_state(&user_id, &doc_id, ws_manager.scribe_state());
|
||||
@ -69,18 +62,26 @@ fn listen_document_ws_state(_user_id: &str, _doc_id: &str, mut subscriber: broad
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) struct DocumentWSSteamConsumerAdapter {
|
||||
resolver: Arc<RevisionConflictResolver<RichTextAttributes>>,
|
||||
pub(crate) struct BlockRevisionWSDataStream {
|
||||
conflict_controller: Arc<RichTextConflictController>,
|
||||
}
|
||||
|
||||
impl RevisionWSSteamConsumer for DocumentWSSteamConsumerAdapter {
|
||||
impl BlockRevisionWSDataStream {
|
||||
pub fn new(conflict_controller: RichTextConflictController) -> Self {
|
||||
Self {
|
||||
conflict_controller: Arc::new(conflict_controller),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RevisionWSDataStream for BlockRevisionWSDataStream {
|
||||
fn receive_push_revision(&self, bytes: Bytes) -> BoxResultFuture<(), FlowyError> {
|
||||
let resolver = self.resolver.clone();
|
||||
let resolver = self.conflict_controller.clone();
|
||||
Box::pin(async move { resolver.receive_bytes(bytes).await })
|
||||
}
|
||||
|
||||
fn receive_ack(&self, id: String, ty: ServerRevisionWSDataType) -> BoxResultFuture<(), FlowyError> {
|
||||
let resolver = self.resolver.clone();
|
||||
let resolver = self.conflict_controller.clone();
|
||||
Box::pin(async move { resolver.ack_revision(id, ty).await })
|
||||
}
|
||||
|
||||
@ -90,25 +91,25 @@ impl RevisionWSSteamConsumer for DocumentWSSteamConsumerAdapter {
|
||||
}
|
||||
|
||||
fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError> {
|
||||
let resolver = self.resolver.clone();
|
||||
let resolver = self.conflict_controller.clone();
|
||||
Box::pin(async move { resolver.send_revisions(range).await })
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DocumentWSSinkDataProviderAdapter(pub(crate) Arc<CompositeWSSinkDataProvider>);
|
||||
impl RevisionWSSinkDataProvider for DocumentWSSinkDataProviderAdapter {
|
||||
pub(crate) struct BlockWSDataSink(pub(crate) Arc<WSDataProvider>);
|
||||
impl RevisionWSDataIterator for BlockWSDataSink {
|
||||
fn next(&self) -> FutureResult<Option<ClientRevisionWSData>, FlowyError> {
|
||||
let sink_provider = self.0.clone();
|
||||
FutureResult::new(async move { sink_provider.next().await })
|
||||
}
|
||||
}
|
||||
|
||||
struct DocumentRevisionResolveTarget {
|
||||
struct BlockConflictResolver {
|
||||
edit_cmd_tx: EditorCommandSender,
|
||||
}
|
||||
|
||||
impl ResolverTarget<RichTextAttributes> for DocumentRevisionResolveTarget {
|
||||
fn compose_delta(&self, delta: Delta<RichTextAttributes>) -> BoxResultFuture<DeltaMD5, FlowyError> {
|
||||
impl ConflictResolver<RichTextAttributes> for BlockConflictResolver {
|
||||
fn compose_delta(&self, delta: RichTextDelta) -> BoxResultFuture<DeltaMD5, FlowyError> {
|
||||
let tx = self.edit_cmd_tx.clone();
|
||||
Box::pin(async move {
|
||||
let (ret, rx) = oneshot::channel();
|
||||
@ -127,11 +128,11 @@ impl ResolverTarget<RichTextAttributes> for DocumentRevisionResolveTarget {
|
||||
|
||||
fn transform_delta(
|
||||
&self,
|
||||
delta: Delta<RichTextAttributes>,
|
||||
) -> BoxResultFuture<flowy_sync::TransformDeltas<RichTextAttributes>, FlowyError> {
|
||||
delta: RichTextDelta,
|
||||
) -> BoxResultFuture<flowy_sync::RichTextTransformDeltas, FlowyError> {
|
||||
let tx = self.edit_cmd_tx.clone();
|
||||
Box::pin(async move {
|
||||
let (ret, rx) = oneshot::channel::<CollaborateResult<TransformDeltas<RichTextAttributes>>>();
|
||||
let (ret, rx) = oneshot::channel::<CollaborateResult<RichTextTransformDeltas>>();
|
||||
tx.send(EditorCommand::TransformDelta { delta, ret })
|
||||
.await
|
||||
.map_err(internal_error)?;
|
||||
@ -142,7 +143,7 @@ impl ResolverTarget<RichTextAttributes> for DocumentRevisionResolveTarget {
|
||||
})
|
||||
}
|
||||
|
||||
fn reset_delta(&self, delta: Delta<RichTextAttributes>) -> BoxResultFuture<DeltaMD5, FlowyError> {
|
||||
fn reset_delta(&self, delta: RichTextDelta) -> BoxResultFuture<DeltaMD5, FlowyError> {
|
||||
let tx = self.edit_cmd_tx.clone();
|
||||
Box::pin(async move {
|
||||
let (ret, rx) = oneshot::channel();
|
||||
@ -157,24 +158,3 @@ impl ResolverTarget<RichTextAttributes> for DocumentRevisionResolveTarget {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// RevisionWebSocketManager registers itself as a DocumentWSReceiver for each
|
||||
// opened document.
|
||||
#[async_trait]
|
||||
impl DocumentWSReceiver for RevisionWebSocketManager {
|
||||
#[tracing::instrument(level = "debug", skip(self, data), err)]
|
||||
async fn receive_ws_data(&self, data: ServerRevisionWSData) -> Result<(), FlowyError> {
|
||||
let _ = self.ws_passthrough_tx.send(data).await.map_err(|e| {
|
||||
let err_msg = format!("{} passthrough error: {}", self.object_id, e);
|
||||
FlowyError::internal().context(err_msg)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn connect_state_changed(&self, state: WSConnectState) {
|
||||
match self.state_passthrough_tx.send(state) {
|
||||
Ok(_) => {}
|
||||
Err(e) => tracing::error!("{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use flowy_collaboration::entities::revision::RevisionState;
|
||||
use flowy_document::editor::ClientDocumentEditor;
|
||||
use flowy_document::block_editor::ClientBlockEditor;
|
||||
use flowy_document::DOCUMENT_SYNC_INTERVAL_IN_MILLIS;
|
||||
use flowy_test::{helper::ViewTest, FlowySDKTest};
|
||||
use lib_ot::{core::Interval, rich_text::RichTextDelta};
|
||||
@ -19,7 +19,7 @@ pub enum EditorScript {
|
||||
|
||||
pub struct EditorTest {
|
||||
pub sdk: FlowySDKTest,
|
||||
pub editor: Arc<ClientDocumentEditor>,
|
||||
pub editor: Arc<ClientBlockEditor>,
|
||||
}
|
||||
|
||||
impl EditorTest {
|
||||
@ -27,7 +27,7 @@ impl EditorTest {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let _ = sdk.init_user().await;
|
||||
let test = ViewTest::new(&sdk).await;
|
||||
let editor = sdk.document_manager.open_document(&test.view.id).await.unwrap();
|
||||
let editor = sdk.document_manager.open_block(&test.view.id).await.unwrap();
|
||||
Self { sdk, editor }
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ impl EditorTest {
|
||||
let delta = self.editor.doc_delta().await.unwrap();
|
||||
if expected_delta != delta {
|
||||
eprintln!("✅ expect: {}", expected,);
|
||||
eprintln!("❌ receive: {}", delta.to_json());
|
||||
eprintln!("❌ receive: {}", delta.to_delta_json());
|
||||
}
|
||||
assert_eq!(expected_delta, delta);
|
||||
}
|
||||
|
@ -774,7 +774,7 @@ fn delta_compose() {
|
||||
delta = delta.compose(&d).unwrap();
|
||||
}
|
||||
assert_eq!(
|
||||
delta.to_json(),
|
||||
delta.to_delta_json(),
|
||||
r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"\n"}]"#
|
||||
);
|
||||
|
||||
|
@ -108,20 +108,20 @@ impl TestBuilder {
|
||||
TestOp::Insert(delta_i, s, index) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let delta = document.insert(*index, s).unwrap();
|
||||
tracing::debug!("Insert delta: {}", delta.to_json());
|
||||
tracing::debug!("Insert delta: {}", delta.to_delta_json());
|
||||
|
||||
self.deltas.insert(*delta_i, Some(delta));
|
||||
}
|
||||
TestOp::Delete(delta_i, iv) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let delta = document.replace(*iv, "").unwrap();
|
||||
tracing::trace!("Delete delta: {}", delta.to_json());
|
||||
tracing::trace!("Delete delta: {}", delta.to_delta_json());
|
||||
self.deltas.insert(*delta_i, Some(delta));
|
||||
}
|
||||
TestOp::Replace(delta_i, iv, s) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let delta = document.replace(*iv, s).unwrap();
|
||||
tracing::trace!("Replace delta: {}", delta.to_json());
|
||||
tracing::trace!("Replace delta: {}", delta.to_delta_json());
|
||||
self.deltas.insert(*delta_i, Some(delta));
|
||||
}
|
||||
TestOp::InsertBold(delta_i, s, iv) => {
|
||||
@ -133,7 +133,7 @@ impl TestBuilder {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let attribute = RichTextAttribute::Bold(*enable);
|
||||
let delta = document.format(*iv, attribute).unwrap();
|
||||
tracing::trace!("Bold delta: {}", delta.to_json());
|
||||
tracing::trace!("Bold delta: {}", delta.to_delta_json());
|
||||
self.deltas.insert(*delta_i, Some(delta));
|
||||
}
|
||||
TestOp::Italic(delta_i, iv, enable) => {
|
||||
@ -143,28 +143,28 @@ impl TestBuilder {
|
||||
false => RichTextAttribute::Italic(false),
|
||||
};
|
||||
let delta = document.format(*iv, attribute).unwrap();
|
||||
tracing::trace!("Italic delta: {}", delta.to_json());
|
||||
tracing::trace!("Italic delta: {}", delta.to_delta_json());
|
||||
self.deltas.insert(*delta_i, Some(delta));
|
||||
}
|
||||
TestOp::Header(delta_i, iv, level) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let attribute = RichTextAttribute::Header(*level);
|
||||
let delta = document.format(*iv, attribute).unwrap();
|
||||
tracing::trace!("Header delta: {}", delta.to_json());
|
||||
tracing::trace!("Header delta: {}", delta.to_delta_json());
|
||||
self.deltas.insert(*delta_i, Some(delta));
|
||||
}
|
||||
TestOp::Link(delta_i, iv, link) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let attribute = RichTextAttribute::Link(link.to_owned());
|
||||
let delta = document.format(*iv, attribute).unwrap();
|
||||
tracing::trace!("Link delta: {}", delta.to_json());
|
||||
tracing::trace!("Link delta: {}", delta.to_delta_json());
|
||||
self.deltas.insert(*delta_i, Some(delta));
|
||||
}
|
||||
TestOp::Bullet(delta_i, iv, enable) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let attribute = RichTextAttribute::Bullet(*enable);
|
||||
let delta = document.format(*iv, attribute).unwrap();
|
||||
tracing::debug!("Bullet delta: {}", delta.to_json());
|
||||
tracing::debug!("Bullet delta: {}", delta.to_delta_json());
|
||||
|
||||
self.deltas.insert(*delta_i, Some(delta));
|
||||
}
|
||||
@ -194,15 +194,15 @@ impl TestBuilder {
|
||||
let delta_a = &self.documents[*delta_a_i].delta();
|
||||
let delta_b = &self.documents[*delta_b_i].delta();
|
||||
tracing::debug!("Invert: ");
|
||||
tracing::debug!("a: {}", delta_a.to_json());
|
||||
tracing::debug!("b: {}", delta_b.to_json());
|
||||
tracing::debug!("a: {}", delta_a.to_delta_json());
|
||||
tracing::debug!("b: {}", delta_b.to_delta_json());
|
||||
|
||||
let (_, b_prime) = delta_a.transform(delta_b).unwrap();
|
||||
let undo = b_prime.invert(delta_a);
|
||||
|
||||
let new_delta = delta_a.compose(&b_prime).unwrap();
|
||||
tracing::debug!("new delta: {}", new_delta.to_json());
|
||||
tracing::debug!("undo delta: {}", undo.to_json());
|
||||
tracing::debug!("new delta: {}", new_delta.to_delta_json());
|
||||
tracing::debug!("undo delta: {}", undo.to_delta_json());
|
||||
|
||||
let new_delta_after_undo = new_delta.compose(&undo).unwrap();
|
||||
|
||||
@ -238,7 +238,7 @@ impl TestBuilder {
|
||||
}
|
||||
|
||||
TestOp::AssertPrimeJson(doc_i, expected) => {
|
||||
let prime_json = self.primes[*doc_i].as_ref().unwrap().to_json();
|
||||
let prime_json = self.primes[*doc_i].as_ref().unwrap().to_delta_json();
|
||||
let expected_prime: RichTextDelta = serde_json::from_str(expected).unwrap();
|
||||
let target_prime: RichTextDelta = serde_json::from_str(&prime_json).unwrap();
|
||||
|
||||
|
@ -92,7 +92,7 @@ fn delta_deserialize_null_test() {
|
||||
attribute.value = RichTextAttributeValue(None);
|
||||
let delta2 = DeltaBuilder::new().retain_with_attributes(7, attribute.into()).build();
|
||||
|
||||
assert_eq!(delta2.to_json(), r#"[{"retain":7,"attributes":{"bold":""}}]"#);
|
||||
assert_eq!(delta2.to_delta_json(), r#"[{"retain":7,"attributes":{"bold":""}}]"#);
|
||||
assert_eq!(delta1, delta2);
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,9 @@ use flowy_sync::RevisionWebSocket;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use flowy_collaboration::{client_folder::FolderPad, entities::ws_data::ServerRevisionWSData};
|
||||
use flowy_document::FlowyDocumentManager;
|
||||
use flowy_document::BlockManager;
|
||||
|
||||
use flowy_collaboration::entities::revision::{RepeatedRevision, Revision};
|
||||
use std::{collections::HashMap, convert::TryInto, fmt::Formatter, sync::Arc};
|
||||
use tokio::sync::RwLock as TokioRwLock;
|
||||
|
||||
@ -17,7 +18,7 @@ use crate::{
|
||||
errors::FlowyResult,
|
||||
event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser},
|
||||
services::{
|
||||
folder_editor::FolderEditor, persistence::FolderPersistence, set_current_workspace, AppController,
|
||||
folder_editor::ClientFolderEditor, persistence::FolderPersistence, set_current_workspace, AppController,
|
||||
TrashController, ViewController, WorkspaceController,
|
||||
},
|
||||
};
|
||||
@ -63,7 +64,7 @@ pub struct FolderManager {
|
||||
pub(crate) view_controller: Arc<ViewController>,
|
||||
pub(crate) trash_controller: Arc<TrashController>,
|
||||
web_socket: Arc<dyn RevisionWebSocket>,
|
||||
folder_editor: Arc<TokioRwLock<Option<Arc<FolderEditor>>>>,
|
||||
folder_editor: Arc<TokioRwLock<Option<Arc<ClientFolderEditor>>>>,
|
||||
}
|
||||
|
||||
impl FolderManager {
|
||||
@ -71,7 +72,7 @@ impl FolderManager {
|
||||
user: Arc<dyn WorkspaceUser>,
|
||||
cloud_service: Arc<dyn FolderCouldServiceV1>,
|
||||
database: Arc<dyn WorkspaceDatabase>,
|
||||
document_manager: Arc<FlowyDocumentManager>,
|
||||
document_manager: Arc<BlockManager>,
|
||||
web_socket: Arc<dyn RevisionWebSocket>,
|
||||
) -> Self {
|
||||
if let Ok(user_id) = user.user_id() {
|
||||
@ -162,7 +163,7 @@ impl FolderManager {
|
||||
let _ = self.persistence.initialize(user_id, &folder_id).await?;
|
||||
|
||||
let pool = self.persistence.db_pool()?;
|
||||
let folder_editor = FolderEditor::new(user_id, &folder_id, token, pool, self.web_socket.clone()).await?;
|
||||
let folder_editor = ClientFolderEditor::new(user_id, &folder_id, token, pool, self.web_socket.clone()).await?;
|
||||
*self.folder_editor.write().await = Some(Arc::new(folder_editor));
|
||||
|
||||
let _ = self.app_controller.initialize()?;
|
||||
@ -196,14 +197,15 @@ impl DefaultFolderBuilder {
|
||||
for app in workspace.apps.iter() {
|
||||
for (index, view) in app.belongings.iter().enumerate() {
|
||||
let view_data = if index == 0 {
|
||||
initial_read_me().to_json()
|
||||
initial_read_me().to_delta_json()
|
||||
} else {
|
||||
initial_delta().to_json()
|
||||
initial_delta().to_delta_json()
|
||||
};
|
||||
view_controller.set_latest_view(view);
|
||||
let _ = view_controller
|
||||
.create_view_document_content(&view.id, view_data)
|
||||
.await?;
|
||||
let delta_data = Bytes::from(view_data);
|
||||
let repeated_revision: RepeatedRevision =
|
||||
Revision::initial_revision(user_id, &view.id, delta_data).into();
|
||||
let _ = view_controller.create_view(&view.id, repeated_revision).await?;
|
||||
}
|
||||
}
|
||||
let folder = FolderPad::new(vec![workspace.clone()], vec![])?;
|
||||
@ -219,7 +221,7 @@ impl DefaultFolderBuilder {
|
||||
|
||||
#[cfg(feature = "flowy_unit_test")]
|
||||
impl FolderManager {
|
||||
pub async fn folder_editor(&self) -> Arc<FolderEditor> {
|
||||
pub async fn folder_editor(&self) -> Arc<ClientFolderEditor> {
|
||||
self.folder_editor.read().await.clone().unwrap()
|
||||
}
|
||||
}
|
||||
|
@ -63,9 +63,9 @@ pub fn create(folder: Arc<FolderManager>) -> Module {
|
||||
.event(FolderEvent::UpdateView, update_view_handler)
|
||||
.event(FolderEvent::DeleteView, delete_view_handler)
|
||||
.event(FolderEvent::DuplicateView, duplicate_view_handler)
|
||||
.event(FolderEvent::OpenView, open_document_handler)
|
||||
.event(FolderEvent::OpenView, open_view_handler)
|
||||
.event(FolderEvent::CloseView, close_view_handler)
|
||||
.event(FolderEvent::ApplyDocDelta, document_delta_handler);
|
||||
.event(FolderEvent::ApplyDocDelta, block_delta_handler);
|
||||
|
||||
module = module
|
||||
.event(FolderEvent::ReadTrash, read_trash_handler)
|
||||
@ -130,7 +130,7 @@ pub enum FolderEvent {
|
||||
#[event()]
|
||||
CopyLink = 206,
|
||||
|
||||
#[event(input = "ViewId", output = "DocumentDelta")]
|
||||
#[event(input = "ViewId", output = "BlockDelta")]
|
||||
OpenView = 207,
|
||||
|
||||
#[event(input = "ViewId")]
|
||||
@ -151,7 +151,7 @@ pub enum FolderEvent {
|
||||
#[event()]
|
||||
DeleteAllTrash = 304,
|
||||
|
||||
#[event(input = "DocumentDelta", output = "DocumentDelta")]
|
||||
#[event(input = "BlockDelta", output = "BlockDelta")]
|
||||
ApplyDocDelta = 400,
|
||||
|
||||
#[event(input = "ExportPayload", output = "ExportData")]
|
||||
|
@ -6,12 +6,12 @@ use crate::{
|
||||
errors::FlowyError,
|
||||
services::{AppController, TrashController, ViewController},
|
||||
};
|
||||
use lib_dispatch::prelude::{data_result, Data, DataResult, Unit};
|
||||
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
|
||||
use std::{convert::TryInto, sync::Arc};
|
||||
|
||||
pub(crate) async fn create_app_handler(
|
||||
data: Data<CreateAppPayload>,
|
||||
controller: Unit<Arc<AppController>>,
|
||||
controller: AppData<Arc<AppController>>,
|
||||
) -> DataResult<App, FlowyError> {
|
||||
let params: CreateAppParams = data.into_inner().try_into()?;
|
||||
let detail = controller.create_app_from_params(params).await?;
|
||||
@ -21,8 +21,8 @@ pub(crate) async fn create_app_handler(
|
||||
|
||||
pub(crate) async fn delete_app_handler(
|
||||
data: Data<AppId>,
|
||||
app_controller: Unit<Arc<AppController>>,
|
||||
trash_controller: Unit<Arc<TrashController>>,
|
||||
app_controller: AppData<Arc<AppController>>,
|
||||
trash_controller: AppData<Arc<TrashController>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: AppId = data.into_inner();
|
||||
let trash = app_controller
|
||||
@ -39,7 +39,7 @@ pub(crate) async fn delete_app_handler(
|
||||
#[tracing::instrument(skip(data, controller))]
|
||||
pub(crate) async fn update_app_handler(
|
||||
data: Data<UpdateAppPayload>,
|
||||
controller: Unit<Arc<AppController>>,
|
||||
controller: AppData<Arc<AppController>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: UpdateAppParams = data.into_inner().try_into()?;
|
||||
let _ = controller.update_app(params).await?;
|
||||
@ -49,8 +49,8 @@ pub(crate) async fn update_app_handler(
|
||||
#[tracing::instrument(skip(data, app_controller, view_controller))]
|
||||
pub(crate) async fn read_app_handler(
|
||||
data: Data<AppId>,
|
||||
app_controller: Unit<Arc<AppController>>,
|
||||
view_controller: Unit<Arc<ViewController>>,
|
||||
app_controller: AppData<Arc<AppController>>,
|
||||
view_controller: AppData<Arc<ViewController>>,
|
||||
) -> DataResult<App, FlowyError> {
|
||||
let params: AppId = data.into_inner();
|
||||
let mut app = app_controller.read_app(params.clone()).await?;
|
||||
|
@ -8,16 +8,16 @@ use crate::controller::FolderId;
|
||||
use flowy_collaboration::util::make_delta_from_revisions;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_sync::{
|
||||
RevisionCache, RevisionCloudService, RevisionCompact, RevisionManager, RevisionObjectBuilder, RevisionWebSocket,
|
||||
RevisionWebSocketManager,
|
||||
RevisionCloudService, RevisionCompact, RevisionManager, RevisionObjectBuilder, RevisionPersistence,
|
||||
RevisionWebSocket, RevisionWebSocketManager,
|
||||
};
|
||||
use lib_infra::future::FutureResult;
|
||||
use lib_ot::core::PlainAttributes;
|
||||
use lib_ot::core::PlainTextAttributes;
|
||||
use lib_sqlite::ConnectionPool;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct FolderEditor {
|
||||
pub struct ClientFolderEditor {
|
||||
user_id: String,
|
||||
pub(crate) folder_id: FolderId,
|
||||
pub(crate) folder: Arc<RwLock<FolderPad>>,
|
||||
@ -25,7 +25,7 @@ pub struct FolderEditor {
|
||||
ws_manager: Arc<RevisionWebSocketManager>,
|
||||
}
|
||||
|
||||
impl FolderEditor {
|
||||
impl ClientFolderEditor {
|
||||
pub async fn new(
|
||||
user_id: &str,
|
||||
folder_id: &FolderId,
|
||||
@ -33,9 +33,9 @@ impl FolderEditor {
|
||||
pool: Arc<ConnectionPool>,
|
||||
web_socket: Arc<dyn RevisionWebSocket>,
|
||||
) -> FlowyResult<Self> {
|
||||
let cache = Arc::new(RevisionCache::new(user_id, folder_id.as_ref(), pool));
|
||||
let mut rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), cache);
|
||||
let cloud = Arc::new(FolderRevisionCloudServiceImpl {
|
||||
let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), pool));
|
||||
let mut rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), rev_persistence);
|
||||
let cloud = Arc::new(FolderRevisionCloudService {
|
||||
token: token.to_string(),
|
||||
});
|
||||
let folder = Arc::new(RwLock::new(
|
||||
@ -109,12 +109,12 @@ impl RevisionObjectBuilder for FolderPadBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
struct FolderRevisionCloudServiceImpl {
|
||||
struct FolderRevisionCloudService {
|
||||
#[allow(dead_code)]
|
||||
token: String,
|
||||
}
|
||||
|
||||
impl RevisionCloudService for FolderRevisionCloudServiceImpl {
|
||||
impl RevisionCloudService for FolderRevisionCloudService {
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult<Vec<Revision>, FlowyError> {
|
||||
FutureResult::new(async move { Ok(vec![]) })
|
||||
@ -122,7 +122,7 @@ impl RevisionCloudService for FolderRevisionCloudServiceImpl {
|
||||
}
|
||||
|
||||
#[cfg(feature = "flowy_unit_test")]
|
||||
impl FolderEditor {
|
||||
impl ClientFolderEditor {
|
||||
pub fn rev_manager(&self) -> Arc<RevisionManager> {
|
||||
self.rev_manager.clone()
|
||||
}
|
||||
@ -144,7 +144,7 @@ impl RevisionCompact for FolderRevisionCompact {
|
||||
|
||||
let (base_rev_id, rev_id) = first_revision.pair_rev_id();
|
||||
let md5 = last_revision.md5.clone();
|
||||
let delta = make_delta_from_revisions::<PlainAttributes>(revisions)?;
|
||||
let delta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
|
||||
let delta_data = delta.to_bytes();
|
||||
Ok(Revision::new(object_id, base_rev_id, rev_id, delta_data, user_id, md5))
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::controller::FolderId;
|
||||
use crate::{
|
||||
event_map::WorkspaceDatabase,
|
||||
services::persistence::{AppTableSql, TrashTableSql, ViewTableSql, WorkspaceTableSql},
|
||||
@ -10,9 +11,11 @@ use flowy_folder_data_model::entities::{
|
||||
view::{RepeatedView, View},
|
||||
workspace::Workspace,
|
||||
};
|
||||
use flowy_sync::{RevisionLoader, RevisionPersistence};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) const V1_MIGRATION: &str = "FOLDER_V1_MIGRATION";
|
||||
const V1_MIGRATION: &str = "FOLDER_V1_MIGRATION";
|
||||
const V2_MIGRATION: &str = "FOLDER_V2_MIGRATION";
|
||||
|
||||
pub(crate) struct FolderMigration {
|
||||
user_id: String,
|
||||
@ -32,7 +35,7 @@ impl FolderMigration {
|
||||
if KV::get_bool(&key) {
|
||||
return Ok(None);
|
||||
}
|
||||
tracing::trace!("Run folder version 1 migrations");
|
||||
|
||||
let pool = self.database.db_pool()?;
|
||||
let conn = &*pool.get()?;
|
||||
let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| {
|
||||
@ -62,6 +65,7 @@ impl FolderMigration {
|
||||
})?;
|
||||
|
||||
if workspaces.is_empty() {
|
||||
tracing::trace!("Run folder v1 migration, but workspace is empty");
|
||||
KV::set_bool(&key, true);
|
||||
return Ok(None);
|
||||
}
|
||||
@ -73,6 +77,35 @@ impl FolderMigration {
|
||||
|
||||
let folder = FolderPad::new(workspaces, trash)?;
|
||||
KV::set_bool(&key, true);
|
||||
tracing::trace!("Run folder v1 migration");
|
||||
Ok(Some(folder))
|
||||
}
|
||||
|
||||
pub async fn run_v2_migration(&self, user_id: &str, folder_id: &FolderId) -> FlowyResult<Option<FolderPad>> {
|
||||
let key = md5(format!("{}{}", self.user_id, V2_MIGRATION));
|
||||
if KV::get_bool(&key) {
|
||||
return Ok(None);
|
||||
}
|
||||
let pool = self.database.db_pool()?;
|
||||
let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), pool.clone()));
|
||||
let (revisions, _) = RevisionLoader {
|
||||
object_id: folder_id.as_ref().to_owned(),
|
||||
user_id: self.user_id.clone(),
|
||||
cloud: None,
|
||||
rev_persistence,
|
||||
}
|
||||
.load()
|
||||
.await?;
|
||||
|
||||
if revisions.is_empty() {
|
||||
tracing::trace!("Run folder v2 migration, but revision is empty");
|
||||
KV::set_bool(&key, true);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let pad = FolderPad::from_revisions(revisions)?;
|
||||
KV::set_bool(&key, true);
|
||||
tracing::trace!("Run folder v2 migration");
|
||||
Ok(Some(pad))
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ mod migration;
|
||||
pub mod version_1;
|
||||
mod version_2;
|
||||
|
||||
use flowy_collaboration::client_folder::initial_folder_delta;
|
||||
use flowy_collaboration::{
|
||||
client_folder::FolderPad,
|
||||
entities::revision::{Revision, RevisionState},
|
||||
@ -13,7 +14,7 @@ pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::
|
||||
use crate::{
|
||||
controller::FolderId,
|
||||
event_map::WorkspaceDatabase,
|
||||
services::{folder_editor::FolderEditor, persistence::migration::FolderMigration},
|
||||
services::{folder_editor::ClientFolderEditor, persistence::migration::FolderMigration},
|
||||
};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_folder_data_model::entities::{
|
||||
@ -50,11 +51,14 @@ pub trait FolderPersistenceTransaction {
|
||||
|
||||
pub struct FolderPersistence {
|
||||
database: Arc<dyn WorkspaceDatabase>,
|
||||
folder_editor: Arc<RwLock<Option<Arc<FolderEditor>>>>,
|
||||
folder_editor: Arc<RwLock<Option<Arc<ClientFolderEditor>>>>,
|
||||
}
|
||||
|
||||
impl FolderPersistence {
|
||||
pub fn new(database: Arc<dyn WorkspaceDatabase>, folder_editor: Arc<RwLock<Option<Arc<FolderEditor>>>>) -> Self {
|
||||
pub fn new(
|
||||
database: Arc<dyn WorkspaceDatabase>,
|
||||
folder_editor: Arc<RwLock<Option<Arc<ClientFolderEditor>>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
database,
|
||||
folder_editor,
|
||||
@ -102,7 +106,10 @@ impl FolderPersistence {
|
||||
pub async fn initialize(&self, user_id: &str, folder_id: &FolderId) -> FlowyResult<()> {
|
||||
let migrations = FolderMigration::new(user_id, self.database.clone());
|
||||
if let Some(migrated_folder) = migrations.run_v1_migration()? {
|
||||
tracing::trace!("Save migration folder");
|
||||
self.save_folder(user_id, folder_id, migrated_folder).await?;
|
||||
}
|
||||
|
||||
if let Some(migrated_folder) = migrations.run_v2_migration(user_id, folder_id).await? {
|
||||
self.save_folder(user_id, folder_id, migrated_folder).await?;
|
||||
}
|
||||
|
||||
@ -111,7 +118,7 @@ impl FolderPersistence {
|
||||
|
||||
pub async fn save_folder(&self, user_id: &str, folder_id: &FolderId, folder: FolderPad) -> FlowyResult<()> {
|
||||
let pool = self.database.db_pool()?;
|
||||
let delta_data = folder.delta().to_bytes();
|
||||
let delta_data = initial_folder_delta(&folder)?.to_bytes();
|
||||
let md5 = folder.md5();
|
||||
let revision = Revision::new(folder_id.as_ref(), 0, 0, delta_data, user_id, md5);
|
||||
let record = RevisionRecord {
|
||||
@ -120,8 +127,7 @@ impl FolderPersistence {
|
||||
write_to_disk: true,
|
||||
};
|
||||
|
||||
let conn = pool.get()?;
|
||||
let disk_cache = mk_revision_disk_cache(user_id, pool);
|
||||
disk_cache.create_revision_records(vec![record], &conn)
|
||||
disk_cache.delete_and_insert_records(folder_id.as_ref(), None, vec![record])
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
entities::{
|
||||
trash::{Trash, TrashType},
|
||||
view::{RepeatedView, UpdateViewParams, View, ViewType},
|
||||
view::{RepeatedView, UpdateViewParams, View, ViewDataType},
|
||||
},
|
||||
errors::FlowyError,
|
||||
services::persistence::version_1::app_sql::AppTable,
|
||||
@ -65,49 +65,6 @@ impl ViewTableSql {
|
||||
}
|
||||
}
|
||||
|
||||
// pub(crate) fn read_views(
|
||||
// belong_to_id: &str,
|
||||
// is_trash: Option<bool>,
|
||||
// conn: &SqliteConnection,
|
||||
// ) -> Result<RepeatedView, FlowyError> {
|
||||
// let views = dsl::view_table
|
||||
// .inner_join(trash_table::dsl::trash_table.on(trash_id.ne(view_table::
|
||||
// id))) .filter(view_table::belong_to_id.eq(belong_to_id))
|
||||
// .select((
|
||||
// view_table::id,
|
||||
// view_table::belong_to_id,
|
||||
// view_table::name,
|
||||
// view_table::desc,
|
||||
// view_table::modified_time,
|
||||
// view_table::create_time,
|
||||
// view_table::thumbnail,
|
||||
// view_table::view_type,
|
||||
// view_table::version,
|
||||
// ))
|
||||
// .load(conn)?
|
||||
// .into_iter()
|
||||
// .map(
|
||||
// |(id, belong_to_id, name, desc, create_time, modified_time,
|
||||
// thumbnail, view_type, version)| { ViewTable {
|
||||
// id,
|
||||
// belong_to_id,
|
||||
// name,
|
||||
// desc,
|
||||
// modified_time,
|
||||
// create_time,
|
||||
// thumbnail,
|
||||
// view_type,
|
||||
// version,
|
||||
// is_trash: false,
|
||||
// }
|
||||
// .into()
|
||||
// },
|
||||
// )
|
||||
// .collect::<Vec<View>>();
|
||||
//
|
||||
// Ok(RepeatedView { items: views })
|
||||
// }
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
|
||||
#[belongs_to(AppTable, foreign_key = "belong_to_id")]
|
||||
#[table_name = "view_table"]
|
||||
@ -119,16 +76,16 @@ pub(crate) struct ViewTable {
|
||||
pub modified_time: i64,
|
||||
pub create_time: i64,
|
||||
pub thumbnail: String,
|
||||
pub view_type: ViewTableType,
|
||||
pub view_type: SqlViewDataType,
|
||||
pub version: i64,
|
||||
pub is_trash: bool,
|
||||
}
|
||||
|
||||
impl ViewTable {
|
||||
pub fn new(view: View) -> Self {
|
||||
let view_type = match view.view_type {
|
||||
ViewType::Blank => ViewTableType::Docs,
|
||||
ViewType::Doc => ViewTableType::Docs,
|
||||
let data_type = match view.data_type {
|
||||
ViewDataType::RichText => SqlViewDataType::RichText,
|
||||
ViewDataType::PlainText => SqlViewDataType::PlainText,
|
||||
};
|
||||
|
||||
ViewTable {
|
||||
@ -138,9 +95,8 @@ impl ViewTable {
|
||||
desc: view.desc,
|
||||
modified_time: view.modified_time,
|
||||
create_time: view.create_time,
|
||||
// TODO: thumbnail
|
||||
thumbnail: "".to_owned(),
|
||||
view_type,
|
||||
thumbnail: view.thumbnail,
|
||||
view_type: data_type,
|
||||
version: 0,
|
||||
is_trash: false,
|
||||
}
|
||||
@ -149,8 +105,9 @@ impl ViewTable {
|
||||
|
||||
impl std::convert::From<ViewTable> for View {
|
||||
fn from(table: ViewTable) -> Self {
|
||||
let view_type = match table.view_type {
|
||||
ViewTableType::Docs => ViewType::Doc,
|
||||
let data_type = match table.view_type {
|
||||
SqlViewDataType::RichText => ViewDataType::RichText,
|
||||
SqlViewDataType::PlainText => ViewDataType::PlainText,
|
||||
};
|
||||
|
||||
View {
|
||||
@ -158,11 +115,16 @@ impl std::convert::From<ViewTable> for View {
|
||||
belong_to_id: table.belong_to_id,
|
||||
name: table.name,
|
||||
desc: table.desc,
|
||||
view_type,
|
||||
data_type,
|
||||
belongings: RepeatedView::default(),
|
||||
modified_time: table.modified_time,
|
||||
version: table.version,
|
||||
create_time: table.create_time,
|
||||
ext_data: "".to_string(),
|
||||
thumbnail: table.thumbnail,
|
||||
// Store the view in ViewTable was deprecated since v0.0.2.
|
||||
// No need worry about plugin_type.
|
||||
plugin_type: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -214,32 +176,34 @@ impl ViewChangeset {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)]
|
||||
#[repr(i32)]
|
||||
#[sql_type = "Integer"]
|
||||
pub enum ViewTableType {
|
||||
Docs = 0,
|
||||
pub enum SqlViewDataType {
|
||||
RichText = 0,
|
||||
PlainText = 1,
|
||||
}
|
||||
|
||||
impl std::default::Default for ViewTableType {
|
||||
impl std::default::Default for SqlViewDataType {
|
||||
fn default() -> Self {
|
||||
ViewTableType::Docs
|
||||
SqlViewDataType::RichText
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<i32> for ViewTableType {
|
||||
impl std::convert::From<i32> for SqlViewDataType {
|
||||
fn from(value: i32) -> Self {
|
||||
match value {
|
||||
0 => ViewTableType::Docs,
|
||||
0 => SqlViewDataType::RichText,
|
||||
1 => SqlViewDataType::PlainText,
|
||||
o => {
|
||||
log::error!("Unsupported view type {}, fallback to ViewType::Docs", o);
|
||||
ViewTableType::Docs
|
||||
SqlViewDataType::PlainText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewTableType {
|
||||
impl SqlViewDataType {
|
||||
pub fn value(&self) -> i32 {
|
||||
*self as i32
|
||||
}
|
||||
}
|
||||
|
||||
impl_sql_integer_expression!(ViewTableType);
|
||||
impl_sql_integer_expression!(SqlViewDataType);
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::services::{
|
||||
folder_editor::FolderEditor,
|
||||
folder_editor::ClientFolderEditor,
|
||||
persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset},
|
||||
};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -11,7 +11,7 @@ use flowy_folder_data_model::entities::{
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
impl FolderPersistenceTransaction for FolderEditor {
|
||||
impl FolderPersistenceTransaction for ClientFolderEditor {
|
||||
fn create_workspace(&self, _user_id: &str, workspace: Workspace) -> FlowyResult<()> {
|
||||
if let Some(change) = self.folder.write().create_workspace(workspace)? {
|
||||
let _ = self.apply_change(change)?;
|
||||
|
@ -3,12 +3,12 @@ use crate::{
|
||||
errors::FlowyError,
|
||||
services::TrashController,
|
||||
};
|
||||
use lib_dispatch::prelude::{data_result, Data, DataResult, Unit};
|
||||
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tracing::instrument(skip(controller), err)]
|
||||
pub(crate) async fn read_trash_handler(
|
||||
controller: Unit<Arc<TrashController>>,
|
||||
controller: AppData<Arc<TrashController>>,
|
||||
) -> DataResult<RepeatedTrash, FlowyError> {
|
||||
let repeated_trash = controller.read_trash().await?;
|
||||
data_result(repeated_trash)
|
||||
@ -17,7 +17,7 @@ pub(crate) async fn read_trash_handler(
|
||||
#[tracing::instrument(skip(identifier, controller), err)]
|
||||
pub(crate) async fn putback_trash_handler(
|
||||
identifier: Data<TrashId>,
|
||||
controller: Unit<Arc<TrashController>>,
|
||||
controller: AppData<Arc<TrashController>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let _ = controller.putback(&identifier.id).await?;
|
||||
Ok(())
|
||||
@ -26,20 +26,20 @@ pub(crate) async fn putback_trash_handler(
|
||||
#[tracing::instrument(skip(identifiers, controller), err)]
|
||||
pub(crate) async fn delete_trash_handler(
|
||||
identifiers: Data<RepeatedTrashId>,
|
||||
controller: Unit<Arc<TrashController>>,
|
||||
controller: AppData<Arc<TrashController>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let _ = controller.delete(identifiers.into_inner()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(controller), err)]
|
||||
pub(crate) async fn restore_all_trash_handler(controller: Unit<Arc<TrashController>>) -> Result<(), FlowyError> {
|
||||
pub(crate) async fn restore_all_trash_handler(controller: AppData<Arc<TrashController>>) -> Result<(), FlowyError> {
|
||||
let _ = controller.restore_all_trash().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(controller), err)]
|
||||
pub(crate) async fn delete_all_trash_handler(controller: Unit<Arc<TrashController>>) -> Result<(), FlowyError> {
|
||||
pub(crate) async fn delete_all_trash_handler(controller: AppData<Arc<TrashController>>) -> Result<(), FlowyError> {
|
||||
let _ = controller.delete_all_trash().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use bytes::Bytes;
|
||||
use flowy_collaboration::entities::{
|
||||
document_info::{DocumentDelta, DocumentId},
|
||||
document_info::{BlockDelta, BlockId},
|
||||
revision::{RepeatedRevision, Revision},
|
||||
};
|
||||
|
||||
@ -22,8 +22,9 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use flowy_database::kv::KV;
|
||||
use flowy_document::FlowyDocumentManager;
|
||||
use flowy_document::BlockManager;
|
||||
use flowy_folder_data_model::entities::share::{ExportData, ExportParams};
|
||||
|
||||
use lib_infra::uuid_string;
|
||||
|
||||
const LATEST_VIEW_ID: &str = "latest_view_id";
|
||||
@ -33,7 +34,7 @@ pub(crate) struct ViewController {
|
||||
cloud_service: Arc<dyn FolderCouldServiceV1>,
|
||||
persistence: Arc<FolderPersistence>,
|
||||
trash_controller: Arc<TrashController>,
|
||||
document_manager: Arc<FlowyDocumentManager>,
|
||||
block_manager: Arc<BlockManager>,
|
||||
}
|
||||
|
||||
impl ViewController {
|
||||
@ -42,62 +43,51 @@ impl ViewController {
|
||||
persistence: Arc<FolderPersistence>,
|
||||
cloud_service: Arc<dyn FolderCouldServiceV1>,
|
||||
trash_can: Arc<TrashController>,
|
||||
document_manager: Arc<FlowyDocumentManager>,
|
||||
document_manager: Arc<BlockManager>,
|
||||
) -> Self {
|
||||
Self {
|
||||
user,
|
||||
cloud_service,
|
||||
persistence,
|
||||
trash_controller: trash_can,
|
||||
document_manager,
|
||||
block_manager: document_manager,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn initialize(&self) -> Result<(), FlowyError> {
|
||||
let _ = self.document_manager.init()?;
|
||||
let _ = self.block_manager.init()?;
|
||||
self.listen_trash_can_event();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self, params), fields(name = %params.name), err)]
|
||||
pub(crate) async fn create_view_from_params(&self, params: CreateViewParams) -> Result<View, FlowyError> {
|
||||
let view_data = if params.view_data.is_empty() {
|
||||
let view_data = if params.data.is_empty() {
|
||||
initial_delta_string()
|
||||
} else {
|
||||
params.view_data.clone()
|
||||
params.data.clone()
|
||||
};
|
||||
|
||||
let delta_data = Bytes::from(view_data);
|
||||
let user_id = self.user.user_id()?;
|
||||
let repeated_revision: RepeatedRevision =
|
||||
Revision::initial_revision(&user_id, ¶ms.view_id, delta_data).into();
|
||||
let _ = self
|
||||
.document_manager
|
||||
.reset_with_revisions(¶ms.view_id, repeated_revision)
|
||||
.await?;
|
||||
let _ = self.create_view(¶ms.view_id, repeated_revision).await?;
|
||||
let view = self.create_view_on_server(params).await?;
|
||||
let _ = self.create_view_on_local(view.clone()).await?;
|
||||
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self, view_id, view_data), err)]
|
||||
pub(crate) async fn create_view_document_content(
|
||||
#[tracing::instrument(level = "debug", skip(self, view_id, repeated_revision), err)]
|
||||
pub(crate) async fn create_view(
|
||||
&self,
|
||||
view_id: &str,
|
||||
view_data: String,
|
||||
repeated_revision: RepeatedRevision,
|
||||
) -> Result<(), FlowyError> {
|
||||
if view_data.is_empty() {
|
||||
if repeated_revision.is_empty() {
|
||||
return Err(FlowyError::internal().context("The content of the view should not be empty"));
|
||||
}
|
||||
|
||||
let delta_data = Bytes::from(view_data);
|
||||
let user_id = self.user.user_id()?;
|
||||
let repeated_revision: RepeatedRevision = Revision::initial_revision(&user_id, view_id, delta_data).into();
|
||||
let _ = self
|
||||
.document_manager
|
||||
.reset_with_revisions(view_id, repeated_revision)
|
||||
.await?;
|
||||
let _ = self.block_manager.create_block(view_id, repeated_revision).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -143,50 +133,52 @@ impl ViewController {
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub(crate) async fn open_document(&self, doc_id: &str) -> Result<DocumentDelta, FlowyError> {
|
||||
let editor = self.document_manager.open_document(doc_id).await?;
|
||||
KV::set_str(LATEST_VIEW_ID, doc_id.to_owned());
|
||||
let document_json = editor.document_json().await?;
|
||||
Ok(DocumentDelta {
|
||||
doc_id: doc_id.to_string(),
|
||||
pub(crate) async fn open_view(&self, view_id: &str) -> Result<BlockDelta, FlowyError> {
|
||||
let editor = self.block_manager.open_block(view_id).await?;
|
||||
KV::set_str(LATEST_VIEW_ID, view_id.to_owned());
|
||||
let document_json = editor.block_json().await?;
|
||||
Ok(BlockDelta {
|
||||
block_id: view_id.to_string(),
|
||||
delta_json: document_json,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub(crate) async fn close_view(&self, doc_id: &str) -> Result<(), FlowyError> {
|
||||
let _ = self.document_manager.close_document(doc_id)?;
|
||||
let _ = self.block_manager.close_block(doc_id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self,params), fields(doc_id = %params.value), err)]
|
||||
pub(crate) async fn delete_view(&self, params: DocumentId) -> Result<(), FlowyError> {
|
||||
pub(crate) async fn delete_view(&self, params: BlockId) -> Result<(), FlowyError> {
|
||||
if let Some(view_id) = KV::get_str(LATEST_VIEW_ID) {
|
||||
if view_id == params.value {
|
||||
let _ = KV::remove(LATEST_VIEW_ID);
|
||||
}
|
||||
}
|
||||
let _ = self.document_manager.close_document(¶ms.value)?;
|
||||
let _ = self.block_manager.close_block(¶ms.value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub(crate) async fn duplicate_view(&self, doc_id: &str) -> Result<(), FlowyError> {
|
||||
pub(crate) async fn duplicate_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
let view = self
|
||||
.persistence
|
||||
.begin_transaction(|transaction| transaction.read_view(doc_id))
|
||||
.begin_transaction(|transaction| transaction.read_view(view_id))
|
||||
.await?;
|
||||
|
||||
let editor = self.document_manager.open_document(doc_id).await?;
|
||||
let document_json = editor.document_json().await?;
|
||||
let editor = self.block_manager.open_block(view_id).await?;
|
||||
let document_json = editor.block_json().await?;
|
||||
let duplicate_params = CreateViewParams {
|
||||
belong_to_id: view.belong_to_id.clone(),
|
||||
name: format!("{} (copy)", &view.name),
|
||||
desc: view.desc.clone(),
|
||||
thumbnail: "".to_owned(),
|
||||
view_type: view.view_type.clone(),
|
||||
view_data: document_json,
|
||||
desc: view.desc,
|
||||
thumbnail: view.thumbnail,
|
||||
data_type: view.data_type,
|
||||
data: document_json,
|
||||
view_id: uuid_string(),
|
||||
ext_data: view.ext_data,
|
||||
plugin_type: view.plugin_type,
|
||||
};
|
||||
|
||||
let _ = self.create_view_from_params(duplicate_params).await?;
|
||||
@ -194,9 +186,9 @@ impl ViewController {
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self, params), err)]
|
||||
pub(crate) async fn export_doc(&self, params: ExportParams) -> Result<ExportData, FlowyError> {
|
||||
let editor = self.document_manager.open_document(¶ms.doc_id).await?;
|
||||
let delta_json = editor.document_json().await?;
|
||||
pub(crate) async fn export_view(&self, params: ExportParams) -> Result<ExportData, FlowyError> {
|
||||
let editor = self.block_manager.open_block(¶ms.view_id).await?;
|
||||
let delta_json = editor.block_json().await?;
|
||||
Ok(ExportData {
|
||||
data: delta_json,
|
||||
export_type: params.export_type,
|
||||
@ -234,8 +226,8 @@ impl ViewController {
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
pub(crate) async fn receive_document_delta(&self, params: DocumentDelta) -> Result<DocumentDelta, FlowyError> {
|
||||
let doc = self.document_manager.receive_local_delta(params).await?;
|
||||
pub(crate) async fn receive_delta(&self, params: BlockDelta) -> Result<BlockDelta, FlowyError> {
|
||||
let doc = self.block_manager.receive_local_delta(params).await?;
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
@ -312,7 +304,7 @@ impl ViewController {
|
||||
fn listen_trash_can_event(&self) {
|
||||
let mut rx = self.trash_controller.subscribe();
|
||||
let persistence = self.persistence.clone();
|
||||
let document_manager = self.document_manager.clone();
|
||||
let document_manager = self.block_manager.clone();
|
||||
let trash_controller = self.trash_controller.clone();
|
||||
let _ = tokio::spawn(async move {
|
||||
loop {
|
||||
@ -340,7 +332,7 @@ impl ViewController {
|
||||
#[tracing::instrument(level = "trace", skip(persistence, document_manager, trash_can))]
|
||||
async fn handle_trash_event(
|
||||
persistence: Arc<FolderPersistence>,
|
||||
document_manager: Arc<FlowyDocumentManager>,
|
||||
document_manager: Arc<BlockManager>,
|
||||
trash_can: Arc<TrashController>,
|
||||
event: TrashEvent,
|
||||
) {
|
||||
|
@ -8,14 +8,14 @@ use crate::{
|
||||
errors::FlowyError,
|
||||
services::{TrashController, ViewController},
|
||||
};
|
||||
use flowy_collaboration::entities::document_info::DocumentDelta;
|
||||
use flowy_collaboration::entities::document_info::BlockDelta;
|
||||
use flowy_folder_data_model::entities::share::{ExportData, ExportParams, ExportPayload};
|
||||
use lib_dispatch::prelude::{data_result, Data, DataResult, Unit};
|
||||
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
|
||||
use std::{convert::TryInto, sync::Arc};
|
||||
|
||||
pub(crate) async fn create_view_handler(
|
||||
data: Data<CreateViewPayload>,
|
||||
controller: Unit<Arc<ViewController>>,
|
||||
controller: AppData<Arc<ViewController>>,
|
||||
) -> DataResult<View, FlowyError> {
|
||||
let params: CreateViewParams = data.into_inner().try_into()?;
|
||||
let view = controller.create_view_from_params(params).await?;
|
||||
@ -24,7 +24,7 @@ pub(crate) async fn create_view_handler(
|
||||
|
||||
pub(crate) async fn read_view_handler(
|
||||
data: Data<ViewId>,
|
||||
controller: Unit<Arc<ViewController>>,
|
||||
controller: AppData<Arc<ViewController>>,
|
||||
) -> DataResult<View, FlowyError> {
|
||||
let view_id: ViewId = data.into_inner();
|
||||
let mut view = controller.read_view(view_id.clone()).await?;
|
||||
@ -38,7 +38,7 @@ pub(crate) async fn read_view_handler(
|
||||
#[tracing::instrument(skip(data, controller), err)]
|
||||
pub(crate) async fn update_view_handler(
|
||||
data: Data<UpdateViewPayload>,
|
||||
controller: Unit<Arc<ViewController>>,
|
||||
controller: AppData<Arc<ViewController>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: UpdateViewParams = data.into_inner().try_into()?;
|
||||
let _ = controller.update_view(params).await?;
|
||||
@ -46,18 +46,18 @@ pub(crate) async fn update_view_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn document_delta_handler(
|
||||
data: Data<DocumentDelta>,
|
||||
controller: Unit<Arc<ViewController>>,
|
||||
) -> DataResult<DocumentDelta, FlowyError> {
|
||||
let doc = controller.receive_document_delta(data.into_inner()).await?;
|
||||
data_result(doc)
|
||||
pub(crate) async fn block_delta_handler(
|
||||
data: Data<BlockDelta>,
|
||||
controller: AppData<Arc<ViewController>>,
|
||||
) -> DataResult<BlockDelta, FlowyError> {
|
||||
let block_delta = controller.receive_delta(data.into_inner()).await?;
|
||||
data_result(block_delta)
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_view_handler(
|
||||
data: Data<RepeatedViewId>,
|
||||
view_controller: Unit<Arc<ViewController>>,
|
||||
trash_controller: Unit<Arc<TrashController>>,
|
||||
view_controller: AppData<Arc<ViewController>>,
|
||||
trash_controller: AppData<Arc<TrashController>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: RepeatedViewId = data.into_inner();
|
||||
for view_id in ¶ms.items {
|
||||
@ -75,18 +75,18 @@ pub(crate) async fn delete_view_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn open_document_handler(
|
||||
pub(crate) async fn open_view_handler(
|
||||
data: Data<ViewId>,
|
||||
controller: Unit<Arc<ViewController>>,
|
||||
) -> DataResult<DocumentDelta, FlowyError> {
|
||||
controller: AppData<Arc<ViewController>>,
|
||||
) -> DataResult<BlockDelta, FlowyError> {
|
||||
let view_id: ViewId = data.into_inner();
|
||||
let doc = controller.open_document(&view_id.value).await?;
|
||||
let doc = controller.open_view(&view_id.value).await?;
|
||||
data_result(doc)
|
||||
}
|
||||
|
||||
pub(crate) async fn close_view_handler(
|
||||
data: Data<ViewId>,
|
||||
controller: Unit<Arc<ViewController>>,
|
||||
controller: AppData<Arc<ViewController>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let view_id: ViewId = data.into_inner();
|
||||
let _ = controller.close_view(&view_id.value).await?;
|
||||
@ -96,7 +96,7 @@ pub(crate) async fn close_view_handler(
|
||||
#[tracing::instrument(skip(data, controller), err)]
|
||||
pub(crate) async fn duplicate_view_handler(
|
||||
data: Data<ViewId>,
|
||||
controller: Unit<Arc<ViewController>>,
|
||||
controller: AppData<Arc<ViewController>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let view_id: ViewId = data.into_inner();
|
||||
let _ = controller.duplicate_view(&view_id.value).await?;
|
||||
@ -106,9 +106,9 @@ pub(crate) async fn duplicate_view_handler(
|
||||
#[tracing::instrument(skip(data, controller), err)]
|
||||
pub(crate) async fn export_handler(
|
||||
data: Data<ExportPayload>,
|
||||
controller: Unit<Arc<ViewController>>,
|
||||
controller: AppData<Arc<ViewController>>,
|
||||
) -> DataResult<ExportData, FlowyError> {
|
||||
let params: ExportParams = data.into_inner().try_into()?;
|
||||
let data = controller.export_doc(params).await?;
|
||||
let data = controller.export_view(params).await?;
|
||||
data_result(data)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use flowy_collaboration::{
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_sync::*;
|
||||
use lib_infra::future::{BoxResultFuture, FutureResult};
|
||||
use lib_ot::core::{Delta, OperationTransformable, PlainAttributes, PlainDelta};
|
||||
use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta};
|
||||
use parking_lot::RwLock;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
@ -21,45 +21,41 @@ pub(crate) async fn make_folder_ws_manager(
|
||||
web_socket: Arc<dyn RevisionWebSocket>,
|
||||
folder_pad: Arc<RwLock<FolderPad>>,
|
||||
) -> Arc<RevisionWebSocketManager> {
|
||||
let composite_sink_provider = Arc::new(CompositeWSSinkDataProvider::new(folder_id, rev_manager.clone()));
|
||||
let resolve_target = Arc::new(FolderRevisionResolveTarget { folder_pad });
|
||||
let resolver = RevisionConflictResolver::<PlainAttributes>::new(
|
||||
let ws_data_provider = Arc::new(WSDataProvider::new(folder_id, Arc::new(rev_manager.clone())));
|
||||
let resolver = Arc::new(FolderConflictResolver { folder_pad });
|
||||
let conflict_controller = ConflictController::<PlainTextAttributes>::new(
|
||||
user_id,
|
||||
resolve_target,
|
||||
Arc::new(composite_sink_provider.clone()),
|
||||
resolver,
|
||||
Arc::new(ws_data_provider.clone()),
|
||||
rev_manager,
|
||||
);
|
||||
|
||||
let ws_stream_consumer = Arc::new(FolderWSStreamConsumerAdapter {
|
||||
resolver: Arc::new(resolver),
|
||||
});
|
||||
|
||||
let sink_provider = Arc::new(FolderWSSinkDataProviderAdapter(composite_sink_provider));
|
||||
let ws_data_stream = Arc::new(FolderRevisionWSDataStream::new(conflict_controller));
|
||||
let ws_data_sink = Arc::new(FolderWSDataSink(ws_data_provider));
|
||||
let ping_duration = Duration::from_millis(FOLDER_SYNC_INTERVAL_IN_MILLIS);
|
||||
Arc::new(RevisionWebSocketManager::new(
|
||||
"Folder",
|
||||
folder_id,
|
||||
web_socket,
|
||||
sink_provider,
|
||||
ws_stream_consumer,
|
||||
ws_data_sink,
|
||||
ws_data_stream,
|
||||
ping_duration,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) struct FolderWSSinkDataProviderAdapter(Arc<CompositeWSSinkDataProvider>);
|
||||
impl RevisionWSSinkDataProvider for FolderWSSinkDataProviderAdapter {
|
||||
pub(crate) struct FolderWSDataSink(Arc<WSDataProvider>);
|
||||
impl RevisionWSDataIterator for FolderWSDataSink {
|
||||
fn next(&self) -> FutureResult<Option<ClientRevisionWSData>, FlowyError> {
|
||||
let sink_provider = self.0.clone();
|
||||
FutureResult::new(async move { sink_provider.next().await })
|
||||
}
|
||||
}
|
||||
|
||||
struct FolderRevisionResolveTarget {
|
||||
struct FolderConflictResolver {
|
||||
folder_pad: Arc<RwLock<FolderPad>>,
|
||||
}
|
||||
|
||||
impl ResolverTarget<PlainAttributes> for FolderRevisionResolveTarget {
|
||||
fn compose_delta(&self, delta: Delta<PlainAttributes>) -> BoxResultFuture<DeltaMD5, FlowyError> {
|
||||
impl ConflictResolver<PlainTextAttributes> for FolderConflictResolver {
|
||||
fn compose_delta(&self, delta: PlainTextDelta) -> BoxResultFuture<DeltaMD5, FlowyError> {
|
||||
let folder_pad = self.folder_pad.clone();
|
||||
Box::pin(async move {
|
||||
let md5 = folder_pad.write().compose_remote_delta(delta)?;
|
||||
@ -69,13 +65,13 @@ impl ResolverTarget<PlainAttributes> for FolderRevisionResolveTarget {
|
||||
|
||||
fn transform_delta(
|
||||
&self,
|
||||
delta: Delta<PlainAttributes>,
|
||||
) -> BoxResultFuture<TransformDeltas<PlainAttributes>, FlowyError> {
|
||||
delta: PlainTextDelta,
|
||||
) -> BoxResultFuture<TransformDeltas<PlainTextAttributes>, FlowyError> {
|
||||
let folder_pad = self.folder_pad.clone();
|
||||
Box::pin(async move {
|
||||
let read_guard = folder_pad.read();
|
||||
let mut server_prime: Option<PlainDelta> = None;
|
||||
let client_prime: PlainDelta;
|
||||
let mut server_prime: Option<PlainTextDelta> = None;
|
||||
let client_prime: PlainTextDelta;
|
||||
if read_guard.is_empty() {
|
||||
// Do nothing
|
||||
client_prime = delta;
|
||||
@ -92,7 +88,7 @@ impl ResolverTarget<PlainAttributes> for FolderRevisionResolveTarget {
|
||||
})
|
||||
}
|
||||
|
||||
fn reset_delta(&self, delta: Delta<PlainAttributes>) -> BoxResultFuture<DeltaMD5, FlowyError> {
|
||||
fn reset_delta(&self, delta: PlainTextDelta) -> BoxResultFuture<DeltaMD5, FlowyError> {
|
||||
let folder_pad = self.folder_pad.clone();
|
||||
Box::pin(async move {
|
||||
let md5 = folder_pad.write().reset_folder(delta)?;
|
||||
@ -101,18 +97,26 @@ impl ResolverTarget<PlainAttributes> for FolderRevisionResolveTarget {
|
||||
}
|
||||
}
|
||||
|
||||
struct FolderWSStreamConsumerAdapter {
|
||||
resolver: Arc<RevisionConflictResolver<PlainAttributes>>,
|
||||
struct FolderRevisionWSDataStream {
|
||||
conflict_controller: Arc<PlainTextConflictController>,
|
||||
}
|
||||
|
||||
impl RevisionWSSteamConsumer for FolderWSStreamConsumerAdapter {
|
||||
impl FolderRevisionWSDataStream {
|
||||
pub fn new(conflict_controller: PlainTextConflictController) -> Self {
|
||||
Self {
|
||||
conflict_controller: Arc::new(conflict_controller),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RevisionWSDataStream for FolderRevisionWSDataStream {
|
||||
fn receive_push_revision(&self, bytes: Bytes) -> BoxResultFuture<(), FlowyError> {
|
||||
let resolver = self.resolver.clone();
|
||||
let resolver = self.conflict_controller.clone();
|
||||
Box::pin(async move { resolver.receive_bytes(bytes).await })
|
||||
}
|
||||
|
||||
fn receive_ack(&self, id: String, ty: ServerRevisionWSDataType) -> BoxResultFuture<(), FlowyError> {
|
||||
let resolver = self.resolver.clone();
|
||||
let resolver = self.conflict_controller.clone();
|
||||
Box::pin(async move { resolver.ack_revision(id, ty).await })
|
||||
}
|
||||
|
||||
@ -122,7 +126,7 @@ impl RevisionWSSteamConsumer for FolderWSStreamConsumerAdapter {
|
||||
}
|
||||
|
||||
fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError> {
|
||||
let resolver = self.resolver.clone();
|
||||
let resolver = self.conflict_controller.clone();
|
||||
Box::pin(async move { resolver.send_revisions(range).await })
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user