Merge pull request #403 from AppFlowy-IO/refactor_document_data_layer

Refactor document data layer
This commit is contained in:
Nathan.fooo
2022-03-01 23:56:05 +08:00
committed by GitHub
155 changed files with 3546 additions and 1823 deletions

View 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,
}

View File

@ -0,0 +1 @@
class PluginRunner {}

View 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;
}

View File

@ -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/welcome_bloc.dart';
import 'package:app_flowy/workspace/application/workspace/workspace_listener.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/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/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.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'; import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
@ -55,7 +55,7 @@ class HomeDepsResolver {
getIt.registerFactoryParam<ViewBloc, View, void>( getIt.registerFactoryParam<ViewBloc, View, void>(
(view, _) => ViewBloc( (view, _) => ViewBloc(
view: view, view: view,
service: ViewService(), service: ViewService(),
listener: getIt<ViewListener>(param1: view), listener: getIt<ViewListener>(param1: view),
), ),

View File

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:app_flowy/plugin/plugin.dart';
import 'package:app_flowy/startup/tasks/prelude.dart'; import 'package:app_flowy/startup/tasks/prelude.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -41,6 +42,7 @@ class FlowyRunner {
getIt<AppLauncher>().addTask(InitRustSDKTask()); getIt<AppLauncher>().addTask(InitRustSDKTask());
if (!env.isTest()) { if (!env.isTest()) {
getIt<AppLauncher>().addTask(PluginLoadTask());
getIt<AppLauncher>().addTask(InitAppWidgetTask()); getIt<AppLauncher>().addTask(InitAppWidgetTask());
getIt<AppLauncher>().addTask(InitPlatformServiceTask()); getIt<AppLauncher>().addTask(InitPlatformServiceTask());
} }
@ -58,6 +60,7 @@ Future<void> initGetIt(
getIt.registerFactory<EntryPoint>(() => f); getIt.registerFactory<EntryPoint>(() => f);
getIt.registerLazySingleton<FlowySDK>(() => const FlowySDK()); getIt.registerLazySingleton<FlowySDK>(() => const FlowySDK());
getIt.registerLazySingleton<AppLauncher>(() => AppLauncher(env, getIt)); getIt.registerLazySingleton<AppLauncher>(() => AppLauncher(env, getIt));
getIt.registerSingleton<PluginSandbox>(PluginSandbox());
await UserDepsResolver.resolve(getIt); await UserDepsResolver.resolve(getIt);
await HomeDepsResolver.resolve(getIt); await HomeDepsResolver.resolve(getIt);

View 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());
}
}

View File

@ -1,3 +1,4 @@
export 'app_widget.dart'; export 'app_widget.dart';
export 'init_sdk.dart'; export 'rust_sdk.dart';
export 'platform_service.dart'; export 'platform_service.dart';
export 'load_plugin.dart';

View File

@ -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_listener.dart';
import 'package:app_flowy/workspace/application/app/app_service.dart'; import 'package:app_flowy/workspace/application/app/app_service.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
@ -15,8 +16,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
final AppService service; final AppService service;
final AppListener listener; final AppListener listener;
AppBloc({required this.app, required this.service, required this.listener}) AppBloc({required this.app, required this.service, required this.listener}) : super(AppState.initial(app)) {
: super(AppState.initial(app)) {
on<AppEvent>((event, emit) async { on<AppEvent>((event, emit) async {
await event.map(initial: (e) async { await event.map(initial: (e) async {
listener.startListening( listener.startListening(
@ -25,8 +25,13 @@ class AppBloc extends Bloc<AppEvent, AppState> {
); );
await _fetchViews(emit); await _fetchViews(emit);
}, createView: (CreateView value) async { }, createView: (CreateView value) async {
final viewOrFailed = final viewOrFailed = await service.createView(
await service.createView(appId: app.id, name: value.name, desc: value.desc, viewType: value.viewType); appId: app.id,
name: value.name,
desc: value.desc,
dataType: value.dataType,
pluginType: value.pluginType,
);
viewOrFailed.fold( viewOrFailed.fold(
(view) => emit(state.copyWith( (view) => emit(state.copyWith(
latestCreatedView: view, latestCreatedView: view,
@ -100,7 +105,12 @@ class AppBloc extends Bloc<AppEvent, AppState> {
@freezed @freezed
class AppEvent with _$AppEvent { class AppEvent with _$AppEvent {
const factory AppEvent.initial() = Initial; 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.delete() = Delete;
const factory AppEvent.rename(String newName) = Rename; const factory AppEvent.rename(String newName) = Rename;
const factory AppEvent.didReceiveViews(List<View> views) = ReceiveViews; const factory AppEvent.didReceiveViews(List<View> views) = ReceiveViews;

View File

@ -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/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.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-error/errors.pb.dart';
import 'package:app_flowy/plugin/plugin.dart';
class AppService { class AppService {
Future<Either<App, FlowyError>> getAppDesc({required String appId}) { Future<Either<App, FlowyError>> getAppDesc({required String appId}) {
final request = AppId.create()..value = appId; final request = AppId.create()..value = appId;
@ -17,13 +17,15 @@ class AppService {
required String appId, required String appId,
required String name, required String name,
required String desc, required String desc,
required ViewType viewType, required PluginDataType dataType,
required PluginType pluginType,
}) { }) {
final request = CreateViewPayload.create() final request = CreateViewPayload.create()
..belongToId = appId ..belongToId = appId
..name = name ..name = name
..desc = desc ..desc = desc
..viewType = viewType; ..dataType = dataType
..pluginType = pluginType;
return FolderEventCreateView(request).send(); return FolderEventCreateView(request).send();
} }
@ -53,5 +55,3 @@ class AppService {
return FolderEventUpdateApp(request).send(); return FolderEventUpdateApp(request).send();
} }
} }

View File

@ -48,7 +48,7 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
void setLocale(BuildContext context, Locale newLocale) { void setLocale(BuildContext context, Locale newLocale) {
if (_locale != newLocale) { if (_locale != newLocale) {
if (!context.supportedLocales.contains(newLocale)) { if (!context.supportedLocales.contains(newLocale)) {
Log.error("Unsupported locale: $newLocale"); Log.warn("Unsupported locale: $newLocale");
newLocale = const Locale('en'); newLocale = const Locale('en');
Log.debug("Fallback to locale: $newLocale"); Log.debug("Fallback to locale: $newLocale");
} }

View File

@ -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/trash/trash_service.dart';
import 'package:app_flowy/workspace/application/view/view_listener.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/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-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:flutter_quill/flutter_quill.dart' show Document, Delta;
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -19,6 +19,7 @@ typedef FlutterQuillDocument = Document;
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> { class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
final View view; final View view;
final DocumentService service; final DocumentService service;
final ViewListener listener; final ViewListener listener;
final TrashService trashService; final TrashService trashService;
late FlutterQuillDocument document; late FlutterQuillDocument document;
@ -43,6 +44,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
}, },
deletePermanently: (DeletePermanently value) async { deletePermanently: (DeletePermanently value) async {
final result = await trashService.deleteViews([Tuple2(view.id, TrashType.TrashView)]); final result = await trashService.deleteViews([Tuple2(view.id, TrashType.TrashView)]);
final newState = result.fold((l) => state.copyWith(forceClose: true), (r) => state); final newState = result.fold((l) => state.copyWith(forceClose: true), (r) => state);
emit(newState); emit(newState);
}, },
@ -85,8 +87,8 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
listener.start(); listener.start();
final result = await service.openDocument(docId: view.id); final result = await service.openDocument(docId: view.id);
result.fold( result.fold(
(doc) { (block) {
document = _decodeJsonToDocument(doc.deltaJson); document = _decodeJsonToDocument(block.deltaJson);
_subscription = document.changes.listen((event) { _subscription = document.changes.listen((event) {
final delta = event.item2; final delta = event.item2;
final documentDelta = document.toDelta(); final documentDelta = document.toDelta();

View File

@ -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'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
class DocumentService { class DocumentService {
Future<Either<DocumentDelta, FlowyError>> openDocument({required String docId}) { Future<Either<BlockDelta, FlowyError>> openDocument({required String docId}) {
final request = ViewId(value: docId); final request = ViewId(value: docId);
return FolderEventOpenView(request).send(); return FolderEventOpenView(request).send();
} }
Future<Either<DocumentDelta, FlowyError>> composeDelta({required String docId, required String data}) { Future<Either<BlockDelta, FlowyError>> composeDelta({required String docId, required String data}) {
final request = DocumentDelta.create() final request = BlockDelta.create()
..docId = docId ..blockId = docId
..deltaJson = data; ..deltaJson = data;
return FolderEventApplyDocDelta(request).send(); return FolderEventApplyDocDelta(request).send();
} }

View File

@ -7,7 +7,7 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
class ShareService { class ShareService {
Future<Either<ExportData, FlowyError>> export(String docId, ExportType type) { Future<Either<ExportData, FlowyError>> export(String docId, ExportType type) {
final request = ExportPayload.create() final request = ExportPayload.create()
..docId = docId ..viewId = docId
..exportType = type; ..exportType = type;
return FolderEventExportDocument(request).send(); return FolderEventExportDocument(request).send();

View File

@ -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:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
// ignore: import_of_legacy_library_into_null_safe // ignore: import_of_legacy_library_into_null_safe

View File

@ -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:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';

View File

@ -1,8 +1,8 @@
import 'dart:async'; 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_listener.dart';
import 'package:app_flowy/workspace/application/workspace/workspace_service.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:dartz/dartz.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.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)); emit(state.copyWith(isCollapse: !isCollapse));
}, },
openPage: (e) async { openPage: (e) async {
emit(state.copyWith(stackContext: e.context)); emit(state.copyWith(plugin: e.plugin));
}, },
createApp: (CreateApp event) async { createApp: (CreateApp event) async {
await _performActionOnCreateApp(event, emit); await _performActionOnCreateApp(event, emit);
@ -85,7 +85,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
class MenuEvent with _$MenuEvent { class MenuEvent with _$MenuEvent {
const factory MenuEvent.initial() = _Initial; const factory MenuEvent.initial() = _Initial;
const factory MenuEvent.collapse() = Collapse; 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.createApp(String name, {String? desc}) = CreateApp;
const factory MenuEvent.didReceiveApps(Either<List<App>, FlowyError> appsOrFail) = ReceiveApps; const factory MenuEvent.didReceiveApps(Either<List<App>, FlowyError> appsOrFail) = ReceiveApps;
} }
@ -96,13 +96,13 @@ class MenuState with _$MenuState {
required bool isCollapse, required bool isCollapse,
required Option<List<App>> apps, required Option<List<App>> apps,
required Either<Unit, FlowyError> successOrFailure, required Either<Unit, FlowyError> successOrFailure,
required HomeStackContext stackContext, required Plugin plugin,
}) = _MenuState; }) = _MenuState;
factory MenuState.initial() => MenuState( factory MenuState.initial() => MenuState(
isCollapse: false, isCollapse: false,
apps: none(), apps: none(),
successOrFailure: left(unit), successOrFailure: left(unit),
stackContext: BlankStackContext(), plugin: makePlugin(pluginType: DefaultPlugin.blank.type()),
); );
} }

File diff suppressed because it is too large Load Diff

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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";
}
}

View File

@ -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(),
);
}),
);
}
}

View File

@ -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;
}
}
}

View File

@ -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_bloc.dart';
import 'package:app_flowy/workspace/application/home/home_listen_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/widgets/edit_pannel/pannel_animation.dart';
import 'package:app_flowy/workspace/presentation/stack_page/home_stack.dart';
import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.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:app_flowy/startup/startup.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flowy_infra_ui/style_widget/container.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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.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_layout.dart';
import 'home_stack.dart';
import 'menu/menu.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey(); static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
@ -109,7 +111,8 @@ class _HomeScreenState extends State<HomeScreen> {
Widget _buildHomeMenu({required HomeLayout layout, required BuildContext context}) { Widget _buildHomeMenu({required HomeLayout layout, required BuildContext context}) {
if (initialView == null && widget.workspaceSetting.hasLatestView()) { if (initialView == null && widget.workspaceSetting.hasLatestView()) {
initialView = widget.workspaceSetting.latestView; initialView = widget.workspaceSetting.latestView;
getIt<HomeStackManager>().setStack(initialView!.stackContext()); final plugin = makePlugin(pluginType: initialView!.pluginType, data: initialView);
getIt<HomeStackManager>().setPlugin(plugin);
} }
HomeMenu homeMenu = HomeMenu( HomeMenu homeMenu = HomeMenu(

View File

@ -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),
);
}
}

View File

@ -1,16 +1,16 @@
import 'package:app_flowy/plugin/plugin.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.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/hover.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/text.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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
class AddButton extends StatelessWidget { class AddButton extends StatelessWidget {
final Function(ViewType) onSelected; final Function(PluginBuilder) onSelected;
const AddButton({ const AddButton({
Key? key, Key? key,
required this.onSelected, required this.onSelected,
@ -34,21 +34,24 @@ class AddButton extends StatelessWidget {
} }
class ActionList { class ActionList {
final Function(ViewType) onSelected; final Function(PluginBuilder) onSelected;
final BuildContext anchorContext; final BuildContext anchorContext;
final String _identifier = 'DisclosureButtonActionList'; final String _identifier = 'DisclosureButtonActionList';
const ActionList({required this.anchorContext, required this.onSelected}); const ActionList({required this.anchorContext, required this.onSelected});
void show(BuildContext buildContext) { void show(BuildContext buildContext) {
final items = ViewType.values.where((element) => element != ViewType.Blank).map((ty) { final items = pluginBuilders().map(
return CreateItem( (pluginBuilder) {
viewType: ty, return CreateItem(
onSelected: (viewType) { pluginBuilder: pluginBuilder,
onSelected: (builder) {
FlowyOverlay.of(buildContext).remove(_identifier); FlowyOverlay.of(buildContext).remove(_identifier);
onSelected(viewType); onSelected(builder);
}); },
}).toList(); );
},
).toList();
ListOverlay.showWithAnchor( ListOverlay.showWithAnchor(
buildContext, buildContext,
@ -64,11 +67,11 @@ class ActionList {
} }
class CreateItem extends StatelessWidget { class CreateItem extends StatelessWidget {
final ViewType viewType; final PluginBuilder pluginBuilder;
final Function(ViewType) onSelected; final Function(PluginBuilder) onSelected;
const CreateItem({ const CreateItem({
Key? key, Key? key,
required this.viewType, required this.pluginBuilder,
required this.onSelected, required this.onSelected,
}) : super(key: key); }) : super(key: key);
@ -81,9 +84,9 @@ class CreateItem extends StatelessWidget {
config: config, config: config,
builder: (context, onHover) { builder: (context, onHover) {
return GestureDetector( return GestureDetector(
onTap: () => onSelected(viewType), onTap: () => onSelected(pluginBuilder),
child: FlowyText.medium( child: FlowyText.medium(
viewType.name, pluginBuilder.menuName,
color: theme.textColor, color: theme.textColor,
fontSize: 12, fontSize: 12,
).padding(horizontal: 10, vertical: 6), ).padding(horizontal: 10, vertical: 6),

View File

@ -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:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:expandable/expandable.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:styled_widget/styled_widget.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/image.dart';
import '../menu_app.dart'; import '../menu_app.dart';
import 'add_button.dart'; import 'add_button.dart';
import 'right_click_action.dart'; import 'right_click_action.dart';
@ -102,10 +103,13 @@ class MenuAppHeader extends StatelessWidget {
return Tooltip( return Tooltip(
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(), message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
child: AddButton( child: AddButton(
onSelected: (viewType) { onSelected: (pluginBuilder) {
context context.read<AppBloc>().add(AppEvent.createView(
.read<AppBloc>() LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
.add(AppEvent.createView(LocaleKeys.menuAppHeader_defaultNewPageName.tr(), "", viewType)); "",
pluginBuilder.dataType,
pluginBuilder.pluginType,
));
}, },
).padding(right: MenuAppSizes.headerPadding), ).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));
}
}
}

View File

@ -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:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:dartz/dartz.dart' as dartz; import 'package:dartz/dartz.dart' as dartz;
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'header.dart';
class AppDisclosureActionSheet with ActionList<DisclosureActionWrapper> implements FlowyOverlayDelegate { class AppDisclosureActionSheet with ActionList<DisclosureActionWrapper> implements FlowyOverlayDelegate {
final Function(dartz.Option<AppDisclosureAction>) onSelected; final Function(dartz.Option<AppDisclosureAction>) onSelected;
final _items = AppDisclosureAction.values.map((action) => DisclosureActionWrapper(action)).toList(); final _items = AppDisclosureAction.values.map((action) => DisclosureActionWrapper(action)).toList();

View File

@ -1,6 +1,5 @@
import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart'; import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/header/header.dart';
import 'package:expandable/expandable.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/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';

View File

@ -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:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:dartz/dartz.dart' as dartz; import 'package:dartz/dartz.dart' as dartz;
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
@ -8,6 +7,8 @@ import 'package:flutter/material.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'item.dart';
// [[Widget: LifeCycle]] // [[Widget: LifeCycle]]
// https://flutterbyexample.com/lesson/stateful-widget-lifecycle // https://flutterbyexample.com/lesson/stateful-widget-lifecycle
class ViewDisclosureButton extends StatelessWidget class ViewDisclosureButton extends StatelessWidget

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/view/view_bloc.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:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:dartz/dartz.dart' as dartz; import 'package:dartz/dartz.dart' as dartz;
import 'package:easy_localization/easy_localization.dart'; 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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.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:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/image.dart';
import 'disclosure_action.dart'; import 'disclosure_action.dart';
@ -55,7 +55,7 @@ class ViewSectionItem extends StatelessWidget {
Widget _render(BuildContext context, bool onHover, ViewState state, Color iconColor) { Widget _render(BuildContext context, bool onHover, ViewState state, Color iconColor) {
List<Widget> children = [ 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), const HSpace(2),
Expanded(child: FlowyText.regular(state.view.name, fontSize: 12, overflow: TextOverflow.clip)), 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));
}
}
}

View File

@ -1,8 +1,7 @@
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart'; import 'package:app_flowy/workspace/application/view/view_ext.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/menu/menu.dart'; import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/menu_app.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -106,7 +105,7 @@ class ViewSectionNotifier with ChangeNotifier {
if (view != null) { if (view != null) {
WidgetsBinding.instance?.addPostFrameCallback((_) { WidgetsBinding.instance?.addPostFrameCallback((_) {
getIt<HomeStackManager>().setStack(view.stackContext()); getIt<HomeStackManager>().setPlugin(view.plugin());
}); });
} else { } else {
// do nothing // do nothing

View File

@ -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/notifier.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.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:flowy_infra/time/duration.dart';
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/menu/menu_bloc.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/home/home_sizes.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/menu_user.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'widget/app/menu_app.dart'; import 'app/menu_app.dart';
import 'widget/app/create_button.dart'; import 'app/create_button.dart';
import 'widget/menu_trash.dart'; import 'menu_user.dart';
// [[diagram: HomeMenu's widget structure]]
// get user profile or modify user
//
// IUser
// MenuTopBar
// MenuUserMenuUserBloc
//
// HomeMenu IUserListener
//
// listen workspace changes or user
// impl profile changes
//
// MenuList MenuItem
//
// AppBloc fetch app's views or modify view
//
//
// MenuApp
//
class HomeMenu extends StatelessWidget { class HomeMenu extends StatelessWidget {
final PublishNotifier<bool> _collapsedNotifier; final PublishNotifier<bool> _collapsedNotifier;
@ -70,9 +55,9 @@ class HomeMenu extends StatelessWidget {
child: MultiBlocListener( child: MultiBlocListener(
listeners: [ listeners: [
BlocListener<MenuBloc, MenuState>( BlocListener<MenuBloc, MenuState>(
listenWhen: (p, c) => p.stackContext != c.stackContext, listenWhen: (p, c) => p.plugin.pluginId != c.plugin.pluginId,
listener: (context, state) { listener: (context, state) {
getIt<HomeStackManager>().setStack(state.stackContext); getIt<HomeStackManager>().setPlugin(state.plugin);
}, },
), ),
BlocListener<MenuBloc, MenuState>( 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),
)
],
),
);
},
);
}
}

View File

@ -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/image.dart';
import 'package:flowy_infra/notifier.dart'; import 'package:flowy_infra/notifier.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
@ -17,8 +17,8 @@ class NavigationNotifier with ChangeNotifier {
void update(HomeStackNotifier notifier) { void update(HomeStackNotifier notifier) {
bool shouldNotify = false; bool shouldNotify = false;
if (navigationItems != notifier.context.navigationItems) { if (navigationItems != notifier.plugin.pluginDisplay.navigationItems) {
navigationItems = notifier.context.navigationItems; navigationItems = notifier.plugin.pluginDisplay.navigationItems;
shouldNotify = true; shouldNotify = true;
} }
@ -59,7 +59,7 @@ class FlowyNavigation extends StatelessWidget {
create: (_) { create: (_) {
final notifier = Provider.of<HomeStackNotifier>(context, listen: false); final notifier = Provider.of<HomeStackNotifier>(context, listen: false);
return NavigationNotifier( return NavigationNotifier(
navigationItems: notifier.context.navigationItems, navigationItems: notifier.plugin.pluginDisplay.navigationItems,
collapasedNotifier: notifier.collapsedNotifier, collapasedNotifier: notifier.collapsedNotifier,
); );
}, },
@ -179,7 +179,4 @@ class EllipsisNaviItem extends NavigationItem {
@override @override
NavigationCallback get action => (id) {}; NavigationCallback get action => (id) {};
@override
String get identifier => "Ellipsis";
} }

View File

@ -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:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
class BlankStackContext extends HomeStackContext { import 'package:app_flowy/generated/locale_keys.g.dart';
final ValueNotifier<bool> _isUpdated = ValueNotifier<bool>(false); import 'package:app_flowy/plugin/plugin.dart';
class BlankPluginBuilder extends PluginBuilder {
@override
Plugin build(dynamic data) {
return BlankPagePlugin(pluginType: pluginType);
}
@override @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 @override
Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr(), fontSize: 12); Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr(), fontSize: 12);
@override @override
Widget? get rightBarItem => null; Widget buildWidget() => const BlankStackPage();
@override
HomeStackType get type => HomeStackType.blank;
@override
Widget buildWidget() {
return const BlankStackPage();
}
@override @override
List<NavigationItem> get navigationItems => [this]; List<NavigationItem> get navigationItems => [this];
@override
ValueNotifier<bool> get isUpdated => _isUpdated;
@override
void dispose() {}
} }
class BlankStackPage extends StatefulWidget { class BlankStackPage extends StatefulWidget {

View File

@ -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/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/appearance.dart';
import 'package:app_flowy/workspace/application/doc/share_bloc.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_listener.dart';
import 'package:app_flowy/workspace/application/view/view_service.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/presentation/home/home_stack.dart';
import 'package:app_flowy/workspace/domain/view_ext.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:easy_localization/easy_localization.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:app_flowy/generated/locale_keys.g.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'document_page.dart'; import 'src/document_page.dart';
class DocStackContext extends HomeStackContext<int, ShareActionWrapper> { class DocumentPluginBuilder extends PluginBuilder {
View _view; @override
late ViewListener _listener; Plugin build(dynamic data) {
final ValueNotifier<int> _isUpdated = ValueNotifier<int>(0); 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 = getIt<ViewListener>(param1: view);
_listener.updatedNotifier.addPublishListener((result) { _listener?.updatedNotifier.addPublishListener((result) {
result.fold( result.fold(
(newView) { (newView) {
_view = newView; _view = newView;
_isUpdated.value = _view.hashCode; _displayNotifier.value = _view.hashCode;
}, },
(error) {}, (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 @override
Widget get leftBarItem => DocumentLeftBarItem(view: _view); Widget get leftBarItem => DocumentLeftBarItem(view: _view);
@override @override
Widget? get rightBarItem => DocumentShareButton(view: _view); 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 @override
List<NavigationItem> get navigationItems => _makeNavigationItems(); 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() { List<NavigationItem> _makeNavigationItems() {
return [ return [
this, this,
]; ];
} }
@override
void dispose() {
_listener.close();
}
} }
class DocumentLeftBarItem extends StatefulWidget { class DocumentLeftBarItem extends StatefulWidget {

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/workspace/application/doc/doc_bloc.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/style_widget/scrolling/styled_scroll_bar.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter_quill/flutter_quill.dart' as quill; 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 'package:styled_widget/styled_widget.dart';
import 'styles.dart'; import 'styles.dart';
import 'widget/banner.dart'; import 'widget/banner.dart';
import 'widget/toolbar/tool_bar.dart';
class DocumentPage extends StatefulWidget { class DocumentPage extends StatefulWidget {
final View view; final View view;

View File

@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'widget/style_widgets/style_widgets.dart'; import 'widget/style_widgets.dart';
DefaultStyles customStyles(BuildContext context) { DefaultStyles customStyles(BuildContext context) {
const baseSpacing = Tuple2<double, double>(6, 0); const baseSpacing = Tuple2<double, double>(6, 0);

View File

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:math'; 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:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill/flutter_quill.dart';
@ -10,6 +9,7 @@ import 'package:styled_widget/styled_widget.dart';
import 'check_button.dart'; import 'check_button.dart';
import 'color_picker.dart'; import 'color_picker.dart';
import 'header_button.dart'; import 'header_button.dart';
import 'history_button.dart';
import 'link_button.dart'; import 'link_button.dart';
import 'toggle_button.dart'; import 'toggle_button.dart';
import 'toolbar_icon_button.dart'; import 'toolbar_icon_button.dart';

View File

@ -1,8 +1,9 @@
import 'package:app_flowy/plugin/plugin.dart';
import 'package:app_flowy/startup/startup.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/appearance.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:app_flowy/workspace/presentation/stack_page/trash/trash_page.dart'; import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
@ -22,7 +23,7 @@ class MenuTrash extends StatelessWidget {
child: InkWell( child: InkWell(
onTap: () { onTap: () {
Provider.of<MenuSharedState>(context, listen: false).selectedView.value = null; Provider.of<MenuSharedState>(context, listen: false).selectedView.value = null;
getIt<HomeStackManager>().setStack(TrashStackContext()); getIt<HomeStackManager>().setPlugin(makePlugin(pluginType: DefaultPlugin.trash.type()));
}, },
child: _render(context), child: _render(context),
), ),

View File

@ -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/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/application/trash/trash_bloc.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart'; import 'package:app_flowy/workspace/presentation/home/home_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:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.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:styled_widget/styled_widget.dart';
import 'package:app_flowy/generated/locale_keys.g.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 { class TrashPluginBuilder extends PluginBuilder {
final ValueNotifier<bool> _isUpdated = ValueNotifier<bool>(false); @override
Plugin build(dynamic data) {
return TrashPlugin(pluginType: pluginType);
}
@override @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 @override
Widget get leftBarItem => FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12); Widget get leftBarItem => FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12);
@ -32,21 +69,10 @@ class TrashStackContext extends HomeStackContext {
Widget? get rightBarItem => null; Widget? get rightBarItem => null;
@override @override
HomeStackType get type => HomeStackType.trash; Widget buildWidget() => const TrashStackPage(key: ValueKey('TrashStackPage'));
@override
Widget buildWidget() {
return const TrashStackPage(key: ValueKey('TrashStackPage'));
}
@override @override
List<NavigationItem> get navigationItems => [this]; List<NavigationItem> get navigationItems => [this];
@override
ValueNotifier<bool> get isUpdated => _isUpdated;
@override
void dispose() {}
} }
class TrashStackPage extends StatefulWidget { class TrashStackPage extends StatefulWidget {

View File

@ -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),
);
}
}

View File

@ -1,5 +1,5 @@
import 'package:app_flowy/workspace/application/edit_pannel/edit_pannel_bloc.dart'; 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/startup/startup.dart';
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';

View File

@ -1,7 +1,6 @@
import 'package:app_flowy/workspace/presentation/plugins/doc/document.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.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'; import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
class FlowyEmojiStyleButton extends StatefulWidget { class FlowyEmojiStyleButton extends StatefulWidget {

View File

@ -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:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme.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:app_flowy/generated/locale_keys.g.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:app_flowy/workspace/presentation/stack_page/home_stack.dart';
class QuestionBubble extends StatelessWidget { class QuestionBubble extends StatelessWidget {
const QuestionBubble({Key? key}) : super(key: key); const QuestionBubble({Key? key}) : super(key: key);

View File

@ -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),
],
),
);
}
}

View File

@ -1 +0,0 @@
export 'menu.dart';

View File

@ -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),
)
],
),
);
},
);
}
}

View File

@ -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';

View File

@ -271,14 +271,14 @@ class FolderEventOpenView {
ViewId request; ViewId request;
FolderEventOpenView(this.request); FolderEventOpenView(this.request);
Future<Either<DocumentDelta, FlowyError>> send() { Future<Either<BlockDelta, FlowyError>> send() {
final request = FFIRequest.create() final request = FFIRequest.create()
..event = FolderEvent.OpenView.toString() ..event = FolderEvent.OpenView.toString()
..payload = requestToBytes(this.request); ..payload = requestToBytes(this.request);
return Dispatch.asyncRequest(request) return Dispatch.asyncRequest(request)
.then((bytesResult) => bytesResult.fold( .then((bytesResult) => bytesResult.fold(
(okBytes) => left(DocumentDelta.fromBuffer(okBytes)), (okBytes) => left(BlockDelta.fromBuffer(okBytes)),
(errBytes) => right(FlowyError.fromBuffer(errBytes)), (errBytes) => right(FlowyError.fromBuffer(errBytes)),
)); ));
} }
@ -378,17 +378,17 @@ class FolderEventDeleteAllTrash {
} }
class FolderEventApplyDocDelta { class FolderEventApplyDocDelta {
DocumentDelta request; BlockDelta request;
FolderEventApplyDocDelta(this.request); FolderEventApplyDocDelta(this.request);
Future<Either<DocumentDelta, FlowyError>> send() { Future<Either<BlockDelta, FlowyError>> send() {
final request = FFIRequest.create() final request = FFIRequest.create()
..event = FolderEvent.ApplyDocDelta.toString() ..event = FolderEvent.ApplyDocDelta.toString()
..payload = requestToBytes(this.request); ..payload = requestToBytes(this.request);
return Dispatch.asyncRequest(request) return Dispatch.asyncRequest(request)
.then((bytesResult) => bytesResult.fold( .then((bytesResult) => bytesResult.fold(
(okBytes) => left(DocumentDelta.fromBuffer(okBytes)), (okBytes) => left(BlockDelta.fromBuffer(okBytes)),
(errBytes) => right(FlowyError.fromBuffer(errBytes)), (errBytes) => right(FlowyError.fromBuffer(errBytes)),
)); ));
} }

View File

@ -12,15 +12,15 @@ import 'package:protobuf/protobuf.dart' as $pb;
import 'revision.pb.dart' as $0; import 'revision.pb.dart' as $0;
class CreateDocParams extends $pb.GeneratedMessage { class CreateBlockParams extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateDocParams', createEmptyInstance: create) 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') ..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) ..aOM<$0.RepeatedRevision>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'revisions', subBuilder: $0.RepeatedRevision.create)
..hasRequiredFields = false ..hasRequiredFields = false
; ;
CreateDocParams._() : super(); CreateBlockParams._() : super();
factory CreateDocParams({ factory CreateBlockParams({
$core.String? id, $core.String? id,
$0.RepeatedRevision? revisions, $0.RepeatedRevision? revisions,
}) { }) {
@ -33,26 +33,26 @@ class CreateDocParams extends $pb.GeneratedMessage {
} }
return _result; return _result;
} }
factory CreateDocParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory CreateBlockParams.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.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
@$core.Deprecated( @$core.Deprecated(
'Using this can add significant overhead to your binary. ' 'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version') 'Will be removed in next major version')
CreateDocParams clone() => CreateDocParams()..mergeFromMessage(this); CreateBlockParams clone() => CreateBlockParams()..mergeFromMessage(this);
@$core.Deprecated( @$core.Deprecated(
'Using this can add significant overhead to your binary. ' 'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version') '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; $pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline') @$core.pragma('dart2js:noInline')
static CreateDocParams create() => CreateDocParams._(); static CreateBlockParams create() => CreateBlockParams._();
CreateDocParams createEmptyInstance() => create(); CreateBlockParams createEmptyInstance() => create();
static $pb.PbList<CreateDocParams> createRepeated() => $pb.PbList<CreateDocParams>(); static $pb.PbList<CreateBlockParams> createRepeated() => $pb.PbList<CreateBlockParams>();
@$core.pragma('dart2js:noInline') @$core.pragma('dart2js:noInline')
static CreateDocParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateDocParams>(create); static CreateBlockParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateBlockParams>(create);
static CreateDocParams? _defaultInstance; static CreateBlockParams? _defaultInstance;
@$pb.TagNumber(1) @$pb.TagNumber(1)
$core.String get id => $_getSZ(0); $core.String get id => $_getSZ(0);
@ -75,8 +75,8 @@ class CreateDocParams extends $pb.GeneratedMessage {
$0.RepeatedRevision ensureRevisions() => $_ensure(1); $0.RepeatedRevision ensureRevisions() => $_ensure(1);
} }
class DocumentInfo extends $pb.GeneratedMessage { class BlockInfo extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocumentInfo', createEmptyInstance: create) 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(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'docId')
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'text') ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'text')
..aInt64(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'revId') ..aInt64(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'revId')
@ -84,8 +84,8 @@ class DocumentInfo extends $pb.GeneratedMessage {
..hasRequiredFields = false ..hasRequiredFields = false
; ;
DocumentInfo._() : super(); BlockInfo._() : super();
factory DocumentInfo({ factory BlockInfo({
$core.String? docId, $core.String? docId,
$core.String? text, $core.String? text,
$fixnum.Int64? revId, $fixnum.Int64? revId,
@ -106,26 +106,26 @@ class DocumentInfo extends $pb.GeneratedMessage {
} }
return _result; return _result;
} }
factory DocumentInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory BlockInfo.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.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
@$core.Deprecated( @$core.Deprecated(
'Using this can add significant overhead to your binary. ' 'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version') 'Will be removed in next major version')
DocumentInfo clone() => DocumentInfo()..mergeFromMessage(this); BlockInfo clone() => BlockInfo()..mergeFromMessage(this);
@$core.Deprecated( @$core.Deprecated(
'Using this can add significant overhead to your binary. ' 'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version') '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; $pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline') @$core.pragma('dart2js:noInline')
static DocumentInfo create() => DocumentInfo._(); static BlockInfo create() => BlockInfo._();
DocumentInfo createEmptyInstance() => create(); BlockInfo createEmptyInstance() => create();
static $pb.PbList<DocumentInfo> createRepeated() => $pb.PbList<DocumentInfo>(); static $pb.PbList<BlockInfo> createRepeated() => $pb.PbList<BlockInfo>();
@$core.pragma('dart2js:noInline') @$core.pragma('dart2js:noInline')
static DocumentInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocumentInfo>(create); static BlockInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BlockInfo>(create);
static DocumentInfo? _defaultInstance; static BlockInfo? _defaultInstance;
@$pb.TagNumber(1) @$pb.TagNumber(1)
$core.String get docId => $_getSZ(0); $core.String get docId => $_getSZ(0);
@ -227,56 +227,56 @@ class ResetDocumentParams extends $pb.GeneratedMessage {
$0.RepeatedRevision ensureRevisions() => $_ensure(1); $0.RepeatedRevision ensureRevisions() => $_ensure(1);
} }
class DocumentDelta extends $pb.GeneratedMessage { class BlockDelta extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocumentDelta', createEmptyInstance: create) 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') ? '' : 'docId') ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deltaJson') ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deltaJson')
..hasRequiredFields = false ..hasRequiredFields = false
; ;
DocumentDelta._() : super(); BlockDelta._() : super();
factory DocumentDelta({ factory BlockDelta({
$core.String? docId, $core.String? blockId,
$core.String? deltaJson, $core.String? deltaJson,
}) { }) {
final _result = create(); final _result = create();
if (docId != null) { if (blockId != null) {
_result.docId = docId; _result.blockId = blockId;
} }
if (deltaJson != null) { if (deltaJson != null) {
_result.deltaJson = deltaJson; _result.deltaJson = deltaJson;
} }
return _result; return _result;
} }
factory DocumentDelta.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory BlockDelta.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.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
@$core.Deprecated( @$core.Deprecated(
'Using this can add significant overhead to your binary. ' 'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version') 'Will be removed in next major version')
DocumentDelta clone() => DocumentDelta()..mergeFromMessage(this); BlockDelta clone() => BlockDelta()..mergeFromMessage(this);
@$core.Deprecated( @$core.Deprecated(
'Using this can add significant overhead to your binary. ' 'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version') '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; $pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline') @$core.pragma('dart2js:noInline')
static DocumentDelta create() => DocumentDelta._(); static BlockDelta create() => BlockDelta._();
DocumentDelta createEmptyInstance() => create(); BlockDelta createEmptyInstance() => create();
static $pb.PbList<DocumentDelta> createRepeated() => $pb.PbList<DocumentDelta>(); static $pb.PbList<BlockDelta> createRepeated() => $pb.PbList<BlockDelta>();
@$core.pragma('dart2js:noInline') @$core.pragma('dart2js:noInline')
static DocumentDelta getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocumentDelta>(create); static BlockDelta getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BlockDelta>(create);
static DocumentDelta? _defaultInstance; static BlockDelta? _defaultInstance;
@$pb.TagNumber(1) @$pb.TagNumber(1)
$core.String get docId => $_getSZ(0); $core.String get blockId => $_getSZ(0);
@$pb.TagNumber(1) @$pb.TagNumber(1)
set docId($core.String v) { $_setString(0, v); } set blockId($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1) @$pb.TagNumber(1)
$core.bool hasDocId() => $_has(0); $core.bool hasBlockId() => $_has(0);
@$pb.TagNumber(1) @$pb.TagNumber(1)
void clearDocId() => clearField(1); void clearBlockId() => clearField(1);
@$pb.TagNumber(2) @$pb.TagNumber(2)
$core.String get deltaJson => $_getSZ(1); $core.String get deltaJson => $_getSZ(1);
@ -363,14 +363,14 @@ class NewDocUser extends $pb.GeneratedMessage {
void clearDocId() => clearField(3); void clearDocId() => clearField(3);
} }
class DocumentId extends $pb.GeneratedMessage { class BlockId extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'DocumentId', createEmptyInstance: create) 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') ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'value')
..hasRequiredFields = false ..hasRequiredFields = false
; ;
DocumentId._() : super(); BlockId._() : super();
factory DocumentId({ factory BlockId({
$core.String? value, $core.String? value,
}) { }) {
final _result = create(); final _result = create();
@ -379,26 +379,26 @@ class DocumentId extends $pb.GeneratedMessage {
} }
return _result; return _result;
} }
factory DocumentId.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory BlockId.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.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
@$core.Deprecated( @$core.Deprecated(
'Using this can add significant overhead to your binary. ' 'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version') 'Will be removed in next major version')
DocumentId clone() => DocumentId()..mergeFromMessage(this); BlockId clone() => BlockId()..mergeFromMessage(this);
@$core.Deprecated( @$core.Deprecated(
'Using this can add significant overhead to your binary. ' 'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version') '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; $pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline') @$core.pragma('dart2js:noInline')
static DocumentId create() => DocumentId._(); static BlockId create() => BlockId._();
DocumentId createEmptyInstance() => create(); BlockId createEmptyInstance() => create();
static $pb.PbList<DocumentId> createRepeated() => $pb.PbList<DocumentId>(); static $pb.PbList<BlockId> createRepeated() => $pb.PbList<BlockId>();
@$core.pragma('dart2js:noInline') @$core.pragma('dart2js:noInline')
static DocumentId getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DocumentId>(create); static BlockId getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BlockId>(create);
static DocumentId? _defaultInstance; static BlockId? _defaultInstance;
@$pb.TagNumber(1) @$pb.TagNumber(1)
$core.String get value => $_getSZ(0); $core.String get value => $_getSZ(0);

View File

@ -8,20 +8,20 @@
import 'dart:core' as $core; import 'dart:core' as $core;
import 'dart:convert' as $convert; import 'dart:convert' as $convert;
import 'dart:typed_data' as $typed_data; import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use createDocParamsDescriptor instead') @$core.Deprecated('Use createBlockParamsDescriptor instead')
const CreateDocParams$json = const { const CreateBlockParams$json = const {
'1': 'CreateDocParams', '1': 'CreateBlockParams',
'2': const [ '2': const [
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
const {'1': 'revisions', '3': 2, '4': 1, '5': 11, '6': '.RepeatedRevision', '10': 'revisions'}, const {'1': 'revisions', '3': 2, '4': 1, '5': 11, '6': '.RepeatedRevision', '10': 'revisions'},
], ],
}; };
/// Descriptor for `CreateDocParams`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `CreateBlockParams`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List createDocParamsDescriptor = $convert.base64Decode('Cg9DcmVhdGVEb2NQYXJhbXMSDgoCaWQYASABKAlSAmlkEi8KCXJldmlzaW9ucxgCIAEoCzIRLlJlcGVhdGVkUmV2aXNpb25SCXJldmlzaW9ucw=='); final $typed_data.Uint8List createBlockParamsDescriptor = $convert.base64Decode('ChFDcmVhdGVCbG9ja1BhcmFtcxIOCgJpZBgBIAEoCVICaWQSLwoJcmV2aXNpb25zGAIgASgLMhEuUmVwZWF0ZWRSZXZpc2lvblIJcmV2aXNpb25z');
@$core.Deprecated('Use documentInfoDescriptor instead') @$core.Deprecated('Use blockInfoDescriptor instead')
const DocumentInfo$json = const { const BlockInfo$json = const {
'1': 'DocumentInfo', '1': 'BlockInfo',
'2': const [ '2': const [
const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'}, const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'},
const {'1': 'text', '3': 2, '4': 1, '5': 9, '10': 'text'}, 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`. /// Descriptor for `BlockInfo`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List documentInfoDescriptor = $convert.base64Decode('CgxEb2N1bWVudEluZm8SFQoGZG9jX2lkGAEgASgJUgVkb2NJZBISCgR0ZXh0GAIgASgJUgR0ZXh0EhUKBnJldl9pZBgDIAEoA1IFcmV2SWQSHgoLYmFzZV9yZXZfaWQYBCABKANSCWJhc2VSZXZJZA=='); final $typed_data.Uint8List blockInfoDescriptor = $convert.base64Decode('CglCbG9ja0luZm8SFQoGZG9jX2lkGAEgASgJUgVkb2NJZBISCgR0ZXh0GAIgASgJUgR0ZXh0EhUKBnJldl9pZBgDIAEoA1IFcmV2SWQSHgoLYmFzZV9yZXZfaWQYBCABKANSCWJhc2VSZXZJZA==');
@$core.Deprecated('Use resetDocumentParamsDescriptor instead') @$core.Deprecated('Use resetDocumentParamsDescriptor instead')
const ResetDocumentParams$json = const { const ResetDocumentParams$json = const {
'1': 'ResetDocumentParams', '1': 'ResetDocumentParams',
@ -43,17 +43,17 @@ const ResetDocumentParams$json = const {
/// Descriptor for `ResetDocumentParams`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `ResetDocumentParams`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List resetDocumentParamsDescriptor = $convert.base64Decode('ChNSZXNldERvY3VtZW50UGFyYW1zEhUKBmRvY19pZBgBIAEoCVIFZG9jSWQSLwoJcmV2aXNpb25zGAIgASgLMhEuUmVwZWF0ZWRSZXZpc2lvblIJcmV2aXNpb25z'); final $typed_data.Uint8List resetDocumentParamsDescriptor = $convert.base64Decode('ChNSZXNldERvY3VtZW50UGFyYW1zEhUKBmRvY19pZBgBIAEoCVIFZG9jSWQSLwoJcmV2aXNpb25zGAIgASgLMhEuUmVwZWF0ZWRSZXZpc2lvblIJcmV2aXNpb25z');
@$core.Deprecated('Use documentDeltaDescriptor instead') @$core.Deprecated('Use blockDeltaDescriptor instead')
const DocumentDelta$json = const { const BlockDelta$json = const {
'1': 'DocumentDelta', '1': 'BlockDelta',
'2': const [ '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'}, const {'1': 'delta_json', '3': 2, '4': 1, '5': 9, '10': 'deltaJson'},
], ],
}; };
/// Descriptor for `DocumentDelta`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `BlockDelta`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List documentDeltaDescriptor = $convert.base64Decode('Cg1Eb2N1bWVudERlbHRhEhUKBmRvY19pZBgBIAEoCVIFZG9jSWQSHQoKZGVsdGFfanNvbhgCIAEoCVIJZGVsdGFKc29u'); final $typed_data.Uint8List blockDeltaDescriptor = $convert.base64Decode('CgpCbG9ja0RlbHRhEhkKCGJsb2NrX2lkGAEgASgJUgdibG9ja0lkEh0KCmRlbHRhX2pzb24YAiABKAlSCWRlbHRhSnNvbg==');
@$core.Deprecated('Use newDocUserDescriptor instead') @$core.Deprecated('Use newDocUserDescriptor instead')
const NewDocUser$json = const { const NewDocUser$json = const {
'1': 'NewDocUser', '1': 'NewDocUser',
@ -66,13 +66,13 @@ const NewDocUser$json = const {
/// Descriptor for `NewDocUser`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `NewDocUser`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List newDocUserDescriptor = $convert.base64Decode('CgpOZXdEb2NVc2VyEhcKB3VzZXJfaWQYASABKAlSBnVzZXJJZBIVCgZyZXZfaWQYAiABKANSBXJldklkEhUKBmRvY19pZBgDIAEoCVIFZG9jSWQ='); final $typed_data.Uint8List newDocUserDescriptor = $convert.base64Decode('CgpOZXdEb2NVc2VyEhcKB3VzZXJfaWQYASABKAlSBnVzZXJJZBIVCgZyZXZfaWQYAiABKANSBXJldklkEhUKBmRvY19pZBgDIAEoCVIFZG9jSWQ=');
@$core.Deprecated('Use documentIdDescriptor instead') @$core.Deprecated('Use blockIdDescriptor instead')
const DocumentId$json = const { const BlockId$json = const {
'1': 'DocumentId', '1': 'BlockId',
'2': const [ '2': const [
const {'1': 'value', '3': 1, '4': 1, '5': 9, '10': 'value'}, const {'1': 'value', '3': 1, '4': 1, '5': 9, '10': 'value'},
], ],
}; };
/// Descriptor for `DocumentId`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `BlockId`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List documentIdDescriptor = $convert.base64Decode('CgpEb2N1bWVudElkEhQKBXZhbHVlGAEgASgJUgV2YWx1ZQ=='); final $typed_data.Uint8List blockIdDescriptor = $convert.base64Decode('CgdCbG9ja0lkEhQKBXZhbHVlGAEgASgJUgV2YWx1ZQ==');

View File

@ -15,19 +15,19 @@ export 'share.pbenum.dart';
class ExportPayload extends $pb.GeneratedMessage { class ExportPayload extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ExportPayload', createEmptyInstance: create) 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) ..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 ..hasRequiredFields = false
; ;
ExportPayload._() : super(); ExportPayload._() : super();
factory ExportPayload({ factory ExportPayload({
$core.String? docId, $core.String? viewId,
ExportType? exportType, ExportType? exportType,
}) { }) {
final _result = create(); final _result = create();
if (docId != null) { if (viewId != null) {
_result.docId = docId; _result.viewId = viewId;
} }
if (exportType != null) { if (exportType != null) {
_result.exportType = exportType; _result.exportType = exportType;
@ -56,13 +56,13 @@ class ExportPayload extends $pb.GeneratedMessage {
static ExportPayload? _defaultInstance; static ExportPayload? _defaultInstance;
@$pb.TagNumber(1) @$pb.TagNumber(1)
$core.String get docId => $_getSZ(0); $core.String get viewId => $_getSZ(0);
@$pb.TagNumber(1) @$pb.TagNumber(1)
set docId($core.String v) { $_setString(0, v); } set viewId($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1) @$pb.TagNumber(1)
$core.bool hasDocId() => $_has(0); $core.bool hasViewId() => $_has(0);
@$pb.TagNumber(1) @$pb.TagNumber(1)
void clearDocId() => clearField(1); void clearViewId() => clearField(1);
@$pb.TagNumber(2) @$pb.TagNumber(2)
ExportType get exportType => $_getN(1); ExportType get exportType => $_getN(1);

View File

@ -24,13 +24,13 @@ final $typed_data.Uint8List exportTypeDescriptor = $convert.base64Decode('CgpFeH
const ExportPayload$json = const { const ExportPayload$json = const {
'1': 'ExportPayload', '1': 'ExportPayload',
'2': const [ '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'}, const {'1': 'export_type', '3': 2, '4': 1, '5': 14, '6': '.ExportType', '10': 'exportType'},
], ],
}; };
/// Descriptor for `ExportPayload`. Decode as a `google.protobuf.DescriptorProto`. /// 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') @$core.Deprecated('Use exportDataDescriptor instead')
const ExportData$json = const { const ExportData$json = const {
'1': 'ExportData', '1': 'ExportData',

View File

@ -20,11 +20,14 @@ class View extends $pb.GeneratedMessage {
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongToId') ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongToId')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name') ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc') ..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') ..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) ..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(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
..aInt64(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createTime') ..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 ..hasRequiredFields = false
; ;
@ -34,11 +37,14 @@ class View extends $pb.GeneratedMessage {
$core.String? belongToId, $core.String? belongToId,
$core.String? name, $core.String? name,
$core.String? desc, $core.String? desc,
ViewType? viewType, ViewDataType? dataType,
$fixnum.Int64? version, $fixnum.Int64? version,
RepeatedView? belongings, RepeatedView? belongings,
$fixnum.Int64? modifiedTime, $fixnum.Int64? modifiedTime,
$fixnum.Int64? createTime, $fixnum.Int64? createTime,
$core.String? extData,
$core.String? thumbnail,
$core.int? pluginType,
}) { }) {
final _result = create(); final _result = create();
if (id != null) { if (id != null) {
@ -53,8 +59,8 @@ class View extends $pb.GeneratedMessage {
if (desc != null) { if (desc != null) {
_result.desc = desc; _result.desc = desc;
} }
if (viewType != null) { if (dataType != null) {
_result.viewType = viewType; _result.dataType = dataType;
} }
if (version != null) { if (version != null) {
_result.version = version; _result.version = version;
@ -68,6 +74,15 @@ class View extends $pb.GeneratedMessage {
if (createTime != null) { if (createTime != null) {
_result.createTime = createTime; _result.createTime = createTime;
} }
if (extData != null) {
_result.extData = extData;
}
if (thumbnail != null) {
_result.thumbnail = thumbnail;
}
if (pluginType != null) {
_result.pluginType = pluginType;
}
return _result; return _result;
} }
factory View.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 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); void clearDesc() => clearField(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
ViewType get viewType => $_getN(4); ViewDataType get dataType => $_getN(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
set viewType(ViewType v) { setField(5, v); } set dataType(ViewDataType v) { setField(5, v); }
@$pb.TagNumber(5) @$pb.TagNumber(5)
$core.bool hasViewType() => $_has(4); $core.bool hasDataType() => $_has(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
void clearViewType() => clearField(5); void clearDataType() => clearField(5);
@$pb.TagNumber(6) @$pb.TagNumber(6)
$fixnum.Int64 get version => $_getI64(5); $fixnum.Int64 get version => $_getI64(5);
@ -173,6 +188,33 @@ class View extends $pb.GeneratedMessage {
$core.bool hasCreateTime() => $_has(8); $core.bool hasCreateTime() => $_has(8);
@$pb.TagNumber(9) @$pb.TagNumber(9)
void clearCreateTime() => clearField(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 { 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(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc') ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail') ..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 ..hasRequiredFields = false
; ;
@ -242,7 +286,9 @@ class CreateViewPayload extends $pb.GeneratedMessage {
$core.String? name, $core.String? name,
$core.String? desc, $core.String? desc,
$core.String? thumbnail, $core.String? thumbnail,
ViewType? viewType, ViewDataType? dataType,
$core.String? extData,
$core.int? pluginType,
}) { }) {
final _result = create(); final _result = create();
if (belongToId != null) { if (belongToId != null) {
@ -257,8 +303,14 @@ class CreateViewPayload extends $pb.GeneratedMessage {
if (thumbnail != null) { if (thumbnail != null) {
_result.thumbnail = thumbnail; _result.thumbnail = thumbnail;
} }
if (viewType != null) { if (dataType != null) {
_result.viewType = viewType; _result.dataType = dataType;
}
if (extData != null) {
_result.extData = extData;
}
if (pluginType != null) {
_result.pluginType = pluginType;
} }
return _result; return _result;
} }
@ -323,13 +375,31 @@ class CreateViewPayload extends $pb.GeneratedMessage {
void clearThumbnail() => clearField(4); void clearThumbnail() => clearField(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
ViewType get viewType => $_getN(4); ViewDataType get dataType => $_getN(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
set viewType(ViewType v) { setField(5, v); } set dataType(ViewDataType v) { setField(5, v); }
@$pb.TagNumber(5) @$pb.TagNumber(5)
$core.bool hasViewType() => $_has(4); $core.bool hasDataType() => $_has(4);
@$pb.TagNumber(5) @$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 { 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(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc') ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail') ..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') ? '' : 'viewData') ..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
..aOS(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewId') ..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 ..hasRequiredFields = false
; ;
@ -350,9 +422,11 @@ class CreateViewParams extends $pb.GeneratedMessage {
$core.String? name, $core.String? name,
$core.String? desc, $core.String? desc,
$core.String? thumbnail, $core.String? thumbnail,
ViewType? viewType, ViewDataType? dataType,
$core.String? viewData, $core.String? extData,
$core.String? viewId, $core.String? viewId,
$core.String? data,
$core.int? pluginType,
}) { }) {
final _result = create(); final _result = create();
if (belongToId != null) { if (belongToId != null) {
@ -367,15 +441,21 @@ class CreateViewParams extends $pb.GeneratedMessage {
if (thumbnail != null) { if (thumbnail != null) {
_result.thumbnail = thumbnail; _result.thumbnail = thumbnail;
} }
if (viewType != null) { if (dataType != null) {
_result.viewType = viewType; _result.dataType = dataType;
} }
if (viewData != null) { if (extData != null) {
_result.viewData = viewData; _result.extData = extData;
} }
if (viewId != null) { if (viewId != null) {
_result.viewId = viewId; _result.viewId = viewId;
} }
if (data != null) {
_result.data = data;
}
if (pluginType != null) {
_result.pluginType = pluginType;
}
return _result; return _result;
} }
factory CreateViewParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 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); void clearThumbnail() => clearField(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
ViewType get viewType => $_getN(4); ViewDataType get dataType => $_getN(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
set viewType(ViewType v) { setField(5, v); } set dataType(ViewDataType v) { setField(5, v); }
@$pb.TagNumber(5) @$pb.TagNumber(5)
$core.bool hasViewType() => $_has(4); $core.bool hasDataType() => $_has(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
void clearViewType() => clearField(5); void clearDataType() => clearField(5);
@$pb.TagNumber(6) @$pb.TagNumber(6)
$core.String get viewData => $_getSZ(5); $core.String get extData => $_getSZ(5);
@$pb.TagNumber(6) @$pb.TagNumber(6)
set viewData($core.String v) { $_setString(5, v); } set extData($core.String v) { $_setString(5, v); }
@$pb.TagNumber(6) @$pb.TagNumber(6)
$core.bool hasViewData() => $_has(5); $core.bool hasExtData() => $_has(5);
@$pb.TagNumber(6) @$pb.TagNumber(6)
void clearViewData() => clearField(6); void clearExtData() => clearField(6);
@$pb.TagNumber(7) @$pb.TagNumber(7)
$core.String get viewId => $_getSZ(6); $core.String get viewId => $_getSZ(6);
@ -461,6 +541,24 @@ class CreateViewParams extends $pb.GeneratedMessage {
$core.bool hasViewId() => $_has(6); $core.bool hasViewId() => $_has(6);
@$pb.TagNumber(7) @$pb.TagNumber(7)
void clearViewId() => clearField(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 { class ViewId extends $pb.GeneratedMessage {

View File

@ -9,18 +9,18 @@
import 'dart:core' as $core; import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb; import 'package:protobuf/protobuf.dart' as $pb;
class ViewType extends $pb.ProtobufEnum { class ViewDataType extends $pb.ProtobufEnum {
static const ViewType Blank = ViewType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Blank'); static const ViewDataType RichText = ViewDataType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RichText');
static const ViewType Doc = ViewType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Doc'); static const ViewDataType PlainText = ViewDataType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PlainText');
static const $core.List<ViewType> values = <ViewType> [ static const $core.List<ViewDataType> values = <ViewDataType> [
Blank, RichText,
Doc, PlainText,
]; ];
static final $core.Map<$core.int, ViewType> _byValue = $pb.ProtobufEnum.initByValue(values); static final $core.Map<$core.int, ViewDataType> _byValue = $pb.ProtobufEnum.initByValue(values);
static ViewType? valueOf($core.int value) => _byValue[value]; 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);
} }

View File

@ -8,17 +8,17 @@
import 'dart:core' as $core; import 'dart:core' as $core;
import 'dart:convert' as $convert; import 'dart:convert' as $convert;
import 'dart:typed_data' as $typed_data; import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use viewTypeDescriptor instead') @$core.Deprecated('Use viewDataTypeDescriptor instead')
const ViewType$json = const { const ViewDataType$json = const {
'1': 'ViewType', '1': 'ViewDataType',
'2': const [ '2': const [
const {'1': 'Blank', '2': 0}, const {'1': 'RichText', '2': 0},
const {'1': 'Doc', '2': 1}, const {'1': 'PlainText', '2': 1},
], ],
}; };
/// Descriptor for `ViewType`. Decode as a `google.protobuf.EnumDescriptorProto`. /// Descriptor for `ViewDataType`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List viewTypeDescriptor = $convert.base64Decode('CghWaWV3VHlwZRIJCgVCbGFuaxAAEgcKA0RvYxAB'); final $typed_data.Uint8List viewDataTypeDescriptor = $convert.base64Decode('CgxWaWV3RGF0YVR5cGUSDAoIUmljaFRleHQQABINCglQbGFpblRleHQQAQ==');
@$core.Deprecated('Use viewDescriptor instead') @$core.Deprecated('Use viewDescriptor instead')
const View$json = const { const View$json = const {
'1': 'View', '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': 'belong_to_id', '3': 2, '4': 1, '5': 9, '10': 'belongToId'},
const {'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'}, const {'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'},
const {'1': 'desc', '3': 4, '4': 1, '5': 9, '10': 'desc'}, 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': 'version', '3': 6, '4': 1, '5': 3, '10': 'version'},
const {'1': 'belongings', '3': 7, '4': 1, '5': 11, '6': '.RepeatedView', '10': 'belongings'}, 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': 'modified_time', '3': 8, '4': 1, '5': 3, '10': 'modifiedTime'},
const {'1': 'create_time', '3': 9, '4': 1, '5': 3, '10': 'createTime'}, 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`. /// 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') @$core.Deprecated('Use repeatedViewDescriptor instead')
const RepeatedView$json = const { const RepeatedView$json = const {
'1': 'RepeatedView', '1': 'RepeatedView',
@ -55,7 +58,9 @@ const CreateViewPayload$json = const {
const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'}, const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'}, 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': '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 [ '8': const [
const {'1': 'one_of_thumbnail'}, const {'1': 'one_of_thumbnail'},
@ -63,7 +68,7 @@ const CreateViewPayload$json = const {
}; };
/// Descriptor for `CreateViewPayload`. Decode as a `google.protobuf.DescriptorProto`. /// 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') @$core.Deprecated('Use createViewParamsDescriptor instead')
const CreateViewParams$json = const { const CreateViewParams$json = const {
'1': 'CreateViewParams', '1': 'CreateViewParams',
@ -72,14 +77,16 @@ const CreateViewParams$json = const {
const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'}, const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'}, const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
const {'1': 'thumbnail', '3': 4, '4': 1, '5': 9, '10': 'thumbnail'}, 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': 'data_type', '3': 5, '4': 1, '5': 14, '6': '.ViewDataType', '10': 'dataType'},
const {'1': 'view_data', '3': 6, '4': 1, '5': 9, '10': 'viewData'}, 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': '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`. /// 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') @$core.Deprecated('Use viewIdDescriptor instead')
const ViewId$json = const { const ViewId$json = const {
'1': 'ViewId', '1': 'ViewId',

View File

@ -645,7 +645,7 @@ packages:
name: js name: js
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.3" version: "0.6.4"
json_annotation: json_annotation:
dependency: transitive dependency: transitive
description: description:
@ -799,7 +799,7 @@ packages:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.1"
path_drawing: path_drawing:
dependency: transitive dependency: transitive
description: description:
@ -1133,21 +1133,21 @@ packages:
name: test name: test
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.19.5" version: "1.20.1"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.8" version: "0.4.9"
test_core: test_core:
dependency: transitive dependency: transitive
description: description:
name: test_core name: test_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.9" version: "0.4.11"
textstyle_extensions: textstyle_extensions:
dependency: transitive dependency: transitive
description: description:
@ -1354,5 +1354,5 @@ packages:
source: hosted source: hosted
version: "8.0.0" version: "8.0.0"
sdks: 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" flutter: ">=2.5.0"

View File

@ -1031,6 +1031,7 @@ dependencies = [
"protobuf", "protobuf",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr",
"strum", "strum",
"strum_macros", "strum_macros",
"unicode-segmentation", "unicode-segmentation",
@ -1108,6 +1109,7 @@ name = "flowy-sync"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait",
"bytes", "bytes",
"dashmap", "dashmap",
"diesel", "diesel",

View File

@ -1,13 +1,14 @@
use crate::queue::DocumentRevisionCompact; use crate::queue::BlockRevisionCompact;
use crate::web_socket::{make_document_ws_manager, EditorCommandSender}; use crate::web_socket::{make_block_ws_manager, EditorCommandSender};
use crate::{ use crate::{
errors::FlowyError, errors::FlowyError,
queue::{EditorCommand, EditorCommandQueue}, queue::{EditBlockQueue, EditorCommand},
DocumentUser, DocumentWSReceiver, BlockUser,
}; };
use bytes::Bytes; use bytes::Bytes;
use flowy_collaboration::entities::ws_data::ServerRevisionWSData;
use flowy_collaboration::{ use flowy_collaboration::{
entities::{document_info::DocumentInfo, revision::Revision}, entities::{document_info::BlockInfo, revision::Revision},
errors::CollaborateResult, errors::CollaborateResult,
util::make_delta_from_revisions, util::make_delta_from_revisions,
}; };
@ -19,10 +20,11 @@ use lib_ot::{
core::{Interval, Operation}, core::{Interval, Operation},
rich_text::{RichTextAttribute, RichTextDelta}, rich_text::{RichTextAttribute, RichTextDelta},
}; };
use lib_ws::WSConnectState;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
pub struct ClientDocumentEditor { pub struct ClientBlockEditor {
pub doc_id: String, pub doc_id: String,
#[allow(dead_code)] #[allow(dead_code)]
rev_manager: Arc<RevisionManager>, rev_manager: Arc<RevisionManager>,
@ -30,16 +32,16 @@ pub struct ClientDocumentEditor {
edit_cmd_tx: EditorCommandSender, edit_cmd_tx: EditorCommandSender,
} }
impl ClientDocumentEditor { impl ClientBlockEditor {
pub(crate) async fn new( pub(crate) async fn new(
doc_id: &str, doc_id: &str,
user: Arc<dyn DocumentUser>, user: Arc<dyn BlockUser>,
mut rev_manager: RevisionManager, mut rev_manager: RevisionManager,
rev_web_socket: Arc<dyn RevisionWebSocket>, rev_web_socket: Arc<dyn RevisionWebSocket>,
cloud_service: Arc<dyn RevisionCloudService>, cloud_service: Arc<dyn RevisionCloudService>,
) -> FlowyResult<Arc<Self>> { ) -> FlowyResult<Arc<Self>> {
let document_info = rev_manager let document_info = rev_manager
.load::<DocumentInfoBuilder, DocumentRevisionCompact>(cloud_service) .load::<BlockInfoBuilder, BlockRevisionCompact>(cloud_service)
.await?; .await?;
let delta = document_info.delta()?; let delta = document_info.delta()?;
let rev_manager = Arc::new(rev_manager); let rev_manager = Arc::new(rev_manager);
@ -47,7 +49,7 @@ impl ClientDocumentEditor {
let user_id = user.user_id()?; let user_id = user.user_id()?;
let edit_cmd_tx = spawn_edit_queue(user, rev_manager.clone(), delta); 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(), doc_id.clone(),
user_id.clone(), user_id.clone(),
edit_cmd_tx.clone(), edit_cmd_tx.clone(),
@ -138,9 +140,9 @@ impl ClientDocumentEditor {
Ok(()) 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 (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 _ = self.edit_cmd_tx.send(msg).await;
let json = rx.await.map_err(internal_error)??; let json = rx.await.map_err(internal_error)??;
Ok(json) Ok(json)
@ -163,34 +165,38 @@ impl ClientDocumentEditor {
self.ws_manager.stop(); self.ws_manager.stop();
} }
pub(crate) fn ws_handler(&self) -> Arc<dyn DocumentWSReceiver> { pub(crate) async fn receive_ws_data(&self, data: ServerRevisionWSData) -> Result<(), FlowyError> {
self.ws_manager.clone() 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) { 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. // The edit queue will exit after the EditorCommandSender was dropped.
fn spawn_edit_queue( fn spawn_edit_queue(
user: Arc<dyn DocumentUser>, user: Arc<dyn BlockUser>,
rev_manager: Arc<RevisionManager>, rev_manager: Arc<RevisionManager>,
delta: RichTextDelta, delta: RichTextDelta,
) -> EditorCommandSender { ) -> EditorCommandSender {
let (sender, receiver) = mpsc::channel(1000); let (sender, receiver) = mpsc::channel(1000);
let actor = EditorCommandQueue::new(user, rev_manager, delta, receiver); let edit_queue = EditBlockQueue::new(user, rev_manager, delta, receiver);
tokio::spawn(actor.run()); tokio::spawn(edit_queue.run());
sender sender
} }
#[cfg(feature = "flowy_unit_test")] #[cfg(feature = "flowy_unit_test")]
impl ClientDocumentEditor { impl ClientBlockEditor {
pub async fn doc_json(&self) -> FlowyResult<String> { pub async fn doc_json(&self) -> FlowyResult<String> {
let (ret, rx) = oneshot::channel::<CollaborateResult<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 _ = self.edit_cmd_tx.send(msg).await;
let s = rx.await.map_err(internal_error)??; let s = rx.await.map_err(internal_error)??;
Ok(s) Ok(s)
@ -198,7 +204,7 @@ impl ClientDocumentEditor {
pub async fn doc_delta(&self) -> FlowyResult<RichTextDelta> { pub async fn doc_delta(&self) -> FlowyResult<RichTextDelta> {
let (ret, rx) = oneshot::channel::<CollaborateResult<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 _ = self.edit_cmd_tx.send(msg).await;
let delta = rx.await.map_err(internal_error)??; let delta = rx.await.map_err(internal_error)??;
Ok(delta) Ok(delta)
@ -209,18 +215,18 @@ impl ClientDocumentEditor {
} }
} }
struct DocumentInfoBuilder(); struct BlockInfoBuilder();
impl RevisionObjectBuilder for DocumentInfoBuilder { impl RevisionObjectBuilder for BlockInfoBuilder {
type Output = DocumentInfo; type Output = BlockInfo;
fn build_object(object_id: &str, revisions: Vec<Revision>) -> FlowyResult<Self::Output> { 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 (base_rev_id, rev_id) = revisions.last().unwrap().pair_rev_id();
let mut delta = make_delta_from_revisions(revisions)?; let mut delta = make_delta_from_revisions(revisions)?;
correct_delta(&mut delta); correct_delta(&mut delta);
Result::<DocumentInfo, FlowyError>::Ok(DocumentInfo { Result::<BlockInfo, FlowyError>::Ok(BlockInfo {
doc_id: object_id.to_owned(), doc_id: object_id.to_owned(),
text: delta.to_json(), text: delta.to_delta_json(),
rev_id, rev_id,
base_rev_id, base_rev_id,
}) })

View File

@ -1,4 +1,4 @@
pub mod editor; pub mod block_editor;
pub mod manager; pub mod manager;
mod queue; mod queue;
mod web_socket; mod web_socket;
@ -11,13 +11,13 @@ pub mod errors {
pub const DOCUMENT_SYNC_INTERVAL_IN_MILLIS: u64 = 1000; pub const DOCUMENT_SYNC_INTERVAL_IN_MILLIS: u64 = 1000;
use crate::errors::FlowyError; 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; use lib_infra::future::FutureResult;
pub trait DocumentCloudService: Send + Sync { pub trait BlockCloudService: Send + Sync {
fn create_document(&self, token: &str, params: CreateDocParams) -> FutureResult<(), FlowyError>; 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>;
} }

View File

@ -1,76 +1,64 @@
use crate::{editor::ClientDocumentEditor, errors::FlowyError, DocumentCloudService}; use crate::{block_editor::ClientBlockEditor, errors::FlowyError, BlockCloudService};
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use dashmap::DashMap; use dashmap::DashMap;
use flowy_collaboration::entities::{ use flowy_collaboration::entities::{
document_info::{DocumentDelta, DocumentId}, document_info::{BlockDelta, BlockId},
revision::{md5, RepeatedRevision, Revision}, revision::{md5, RepeatedRevision, Revision},
ws_data::ServerRevisionWSData, ws_data::ServerRevisionWSData,
}; };
use flowy_database::ConnectionPool; use flowy_database::ConnectionPool;
use flowy_error::FlowyResult; 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_infra::future::FutureResult;
use lib_ws::WSConnectState;
use std::{convert::TryInto, sync::Arc}; 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_dir(&self) -> Result<String, FlowyError>;
fn user_id(&self) -> Result<String, FlowyError>; fn user_id(&self) -> Result<String, FlowyError>;
fn token(&self) -> Result<String, FlowyError>; fn token(&self) -> Result<String, FlowyError>;
fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>; fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>;
} }
#[async_trait] pub struct BlockManager {
pub(crate) trait DocumentWSReceiver: Send + Sync { cloud_service: Arc<dyn BlockCloudService>,
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,
rev_web_socket: Arc<dyn RevisionWebSocket>, rev_web_socket: Arc<dyn RevisionWebSocket>,
document_handlers: Arc<DocumentEditorHandlers>, block_editors: Arc<BlockEditors>,
document_user: Arc<dyn DocumentUser>, block_user: Arc<dyn BlockUser>,
} }
impl FlowyDocumentManager { impl BlockManager {
pub fn new( pub fn new(
cloud_service: Arc<dyn DocumentCloudService>, cloud_service: Arc<dyn BlockCloudService>,
document_user: Arc<dyn DocumentUser>, block_user: Arc<dyn BlockUser>,
rev_web_socket: Arc<dyn RevisionWebSocket>, rev_web_socket: Arc<dyn RevisionWebSocket>,
) -> Self { ) -> Self {
let ws_data_receivers = Arc::new(DashMap::new()); let block_handlers = Arc::new(BlockEditors::new());
let document_handlers = Arc::new(DocumentEditorHandlers::new());
Self { Self {
cloud_service, cloud_service,
ws_data_receivers,
rev_web_socket, rev_web_socket,
document_handlers, block_editors: block_handlers,
document_user, block_user,
} }
} }
pub fn init(&self) -> FlowyResult<()> { 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(()) Ok(())
} }
#[tracing::instrument(level = "debug", skip(self, doc_id), fields(doc_id), err)] #[tracing::instrument(level = "debug", skip(self, block_id), fields(block_id), err)]
pub async fn open_document<T: AsRef<str>>(&self, doc_id: T) -> Result<Arc<ClientDocumentEditor>, FlowyError> { pub async fn open_block<T: AsRef<str>>(&self, block_id: T) -> Result<Arc<ClientBlockEditor>, FlowyError> {
let doc_id = doc_id.as_ref(); let block_id = block_id.as_ref();
tracing::Span::current().record("doc_id", &doc_id); tracing::Span::current().record("block_id", &block_id);
self.get_editor(doc_id).await self.get_block_editor(block_id).await
} }
#[tracing::instrument(level = "trace", skip(self, doc_id), fields(doc_id), err)] #[tracing::instrument(level = "trace", skip(self, block_id), fields(block_id), err)]
pub fn close_document<T: AsRef<str>>(&self, doc_id: T) -> Result<(), FlowyError> { pub fn close_block<T: AsRef<str>>(&self, block_id: T) -> Result<(), FlowyError> {
let doc_id = doc_id.as_ref(); let block_id = block_id.as_ref();
tracing::Span::current().record("doc_id", &doc_id); tracing::Span::current().record("block_id", &block_id);
self.document_handlers.remove(doc_id); self.block_editors.remove(block_id);
self.ws_data_receivers.remove(doc_id);
Ok(()) Ok(())
} }
@ -78,25 +66,25 @@ impl FlowyDocumentManager {
pub fn delete<T: AsRef<str>>(&self, doc_id: T) -> Result<(), FlowyError> { pub fn delete<T: AsRef<str>>(&self, doc_id: T) -> Result<(), FlowyError> {
let doc_id = doc_id.as_ref(); let doc_id = doc_id.as_ref();
tracing::Span::current().record("doc_id", &doc_id); tracing::Span::current().record("doc_id", &doc_id);
self.document_handlers.remove(doc_id); self.block_editors.remove(doc_id);
self.ws_data_receivers.remove(doc_id);
Ok(()) Ok(())
} }
#[tracing::instrument(level = "debug", skip(self, delta), fields(doc_id = %delta.doc_id), err)] #[tracing::instrument(level = "debug", skip(self, delta), fields(doc_id = %delta.block_id), err)]
pub async fn receive_local_delta(&self, delta: DocumentDelta) -> Result<DocumentDelta, FlowyError> { pub async fn receive_local_delta(&self, delta: BlockDelta) -> Result<BlockDelta, FlowyError> {
let editor = self.get_editor(&delta.doc_id).await?; let editor = self.get_block_editor(&delta.block_id).await?;
let _ = editor.compose_local_delta(Bytes::from(delta.delta_json)).await?; let _ = editor.compose_local_delta(Bytes::from(delta.delta_json)).await?;
let document_json = editor.document_json().await?; let document_json = editor.block_json().await?;
Ok(DocumentDelta { Ok(BlockDelta {
doc_id: delta.doc_id.clone(), block_id: delta.block_id.clone(),
delta_json: document_json, 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 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 = self.make_rev_manager(&doc_id, db_pool)?;
let _ = rev_manager.reset_object(revisions).await?; let _ = rev_manager.reset_object(revisions).await?;
Ok(()) Ok(())
@ -105,9 +93,9 @@ impl FlowyDocumentManager {
pub async fn receive_ws_data(&self, data: Bytes) { pub async fn receive_ws_data(&self, data: Bytes) {
let result: Result<ServerRevisionWSData, protobuf::ProtobufError> = data.try_into(); let result: Result<ServerRevisionWSData, protobuf::ProtobufError> = data.try_into();
match result { 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), 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(_) => {} Ok(_) => {}
Err(e) => tracing::error!("{}", e), Err(e) => tracing::error!("{}", e),
}, },
@ -119,59 +107,57 @@ impl FlowyDocumentManager {
} }
} }
impl FlowyDocumentManager { impl BlockManager {
async fn get_editor(&self, doc_id: &str) -> FlowyResult<Arc<ClientDocumentEditor>> { async fn get_block_editor(&self, block_id: &str) -> FlowyResult<Arc<ClientBlockEditor>> {
match self.document_handlers.get(doc_id) { match self.block_editors.get(block_id) {
None => { None => {
let db_pool = self.document_user.db_pool()?; let db_pool = self.block_user.db_pool()?;
self.make_editor(doc_id, db_pool).await self.make_block_editor(block_id, db_pool).await
} }
Some(editor) => Ok(editor), Some(editor) => Ok(editor),
} }
} }
async fn make_editor( async fn make_block_editor(
&self, &self,
doc_id: &str, block_id: &str,
pool: Arc<ConnectionPool>, pool: Arc<ConnectionPool>,
) -> Result<Arc<ClientDocumentEditor>, FlowyError> { ) -> Result<Arc<ClientBlockEditor>, FlowyError> {
let user = self.document_user.clone(); let user = self.block_user.clone();
let token = self.document_user.token()?; let token = self.block_user.token()?;
let rev_manager = self.make_rev_manager(doc_id, pool.clone())?; let rev_manager = self.make_rev_manager(block_id, pool.clone())?;
let cloud_service = Arc::new(DocumentRevisionCloudServiceImpl { let cloud_service = Arc::new(BlockRevisionCloudService {
token, token,
server: self.cloud_service.clone(), server: self.cloud_service.clone(),
}); });
let doc_editor = let doc_editor =
ClientDocumentEditor::new(doc_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?; ClientBlockEditor::new(block_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?;
self.ws_data_receivers self.block_editors.insert(block_id, &doc_editor);
.insert(doc_id.to_string(), doc_editor.ws_handler());
self.document_handlers.insert(doc_id, &doc_editor);
Ok(doc_editor) Ok(doc_editor)
} }
fn make_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> { fn make_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> {
let user_id = self.document_user.user_id()?; let user_id = self.block_user.user_id()?;
let cache = Arc::new(RevisionCache::new(&user_id, doc_id, pool)); let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, doc_id, pool));
Ok(RevisionManager::new(&user_id, doc_id, cache)) Ok(RevisionManager::new(&user_id, doc_id, rev_persistence))
} }
} }
struct DocumentRevisionCloudServiceImpl { struct BlockRevisionCloudService {
token: String, token: String,
server: Arc<dyn DocumentCloudService>, server: Arc<dyn BlockCloudService>,
} }
impl RevisionCloudService for DocumentRevisionCloudServiceImpl { impl RevisionCloudService for BlockRevisionCloudService {
#[tracing::instrument(level = "trace", skip(self))] #[tracing::instrument(level = "trace", skip(self))]
fn fetch_object(&self, user_id: &str, object_id: &str) -> FutureResult<Vec<Revision>, FlowyError> { 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 server = self.server.clone();
let token = self.token.clone(); let token = self.token.clone();
let user_id = user_id.to_string(); let user_id = user_id.to_string();
FutureResult::new(async move { 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")), None => Err(FlowyError::record_not_found().context("Remote doesn't have this document")),
Some(doc) => { Some(doc) => {
let delta_data = Bytes::from(doc.text.clone()); let delta_data = Bytes::from(doc.text.clone());
@ -185,51 +171,50 @@ impl RevisionCloudService for DocumentRevisionCloudServiceImpl {
} }
} }
pub struct DocumentEditorHandlers { pub struct BlockEditors {
inner: DashMap<String, Arc<ClientDocumentEditor>>, inner: DashMap<String, Arc<ClientBlockEditor>>,
} }
impl DocumentEditorHandlers { impl BlockEditors {
fn new() -> Self { fn new() -> Self {
Self { inner: DashMap::new() } Self { inner: DashMap::new() }
} }
pub(crate) fn insert(&self, doc_id: &str, doc: &Arc<ClientDocumentEditor>) { pub(crate) fn insert(&self, block_id: &str, doc: &Arc<ClientBlockEditor>) {
if self.inner.contains_key(doc_id) { if self.inner.contains_key(block_id) {
log::warn!("Doc:{} already exists in cache", doc_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 { pub(crate) fn contains(&self, block_id: &str) -> bool {
self.inner.get(doc_id).is_some() self.inner.get(block_id).is_some()
} }
pub(crate) fn get(&self, doc_id: &str) -> Option<Arc<ClientDocumentEditor>> { pub(crate) fn get(&self, block_id: &str) -> Option<Arc<ClientBlockEditor>> {
if !self.contains(doc_id) { if !self.contains(block_id) {
return None; return None;
} }
let opened_doc = self.inner.get(doc_id).unwrap(); let opened_doc = self.inner.get(block_id).unwrap();
Some(opened_doc.clone()) Some(opened_doc.clone())
} }
pub(crate) fn remove(&self, id: &str) { pub(crate) fn remove(&self, block_id: &str) {
let doc_id = id.to_string(); if let Some(editor) = self.get(block_id) {
if let Some(editor) = self.get(id) {
editor.stop() editor.stop()
} }
self.inner.remove(&doc_id); self.inner.remove(block_id);
} }
} }
#[tracing::instrument(level = "trace", skip(web_socket, receivers))] #[tracing::instrument(level = "trace", skip(web_socket, handlers))]
fn listen_ws_state_changed(web_socket: Arc<dyn RevisionWebSocket>, receivers: WebSocketDataReceivers) { fn listen_ws_state_changed(web_socket: Arc<dyn RevisionWebSocket>, handlers: Arc<BlockEditors>) {
tokio::spawn(async move { tokio::spawn(async move {
let mut notify = web_socket.subscribe_state_changed().await; let mut notify = web_socket.subscribe_state_changed().await;
while let Ok(state) = notify.recv().await { while let Ok(state) = notify.recv().await {
for receiver in receivers.iter() { handlers.inner.iter().for_each(|handler| {
receiver.value().connect_state_changed(state.clone()); handler.receive_ws_state(&state);
} })
} }
}); });
} }

View File

@ -1,5 +1,5 @@
use crate::web_socket::EditorCommandReceiver; use crate::web_socket::EditorCommandReceiver;
use crate::DocumentUser; use crate::BlockUser;
use async_stream::stream; use async_stream::stream;
use flowy_collaboration::util::make_delta_from_revisions; use flowy_collaboration::util::make_delta_from_revisions;
use flowy_collaboration::{ use flowy_collaboration::{
@ -8,7 +8,7 @@ use flowy_collaboration::{
errors::CollaborateError, errors::CollaborateError,
}; };
use flowy_error::{FlowyError, FlowyResult}; 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 futures::stream::StreamExt;
use lib_ot::{ use lib_ot::{
core::{Interval, OperationTransformable}, core::{Interval, OperationTransformable},
@ -19,16 +19,16 @@ use tokio::sync::{oneshot, RwLock};
// The EditorCommandQueue executes each command that will alter the document in // The EditorCommandQueue executes each command that will alter the document in
// serial. // serial.
pub(crate) struct EditorCommandQueue { pub(crate) struct EditBlockQueue {
document: Arc<RwLock<ClientDocument>>, document: Arc<RwLock<ClientDocument>>,
user: Arc<dyn DocumentUser>, user: Arc<dyn BlockUser>,
rev_manager: Arc<RevisionManager>, rev_manager: Arc<RevisionManager>,
receiver: Option<EditorCommandReceiver>, receiver: Option<EditorCommandReceiver>,
} }
impl EditorCommandQueue { impl EditBlockQueue {
pub(crate) fn new( pub(crate) fn new(
user: Arc<dyn DocumentUser>, user: Arc<dyn BlockUser>,
rev_manager: Arc<RevisionManager>, rev_manager: Arc<RevisionManager>,
delta: RichTextDelta, delta: RichTextDelta,
receiver: EditorCommandReceiver, receiver: EditorCommandReceiver,
@ -102,7 +102,7 @@ impl EditorCommandQueue {
server_prime = Some(s_prime); server_prime = Some(s_prime);
} }
drop(read_guard); drop(read_guard);
Ok::<TransformDeltas<RichTextAttributes>, CollaborateError>(TransformDeltas { Ok::<RichTextTransformDeltas, CollaborateError>(TransformDeltas {
client_prime, client_prime,
server_prime, server_prime,
}) })
@ -161,11 +161,11 @@ impl EditorCommandQueue {
let _ = self.save_local_delta(delta, md5).await?; let _ = self.save_local_delta(delta, md5).await?;
let _ = ret.send(Ok(())); let _ = ret.send(Ok(()));
} }
EditorCommand::ReadDocumentAsJson { ret } => { EditorCommand::ReadBlockJson { ret } => {
let data = self.document.read().await.to_json(); let data = self.document.read().await.to_json();
let _ = ret.send(Ok(data)); let _ = ret.send(Ok(data));
} }
EditorCommand::ReadDocumentAsDelta { ret } => { EditorCommand::ReadBlockDelta { ret } => {
let delta = self.document.read().await.delta().clone(); let delta = self.document.read().await.delta().clone();
let _ = ret.send(Ok(delta)); let _ = ret.send(Ok(delta));
} }
@ -187,17 +187,17 @@ impl EditorCommandQueue {
); );
let _ = self let _ = self
.rev_manager .rev_manager
.add_local_revision::<DocumentRevisionCompact>(&revision) .add_local_revision::<BlockRevisionCompact>(&revision)
.await?; .await?;
Ok(rev_id.into()) Ok(rev_id.into())
} }
} }
pub(crate) struct DocumentRevisionCompact(); pub(crate) struct BlockRevisionCompact();
impl RevisionCompact for DocumentRevisionCompact { impl RevisionCompact for BlockRevisionCompact {
fn compact_revisions(user_id: &str, object_id: &str, mut revisions: Vec<Revision>) -> FlowyResult<Revision> { fn compact_revisions(user_id: &str, object_id: &str, mut revisions: Vec<Revision>) -> FlowyResult<Revision> {
if revisions.is_empty() { 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 { if revisions.len() == 1 {
@ -232,7 +232,7 @@ pub(crate) enum EditorCommand {
}, },
TransformDelta { TransformDelta {
delta: RichTextDelta, delta: RichTextDelta,
ret: Ret<TransformDeltas<RichTextAttributes>>, ret: Ret<RichTextTransformDeltas>,
}, },
Insert { Insert {
index: usize, index: usize,
@ -265,11 +265,11 @@ pub(crate) enum EditorCommand {
Redo { Redo {
ret: Ret<()>, ret: Ret<()>,
}, },
ReadDocumentAsJson { ReadBlockJson {
ret: Ret<String>, ret: Ret<String>,
}, },
#[allow(dead_code)] #[allow(dead_code)]
ReadDocumentAsDelta { ReadBlockDelta {
ret: Ret<RichTextDelta>, ret: Ret<RichTextDelta>,
}, },
} }
@ -289,8 +289,8 @@ impl std::fmt::Debug for EditorCommand {
EditorCommand::CanRedo { .. } => "CanRedo", EditorCommand::CanRedo { .. } => "CanRedo",
EditorCommand::Undo { .. } => "Undo", EditorCommand::Undo { .. } => "Undo",
EditorCommand::Redo { .. } => "Redo", EditorCommand::Redo { .. } => "Redo",
EditorCommand::ReadDocumentAsJson { .. } => "ReadDocumentAsJson", EditorCommand::ReadBlockJson { .. } => "ReadDocumentAsJson",
EditorCommand::ReadDocumentAsDelta { .. } => "ReadDocumentAsDelta", EditorCommand::ReadBlockDelta { .. } => "ReadDocumentAsDelta",
}; };
f.write_str(s) f.write_str(s)
} }

View File

@ -1,17 +1,17 @@
use crate::{queue::EditorCommand, DocumentWSReceiver, DOCUMENT_SYNC_INTERVAL_IN_MILLIS}; use crate::{queue::EditorCommand, DOCUMENT_SYNC_INTERVAL_IN_MILLIS};
use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use flowy_collaboration::{ use flowy_collaboration::{
entities::{ entities::{
revision::RevisionRange, revision::RevisionRange,
ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSData, ServerRevisionWSDataType}, ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSDataType},
}, },
errors::CollaborateResult, errors::CollaborateResult,
}; };
use flowy_error::{internal_error, FlowyError}; use flowy_error::{internal_error, FlowyError};
use flowy_sync::*; use flowy_sync::*;
use lib_infra::future::{BoxResultFuture, FutureResult}; 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 lib_ws::WSConnectState;
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use tokio::sync::{ use tokio::sync::{
@ -23,33 +23,26 @@ use tokio::sync::{
pub(crate) type EditorCommandSender = Sender<EditorCommand>; pub(crate) type EditorCommandSender = Sender<EditorCommand>;
pub(crate) type EditorCommandReceiver = Receiver<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, doc_id: String,
user_id: String, user_id: String,
edit_cmd_tx: EditorCommandSender, edit_cmd_tx: EditorCommandSender,
rev_manager: Arc<RevisionManager>, rev_manager: Arc<RevisionManager>,
rev_web_socket: Arc<dyn RevisionWebSocket>, rev_web_socket: Arc<dyn RevisionWebSocket>,
) -> Arc<RevisionWebSocketManager> { ) -> Arc<RevisionWebSocketManager> {
let composite_sink_provider = Arc::new(CompositeWSSinkDataProvider::new(&doc_id, rev_manager.clone())); let ws_data_provider = Arc::new(WSDataProvider::new(&doc_id, Arc::new(rev_manager.clone())));
let resolve_target = Arc::new(DocumentRevisionResolveTarget { edit_cmd_tx }); let resolver = Arc::new(BlockConflictResolver { edit_cmd_tx });
let resolver = RevisionConflictResolver::<RichTextAttributes>::new( let conflict_controller =
&user_id, RichTextConflictController::new(&user_id, resolver, Arc::new(ws_data_provider.clone()), rev_manager);
resolve_target, let ws_data_stream = Arc::new(BlockRevisionWSDataStream::new(conflict_controller));
Arc::new(composite_sink_provider.clone()), let ws_data_sink = Arc::new(BlockWSDataSink(ws_data_provider));
rev_manager,
);
let ws_stream_consumer = Arc::new(DocumentWSSteamConsumerAdapter {
resolver: Arc::new(resolver),
});
let sink_provider = Arc::new(DocumentWSSinkDataProviderAdapter(composite_sink_provider));
let ping_duration = Duration::from_millis(DOCUMENT_SYNC_INTERVAL_IN_MILLIS); let ping_duration = Duration::from_millis(DOCUMENT_SYNC_INTERVAL_IN_MILLIS);
let ws_manager = Arc::new(RevisionWebSocketManager::new( let ws_manager = Arc::new(RevisionWebSocketManager::new(
"Document", "Block",
&doc_id, &doc_id,
rev_web_socket, rev_web_socket,
sink_provider, ws_data_sink,
ws_stream_consumer, ws_data_stream,
ping_duration, ping_duration,
)); ));
listen_document_ws_state(&user_id, &doc_id, ws_manager.scribe_state()); 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 { pub(crate) struct BlockRevisionWSDataStream {
resolver: Arc<RevisionConflictResolver<RichTextAttributes>>, 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> { 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 }) Box::pin(async move { resolver.receive_bytes(bytes).await })
} }
fn receive_ack(&self, id: String, ty: ServerRevisionWSDataType) -> BoxResultFuture<(), FlowyError> { 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 }) 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> { 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 }) Box::pin(async move { resolver.send_revisions(range).await })
} }
} }
pub(crate) struct DocumentWSSinkDataProviderAdapter(pub(crate) Arc<CompositeWSSinkDataProvider>); pub(crate) struct BlockWSDataSink(pub(crate) Arc<WSDataProvider>);
impl RevisionWSSinkDataProvider for DocumentWSSinkDataProviderAdapter { impl RevisionWSDataIterator for BlockWSDataSink {
fn next(&self) -> FutureResult<Option<ClientRevisionWSData>, FlowyError> { fn next(&self) -> FutureResult<Option<ClientRevisionWSData>, FlowyError> {
let sink_provider = self.0.clone(); let sink_provider = self.0.clone();
FutureResult::new(async move { sink_provider.next().await }) FutureResult::new(async move { sink_provider.next().await })
} }
} }
struct DocumentRevisionResolveTarget { struct BlockConflictResolver {
edit_cmd_tx: EditorCommandSender, edit_cmd_tx: EditorCommandSender,
} }
impl ResolverTarget<RichTextAttributes> for DocumentRevisionResolveTarget { impl ConflictResolver<RichTextAttributes> for BlockConflictResolver {
fn compose_delta(&self, delta: Delta<RichTextAttributes>) -> BoxResultFuture<DeltaMD5, FlowyError> { fn compose_delta(&self, delta: RichTextDelta) -> BoxResultFuture<DeltaMD5, FlowyError> {
let tx = self.edit_cmd_tx.clone(); let tx = self.edit_cmd_tx.clone();
Box::pin(async move { Box::pin(async move {
let (ret, rx) = oneshot::channel(); let (ret, rx) = oneshot::channel();
@ -127,11 +128,11 @@ impl ResolverTarget<RichTextAttributes> for DocumentRevisionResolveTarget {
fn transform_delta( fn transform_delta(
&self, &self,
delta: Delta<RichTextAttributes>, delta: RichTextDelta,
) -> BoxResultFuture<flowy_sync::TransformDeltas<RichTextAttributes>, FlowyError> { ) -> BoxResultFuture<flowy_sync::RichTextTransformDeltas, FlowyError> {
let tx = self.edit_cmd_tx.clone(); let tx = self.edit_cmd_tx.clone();
Box::pin(async move { 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 }) tx.send(EditorCommand::TransformDelta { delta, ret })
.await .await
.map_err(internal_error)?; .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(); let tx = self.edit_cmd_tx.clone();
Box::pin(async move { Box::pin(async move {
let (ret, rx) = oneshot::channel(); 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),
}
}
}

View File

@ -1,5 +1,5 @@
use flowy_collaboration::entities::revision::RevisionState; 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_document::DOCUMENT_SYNC_INTERVAL_IN_MILLIS;
use flowy_test::{helper::ViewTest, FlowySDKTest}; use flowy_test::{helper::ViewTest, FlowySDKTest};
use lib_ot::{core::Interval, rich_text::RichTextDelta}; use lib_ot::{core::Interval, rich_text::RichTextDelta};
@ -19,7 +19,7 @@ pub enum EditorScript {
pub struct EditorTest { pub struct EditorTest {
pub sdk: FlowySDKTest, pub sdk: FlowySDKTest,
pub editor: Arc<ClientDocumentEditor>, pub editor: Arc<ClientBlockEditor>,
} }
impl EditorTest { impl EditorTest {
@ -27,7 +27,7 @@ impl EditorTest {
let sdk = FlowySDKTest::default(); let sdk = FlowySDKTest::default();
let _ = sdk.init_user().await; let _ = sdk.init_user().await;
let test = ViewTest::new(&sdk).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 } Self { sdk, editor }
} }
@ -77,7 +77,7 @@ impl EditorTest {
let delta = self.editor.doc_delta().await.unwrap(); let delta = self.editor.doc_delta().await.unwrap();
if expected_delta != delta { if expected_delta != delta {
eprintln!("✅ expect: {}", expected,); eprintln!("✅ expect: {}", expected,);
eprintln!("❌ receive: {}", delta.to_json()); eprintln!("❌ receive: {}", delta.to_delta_json());
} }
assert_eq!(expected_delta, delta); assert_eq!(expected_delta, delta);
} }

View File

@ -774,7 +774,7 @@ fn delta_compose() {
delta = delta.compose(&d).unwrap(); delta = delta.compose(&d).unwrap();
} }
assert_eq!( assert_eq!(
delta.to_json(), delta.to_delta_json(),
r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"\n"}]"# r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"\n"}]"#
); );

View File

@ -108,20 +108,20 @@ impl TestBuilder {
TestOp::Insert(delta_i, s, index) => { TestOp::Insert(delta_i, s, index) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
let delta = document.insert(*index, s).unwrap(); 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)); self.deltas.insert(*delta_i, Some(delta));
} }
TestOp::Delete(delta_i, iv) => { TestOp::Delete(delta_i, iv) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
let delta = document.replace(*iv, "").unwrap(); 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)); self.deltas.insert(*delta_i, Some(delta));
} }
TestOp::Replace(delta_i, iv, s) => { TestOp::Replace(delta_i, iv, s) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
let delta = document.replace(*iv, s).unwrap(); 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)); self.deltas.insert(*delta_i, Some(delta));
} }
TestOp::InsertBold(delta_i, s, iv) => { TestOp::InsertBold(delta_i, s, iv) => {
@ -133,7 +133,7 @@ impl TestBuilder {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
let attribute = RichTextAttribute::Bold(*enable); let attribute = RichTextAttribute::Bold(*enable);
let delta = document.format(*iv, attribute).unwrap(); 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)); self.deltas.insert(*delta_i, Some(delta));
} }
TestOp::Italic(delta_i, iv, enable) => { TestOp::Italic(delta_i, iv, enable) => {
@ -143,28 +143,28 @@ impl TestBuilder {
false => RichTextAttribute::Italic(false), false => RichTextAttribute::Italic(false),
}; };
let delta = document.format(*iv, attribute).unwrap(); 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)); self.deltas.insert(*delta_i, Some(delta));
} }
TestOp::Header(delta_i, iv, level) => { TestOp::Header(delta_i, iv, level) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
let attribute = RichTextAttribute::Header(*level); let attribute = RichTextAttribute::Header(*level);
let delta = document.format(*iv, attribute).unwrap(); 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)); self.deltas.insert(*delta_i, Some(delta));
} }
TestOp::Link(delta_i, iv, link) => { TestOp::Link(delta_i, iv, link) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
let attribute = RichTextAttribute::Link(link.to_owned()); let attribute = RichTextAttribute::Link(link.to_owned());
let delta = document.format(*iv, attribute).unwrap(); 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)); self.deltas.insert(*delta_i, Some(delta));
} }
TestOp::Bullet(delta_i, iv, enable) => { TestOp::Bullet(delta_i, iv, enable) => {
let document = &mut self.documents[*delta_i]; let document = &mut self.documents[*delta_i];
let attribute = RichTextAttribute::Bullet(*enable); let attribute = RichTextAttribute::Bullet(*enable);
let delta = document.format(*iv, attribute).unwrap(); 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)); self.deltas.insert(*delta_i, Some(delta));
} }
@ -194,15 +194,15 @@ impl TestBuilder {
let delta_a = &self.documents[*delta_a_i].delta(); let delta_a = &self.documents[*delta_a_i].delta();
let delta_b = &self.documents[*delta_b_i].delta(); let delta_b = &self.documents[*delta_b_i].delta();
tracing::debug!("Invert: "); tracing::debug!("Invert: ");
tracing::debug!("a: {}", delta_a.to_json()); tracing::debug!("a: {}", delta_a.to_delta_json());
tracing::debug!("b: {}", delta_b.to_json()); tracing::debug!("b: {}", delta_b.to_delta_json());
let (_, b_prime) = delta_a.transform(delta_b).unwrap(); let (_, b_prime) = delta_a.transform(delta_b).unwrap();
let undo = b_prime.invert(delta_a); let undo = b_prime.invert(delta_a);
let new_delta = delta_a.compose(&b_prime).unwrap(); let new_delta = delta_a.compose(&b_prime).unwrap();
tracing::debug!("new delta: {}", new_delta.to_json()); tracing::debug!("new delta: {}", new_delta.to_delta_json());
tracing::debug!("undo delta: {}", undo.to_json()); tracing::debug!("undo delta: {}", undo.to_delta_json());
let new_delta_after_undo = new_delta.compose(&undo).unwrap(); let new_delta_after_undo = new_delta.compose(&undo).unwrap();
@ -238,7 +238,7 @@ impl TestBuilder {
} }
TestOp::AssertPrimeJson(doc_i, expected) => { 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 expected_prime: RichTextDelta = serde_json::from_str(expected).unwrap();
let target_prime: RichTextDelta = serde_json::from_str(&prime_json).unwrap(); let target_prime: RichTextDelta = serde_json::from_str(&prime_json).unwrap();

View File

@ -92,7 +92,7 @@ fn delta_deserialize_null_test() {
attribute.value = RichTextAttributeValue(None); attribute.value = RichTextAttributeValue(None);
let delta2 = DeltaBuilder::new().retain_with_attributes(7, attribute.into()).build(); 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); assert_eq!(delta1, delta2);
} }

View File

@ -6,8 +6,9 @@ use flowy_sync::RevisionWebSocket;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use flowy_collaboration::{client_folder::FolderPad, entities::ws_data::ServerRevisionWSData}; 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 std::{collections::HashMap, convert::TryInto, fmt::Formatter, sync::Arc};
use tokio::sync::RwLock as TokioRwLock; use tokio::sync::RwLock as TokioRwLock;
@ -17,7 +18,7 @@ use crate::{
errors::FlowyResult, errors::FlowyResult,
event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser},
services::{ services::{
folder_editor::FolderEditor, persistence::FolderPersistence, set_current_workspace, AppController, folder_editor::ClientFolderEditor, persistence::FolderPersistence, set_current_workspace, AppController,
TrashController, ViewController, WorkspaceController, TrashController, ViewController, WorkspaceController,
}, },
}; };
@ -63,7 +64,7 @@ pub struct FolderManager {
pub(crate) view_controller: Arc<ViewController>, pub(crate) view_controller: Arc<ViewController>,
pub(crate) trash_controller: Arc<TrashController>, pub(crate) trash_controller: Arc<TrashController>,
web_socket: Arc<dyn RevisionWebSocket>, web_socket: Arc<dyn RevisionWebSocket>,
folder_editor: Arc<TokioRwLock<Option<Arc<FolderEditor>>>>, folder_editor: Arc<TokioRwLock<Option<Arc<ClientFolderEditor>>>>,
} }
impl FolderManager { impl FolderManager {
@ -71,7 +72,7 @@ impl FolderManager {
user: Arc<dyn WorkspaceUser>, user: Arc<dyn WorkspaceUser>,
cloud_service: Arc<dyn FolderCouldServiceV1>, cloud_service: Arc<dyn FolderCouldServiceV1>,
database: Arc<dyn WorkspaceDatabase>, database: Arc<dyn WorkspaceDatabase>,
document_manager: Arc<FlowyDocumentManager>, document_manager: Arc<BlockManager>,
web_socket: Arc<dyn RevisionWebSocket>, web_socket: Arc<dyn RevisionWebSocket>,
) -> Self { ) -> Self {
if let Ok(user_id) = user.user_id() { if let Ok(user_id) = user.user_id() {
@ -162,7 +163,7 @@ impl FolderManager {
let _ = self.persistence.initialize(user_id, &folder_id).await?; let _ = self.persistence.initialize(user_id, &folder_id).await?;
let pool = self.persistence.db_pool()?; 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)); *self.folder_editor.write().await = Some(Arc::new(folder_editor));
let _ = self.app_controller.initialize()?; let _ = self.app_controller.initialize()?;
@ -196,14 +197,15 @@ impl DefaultFolderBuilder {
for app in workspace.apps.iter() { for app in workspace.apps.iter() {
for (index, view) in app.belongings.iter().enumerate() { for (index, view) in app.belongings.iter().enumerate() {
let view_data = if index == 0 { let view_data = if index == 0 {
initial_read_me().to_json() initial_read_me().to_delta_json()
} else { } else {
initial_delta().to_json() initial_delta().to_delta_json()
}; };
view_controller.set_latest_view(view); view_controller.set_latest_view(view);
let _ = view_controller let delta_data = Bytes::from(view_data);
.create_view_document_content(&view.id, view_data) let repeated_revision: RepeatedRevision =
.await?; 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![])?; let folder = FolderPad::new(vec![workspace.clone()], vec![])?;
@ -219,7 +221,7 @@ impl DefaultFolderBuilder {
#[cfg(feature = "flowy_unit_test")] #[cfg(feature = "flowy_unit_test")]
impl FolderManager { 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() self.folder_editor.read().await.clone().unwrap()
} }
} }

View File

@ -63,9 +63,9 @@ pub fn create(folder: Arc<FolderManager>) -> Module {
.event(FolderEvent::UpdateView, update_view_handler) .event(FolderEvent::UpdateView, update_view_handler)
.event(FolderEvent::DeleteView, delete_view_handler) .event(FolderEvent::DeleteView, delete_view_handler)
.event(FolderEvent::DuplicateView, duplicate_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::CloseView, close_view_handler)
.event(FolderEvent::ApplyDocDelta, document_delta_handler); .event(FolderEvent::ApplyDocDelta, block_delta_handler);
module = module module = module
.event(FolderEvent::ReadTrash, read_trash_handler) .event(FolderEvent::ReadTrash, read_trash_handler)
@ -130,7 +130,7 @@ pub enum FolderEvent {
#[event()] #[event()]
CopyLink = 206, CopyLink = 206,
#[event(input = "ViewId", output = "DocumentDelta")] #[event(input = "ViewId", output = "BlockDelta")]
OpenView = 207, OpenView = 207,
#[event(input = "ViewId")] #[event(input = "ViewId")]
@ -151,7 +151,7 @@ pub enum FolderEvent {
#[event()] #[event()]
DeleteAllTrash = 304, DeleteAllTrash = 304,
#[event(input = "DocumentDelta", output = "DocumentDelta")] #[event(input = "BlockDelta", output = "BlockDelta")]
ApplyDocDelta = 400, ApplyDocDelta = 400,
#[event(input = "ExportPayload", output = "ExportData")] #[event(input = "ExportPayload", output = "ExportData")]

View File

@ -6,12 +6,12 @@ use crate::{
errors::FlowyError, errors::FlowyError,
services::{AppController, TrashController, ViewController}, 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}; use std::{convert::TryInto, sync::Arc};
pub(crate) async fn create_app_handler( pub(crate) async fn create_app_handler(
data: Data<CreateAppPayload>, data: Data<CreateAppPayload>,
controller: Unit<Arc<AppController>>, controller: AppData<Arc<AppController>>,
) -> DataResult<App, FlowyError> { ) -> DataResult<App, FlowyError> {
let params: CreateAppParams = data.into_inner().try_into()?; let params: CreateAppParams = data.into_inner().try_into()?;
let detail = controller.create_app_from_params(params).await?; 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( pub(crate) async fn delete_app_handler(
data: Data<AppId>, data: Data<AppId>,
app_controller: Unit<Arc<AppController>>, app_controller: AppData<Arc<AppController>>,
trash_controller: Unit<Arc<TrashController>>, trash_controller: AppData<Arc<TrashController>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let params: AppId = data.into_inner(); let params: AppId = data.into_inner();
let trash = app_controller let trash = app_controller
@ -39,7 +39,7 @@ pub(crate) async fn delete_app_handler(
#[tracing::instrument(skip(data, controller))] #[tracing::instrument(skip(data, controller))]
pub(crate) async fn update_app_handler( pub(crate) async fn update_app_handler(
data: Data<UpdateAppPayload>, data: Data<UpdateAppPayload>,
controller: Unit<Arc<AppController>>, controller: AppData<Arc<AppController>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let params: UpdateAppParams = data.into_inner().try_into()?; let params: UpdateAppParams = data.into_inner().try_into()?;
let _ = controller.update_app(params).await?; 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))] #[tracing::instrument(skip(data, app_controller, view_controller))]
pub(crate) async fn read_app_handler( pub(crate) async fn read_app_handler(
data: Data<AppId>, data: Data<AppId>,
app_controller: Unit<Arc<AppController>>, app_controller: AppData<Arc<AppController>>,
view_controller: Unit<Arc<ViewController>>, view_controller: AppData<Arc<ViewController>>,
) -> DataResult<App, FlowyError> { ) -> DataResult<App, FlowyError> {
let params: AppId = data.into_inner(); let params: AppId = data.into_inner();
let mut app = app_controller.read_app(params.clone()).await?; let mut app = app_controller.read_app(params.clone()).await?;

View File

@ -8,16 +8,16 @@ use crate::controller::FolderId;
use flowy_collaboration::util::make_delta_from_revisions; use flowy_collaboration::util::make_delta_from_revisions;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use flowy_sync::{ use flowy_sync::{
RevisionCache, RevisionCloudService, RevisionCompact, RevisionManager, RevisionObjectBuilder, RevisionWebSocket, RevisionCloudService, RevisionCompact, RevisionManager, RevisionObjectBuilder, RevisionPersistence,
RevisionWebSocketManager, RevisionWebSocket, RevisionWebSocketManager,
}; };
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
use lib_ot::core::PlainAttributes; use lib_ot::core::PlainTextAttributes;
use lib_sqlite::ConnectionPool; use lib_sqlite::ConnectionPool;
use parking_lot::RwLock; use parking_lot::RwLock;
use std::sync::Arc; use std::sync::Arc;
pub struct FolderEditor { pub struct ClientFolderEditor {
user_id: String, user_id: String,
pub(crate) folder_id: FolderId, pub(crate) folder_id: FolderId,
pub(crate) folder: Arc<RwLock<FolderPad>>, pub(crate) folder: Arc<RwLock<FolderPad>>,
@ -25,7 +25,7 @@ pub struct FolderEditor {
ws_manager: Arc<RevisionWebSocketManager>, ws_manager: Arc<RevisionWebSocketManager>,
} }
impl FolderEditor { impl ClientFolderEditor {
pub async fn new( pub async fn new(
user_id: &str, user_id: &str,
folder_id: &FolderId, folder_id: &FolderId,
@ -33,9 +33,9 @@ impl FolderEditor {
pool: Arc<ConnectionPool>, pool: Arc<ConnectionPool>,
web_socket: Arc<dyn RevisionWebSocket>, web_socket: Arc<dyn RevisionWebSocket>,
) -> FlowyResult<Self> { ) -> FlowyResult<Self> {
let cache = Arc::new(RevisionCache::new(user_id, folder_id.as_ref(), pool)); 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(), cache); let mut rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), rev_persistence);
let cloud = Arc::new(FolderRevisionCloudServiceImpl { let cloud = Arc::new(FolderRevisionCloudService {
token: token.to_string(), token: token.to_string(),
}); });
let folder = Arc::new(RwLock::new( let folder = Arc::new(RwLock::new(
@ -109,12 +109,12 @@ impl RevisionObjectBuilder for FolderPadBuilder {
} }
} }
struct FolderRevisionCloudServiceImpl { struct FolderRevisionCloudService {
#[allow(dead_code)] #[allow(dead_code)]
token: String, token: String,
} }
impl RevisionCloudService for FolderRevisionCloudServiceImpl { impl RevisionCloudService for FolderRevisionCloudService {
#[tracing::instrument(level = "trace", skip(self))] #[tracing::instrument(level = "trace", skip(self))]
fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult<Vec<Revision>, FlowyError> { fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult<Vec<Revision>, FlowyError> {
FutureResult::new(async move { Ok(vec![]) }) FutureResult::new(async move { Ok(vec![]) })
@ -122,7 +122,7 @@ impl RevisionCloudService for FolderRevisionCloudServiceImpl {
} }
#[cfg(feature = "flowy_unit_test")] #[cfg(feature = "flowy_unit_test")]
impl FolderEditor { impl ClientFolderEditor {
pub fn rev_manager(&self) -> Arc<RevisionManager> { pub fn rev_manager(&self) -> Arc<RevisionManager> {
self.rev_manager.clone() self.rev_manager.clone()
} }
@ -144,7 +144,7 @@ impl RevisionCompact for FolderRevisionCompact {
let (base_rev_id, rev_id) = first_revision.pair_rev_id(); let (base_rev_id, rev_id) = first_revision.pair_rev_id();
let md5 = last_revision.md5.clone(); 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(); let delta_data = delta.to_bytes();
Ok(Revision::new(object_id, base_rev_id, rev_id, delta_data, user_id, md5)) Ok(Revision::new(object_id, base_rev_id, rev_id, delta_data, user_id, md5))
} }

View File

@ -1,3 +1,4 @@
use crate::controller::FolderId;
use crate::{ use crate::{
event_map::WorkspaceDatabase, event_map::WorkspaceDatabase,
services::persistence::{AppTableSql, TrashTableSql, ViewTableSql, WorkspaceTableSql}, services::persistence::{AppTableSql, TrashTableSql, ViewTableSql, WorkspaceTableSql},
@ -10,9 +11,11 @@ use flowy_folder_data_model::entities::{
view::{RepeatedView, View}, view::{RepeatedView, View},
workspace::Workspace, workspace::Workspace,
}; };
use flowy_sync::{RevisionLoader, RevisionPersistence};
use std::sync::Arc; 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 { pub(crate) struct FolderMigration {
user_id: String, user_id: String,
@ -32,7 +35,7 @@ impl FolderMigration {
if KV::get_bool(&key) { if KV::get_bool(&key) {
return Ok(None); return Ok(None);
} }
tracing::trace!("Run folder version 1 migrations");
let pool = self.database.db_pool()?; let pool = self.database.db_pool()?;
let conn = &*pool.get()?; let conn = &*pool.get()?;
let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| { let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| {
@ -62,6 +65,7 @@ impl FolderMigration {
})?; })?;
if workspaces.is_empty() { if workspaces.is_empty() {
tracing::trace!("Run folder v1 migration, but workspace is empty");
KV::set_bool(&key, true); KV::set_bool(&key, true);
return Ok(None); return Ok(None);
} }
@ -73,6 +77,35 @@ impl FolderMigration {
let folder = FolderPad::new(workspaces, trash)?; let folder = FolderPad::new(workspaces, trash)?;
KV::set_bool(&key, true); KV::set_bool(&key, true);
tracing::trace!("Run folder v1 migration");
Ok(Some(folder)) 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))
}
} }

View File

@ -2,6 +2,7 @@ mod migration;
pub mod version_1; pub mod version_1;
mod version_2; mod version_2;
use flowy_collaboration::client_folder::initial_folder_delta;
use flowy_collaboration::{ use flowy_collaboration::{
client_folder::FolderPad, client_folder::FolderPad,
entities::revision::{Revision, RevisionState}, entities::revision::{Revision, RevisionState},
@ -13,7 +14,7 @@ pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::
use crate::{ use crate::{
controller::FolderId, controller::FolderId,
event_map::WorkspaceDatabase, event_map::WorkspaceDatabase,
services::{folder_editor::FolderEditor, persistence::migration::FolderMigration}, services::{folder_editor::ClientFolderEditor, persistence::migration::FolderMigration},
}; };
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use flowy_folder_data_model::entities::{ use flowy_folder_data_model::entities::{
@ -50,11 +51,14 @@ pub trait FolderPersistenceTransaction {
pub struct FolderPersistence { pub struct FolderPersistence {
database: Arc<dyn WorkspaceDatabase>, database: Arc<dyn WorkspaceDatabase>,
folder_editor: Arc<RwLock<Option<Arc<FolderEditor>>>>, folder_editor: Arc<RwLock<Option<Arc<ClientFolderEditor>>>>,
} }
impl FolderPersistence { 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 { Self {
database, database,
folder_editor, folder_editor,
@ -102,7 +106,10 @@ impl FolderPersistence {
pub async fn initialize(&self, user_id: &str, folder_id: &FolderId) -> FlowyResult<()> { pub async fn initialize(&self, user_id: &str, folder_id: &FolderId) -> FlowyResult<()> {
let migrations = FolderMigration::new(user_id, self.database.clone()); let migrations = FolderMigration::new(user_id, self.database.clone());
if let Some(migrated_folder) = migrations.run_v1_migration()? { 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?; 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<()> { pub async fn save_folder(&self, user_id: &str, folder_id: &FolderId, folder: FolderPad) -> FlowyResult<()> {
let pool = self.database.db_pool()?; 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 md5 = folder.md5();
let revision = Revision::new(folder_id.as_ref(), 0, 0, delta_data, user_id, md5); let revision = Revision::new(folder_id.as_ref(), 0, 0, delta_data, user_id, md5);
let record = RevisionRecord { let record = RevisionRecord {
@ -120,8 +127,7 @@ impl FolderPersistence {
write_to_disk: true, write_to_disk: true,
}; };
let conn = pool.get()?;
let disk_cache = mk_revision_disk_cache(user_id, pool); 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])
} }
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
entities::{ entities::{
trash::{Trash, TrashType}, trash::{Trash, TrashType},
view::{RepeatedView, UpdateViewParams, View, ViewType}, view::{RepeatedView, UpdateViewParams, View, ViewDataType},
}, },
errors::FlowyError, errors::FlowyError,
services::persistence::version_1::app_sql::AppTable, 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)] #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
#[belongs_to(AppTable, foreign_key = "belong_to_id")] #[belongs_to(AppTable, foreign_key = "belong_to_id")]
#[table_name = "view_table"] #[table_name = "view_table"]
@ -119,16 +76,16 @@ pub(crate) struct ViewTable {
pub modified_time: i64, pub modified_time: i64,
pub create_time: i64, pub create_time: i64,
pub thumbnail: String, pub thumbnail: String,
pub view_type: ViewTableType, pub view_type: SqlViewDataType,
pub version: i64, pub version: i64,
pub is_trash: bool, pub is_trash: bool,
} }
impl ViewTable { impl ViewTable {
pub fn new(view: View) -> Self { pub fn new(view: View) -> Self {
let view_type = match view.view_type { let data_type = match view.data_type {
ViewType::Blank => ViewTableType::Docs, ViewDataType::RichText => SqlViewDataType::RichText,
ViewType::Doc => ViewTableType::Docs, ViewDataType::PlainText => SqlViewDataType::PlainText,
}; };
ViewTable { ViewTable {
@ -138,9 +95,8 @@ impl ViewTable {
desc: view.desc, desc: view.desc,
modified_time: view.modified_time, modified_time: view.modified_time,
create_time: view.create_time, create_time: view.create_time,
// TODO: thumbnail thumbnail: view.thumbnail,
thumbnail: "".to_owned(), view_type: data_type,
view_type,
version: 0, version: 0,
is_trash: false, is_trash: false,
} }
@ -149,8 +105,9 @@ impl ViewTable {
impl std::convert::From<ViewTable> for View { impl std::convert::From<ViewTable> for View {
fn from(table: ViewTable) -> Self { fn from(table: ViewTable) -> Self {
let view_type = match table.view_type { let data_type = match table.view_type {
ViewTableType::Docs => ViewType::Doc, SqlViewDataType::RichText => ViewDataType::RichText,
SqlViewDataType::PlainText => ViewDataType::PlainText,
}; };
View { View {
@ -158,11 +115,16 @@ impl std::convert::From<ViewTable> for View {
belong_to_id: table.belong_to_id, belong_to_id: table.belong_to_id,
name: table.name, name: table.name,
desc: table.desc, desc: table.desc,
view_type, data_type,
belongings: RepeatedView::default(), belongings: RepeatedView::default(),
modified_time: table.modified_time, modified_time: table.modified_time,
version: table.version, version: table.version,
create_time: table.create_time, 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)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)]
#[repr(i32)] #[repr(i32)]
#[sql_type = "Integer"] #[sql_type = "Integer"]
pub enum ViewTableType { pub enum SqlViewDataType {
Docs = 0, RichText = 0,
PlainText = 1,
} }
impl std::default::Default for ViewTableType { impl std::default::Default for SqlViewDataType {
fn default() -> Self { 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 { fn from(value: i32) -> Self {
match value { match value {
0 => ViewTableType::Docs, 0 => SqlViewDataType::RichText,
1 => SqlViewDataType::PlainText,
o => { o => {
log::error!("Unsupported view type {}, fallback to ViewType::Docs", o); log::error!("Unsupported view type {}, fallback to ViewType::Docs", o);
ViewTableType::Docs SqlViewDataType::PlainText
} }
} }
} }
} }
impl ViewTableType { impl SqlViewDataType {
pub fn value(&self) -> i32 { pub fn value(&self) -> i32 {
*self as i32 *self as i32
} }
} }
impl_sql_integer_expression!(ViewTableType); impl_sql_integer_expression!(SqlViewDataType);

View File

@ -1,5 +1,5 @@
use crate::services::{ use crate::services::{
folder_editor::FolderEditor, folder_editor::ClientFolderEditor,
persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}, persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset},
}; };
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
@ -11,7 +11,7 @@ use flowy_folder_data_model::entities::{
}; };
use std::sync::Arc; use std::sync::Arc;
impl FolderPersistenceTransaction for FolderEditor { impl FolderPersistenceTransaction for ClientFolderEditor {
fn create_workspace(&self, _user_id: &str, workspace: Workspace) -> FlowyResult<()> { fn create_workspace(&self, _user_id: &str, workspace: Workspace) -> FlowyResult<()> {
if let Some(change) = self.folder.write().create_workspace(workspace)? { if let Some(change) = self.folder.write().create_workspace(workspace)? {
let _ = self.apply_change(change)?; let _ = self.apply_change(change)?;

View File

@ -3,12 +3,12 @@ use crate::{
errors::FlowyError, errors::FlowyError,
services::TrashController, services::TrashController,
}; };
use lib_dispatch::prelude::{data_result, Data, DataResult, Unit}; use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
use std::sync::Arc; use std::sync::Arc;
#[tracing::instrument(skip(controller), err)] #[tracing::instrument(skip(controller), err)]
pub(crate) async fn read_trash_handler( pub(crate) async fn read_trash_handler(
controller: Unit<Arc<TrashController>>, controller: AppData<Arc<TrashController>>,
) -> DataResult<RepeatedTrash, FlowyError> { ) -> DataResult<RepeatedTrash, FlowyError> {
let repeated_trash = controller.read_trash().await?; let repeated_trash = controller.read_trash().await?;
data_result(repeated_trash) data_result(repeated_trash)
@ -17,7 +17,7 @@ pub(crate) async fn read_trash_handler(
#[tracing::instrument(skip(identifier, controller), err)] #[tracing::instrument(skip(identifier, controller), err)]
pub(crate) async fn putback_trash_handler( pub(crate) async fn putback_trash_handler(
identifier: Data<TrashId>, identifier: Data<TrashId>,
controller: Unit<Arc<TrashController>>, controller: AppData<Arc<TrashController>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let _ = controller.putback(&identifier.id).await?; let _ = controller.putback(&identifier.id).await?;
Ok(()) Ok(())
@ -26,20 +26,20 @@ pub(crate) async fn putback_trash_handler(
#[tracing::instrument(skip(identifiers, controller), err)] #[tracing::instrument(skip(identifiers, controller), err)]
pub(crate) async fn delete_trash_handler( pub(crate) async fn delete_trash_handler(
identifiers: Data<RepeatedTrashId>, identifiers: Data<RepeatedTrashId>,
controller: Unit<Arc<TrashController>>, controller: AppData<Arc<TrashController>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let _ = controller.delete(identifiers.into_inner()).await?; let _ = controller.delete(identifiers.into_inner()).await?;
Ok(()) Ok(())
} }
#[tracing::instrument(skip(controller), err)] #[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?; let _ = controller.restore_all_trash().await?;
Ok(()) Ok(())
} }
#[tracing::instrument(skip(controller), err)] #[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?; let _ = controller.delete_all_trash().await?;
Ok(()) Ok(())
} }

View File

@ -1,6 +1,6 @@
use bytes::Bytes; use bytes::Bytes;
use flowy_collaboration::entities::{ use flowy_collaboration::entities::{
document_info::{DocumentDelta, DocumentId}, document_info::{BlockDelta, BlockId},
revision::{RepeatedRevision, Revision}, revision::{RepeatedRevision, Revision},
}; };
@ -22,8 +22,9 @@ use crate::{
}, },
}; };
use flowy_database::kv::KV; use flowy_database::kv::KV;
use flowy_document::FlowyDocumentManager; use flowy_document::BlockManager;
use flowy_folder_data_model::entities::share::{ExportData, ExportParams}; use flowy_folder_data_model::entities::share::{ExportData, ExportParams};
use lib_infra::uuid_string; use lib_infra::uuid_string;
const LATEST_VIEW_ID: &str = "latest_view_id"; const LATEST_VIEW_ID: &str = "latest_view_id";
@ -33,7 +34,7 @@ pub(crate) struct ViewController {
cloud_service: Arc<dyn FolderCouldServiceV1>, cloud_service: Arc<dyn FolderCouldServiceV1>,
persistence: Arc<FolderPersistence>, persistence: Arc<FolderPersistence>,
trash_controller: Arc<TrashController>, trash_controller: Arc<TrashController>,
document_manager: Arc<FlowyDocumentManager>, block_manager: Arc<BlockManager>,
} }
impl ViewController { impl ViewController {
@ -42,62 +43,51 @@ impl ViewController {
persistence: Arc<FolderPersistence>, persistence: Arc<FolderPersistence>,
cloud_service: Arc<dyn FolderCouldServiceV1>, cloud_service: Arc<dyn FolderCouldServiceV1>,
trash_can: Arc<TrashController>, trash_can: Arc<TrashController>,
document_manager: Arc<FlowyDocumentManager>, document_manager: Arc<BlockManager>,
) -> Self { ) -> Self {
Self { Self {
user, user,
cloud_service, cloud_service,
persistence, persistence,
trash_controller: trash_can, trash_controller: trash_can,
document_manager, block_manager: document_manager,
} }
} }
pub(crate) fn initialize(&self) -> Result<(), FlowyError> { pub(crate) fn initialize(&self) -> Result<(), FlowyError> {
let _ = self.document_manager.init()?; let _ = self.block_manager.init()?;
self.listen_trash_can_event(); self.listen_trash_can_event();
Ok(()) Ok(())
} }
#[tracing::instrument(level = "trace", skip(self, params), fields(name = %params.name), err)] #[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> { 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() initial_delta_string()
} else { } else {
params.view_data.clone() params.data.clone()
}; };
let delta_data = Bytes::from(view_data); let delta_data = Bytes::from(view_data);
let user_id = self.user.user_id()?; let user_id = self.user.user_id()?;
let repeated_revision: RepeatedRevision = let repeated_revision: RepeatedRevision =
Revision::initial_revision(&user_id, &params.view_id, delta_data).into(); Revision::initial_revision(&user_id, &params.view_id, delta_data).into();
let _ = self let _ = self.create_view(&params.view_id, repeated_revision).await?;
.document_manager
.reset_with_revisions(&params.view_id, repeated_revision)
.await?;
let view = self.create_view_on_server(params).await?; let view = self.create_view_on_server(params).await?;
let _ = self.create_view_on_local(view.clone()).await?; let _ = self.create_view_on_local(view.clone()).await?;
Ok(view) Ok(view)
} }
#[tracing::instrument(level = "debug", skip(self, view_id, view_data), err)] #[tracing::instrument(level = "debug", skip(self, view_id, repeated_revision), err)]
pub(crate) async fn create_view_document_content( pub(crate) async fn create_view(
&self, &self,
view_id: &str, view_id: &str,
view_data: String, repeated_revision: RepeatedRevision,
) -> Result<(), FlowyError> { ) -> 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")); return Err(FlowyError::internal().context("The content of the view should not be empty"));
} }
let _ = self.block_manager.create_block(view_id, repeated_revision).await?;
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?;
Ok(()) Ok(())
} }
@ -143,50 +133,52 @@ impl ViewController {
} }
#[tracing::instrument(level = "debug", skip(self), err)] #[tracing::instrument(level = "debug", skip(self), err)]
pub(crate) async fn open_document(&self, doc_id: &str) -> Result<DocumentDelta, FlowyError> { pub(crate) async fn open_view(&self, view_id: &str) -> Result<BlockDelta, FlowyError> {
let editor = self.document_manager.open_document(doc_id).await?; let editor = self.block_manager.open_block(view_id).await?;
KV::set_str(LATEST_VIEW_ID, doc_id.to_owned()); KV::set_str(LATEST_VIEW_ID, view_id.to_owned());
let document_json = editor.document_json().await?; let document_json = editor.block_json().await?;
Ok(DocumentDelta { Ok(BlockDelta {
doc_id: doc_id.to_string(), block_id: view_id.to_string(),
delta_json: document_json, delta_json: document_json,
}) })
} }
#[tracing::instrument(level = "debug", skip(self), err)] #[tracing::instrument(level = "debug", skip(self), err)]
pub(crate) async fn close_view(&self, doc_id: &str) -> Result<(), FlowyError> { 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(()) Ok(())
} }
#[tracing::instrument(level = "debug", skip(self,params), fields(doc_id = %params.value), err)] #[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 let Some(view_id) = KV::get_str(LATEST_VIEW_ID) {
if view_id == params.value { if view_id == params.value {
let _ = KV::remove(LATEST_VIEW_ID); let _ = KV::remove(LATEST_VIEW_ID);
} }
} }
let _ = self.document_manager.close_document(&params.value)?; let _ = self.block_manager.close_block(&params.value)?;
Ok(()) Ok(())
} }
#[tracing::instrument(level = "debug", skip(self), err)] #[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 let view = self
.persistence .persistence
.begin_transaction(|transaction| transaction.read_view(doc_id)) .begin_transaction(|transaction| transaction.read_view(view_id))
.await?; .await?;
let editor = self.document_manager.open_document(doc_id).await?; let editor = self.block_manager.open_block(view_id).await?;
let document_json = editor.document_json().await?; let document_json = editor.block_json().await?;
let duplicate_params = CreateViewParams { let duplicate_params = CreateViewParams {
belong_to_id: view.belong_to_id.clone(), belong_to_id: view.belong_to_id.clone(),
name: format!("{} (copy)", &view.name), name: format!("{} (copy)", &view.name),
desc: view.desc.clone(), desc: view.desc,
thumbnail: "".to_owned(), thumbnail: view.thumbnail,
view_type: view.view_type.clone(), data_type: view.data_type,
view_data: document_json, data: document_json,
view_id: uuid_string(), view_id: uuid_string(),
ext_data: view.ext_data,
plugin_type: view.plugin_type,
}; };
let _ = self.create_view_from_params(duplicate_params).await?; let _ = self.create_view_from_params(duplicate_params).await?;
@ -194,9 +186,9 @@ impl ViewController {
} }
#[tracing::instrument(level = "debug", skip(self, params), err)] #[tracing::instrument(level = "debug", skip(self, params), err)]
pub(crate) async fn export_doc(&self, params: ExportParams) -> Result<ExportData, FlowyError> { pub(crate) async fn export_view(&self, params: ExportParams) -> Result<ExportData, FlowyError> {
let editor = self.document_manager.open_document(&params.doc_id).await?; let editor = self.block_manager.open_block(&params.view_id).await?;
let delta_json = editor.document_json().await?; let delta_json = editor.block_json().await?;
Ok(ExportData { Ok(ExportData {
data: delta_json, data: delta_json,
export_type: params.export_type, export_type: params.export_type,
@ -234,8 +226,8 @@ impl ViewController {
Ok(view) Ok(view)
} }
pub(crate) async fn receive_document_delta(&self, params: DocumentDelta) -> Result<DocumentDelta, FlowyError> { pub(crate) async fn receive_delta(&self, params: BlockDelta) -> Result<BlockDelta, FlowyError> {
let doc = self.document_manager.receive_local_delta(params).await?; let doc = self.block_manager.receive_local_delta(params).await?;
Ok(doc) Ok(doc)
} }
@ -312,7 +304,7 @@ impl ViewController {
fn listen_trash_can_event(&self) { fn listen_trash_can_event(&self) {
let mut rx = self.trash_controller.subscribe(); let mut rx = self.trash_controller.subscribe();
let persistence = self.persistence.clone(); 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 trash_controller = self.trash_controller.clone();
let _ = tokio::spawn(async move { let _ = tokio::spawn(async move {
loop { loop {
@ -340,7 +332,7 @@ impl ViewController {
#[tracing::instrument(level = "trace", skip(persistence, document_manager, trash_can))] #[tracing::instrument(level = "trace", skip(persistence, document_manager, trash_can))]
async fn handle_trash_event( async fn handle_trash_event(
persistence: Arc<FolderPersistence>, persistence: Arc<FolderPersistence>,
document_manager: Arc<FlowyDocumentManager>, document_manager: Arc<BlockManager>,
trash_can: Arc<TrashController>, trash_can: Arc<TrashController>,
event: TrashEvent, event: TrashEvent,
) { ) {

View File

@ -8,14 +8,14 @@ use crate::{
errors::FlowyError, errors::FlowyError,
services::{TrashController, ViewController}, 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 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}; use std::{convert::TryInto, sync::Arc};
pub(crate) async fn create_view_handler( pub(crate) async fn create_view_handler(
data: Data<CreateViewPayload>, data: Data<CreateViewPayload>,
controller: Unit<Arc<ViewController>>, controller: AppData<Arc<ViewController>>,
) -> DataResult<View, FlowyError> { ) -> DataResult<View, FlowyError> {
let params: CreateViewParams = data.into_inner().try_into()?; let params: CreateViewParams = data.into_inner().try_into()?;
let view = controller.create_view_from_params(params).await?; 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( pub(crate) async fn read_view_handler(
data: Data<ViewId>, data: Data<ViewId>,
controller: Unit<Arc<ViewController>>, controller: AppData<Arc<ViewController>>,
) -> DataResult<View, FlowyError> { ) -> DataResult<View, FlowyError> {
let view_id: ViewId = data.into_inner(); let view_id: ViewId = data.into_inner();
let mut view = controller.read_view(view_id.clone()).await?; 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)] #[tracing::instrument(skip(data, controller), err)]
pub(crate) async fn update_view_handler( pub(crate) async fn update_view_handler(
data: Data<UpdateViewPayload>, data: Data<UpdateViewPayload>,
controller: Unit<Arc<ViewController>>, controller: AppData<Arc<ViewController>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let params: UpdateViewParams = data.into_inner().try_into()?; let params: UpdateViewParams = data.into_inner().try_into()?;
let _ = controller.update_view(params).await?; let _ = controller.update_view(params).await?;
@ -46,18 +46,18 @@ pub(crate) async fn update_view_handler(
Ok(()) Ok(())
} }
pub(crate) async fn document_delta_handler( pub(crate) async fn block_delta_handler(
data: Data<DocumentDelta>, data: Data<BlockDelta>,
controller: Unit<Arc<ViewController>>, controller: AppData<Arc<ViewController>>,
) -> DataResult<DocumentDelta, FlowyError> { ) -> DataResult<BlockDelta, FlowyError> {
let doc = controller.receive_document_delta(data.into_inner()).await?; let block_delta = controller.receive_delta(data.into_inner()).await?;
data_result(doc) data_result(block_delta)
} }
pub(crate) async fn delete_view_handler( pub(crate) async fn delete_view_handler(
data: Data<RepeatedViewId>, data: Data<RepeatedViewId>,
view_controller: Unit<Arc<ViewController>>, view_controller: AppData<Arc<ViewController>>,
trash_controller: Unit<Arc<TrashController>>, trash_controller: AppData<Arc<TrashController>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let params: RepeatedViewId = data.into_inner(); let params: RepeatedViewId = data.into_inner();
for view_id in &params.items { for view_id in &params.items {
@ -75,18 +75,18 @@ pub(crate) async fn delete_view_handler(
Ok(()) Ok(())
} }
pub(crate) async fn open_document_handler( pub(crate) async fn open_view_handler(
data: Data<ViewId>, data: Data<ViewId>,
controller: Unit<Arc<ViewController>>, controller: AppData<Arc<ViewController>>,
) -> DataResult<DocumentDelta, FlowyError> { ) -> DataResult<BlockDelta, FlowyError> {
let view_id: ViewId = data.into_inner(); 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) data_result(doc)
} }
pub(crate) async fn close_view_handler( pub(crate) async fn close_view_handler(
data: Data<ViewId>, data: Data<ViewId>,
controller: Unit<Arc<ViewController>>, controller: AppData<Arc<ViewController>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let view_id: ViewId = data.into_inner(); let view_id: ViewId = data.into_inner();
let _ = controller.close_view(&view_id.value).await?; 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)] #[tracing::instrument(skip(data, controller), err)]
pub(crate) async fn duplicate_view_handler( pub(crate) async fn duplicate_view_handler(
data: Data<ViewId>, data: Data<ViewId>,
controller: Unit<Arc<ViewController>>, controller: AppData<Arc<ViewController>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let view_id: ViewId = data.into_inner(); let view_id: ViewId = data.into_inner();
let _ = controller.duplicate_view(&view_id.value).await?; 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)] #[tracing::instrument(skip(data, controller), err)]
pub(crate) async fn export_handler( pub(crate) async fn export_handler(
data: Data<ExportPayload>, data: Data<ExportPayload>,
controller: Unit<Arc<ViewController>>, controller: AppData<Arc<ViewController>>,
) -> DataResult<ExportData, FlowyError> { ) -> DataResult<ExportData, FlowyError> {
let params: ExportParams = data.into_inner().try_into()?; 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) data_result(data)
} }

View File

@ -10,7 +10,7 @@ use flowy_collaboration::{
use flowy_error::FlowyError; use flowy_error::FlowyError;
use flowy_sync::*; use flowy_sync::*;
use lib_infra::future::{BoxResultFuture, FutureResult}; 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 parking_lot::RwLock;
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
@ -21,45 +21,41 @@ pub(crate) async fn make_folder_ws_manager(
web_socket: Arc<dyn RevisionWebSocket>, web_socket: Arc<dyn RevisionWebSocket>,
folder_pad: Arc<RwLock<FolderPad>>, folder_pad: Arc<RwLock<FolderPad>>,
) -> Arc<RevisionWebSocketManager> { ) -> Arc<RevisionWebSocketManager> {
let composite_sink_provider = Arc::new(CompositeWSSinkDataProvider::new(folder_id, rev_manager.clone())); let ws_data_provider = Arc::new(WSDataProvider::new(folder_id, Arc::new(rev_manager.clone())));
let resolve_target = Arc::new(FolderRevisionResolveTarget { folder_pad }); let resolver = Arc::new(FolderConflictResolver { folder_pad });
let resolver = RevisionConflictResolver::<PlainAttributes>::new( let conflict_controller = ConflictController::<PlainTextAttributes>::new(
user_id, user_id,
resolve_target, resolver,
Arc::new(composite_sink_provider.clone()), Arc::new(ws_data_provider.clone()),
rev_manager, rev_manager,
); );
let ws_data_stream = Arc::new(FolderRevisionWSDataStream::new(conflict_controller));
let ws_stream_consumer = Arc::new(FolderWSStreamConsumerAdapter { let ws_data_sink = Arc::new(FolderWSDataSink(ws_data_provider));
resolver: Arc::new(resolver),
});
let sink_provider = Arc::new(FolderWSSinkDataProviderAdapter(composite_sink_provider));
let ping_duration = Duration::from_millis(FOLDER_SYNC_INTERVAL_IN_MILLIS); let ping_duration = Duration::from_millis(FOLDER_SYNC_INTERVAL_IN_MILLIS);
Arc::new(RevisionWebSocketManager::new( Arc::new(RevisionWebSocketManager::new(
"Folder", "Folder",
folder_id, folder_id,
web_socket, web_socket,
sink_provider, ws_data_sink,
ws_stream_consumer, ws_data_stream,
ping_duration, ping_duration,
)) ))
} }
pub(crate) struct FolderWSSinkDataProviderAdapter(Arc<CompositeWSSinkDataProvider>); pub(crate) struct FolderWSDataSink(Arc<WSDataProvider>);
impl RevisionWSSinkDataProvider for FolderWSSinkDataProviderAdapter { impl RevisionWSDataIterator for FolderWSDataSink {
fn next(&self) -> FutureResult<Option<ClientRevisionWSData>, FlowyError> { fn next(&self) -> FutureResult<Option<ClientRevisionWSData>, FlowyError> {
let sink_provider = self.0.clone(); let sink_provider = self.0.clone();
FutureResult::new(async move { sink_provider.next().await }) FutureResult::new(async move { sink_provider.next().await })
} }
} }
struct FolderRevisionResolveTarget { struct FolderConflictResolver {
folder_pad: Arc<RwLock<FolderPad>>, folder_pad: Arc<RwLock<FolderPad>>,
} }
impl ResolverTarget<PlainAttributes> for FolderRevisionResolveTarget { impl ConflictResolver<PlainTextAttributes> for FolderConflictResolver {
fn compose_delta(&self, delta: Delta<PlainAttributes>) -> BoxResultFuture<DeltaMD5, FlowyError> { fn compose_delta(&self, delta: PlainTextDelta) -> BoxResultFuture<DeltaMD5, FlowyError> {
let folder_pad = self.folder_pad.clone(); let folder_pad = self.folder_pad.clone();
Box::pin(async move { Box::pin(async move {
let md5 = folder_pad.write().compose_remote_delta(delta)?; let md5 = folder_pad.write().compose_remote_delta(delta)?;
@ -69,13 +65,13 @@ impl ResolverTarget<PlainAttributes> for FolderRevisionResolveTarget {
fn transform_delta( fn transform_delta(
&self, &self,
delta: Delta<PlainAttributes>, delta: PlainTextDelta,
) -> BoxResultFuture<TransformDeltas<PlainAttributes>, FlowyError> { ) -> BoxResultFuture<TransformDeltas<PlainTextAttributes>, FlowyError> {
let folder_pad = self.folder_pad.clone(); let folder_pad = self.folder_pad.clone();
Box::pin(async move { Box::pin(async move {
let read_guard = folder_pad.read(); let read_guard = folder_pad.read();
let mut server_prime: Option<PlainDelta> = None; let mut server_prime: Option<PlainTextDelta> = None;
let client_prime: PlainDelta; let client_prime: PlainTextDelta;
if read_guard.is_empty() { if read_guard.is_empty() {
// Do nothing // Do nothing
client_prime = delta; 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(); let folder_pad = self.folder_pad.clone();
Box::pin(async move { Box::pin(async move {
let md5 = folder_pad.write().reset_folder(delta)?; let md5 = folder_pad.write().reset_folder(delta)?;
@ -101,18 +97,26 @@ impl ResolverTarget<PlainAttributes> for FolderRevisionResolveTarget {
} }
} }
struct FolderWSStreamConsumerAdapter { struct FolderRevisionWSDataStream {
resolver: Arc<RevisionConflictResolver<PlainAttributes>>, 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> { 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 }) Box::pin(async move { resolver.receive_bytes(bytes).await })
} }
fn receive_ack(&self, id: String, ty: ServerRevisionWSDataType) -> BoxResultFuture<(), FlowyError> { 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 }) 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> { 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 }) 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