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
commit e7033aa6e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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/workspace_listener.dart';
import 'package:app_flowy/workspace/application/workspace/workspace_service.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
@ -55,7 +55,7 @@ class HomeDepsResolver {
getIt.registerFactoryParam<ViewBloc, View, void>(
(view, _) => ViewBloc(
view: view,
view: view,
service: ViewService(),
listener: getIt<ViewListener>(param1: view),
),

View File

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

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

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

View File

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

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

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';
class DocumentService {
Future<Either<DocumentDelta, FlowyError>> openDocument({required String docId}) {
Future<Either<BlockDelta, FlowyError>> openDocument({required String docId}) {
final request = ViewId(value: docId);
return FolderEventOpenView(request).send();
}
Future<Either<DocumentDelta, FlowyError>> composeDelta({required String docId, required String data}) {
final request = DocumentDelta.create()
..docId = docId
Future<Either<BlockDelta, FlowyError>> composeDelta({required String docId, required String data}) {
final request = BlockDelta.create()
..blockId = docId
..deltaJson = data;
return FolderEventApplyDocDelta(request).send();
}

View File

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

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

View File

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

File diff suppressed because it is too large Load Diff

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

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

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:easy_localization/easy_localization.dart';
import 'package:expandable/expandable.dart';
@ -13,6 +12,8 @@ import 'package:app_flowy/workspace/application/app/app_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:dartz/dartz.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/image.dart';
import '../menu_app.dart';
import 'add_button.dart';
import 'right_click_action.dart';
@ -102,10 +103,13 @@ class MenuAppHeader extends StatelessWidget {
return Tooltip(
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
child: AddButton(
onSelected: (viewType) {
context
.read<AppBloc>()
.add(AppEvent.createView(LocaleKeys.menuAppHeader_defaultNewPageName.tr(), "", viewType));
onSelected: (pluginBuilder) {
context.read<AppBloc>().add(AppEvent.createView(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
"",
pluginBuilder.dataType,
pluginBuilder.pluginType,
));
},
).padding(right: MenuAppSizes.headerPadding),
);
@ -131,3 +135,28 @@ class MenuAppHeader extends StatelessWidget {
});
}
}
enum AppDisclosureAction {
rename,
delete,
}
extension AppDisclosureExtension on AppDisclosureAction {
String get name {
switch (this) {
case AppDisclosureAction.rename:
return LocaleKeys.disclosureAction_rename.tr();
case AppDisclosureAction.delete:
return LocaleKeys.disclosureAction_delete.tr();
}
}
Widget get icon {
switch (this) {
case AppDisclosureAction.rename:
return svg('editor/edit', color: const Color(0xffe5e5e5));
case AppDisclosureAction.delete:
return svg('editor/delete', color: const Color(0xffe5e5e5));
}
}
}

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

View File

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

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

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/view/view_bloc.dart';
import 'package:app_flowy/workspace/domain/edit_action/view_edit.dart';
import 'package:app_flowy/workspace/application/view/view_ext.dart';
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:dartz/dartz.dart' as dartz;
import 'package:easy_localization/easy_localization.dart';
@ -12,9 +13,8 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:app_flowy/workspace/domain/image.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/menu_app.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/image.dart';
import 'disclosure_action.dart';
@ -55,7 +55,7 @@ class ViewSectionItem extends StatelessWidget {
Widget _render(BuildContext context, bool onHover, ViewState state, Color iconColor) {
List<Widget> children = [
SizedBox(width: 16, height: 16, child: state.view.thumbnail(iconColor: iconColor)),
SizedBox(width: 16, height: 16, child: state.view.renderThumbnail(iconColor: iconColor)),
const HSpace(2),
Expanded(child: FlowyText.regular(state.view.name, fontSize: 12, overflow: TextOverflow.clip)),
];
@ -104,3 +104,33 @@ class ViewSectionItem extends StatelessWidget {
});
}
}
enum ViewDisclosureAction {
rename,
delete,
duplicate,
}
extension ViewDisclosureExtension on ViewDisclosureAction {
String get name {
switch (this) {
case ViewDisclosureAction.rename:
return LocaleKeys.disclosureAction_rename.tr();
case ViewDisclosureAction.delete:
return LocaleKeys.disclosureAction_delete.tr();
case ViewDisclosureAction.duplicate:
return LocaleKeys.disclosureAction_duplicate.tr();
}
}
Widget get icon {
switch (this) {
case ViewDisclosureAction.rename:
return svg('editor/edit', color: const Color(0xff999999));
case ViewDisclosureAction.delete:
return svg('editor/delete', color: const Color(0xff999999));
case ViewDisclosureAction.duplicate:
return svg('editor/copy', color: const Color(0xff999999));
}
}
}

View File

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

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/size.dart';
import 'package:flowy_infra/theme.dart';
@ -15,32 +19,13 @@ import 'package:expandable/expandable.dart';
import 'package:flowy_infra/time/duration.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/menu_user.dart';
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'widget/app/menu_app.dart';
import 'widget/app/create_button.dart';
import 'widget/menu_trash.dart';
// [[diagram: HomeMenu's widget structure]]
// get user profile or modify user
//
// IUser
// MenuTopBar
// MenuUserMenuUserBloc
//
// HomeMenu IUserListener
//
// listen workspace changes or user
// impl profile changes
//
// MenuList MenuItem
//
// AppBloc fetch app's views or modify view
//
//
// MenuApp
//
import 'app/menu_app.dart';
import 'app/create_button.dart';
import 'menu_user.dart';
class HomeMenu extends StatelessWidget {
final PublishNotifier<bool> _collapsedNotifier;
@ -70,9 +55,9 @@ class HomeMenu extends StatelessWidget {
child: MultiBlocListener(
listeners: [
BlocListener<MenuBloc, MenuState>(
listenWhen: (p, c) => p.stackContext != c.stackContext,
listenWhen: (p, c) => p.plugin.pluginId != c.plugin.pluginId,
listener: (context, state) {
getIt<HomeStackManager>().setStack(state.stackContext);
getIt<HomeStackManager>().setPlugin(state.plugin);
},
),
BlocListener<MenuBloc, MenuState>(
@ -181,3 +166,32 @@ class MenuSharedState extends ChangeNotifier {
});
}
}
class MenuTopBar extends StatelessWidget {
const MenuTopBar({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocBuilder<MenuBloc, MenuState>(
builder: (context, state) {
return SizedBox(
height: HomeSizes.topBarHeight,
child: Row(
children: [
(theme.isDark
? svgWithSize("flowy_logo_dark_mode", const Size(92, 17))
: svgWithSize("flowy_logo_with_text", const Size(92, 17))),
const Spacer(),
FlowyIconButton(
width: 28,
onPressed: () => context.read<MenuBloc>().add(const MenuEvent.collapse()),
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
icon: svg("home/hide_menu", color: theme.iconColor),
)
],
),
);
},
);
}
}

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

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:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
class BlankStackContext extends HomeStackContext {
final ValueNotifier<bool> _isUpdated = ValueNotifier<bool>(false);
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugin/plugin.dart';
class BlankPluginBuilder extends PluginBuilder {
@override
Plugin build(dynamic data) {
return BlankPagePlugin(pluginType: pluginType);
}
@override
String get identifier => "1";
String get menuName => "Blank";
@override
PluginType get pluginType => DefaultPlugin.blank.type();
}
class BlankPluginConfig implements PluginConfig {
@override
bool get creatable => false;
}
class BlankPagePlugin extends Plugin {
final PluginType _pluginType;
BlankPagePlugin({
required PluginType pluginType,
}) : _pluginType = pluginType;
@override
void dispose() {}
@override
PluginDisplay get pluginDisplay => BlankPagePluginDisplay();
@override
PluginId get pluginId => "BlankStack";
@override
PluginType get pluginType => _pluginType;
}
class BlankPagePluginDisplay extends PluginDisplay {
@override
Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr(), fontSize: 12);
@override
Widget? get rightBarItem => null;
@override
HomeStackType get type => HomeStackType.blank;
@override
Widget buildWidget() {
return const BlankStackPage();
}
Widget buildWidget() => const BlankStackPage();
@override
List<NavigationItem> get navigationItems => [this];
@override
ValueNotifier<bool> get isUpdated => _isUpdated;
@override
void dispose() {}
}
class BlankStackPage extends StatefulWidget {

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/tasks/load_plugin.dart';
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/workspace/application/doc/share_bloc.dart';
import 'package:app_flowy/workspace/application/view/view_listener.dart';
import 'package:app_flowy/workspace/application/view/view_service.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:app_flowy/workspace/domain/view_ext.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:easy_localization/easy_localization.dart';
@ -23,62 +31,90 @@ import 'package:clipboard/clipboard.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:provider/provider.dart';
import 'document_page.dart';
import 'src/document_page.dart';
class DocStackContext extends HomeStackContext<int, ShareActionWrapper> {
View _view;
late ViewListener _listener;
final ValueNotifier<int> _isUpdated = ValueNotifier<int>(0);
class DocumentPluginBuilder extends PluginBuilder {
@override
Plugin build(dynamic data) {
if (data is View) {
return DocumentPlugin(pluginType: pluginType, view: data);
} else {
throw FlowyPluginException.invalidData;
}
}
DocStackContext({required View view, Key? key}) : _view = view {
@override
String get menuName => "Doc";
@override
PluginType get pluginType => DefaultPlugin.quillEditor.type();
@override
ViewDataType get dataType => ViewDataType.RichText;
}
class DocumentPlugin implements Plugin {
late View _view;
ViewListener? _listener;
final ValueNotifier<int> _displayNotifier = ValueNotifier<int>(0);
late PluginType _pluginType;
DocumentPlugin({required PluginType pluginType, required View view, Key? key}) : _view = view {
_pluginType = pluginType;
_listener = getIt<ViewListener>(param1: view);
_listener.updatedNotifier.addPublishListener((result) {
_listener?.updatedNotifier.addPublishListener((result) {
result.fold(
(newView) {
_view = newView;
_isUpdated.value = _view.hashCode;
_displayNotifier.value = _view.hashCode;
},
(error) {},
);
});
_listener.start();
_listener?.start();
}
@override
void dispose() {
_listener?.close();
_listener = null;
}
@override
PluginDisplay get pluginDisplay => DocumentPluginDisplay(view: _view);
@override
PluginType get pluginType => _pluginType;
@override
PluginId get pluginId => _view.id;
@override
ChangeNotifier? get displayNotifier => _displayNotifier;
}
class DocumentPluginDisplay extends PluginDisplay {
final View _view;
DocumentPluginDisplay({required View view, Key? key}) : _view = view;
@override
Widget buildWidget() => DocumentPage(view: _view, key: ValueKey(_view.id));
@override
Widget get leftBarItem => DocumentLeftBarItem(view: _view);
@override
Widget? get rightBarItem => DocumentShareButton(view: _view);
@override
String get identifier => _view.id;
@override
HomeStackType get type => _view.stackType();
@override
Widget buildWidget() => DocumentPage(view: _view, key: ValueKey(_view.id));
@override
List<NavigationItem> get navigationItems => _makeNavigationItems();
@override
ValueNotifier<int> get isUpdated => _isUpdated;
// List<NavigationItem> get navigationItems => naviStacks.map((stack) {
// return NavigationItemImpl(context: stack);
// }).toList();
List<NavigationItem> _makeNavigationItems() {
return [
this,
];
}
@override
void dispose() {
_listener.close();
}
}
class DocumentLeftBarItem extends StatefulWidget {

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/workspace/application/doc/doc_bloc.dart';
import 'package:app_flowy/workspace/presentation/plugins/doc/document.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter_quill/flutter_quill.dart' as quill;
@ -12,7 +13,6 @@ import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'styles.dart';
import 'widget/banner.dart';
import 'widget/toolbar/tool_bar.dart';
class DocumentPage extends StatefulWidget {
final View view;

View File

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

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:math';
import 'package:app_flowy/workspace/presentation/stack_page/doc/widget/toolbar/history_button.dart';
import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_quill/flutter_quill.dart';
@ -10,6 +9,7 @@ import 'package:styled_widget/styled_widget.dart';
import 'check_button.dart';
import 'color_picker.dart';
import 'header_button.dart';
import 'history_button.dart';
import 'link_button.dart';
import 'toggle_button.dart';
import 'toolbar_icon_button.dart';

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

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/tasks/load_plugin.dart';
import 'package:app_flowy/workspace/application/trash/trash_bloc.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:app_flowy/workspace/presentation/stack_page/trash/widget/sizes.dart';
import 'package:app_flowy/workspace/presentation/stack_page/trash/widget/trash_cell.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -17,14 +21,47 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'widget/trash_header.dart';
import 'src/sizes.dart';
import 'src/trash_cell.dart';
import 'src/trash_header.dart';
class TrashStackContext extends HomeStackContext {
final ValueNotifier<bool> _isUpdated = ValueNotifier<bool>(false);
class TrashPluginBuilder extends PluginBuilder {
@override
Plugin build(dynamic data) {
return TrashPlugin(pluginType: pluginType);
}
@override
String get identifier => "TrashStackContext";
String get menuName => "Trash";
@override
PluginType get pluginType => DefaultPlugin.trash.type();
}
class TrashPluginConfig implements PluginConfig {
@override
bool get creatable => false;
}
class TrashPlugin extends Plugin {
final PluginType _pluginType;
TrashPlugin({required PluginType pluginType}) : _pluginType = pluginType;
@override
void dispose() {}
@override
PluginDisplay get pluginDisplay => TrashPluginDisplay();
@override
PluginId get pluginId => "TrashStack";
@override
PluginType get pluginType => _pluginType;
}
class TrashPluginDisplay extends PluginDisplay {
@override
Widget get leftBarItem => FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12);
@ -32,21 +69,10 @@ class TrashStackContext extends HomeStackContext {
Widget? get rightBarItem => null;
@override
HomeStackType get type => HomeStackType.trash;
@override
Widget buildWidget() {
return const TrashStackPage(key: ValueKey('TrashStackPage'));
}
Widget buildWidget() => const TrashStackPage(key: ValueKey('TrashStackPage'));
@override
List<NavigationItem> get navigationItems => [this];
@override
ValueNotifier<bool> get isUpdated => _isUpdated;
@override
void dispose() {}
}
class TrashStackPage extends StatefulWidget {

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/domain/edit_context.dart';
import 'package:app_flowy/workspace/application/edit_pannel/edit_context.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
import 'package:dartz/dartz.dart';

View File

@ -1,7 +1,6 @@
import 'package:app_flowy/workspace/presentation/plugins/doc/document.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:app_flowy/workspace/presentation/stack_page/doc/widget/toolbar/toolbar_icon_button.dart';
import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
class FlowyEmojiStyleButton extends StatefulWidget {

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

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

View File

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

View File

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

View File

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

View File

@ -24,13 +24,13 @@ final $typed_data.Uint8List exportTypeDescriptor = $convert.base64Decode('CgpFeH
const ExportPayload$json = const {
'1': 'ExportPayload',
'2': const [
const {'1': 'doc_id', '3': 1, '4': 1, '5': 9, '10': 'docId'},
const {'1': 'view_id', '3': 1, '4': 1, '5': 9, '10': 'viewId'},
const {'1': 'export_type', '3': 2, '4': 1, '5': 14, '6': '.ExportType', '10': 'exportType'},
],
};
/// Descriptor for `ExportPayload`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List exportPayloadDescriptor = $convert.base64Decode('Cg1FeHBvcnRQYXlsb2FkEhUKBmRvY19pZBgBIAEoCVIFZG9jSWQSLAoLZXhwb3J0X3R5cGUYAiABKA4yCy5FeHBvcnRUeXBlUgpleHBvcnRUeXBl');
final $typed_data.Uint8List exportPayloadDescriptor = $convert.base64Decode('Cg1FeHBvcnRQYXlsb2FkEhcKB3ZpZXdfaWQYASABKAlSBnZpZXdJZBIsCgtleHBvcnRfdHlwZRgCIAEoDjILLkV4cG9ydFR5cGVSCmV4cG9ydFR5cGU=');
@$core.Deprecated('Use exportDataDescriptor instead')
const ExportData$json = const {
'1': 'ExportData',

View File

@ -20,11 +20,14 @@ class View extends $pb.GeneratedMessage {
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongToId')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
..e<ViewType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewType', $pb.PbFieldType.OE, defaultOrMaker: ViewType.Blank, valueOf: ViewType.valueOf, enumValues: ViewType.values)
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.RichText, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'version')
..aOM<RepeatedView>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongings', subBuilder: RepeatedView.create)
..aInt64(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
..aInt64(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createTime')
..aOS(10, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
..aOS(11, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
..a<$core.int>(12, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pluginType', $pb.PbFieldType.O3)
..hasRequiredFields = false
;
@ -34,11 +37,14 @@ class View extends $pb.GeneratedMessage {
$core.String? belongToId,
$core.String? name,
$core.String? desc,
ViewType? viewType,
ViewDataType? dataType,
$fixnum.Int64? version,
RepeatedView? belongings,
$fixnum.Int64? modifiedTime,
$fixnum.Int64? createTime,
$core.String? extData,
$core.String? thumbnail,
$core.int? pluginType,
}) {
final _result = create();
if (id != null) {
@ -53,8 +59,8 @@ class View extends $pb.GeneratedMessage {
if (desc != null) {
_result.desc = desc;
}
if (viewType != null) {
_result.viewType = viewType;
if (dataType != null) {
_result.dataType = dataType;
}
if (version != null) {
_result.version = version;
@ -68,6 +74,15 @@ class View extends $pb.GeneratedMessage {
if (createTime != null) {
_result.createTime = createTime;
}
if (extData != null) {
_result.extData = extData;
}
if (thumbnail != null) {
_result.thumbnail = thumbnail;
}
if (pluginType != null) {
_result.pluginType = pluginType;
}
return _result;
}
factory View.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@ -128,13 +143,13 @@ class View extends $pb.GeneratedMessage {
void clearDesc() => clearField(4);
@$pb.TagNumber(5)
ViewType get viewType => $_getN(4);
ViewDataType get dataType => $_getN(4);
@$pb.TagNumber(5)
set viewType(ViewType v) { setField(5, v); }
set dataType(ViewDataType v) { setField(5, v); }
@$pb.TagNumber(5)
$core.bool hasViewType() => $_has(4);
$core.bool hasDataType() => $_has(4);
@$pb.TagNumber(5)
void clearViewType() => clearField(5);
void clearDataType() => clearField(5);
@$pb.TagNumber(6)
$fixnum.Int64 get version => $_getI64(5);
@ -173,6 +188,33 @@ class View extends $pb.GeneratedMessage {
$core.bool hasCreateTime() => $_has(8);
@$pb.TagNumber(9)
void clearCreateTime() => clearField(9);
@$pb.TagNumber(10)
$core.String get extData => $_getSZ(9);
@$pb.TagNumber(10)
set extData($core.String v) { $_setString(9, v); }
@$pb.TagNumber(10)
$core.bool hasExtData() => $_has(9);
@$pb.TagNumber(10)
void clearExtData() => clearField(10);
@$pb.TagNumber(11)
$core.String get thumbnail => $_getSZ(10);
@$pb.TagNumber(11)
set thumbnail($core.String v) { $_setString(10, v); }
@$pb.TagNumber(11)
$core.bool hasThumbnail() => $_has(10);
@$pb.TagNumber(11)
void clearThumbnail() => clearField(11);
@$pb.TagNumber(12)
$core.int get pluginType => $_getIZ(11);
@$pb.TagNumber(12)
set pluginType($core.int v) { $_setSignedInt32(11, v); }
@$pb.TagNumber(12)
$core.bool hasPluginType() => $_has(11);
@$pb.TagNumber(12)
void clearPluginType() => clearField(12);
}
class RepeatedView extends $pb.GeneratedMessage {
@ -232,7 +274,9 @@ class CreateViewPayload extends $pb.GeneratedMessage {
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
..e<ViewType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewType', $pb.PbFieldType.OE, defaultOrMaker: ViewType.Blank, valueOf: ViewType.valueOf, enumValues: ViewType.values)
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.RichText, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pluginType', $pb.PbFieldType.O3)
..hasRequiredFields = false
;
@ -242,7 +286,9 @@ class CreateViewPayload extends $pb.GeneratedMessage {
$core.String? name,
$core.String? desc,
$core.String? thumbnail,
ViewType? viewType,
ViewDataType? dataType,
$core.String? extData,
$core.int? pluginType,
}) {
final _result = create();
if (belongToId != null) {
@ -257,8 +303,14 @@ class CreateViewPayload extends $pb.GeneratedMessage {
if (thumbnail != null) {
_result.thumbnail = thumbnail;
}
if (viewType != null) {
_result.viewType = viewType;
if (dataType != null) {
_result.dataType = dataType;
}
if (extData != null) {
_result.extData = extData;
}
if (pluginType != null) {
_result.pluginType = pluginType;
}
return _result;
}
@ -323,13 +375,31 @@ class CreateViewPayload extends $pb.GeneratedMessage {
void clearThumbnail() => clearField(4);
@$pb.TagNumber(5)
ViewType get viewType => $_getN(4);
ViewDataType get dataType => $_getN(4);
@$pb.TagNumber(5)
set viewType(ViewType v) { setField(5, v); }
set dataType(ViewDataType v) { setField(5, v); }
@$pb.TagNumber(5)
$core.bool hasViewType() => $_has(4);
$core.bool hasDataType() => $_has(4);
@$pb.TagNumber(5)
void clearViewType() => clearField(5);
void clearDataType() => clearField(5);
@$pb.TagNumber(6)
$core.String get extData => $_getSZ(5);
@$pb.TagNumber(6)
set extData($core.String v) { $_setString(5, v); }
@$pb.TagNumber(6)
$core.bool hasExtData() => $_has(5);
@$pb.TagNumber(6)
void clearExtData() => clearField(6);
@$pb.TagNumber(7)
$core.int get pluginType => $_getIZ(6);
@$pb.TagNumber(7)
set pluginType($core.int v) { $_setSignedInt32(6, v); }
@$pb.TagNumber(7)
$core.bool hasPluginType() => $_has(6);
@$pb.TagNumber(7)
void clearPluginType() => clearField(7);
}
class CreateViewParams extends $pb.GeneratedMessage {
@ -338,9 +408,11 @@ class CreateViewParams extends $pb.GeneratedMessage {
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
..e<ViewType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewType', $pb.PbFieldType.OE, defaultOrMaker: ViewType.Blank, valueOf: ViewType.valueOf, enumValues: ViewType.values)
..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewData')
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.RichText, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
..aOS(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewId')
..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
..a<$core.int>(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pluginType', $pb.PbFieldType.O3)
..hasRequiredFields = false
;
@ -350,9 +422,11 @@ class CreateViewParams extends $pb.GeneratedMessage {
$core.String? name,
$core.String? desc,
$core.String? thumbnail,
ViewType? viewType,
$core.String? viewData,
ViewDataType? dataType,
$core.String? extData,
$core.String? viewId,
$core.String? data,
$core.int? pluginType,
}) {
final _result = create();
if (belongToId != null) {
@ -367,15 +441,21 @@ class CreateViewParams extends $pb.GeneratedMessage {
if (thumbnail != null) {
_result.thumbnail = thumbnail;
}
if (viewType != null) {
_result.viewType = viewType;
if (dataType != null) {
_result.dataType = dataType;
}
if (viewData != null) {
_result.viewData = viewData;
if (extData != null) {
_result.extData = extData;
}
if (viewId != null) {
_result.viewId = viewId;
}
if (data != null) {
_result.data = data;
}
if (pluginType != null) {
_result.pluginType = pluginType;
}
return _result;
}
factory CreateViewParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@ -436,22 +516,22 @@ class CreateViewParams extends $pb.GeneratedMessage {
void clearThumbnail() => clearField(4);
@$pb.TagNumber(5)
ViewType get viewType => $_getN(4);
ViewDataType get dataType => $_getN(4);
@$pb.TagNumber(5)
set viewType(ViewType v) { setField(5, v); }
set dataType(ViewDataType v) { setField(5, v); }
@$pb.TagNumber(5)
$core.bool hasViewType() => $_has(4);
$core.bool hasDataType() => $_has(4);
@$pb.TagNumber(5)
void clearViewType() => clearField(5);
void clearDataType() => clearField(5);
@$pb.TagNumber(6)
$core.String get viewData => $_getSZ(5);
$core.String get extData => $_getSZ(5);
@$pb.TagNumber(6)
set viewData($core.String v) { $_setString(5, v); }
set extData($core.String v) { $_setString(5, v); }
@$pb.TagNumber(6)
$core.bool hasViewData() => $_has(5);
$core.bool hasExtData() => $_has(5);
@$pb.TagNumber(6)
void clearViewData() => clearField(6);
void clearExtData() => clearField(6);
@$pb.TagNumber(7)
$core.String get viewId => $_getSZ(6);
@ -461,6 +541,24 @@ class CreateViewParams extends $pb.GeneratedMessage {
$core.bool hasViewId() => $_has(6);
@$pb.TagNumber(7)
void clearViewId() => clearField(7);
@$pb.TagNumber(8)
$core.String get data => $_getSZ(7);
@$pb.TagNumber(8)
set data($core.String v) { $_setString(7, v); }
@$pb.TagNumber(8)
$core.bool hasData() => $_has(7);
@$pb.TagNumber(8)
void clearData() => clearField(8);
@$pb.TagNumber(9)
$core.int get pluginType => $_getIZ(8);
@$pb.TagNumber(9)
set pluginType($core.int v) { $_setSignedInt32(8, v); }
@$pb.TagNumber(9)
$core.bool hasPluginType() => $_has(8);
@$pb.TagNumber(9)
void clearPluginType() => clearField(9);
}
class ViewId extends $pb.GeneratedMessage {

View File

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

View File

@ -8,17 +8,17 @@
import 'dart:core' as $core;
import 'dart:convert' as $convert;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use viewTypeDescriptor instead')
const ViewType$json = const {
'1': 'ViewType',
@$core.Deprecated('Use viewDataTypeDescriptor instead')
const ViewDataType$json = const {
'1': 'ViewDataType',
'2': const [
const {'1': 'Blank', '2': 0},
const {'1': 'Doc', '2': 1},
const {'1': 'RichText', '2': 0},
const {'1': 'PlainText', '2': 1},
],
};
/// Descriptor for `ViewType`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List viewTypeDescriptor = $convert.base64Decode('CghWaWV3VHlwZRIJCgVCbGFuaxAAEgcKA0RvYxAB');
/// Descriptor for `ViewDataType`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List viewDataTypeDescriptor = $convert.base64Decode('CgxWaWV3RGF0YVR5cGUSDAoIUmljaFRleHQQABINCglQbGFpblRleHQQAQ==');
@$core.Deprecated('Use viewDescriptor instead')
const View$json = const {
'1': 'View',
@ -27,16 +27,19 @@ const View$json = const {
const {'1': 'belong_to_id', '3': 2, '4': 1, '5': 9, '10': 'belongToId'},
const {'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'},
const {'1': 'desc', '3': 4, '4': 1, '5': 9, '10': 'desc'},
const {'1': 'view_type', '3': 5, '4': 1, '5': 14, '6': '.ViewType', '10': 'viewType'},
const {'1': 'data_type', '3': 5, '4': 1, '5': 14, '6': '.ViewDataType', '10': 'dataType'},
const {'1': 'version', '3': 6, '4': 1, '5': 3, '10': 'version'},
const {'1': 'belongings', '3': 7, '4': 1, '5': 11, '6': '.RepeatedView', '10': 'belongings'},
const {'1': 'modified_time', '3': 8, '4': 1, '5': 3, '10': 'modifiedTime'},
const {'1': 'create_time', '3': 9, '4': 1, '5': 3, '10': 'createTime'},
const {'1': 'ext_data', '3': 10, '4': 1, '5': 9, '10': 'extData'},
const {'1': 'thumbnail', '3': 11, '4': 1, '5': 9, '10': 'thumbnail'},
const {'1': 'plugin_type', '3': 12, '4': 1, '5': 5, '10': 'pluginType'},
],
};
/// Descriptor for `View`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List viewDescriptor = $convert.base64Decode('CgRWaWV3Eg4KAmlkGAEgASgJUgJpZBIgCgxiZWxvbmdfdG9faWQYAiABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNjEiYKCXZpZXdfdHlwZRgFIAEoDjIJLlZpZXdUeXBlUgh2aWV3VHlwZRIYCgd2ZXJzaW9uGAYgASgDUgd2ZXJzaW9uEi0KCmJlbG9uZ2luZ3MYByABKAsyDS5SZXBlYXRlZFZpZXdSCmJlbG9uZ2luZ3MSIwoNbW9kaWZpZWRfdGltZRgIIAEoA1IMbW9kaWZpZWRUaW1lEh8KC2NyZWF0ZV90aW1lGAkgASgDUgpjcmVhdGVUaW1l');
final $typed_data.Uint8List viewDescriptor = $convert.base64Decode('CgRWaWV3Eg4KAmlkGAEgASgJUgJpZBIgCgxiZWxvbmdfdG9faWQYAiABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNjEioKCWRhdGFfdHlwZRgFIAEoDjINLlZpZXdEYXRhVHlwZVIIZGF0YVR5cGUSGAoHdmVyc2lvbhgGIAEoA1IHdmVyc2lvbhItCgpiZWxvbmdpbmdzGAcgASgLMg0uUmVwZWF0ZWRWaWV3UgpiZWxvbmdpbmdzEiMKDW1vZGlmaWVkX3RpbWUYCCABKANSDG1vZGlmaWVkVGltZRIfCgtjcmVhdGVfdGltZRgJIAEoA1IKY3JlYXRlVGltZRIZCghleHRfZGF0YRgKIAEoCVIHZXh0RGF0YRIcCgl0aHVtYm5haWwYCyABKAlSCXRodW1ibmFpbBIfCgtwbHVnaW5fdHlwZRgMIAEoBVIKcGx1Z2luVHlwZQ==');
@$core.Deprecated('Use repeatedViewDescriptor instead')
const RepeatedView$json = const {
'1': 'RepeatedView',
@ -55,7 +58,9 @@ const CreateViewPayload$json = const {
const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
const {'1': 'thumbnail', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'thumbnail'},
const {'1': 'view_type', '3': 5, '4': 1, '5': 14, '6': '.ViewType', '10': 'viewType'},
const {'1': 'data_type', '3': 5, '4': 1, '5': 14, '6': '.ViewDataType', '10': 'dataType'},
const {'1': 'ext_data', '3': 6, '4': 1, '5': 9, '10': 'extData'},
const {'1': 'plugin_type', '3': 7, '4': 1, '5': 5, '10': 'pluginType'},
],
'8': const [
const {'1': 'one_of_thumbnail'},
@ -63,7 +68,7 @@ const CreateViewPayload$json = const {
};
/// Descriptor for `CreateViewPayload`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List createViewPayloadDescriptor = $convert.base64Decode('ChFDcmVhdGVWaWV3UGF5bG9hZBIgCgxiZWxvbmdfdG9faWQYASABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEh4KCXRodW1ibmFpbBgEIAEoCUgAUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBlQhIKEG9uZV9vZl90aHVtYm5haWw=');
final $typed_data.Uint8List createViewPayloadDescriptor = $convert.base64Decode('ChFDcmVhdGVWaWV3UGF5bG9hZBIgCgxiZWxvbmdfdG9faWQYASABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEh4KCXRodW1ibmFpbBgEIAEoCUgAUgl0aHVtYm5haWwSKgoJZGF0YV90eXBlGAUgASgOMg0uVmlld0RhdGFUeXBlUghkYXRhVHlwZRIZCghleHRfZGF0YRgGIAEoCVIHZXh0RGF0YRIfCgtwbHVnaW5fdHlwZRgHIAEoBVIKcGx1Z2luVHlwZUISChBvbmVfb2ZfdGh1bWJuYWls');
@$core.Deprecated('Use createViewParamsDescriptor instead')
const CreateViewParams$json = const {
'1': 'CreateViewParams',
@ -72,14 +77,16 @@ const CreateViewParams$json = const {
const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
const {'1': 'thumbnail', '3': 4, '4': 1, '5': 9, '10': 'thumbnail'},
const {'1': 'view_type', '3': 5, '4': 1, '5': 14, '6': '.ViewType', '10': 'viewType'},
const {'1': 'view_data', '3': 6, '4': 1, '5': 9, '10': 'viewData'},
const {'1': 'data_type', '3': 5, '4': 1, '5': 14, '6': '.ViewDataType', '10': 'dataType'},
const {'1': 'ext_data', '3': 6, '4': 1, '5': 9, '10': 'extData'},
const {'1': 'view_id', '3': 7, '4': 1, '5': 9, '10': 'viewId'},
const {'1': 'data', '3': 8, '4': 1, '5': 9, '10': 'data'},
const {'1': 'plugin_type', '3': 9, '4': 1, '5': 5, '10': 'pluginType'},
],
};
/// Descriptor for `CreateViewParams`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBlEhsKCXZpZXdfZGF0YRgGIAEoCVIIdmlld0RhdGESFwoHdmlld19pZBgHIAEoCVIGdmlld0lk');
final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSKgoJZGF0YV90eXBlGAUgASgOMg0uVmlld0RhdGFUeXBlUghkYXRhVHlwZRIZCghleHRfZGF0YRgGIAEoCVIHZXh0RGF0YRIXCgd2aWV3X2lkGAcgASgJUgZ2aWV3SWQSEgoEZGF0YRgIIAEoCVIEZGF0YRIfCgtwbHVnaW5fdHlwZRgJIAEoBVIKcGx1Z2luVHlwZQ==');
@$core.Deprecated('Use viewIdDescriptor instead')
const ViewId$json = const {
'1': 'ViewId',

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
pub mod editor;
pub mod block_editor;
pub mod manager;
mod queue;
mod web_socket;
@ -11,13 +11,13 @@ pub mod errors {
pub const DOCUMENT_SYNC_INTERVAL_IN_MILLIS: u64 = 1000;
use crate::errors::FlowyError;
use flowy_collaboration::entities::document_info::{CreateDocParams, DocumentId, DocumentInfo, ResetDocumentParams};
use flowy_collaboration::entities::document_info::{BlockId, BlockInfo, CreateBlockParams, ResetDocumentParams};
use lib_infra::future::FutureResult;
pub trait DocumentCloudService: Send + Sync {
fn create_document(&self, token: &str, params: CreateDocParams) -> FutureResult<(), FlowyError>;
pub trait BlockCloudService: Send + Sync {
fn create_block(&self, token: &str, params: CreateBlockParams) -> FutureResult<(), FlowyError>;
fn read_document(&self, token: &str, params: DocumentId) -> FutureResult<Option<DocumentInfo>, FlowyError>;
fn read_block(&self, token: &str, params: BlockId) -> FutureResult<Option<BlockInfo>, FlowyError>;
fn update_document(&self, token: &str, params: ResetDocumentParams) -> FutureResult<(), FlowyError>;
fn update_block(&self, token: &str, params: ResetDocumentParams) -> FutureResult<(), FlowyError>;
}

View File

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

View File

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

View File

@ -1,17 +1,17 @@
use crate::{queue::EditorCommand, DocumentWSReceiver, DOCUMENT_SYNC_INTERVAL_IN_MILLIS};
use async_trait::async_trait;
use crate::{queue::EditorCommand, DOCUMENT_SYNC_INTERVAL_IN_MILLIS};
use bytes::Bytes;
use flowy_collaboration::{
entities::{
revision::RevisionRange,
ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSData, ServerRevisionWSDataType},
ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSDataType},
},
errors::CollaborateResult,
};
use flowy_error::{internal_error, FlowyError};
use flowy_sync::*;
use lib_infra::future::{BoxResultFuture, FutureResult};
use lib_ot::{core::Delta, rich_text::RichTextAttributes};
use lib_ot::rich_text::RichTextAttributes;
use lib_ot::rich_text::RichTextDelta;
use lib_ws::WSConnectState;
use std::{sync::Arc, time::Duration};
use tokio::sync::{
@ -23,33 +23,26 @@ use tokio::sync::{
pub(crate) type EditorCommandSender = Sender<EditorCommand>;
pub(crate) type EditorCommandReceiver = Receiver<EditorCommand>;
pub(crate) async fn make_document_ws_manager(
pub(crate) async fn make_block_ws_manager(
doc_id: String,
user_id: String,
edit_cmd_tx: EditorCommandSender,
rev_manager: Arc<RevisionManager>,
rev_web_socket: Arc<dyn RevisionWebSocket>,
) -> Arc<RevisionWebSocketManager> {
let composite_sink_provider = Arc::new(CompositeWSSinkDataProvider::new(&doc_id, rev_manager.clone()));
let resolve_target = Arc::new(DocumentRevisionResolveTarget { edit_cmd_tx });
let resolver = RevisionConflictResolver::<RichTextAttributes>::new(
&user_id,
resolve_target,
Arc::new(composite_sink_provider.clone()),
rev_manager,
);
let ws_stream_consumer = Arc::new(DocumentWSSteamConsumerAdapter {
resolver: Arc::new(resolver),
});
let sink_provider = Arc::new(DocumentWSSinkDataProviderAdapter(composite_sink_provider));
let ws_data_provider = Arc::new(WSDataProvider::new(&doc_id, Arc::new(rev_manager.clone())));
let resolver = Arc::new(BlockConflictResolver { edit_cmd_tx });
let conflict_controller =
RichTextConflictController::new(&user_id, resolver, Arc::new(ws_data_provider.clone()), rev_manager);
let ws_data_stream = Arc::new(BlockRevisionWSDataStream::new(conflict_controller));
let ws_data_sink = Arc::new(BlockWSDataSink(ws_data_provider));
let ping_duration = Duration::from_millis(DOCUMENT_SYNC_INTERVAL_IN_MILLIS);
let ws_manager = Arc::new(RevisionWebSocketManager::new(
"Document",
"Block",
&doc_id,
rev_web_socket,
sink_provider,
ws_stream_consumer,
ws_data_sink,
ws_data_stream,
ping_duration,
));
listen_document_ws_state(&user_id, &doc_id, ws_manager.scribe_state());
@ -69,18 +62,26 @@ fn listen_document_ws_state(_user_id: &str, _doc_id: &str, mut subscriber: broad
});
}
pub(crate) struct DocumentWSSteamConsumerAdapter {
resolver: Arc<RevisionConflictResolver<RichTextAttributes>>,
pub(crate) struct BlockRevisionWSDataStream {
conflict_controller: Arc<RichTextConflictController>,
}
impl RevisionWSSteamConsumer for DocumentWSSteamConsumerAdapter {
impl BlockRevisionWSDataStream {
pub fn new(conflict_controller: RichTextConflictController) -> Self {
Self {
conflict_controller: Arc::new(conflict_controller),
}
}
}
impl RevisionWSDataStream for BlockRevisionWSDataStream {
fn receive_push_revision(&self, bytes: Bytes) -> BoxResultFuture<(), FlowyError> {
let resolver = self.resolver.clone();
let resolver = self.conflict_controller.clone();
Box::pin(async move { resolver.receive_bytes(bytes).await })
}
fn receive_ack(&self, id: String, ty: ServerRevisionWSDataType) -> BoxResultFuture<(), FlowyError> {
let resolver = self.resolver.clone();
let resolver = self.conflict_controller.clone();
Box::pin(async move { resolver.ack_revision(id, ty).await })
}
@ -90,25 +91,25 @@ impl RevisionWSSteamConsumer for DocumentWSSteamConsumerAdapter {
}
fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError> {
let resolver = self.resolver.clone();
let resolver = self.conflict_controller.clone();
Box::pin(async move { resolver.send_revisions(range).await })
}
}
pub(crate) struct DocumentWSSinkDataProviderAdapter(pub(crate) Arc<CompositeWSSinkDataProvider>);
impl RevisionWSSinkDataProvider for DocumentWSSinkDataProviderAdapter {
pub(crate) struct BlockWSDataSink(pub(crate) Arc<WSDataProvider>);
impl RevisionWSDataIterator for BlockWSDataSink {
fn next(&self) -> FutureResult<Option<ClientRevisionWSData>, FlowyError> {
let sink_provider = self.0.clone();
FutureResult::new(async move { sink_provider.next().await })
}
}
struct DocumentRevisionResolveTarget {
struct BlockConflictResolver {
edit_cmd_tx: EditorCommandSender,
}
impl ResolverTarget<RichTextAttributes> for DocumentRevisionResolveTarget {
fn compose_delta(&self, delta: Delta<RichTextAttributes>) -> BoxResultFuture<DeltaMD5, FlowyError> {
impl ConflictResolver<RichTextAttributes> for BlockConflictResolver {
fn compose_delta(&self, delta: RichTextDelta) -> BoxResultFuture<DeltaMD5, FlowyError> {
let tx = self.edit_cmd_tx.clone();
Box::pin(async move {
let (ret, rx) = oneshot::channel();
@ -127,11 +128,11 @@ impl ResolverTarget<RichTextAttributes> for DocumentRevisionResolveTarget {
fn transform_delta(
&self,
delta: Delta<RichTextAttributes>,
) -> BoxResultFuture<flowy_sync::TransformDeltas<RichTextAttributes>, FlowyError> {
delta: RichTextDelta,
) -> BoxResultFuture<flowy_sync::RichTextTransformDeltas, FlowyError> {
let tx = self.edit_cmd_tx.clone();
Box::pin(async move {
let (ret, rx) = oneshot::channel::<CollaborateResult<TransformDeltas<RichTextAttributes>>>();
let (ret, rx) = oneshot::channel::<CollaborateResult<RichTextTransformDeltas>>();
tx.send(EditorCommand::TransformDelta { delta, ret })
.await
.map_err(internal_error)?;
@ -142,7 +143,7 @@ impl ResolverTarget<RichTextAttributes> for DocumentRevisionResolveTarget {
})
}
fn reset_delta(&self, delta: Delta<RichTextAttributes>) -> BoxResultFuture<DeltaMD5, FlowyError> {
fn reset_delta(&self, delta: RichTextDelta) -> BoxResultFuture<DeltaMD5, FlowyError> {
let tx = self.edit_cmd_tx.clone();
Box::pin(async move {
let (ret, rx) = oneshot::channel();
@ -157,24 +158,3 @@ impl ResolverTarget<RichTextAttributes> for DocumentRevisionResolveTarget {
})
}
}
// RevisionWebSocketManager registers itself as a DocumentWSReceiver for each
// opened document.
#[async_trait]
impl DocumentWSReceiver for RevisionWebSocketManager {
#[tracing::instrument(level = "debug", skip(self, data), err)]
async fn receive_ws_data(&self, data: ServerRevisionWSData) -> Result<(), FlowyError> {
let _ = self.ws_passthrough_tx.send(data).await.map_err(|e| {
let err_msg = format!("{} passthrough error: {}", self.object_id, e);
FlowyError::internal().context(err_msg)
})?;
Ok(())
}
fn connect_state_changed(&self, state: WSConnectState) {
match self.state_passthrough_tx.send(state) {
Ok(_) => {}
Err(e) => tracing::error!("{}", e),
}
}
}

View File

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

View File

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

View File

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

View File

@ -92,7 +92,7 @@ fn delta_deserialize_null_test() {
attribute.value = RichTextAttributeValue(None);
let delta2 = DeltaBuilder::new().retain_with_attributes(7, attribute.into()).build();
assert_eq!(delta2.to_json(), r#"[{"retain":7,"attributes":{"bold":""}}]"#);
assert_eq!(delta2.to_delta_json(), r#"[{"retain":7,"attributes":{"bold":""}}]"#);
assert_eq!(delta1, delta2);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
use crate::controller::FolderId;
use crate::{
event_map::WorkspaceDatabase,
services::persistence::{AppTableSql, TrashTableSql, ViewTableSql, WorkspaceTableSql},
@ -10,9 +11,11 @@ use flowy_folder_data_model::entities::{
view::{RepeatedView, View},
workspace::Workspace,
};
use flowy_sync::{RevisionLoader, RevisionPersistence};
use std::sync::Arc;
pub(crate) const V1_MIGRATION: &str = "FOLDER_V1_MIGRATION";
const V1_MIGRATION: &str = "FOLDER_V1_MIGRATION";
const V2_MIGRATION: &str = "FOLDER_V2_MIGRATION";
pub(crate) struct FolderMigration {
user_id: String,
@ -32,7 +35,7 @@ impl FolderMigration {
if KV::get_bool(&key) {
return Ok(None);
}
tracing::trace!("Run folder version 1 migrations");
let pool = self.database.db_pool()?;
let conn = &*pool.get()?;
let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| {
@ -62,6 +65,7 @@ impl FolderMigration {
})?;
if workspaces.is_empty() {
tracing::trace!("Run folder v1 migration, but workspace is empty");
KV::set_bool(&key, true);
return Ok(None);
}
@ -73,6 +77,35 @@ impl FolderMigration {
let folder = FolderPad::new(workspaces, trash)?;
KV::set_bool(&key, true);
tracing::trace!("Run folder v1 migration");
Ok(Some(folder))
}
pub async fn run_v2_migration(&self, user_id: &str, folder_id: &FolderId) -> FlowyResult<Option<FolderPad>> {
let key = md5(format!("{}{}", self.user_id, V2_MIGRATION));
if KV::get_bool(&key) {
return Ok(None);
}
let pool = self.database.db_pool()?;
let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), pool.clone()));
let (revisions, _) = RevisionLoader {
object_id: folder_id.as_ref().to_owned(),
user_id: self.user_id.clone(),
cloud: None,
rev_persistence,
}
.load()
.await?;
if revisions.is_empty() {
tracing::trace!("Run folder v2 migration, but revision is empty");
KV::set_bool(&key, true);
return Ok(None);
}
let pad = FolderPad::from_revisions(revisions)?;
KV::set_bool(&key, true);
tracing::trace!("Run folder v2 migration");
Ok(Some(pad))
}
}

View File

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

View File

@ -1,7 +1,7 @@
use crate::{
entities::{
trash::{Trash, TrashType},
view::{RepeatedView, UpdateViewParams, View, ViewType},
view::{RepeatedView, UpdateViewParams, View, ViewDataType},
},
errors::FlowyError,
services::persistence::version_1::app_sql::AppTable,
@ -65,49 +65,6 @@ impl ViewTableSql {
}
}
// pub(crate) fn read_views(
// belong_to_id: &str,
// is_trash: Option<bool>,
// conn: &SqliteConnection,
// ) -> Result<RepeatedView, FlowyError> {
// let views = dsl::view_table
// .inner_join(trash_table::dsl::trash_table.on(trash_id.ne(view_table::
// id))) .filter(view_table::belong_to_id.eq(belong_to_id))
// .select((
// view_table::id,
// view_table::belong_to_id,
// view_table::name,
// view_table::desc,
// view_table::modified_time,
// view_table::create_time,
// view_table::thumbnail,
// view_table::view_type,
// view_table::version,
// ))
// .load(conn)?
// .into_iter()
// .map(
// |(id, belong_to_id, name, desc, create_time, modified_time,
// thumbnail, view_type, version)| { ViewTable {
// id,
// belong_to_id,
// name,
// desc,
// modified_time,
// create_time,
// thumbnail,
// view_type,
// version,
// is_trash: false,
// }
// .into()
// },
// )
// .collect::<Vec<View>>();
//
// Ok(RepeatedView { items: views })
// }
#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
#[belongs_to(AppTable, foreign_key = "belong_to_id")]
#[table_name = "view_table"]
@ -119,16 +76,16 @@ pub(crate) struct ViewTable {
pub modified_time: i64,
pub create_time: i64,
pub thumbnail: String,
pub view_type: ViewTableType,
pub view_type: SqlViewDataType,
pub version: i64,
pub is_trash: bool,
}
impl ViewTable {
pub fn new(view: View) -> Self {
let view_type = match view.view_type {
ViewType::Blank => ViewTableType::Docs,
ViewType::Doc => ViewTableType::Docs,
let data_type = match view.data_type {
ViewDataType::RichText => SqlViewDataType::RichText,
ViewDataType::PlainText => SqlViewDataType::PlainText,
};
ViewTable {
@ -138,9 +95,8 @@ impl ViewTable {
desc: view.desc,
modified_time: view.modified_time,
create_time: view.create_time,
// TODO: thumbnail
thumbnail: "".to_owned(),
view_type,
thumbnail: view.thumbnail,
view_type: data_type,
version: 0,
is_trash: false,
}
@ -149,8 +105,9 @@ impl ViewTable {
impl std::convert::From<ViewTable> for View {
fn from(table: ViewTable) -> Self {
let view_type = match table.view_type {
ViewTableType::Docs => ViewType::Doc,
let data_type = match table.view_type {
SqlViewDataType::RichText => ViewDataType::RichText,
SqlViewDataType::PlainText => ViewDataType::PlainText,
};
View {
@ -158,11 +115,16 @@ impl std::convert::From<ViewTable> for View {
belong_to_id: table.belong_to_id,
name: table.name,
desc: table.desc,
view_type,
data_type,
belongings: RepeatedView::default(),
modified_time: table.modified_time,
version: table.version,
create_time: table.create_time,
ext_data: "".to_string(),
thumbnail: table.thumbnail,
// Store the view in ViewTable was deprecated since v0.0.2.
// No need worry about plugin_type.
plugin_type: 0,
}
}
}
@ -214,32 +176,34 @@ impl ViewChangeset {
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)]
#[repr(i32)]
#[sql_type = "Integer"]
pub enum ViewTableType {
Docs = 0,
pub enum SqlViewDataType {
RichText = 0,
PlainText = 1,
}
impl std::default::Default for ViewTableType {
impl std::default::Default for SqlViewDataType {
fn default() -> Self {
ViewTableType::Docs
SqlViewDataType::RichText
}
}
impl std::convert::From<i32> for ViewTableType {
impl std::convert::From<i32> for SqlViewDataType {
fn from(value: i32) -> Self {
match value {
0 => ViewTableType::Docs,
0 => SqlViewDataType::RichText,
1 => SqlViewDataType::PlainText,
o => {
log::error!("Unsupported view type {}, fallback to ViewType::Docs", o);
ViewTableType::Docs
SqlViewDataType::PlainText
}
}
}
}
impl ViewTableType {
impl SqlViewDataType {
pub fn value(&self) -> i32 {
*self as i32
}
}
impl_sql_integer_expression!(ViewTableType);
impl_sql_integer_expression!(SqlViewDataType);

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More