mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: node transform path test
This commit is contained in:
parent
9ff0975f7c
commit
fa2cfd7c20
@ -90,7 +90,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
|||||||
final result = await service.openDocument(docId: view.id);
|
final result = await service.openDocument(docId: view.id);
|
||||||
result.fold(
|
result.fold(
|
||||||
(block) {
|
(block) {
|
||||||
document = _decodeJsonToDocument(block.deltaStr);
|
document = _decodeJsonToDocument(block.snapshot);
|
||||||
_subscription = document.changes.listen((event) {
|
_subscription = document.changes.listen((event) {
|
||||||
final delta = event.item2;
|
final delta = event.item2;
|
||||||
final documentDelta = document.toDelta();
|
final documentDelta = document.toDelta();
|
||||||
@ -115,16 +115,12 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
|||||||
void _composeDelta(Delta composedDelta, Delta documentDelta) async {
|
void _composeDelta(Delta composedDelta, Delta documentDelta) async {
|
||||||
final json = jsonEncode(composedDelta.toJson());
|
final json = jsonEncode(composedDelta.toJson());
|
||||||
Log.debug("doc_id: $view.id - Send json: $json");
|
Log.debug("doc_id: $view.id - Send json: $json");
|
||||||
final result = await service.composeDelta(docId: view.id, data: json);
|
final result = await service.applyEdit(docId: view.id, data: json);
|
||||||
|
|
||||||
result.fold((rustDoc) {
|
result.fold(
|
||||||
// final json = utf8.decode(doc.data);
|
(_) {},
|
||||||
final rustDelta = Delta.fromJson(jsonDecode(rustDoc.deltaStr));
|
(r) => Log.error(r),
|
||||||
if (documentDelta != rustDelta) {
|
);
|
||||||
Log.error("Receive : $rustDelta");
|
|
||||||
Log.error("Expected : $documentDelta");
|
|
||||||
}
|
|
||||||
}, (r) => null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Document _decodeJsonToDocument(String data) {
|
Document _decodeJsonToDocument(String data) {
|
||||||
|
@ -4,22 +4,28 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-sync/text_block.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-sync/text_block.pb.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-text-block/entities.pb.dart';
|
||||||
|
|
||||||
class DocumentService {
|
class DocumentService {
|
||||||
Future<Either<TextBlockDeltaPB, FlowyError>> openDocument({
|
Future<Either<TextBlockPB, FlowyError>> openDocument({
|
||||||
required String docId,
|
required String docId,
|
||||||
}) async {
|
}) async {
|
||||||
await FolderEventSetLatestView(ViewIdPB(value: docId)).send();
|
await FolderEventSetLatestView(ViewIdPB(value: docId)).send();
|
||||||
|
|
||||||
final payload = TextBlockIdPB(value: docId);
|
final payload = TextBlockIdPB(value: docId);
|
||||||
return TextBlockEventGetBlockData(payload).send();
|
return TextBlockEventGetTextBlock(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<TextBlockDeltaPB, FlowyError>> composeDelta({required String docId, required String data}) {
|
Future<Either<Unit, FlowyError>> applyEdit({
|
||||||
final payload = TextBlockDeltaPB.create()
|
required String docId,
|
||||||
..blockId = docId
|
required String data,
|
||||||
..deltaStr = data;
|
String operations = "",
|
||||||
return TextBlockEventApplyDelta(payload).send();
|
}) {
|
||||||
|
final payload = EditPayloadPB.create()
|
||||||
|
..textBlockId = docId
|
||||||
|
..operations = operations
|
||||||
|
..delta = data;
|
||||||
|
return TextBlockEventApplyEdit(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
|
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::FlowyError;
|
||||||
use flowy_sync::entities::text_block::{CreateTextBlockParams, DocumentPB, ResetTextBlockParams, TextBlockIdPB};
|
use flowy_sync::entities::text_block::{CreateTextBlockParams, DocumentPB, ResetTextBlockParams, TextBlockIdPB};
|
||||||
use flowy_text_block::BlockCloudService;
|
use flowy_text_block::TextEditorCloudService;
|
||||||
use http_flowy::response::FlowyResponse;
|
use http_flowy::response::FlowyResponse;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
@ -20,20 +20,20 @@ impl BlockHttpCloudService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockCloudService for BlockHttpCloudService {
|
impl TextEditorCloudService for BlockHttpCloudService {
|
||||||
fn create_block(&self, token: &str, params: CreateTextBlockParams) -> FutureResult<(), FlowyError> {
|
fn create_text_block(&self, token: &str, params: CreateTextBlockParams) -> FutureResult<(), FlowyError> {
|
||||||
let token = token.to_owned();
|
let token = token.to_owned();
|
||||||
let url = self.config.doc_url();
|
let url = self.config.doc_url();
|
||||||
FutureResult::new(async move { create_document_request(&token, params, &url).await })
|
FutureResult::new(async move { create_document_request(&token, params, &url).await })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_block(&self, token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError> {
|
fn read_text_block(&self, token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError> {
|
||||||
let token = token.to_owned();
|
let token = token.to_owned();
|
||||||
let url = self.config.doc_url();
|
let url = self.config.doc_url();
|
||||||
FutureResult::new(async move { read_document_request(&token, params, &url).await })
|
FutureResult::new(async move { read_document_request(&token, params, &url).await })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_block(&self, token: &str, params: ResetTextBlockParams) -> FutureResult<(), FlowyError> {
|
fn update_text_block(&self, token: &str, params: ResetTextBlockParams) -> FutureResult<(), FlowyError> {
|
||||||
let token = token.to_owned();
|
let token = token.to_owned();
|
||||||
let url = self.config.doc_url();
|
let url = self.config.doc_url();
|
||||||
FutureResult::new(async move { reset_doc_request(&token, params, &url).await })
|
FutureResult::new(async move { reset_doc_request(&token, params, &url).await })
|
||||||
|
@ -261,7 +261,7 @@ use flowy_folder::entities::{
|
|||||||
use flowy_folder_data_model::revision::{
|
use flowy_folder_data_model::revision::{
|
||||||
gen_app_id, gen_workspace_id, AppRevision, TrashRevision, ViewRevision, WorkspaceRevision,
|
gen_app_id, gen_workspace_id, AppRevision, TrashRevision, ViewRevision, WorkspaceRevision,
|
||||||
};
|
};
|
||||||
use flowy_text_block::BlockCloudService;
|
use flowy_text_block::TextEditorCloudService;
|
||||||
use flowy_user::entities::{
|
use flowy_user::entities::{
|
||||||
SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB,
|
SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB,
|
||||||
};
|
};
|
||||||
@ -414,12 +414,12 @@ impl UserCloudService for LocalServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockCloudService for LocalServer {
|
impl TextEditorCloudService for LocalServer {
|
||||||
fn create_block(&self, _token: &str, _params: CreateTextBlockParams) -> FutureResult<(), FlowyError> {
|
fn create_text_block(&self, _token: &str, _params: CreateTextBlockParams) -> FutureResult<(), FlowyError> {
|
||||||
FutureResult::new(async { Ok(()) })
|
FutureResult::new(async { Ok(()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_block(&self, _token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError> {
|
fn read_text_block(&self, _token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError> {
|
||||||
let doc = DocumentPB {
|
let doc = DocumentPB {
|
||||||
block_id: params.value,
|
block_id: params.value,
|
||||||
text: initial_quill_delta_string(),
|
text: initial_quill_delta_string(),
|
||||||
@ -429,7 +429,7 @@ impl BlockCloudService for LocalServer {
|
|||||||
FutureResult::new(async { Ok(Some(doc)) })
|
FutureResult::new(async { Ok(Some(doc)) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_block(&self, _token: &str, _params: ResetTextBlockParams) -> FutureResult<(), FlowyError> {
|
fn update_text_block(&self, _token: &str, _params: ResetTextBlockParams) -> FutureResult<(), FlowyError> {
|
||||||
FutureResult::new(async { Ok(()) })
|
FutureResult::new(async { Ok(()) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ use flowy_revision::{RevisionWebSocket, WSStateReceiver};
|
|||||||
use flowy_sync::client_document::default::initial_quill_delta_string;
|
use flowy_sync::client_document::default::initial_quill_delta_string;
|
||||||
use flowy_sync::entities::revision::{RepeatedRevision, Revision};
|
use flowy_sync::entities::revision::{RepeatedRevision, Revision};
|
||||||
use flowy_sync::entities::ws_data::ClientRevisionWSData;
|
use flowy_sync::entities::ws_data::ClientRevisionWSData;
|
||||||
use flowy_text_block::TextBlockManager;
|
use flowy_text_block::TextEditorManager;
|
||||||
use flowy_user::services::UserSession;
|
use flowy_user::services::UserSession;
|
||||||
use futures_core::future::BoxFuture;
|
use futures_core::future::BoxFuture;
|
||||||
use lib_infra::future::{BoxResultFuture, FutureResult};
|
use lib_infra::future::{BoxResultFuture, FutureResult};
|
||||||
@ -34,7 +34,7 @@ impl FolderDepsResolver {
|
|||||||
user_session: Arc<UserSession>,
|
user_session: Arc<UserSession>,
|
||||||
server_config: &ClientServerConfiguration,
|
server_config: &ClientServerConfiguration,
|
||||||
ws_conn: &Arc<FlowyWebSocketConnect>,
|
ws_conn: &Arc<FlowyWebSocketConnect>,
|
||||||
text_block_manager: &Arc<TextBlockManager>,
|
text_block_manager: &Arc<TextEditorManager>,
|
||||||
grid_manager: &Arc<GridManager>,
|
grid_manager: &Arc<GridManager>,
|
||||||
) -> Arc<FolderManager> {
|
) -> Arc<FolderManager> {
|
||||||
let user: Arc<dyn WorkspaceUser> = Arc::new(WorkspaceUserImpl(user_session.clone()));
|
let user: Arc<dyn WorkspaceUser> = Arc::new(WorkspaceUserImpl(user_session.clone()));
|
||||||
@ -63,7 +63,7 @@ impl FolderDepsResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn make_view_data_processor(
|
fn make_view_data_processor(
|
||||||
text_block_manager: Arc<TextBlockManager>,
|
text_block_manager: Arc<TextEditorManager>,
|
||||||
grid_manager: Arc<GridManager>,
|
grid_manager: Arc<GridManager>,
|
||||||
) -> ViewDataProcessorMap {
|
) -> ViewDataProcessorMap {
|
||||||
let mut map: HashMap<ViewDataTypePB, Arc<dyn ViewDataProcessor + Send + Sync>> = HashMap::new();
|
let mut map: HashMap<ViewDataTypePB, Arc<dyn ViewDataProcessor + Send + Sync>> = HashMap::new();
|
||||||
@ -135,7 +135,7 @@ impl WSMessageReceiver for FolderWSMessageReceiverImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TextBlockViewDataProcessor(Arc<TextBlockManager>);
|
struct TextBlockViewDataProcessor(Arc<TextEditorManager>);
|
||||||
impl ViewDataProcessor for TextBlockViewDataProcessor {
|
impl ViewDataProcessor for TextBlockViewDataProcessor {
|
||||||
fn initialize(&self) -> FutureResult<(), FlowyError> {
|
fn initialize(&self) -> FutureResult<(), FlowyError> {
|
||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
@ -147,7 +147,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
|
|||||||
let view_id = view_id.to_string();
|
let view_id = view_id.to_string();
|
||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
let _ = manager.create_block(view_id, repeated_revision).await?;
|
let _ = manager.create_text_block(view_id, repeated_revision).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
|
|||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
let view_id = view_id.to_string();
|
let view_id = view_id.to_string();
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
let _ = manager.delete_block(view_id)?;
|
let _ = manager.close_text_editor(view_id)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
|
|||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
let view_id = view_id.to_string();
|
let view_id = view_id.to_string();
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
let _ = manager.close_block(view_id)?;
|
let _ = manager.close_text_editor(view_id)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -174,7 +174,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
|
|||||||
let view_id = view_id.to_string();
|
let view_id = view_id.to_string();
|
||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
let editor = manager.open_block(view_id).await?;
|
let editor = manager.open_text_editor(view_id).await?;
|
||||||
let delta_bytes = Bytes::from(editor.delta_str().await?);
|
let delta_bytes = Bytes::from(editor.delta_str().await?);
|
||||||
Ok(delta_bytes)
|
Ok(delta_bytes)
|
||||||
})
|
})
|
||||||
@ -195,7 +195,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
|
|||||||
let delta_data = Bytes::from(view_data);
|
let delta_data = Bytes::from(view_data);
|
||||||
let repeated_revision: RepeatedRevision =
|
let repeated_revision: RepeatedRevision =
|
||||||
Revision::initial_revision(&user_id, &view_id, delta_data.clone()).into();
|
Revision::initial_revision(&user_id, &view_id, delta_data.clone()).into();
|
||||||
let _ = manager.create_block(view_id, repeated_revision).await?;
|
let _ = manager.create_text_block(view_id, repeated_revision).await?;
|
||||||
Ok(delta_data)
|
Ok(delta_data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use flowy_revision::{RevisionWebSocket, WSStateReceiver};
|
|||||||
use flowy_sync::entities::ws_data::ClientRevisionWSData;
|
use flowy_sync::entities::ws_data::ClientRevisionWSData;
|
||||||
use flowy_text_block::{
|
use flowy_text_block::{
|
||||||
errors::{internal_error, FlowyError},
|
errors::{internal_error, FlowyError},
|
||||||
BlockCloudService, TextBlockManager, TextBlockUser,
|
TextEditorCloudService, TextEditorManager, TextEditorUser,
|
||||||
};
|
};
|
||||||
use flowy_user::services::UserSession;
|
use flowy_user::services::UserSession;
|
||||||
use futures_core::future::BoxFuture;
|
use futures_core::future::BoxFuture;
|
||||||
@ -23,15 +23,15 @@ impl TextBlockDepsResolver {
|
|||||||
ws_conn: Arc<FlowyWebSocketConnect>,
|
ws_conn: Arc<FlowyWebSocketConnect>,
|
||||||
user_session: Arc<UserSession>,
|
user_session: Arc<UserSession>,
|
||||||
server_config: &ClientServerConfiguration,
|
server_config: &ClientServerConfiguration,
|
||||||
) -> Arc<TextBlockManager> {
|
) -> Arc<TextEditorManager> {
|
||||||
let user = Arc::new(BlockUserImpl(user_session));
|
let user = Arc::new(BlockUserImpl(user_session));
|
||||||
let rev_web_socket = Arc::new(TextBlockWebSocket(ws_conn.clone()));
|
let rev_web_socket = Arc::new(TextBlockWebSocket(ws_conn.clone()));
|
||||||
let cloud_service: Arc<dyn BlockCloudService> = match local_server {
|
let cloud_service: Arc<dyn TextEditorCloudService> = match local_server {
|
||||||
None => Arc::new(BlockHttpCloudService::new(server_config.clone())),
|
None => Arc::new(BlockHttpCloudService::new(server_config.clone())),
|
||||||
Some(local_server) => local_server,
|
Some(local_server) => local_server,
|
||||||
};
|
};
|
||||||
|
|
||||||
let manager = Arc::new(TextBlockManager::new(cloud_service, user, rev_web_socket));
|
let manager = Arc::new(TextEditorManager::new(cloud_service, user, rev_web_socket));
|
||||||
let receiver = Arc::new(DocumentWSMessageReceiverImpl(manager.clone()));
|
let receiver = Arc::new(DocumentWSMessageReceiverImpl(manager.clone()));
|
||||||
ws_conn.add_ws_message_receiver(receiver).unwrap();
|
ws_conn.add_ws_message_receiver(receiver).unwrap();
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ impl TextBlockDepsResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct BlockUserImpl(Arc<UserSession>);
|
struct BlockUserImpl(Arc<UserSession>);
|
||||||
impl TextBlockUser for BlockUserImpl {
|
impl TextEditorUser for BlockUserImpl {
|
||||||
fn user_dir(&self) -> Result<String, FlowyError> {
|
fn user_dir(&self) -> Result<String, FlowyError> {
|
||||||
let dir = self.0.user_dir().map_err(|e| FlowyError::unauthorized().context(e))?;
|
let dir = self.0.user_dir().map_err(|e| FlowyError::unauthorized().context(e))?;
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ impl RevisionWebSocket for TextBlockWebSocket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DocumentWSMessageReceiverImpl(Arc<TextBlockManager>);
|
struct DocumentWSMessageReceiverImpl(Arc<TextEditorManager>);
|
||||||
impl WSMessageReceiver for DocumentWSMessageReceiverImpl {
|
impl WSMessageReceiver for DocumentWSMessageReceiverImpl {
|
||||||
fn source(&self) -> WSChannel {
|
fn source(&self) -> WSChannel {
|
||||||
WSChannel::Document
|
WSChannel::Document
|
||||||
|
@ -11,7 +11,7 @@ use flowy_net::{
|
|||||||
local_server::LocalServer,
|
local_server::LocalServer,
|
||||||
ws::connection::{listen_on_websocket, FlowyWebSocketConnect},
|
ws::connection::{listen_on_websocket, FlowyWebSocketConnect},
|
||||||
};
|
};
|
||||||
use flowy_text_block::TextBlockManager;
|
use flowy_text_block::TextEditorManager;
|
||||||
use flowy_user::services::{notifier::UserStatus, UserSession, UserSessionConfig};
|
use flowy_user::services::{notifier::UserStatus, UserSession, UserSessionConfig};
|
||||||
use lib_dispatch::prelude::*;
|
use lib_dispatch::prelude::*;
|
||||||
use lib_dispatch::runtime::tokio_default_runtime;
|
use lib_dispatch::runtime::tokio_default_runtime;
|
||||||
@ -89,7 +89,7 @@ pub struct FlowySDK {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
config: FlowySDKConfig,
|
config: FlowySDKConfig,
|
||||||
pub user_session: Arc<UserSession>,
|
pub user_session: Arc<UserSession>,
|
||||||
pub text_block_manager: Arc<TextBlockManager>,
|
pub text_block_manager: Arc<TextEditorManager>,
|
||||||
pub folder_manager: Arc<FolderManager>,
|
pub folder_manager: Arc<FolderManager>,
|
||||||
pub grid_manager: Arc<GridManager>,
|
pub grid_manager: Arc<GridManager>,
|
||||||
pub dispatcher: Arc<EventDispatcher>,
|
pub dispatcher: Arc<EventDispatcher>,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use flowy_folder::manager::FolderManager;
|
use flowy_folder::manager::FolderManager;
|
||||||
use flowy_grid::manager::GridManager;
|
use flowy_grid::manager::GridManager;
|
||||||
use flowy_net::ws::connection::FlowyWebSocketConnect;
|
use flowy_net::ws::connection::FlowyWebSocketConnect;
|
||||||
use flowy_text_block::TextBlockManager;
|
use flowy_text_block::TextEditorManager;
|
||||||
use flowy_user::services::UserSession;
|
use flowy_user::services::UserSession;
|
||||||
use lib_dispatch::prelude::Module;
|
use lib_dispatch::prelude::Module;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -11,7 +11,7 @@ pub fn mk_modules(
|
|||||||
folder_manager: &Arc<FolderManager>,
|
folder_manager: &Arc<FolderManager>,
|
||||||
grid_manager: &Arc<GridManager>,
|
grid_manager: &Arc<GridManager>,
|
||||||
user_session: &Arc<UserSession>,
|
user_session: &Arc<UserSession>,
|
||||||
text_block_manager: &Arc<TextBlockManager>,
|
text_block_manager: &Arc<TextEditorManager>,
|
||||||
) -> Vec<Module> {
|
) -> Vec<Module> {
|
||||||
let user_module = mk_user_module(user_session.clone());
|
let user_module = mk_user_module(user_session.clone());
|
||||||
let folder_module = mk_folder_module(folder_manager.clone());
|
let folder_module = mk_folder_module(folder_manager.clone());
|
||||||
@ -43,6 +43,6 @@ fn mk_grid_module(grid_manager: Arc<GridManager>) -> Module {
|
|||||||
flowy_grid::event_map::create(grid_manager)
|
flowy_grid::event_map::create(grid_manager)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mk_text_block_module(text_block_manager: Arc<TextBlockManager>) -> Module {
|
fn mk_text_block_module(text_block_manager: Arc<TextEditorManager>) -> Module {
|
||||||
flowy_text_block::event_map::create(text_block_manager)
|
flowy_text_block::event_map::create(text_block_manager)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use crate::web_socket::EditorCommandSender;
|
|||||||
use crate::{
|
use crate::{
|
||||||
errors::FlowyError,
|
errors::FlowyError,
|
||||||
queue::{EditBlockQueue, EditorCommand},
|
queue::{EditBlockQueue, EditorCommand},
|
||||||
TextBlockUser,
|
TextEditorUser,
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flowy_error::{internal_error, FlowyResult};
|
use flowy_error::{internal_error, FlowyResult};
|
||||||
@ -24,7 +24,6 @@ use tokio::sync::{mpsc, oneshot};
|
|||||||
|
|
||||||
pub struct TextBlockEditor {
|
pub struct TextBlockEditor {
|
||||||
pub doc_id: String,
|
pub doc_id: String,
|
||||||
#[allow(dead_code)]
|
|
||||||
rev_manager: Arc<RevisionManager>,
|
rev_manager: Arc<RevisionManager>,
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
ws_manager: Arc<flowy_revision::RevisionWebSocketManager>,
|
ws_manager: Arc<flowy_revision::RevisionWebSocketManager>,
|
||||||
@ -35,7 +34,7 @@ impl TextBlockEditor {
|
|||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub(crate) async fn new(
|
pub(crate) async fn new(
|
||||||
doc_id: &str,
|
doc_id: &str,
|
||||||
user: Arc<dyn TextBlockUser>,
|
user: Arc<dyn TextEditorUser>,
|
||||||
mut rev_manager: RevisionManager,
|
mut rev_manager: RevisionManager,
|
||||||
rev_web_socket: Arc<dyn RevisionWebSocket>,
|
rev_web_socket: Arc<dyn RevisionWebSocket>,
|
||||||
cloud_service: Arc<dyn RevisionCloudService>,
|
cloud_service: Arc<dyn RevisionCloudService>,
|
||||||
@ -194,7 +193,7 @@ impl std::ops::Drop for TextBlockEditor {
|
|||||||
|
|
||||||
// The edit queue will exit after the EditorCommandSender was dropped.
|
// The edit queue will exit after the EditorCommandSender was dropped.
|
||||||
fn spawn_edit_queue(
|
fn spawn_edit_queue(
|
||||||
user: Arc<dyn TextBlockUser>,
|
user: Arc<dyn TextEditorUser>,
|
||||||
rev_manager: Arc<RevisionManager>,
|
rev_manager: Arc<RevisionManager>,
|
||||||
delta: TextDelta,
|
delta: TextDelta,
|
||||||
) -> EditorCommandSender {
|
) -> EditorCommandSender {
|
||||||
|
@ -29,6 +29,52 @@ impl std::convert::From<i32> for ExportType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, ProtoBuf)]
|
||||||
|
pub struct EditPayloadPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub text_block_id: String,
|
||||||
|
|
||||||
|
// Encode in JSON format
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub operations: String,
|
||||||
|
|
||||||
|
// Encode in JSON format
|
||||||
|
#[pb(index = 3)]
|
||||||
|
pub delta: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct EditParams {
|
||||||
|
pub text_block_id: String,
|
||||||
|
|
||||||
|
// Encode in JSON format
|
||||||
|
pub operations: String,
|
||||||
|
|
||||||
|
// Encode in JSON format
|
||||||
|
pub delta: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<EditParams> for EditPayloadPB {
|
||||||
|
type Error = ErrorCode;
|
||||||
|
fn try_into(self) -> Result<EditParams, Self::Error> {
|
||||||
|
Ok(EditParams {
|
||||||
|
text_block_id: self.text_block_id,
|
||||||
|
operations: self.operations,
|
||||||
|
delta: self.delta,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, ProtoBuf)]
|
||||||
|
pub struct TextBlockPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub text_block_id: String,
|
||||||
|
|
||||||
|
/// Encode in JSON format
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub snapshot: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, ProtoBuf)]
|
#[derive(Default, ProtoBuf)]
|
||||||
pub struct ExportPayloadPB {
|
pub struct ExportPayloadPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
@ -1,39 +1,40 @@
|
|||||||
use crate::entities::{ExportDataPB, ExportParams, ExportPayloadPB};
|
use crate::entities::{EditParams, EditPayloadPB, ExportDataPB, ExportParams, ExportPayloadPB, TextBlockPB};
|
||||||
use crate::TextBlockManager;
|
use crate::TextEditorManager;
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::FlowyError;
|
||||||
use flowy_sync::entities::text_block::{TextBlockDeltaPB, TextBlockIdPB};
|
use flowy_sync::entities::text_block::{TextBlockDeltaPB, TextBlockIdPB};
|
||||||
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
|
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub(crate) async fn get_block_data_handler(
|
pub(crate) async fn get_text_block_handler(
|
||||||
data: Data<TextBlockIdPB>,
|
data: Data<TextBlockIdPB>,
|
||||||
manager: AppData<Arc<TextBlockManager>>,
|
manager: AppData<Arc<TextEditorManager>>,
|
||||||
) -> DataResult<TextBlockDeltaPB, FlowyError> {
|
) -> DataResult<TextBlockPB, FlowyError> {
|
||||||
let block_id: TextBlockIdPB = data.into_inner();
|
let text_block_id: TextBlockIdPB = data.into_inner();
|
||||||
let editor = manager.open_block(&block_id).await?;
|
let editor = manager.open_text_editor(&text_block_id).await?;
|
||||||
let delta_str = editor.delta_str().await?;
|
let delta_str = editor.delta_str().await?;
|
||||||
data_result(TextBlockDeltaPB {
|
data_result(TextBlockPB {
|
||||||
block_id: block_id.into(),
|
text_block_id: text_block_id.into(),
|
||||||
delta_str,
|
snapshot: delta_str,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn apply_delta_handler(
|
pub(crate) async fn apply_edit_handler(
|
||||||
data: Data<TextBlockDeltaPB>,
|
data: Data<EditPayloadPB>,
|
||||||
manager: AppData<Arc<TextBlockManager>>,
|
manager: AppData<Arc<TextEditorManager>>,
|
||||||
) -> DataResult<TextBlockDeltaPB, FlowyError> {
|
) -> Result<(), FlowyError> {
|
||||||
let block_delta = manager.receive_local_delta(data.into_inner()).await?;
|
let params: EditParams = data.into_inner().try_into()?;
|
||||||
data_result(block_delta)
|
let _ = manager.apply_edit(params).await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||||
pub(crate) async fn export_handler(
|
pub(crate) async fn export_handler(
|
||||||
data: Data<ExportPayloadPB>,
|
data: Data<ExportPayloadPB>,
|
||||||
manager: AppData<Arc<TextBlockManager>>,
|
manager: AppData<Arc<TextEditorManager>>,
|
||||||
) -> DataResult<ExportDataPB, FlowyError> {
|
) -> DataResult<ExportDataPB, FlowyError> {
|
||||||
let params: ExportParams = data.into_inner().try_into()?;
|
let params: ExportParams = data.into_inner().try_into()?;
|
||||||
let editor = manager.open_block(¶ms.view_id).await?;
|
let editor = manager.open_text_editor(¶ms.view_id).await?;
|
||||||
let delta_json = editor.delta_str().await?;
|
let delta_json = editor.delta_str().await?;
|
||||||
data_result(ExportDataPB {
|
data_result(ExportDataPB {
|
||||||
data: delta_json,
|
data: delta_json,
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use crate::event_handler::*;
|
use crate::event_handler::*;
|
||||||
use crate::TextBlockManager;
|
use crate::TextEditorManager;
|
||||||
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
|
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
|
||||||
use lib_dispatch::prelude::Module;
|
use lib_dispatch::prelude::Module;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use strum_macros::Display;
|
use strum_macros::Display;
|
||||||
|
|
||||||
pub fn create(block_manager: Arc<TextBlockManager>) -> Module {
|
pub fn create(block_manager: Arc<TextEditorManager>) -> Module {
|
||||||
let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(block_manager);
|
let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(block_manager);
|
||||||
|
|
||||||
module = module
|
module = module
|
||||||
.event(TextBlockEvent::GetBlockData, get_block_data_handler)
|
.event(TextBlockEvent::GetTextBlock, get_text_block_handler)
|
||||||
.event(TextBlockEvent::ApplyDelta, apply_delta_handler)
|
.event(TextBlockEvent::ApplyEdit, apply_edit_handler)
|
||||||
.event(TextBlockEvent::ExportDocument, export_handler);
|
.event(TextBlockEvent::ExportDocument, export_handler);
|
||||||
|
|
||||||
module
|
module
|
||||||
@ -19,11 +19,11 @@ pub fn create(block_manager: Arc<TextBlockManager>) -> Module {
|
|||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||||
#[event_err = "FlowyError"]
|
#[event_err = "FlowyError"]
|
||||||
pub enum TextBlockEvent {
|
pub enum TextBlockEvent {
|
||||||
#[event(input = "TextBlockIdPB", output = "TextBlockDeltaPB")]
|
#[event(input = "TextBlockIdPB", output = "TextBlockPB")]
|
||||||
GetBlockData = 0,
|
GetTextBlock = 0,
|
||||||
|
|
||||||
#[event(input = "TextBlockDeltaPB", output = "TextBlockDeltaPB")]
|
#[event(input = "EditPayloadPB")]
|
||||||
ApplyDelta = 1,
|
ApplyEdit = 1,
|
||||||
|
|
||||||
#[event(input = "ExportPayloadPB", output = "ExportDataPB")]
|
#[event(input = "ExportPayloadPB", output = "ExportDataPB")]
|
||||||
ExportDocument = 2,
|
ExportDocument = 2,
|
||||||
|
@ -18,10 +18,10 @@ use crate::errors::FlowyError;
|
|||||||
use flowy_sync::entities::text_block::{CreateTextBlockParams, DocumentPB, ResetTextBlockParams, TextBlockIdPB};
|
use flowy_sync::entities::text_block::{CreateTextBlockParams, DocumentPB, ResetTextBlockParams, TextBlockIdPB};
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
|
|
||||||
pub trait BlockCloudService: Send + Sync {
|
pub trait TextEditorCloudService: Send + Sync {
|
||||||
fn create_block(&self, token: &str, params: CreateTextBlockParams) -> FutureResult<(), FlowyError>;
|
fn create_text_block(&self, token: &str, params: CreateTextBlockParams) -> FutureResult<(), FlowyError>;
|
||||||
|
|
||||||
fn read_block(&self, token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError>;
|
fn read_text_block(&self, token: &str, params: TextBlockIdPB) -> FutureResult<Option<DocumentPB>, FlowyError>;
|
||||||
|
|
||||||
fn update_block(&self, token: &str, params: ResetTextBlockParams) -> FutureResult<(), FlowyError>;
|
fn update_text_block(&self, token: &str, params: ResetTextBlockParams) -> FutureResult<(), FlowyError>;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
use crate::entities::{EditParams, EditPayloadPB};
|
||||||
use crate::queue::TextBlockRevisionCompactor;
|
use crate::queue::TextBlockRevisionCompactor;
|
||||||
use crate::{editor::TextBlockEditor, errors::FlowyError, BlockCloudService};
|
use crate::{editor::TextBlockEditor, errors::FlowyError, TextEditorCloudService};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use flowy_database::ConnectionPool;
|
use flowy_database::ConnectionPool;
|
||||||
@ -16,30 +17,30 @@ use flowy_sync::entities::{
|
|||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
use std::{convert::TryInto, sync::Arc};
|
use std::{convert::TryInto, sync::Arc};
|
||||||
|
|
||||||
pub trait TextBlockUser: Send + Sync {
|
pub trait TextEditorUser: Send + Sync {
|
||||||
fn user_dir(&self) -> Result<String, FlowyError>;
|
fn user_dir(&self) -> Result<String, FlowyError>;
|
||||||
fn user_id(&self) -> Result<String, FlowyError>;
|
fn user_id(&self) -> Result<String, FlowyError>;
|
||||||
fn token(&self) -> Result<String, FlowyError>;
|
fn token(&self) -> Result<String, FlowyError>;
|
||||||
fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>;
|
fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextBlockManager {
|
pub struct TextEditorManager {
|
||||||
cloud_service: Arc<dyn BlockCloudService>,
|
cloud_service: Arc<dyn TextEditorCloudService>,
|
||||||
rev_web_socket: Arc<dyn RevisionWebSocket>,
|
rev_web_socket: Arc<dyn RevisionWebSocket>,
|
||||||
editor_map: Arc<TextBlockEditorMap>,
|
editor_map: Arc<TextEditorMap>,
|
||||||
user: Arc<dyn TextBlockUser>,
|
user: Arc<dyn TextEditorUser>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextBlockManager {
|
impl TextEditorManager {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cloud_service: Arc<dyn BlockCloudService>,
|
cloud_service: Arc<dyn TextEditorCloudService>,
|
||||||
text_block_user: Arc<dyn TextBlockUser>,
|
text_block_user: Arc<dyn TextEditorUser>,
|
||||||
rev_web_socket: Arc<dyn RevisionWebSocket>,
|
rev_web_socket: Arc<dyn RevisionWebSocket>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cloud_service,
|
cloud_service,
|
||||||
rev_web_socket,
|
rev_web_socket,
|
||||||
editor_map: Arc::new(TextBlockEditorMap::new()),
|
editor_map: Arc::new(TextEditorMap::new()),
|
||||||
user: text_block_user,
|
user: text_block_user,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,45 +51,47 @@ impl TextBlockManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self, block_id), fields(block_id), err)]
|
#[tracing::instrument(level = "trace", skip(self, editor_id), fields(editor_id), err)]
|
||||||
pub async fn open_block<T: AsRef<str>>(&self, block_id: T) -> Result<Arc<TextBlockEditor>, FlowyError> {
|
pub async fn open_text_editor<T: AsRef<str>>(&self, editor_id: T) -> Result<Arc<TextBlockEditor>, FlowyError> {
|
||||||
let block_id = block_id.as_ref();
|
let editor_id = editor_id.as_ref();
|
||||||
tracing::Span::current().record("block_id", &block_id);
|
tracing::Span::current().record("editor_id", &editor_id);
|
||||||
self.get_block_editor(block_id).await
|
self.get_text_editor(editor_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self, block_id), fields(block_id), err)]
|
#[tracing::instrument(level = "trace", skip(self, editor_id), fields(editor_id), err)]
|
||||||
pub fn close_block<T: AsRef<str>>(&self, block_id: T) -> Result<(), FlowyError> {
|
pub fn close_text_editor<T: AsRef<str>>(&self, editor_id: T) -> Result<(), FlowyError> {
|
||||||
let block_id = block_id.as_ref();
|
let editor_id = editor_id.as_ref();
|
||||||
tracing::Span::current().record("block_id", &block_id);
|
tracing::Span::current().record("editor_id", &editor_id);
|
||||||
self.editor_map.remove(block_id);
|
self.editor_map.remove(editor_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(self, doc_id), fields(doc_id), err)]
|
#[tracing::instrument(level = "debug", skip(self, delta), err)]
|
||||||
pub fn delete_block<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.editor_map.remove(doc_id);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(self, delta), fields(doc_id = %delta.block_id), err)]
|
|
||||||
pub async fn receive_local_delta(&self, delta: TextBlockDeltaPB) -> Result<TextBlockDeltaPB, FlowyError> {
|
pub async fn receive_local_delta(&self, delta: TextBlockDeltaPB) -> Result<TextBlockDeltaPB, FlowyError> {
|
||||||
let editor = self.get_block_editor(&delta.block_id).await?;
|
let editor = self.get_text_editor(&delta.text_block_id).await?;
|
||||||
let _ = editor.compose_local_delta(Bytes::from(delta.delta_str)).await?;
|
let _ = editor.compose_local_delta(Bytes::from(delta.delta_str)).await?;
|
||||||
let document_json = editor.delta_str().await?;
|
let delta_str = editor.delta_str().await?;
|
||||||
Ok(TextBlockDeltaPB {
|
Ok(TextBlockDeltaPB {
|
||||||
block_id: delta.block_id.clone(),
|
text_block_id: delta.text_block_id.clone(),
|
||||||
delta_str: document_json,
|
delta_str,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_block<T: AsRef<str>>(&self, doc_id: T, revisions: RepeatedRevision) -> FlowyResult<()> {
|
pub async fn apply_edit(&self, params: EditParams) -> FlowyResult<()> {
|
||||||
let doc_id = doc_id.as_ref().to_owned();
|
let editor = self.get_text_editor(¶ms.text_block_id).await?;
|
||||||
|
let _ = editor.compose_local_delta(Bytes::from(params.delta)).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_text_block<T: AsRef<str>>(
|
||||||
|
&self,
|
||||||
|
text_block_id: T,
|
||||||
|
revisions: RepeatedRevision,
|
||||||
|
) -> FlowyResult<()> {
|
||||||
|
let doc_id = text_block_id.as_ref().to_owned();
|
||||||
let db_pool = self.user.db_pool()?;
|
let db_pool = self.user.db_pool()?;
|
||||||
// Maybe we could save the block to disk without creating the RevisionManager
|
// Maybe we could save the block to disk without creating the RevisionManager
|
||||||
let rev_manager = self.make_rev_manager(&doc_id, db_pool)?;
|
let rev_manager = self.make_text_block_rev_manager(&doc_id, db_pool)?;
|
||||||
let _ = rev_manager.reset_object(revisions).await?;
|
let _ = rev_manager.reset_object(revisions).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -110,26 +113,26 @@ impl TextBlockManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextBlockManager {
|
impl TextEditorManager {
|
||||||
async fn get_block_editor(&self, block_id: &str) -> FlowyResult<Arc<TextBlockEditor>> {
|
async fn get_text_editor(&self, block_id: &str) -> FlowyResult<Arc<TextBlockEditor>> {
|
||||||
match self.editor_map.get(block_id) {
|
match self.editor_map.get(block_id) {
|
||||||
None => {
|
None => {
|
||||||
let db_pool = self.user.db_pool()?;
|
let db_pool = self.user.db_pool()?;
|
||||||
self.make_text_block_editor(block_id, db_pool).await
|
self.make_text_editor(block_id, db_pool).await
|
||||||
}
|
}
|
||||||
Some(editor) => Ok(editor),
|
Some(editor) => Ok(editor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self, pool), err)]
|
#[tracing::instrument(level = "trace", skip(self, pool), err)]
|
||||||
async fn make_text_block_editor(
|
async fn make_text_editor(
|
||||||
&self,
|
&self,
|
||||||
block_id: &str,
|
block_id: &str,
|
||||||
pool: Arc<ConnectionPool>,
|
pool: Arc<ConnectionPool>,
|
||||||
) -> Result<Arc<TextBlockEditor>, FlowyError> {
|
) -> Result<Arc<TextBlockEditor>, FlowyError> {
|
||||||
let user = self.user.clone();
|
let user = self.user.clone();
|
||||||
let token = self.user.token()?;
|
let token = self.user.token()?;
|
||||||
let rev_manager = self.make_rev_manager(block_id, pool.clone())?;
|
let rev_manager = self.make_text_block_rev_manager(block_id, pool.clone())?;
|
||||||
let cloud_service = Arc::new(TextBlockRevisionCloudService {
|
let cloud_service = Arc::new(TextBlockRevisionCloudService {
|
||||||
token,
|
token,
|
||||||
server: self.cloud_service.clone(),
|
server: self.cloud_service.clone(),
|
||||||
@ -140,7 +143,11 @@ impl TextBlockManager {
|
|||||||
Ok(doc_editor)
|
Ok(doc_editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_rev_manager(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<RevisionManager, FlowyError> {
|
fn make_text_block_rev_manager(
|
||||||
|
&self,
|
||||||
|
doc_id: &str,
|
||||||
|
pool: Arc<ConnectionPool>,
|
||||||
|
) -> Result<RevisionManager, FlowyError> {
|
||||||
let user_id = self.user.user_id()?;
|
let user_id = self.user.user_id()?;
|
||||||
let disk_cache = SQLiteTextBlockRevisionPersistence::new(&user_id, pool.clone());
|
let disk_cache = SQLiteTextBlockRevisionPersistence::new(&user_id, pool.clone());
|
||||||
let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache);
|
let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache);
|
||||||
@ -161,7 +168,7 @@ impl TextBlockManager {
|
|||||||
|
|
||||||
struct TextBlockRevisionCloudService {
|
struct TextBlockRevisionCloudService {
|
||||||
token: String,
|
token: String,
|
||||||
server: Arc<dyn BlockCloudService>,
|
server: Arc<dyn TextEditorCloudService>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RevisionCloudService for TextBlockRevisionCloudService {
|
impl RevisionCloudService for TextBlockRevisionCloudService {
|
||||||
@ -173,7 +180,7 @@ impl RevisionCloudService for TextBlockRevisionCloudService {
|
|||||||
let user_id = user_id.to_string();
|
let user_id = user_id.to_string();
|
||||||
|
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
match server.read_block(&token, params).await? {
|
match server.read_text_block(&token, params).await? {
|
||||||
None => Err(FlowyError::record_not_found().context("Remote doesn't have this document")),
|
None => Err(FlowyError::record_not_found().context("Remote doesn't have this document")),
|
||||||
Some(doc) => {
|
Some(doc) => {
|
||||||
let delta_data = Bytes::from(doc.text.clone());
|
let delta_data = Bytes::from(doc.text.clone());
|
||||||
@ -193,36 +200,36 @@ impl RevisionCloudService for TextBlockRevisionCloudService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextBlockEditorMap {
|
pub struct TextEditorMap {
|
||||||
inner: DashMap<String, Arc<TextBlockEditor>>,
|
inner: DashMap<String, Arc<TextBlockEditor>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextBlockEditorMap {
|
impl TextEditorMap {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self { inner: DashMap::new() }
|
Self { inner: DashMap::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn insert(&self, block_id: &str, doc: &Arc<TextBlockEditor>) {
|
pub(crate) fn insert(&self, editor_id: &str, doc: &Arc<TextBlockEditor>) {
|
||||||
if self.inner.contains_key(block_id) {
|
if self.inner.contains_key(editor_id) {
|
||||||
log::warn!("Doc:{} already exists in cache", block_id);
|
log::warn!("Doc:{} already exists in cache", editor_id);
|
||||||
}
|
}
|
||||||
self.inner.insert(block_id.to_string(), doc.clone());
|
self.inner.insert(editor_id.to_string(), doc.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get(&self, block_id: &str) -> Option<Arc<TextBlockEditor>> {
|
pub(crate) fn get(&self, editor_id: &str) -> Option<Arc<TextBlockEditor>> {
|
||||||
Some(self.inner.get(block_id)?.clone())
|
Some(self.inner.get(editor_id)?.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove(&self, block_id: &str) {
|
pub(crate) fn remove(&self, editor_id: &str) {
|
||||||
if let Some(editor) = self.get(block_id) {
|
if let Some(editor) = self.get(editor_id) {
|
||||||
editor.stop()
|
editor.stop()
|
||||||
}
|
}
|
||||||
self.inner.remove(block_id);
|
self.inner.remove(editor_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(web_socket, handlers))]
|
#[tracing::instrument(level = "trace", skip(web_socket, handlers))]
|
||||||
fn listen_ws_state_changed(web_socket: Arc<dyn RevisionWebSocket>, handlers: Arc<TextBlockEditorMap>) {
|
fn listen_ws_state_changed(web_socket: Arc<dyn RevisionWebSocket>, handlers: Arc<TextEditorMap>) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut notify = web_socket.subscribe_state_changed().await;
|
let mut notify = web_socket.subscribe_state_changed().await;
|
||||||
while let Ok(state) = notify.recv().await {
|
while let Ok(state) = notify.recv().await {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::web_socket::EditorCommandReceiver;
|
use crate::web_socket::EditorCommandReceiver;
|
||||||
use crate::TextBlockUser;
|
use crate::TextEditorUser;
|
||||||
use async_stream::stream;
|
use async_stream::stream;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
@ -23,14 +23,14 @@ use tokio::sync::{oneshot, RwLock};
|
|||||||
// serial.
|
// serial.
|
||||||
pub(crate) struct EditBlockQueue {
|
pub(crate) struct EditBlockQueue {
|
||||||
document: Arc<RwLock<ClientDocument>>,
|
document: Arc<RwLock<ClientDocument>>,
|
||||||
user: Arc<dyn TextBlockUser>,
|
user: Arc<dyn TextEditorUser>,
|
||||||
rev_manager: Arc<RevisionManager>,
|
rev_manager: Arc<RevisionManager>,
|
||||||
receiver: Option<EditorCommandReceiver>,
|
receiver: Option<EditorCommandReceiver>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditBlockQueue {
|
impl EditBlockQueue {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
user: Arc<dyn TextBlockUser>,
|
user: Arc<dyn TextEditorUser>,
|
||||||
rev_manager: Arc<RevisionManager>,
|
rev_manager: Arc<RevisionManager>,
|
||||||
delta: TextDelta,
|
delta: TextDelta,
|
||||||
receiver: EditorCommandReceiver,
|
receiver: EditorCommandReceiver,
|
||||||
|
@ -27,7 +27,7 @@ impl TextBlockEditorTest {
|
|||||||
let sdk = FlowySDKTest::default();
|
let sdk = FlowySDKTest::default();
|
||||||
let _ = sdk.init_user().await;
|
let _ = sdk.init_user().await;
|
||||||
let test = ViewTest::new_text_block_view(&sdk).await;
|
let test = ViewTest::new_text_block_view(&sdk).await;
|
||||||
let editor = sdk.text_block_manager.open_block(&test.view.id).await.unwrap();
|
let editor = sdk.text_block_manager.open_text_editor(&test.view.id).await.unwrap();
|
||||||
Self { sdk, editor }
|
Self { sdk, editor }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ pub struct ResetTextBlockParams {
|
|||||||
#[derive(ProtoBuf, Default, Debug, Clone)]
|
#[derive(ProtoBuf, Default, Debug, Clone)]
|
||||||
pub struct TextBlockDeltaPB {
|
pub struct TextBlockDeltaPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
pub block_id: String,
|
pub text_block_id: String,
|
||||||
|
|
||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
pub delta_str: String,
|
pub delta_str: String,
|
||||||
|
@ -6,7 +6,7 @@ use crate::errors::OTError;
|
|||||||
use crate::text_delta::TextDelta;
|
use crate::text_delta::TextDelta;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
pub struct NodeData {
|
pub struct NodeData {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub node_type: String,
|
pub node_type: String,
|
||||||
|
@ -216,7 +216,9 @@ impl NodeTree {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if index == parent.children(&self.arena).count() {
|
/// Append the node to the end of the children list if index greater or equal to the
|
||||||
|
/// length of the children.
|
||||||
|
if index >= parent.children(&self.arena).count() {
|
||||||
self.append_nodes(&parent, nodes);
|
self.append_nodes(&parent, nodes);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use crate::core::attributes::Attributes;
|
use crate::core::attributes::Attributes;
|
||||||
use crate::core::document::path::Path;
|
use crate::core::document::path::Path;
|
||||||
use crate::core::{NodeBodyChangeset, NodeData};
|
use crate::core::{NodeBodyChangeset, NodeData, OperationTransform};
|
||||||
use crate::errors::OTError;
|
use crate::errors::OTError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(tag = "op")]
|
#[serde(tag = "op")]
|
||||||
pub enum NodeOperation {
|
pub enum NodeOperation {
|
||||||
#[serde(rename = "insert")]
|
#[serde(rename = "insert")]
|
||||||
@ -27,8 +27,37 @@ pub enum NodeOperation {
|
|||||||
Delete { path: Path, nodes: Vec<NodeData> },
|
Delete { path: Path, nodes: Vec<NodeData> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// impl OperationTransform for NodeOperation {
|
||||||
|
// fn compose(&self, other: &Self) -> Result<Self, OTError>
|
||||||
|
// where
|
||||||
|
// Self: Sized,
|
||||||
|
// {
|
||||||
|
// match self {
|
||||||
|
// NodeOperation::Insert { path, nodes } => {
|
||||||
|
// let new_path = Path::transform(path, other.path(), nodes.len() as i64);
|
||||||
|
// Ok((self.clone(), other.clone_with_new_path(new_path)))
|
||||||
|
// }
|
||||||
|
// NodeOperation::Delete { path, nodes } => {
|
||||||
|
// let new_path = Path::transform(path, other.path(), nodes.len() as i64);
|
||||||
|
// other.clone_with_new_path(new_path)
|
||||||
|
// }
|
||||||
|
// _ => other.clone(),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn transform(&self, other: &Self) -> Result<(Self, Self), OTError>
|
||||||
|
// where
|
||||||
|
// Self: Sized,
|
||||||
|
// {
|
||||||
|
// todo!()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn invert(&self, other: &Self) -> Self {
|
||||||
|
// todo!()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
impl NodeOperation {
|
impl NodeOperation {
|
||||||
pub fn path(&self) -> &Path {
|
pub fn get_path(&self) -> &Path {
|
||||||
match self {
|
match self {
|
||||||
NodeOperation::Insert { path, .. } => path,
|
NodeOperation::Insert { path, .. } => path,
|
||||||
NodeOperation::UpdateAttributes { path, .. } => path,
|
NodeOperation::UpdateAttributes { path, .. } => path,
|
||||||
@ -36,6 +65,19 @@ impl NodeOperation {
|
|||||||
NodeOperation::UpdateBody { path, .. } => path,
|
NodeOperation::UpdateBody { path, .. } => path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mut_path<F>(&mut self, f: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Path),
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
NodeOperation::Insert { path, .. } => f(path),
|
||||||
|
NodeOperation::UpdateAttributes { path, .. } => f(path),
|
||||||
|
NodeOperation::Delete { path, .. } => f(path),
|
||||||
|
NodeOperation::UpdateBody { path, .. } => f(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn invert(&self) -> NodeOperation {
|
pub fn invert(&self) -> NodeOperation {
|
||||||
match self {
|
match self {
|
||||||
NodeOperation::Insert { path, nodes } => NodeOperation::Delete {
|
NodeOperation::Insert { path, nodes } => NodeOperation::Delete {
|
||||||
@ -61,47 +103,73 @@ impl NodeOperation {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn clone_with_new_path(&self, path: Path) -> NodeOperation {
|
|
||||||
|
/// Transform the `other` operation into a new operation that carries the changes made by
|
||||||
|
/// the current operation.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `other`: The operation that is going to be transformed
|
||||||
|
///
|
||||||
|
/// returns: NodeOperation
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use lib_ot::core::{NodeDataBuilder, NodeOperation, Path};
|
||||||
|
/// let node_1 = NodeDataBuilder::new("text_1").build();
|
||||||
|
/// let node_2 = NodeDataBuilder::new("text_2").build();
|
||||||
|
///
|
||||||
|
/// let op_1 = NodeOperation::Insert {
|
||||||
|
/// path: Path(vec![0, 1]),
|
||||||
|
/// nodes: vec![node_1],
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let mut op_2 = NodeOperation::Insert {
|
||||||
|
/// path: Path(vec![0, 1]),
|
||||||
|
/// nodes: vec![node_2],
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// assert_eq!(serde_json::to_string(&op_2).unwrap(), r#"{"op":"insert","path":[0,1],
|
||||||
|
/// "nodes":[{"type":"text_2"}]}"#);
|
||||||
|
///
|
||||||
|
/// let new_op = op_1.transform(&op_2);
|
||||||
|
/// assert_eq!(serde_json::to_string(&new_op).unwrap(), r#"{"op":"insert","path":[0,2],
|
||||||
|
/// "nodes":[{"type":"text_2"}]}"#);
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
pub fn transform(&self, other: &NodeOperation) -> NodeOperation {
|
||||||
|
let mut other = other.clone();
|
||||||
match self {
|
match self {
|
||||||
NodeOperation::Insert { nodes, .. } => NodeOperation::Insert {
|
NodeOperation::Insert { path, nodes } => {
|
||||||
path,
|
let new_path = Path::transform(path, other.get_path(), nodes.len() as i64);
|
||||||
nodes: nodes.clone(),
|
other.mut_path(|path| *path = new_path);
|
||||||
},
|
|
||||||
NodeOperation::UpdateAttributes {
|
|
||||||
attributes,
|
|
||||||
old_attributes,
|
|
||||||
..
|
|
||||||
} => NodeOperation::UpdateAttributes {
|
|
||||||
path,
|
|
||||||
attributes: attributes.clone(),
|
|
||||||
old_attributes: old_attributes.clone(),
|
|
||||||
},
|
|
||||||
NodeOperation::Delete { nodes, .. } => NodeOperation::Delete {
|
|
||||||
path,
|
|
||||||
nodes: nodes.clone(),
|
|
||||||
},
|
|
||||||
NodeOperation::UpdateBody { path, changeset } => NodeOperation::UpdateBody {
|
|
||||||
path: path.clone(),
|
|
||||||
changeset: changeset.clone(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn transform(a: &NodeOperation, b: &NodeOperation) -> NodeOperation {
|
|
||||||
match a {
|
|
||||||
NodeOperation::Insert { path: a_path, nodes } => {
|
|
||||||
let new_path = Path::transform(a_path, b.path(), nodes.len() as i64);
|
|
||||||
b.clone_with_new_path(new_path)
|
|
||||||
}
|
}
|
||||||
NodeOperation::Delete { path: a_path, nodes } => {
|
NodeOperation::Delete { path: a_path, nodes } => {
|
||||||
let new_path = Path::transform(a_path, b.path(), nodes.len() as i64);
|
let new_path = Path::transform(a_path, other.get_path(), nodes.len() as i64);
|
||||||
b.clone_with_new_path(new_path)
|
other.mut_path(|path| *path = new_path);
|
||||||
}
|
}
|
||||||
_ => b.clone(),
|
_ => {}
|
||||||
|
}
|
||||||
|
other
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mut_transform(&self, other: &mut NodeOperation) {
|
||||||
|
match self {
|
||||||
|
NodeOperation::Insert { path, nodes } => {
|
||||||
|
let new_path = Path::transform(path, other.get_path(), nodes.len() as i64);
|
||||||
|
other.mut_path(|path| *path = new_path);
|
||||||
|
}
|
||||||
|
NodeOperation::Delete { path: a_path, nodes } => {
|
||||||
|
let new_path = Path::transform(a_path, other.get_path(), nodes.len() as i64);
|
||||||
|
other.mut_path(|path| *path = new_path);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
pub struct NodeOperationList {
|
pub struct NodeOperationList {
|
||||||
operations: Vec<NodeOperation>,
|
operations: Vec<NodeOperation>,
|
||||||
}
|
}
|
||||||
@ -126,6 +194,12 @@ impl std::ops::DerefMut for NodeOperationList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<Vec<NodeOperation>> for NodeOperationList {
|
||||||
|
fn from(operations: Vec<NodeOperation>) -> Self {
|
||||||
|
Self { operations }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NodeOperationList {
|
impl NodeOperationList {
|
||||||
pub fn new(operations: Vec<NodeOperation>) -> Self {
|
pub fn new(operations: Vec<NodeOperation>) -> Self {
|
||||||
Self { operations }
|
Self { operations }
|
||||||
|
@ -5,18 +5,35 @@ use indextree::NodeId;
|
|||||||
|
|
||||||
use super::{NodeBodyChangeset, NodeOperationList};
|
use super::{NodeBodyChangeset, NodeOperationList};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
operations: NodeOperationList,
|
operations: NodeOperationList,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
pub fn new(operations: NodeOperationList) -> Transaction {
|
pub fn new() -> Self {
|
||||||
Transaction { operations }
|
Transaction {
|
||||||
|
operations: vec![].into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_operations<T: Into<NodeOperationList>>(operations: T) -> Self {
|
||||||
|
Self {
|
||||||
|
operations: operations.into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_operations(self) -> Vec<NodeOperation> {
|
pub fn into_operations(self) -> Vec<NodeOperation> {
|
||||||
self.operations.into_inner()
|
self.operations.into_inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transform(&self, other: &mut Transaction) {
|
||||||
|
for other_operation in other.iter_mut() {
|
||||||
|
for operation in self.operations.iter() {
|
||||||
|
operation.mut_transform(other_operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for Transaction {
|
impl std::ops::Deref for Transaction {
|
||||||
@ -177,6 +194,6 @@ impl<'a> TransactionBuilder<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn finalize(self) -> Transaction {
|
pub fn finalize(self) -> Transaction {
|
||||||
Transaction::new(self.operations)
|
Transaction::from_operations(self.operations)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,4 @@ use crate::core::{Attributes, Operation, OperationBuilder, Operations};
|
|||||||
|
|
||||||
pub type TextDelta = Operations<Attributes>;
|
pub type TextDelta = Operations<Attributes>;
|
||||||
pub type TextDeltaBuilder = OperationBuilder<Attributes>;
|
pub type TextDeltaBuilder = OperationBuilder<Attributes>;
|
||||||
|
|
||||||
pub type TextOperation = Operation<Attributes>;
|
pub type TextOperation = Operation<Attributes>;
|
||||||
|
@ -26,7 +26,8 @@ fn editor_deserialize_node_test() {
|
|||||||
test.run_scripts(vec![
|
test.run_scripts(vec![
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path,
|
path,
|
||||||
node: node.clone(),
|
node_data: node.clone(),
|
||||||
|
rev_id: 1,
|
||||||
},
|
},
|
||||||
AssertNumberOfNodesAtPath { path: None, len: 1 },
|
AssertNumberOfNodesAtPath { path: None, len: 1 },
|
||||||
AssertNumberOfNodesAtPath {
|
AssertNumberOfNodesAtPath {
|
||||||
@ -41,11 +42,11 @@ fn editor_deserialize_node_test() {
|
|||||||
path: vec![0, 1].into(),
|
path: vec![0, 1].into(),
|
||||||
expected: expected_delta,
|
expected: expected_delta,
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path: vec![0, 0].into(),
|
path: vec![0, 0].into(),
|
||||||
expected: Some(node.children[0].clone()),
|
expected: Some(node.children[0].clone()),
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path: vec![0, 3].into(),
|
path: vec![0, 3].into(),
|
||||||
expected: Some(node.children[3].clone()),
|
expected: Some(node.children[3].clone()),
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use lib_ot::core::AttributeBuilder;
|
use crate::node::script::NodeScript::*;
|
||||||
|
use crate::node::script::NodeTest;
|
||||||
|
use lib_ot::core::{AttributeBuilder, Node, NodeTree, Transaction, TransactionBuilder};
|
||||||
use lib_ot::{
|
use lib_ot::{
|
||||||
core::{NodeBodyChangeset, NodeData, NodeDataBuilder, NodeOperation, Path},
|
core::{NodeBodyChangeset, NodeData, NodeDataBuilder, NodeOperation, Path},
|
||||||
text_delta::TextDeltaBuilder,
|
text_delta::TextDeltaBuilder,
|
||||||
@ -69,3 +71,62 @@ fn operation_update_node_body_deserialize_test() {
|
|||||||
let json_2 = serde_json::to_string(&operation).unwrap();
|
let json_2 = serde_json::to_string(&operation).unwrap();
|
||||||
assert_eq!(json_1, json_2);
|
assert_eq!(json_1, json_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn operation_insert_transform_test() {
|
||||||
|
let node_1 = NodeDataBuilder::new("text_1").build();
|
||||||
|
let node_2 = NodeDataBuilder::new("text_2").build();
|
||||||
|
let op_1 = NodeOperation::Insert {
|
||||||
|
path: Path(vec![0, 1]),
|
||||||
|
nodes: vec![node_1],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut insert_2 = NodeOperation::Insert {
|
||||||
|
path: Path(vec![0, 1]),
|
||||||
|
nodes: vec![node_2],
|
||||||
|
};
|
||||||
|
|
||||||
|
// let mut node_tree = NodeTree::new("root");
|
||||||
|
// node_tree.apply_op(insert_1.clone()).unwrap();
|
||||||
|
|
||||||
|
let new_op = op_1.transform(&insert_2);
|
||||||
|
let json = serde_json::to_string(&new_op).unwrap();
|
||||||
|
assert_eq!(json, r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn operation_insert_transform_test2() {
|
||||||
|
let mut test = NodeTest::new();
|
||||||
|
let node_data_1 = NodeDataBuilder::new("text_1").build();
|
||||||
|
let node_data_2 = NodeDataBuilder::new("text_2").build();
|
||||||
|
let node_2: Node = node_data_2.clone().into();
|
||||||
|
let node_data_3 = NodeDataBuilder::new("text_3").build();
|
||||||
|
let node_3: Node = node_data_3.clone().into();
|
||||||
|
|
||||||
|
let scripts = vec![
|
||||||
|
InsertNode {
|
||||||
|
path: 0.into(),
|
||||||
|
node_data: node_data_1.clone(),
|
||||||
|
rev_id: 1,
|
||||||
|
},
|
||||||
|
InsertNode {
|
||||||
|
path: 1.into(),
|
||||||
|
node_data: node_data_2.clone(),
|
||||||
|
rev_id: 2,
|
||||||
|
},
|
||||||
|
InsertNode {
|
||||||
|
path: 1.into(),
|
||||||
|
node_data: node_data_3.clone(),
|
||||||
|
rev_id: 1,
|
||||||
|
},
|
||||||
|
// AssertNode {
|
||||||
|
// path: 2.into(),
|
||||||
|
// expected: node_2,
|
||||||
|
// },
|
||||||
|
AssertNode {
|
||||||
|
path: 1.into(),
|
||||||
|
expected: node_3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
test.run_scripts(scripts);
|
||||||
|
}
|
||||||
|
@ -1,26 +1,61 @@
|
|||||||
|
use lib_ot::core::{Node, NodeOperation, Transaction};
|
||||||
use lib_ot::{
|
use lib_ot::{
|
||||||
core::attributes::Attributes,
|
core::attributes::Attributes,
|
||||||
core::{NodeBody, NodeBodyChangeset, NodeData, NodeTree, Path, TransactionBuilder},
|
core::{NodeBody, NodeBodyChangeset, NodeData, NodeTree, Path, TransactionBuilder},
|
||||||
text_delta::TextDelta,
|
text_delta::TextDelta,
|
||||||
};
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub enum NodeScript {
|
pub enum NodeScript {
|
||||||
InsertNode { path: Path, node: NodeData },
|
InsertNode {
|
||||||
UpdateAttributes { path: Path, attributes: Attributes },
|
path: Path,
|
||||||
UpdateBody { path: Path, changeset: NodeBodyChangeset },
|
node_data: NodeData,
|
||||||
DeleteNode { path: Path },
|
rev_id: usize,
|
||||||
AssertNumberOfNodesAtPath { path: Option<Path>, len: usize },
|
},
|
||||||
AssertNode { path: Path, expected: Option<NodeData> },
|
UpdateAttributes {
|
||||||
AssertNodeDelta { path: Path, expected: TextDelta },
|
path: Path,
|
||||||
|
attributes: Attributes,
|
||||||
|
},
|
||||||
|
UpdateBody {
|
||||||
|
path: Path,
|
||||||
|
changeset: NodeBodyChangeset,
|
||||||
|
},
|
||||||
|
DeleteNode {
|
||||||
|
path: Path,
|
||||||
|
},
|
||||||
|
AssertNumberOfNodesAtPath {
|
||||||
|
path: Option<Path>,
|
||||||
|
len: usize,
|
||||||
|
},
|
||||||
|
AssertNodeData {
|
||||||
|
path: Path,
|
||||||
|
expected: Option<NodeData>,
|
||||||
|
},
|
||||||
|
AssertNode {
|
||||||
|
path: Path,
|
||||||
|
expected: Node,
|
||||||
|
},
|
||||||
|
AssertNodeDelta {
|
||||||
|
path: Path,
|
||||||
|
expected: TextDelta,
|
||||||
|
},
|
||||||
|
ApplyTransaction {
|
||||||
|
transaction: Transaction,
|
||||||
|
rev_id: usize,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NodeTest {
|
pub struct NodeTest {
|
||||||
|
rev_id: usize,
|
||||||
|
rev_operations: HashMap<usize, Transaction>,
|
||||||
node_tree: NodeTree,
|
node_tree: NodeTree,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeTest {
|
impl NodeTest {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
rev_id: 0,
|
||||||
|
rev_operations: HashMap::new(),
|
||||||
node_tree: NodeTree::new("root"),
|
node_tree: NodeTree::new("root"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,40 +68,49 @@ impl NodeTest {
|
|||||||
|
|
||||||
pub fn run_script(&mut self, script: NodeScript) {
|
pub fn run_script(&mut self, script: NodeScript) {
|
||||||
match script {
|
match script {
|
||||||
NodeScript::InsertNode { path, node } => {
|
NodeScript::InsertNode {
|
||||||
let transaction = TransactionBuilder::new(&self.node_tree)
|
path,
|
||||||
|
node_data: node,
|
||||||
|
rev_id,
|
||||||
|
} => {
|
||||||
|
let mut transaction = TransactionBuilder::new(&self.node_tree)
|
||||||
.insert_node_at_path(path, node)
|
.insert_node_at_path(path, node)
|
||||||
.finalize();
|
.finalize();
|
||||||
|
self.transform_transaction_if_need(&mut transaction, rev_id);
|
||||||
self.node_tree.apply(transaction).unwrap();
|
self.apply_transaction(transaction);
|
||||||
}
|
}
|
||||||
NodeScript::UpdateAttributes { path, attributes } => {
|
NodeScript::UpdateAttributes { path, attributes } => {
|
||||||
let transaction = TransactionBuilder::new(&self.node_tree)
|
let transaction = TransactionBuilder::new(&self.node_tree)
|
||||||
.update_attributes_at_path(&path, attributes)
|
.update_attributes_at_path(&path, attributes)
|
||||||
.finalize();
|
.finalize();
|
||||||
self.node_tree.apply(transaction).unwrap();
|
self.apply_transaction(transaction);
|
||||||
}
|
}
|
||||||
NodeScript::UpdateBody { path, changeset } => {
|
NodeScript::UpdateBody { path, changeset } => {
|
||||||
//
|
//
|
||||||
let transaction = TransactionBuilder::new(&self.node_tree)
|
let transaction = TransactionBuilder::new(&self.node_tree)
|
||||||
.update_body_at_path(&path, changeset)
|
.update_body_at_path(&path, changeset)
|
||||||
.finalize();
|
.finalize();
|
||||||
self.node_tree.apply(transaction).unwrap();
|
self.apply_transaction(transaction);
|
||||||
}
|
}
|
||||||
NodeScript::DeleteNode { path } => {
|
NodeScript::DeleteNode { path } => {
|
||||||
let transaction = TransactionBuilder::new(&self.node_tree)
|
let transaction = TransactionBuilder::new(&self.node_tree)
|
||||||
.delete_node_at_path(&path)
|
.delete_node_at_path(&path)
|
||||||
.finalize();
|
.finalize();
|
||||||
self.node_tree.apply(transaction).unwrap();
|
self.apply_transaction(transaction);
|
||||||
}
|
}
|
||||||
NodeScript::AssertNode { path, expected } => {
|
NodeScript::AssertNode { path, expected } => {
|
||||||
|
let node_id = self.node_tree.node_id_at_path(path).unwrap();
|
||||||
|
let node = self.node_tree.get_node(node_id).cloned().unwrap();
|
||||||
|
assert_eq!(node, expected);
|
||||||
|
}
|
||||||
|
NodeScript::AssertNodeData { path, expected } => {
|
||||||
let node_id = self.node_tree.node_id_at_path(path);
|
let node_id = self.node_tree.node_id_at_path(path);
|
||||||
|
|
||||||
match node_id {
|
match node_id {
|
||||||
None => assert!(node_id.is_none()),
|
None => assert!(node_id.is_none()),
|
||||||
Some(node_id) => {
|
Some(node_id) => {
|
||||||
let node_data = self.node_tree.get_node(node_id).cloned();
|
let node = self.node_tree.get_node(node_id).cloned();
|
||||||
assert_eq!(node_data, expected.map(|e| e.into()));
|
assert_eq!(node, expected.map(|e| e.into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,6 +136,29 @@ impl NodeTest {
|
|||||||
panic!("Node body type not match, expect Delta");
|
panic!("Node body type not match, expect Delta");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NodeScript::ApplyTransaction {
|
||||||
|
mut transaction,
|
||||||
|
rev_id,
|
||||||
|
} => {
|
||||||
|
self.transform_transaction_if_need(&mut transaction, rev_id);
|
||||||
|
self.apply_transaction(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_transaction(&mut self, transaction: Transaction) {
|
||||||
|
self.rev_id += 1;
|
||||||
|
self.rev_operations.insert(self.rev_id, transaction.clone());
|
||||||
|
self.node_tree.apply(transaction).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_transaction_if_need(&mut self, transaction: &mut Transaction, rev_id: usize) {
|
||||||
|
if self.rev_id >= rev_id {
|
||||||
|
for rev_id in rev_id..self.rev_id {
|
||||||
|
let old_transaction = self.rev_operations.get(&rev_id).unwrap();
|
||||||
|
old_transaction.transform(transaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,10 @@ fn node_insert_test() {
|
|||||||
let scripts = vec![
|
let scripts = vec![
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
node: inserted_node.clone(),
|
node_data: inserted_node.clone(),
|
||||||
|
rev_id: 1,
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path,
|
path,
|
||||||
expected: Some(inserted_node),
|
expected: Some(inserted_node),
|
||||||
},
|
},
|
||||||
@ -32,9 +33,10 @@ fn node_insert_node_with_children_test() {
|
|||||||
let scripts = vec![
|
let scripts = vec![
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
node: inserted_node.clone(),
|
node_data: inserted_node.clone(),
|
||||||
|
rev_id: 1,
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path,
|
path,
|
||||||
expected: Some(inserted_node),
|
expected: Some(inserted_node),
|
||||||
},
|
},
|
||||||
@ -57,25 +59,28 @@ fn node_insert_multi_nodes_test() {
|
|||||||
let scripts = vec![
|
let scripts = vec![
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path_1.clone(),
|
path: path_1.clone(),
|
||||||
node: node_1.clone(),
|
node_data: node_1.clone(),
|
||||||
|
rev_id: 1,
|
||||||
},
|
},
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path_2.clone(),
|
path: path_2.clone(),
|
||||||
node: node_2.clone(),
|
node_data: node_2.clone(),
|
||||||
|
rev_id: 2,
|
||||||
},
|
},
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path_3.clone(),
|
path: path_3.clone(),
|
||||||
node: node_3.clone(),
|
node_data: node_3.clone(),
|
||||||
|
rev_id: 3,
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path: path_1,
|
path: path_1,
|
||||||
expected: Some(node_1),
|
expected: Some(node_1),
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path: path_2,
|
path: path_2,
|
||||||
expected: Some(node_2),
|
expected: Some(node_2),
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path: path_3,
|
path: path_3,
|
||||||
expected: Some(node_3),
|
expected: Some(node_3),
|
||||||
},
|
},
|
||||||
@ -101,35 +106,39 @@ fn node_insert_node_in_ordered_nodes_test() {
|
|||||||
let scripts = vec![
|
let scripts = vec![
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path_1.clone(),
|
path: path_1.clone(),
|
||||||
node: node_1.clone(),
|
node_data: node_1.clone(),
|
||||||
|
rev_id: 1,
|
||||||
},
|
},
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path_2.clone(),
|
path: path_2.clone(),
|
||||||
node: node_2_1.clone(),
|
node_data: node_2_1.clone(),
|
||||||
|
rev_id: 2,
|
||||||
},
|
},
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path_3.clone(),
|
path: path_3.clone(),
|
||||||
node: node_3.clone(),
|
node_data: node_3.clone(),
|
||||||
|
rev_id: 3,
|
||||||
},
|
},
|
||||||
// 0:note_1 , 1: note_2_1, 2: note_3
|
// 0:note_1 , 1: note_2_1, 2: note_3
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path_2.clone(),
|
path: path_2.clone(),
|
||||||
node: node_2_2.clone(),
|
node_data: node_2_2.clone(),
|
||||||
|
rev_id: 4,
|
||||||
},
|
},
|
||||||
// 0:note_1 , 1:note_2_2, 2: note_2_1, 3: note_3
|
// 0:note_1 , 1:note_2_2, 2: note_2_1, 3: note_3
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path: path_1,
|
path: path_1,
|
||||||
expected: Some(node_1),
|
expected: Some(node_1),
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path: path_2,
|
path: path_2,
|
||||||
expected: Some(node_2_2),
|
expected: Some(node_2_2),
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path: path_3,
|
path: path_3,
|
||||||
expected: Some(node_2_1),
|
expected: Some(node_2_1),
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path: path_4,
|
path: path_4,
|
||||||
expected: Some(node_3),
|
expected: Some(node_3),
|
||||||
},
|
},
|
||||||
@ -149,13 +158,14 @@ fn node_insert_with_attributes_test() {
|
|||||||
let scripts = vec![
|
let scripts = vec![
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
node: inserted_node.clone(),
|
node_data: inserted_node.clone(),
|
||||||
|
rev_id: 1,
|
||||||
},
|
},
|
||||||
UpdateAttributes {
|
UpdateAttributes {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
attributes: inserted_node.attributes.clone(),
|
attributes: inserted_node.attributes.clone(),
|
||||||
},
|
},
|
||||||
AssertNode {
|
AssertNodeData {
|
||||||
path,
|
path,
|
||||||
expected: Some(inserted_node),
|
expected: Some(inserted_node),
|
||||||
},
|
},
|
||||||
@ -172,10 +182,11 @@ fn node_delete_test() {
|
|||||||
let scripts = vec![
|
let scripts = vec![
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
node: inserted_node,
|
node_data: inserted_node,
|
||||||
|
rev_id: 1,
|
||||||
},
|
},
|
||||||
DeleteNode { path: path.clone() },
|
DeleteNode { path: path.clone() },
|
||||||
AssertNode { path, expected: None },
|
AssertNodeData { path, expected: None },
|
||||||
];
|
];
|
||||||
test.run_scripts(scripts);
|
test.run_scripts(scripts);
|
||||||
}
|
}
|
||||||
@ -198,7 +209,8 @@ fn node_update_body_test() {
|
|||||||
let scripts = vec![
|
let scripts = vec![
|
||||||
InsertNode {
|
InsertNode {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
node,
|
node_data: node,
|
||||||
|
rev_id: 1,
|
||||||
},
|
},
|
||||||
UpdateBody {
|
UpdateBody {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user