From 538ab20c5f917778019feee8843e853af4865b87 Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 20 Oct 2021 22:19:01 +0800 Subject: [PATCH] [flutter]: config document delta data flow --- .../workspace/application/doc/doc_bloc.dart | 67 +++++---- .../application/doc/doc_bloc.freezed.dart | 62 +++------ .../workspace/application/view/view_bloc.dart | 54 +++++--- .../application/view/view_bloc.freezed.dart | 128 ++++++++++++++++++ app_flowy/lib/workspace/domain/i_doc.dart | 49 ------- app_flowy/lib/workspace/domain/i_view.dart | 2 + app_flowy/lib/workspace/domain/view_edit.dart | 19 ++- .../workspace/infrastructure/i_view_impl.dart | 5 + .../infrastructure/repos/view_repo.dart | 5 + .../presentation/stack_page/doc/doc_page.dart | 10 +- .../menu/widget/app/section/action.dart | 19 ++- .../widgets/menu/widget/app/section/item.dart | 3 + .../lib/src/flowy_overlay/flowy_overlay.dart | 2 + .../lib/widget/dialog/styled_dialogs.dart | 2 +- .../flowy_sdk/lib/dispatch/code_gen.dart | 65 ++++++--- .../flowy-workspace/event.pbenum.dart | 12 +- .../flowy-workspace/event.pbjson.dart | 10 +- rust-lib/flowy-document/src/module.rs | 10 ++ rust-lib/flowy-workspace/src/event.rs | 16 ++- .../src/handlers/view_handler.rs | 10 ++ rust-lib/flowy-workspace/src/module.rs | 1 + .../src/protobuf/model/event.rs | 115 +++++++++------- .../src/protobuf/proto/event.proto | 8 +- .../src/services/view_controller.rs | 21 +++ 24 files changed, 459 insertions(+), 236 deletions(-) diff --git a/app_flowy/lib/workspace/application/doc/doc_bloc.dart b/app_flowy/lib/workspace/application/doc/doc_bloc.dart index ce065f87a7..9ce0cd0e51 100644 --- a/app_flowy/lib/workspace/application/doc/doc_bloc.dart +++ b/app_flowy/lib/workspace/application/doc/doc_bloc.dart @@ -1,30 +1,31 @@ +import 'dart:convert'; + +import 'package:editor/flutter_quill.dart'; +import 'package:flowy_log/flowy_log.dart'; import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:app_flowy/workspace/domain/i_doc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:dartz/dartz.dart'; - +import 'dart:async'; part 'doc_bloc.freezed.dart'; class DocBloc extends Bloc { final IDoc docManager; + late Document document; + late StreamSubscription _subscription; DocBloc({required this.docManager}) : super(DocState.initial()); @override Stream mapEventToState(DocEvent event) async* { - yield* event.map( - initial: _initial, - ); + yield* event.map(initial: _initial); } @override Future close() async { + await _subscription.cancel(); docManager.closeDoc(); - - await state.doc.fold(() => null, (doc) async { - await doc.close(); - }); return super.close(); } @@ -32,17 +33,16 @@ class DocBloc extends Bloc { final result = await docManager.readDoc(); yield result.fold( (doc) { - final flowyDoc = FlowyDoc(doc: doc, iDocImpl: docManager); - return state.copyWith( - doc: some(flowyDoc), - loadState: DocLoadState.finish(left(flowyDoc)), - ); + document = _decodeJsonToDocument(doc.data); + _subscription = document.changes.listen((event) { + final delta = event.item2; + final documentDelta = document.toDelta(); + _composeDelta(delta, documentDelta); + }); + return state.copyWith(loadState: DocLoadState.finish(left(unit))); }, (err) { - return state.copyWith( - doc: none(), - loadState: DocLoadState.finish(right(err)), - ); + return state.copyWith(loadState: DocLoadState.finish(right(err))); }, ); } @@ -53,11 +53,26 @@ class DocBloc extends Bloc { // return document; // } - // Document _decodeJsonToDocument(String data) { - // final json = jsonDecode(data); - // final document = Document.fromJson(json); - // return document; - // } + void _composeDelta(Delta composedDelta, Delta documentDelta) async { + final json = jsonEncode(composedDelta.toJson()); + Log.debug("Send json: $json"); + final result = await docManager.composeDelta(json: json); + + result.fold((rustDoc) { + // final json = utf8.decode(doc.data); + final rustDelta = Delta.fromJson(jsonDecode(rustDoc.data)); + if (documentDelta != rustDelta) { + Log.error("Receive : $rustDelta"); + Log.error("Expected : $documentDelta"); + } + }, (r) => null); + } + + Document _decodeJsonToDocument(String data) { + final json = jsonDecode(data); + final document = Document.fromJson(json); + return document; + } } @freezed @@ -67,13 +82,15 @@ class DocEvent with _$DocEvent { @freezed class DocState with _$DocState { - const factory DocState({required Option doc, required DocLoadState loadState}) = _DocState; + const factory DocState({ + required DocLoadState loadState, + }) = _DocState; - factory DocState.initial() => DocState(doc: none(), loadState: const _Loading()); + factory DocState.initial() => const DocState(loadState: _Loading()); } @freezed class DocLoadState with _$DocLoadState { const factory DocLoadState.loading() = _Loading; - const factory DocLoadState.finish(Either successOrFail) = _Finish; + const factory DocLoadState.finish(Either successOrFail) = _Finish; } diff --git a/app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart b/app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart index 61e9e32369..4c20ec5ec6 100644 --- a/app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart @@ -148,10 +148,8 @@ abstract class Initial implements DocEvent { class _$DocStateTearOff { const _$DocStateTearOff(); - _DocState call( - {required Option doc, required DocLoadState loadState}) { + _DocState call({required DocLoadState loadState}) { return _DocState( - doc: doc, loadState: loadState, ); } @@ -162,7 +160,6 @@ const $DocState = _$DocStateTearOff(); /// @nodoc mixin _$DocState { - Option get doc => throw _privateConstructorUsedError; DocLoadState get loadState => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -174,7 +171,7 @@ mixin _$DocState { abstract class $DocStateCopyWith<$Res> { factory $DocStateCopyWith(DocState value, $Res Function(DocState) then) = _$DocStateCopyWithImpl<$Res>; - $Res call({Option doc, DocLoadState loadState}); + $Res call({DocLoadState loadState}); $DocLoadStateCopyWith<$Res> get loadState; } @@ -189,14 +186,9 @@ class _$DocStateCopyWithImpl<$Res> implements $DocStateCopyWith<$Res> { @override $Res call({ - Object? doc = freezed, Object? loadState = freezed, }) { return _then(_value.copyWith( - doc: doc == freezed - ? _value.doc - : doc // ignore: cast_nullable_to_non_nullable - as Option, loadState: loadState == freezed ? _value.loadState : loadState // ignore: cast_nullable_to_non_nullable @@ -217,7 +209,7 @@ abstract class _$DocStateCopyWith<$Res> implements $DocStateCopyWith<$Res> { factory _$DocStateCopyWith(_DocState value, $Res Function(_DocState) then) = __$DocStateCopyWithImpl<$Res>; @override - $Res call({Option doc, DocLoadState loadState}); + $Res call({DocLoadState loadState}); @override $DocLoadStateCopyWith<$Res> get loadState; @@ -234,14 +226,9 @@ class __$DocStateCopyWithImpl<$Res> extends _$DocStateCopyWithImpl<$Res> @override $Res call({ - Object? doc = freezed, Object? loadState = freezed, }) { return _then(_DocState( - doc: doc == freezed - ? _value.doc - : doc // ignore: cast_nullable_to_non_nullable - as Option, loadState: loadState == freezed ? _value.loadState : loadState // ignore: cast_nullable_to_non_nullable @@ -253,24 +240,20 @@ class __$DocStateCopyWithImpl<$Res> extends _$DocStateCopyWithImpl<$Res> /// @nodoc class _$_DocState implements _DocState { - const _$_DocState({required this.doc, required this.loadState}); + const _$_DocState({required this.loadState}); - @override - final Option doc; @override final DocLoadState loadState; @override String toString() { - return 'DocState(doc: $doc, loadState: $loadState)'; + return 'DocState(loadState: $loadState)'; } @override bool operator ==(dynamic other) { return identical(this, other) || (other is _DocState && - (identical(other.doc, doc) || - const DeepCollectionEquality().equals(other.doc, doc)) && (identical(other.loadState, loadState) || const DeepCollectionEquality() .equals(other.loadState, loadState))); @@ -278,9 +261,7 @@ class _$_DocState implements _DocState { @override int get hashCode => - runtimeType.hashCode ^ - const DeepCollectionEquality().hash(doc) ^ - const DeepCollectionEquality().hash(loadState); + runtimeType.hashCode ^ const DeepCollectionEquality().hash(loadState); @JsonKey(ignore: true) @override @@ -289,12 +270,8 @@ class _$_DocState implements _DocState { } abstract class _DocState implements DocState { - const factory _DocState( - {required Option doc, - required DocLoadState loadState}) = _$_DocState; + const factory _DocState({required DocLoadState loadState}) = _$_DocState; - @override - Option get doc => throw _privateConstructorUsedError; @override DocLoadState get loadState => throw _privateConstructorUsedError; @override @@ -311,7 +288,7 @@ class _$DocLoadStateTearOff { return const _Loading(); } - _Finish finish(Either successOrFail) { + _Finish finish(Either successOrFail) { return _Finish( successOrFail, ); @@ -326,14 +303,14 @@ mixin _$DocLoadState { @optionalTypeArgs TResult when({ required TResult Function() loading, - required TResult Function(Either successOrFail) + required TResult Function(Either successOrFail) finish, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ TResult Function()? loading, - TResult Function(Either successOrFail)? finish, + TResult Function(Either successOrFail)? finish, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -406,7 +383,7 @@ class _$_Loading implements _Loading { @optionalTypeArgs TResult when({ required TResult Function() loading, - required TResult Function(Either successOrFail) + required TResult Function(Either successOrFail) finish, }) { return loading(); @@ -416,7 +393,7 @@ class _$_Loading implements _Loading { @optionalTypeArgs TResult maybeWhen({ TResult Function()? loading, - TResult Function(Either successOrFail)? finish, + TResult Function(Either successOrFail)? finish, required TResult orElse(), }) { if (loading != null) { @@ -456,7 +433,7 @@ abstract class _Loading implements DocLoadState { abstract class _$FinishCopyWith<$Res> { factory _$FinishCopyWith(_Finish value, $Res Function(_Finish) then) = __$FinishCopyWithImpl<$Res>; - $Res call({Either successOrFail}); + $Res call({Either successOrFail}); } /// @nodoc @@ -476,7 +453,7 @@ class __$FinishCopyWithImpl<$Res> extends _$DocLoadStateCopyWithImpl<$Res> successOrFail == freezed ? _value.successOrFail : successOrFail // ignore: cast_nullable_to_non_nullable - as Either, + as Either, )); } } @@ -487,7 +464,7 @@ class _$_Finish implements _Finish { const _$_Finish(this.successOrFail); @override - final Either successOrFail; + final Either successOrFail; @override String toString() { @@ -516,7 +493,7 @@ class _$_Finish implements _Finish { @optionalTypeArgs TResult when({ required TResult Function() loading, - required TResult Function(Either successOrFail) + required TResult Function(Either successOrFail) finish, }) { return finish(successOrFail); @@ -526,7 +503,7 @@ class _$_Finish implements _Finish { @optionalTypeArgs TResult maybeWhen({ TResult Function()? loading, - TResult Function(Either successOrFail)? finish, + TResult Function(Either successOrFail)? finish, required TResult orElse(), }) { if (finish != null) { @@ -559,10 +536,9 @@ class _$_Finish implements _Finish { } abstract class _Finish implements DocLoadState { - const factory _Finish(Either successOrFail) = - _$_Finish; + const factory _Finish(Either successOrFail) = _$_Finish; - Either get successOrFail => + Either get successOrFail => throw _privateConstructorUsedError; @JsonKey(ignore: true) _$FinishCopyWith<_Finish> get copyWith => throw _privateConstructorUsedError; diff --git a/app_flowy/lib/workspace/application/view/view_bloc.dart b/app_flowy/lib/workspace/application/view/view_bloc.dart index 87089e5ce7..b755ca2788 100644 --- a/app_flowy/lib/workspace/application/view/view_bloc.dart +++ b/app_flowy/lib/workspace/application/view/view_bloc.dart @@ -18,26 +18,39 @@ class ViewBloc extends Bloc { @override Stream mapEventToState(ViewEvent event) async* { - yield* event.map(initial: (e) async* { - listener.start(updatedCallback: (result) => add(ViewEvent.viewDidUpdate(result))); - yield state; - }, setIsEditing: (e) async* { - yield state.copyWith(isEditing: e.isEditing); - }, viewDidUpdate: (e) async* { - yield* _handleViewDidUpdate(e.result); - }, rename: (e) async* { - final result = await viewManager.rename(e.newName); - yield result.fold( - (l) => state.copyWith(successOrFailure: left(unit)), - (error) => state.copyWith(successOrFailure: right(error)), - ); - }, delete: (e) async* { - final result = await viewManager.delete(); - yield result.fold( - (l) => state.copyWith(successOrFailure: left(unit)), - (error) => state.copyWith(successOrFailure: right(error)), - ); - }); + yield* event.map( + initial: (e) async* { + listener.start(updatedCallback: (result) => add(ViewEvent.viewDidUpdate(result))); + yield state; + }, + setIsEditing: (e) async* { + yield state.copyWith(isEditing: e.isEditing); + }, + viewDidUpdate: (e) async* { + yield* _handleViewDidUpdate(e.result); + }, + rename: (e) async* { + final result = await viewManager.rename(e.newName); + yield result.fold( + (l) => state.copyWith(successOrFailure: left(unit)), + (error) => state.copyWith(successOrFailure: right(error)), + ); + }, + delete: (e) async* { + final result = await viewManager.delete(); + yield result.fold( + (l) => state.copyWith(successOrFailure: left(unit)), + (error) => state.copyWith(successOrFailure: right(error)), + ); + }, + duplicate: (e) async* { + final result = await viewManager.duplicate(); + yield result.fold( + (l) => state.copyWith(successOrFailure: left(unit)), + (error) => state.copyWith(successOrFailure: right(error)), + ); + }, + ); } Stream _handleViewDidUpdate(Either result) async* { @@ -60,6 +73,7 @@ class ViewEvent with _$ViewEvent { const factory ViewEvent.setIsEditing(bool isEditing) = SetEditing; const factory ViewEvent.rename(String newName) = Rename; const factory ViewEvent.delete() = Delete; + const factory ViewEvent.duplicate() = Duplicate; const factory ViewEvent.viewDidUpdate(Either result) = ViewDidUpdate; } diff --git a/app_flowy/lib/workspace/application/view/view_bloc.freezed.dart b/app_flowy/lib/workspace/application/view/view_bloc.freezed.dart index 1977c21159..cfa257d1ee 100644 --- a/app_flowy/lib/workspace/application/view/view_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/view/view_bloc.freezed.dart @@ -36,6 +36,10 @@ class _$ViewEventTearOff { return const Delete(); } + Duplicate duplicate() { + return const Duplicate(); + } + ViewDidUpdate viewDidUpdate(Either result) { return ViewDidUpdate( result, @@ -54,6 +58,7 @@ mixin _$ViewEvent { required TResult Function(bool isEditing) setIsEditing, required TResult Function(String newName) rename, required TResult Function() delete, + required TResult Function() duplicate, required TResult Function(Either result) viewDidUpdate, }) => @@ -64,6 +69,7 @@ mixin _$ViewEvent { TResult Function(bool isEditing)? setIsEditing, TResult Function(String newName)? rename, TResult Function()? delete, + TResult Function()? duplicate, TResult Function(Either result)? viewDidUpdate, required TResult orElse(), }) => @@ -74,6 +80,7 @@ mixin _$ViewEvent { required TResult Function(SetEditing value) setIsEditing, required TResult Function(Rename value) rename, required TResult Function(Delete value) delete, + required TResult Function(Duplicate value) duplicate, required TResult Function(ViewDidUpdate value) viewDidUpdate, }) => throw _privateConstructorUsedError; @@ -83,6 +90,7 @@ mixin _$ViewEvent { TResult Function(SetEditing value)? setIsEditing, TResult Function(Rename value)? rename, TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, TResult Function(ViewDidUpdate value)? viewDidUpdate, required TResult orElse(), }) => @@ -145,6 +153,7 @@ class _$Initial implements Initial { required TResult Function(bool isEditing) setIsEditing, required TResult Function(String newName) rename, required TResult Function() delete, + required TResult Function() duplicate, required TResult Function(Either result) viewDidUpdate, }) { @@ -158,6 +167,7 @@ class _$Initial implements Initial { TResult Function(bool isEditing)? setIsEditing, TResult Function(String newName)? rename, TResult Function()? delete, + TResult Function()? duplicate, TResult Function(Either result)? viewDidUpdate, required TResult orElse(), }) { @@ -174,6 +184,7 @@ class _$Initial implements Initial { required TResult Function(SetEditing value) setIsEditing, required TResult Function(Rename value) rename, required TResult Function(Delete value) delete, + required TResult Function(Duplicate value) duplicate, required TResult Function(ViewDidUpdate value) viewDidUpdate, }) { return initial(this); @@ -186,6 +197,7 @@ class _$Initial implements Initial { TResult Function(SetEditing value)? setIsEditing, TResult Function(Rename value)? rename, TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, TResult Function(ViewDidUpdate value)? viewDidUpdate, required TResult orElse(), }) { @@ -268,6 +280,7 @@ class _$SetEditing implements SetEditing { required TResult Function(bool isEditing) setIsEditing, required TResult Function(String newName) rename, required TResult Function() delete, + required TResult Function() duplicate, required TResult Function(Either result) viewDidUpdate, }) { @@ -281,6 +294,7 @@ class _$SetEditing implements SetEditing { TResult Function(bool isEditing)? setIsEditing, TResult Function(String newName)? rename, TResult Function()? delete, + TResult Function()? duplicate, TResult Function(Either result)? viewDidUpdate, required TResult orElse(), }) { @@ -297,6 +311,7 @@ class _$SetEditing implements SetEditing { required TResult Function(SetEditing value) setIsEditing, required TResult Function(Rename value) rename, required TResult Function(Delete value) delete, + required TResult Function(Duplicate value) duplicate, required TResult Function(ViewDidUpdate value) viewDidUpdate, }) { return setIsEditing(this); @@ -309,6 +324,7 @@ class _$SetEditing implements SetEditing { TResult Function(SetEditing value)? setIsEditing, TResult Function(Rename value)? rename, TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, TResult Function(ViewDidUpdate value)? viewDidUpdate, required TResult orElse(), }) { @@ -394,6 +410,7 @@ class _$Rename implements Rename { required TResult Function(bool isEditing) setIsEditing, required TResult Function(String newName) rename, required TResult Function() delete, + required TResult Function() duplicate, required TResult Function(Either result) viewDidUpdate, }) { @@ -407,6 +424,7 @@ class _$Rename implements Rename { TResult Function(bool isEditing)? setIsEditing, TResult Function(String newName)? rename, TResult Function()? delete, + TResult Function()? duplicate, TResult Function(Either result)? viewDidUpdate, required TResult orElse(), }) { @@ -423,6 +441,7 @@ class _$Rename implements Rename { required TResult Function(SetEditing value) setIsEditing, required TResult Function(Rename value) rename, required TResult Function(Delete value) delete, + required TResult Function(Duplicate value) duplicate, required TResult Function(ViewDidUpdate value) viewDidUpdate, }) { return rename(this); @@ -435,6 +454,7 @@ class _$Rename implements Rename { TResult Function(SetEditing value)? setIsEditing, TResult Function(Rename value)? rename, TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, TResult Function(ViewDidUpdate value)? viewDidUpdate, required TResult orElse(), }) { @@ -494,6 +514,7 @@ class _$Delete implements Delete { required TResult Function(bool isEditing) setIsEditing, required TResult Function(String newName) rename, required TResult Function() delete, + required TResult Function() duplicate, required TResult Function(Either result) viewDidUpdate, }) { @@ -507,6 +528,7 @@ class _$Delete implements Delete { TResult Function(bool isEditing)? setIsEditing, TResult Function(String newName)? rename, TResult Function()? delete, + TResult Function()? duplicate, TResult Function(Either result)? viewDidUpdate, required TResult orElse(), }) { @@ -523,6 +545,7 @@ class _$Delete implements Delete { required TResult Function(SetEditing value) setIsEditing, required TResult Function(Rename value) rename, required TResult Function(Delete value) delete, + required TResult Function(Duplicate value) duplicate, required TResult Function(ViewDidUpdate value) viewDidUpdate, }) { return delete(this); @@ -535,6 +558,7 @@ class _$Delete implements Delete { TResult Function(SetEditing value)? setIsEditing, TResult Function(Rename value)? rename, TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, TResult Function(ViewDidUpdate value)? viewDidUpdate, required TResult orElse(), }) { @@ -549,6 +573,106 @@ abstract class Delete implements ViewEvent { const factory Delete() = _$Delete; } +/// @nodoc +abstract class $DuplicateCopyWith<$Res> { + factory $DuplicateCopyWith(Duplicate value, $Res Function(Duplicate) then) = + _$DuplicateCopyWithImpl<$Res>; +} + +/// @nodoc +class _$DuplicateCopyWithImpl<$Res> extends _$ViewEventCopyWithImpl<$Res> + implements $DuplicateCopyWith<$Res> { + _$DuplicateCopyWithImpl(Duplicate _value, $Res Function(Duplicate) _then) + : super(_value, (v) => _then(v as Duplicate)); + + @override + Duplicate get _value => super._value as Duplicate; +} + +/// @nodoc + +class _$Duplicate implements Duplicate { + const _$Duplicate(); + + @override + String toString() { + return 'ViewEvent.duplicate()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || (other is Duplicate); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function(bool isEditing) setIsEditing, + required TResult Function(String newName) rename, + required TResult Function() delete, + required TResult Function() duplicate, + required TResult Function(Either result) + viewDidUpdate, + }) { + return duplicate(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function(bool isEditing)? setIsEditing, + TResult Function(String newName)? rename, + TResult Function()? delete, + TResult Function()? duplicate, + TResult Function(Either result)? viewDidUpdate, + required TResult orElse(), + }) { + if (duplicate != null) { + return duplicate(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(Initial value) initial, + required TResult Function(SetEditing value) setIsEditing, + required TResult Function(Rename value) rename, + required TResult Function(Delete value) delete, + required TResult Function(Duplicate value) duplicate, + required TResult Function(ViewDidUpdate value) viewDidUpdate, + }) { + return duplicate(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(Initial value)? initial, + TResult Function(SetEditing value)? setIsEditing, + TResult Function(Rename value)? rename, + TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, + TResult Function(ViewDidUpdate value)? viewDidUpdate, + required TResult orElse(), + }) { + if (duplicate != null) { + return duplicate(this); + } + return orElse(); + } +} + +abstract class Duplicate implements ViewEvent { + const factory Duplicate() = _$Duplicate; +} + /// @nodoc abstract class $ViewDidUpdateCopyWith<$Res> { factory $ViewDidUpdateCopyWith( @@ -617,6 +741,7 @@ class _$ViewDidUpdate implements ViewDidUpdate { required TResult Function(bool isEditing) setIsEditing, required TResult Function(String newName) rename, required TResult Function() delete, + required TResult Function() duplicate, required TResult Function(Either result) viewDidUpdate, }) { @@ -630,6 +755,7 @@ class _$ViewDidUpdate implements ViewDidUpdate { TResult Function(bool isEditing)? setIsEditing, TResult Function(String newName)? rename, TResult Function()? delete, + TResult Function()? duplicate, TResult Function(Either result)? viewDidUpdate, required TResult orElse(), }) { @@ -646,6 +772,7 @@ class _$ViewDidUpdate implements ViewDidUpdate { required TResult Function(SetEditing value) setIsEditing, required TResult Function(Rename value) rename, required TResult Function(Delete value) delete, + required TResult Function(Duplicate value) duplicate, required TResult Function(ViewDidUpdate value) viewDidUpdate, }) { return viewDidUpdate(this); @@ -658,6 +785,7 @@ class _$ViewDidUpdate implements ViewDidUpdate { TResult Function(SetEditing value)? setIsEditing, TResult Function(Rename value)? rename, TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, TResult Function(ViewDidUpdate value)? viewDidUpdate, required TResult orElse(), }) { diff --git a/app_flowy/lib/workspace/domain/i_doc.dart b/app_flowy/lib/workspace/domain/i_doc.dart index 92843e29bb..4527a94455 100644 --- a/app_flowy/lib/workspace/domain/i_doc.dart +++ b/app_flowy/lib/workspace/domain/i_doc.dart @@ -1,57 +1,8 @@ -import 'dart:convert'; import 'dart:async'; import 'package:dartz/dartz.dart'; -// ignore: implementation_imports -import 'package:editor/flutter_quill.dart'; -// import 'package:flowy_editor/flowy_editor.dart'; -import 'package:flowy_log/flowy_log.dart'; import 'package:flowy_sdk/protobuf/flowy-document/doc.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart'; -class FlowyDoc { - final DocDelta doc; - final IDoc iDocImpl; - late Document document; - late StreamSubscription _subscription; - - FlowyDoc({required this.doc, required this.iDocImpl}) { - document = _decodeJsonToDocument(doc.data); - - _subscription = document.changes.listen((event) { - final delta = event.item2; - final documentDelta = document.toDelta(); - _composeDelta(delta, documentDelta); - }); - } - - String get id => doc.docId; - - Future close() async { - await _subscription.cancel(); - } - - void _composeDelta(Delta composedDelta, Delta documentDelta) async { - final json = jsonEncode(composedDelta.toJson()); - Log.debug("Send json: $json"); - final result = await iDocImpl.composeDelta(json: json); - - result.fold((rustDoc) { - // final json = utf8.decode(doc.data); - final rustDelta = Delta.fromJson(jsonDecode(rustDoc.data)); - if (documentDelta != rustDelta) { - Log.error("Receive : $rustDelta"); - Log.error("Expected : $documentDelta"); - } - }, (r) => null); - } - - Document _decodeJsonToDocument(String data) { - final json = jsonDecode(data); - final document = Document.fromJson(json); - return document; - } -} - abstract class IDoc { Future> readDoc(); Future> composeDelta({required String json}); diff --git a/app_flowy/lib/workspace/domain/i_view.dart b/app_flowy/lib/workspace/domain/i_view.dart index 972bad6fd6..4ecd8b6842 100644 --- a/app_flowy/lib/workspace/domain/i_view.dart +++ b/app_flowy/lib/workspace/domain/i_view.dart @@ -10,6 +10,8 @@ abstract class IView { Future> delete(); Future> rename(String newName); + + Future> duplicate(); } abstract class IViewListener { diff --git a/app_flowy/lib/workspace/domain/view_edit.dart b/app_flowy/lib/workspace/domain/view_edit.dart index 7619233776..4229d41b6b 100644 --- a/app_flowy/lib/workspace/domain/view_edit.dart +++ b/app_flowy/lib/workspace/domain/view_edit.dart @@ -1,6 +1,10 @@ +import 'package:flowy_infra/image.dart'; +import 'package:flutter/material.dart'; + enum ViewAction { rename, delete, + duplicate, } extension ViewActionExtension on ViewAction { @@ -10,8 +14,19 @@ extension ViewActionExtension on ViewAction { return 'rename'; case ViewAction.delete: return 'delete'; - default: - return ''; + case ViewAction.duplicate: + return 'duplicate'; + } + } + + Widget get icon { + switch (this) { + case ViewAction.rename: + return svg('editor/edit'); + case ViewAction.delete: + return svg('editor/delete'); + case ViewAction.duplicate: + return svg('editor/copy'); } } } diff --git a/app_flowy/lib/workspace/infrastructure/i_view_impl.dart b/app_flowy/lib/workspace/infrastructure/i_view_impl.dart index 43bf55ec90..207b352e12 100644 --- a/app_flowy/lib/workspace/infrastructure/i_view_impl.dart +++ b/app_flowy/lib/workspace/infrastructure/i_view_impl.dart @@ -26,6 +26,11 @@ class IViewImpl extends IView { Future> rename(String newName) { return repo.updateView(name: newName); } + + @override + Future> duplicate() { + return repo.duplicate(); + } } class IViewListenerImpl extends IViewListener { diff --git a/app_flowy/lib/workspace/infrastructure/repos/view_repo.dart b/app_flowy/lib/workspace/infrastructure/repos/view_repo.dart index d88731f761..6a9d109d65 100644 --- a/app_flowy/lib/workspace/infrastructure/repos/view_repo.dart +++ b/app_flowy/lib/workspace/infrastructure/repos/view_repo.dart @@ -43,6 +43,11 @@ class ViewRepository { final request = QueryViewRequest.create()..viewIds.add(view.id); return WorkspaceEventDeleteView(request).send(); } + + Future> duplicate() { + final request = QueryViewRequest.create()..viewIds.add(view.id); + return WorkspaceEventDuplicateView(request).send(); + } } class ViewListenerRepository { diff --git a/app_flowy/lib/workspace/presentation/stack_page/doc/doc_page.dart b/app_flowy/lib/workspace/presentation/stack_page/doc/doc_page.dart index 1b0045999a..e4d6c1686c 100644 --- a/app_flowy/lib/workspace/presentation/stack_page/doc/doc_page.dart +++ b/app_flowy/lib/workspace/presentation/stack_page/doc/doc_page.dart @@ -1,13 +1,13 @@ import 'dart:io'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/doc/doc_bloc.dart'; -import 'package:app_flowy/workspace/domain/i_doc.dart'; import 'package:editor/flutter_quill.dart'; import 'package:flowy_infra_ui/style_widget/progress_indicator.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:styled_widget/styled_widget.dart'; class DocPage extends StatefulWidget { final View view; @@ -38,7 +38,7 @@ class _DocPageState extends State { return state.loadState.map( loading: (_) => const FlowyProgressIndicator(), finish: (result) => result.successOrFail.fold( - (doc) => _renderDoc(context, doc), + (_) => _renderDoc(context), (err) => FlowyErrorPage(err.toString()), ), ); @@ -52,9 +52,9 @@ class _DocPageState extends State { super.dispose(); } - Widget _renderDoc(BuildContext context, FlowyDoc doc) { + Widget _renderDoc(BuildContext context) { QuillController controller = QuillController( - document: doc.document, + document: context.read().document, selection: const TextSelection.collapsed(offset: 0), ); return Column( @@ -63,7 +63,7 @@ class _DocPageState extends State { _renderEditor(controller), _renderToolbar(controller), ], - ); + ).padding(horizontal: 80, vertical: 48); } Widget _renderEditor(QuillController controller) { diff --git a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/action.dart b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/action.dart index 63d1e1788e..a3f941fc04 100644 --- a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/action.dart +++ b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/action.dart @@ -3,6 +3,7 @@ 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/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; @@ -33,8 +34,8 @@ class ViewActionList implements FlowyOverlayDelegate { itemBuilder: (context, index) => items[index], anchorContext: anchorContext, anchorDirection: AnchorDirection.bottomRight, - maxWidth: 120, - maxHeight: 80, + maxWidth: 162, + maxHeight: ViewAction.values.length * 32, delegate: this, ); } @@ -63,11 +64,17 @@ class ActionItem extends StatelessWidget { builder: (context, onHover) { return GestureDetector( onTap: () => onSelected(action), - child: FlowyText.medium( - action.name, - fontSize: 12, + child: Row( + children: [ + action.icon, + const HSpace(10), + FlowyText.medium( + action.name, + fontSize: 12, + ), + ], ).padding( - horizontal: 10, + horizontal: 6, vertical: 6, ), ); diff --git a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart index 7908efba1f..169188a328 100644 --- a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart +++ b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart @@ -103,6 +103,9 @@ class ViewSectionItem extends StatelessWidget { case ViewAction.delete: context.read().add(const ViewEvent.delete()); break; + case ViewAction.duplicate: + context.read().add(const ViewEvent.duplicate()); + break; } }); } diff --git a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart index c93718f72d..cfcd1b0140 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart @@ -163,6 +163,7 @@ class FlowyOverlayState extends State { FlowyOverlayDelegate? delegate, OverlapBehaviour? overlapBehaviour, FlowyOverlayStyle? style, + Offset? anchorPosition, }) { this.style = style ?? FlowyOverlayStyle(); @@ -174,6 +175,7 @@ class FlowyOverlayState extends State { anchorContext: anchorContext, anchorDirection: anchorDirection, overlapBehaviour: overlapBehaviour, + anchorPosition: anchorPosition, ); } diff --git a/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart b/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart index 3005996471..52348dab00 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart @@ -108,7 +108,7 @@ class StyledDialogRoute extends PopupRoute { StyledDialogRoute({ required RoutePageBuilder pageBuilder, required this.barrier, - Duration transitionDuration = const Duration(milliseconds: 360), + Duration transitionDuration = const Duration(milliseconds: 300), RouteTransitionsBuilder? transitionBuilder, RouteSettings? settings, }) : _pageBuilder = pageBuilder, diff --git a/app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart b/app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart index 001e35631d..14b047bf3f 100644 --- a/app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart +++ b/app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart @@ -237,6 +237,37 @@ class WorkspaceEventDeleteView { } } +class WorkspaceEventDuplicateView { + QueryViewRequest request; + WorkspaceEventDuplicateView(this.request); + + Future> send() { + final request = FFIRequest.create() + ..event = WorkspaceEvent.DuplicateView.toString() + ..payload = requestToBytes(this.request); + + return Dispatch.asyncRequest(request) + .then((bytesResult) => bytesResult.fold( + (bytes) => left(unit), + (errBytes) => right(WorkspaceError.fromBuffer(errBytes)), + )); + } +} + +class WorkspaceEventCopyLink { + WorkspaceEventCopyLink(); + + Future> send() { + final request = FFIRequest.create() + ..event = WorkspaceEvent.CopyLink.toString(); + + return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold( + (bytes) => left(unit), + (errBytes) => right(WorkspaceError.fromBuffer(errBytes)), + )); + } +} + class WorkspaceEventOpenView { QueryViewRequest request; WorkspaceEventOpenView(this.request); @@ -271,23 +302,6 @@ class WorkspaceEventCloseView { } } -class WorkspaceEventApplyDocDelta { - DocDelta request; - WorkspaceEventApplyDocDelta(this.request); - - Future> send() { - final request = FFIRequest.create() - ..event = WorkspaceEvent.ApplyDocDelta.toString() - ..payload = requestToBytes(this.request); - - return Dispatch.asyncRequest(request) - .then((bytesResult) => bytesResult.fold( - (okBytes) => left(DocDelta.fromBuffer(okBytes)), - (errBytes) => right(WorkspaceError.fromBuffer(errBytes)), - )); - } -} - class WorkspaceEventReadTrash { WorkspaceEventReadTrash(); @@ -364,6 +378,23 @@ class WorkspaceEventDeleteAll { } } +class WorkspaceEventApplyDocDelta { + DocDelta request; + WorkspaceEventApplyDocDelta(this.request); + + Future> send() { + final request = FFIRequest.create() + ..event = WorkspaceEvent.ApplyDocDelta.toString() + ..payload = requestToBytes(this.request); + + return Dispatch.asyncRequest(request) + .then((bytesResult) => bytesResult.fold( + (okBytes) => left(DocDelta.fromBuffer(okBytes)), + (errBytes) => right(WorkspaceError.fromBuffer(errBytes)), + )); + } +} + class WorkspaceEventInitWorkspace { WorkspaceEventInitWorkspace(); diff --git a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart index 5fa300c4e0..ad37b34518 100644 --- a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart +++ b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart @@ -24,14 +24,16 @@ class WorkspaceEvent extends $pb.ProtobufEnum { static const WorkspaceEvent ReadView = WorkspaceEvent._(202, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadView'); static const WorkspaceEvent UpdateView = WorkspaceEvent._(203, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateView'); static const WorkspaceEvent DeleteView = WorkspaceEvent._(204, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteView'); - static const WorkspaceEvent OpenView = WorkspaceEvent._(205, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OpenView'); - static const WorkspaceEvent CloseView = WorkspaceEvent._(206, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CloseView'); - static const WorkspaceEvent ApplyDocDelta = WorkspaceEvent._(207, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ApplyDocDelta'); + static const WorkspaceEvent DuplicateView = WorkspaceEvent._(205, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateView'); + static const WorkspaceEvent CopyLink = WorkspaceEvent._(206, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CopyLink'); + static const WorkspaceEvent OpenView = WorkspaceEvent._(207, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OpenView'); + static const WorkspaceEvent CloseView = WorkspaceEvent._(208, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CloseView'); static const WorkspaceEvent ReadTrash = WorkspaceEvent._(300, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadTrash'); static const WorkspaceEvent PutbackTrash = WorkspaceEvent._(301, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PutbackTrash'); static const WorkspaceEvent DeleteTrash = WorkspaceEvent._(302, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteTrash'); static const WorkspaceEvent RestoreAll = WorkspaceEvent._(303, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RestoreAll'); static const WorkspaceEvent DeleteAll = WorkspaceEvent._(304, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteAll'); + static const WorkspaceEvent ApplyDocDelta = WorkspaceEvent._(400, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ApplyDocDelta'); static const WorkspaceEvent InitWorkspace = WorkspaceEvent._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InitWorkspace'); static const $core.List values = [ @@ -49,14 +51,16 @@ class WorkspaceEvent extends $pb.ProtobufEnum { ReadView, UpdateView, DeleteView, + DuplicateView, + CopyLink, OpenView, CloseView, - ApplyDocDelta, ReadTrash, PutbackTrash, DeleteTrash, RestoreAll, DeleteAll, + ApplyDocDelta, InitWorkspace, ]; diff --git a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart index 36c47adc40..3341463341 100644 --- a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart +++ b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart @@ -26,17 +26,19 @@ const WorkspaceEvent$json = const { const {'1': 'ReadView', '2': 202}, const {'1': 'UpdateView', '2': 203}, const {'1': 'DeleteView', '2': 204}, - const {'1': 'OpenView', '2': 205}, - const {'1': 'CloseView', '2': 206}, - const {'1': 'ApplyDocDelta', '2': 207}, + const {'1': 'DuplicateView', '2': 205}, + const {'1': 'CopyLink', '2': 206}, + const {'1': 'OpenView', '2': 207}, + const {'1': 'CloseView', '2': 208}, const {'1': 'ReadTrash', '2': 300}, const {'1': 'PutbackTrash', '2': 301}, const {'1': 'DeleteTrash', '2': 302}, const {'1': 'RestoreAll', '2': 303}, const {'1': 'DeleteAll', '2': 304}, + const {'1': 'ApplyDocDelta', '2': 400}, const {'1': 'InitWorkspace', '2': 1000}, ], }; /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIUChBSZWFkQ3VyV29ya3NwYWNlEAESEgoOUmVhZFdvcmtzcGFjZXMQAhITCg9EZWxldGVXb3Jrc3BhY2UQAxIRCg1PcGVuV29ya3NwYWNlEAQSFQoRUmVhZFdvcmtzcGFjZUFwcHMQBRINCglDcmVhdGVBcHAQZRINCglEZWxldGVBcHAQZhILCgdSZWFkQXBwEGcSDQoJVXBkYXRlQXBwEGgSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsBEg8KCkRlbGV0ZVZpZXcQzAESDQoIT3BlblZpZXcQzQESDgoJQ2xvc2VWaWV3EM4BEhIKDUFwcGx5RG9jRGVsdGEQzwESDgoJUmVhZFRyYXNoEKwCEhEKDFB1dGJhY2tUcmFzaBCtAhIQCgtEZWxldGVUcmFzaBCuAhIPCgpSZXN0b3JlQWxsEK8CEg4KCURlbGV0ZUFsbBCwAhISCg1Jbml0V29ya3NwYWNlEOgH'); +final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIUChBSZWFkQ3VyV29ya3NwYWNlEAESEgoOUmVhZFdvcmtzcGFjZXMQAhITCg9EZWxldGVXb3Jrc3BhY2UQAxIRCg1PcGVuV29ya3NwYWNlEAQSFQoRUmVhZFdvcmtzcGFjZUFwcHMQBRINCglDcmVhdGVBcHAQZRINCglEZWxldGVBcHAQZhILCgdSZWFkQXBwEGcSDQoJVXBkYXRlQXBwEGgSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsBEg8KCkRlbGV0ZVZpZXcQzAESEgoNRHVwbGljYXRlVmlldxDNARINCghDb3B5TGluaxDOARINCghPcGVuVmlldxDPARIOCglDbG9zZVZpZXcQ0AESDgoJUmVhZFRyYXNoEKwCEhEKDFB1dGJhY2tUcmFzaBCtAhIQCgtEZWxldGVUcmFzaBCuAhIPCgpSZXN0b3JlQWxsEK8CEg4KCURlbGV0ZUFsbBCwAhISCg1BcHBseURvY0RlbHRhEJADEhIKDUluaXRXb3Jrc3BhY2UQ6Ac='); diff --git a/rust-lib/flowy-document/src/module.rs b/rust-lib/flowy-document/src/module.rs index 21177c43ce..1280222a52 100644 --- a/rust-lib/flowy-document/src/module.rs +++ b/rust-lib/flowy-document/src/module.rs @@ -52,6 +52,16 @@ impl FlowyDocument { Ok(()) } + pub async fn read_document_data( + &self, + params: DocIdentifier, + pool: Arc, + ) -> Result { + let edit_context = self.doc_ctrl.open(params, pool).await?; + let delta = edit_context.delta().await?; + Ok(delta) + } + pub async fn apply_doc_delta(&self, params: DocDelta) -> Result { // workaround: compare the rust's delta with flutter's delta. Will be removed // very soon diff --git a/rust-lib/flowy-workspace/src/event.rs b/rust-lib/flowy-workspace/src/event.rs index 2b66422523..02a9ba7a83 100644 --- a/rust-lib/flowy-workspace/src/event.rs +++ b/rust-lib/flowy-workspace/src/event.rs @@ -46,14 +46,17 @@ pub enum WorkspaceEvent { #[event(input = "QueryViewRequest")] DeleteView = 204, + #[event(input = "QueryViewRequest")] + DuplicateView = 205, + + #[event()] + CopyLink = 206, + #[event(input = "QueryViewRequest", output = "DocDelta")] - OpenView = 205, + OpenView = 207, #[event(input = "QueryViewRequest")] - CloseView = 206, - - #[event(input = "DocDelta", output = "DocDelta")] - ApplyDocDelta = 207, + CloseView = 208, #[event(output = "RepeatedTrash")] ReadTrash = 300, @@ -70,6 +73,9 @@ pub enum WorkspaceEvent { #[event()] DeleteAll = 304, + #[event(input = "DocDelta", output = "DocDelta")] + ApplyDocDelta = 400, + #[event()] InitWorkspace = 1000, } diff --git a/rust-lib/flowy-workspace/src/handlers/view_handler.rs b/rust-lib/flowy-workspace/src/handlers/view_handler.rs index c75aa894aa..9ce9d76eae 100644 --- a/rust-lib/flowy-workspace/src/handlers/view_handler.rs +++ b/rust-lib/flowy-workspace/src/handlers/view_handler.rs @@ -102,3 +102,13 @@ pub(crate) async fn close_view_handler( let _ = controller.close_view(params.into()).await?; Ok(()) } + +#[tracing::instrument(skip(data, controller), err)] +pub(crate) async fn duplicate_view_handler( + data: Data, + controller: Unit>, +) -> Result<(), WorkspaceError> { + let params: ViewIdentifier = data.into_inner().try_into()?; + let _ = controller.duplicate_view(params.into()).await?; + Ok(()) +} diff --git a/rust-lib/flowy-workspace/src/module.rs b/rust-lib/flowy-workspace/src/module.rs index 2225fd0a2c..0c1c54516b 100644 --- a/rust-lib/flowy-workspace/src/module.rs +++ b/rust-lib/flowy-workspace/src/module.rs @@ -88,6 +88,7 @@ pub fn create(workspace: Arc) -> Module { .event(WorkspaceEvent::ReadView, read_view_handler) .event(WorkspaceEvent::UpdateView, update_view_handler) .event(WorkspaceEvent::DeleteView, delete_view_handler) + .event(WorkspaceEvent::DuplicateView, duplicate_view_handler) .event(WorkspaceEvent::OpenView, open_view_handler) .event(WorkspaceEvent::CloseView, close_view_handler) .event(WorkspaceEvent::ApplyDocDelta, apply_doc_delta_handler); diff --git a/rust-lib/flowy-workspace/src/protobuf/model/event.rs b/rust-lib/flowy-workspace/src/protobuf/model/event.rs index 8a22a1370f..066da47e1e 100644 --- a/rust-lib/flowy-workspace/src/protobuf/model/event.rs +++ b/rust-lib/flowy-workspace/src/protobuf/model/event.rs @@ -39,14 +39,16 @@ pub enum WorkspaceEvent { ReadView = 202, UpdateView = 203, DeleteView = 204, - OpenView = 205, - CloseView = 206, - ApplyDocDelta = 207, + DuplicateView = 205, + CopyLink = 206, + OpenView = 207, + CloseView = 208, ReadTrash = 300, PutbackTrash = 301, DeleteTrash = 302, RestoreAll = 303, DeleteAll = 304, + ApplyDocDelta = 400, InitWorkspace = 1000, } @@ -71,14 +73,16 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent { 202 => ::std::option::Option::Some(WorkspaceEvent::ReadView), 203 => ::std::option::Option::Some(WorkspaceEvent::UpdateView), 204 => ::std::option::Option::Some(WorkspaceEvent::DeleteView), - 205 => ::std::option::Option::Some(WorkspaceEvent::OpenView), - 206 => ::std::option::Option::Some(WorkspaceEvent::CloseView), - 207 => ::std::option::Option::Some(WorkspaceEvent::ApplyDocDelta), + 205 => ::std::option::Option::Some(WorkspaceEvent::DuplicateView), + 206 => ::std::option::Option::Some(WorkspaceEvent::CopyLink), + 207 => ::std::option::Option::Some(WorkspaceEvent::OpenView), + 208 => ::std::option::Option::Some(WorkspaceEvent::CloseView), 300 => ::std::option::Option::Some(WorkspaceEvent::ReadTrash), 301 => ::std::option::Option::Some(WorkspaceEvent::PutbackTrash), 302 => ::std::option::Option::Some(WorkspaceEvent::DeleteTrash), 303 => ::std::option::Option::Some(WorkspaceEvent::RestoreAll), 304 => ::std::option::Option::Some(WorkspaceEvent::DeleteAll), + 400 => ::std::option::Option::Some(WorkspaceEvent::ApplyDocDelta), 1000 => ::std::option::Option::Some(WorkspaceEvent::InitWorkspace), _ => ::std::option::Option::None } @@ -100,14 +104,16 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent { WorkspaceEvent::ReadView, WorkspaceEvent::UpdateView, WorkspaceEvent::DeleteView, + WorkspaceEvent::DuplicateView, + WorkspaceEvent::CopyLink, WorkspaceEvent::OpenView, WorkspaceEvent::CloseView, - WorkspaceEvent::ApplyDocDelta, WorkspaceEvent::ReadTrash, WorkspaceEvent::PutbackTrash, WorkspaceEvent::DeleteTrash, WorkspaceEvent::RestoreAll, WorkspaceEvent::DeleteAll, + WorkspaceEvent::ApplyDocDelta, WorkspaceEvent::InitWorkspace, ]; values @@ -137,38 +143,39 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceEvent { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x0bevent.proto*\xa7\x03\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorksp\ + \n\x0bevent.proto*\xca\x03\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorksp\ ace\x10\0\x12\x14\n\x10ReadCurWorkspace\x10\x01\x12\x12\n\x0eReadWorkspa\ ces\x10\x02\x12\x13\n\x0fDeleteWorkspace\x10\x03\x12\x11\n\rOpenWorkspac\ e\x10\x04\x12\x15\n\x11ReadWorkspaceApps\x10\x05\x12\r\n\tCreateApp\x10e\ \x12\r\n\tDeleteApp\x10f\x12\x0b\n\x07ReadApp\x10g\x12\r\n\tUpdateApp\ \x10h\x12\x0f\n\nCreateView\x10\xc9\x01\x12\r\n\x08ReadView\x10\xca\x01\ \x12\x0f\n\nUpdateView\x10\xcb\x01\x12\x0f\n\nDeleteView\x10\xcc\x01\x12\ - \r\n\x08OpenView\x10\xcd\x01\x12\x0e\n\tCloseView\x10\xce\x01\x12\x12\n\ - \rApplyDocDelta\x10\xcf\x01\x12\x0e\n\tReadTrash\x10\xac\x02\x12\x11\n\ - \x0cPutbackTrash\x10\xad\x02\x12\x10\n\x0bDeleteTrash\x10\xae\x02\x12\ - \x0f\n\nRestoreAll\x10\xaf\x02\x12\x0e\n\tDeleteAll\x10\xb0\x02\x12\x12\ - \n\rInitWorkspace\x10\xe8\x07J\xd9\x07\n\x06\x12\x04\0\0\x1a\x01\n\x08\n\ - \x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x1a\x01\n\n\n\x03\ - \x05\0\x01\x12\x03\x02\x05\x13\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\ - \x18\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x13\n\x0c\n\x05\x05\0\ - \x02\0\x02\x12\x03\x03\x16\x17\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\ - \x19\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x14\n\x0c\n\x05\x05\0\ - \x02\x01\x02\x12\x03\x04\x17\x18\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\ - \x04\x17\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\x12\n\x0c\n\x05\ - \x05\0\x02\x02\x02\x12\x03\x05\x15\x16\n\x0b\n\x04\x05\0\x02\x03\x12\x03\ - \x06\x04\x18\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x13\n\x0c\n\ - \x05\x05\0\x02\x03\x02\x12\x03\x06\x16\x17\n\x0b\n\x04\x05\0\x02\x04\x12\ - \x03\x07\x04\x16\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\x11\n\x0c\ - \n\x05\x05\0\x02\x04\x02\x12\x03\x07\x14\x15\n\x0b\n\x04\x05\0\x02\x05\ - \x12\x03\x08\x04\x1a\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x08\x04\x15\n\ - \x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x08\x18\x19\n\x0b\n\x04\x05\0\x02\ - \x06\x12\x03\t\x04\x14\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\t\x04\r\n\ - \x0c\n\x05\x05\0\x02\x06\x02\x12\x03\t\x10\x13\n\x0b\n\x04\x05\0\x02\x07\ - \x12\x03\n\x04\x14\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\n\x04\r\n\x0c\n\ - \x05\x05\0\x02\x07\x02\x12\x03\n\x10\x13\n\x0b\n\x04\x05\0\x02\x08\x12\ - \x03\x0b\x04\x12\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0b\x04\x0b\n\x0c\ - \n\x05\x05\0\x02\x08\x02\x12\x03\x0b\x0e\x11\n\x0b\n\x04\x05\0\x02\t\x12\ + \x12\n\rDuplicateView\x10\xcd\x01\x12\r\n\x08CopyLink\x10\xce\x01\x12\r\ + \n\x08OpenView\x10\xcf\x01\x12\x0e\n\tCloseView\x10\xd0\x01\x12\x0e\n\tR\ + eadTrash\x10\xac\x02\x12\x11\n\x0cPutbackTrash\x10\xad\x02\x12\x10\n\x0b\ + DeleteTrash\x10\xae\x02\x12\x0f\n\nRestoreAll\x10\xaf\x02\x12\x0e\n\tDel\ + eteAll\x10\xb0\x02\x12\x12\n\rApplyDocDelta\x10\x90\x03\x12\x12\n\rInitW\ + orkspace\x10\xe8\x07J\xab\x08\n\x06\x12\x04\0\0\x1c\x01\n\x08\n\x01\x0c\ + \x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x1c\x01\n\n\n\x03\x05\0\ + \x01\x12\x03\x02\x05\x13\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x18\n\ + \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x13\n\x0c\n\x05\x05\0\x02\0\ + \x02\x12\x03\x03\x16\x17\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x19\n\ + \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x14\n\x0c\n\x05\x05\0\x02\ + \x01\x02\x12\x03\x04\x17\x18\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\ + \x17\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\x12\n\x0c\n\x05\x05\0\ + \x02\x02\x02\x12\x03\x05\x15\x16\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\ + \x04\x18\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x13\n\x0c\n\x05\ + \x05\0\x02\x03\x02\x12\x03\x06\x16\x17\n\x0b\n\x04\x05\0\x02\x04\x12\x03\ + \x07\x04\x16\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\x11\n\x0c\n\ + \x05\x05\0\x02\x04\x02\x12\x03\x07\x14\x15\n\x0b\n\x04\x05\0\x02\x05\x12\ + \x03\x08\x04\x1a\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x08\x04\x15\n\x0c\ + \n\x05\x05\0\x02\x05\x02\x12\x03\x08\x18\x19\n\x0b\n\x04\x05\0\x02\x06\ + \x12\x03\t\x04\x14\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\t\x04\r\n\x0c\n\ + \x05\x05\0\x02\x06\x02\x12\x03\t\x10\x13\n\x0b\n\x04\x05\0\x02\x07\x12\ + \x03\n\x04\x14\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\n\x04\r\n\x0c\n\x05\ + \x05\0\x02\x07\x02\x12\x03\n\x10\x13\n\x0b\n\x04\x05\0\x02\x08\x12\x03\ + \x0b\x04\x12\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0b\x04\x0b\n\x0c\n\ + \x05\x05\0\x02\x08\x02\x12\x03\x0b\x0e\x11\n\x0b\n\x04\x05\0\x02\t\x12\ \x03\x0c\x04\x14\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x0c\x04\r\n\x0c\n\ \x05\x05\0\x02\t\x02\x12\x03\x0c\x10\x13\n\x0b\n\x04\x05\0\x02\n\x12\x03\ \r\x04\x15\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\r\x04\x0e\n\x0c\n\x05\x05\ @@ -179,25 +186,29 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x05\0\x02\x0c\x02\x12\x03\x0f\x11\x14\n\x0b\n\x04\x05\0\x02\r\x12\x03\ \x10\x04\x15\n\x0c\n\x05\x05\0\x02\r\x01\x12\x03\x10\x04\x0e\n\x0c\n\x05\ \x05\0\x02\r\x02\x12\x03\x10\x11\x14\n\x0b\n\x04\x05\0\x02\x0e\x12\x03\ - \x11\x04\x13\n\x0c\n\x05\x05\0\x02\x0e\x01\x12\x03\x11\x04\x0c\n\x0c\n\ - \x05\x05\0\x02\x0e\x02\x12\x03\x11\x0f\x12\n\x0b\n\x04\x05\0\x02\x0f\x12\ - \x03\x12\x04\x14\n\x0c\n\x05\x05\0\x02\x0f\x01\x12\x03\x12\x04\r\n\x0c\n\ - \x05\x05\0\x02\x0f\x02\x12\x03\x12\x10\x13\n\x0b\n\x04\x05\0\x02\x10\x12\ - \x03\x13\x04\x18\n\x0c\n\x05\x05\0\x02\x10\x01\x12\x03\x13\x04\x11\n\x0c\ - \n\x05\x05\0\x02\x10\x02\x12\x03\x13\x14\x17\n\x0b\n\x04\x05\0\x02\x11\ - \x12\x03\x14\x04\x14\n\x0c\n\x05\x05\0\x02\x11\x01\x12\x03\x14\x04\r\n\ - \x0c\n\x05\x05\0\x02\x11\x02\x12\x03\x14\x10\x13\n\x0b\n\x04\x05\0\x02\ - \x12\x12\x03\x15\x04\x17\n\x0c\n\x05\x05\0\x02\x12\x01\x12\x03\x15\x04\ - \x10\n\x0c\n\x05\x05\0\x02\x12\x02\x12\x03\x15\x13\x16\n\x0b\n\x04\x05\0\ - \x02\x13\x12\x03\x16\x04\x16\n\x0c\n\x05\x05\0\x02\x13\x01\x12\x03\x16\ - \x04\x0f\n\x0c\n\x05\x05\0\x02\x13\x02\x12\x03\x16\x12\x15\n\x0b\n\x04\ - \x05\0\x02\x14\x12\x03\x17\x04\x15\n\x0c\n\x05\x05\0\x02\x14\x01\x12\x03\ - \x17\x04\x0e\n\x0c\n\x05\x05\0\x02\x14\x02\x12\x03\x17\x11\x14\n\x0b\n\ - \x04\x05\0\x02\x15\x12\x03\x18\x04\x14\n\x0c\n\x05\x05\0\x02\x15\x01\x12\ - \x03\x18\x04\r\n\x0c\n\x05\x05\0\x02\x15\x02\x12\x03\x18\x10\x13\n\x0b\n\ - \x04\x05\0\x02\x16\x12\x03\x19\x04\x19\n\x0c\n\x05\x05\0\x02\x16\x01\x12\ - \x03\x19\x04\x11\n\x0c\n\x05\x05\0\x02\x16\x02\x12\x03\x19\x14\x18b\x06p\ - roto3\ + \x11\x04\x18\n\x0c\n\x05\x05\0\x02\x0e\x01\x12\x03\x11\x04\x11\n\x0c\n\ + \x05\x05\0\x02\x0e\x02\x12\x03\x11\x14\x17\n\x0b\n\x04\x05\0\x02\x0f\x12\ + \x03\x12\x04\x13\n\x0c\n\x05\x05\0\x02\x0f\x01\x12\x03\x12\x04\x0c\n\x0c\ + \n\x05\x05\0\x02\x0f\x02\x12\x03\x12\x0f\x12\n\x0b\n\x04\x05\0\x02\x10\ + \x12\x03\x13\x04\x13\n\x0c\n\x05\x05\0\x02\x10\x01\x12\x03\x13\x04\x0c\n\ + \x0c\n\x05\x05\0\x02\x10\x02\x12\x03\x13\x0f\x12\n\x0b\n\x04\x05\0\x02\ + \x11\x12\x03\x14\x04\x14\n\x0c\n\x05\x05\0\x02\x11\x01\x12\x03\x14\x04\r\ + \n\x0c\n\x05\x05\0\x02\x11\x02\x12\x03\x14\x10\x13\n\x0b\n\x04\x05\0\x02\ + \x12\x12\x03\x15\x04\x14\n\x0c\n\x05\x05\0\x02\x12\x01\x12\x03\x15\x04\r\ + \n\x0c\n\x05\x05\0\x02\x12\x02\x12\x03\x15\x10\x13\n\x0b\n\x04\x05\0\x02\ + \x13\x12\x03\x16\x04\x17\n\x0c\n\x05\x05\0\x02\x13\x01\x12\x03\x16\x04\ + \x10\n\x0c\n\x05\x05\0\x02\x13\x02\x12\x03\x16\x13\x16\n\x0b\n\x04\x05\0\ + \x02\x14\x12\x03\x17\x04\x16\n\x0c\n\x05\x05\0\x02\x14\x01\x12\x03\x17\ + \x04\x0f\n\x0c\n\x05\x05\0\x02\x14\x02\x12\x03\x17\x12\x15\n\x0b\n\x04\ + \x05\0\x02\x15\x12\x03\x18\x04\x15\n\x0c\n\x05\x05\0\x02\x15\x01\x12\x03\ + \x18\x04\x0e\n\x0c\n\x05\x05\0\x02\x15\x02\x12\x03\x18\x11\x14\n\x0b\n\ + \x04\x05\0\x02\x16\x12\x03\x19\x04\x14\n\x0c\n\x05\x05\0\x02\x16\x01\x12\ + \x03\x19\x04\r\n\x0c\n\x05\x05\0\x02\x16\x02\x12\x03\x19\x10\x13\n\x0b\n\ + \x04\x05\0\x02\x17\x12\x03\x1a\x04\x18\n\x0c\n\x05\x05\0\x02\x17\x01\x12\ + \x03\x1a\x04\x11\n\x0c\n\x05\x05\0\x02\x17\x02\x12\x03\x1a\x14\x17\n\x0b\ + \n\x04\x05\0\x02\x18\x12\x03\x1b\x04\x19\n\x0c\n\x05\x05\0\x02\x18\x01\ + \x12\x03\x1b\x04\x11\n\x0c\n\x05\x05\0\x02\x18\x02\x12\x03\x1b\x14\x18b\ + \x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/rust-lib/flowy-workspace/src/protobuf/proto/event.proto b/rust-lib/flowy-workspace/src/protobuf/proto/event.proto index f5e0155017..83a2c8de99 100644 --- a/rust-lib/flowy-workspace/src/protobuf/proto/event.proto +++ b/rust-lib/flowy-workspace/src/protobuf/proto/event.proto @@ -15,13 +15,15 @@ enum WorkspaceEvent { ReadView = 202; UpdateView = 203; DeleteView = 204; - OpenView = 205; - CloseView = 206; - ApplyDocDelta = 207; + DuplicateView = 205; + CopyLink = 206; + OpenView = 207; + CloseView = 208; ReadTrash = 300; PutbackTrash = 301; DeleteTrash = 302; RestoreAll = 303; DeleteAll = 304; + ApplyDocDelta = 400; InitWorkspace = 1000; } diff --git a/rust-lib/flowy-workspace/src/services/view_controller.rs b/rust-lib/flowy-workspace/src/services/view_controller.rs index 6f7a706fe7..6a6271a75b 100644 --- a/rust-lib/flowy-workspace/src/services/view_controller.rs +++ b/rust-lib/flowy-workspace/src/services/view_controller.rs @@ -119,6 +119,27 @@ impl ViewController { Ok(()) } + #[tracing::instrument(level = "debug", skip(self), err)] + pub(crate) async fn duplicate_view(&self, params: DocIdentifier) -> Result<(), WorkspaceError> { + let view: View = ViewTableSql::read_view(¶ms.doc_id, &*self.database.db_connection()?)?.into(); + let delta_data = self + .document + .read_document_data(params, self.database.db_pool()?) + .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(), + data: delta_data.data, + }; + + let _ = self.create_view(duplicate_params).await?; + Ok(()) + } + // belong_to_id will be the app_id or view_id. #[tracing::instrument(level = "debug", skip(self), err)] pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result {