chore: create default grid

This commit is contained in:
appflowy 2022-03-12 09:30:13 +08:00
parent df399d3f35
commit 6579940dc8
42 changed files with 412 additions and 293 deletions

View File

@ -20,7 +20,7 @@ class View extends $pb.GeneratedMessage {
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongToId')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.Block, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.TextBlock, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'version')
..aOM<RepeatedView>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongings', subBuilder: RepeatedView.create)
..aInt64(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
@ -274,7 +274,7 @@ class CreateViewPayload extends $pb.GeneratedMessage {
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.Block, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.TextBlock, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pluginType', $pb.PbFieldType.O3)
..hasRequiredFields = false
@ -408,7 +408,7 @@ class CreateViewParams extends $pb.GeneratedMessage {
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.Block, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.TextBlock, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
..aOS(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewId')
..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')

View File

@ -10,11 +10,11 @@ import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
class ViewDataType extends $pb.ProtobufEnum {
static const ViewDataType Block = ViewDataType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Block');
static const ViewDataType TextBlock = ViewDataType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TextBlock');
static const ViewDataType Grid = ViewDataType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Grid');
static const $core.List<ViewDataType> values = <ViewDataType> [
Block,
TextBlock,
Grid,
];

View File

@ -12,13 +12,13 @@ import 'dart:typed_data' as $typed_data;
const ViewDataType$json = const {
'1': 'ViewDataType',
'2': const [
const {'1': 'Block', '2': 0},
const {'1': 'TextBlock', '2': 0},
const {'1': 'Grid', '2': 1},
],
};
/// Descriptor for `ViewDataType`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List viewDataTypeDescriptor = $convert.base64Decode('CgxWaWV3RGF0YVR5cGUSCQoFQmxvY2sQABIICgRHcmlkEAE=');
final $typed_data.Uint8List viewDataTypeDescriptor = $convert.base64Decode('CgxWaWV3RGF0YVR5cGUSDQoJVGV4dEJsb2NrEAASCAoER3JpZBAB');
@$core.Deprecated('Use viewDescriptor instead')
const View$json = const {
'1': 'View',

View File

@ -1,4 +1,3 @@
use crate::queue::TextBlockRevisionCompactor;
use crate::web_socket::{make_block_ws_manager, EditorCommandSender};
use crate::{
errors::FlowyError,
@ -40,7 +39,7 @@ impl ClientTextBlockEditor {
rev_web_socket: Arc<dyn RevisionWebSocket>,
cloud_service: Arc<dyn RevisionCloudService>,
) -> FlowyResult<Arc<Self>> {
let document_info = rev_manager.load::<BlockInfoBuilder>(cloud_service).await?;
let document_info = rev_manager.load::<TextBlockInfoBuilder>(cloud_service).await?;
let delta = document_info.delta()?;
let rev_manager = Arc::new(rev_manager);
let doc_id = doc_id.to_string();
@ -213,8 +212,8 @@ impl ClientTextBlockEditor {
}
}
struct BlockInfoBuilder();
impl RevisionObjectBuilder for BlockInfoBuilder {
struct TextBlockInfoBuilder();
impl RevisionObjectBuilder for TextBlockInfoBuilder {
type Output = TextBlockInfo;
fn build_object(object_id: &str, revisions: Vec<Revision>) -> FlowyResult<Self::Output> {

View File

@ -11,7 +11,6 @@ use flowy_collaboration::{
use flowy_error::{FlowyError, FlowyResult};
use flowy_sync::{DeltaMD5, RevisionCompactor, RevisionManager, RichTextTransformDeltas, TransformDeltas};
use futures::stream::StreamExt;
use lib_ot::core::{Attributes, Delta};
use lib_ot::{
core::{Interval, OperationTransformable},
rich_text::{RichTextAttribute, RichTextAttributes, RichTextDelta},

View File

@ -1,5 +1,5 @@
use crate::document::edit_script::{EditorScript::*, *};
use flowy_collaboration::entities::revision::RevisionState;
use flowy_sync::disk::RevisionState;
use lib_ot::core::{count_utf16_code_units, Interval};
#[tokio::test]

View File

@ -1,6 +1,6 @@
use flowy_block::editor::ClientTextBlockEditor;
use flowy_block::DOCUMENT_SYNC_INTERVAL_IN_MILLIS;
use flowy_collaboration::entities::revision::RevisionState;
use flowy_sync::disk::RevisionState;
use flowy_test::{helper::ViewTest, FlowySDKTest};
use lib_ot::{core::Interval, rich_text::RichTextDelta};
use std::sync::Arc;

View File

@ -12,7 +12,7 @@ use bytes::Bytes;
use chrono::Utc;
use flowy_collaboration::client_document::default::{initial_quill_delta_string, initial_read_me};
use flowy_collaboration::entities::revision::RepeatedRevision;
use flowy_collaboration::{client_folder::FolderPad, entities::ws_data::ServerRevisionWSData};
use flowy_error::FlowyError;
use flowy_folder_data_model::entities::view::ViewDataType;
@ -164,7 +164,7 @@ impl FolderManager {
let _ = self.persistence.initialize(user_id, &folder_id).await?;
let pool = self.persistence.db_pool()?;
let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(&user_id, pool));
let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(user_id, pool));
let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache));
let rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), rev_persistence);
@ -214,7 +214,7 @@ impl DefaultFolderBuilder {
};
let _ = view_controller.set_latest_view(&view.id);
let _ = view_controller
.create_view(&view.id, ViewDataType::Block, Bytes::from(view_data))
.create_view(&view.id, ViewDataType::TextBlock, Bytes::from(view_data))
.await?;
}
}
@ -239,7 +239,7 @@ impl FolderManager {
pub trait ViewDataProcessor {
fn initialize(&self) -> FutureResult<(), FlowyError>;
fn create_container(&self, view_id: &str, repeated_revision: RepeatedRevision) -> FutureResult<(), FlowyError>;
fn create_container(&self, user_id: &str, view_id: &str, delta_data: Bytes) -> FutureResult<(), FlowyError>;
fn delete_container(&self, view_id: &str) -> FutureResult<(), FlowyError>;
@ -247,7 +247,7 @@ pub trait ViewDataProcessor {
fn delta_str(&self, view_id: &str) -> FutureResult<String, FlowyError>;
fn default_view_data(&self, view_id: &str) -> String;
fn create_default_view(&self, user_id: &str, view_id: &str) -> FutureResult<String, FlowyError>;
fn data_type(&self) -> ViewDataType;
}

View File

@ -8,14 +8,14 @@ use crate::manager::FolderId;
use bytes::Bytes;
use flowy_collaboration::util::make_delta_from_revisions;
use flowy_error::{FlowyError, FlowyResult};
use flowy_sync::disk::RevisionDiskCache;
use flowy_sync::{
RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder, RevisionPersistence,
RevisionWebSocket, RevisionWebSocketManager,
RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder, RevisionWebSocket,
RevisionWebSocketManager,
};
use lib_infra::future::FutureResult;
use lib_ot::core::{Delta, PlainTextAttributes};
use lib_sqlite::ConnectionPool;
use lib_ot::core::PlainTextAttributes;
use parking_lot::RwLock;
use std::sync::Arc;

View File

@ -88,7 +88,7 @@ impl FolderMigration {
return Ok(None);
}
let pool = self.database.db_pool()?;
let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(&user_id, pool));
let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(user_id, pool));
let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache));
let (revisions, _) = RevisionLoader {
object_id: folder_id.as_ref().to_owned(),

View File

@ -84,7 +84,7 @@ pub(crate) struct ViewTable {
impl ViewTable {
pub fn new(view: View) -> Self {
let data_type = match view.data_type {
ViewDataType::Block => SqlViewDataType::Block,
ViewDataType::TextBlock => SqlViewDataType::Block,
ViewDataType::Grid => SqlViewDataType::Grid,
};
@ -106,7 +106,7 @@ impl ViewTable {
impl std::convert::From<ViewTable> for View {
fn from(table: ViewTable) -> Self {
let data_type = match table.view_type {
SqlViewDataType::Block => ViewDataType::Block,
SqlViewDataType::Block => ViewDataType::TextBlock,
SqlViewDataType::Grid => ViewDataType::Grid,
};

View File

@ -13,10 +13,7 @@ use crate::{
},
};
use bytes::Bytes;
use flowy_collaboration::entities::{
revision::{RepeatedRevision, Revision},
text_block_info::TextBlockId,
};
use flowy_collaboration::entities::text_block_info::TextBlockId;
use flowy_database::kv::KV;
use flowy_folder_data_model::entities::view::ViewDataType;
use futures::{FutureExt, StreamExt};
@ -58,18 +55,18 @@ impl ViewController {
#[tracing::instrument(level = "trace", skip(self, params), fields(name = %params.name), err)]
pub(crate) async fn create_view_from_params(&self, mut params: CreateViewParams) -> Result<View, FlowyError> {
let processor = self.get_data_processor(&params.data_type)?;
let content = if params.data.is_empty() {
let default_view_data = processor.default_view_data(&params.view_id);
params.data = default_view_data.clone();
default_view_data
if params.data.is_empty() {
let user_id = self.user.user_id()?;
let view_data = processor.create_default_view(&user_id, &params.view_id).await?;
params.data = view_data;
} else {
params.data.clone()
let delta_data = Bytes::from(params.data.clone());
let _ = self
.create_view(&params.view_id, params.data_type.clone(), delta_data)
.await?;
};
let delta_data = Bytes::from(content);
let _ = self
.create_view(&params.view_id, params.data_type.clone(), delta_data)
.await?;
let view = self.create_view_on_server(params).await?;
let _ = self.create_view_on_local(view.clone()).await?;
Ok(view)
@ -86,9 +83,8 @@ impl ViewController {
return Err(FlowyError::internal().context("The content of the view should not be empty"));
}
let user_id = self.user.user_id()?;
let repeated_revision: RepeatedRevision = Revision::initial_revision(&user_id, view_id, delta_data).into();
let processor = self.get_data_processor(&data_type)?;
let _ = processor.create_container(view_id, repeated_revision).await?;
let _ = processor.create_container(&user_id, view_id, delta_data).await?;
Ok(())
}

View File

@ -1,6 +1,7 @@
use crate::script::{invalid_workspace_name_test_case, FolderScript::*, FolderTest};
use flowy_collaboration::{client_document::default::initial_quill_delta_string, entities::revision::RevisionState};
use flowy_folder::entities::workspace::CreateWorkspacePayload;
use flowy_sync::disk::RevisionState;
use flowy_test::{event_builder::*, FlowySDKTest};
#[tokio::test]
@ -168,16 +169,6 @@ async fn view_update() {
assert_eq!(test.view.name, new_name);
}
#[tokio::test]
async fn open_document_view() {
let mut test = FolderTest::new().await;
assert_eq!(test.document_info, None);
test.run_scripts(vec![OpenDocument]).await;
let document_info = test.document_info.unwrap();
assert_eq!(document_info.text, initial_quill_delta_string());
}
#[tokio::test]
#[should_panic]
async fn view_delete() {

View File

@ -161,7 +161,8 @@ pub async fn delete_view(sdk: &FlowySDKTest, view_ids: Vec<String>) {
.await;
}
pub async fn open_document(sdk: &FlowySDKTest, view_id: &str) -> TextBlockInfo {
#[allow(dead_code)]
pub async fn set_latest_view(sdk: &FlowySDKTest, view_id: &str) -> TextBlockInfo {
let view_id: ViewId = view_id.into();
FolderEventBuilder::new(sdk.clone())
.event(SetLatestView)

View File

@ -1,5 +1,5 @@
use crate::helper::*;
use flowy_collaboration::entities::{revision::RevisionState, text_block_info::TextBlockInfo};
use flowy_folder::{errors::ErrorCode, services::folder_editor::ClientFolderEditor};
use flowy_folder_data_model::entities::{
app::{App, RepeatedApp},
@ -7,6 +7,7 @@ use flowy_folder_data_model::entities::{
view::{RepeatedView, View, ViewDataType},
workspace::Workspace,
};
use flowy_sync::disk::RevisionState;
use flowy_sync::REVISION_WRITE_INTERVAL_IN_MILLIS;
use flowy_test::FlowySDKTest;
use std::{sync::Arc, time::Duration};
@ -42,9 +43,6 @@ pub enum FolderScript {
ReadTrash,
DeleteAllTrash,
// Document
OpenDocument,
// Sync
AssertCurrentRevId(i64),
AssertNextSyncRevId(Option<i64>),
@ -58,7 +56,6 @@ pub struct FolderTest {
pub app: App,
pub view: View,
pub trash: Vec<Trash>,
pub document_info: Option<TextBlockInfo>,
// pub folder_editor:
}
@ -68,7 +65,14 @@ impl FolderTest {
let _ = sdk.init_user().await;
let mut workspace = create_workspace(&sdk, "FolderWorkspace", "Folder test workspace").await;
let mut app = create_app(&sdk, &workspace.id, "Folder App", "Folder test app").await;
let view = create_view(&sdk, &app.id, "Folder View", "Folder test view", ViewDataType::Block).await;
let view = create_view(
&sdk,
&app.id,
"Folder View",
"Folder test view",
ViewDataType::TextBlock,
)
.await;
app.belongings = RepeatedView {
items: vec![view.clone()],
};
@ -83,7 +87,6 @@ impl FolderTest {
app,
view,
trash: vec![],
document_info: None,
}
}
@ -146,7 +149,7 @@ impl FolderTest {
}
FolderScript::CreateView { name, desc } => {
let view = create_view(sdk, &self.app.id, &name, &desc, ViewDataType::Block).await;
let view = create_view(sdk, &self.app.id, &name, &desc, ViewDataType::TextBlock).await;
self.view = view;
}
FolderScript::AssertView(view) => {
@ -179,10 +182,6 @@ impl FolderTest {
delete_all_trash(sdk).await;
self.trash = vec![];
}
FolderScript::OpenDocument => {
let document_info = open_document(sdk, &self.view.id).await;
self.document_info = Some(document_info);
}
FolderScript::AssertRevisionState { rev_id, state } => {
let record = cache.get(rev_id).await.unwrap();
assert_eq!(record.state, state);

View File

@ -46,7 +46,7 @@ pub(crate) async fn create_row_handler(
) -> Result<(), FlowyError> {
let id: GridId = data.into_inner();
let editor = manager.get_grid_editor(id.as_ref())?;
let _ = editor.create_empty_row().await?;
let _ = editor.create_row().await?;
Ok(())
}
@ -55,7 +55,7 @@ pub(crate) async fn update_cell_handler(
data: Data<Cell>,
manager: AppData<Arc<GridManager>>,
) -> Result<(), FlowyError> {
let cell: Cell = data.into_inner();
let _cell: Cell = data.into_inner();
// let editor = manager.get_grid_editor(id.as_ref())?;
// let _ = editor.create_empty_row().await?;
Ok(())

View File

@ -1,13 +1,10 @@
use crate::services::grid_editor::ClientGridEditor;
use crate::services::kv_persistence::{GridKVPersistence, KVTransaction};
use crate::services::kv_persistence::GridKVPersistence;
use dashmap::DashMap;
use flowy_collaboration::entities::revision::RepeatedRevision;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{Field, RowMeta};
use flowy_sync::disk::{SQLiteGridBlockMetaRevisionPersistence, SQLiteGridRevisionPersistence};
use flowy_sync::{RevisionManager, RevisionPersistence, RevisionWebSocket};
use flowy_sync::disk::SQLiteGridRevisionPersistence;
use lib_sqlite::ConnectionPool;
use parking_lot::RwLock;
use std::sync::Arc;
@ -47,6 +44,19 @@ impl GridManager {
Ok(())
}
#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn create_grid_block_meta<T: AsRef<str>>(
&self,
block_id: T,
revisions: RepeatedRevision,
) -> FlowyResult<()> {
let block_id = block_id.as_ref();
let db_pool = self.grid_user.db_pool()?;
let rev_manager = self.make_grid_block_meta_rev_manager(block_id, db_pool)?;
let _ = rev_manager.reset_object(revisions).await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip_all, fields(grid_id), err)]
pub async fn open_grid<T: AsRef<str>>(&self, grid_id: T) -> FlowyResult<Arc<ClientGridEditor>> {
let grid_id = grid_id.as_ref();
@ -112,6 +122,18 @@ impl GridManager {
Ok(rev_manager)
}
fn make_grid_block_meta_rev_manager(
&self,
block_d: &str,
pool: Arc<ConnectionPool>,
) -> FlowyResult<RevisionManager> {
let user_id = self.grid_user.user_id()?;
let disk_cache = Arc::new(SQLiteGridBlockMetaRevisionPersistence::new(&user_id, pool));
let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, block_d, disk_cache));
let rev_manager = RevisionManager::new(&user_id, block_d, rev_persistence);
Ok(rev_manager)
}
fn get_kv_persistence(&self) -> FlowyResult<Arc<GridKVPersistence>> {
let read_guard = self.kv_persistence.read();
if read_guard.is_some() {

View File

@ -1,77 +0,0 @@
use crate::manager::GridManager;
use flowy_collaboration::client_grid::make_grid_delta;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{CellMeta, Field, FieldType, Grid, GridMeta, RowMeta, RowOrder};
use lib_infra::uuid;
use std::sync::Arc;
pub struct GridBuilder {
grid_manager: Arc<GridManager>,
grid_id: String,
fields: Vec<Field>,
rows: Vec<RowMeta>,
}
impl GridBuilder {
pub fn new(grid_id: &str, grid_manager: Arc<GridManager>) -> Self {
Self {
grid_manager,
grid_id: grid_id.to_owned(),
fields: vec![],
rows: vec![],
}
}
pub fn add_field(mut self, name: &str, desc: &str, field_type: FieldType) -> Self {
let field = Field::new(&uuid(), name, desc, field_type);
self.fields.push(field);
self
}
pub fn add_empty_row(mut self) -> Self {
let row = RowMeta::new(&uuid(), &self.grid_id, vec![]);
self.rows.push(row);
self
}
pub fn add_row(mut self, cells: Vec<CellMeta>) -> Self {
let row = RowMeta::new(&uuid(), &self.grid_id, cells);
self.rows.push(row);
self
}
pub fn build(self) -> FlowyResult<String> {
let grid_meta = GridMeta {
grid_id: self.grid_id,
fields: self.fields,
blocks: vec![],
};
// let _ = check_rows(&self.fields, &self.rows)?;
let delta = make_grid_delta(&grid_meta);
Ok(delta.to_delta_str())
}
}
#[allow(dead_code)]
fn check_rows(fields: &[Field], rows: &[RowMeta]) -> FlowyResult<()> {
let field_ids = fields.iter().map(|field| &field.id).collect::<Vec<&String>>();
for row in rows {
let cell_field_ids = row.cell_by_field_id.keys().into_iter().collect::<Vec<&String>>();
if cell_field_ids != field_ids {
let msg = format!("{:?} contains invalid cells", row);
return Err(FlowyError::internal().context(msg));
}
}
Ok(())
}
pub fn make_default_grid(grid_id: &str, grid_manager: Arc<GridManager>) -> String {
GridBuilder::new(grid_id, grid_manager)
.add_field("Name", "", FieldType::RichText)
.add_field("Tags", "", FieldType::SingleSelect)
.add_empty_row()
.add_empty_row()
.build()
.unwrap()
}

View File

@ -1,6 +1,5 @@
use crate::manager::GridUser;
use crate::services::kv_persistence::{GridKVPersistence, KVTransaction};
use crate::services::stringify::stringify_deserialize;
use crate::services::grid_meta_editor::ClientGridBlockMetaEditor;
use bytes::Bytes;
@ -10,14 +9,14 @@ use flowy_collaboration::entities::revision::Revision;
use flowy_collaboration::util::make_delta_from_revisions;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::entities::{
Cell, CellMeta, Field, Grid, RepeatedField, RepeatedFieldOrder, RepeatedRow, RepeatedRowOrder, Row, RowMeta,
Field, Grid, GridBlock, RepeatedField, RepeatedFieldOrder, RepeatedRow, RepeatedRowOrder,
};
use flowy_sync::disk::SQLiteGridBlockMetaRevisionPersistence;
use flowy_sync::{
RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder, RevisionPersistence,
};
use flowy_sync::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
use lib_infra::future::FutureResult;
use lib_infra::uuid;
use lib_ot::core::{Delta, PlainTextAttributes};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use std::collections::HashMap;
use lib_ot::core::PlainTextAttributes;
use std::sync::Arc;
use tokio::sync::RwLock;
@ -40,10 +39,11 @@ impl ClientGridEditor {
let token = user.token()?;
let cloud = Arc::new(GridRevisionCloudService { token });
let grid_pad = rev_manager.load::<GridPadBuilder>(cloud).await?;
let rev_manager = Arc::new(rev_manager);
let grid_meta_pad = Arc::new(RwLock::new(grid_pad));
let block_meta_manager = Arc::new(GridBlockMetaEditorManager::new());
let block_meta_manager =
Arc::new(GridBlockMetaEditorManager::new(&user, grid_meta_pad.read().await.get_blocks()).await?);
Ok(Arc::new(Self {
grid_id: grid_id.to_owned(),
@ -65,25 +65,15 @@ impl ClientGridEditor {
Ok(())
}
pub async fn create_empty_row(&self) -> FlowyResult<()> {
// let _ = self.modify(|grid| {
//
//
// grid.blocks
//
// }).await?;
pub async fn create_row(&self) -> FlowyResult<()> {
todo!()
}
async fn create_row(&self, row: RowMeta) -> FlowyResult<()> {
pub async fn get_rows(&self, _row_orders: RepeatedRowOrder) -> FlowyResult<RepeatedRow> {
todo!()
}
pub async fn get_rows(&self, row_orders: RepeatedRowOrder) -> FlowyResult<RepeatedRow> {
todo!()
}
pub async fn delete_rows(&self, ids: Vec<String>) -> FlowyResult<()> {
pub async fn delete_rows(&self, _ids: Vec<String>) -> FlowyResult<()> {
todo!()
}
@ -188,13 +178,17 @@ struct GridBlockMetaEditorManager {
}
impl GridBlockMetaEditorManager {
fn new() -> Self {
Self {
editor_map: DashMap::new(),
}
async fn new(user: &Arc<dyn GridUser>, blocks: Vec<GridBlock>) -> FlowyResult<Self> {
let editor_map = make_block_meta_editor_map(user, blocks).await?;
let manager = Self { editor_map };
Ok(manager)
}
pub async fn get_rows(&self, row_orders: RepeatedRowOrder) -> FlowyResult<RepeatedRow> {
async fn get_editor(&self, _block_id: &str) -> Arc<ClientGridBlockMetaEditor> {
todo!()
}
pub async fn get_rows(&self, _row_orders: RepeatedRowOrder) -> FlowyResult<RepeatedRow> {
// let ids = row_orders
// .items
// .into_iter()
@ -245,3 +239,23 @@ impl GridBlockMetaEditorManager {
todo!()
}
}
async fn make_block_meta_editor_map(
user: &Arc<dyn GridUser>,
blocks: Vec<GridBlock>,
) -> FlowyResult<DashMap<String, Arc<ClientGridBlockMetaEditor>>> {
let token = user.token()?;
let user_id = user.user_id()?;
let pool = user.db_pool()?;
let editor_map = DashMap::new();
for block in blocks {
let disk_cache = Arc::new(SQLiteGridBlockMetaRevisionPersistence::new(&user_id, pool.clone()));
let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, &block.id, disk_cache));
let rev_manager = RevisionManager::new(&user_id, &block.id, rev_persistence);
let editor = ClientGridBlockMetaEditor::new(&user_id, &token, &block.id, rev_manager).await?;
editor_map.insert(block.id, Arc::new(editor));
}
Ok(editor_map)
}

View File

@ -3,17 +3,16 @@ use flowy_collaboration::client_grid::{GridBlockMetaChange, GridBlockMetaPad};
use flowy_collaboration::entities::revision::Revision;
use flowy_collaboration::util::make_delta_from_revisions;
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::entities::RowMeta;
use flowy_grid_data_model::entities::{RowMeta, RowMetaChangeset};
use flowy_sync::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
use lib_infra::future::FutureResult;
use lib_infra::uuid;
use lib_ot::core::PlainTextAttributes;
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct ClientGridBlockMetaEditor {
user_id: String,
block_id: String,
pub block_id: String,
meta_pad: Arc<RwLock<GridBlockMetaPad>>,
rev_manager: Arc<RevisionManager>,
}
@ -22,7 +21,7 @@ impl ClientGridBlockMetaEditor {
pub async fn new(
user_id: &str,
token: &str,
block_id: String,
block_id: &str,
mut rev_manager: RevisionManager,
) -> FlowyResult<Self> {
let cloud = Arc::new(GridBlockMetaRevisionCloudService {
@ -32,6 +31,7 @@ impl ClientGridBlockMetaEditor {
let meta_pad = Arc::new(RwLock::new(block_meta_pad));
let rev_manager = Arc::new(rev_manager);
let user_id = user_id.to_owned();
let block_id = block_id.to_owned();
Ok(Self {
user_id,
block_id,
@ -40,25 +40,27 @@ impl ClientGridBlockMetaEditor {
})
}
pub async fn create_empty_row(&self) -> FlowyResult<()> {
let row = RowMeta::new(&uuid(), &self.block_id, vec![]);
self.create_row(row).await?;
Ok(())
}
async fn create_row(&self, row: RowMeta) -> FlowyResult<()> {
// let _ = self.modify(|grid| Ok(grid.create_row(row)?)).await?;
// self.cell_map.insert(row.id.clone(), row.clone());
// let _ = self.kv_persistence.set(row)?;
async fn create_row(&self) -> FlowyResult<()> {
let row = RowMeta::new(&self.block_id, vec![]);
let _ = self.modify(|pad| Ok(pad.add_row(row)?)).await?;
Ok(())
}
pub async fn delete_rows(&self, ids: Vec<String>) -> FlowyResult<()> {
// let _ = self.modify(|grid| Ok(grid.delete_rows(&ids)?)).await?;
// let _ = self.kv.batch_delete(ids)?;
let _ = self.modify(|pad| Ok(pad.delete_rows(&ids)?)).await?;
Ok(())
}
pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
let _ = self.modify(|pad| Ok(pad.update_row(changeset)?)).await?;
Ok(())
}
pub async fn get_rows(&self, row_ids: Vec<String>) -> FlowyResult<Vec<RowMeta>> {
let rows = self.meta_pad.read().await.get_rows(row_ids)?;
Ok(rows)
}
async fn modify<F>(&self, f: F) -> FlowyResult<()>
where
F: for<'a> FnOnce(&'a mut GridBlockMetaPad) -> FlowyResult<Option<GridBlockMetaChange>>,

View File

@ -1,7 +1,6 @@
mod util;
pub mod cell_data;
pub mod grid_builder;
pub mod grid_editor;
pub mod grid_meta_editor;
pub mod kv_persistence;

View File

@ -29,25 +29,25 @@ pub trait RevisionCloudStorage: Send + Sync {
) -> BoxResultFuture<(), CollaborateError>;
}
pub(crate) struct LocalDocumentCloudPersistence {
pub(crate) struct LocalTextBlockCloudPersistence {
storage: Arc<dyn RevisionCloudStorage>,
}
impl Debug for LocalDocumentCloudPersistence {
impl Debug for LocalTextBlockCloudPersistence {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("LocalRevisionCloudPersistence")
}
}
impl std::default::Default for LocalDocumentCloudPersistence {
impl std::default::Default for LocalTextBlockCloudPersistence {
fn default() -> Self {
LocalDocumentCloudPersistence {
LocalTextBlockCloudPersistence {
storage: Arc::new(MemoryDocumentCloudStorage::default()),
}
}
}
impl FolderCloudPersistence for LocalDocumentCloudPersistence {
impl FolderCloudPersistence for LocalTextBlockCloudPersistence {
fn read_folder(&self, _user_id: &str, folder_id: &str) -> BoxResultFuture<FolderInfo, CollaborateError> {
let storage = self.storage.clone();
let folder_id = folder_id.to_owned();
@ -110,8 +110,8 @@ impl FolderCloudPersistence for LocalDocumentCloudPersistence {
}
}
impl DocumentCloudPersistence for LocalDocumentCloudPersistence {
fn read_document(&self, doc_id: &str) -> BoxResultFuture<TextBlockInfo, CollaborateError> {
impl TextBlockCloudPersistence for LocalTextBlockCloudPersistence {
fn read_text_block(&self, doc_id: &str) -> BoxResultFuture<TextBlockInfo, CollaborateError> {
let storage = self.storage.clone();
let doc_id = doc_id.to_owned();
Box::pin(async move {
@ -123,7 +123,7 @@ impl DocumentCloudPersistence for LocalDocumentCloudPersistence {
})
}
fn create_document(
fn create_text_block(
&self,
doc_id: &str,
repeated_revision: RepeatedRevisionPB,
@ -136,7 +136,7 @@ impl DocumentCloudPersistence for LocalDocumentCloudPersistence {
})
}
fn read_document_revisions(
fn read_text_block_revisions(
&self,
doc_id: &str,
rev_ids: Option<Vec<i64>>,
@ -150,7 +150,10 @@ impl DocumentCloudPersistence for LocalDocumentCloudPersistence {
})
}
fn save_document_revisions(&self, repeated_revision: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError> {
fn save_text_block_revisions(
&self,
repeated_revision: RepeatedRevisionPB,
) -> BoxResultFuture<(), CollaborateError> {
let storage = self.storage.clone();
Box::pin(async move {
let _ = storage.set_revisions(repeated_revision).await?;
@ -158,7 +161,7 @@ impl DocumentCloudPersistence for LocalDocumentCloudPersistence {
})
}
fn reset_document(&self, doc_id: &str, revisions: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError> {
fn reset_text_block(&self, doc_id: &str, revisions: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError> {
let storage = self.storage.clone();
let doc_id = doc_id.to_owned();
Box::pin(async move {

View File

@ -1,4 +1,4 @@
use crate::local_server::persistence::LocalDocumentCloudPersistence;
use crate::local_server::persistence::LocalTextBlockCloudPersistence;
use async_stream::stream;
use bytes::Bytes;
use flowy_collaboration::{
@ -38,7 +38,7 @@ impl LocalServer {
client_ws_sender: mpsc::UnboundedSender<WebSocketRawMessage>,
client_ws_receiver: broadcast::Sender<WebSocketRawMessage>,
) -> Self {
let persistence = Arc::new(LocalDocumentCloudPersistence::default());
let persistence = Arc::new(LocalTextBlockCloudPersistence::default());
let doc_manager = Arc::new(ServerDocumentManager::new(persistence.clone()));
let folder_manager = Arc::new(ServerFolderManager::new(persistence));
let stop_tx = RwLock::new(None);

View File

@ -1,7 +1,8 @@
use bytes::Bytes;
use flowy_block::TextBlockManager;
use flowy_collaboration::client_document::default::initial_quill_delta_string;
use flowy_collaboration::entities::revision::RepeatedRevision;
use flowy_collaboration::client_grid::make_default_grid;
use flowy_collaboration::entities::revision::{RepeatedRevision, Revision};
use flowy_collaboration::entities::ws_data::ClientRevisionWSData;
use flowy_database::ConnectionPool;
use flowy_folder::manager::{ViewDataProcessor, ViewDataProcessorMap};
@ -12,7 +13,6 @@ use flowy_folder::{
manager::FolderManager,
};
use flowy_grid::manager::GridManager;
use flowy_grid::services::grid_builder::make_default_grid;
use flowy_net::ClientServerConfiguration;
use flowy_net::{
http_server::folder::FolderHttpCloudService, local_server::LocalServer, ws::connection::FlowyWebSocketConnect,
@ -140,9 +140,10 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
FutureResult::new(async move { manager.init() })
}
fn create_container(&self, view_id: &str, repeated_revision: RepeatedRevision) -> FutureResult<(), FlowyError> {
let manager = self.0.clone();
fn create_container(&self, user_id: &str, view_id: &str, delta_data: Bytes) -> FutureResult<(), FlowyError> {
let repeated_revision: RepeatedRevision = Revision::initial_revision(user_id, view_id, delta_data).into();
let view_id = view_id.to_string();
let manager = self.0.clone();
FutureResult::new(async move {
let _ = manager.create_block(view_id, repeated_revision).await?;
Ok(())
@ -177,12 +178,21 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
})
}
fn default_view_data(&self, _view_id: &str) -> String {
initial_quill_delta_string()
fn create_default_view(&self, user_id: &str, view_id: &str) -> FutureResult<String, FlowyError> {
let user_id = user_id.to_string();
let view_id = view_id.to_string();
let manager = self.0.clone();
FutureResult::new(async move {
let view_data = initial_quill_delta_string();
let delta_data = Bytes::from(view_data.clone());
let repeated_revision: RepeatedRevision = Revision::initial_revision(&user_id, &view_id, delta_data).into();
let _ = manager.create_block(view_id, repeated_revision).await?;
Ok(view_data)
})
}
fn data_type(&self) -> ViewDataType {
ViewDataType::Block
ViewDataType::TextBlock
}
}
@ -192,9 +202,10 @@ impl ViewDataProcessor for GridViewDataProcessor {
FutureResult::new(async { Ok(()) })
}
fn create_container(&self, view_id: &str, repeated_revision: RepeatedRevision) -> FutureResult<(), FlowyError> {
let grid_manager = self.0.clone();
fn create_container(&self, user_id: &str, view_id: &str, delta_data: Bytes) -> FutureResult<(), FlowyError> {
let repeated_revision: RepeatedRevision = Revision::initial_revision(user_id, view_id, delta_data).into();
let view_id = view_id.to_string();
let grid_manager = self.0.clone();
FutureResult::new(async move {
let _ = grid_manager.create_grid(view_id, repeated_revision).await?;
Ok(())
@ -229,8 +240,27 @@ impl ViewDataProcessor for GridViewDataProcessor {
})
}
fn default_view_data(&self, view_id: &str) -> String {
make_default_grid(view_id, self.0.clone())
fn create_default_view(&self, user_id: &str, view_id: &str) -> FutureResult<String, FlowyError> {
let info = make_default_grid(view_id);
let user_id = user_id.to_string();
let view_id = view_id.to_string();
let grid_manager = self.0.clone();
FutureResult::new(async move {
let grid_delta_data = Bytes::from(info.grid_delta.to_delta_str());
let repeated_revision: RepeatedRevision =
Revision::initial_revision(&user_id, &view_id, grid_delta_data).into();
let _ = grid_manager.create_grid(&view_id, repeated_revision).await?;
let block_meta_delta_data = Bytes::from(info.grid_block_meta_delta.to_delta_str());
let repeated_revision: RepeatedRevision =
Revision::initial_revision(&user_id, &info.block_id, block_meta_delta_data).into();
let _ = grid_manager
.create_grid_block_meta(&info.block_id, repeated_revision)
.await?;
Ok(info.grid_delta.to_delta_str())
})
}
fn data_type(&self) -> ViewDataType {

View File

@ -1,10 +1,10 @@
use crate::cache::disk::RevisionDiskCache;
use crate::disk::{RevisionChangeset, RevisionRecord, RevisionState};
use crate::memory::RevisionMemoryCacheDelegate;
use bytes::Bytes;
use diesel::{sql_types::Integer, update, SqliteConnection};
use flowy_collaboration::{
entities::revision::{RevId, RevType, Revision, RevisionRange},
entities::revision::{Revision, RevisionRange},
util::md5,
};
use flowy_database::{

View File

@ -1,10 +1,10 @@
use crate::cache::disk::RevisionDiskCache;
use crate::disk::{RevisionChangeset, RevisionRecord, RevisionState};
use crate::memory::RevisionMemoryCacheDelegate;
use bytes::Bytes;
use diesel::{sql_types::Integer, update, SqliteConnection};
use flowy_collaboration::{
entities::revision::{RevId, RevType, Revision, RevisionRange},
entities::revision::{Revision, RevisionRange},
util::md5,
};
use flowy_database::{

View File

@ -8,8 +8,6 @@ pub use grid_meta_rev_impl::*;
pub use grid_rev_impl::*;
pub use text_rev_impl::*;
use crate::memory::RevisionMemoryCacheDelegate;
use diesel::SqliteConnection;
use flowy_collaboration::entities::revision::{RevId, Revision, RevisionRange};
use flowy_error::FlowyResult;
use std::fmt::Debug;

View File

@ -1,10 +1,10 @@
use crate::cache::disk::RevisionDiskCache;
use crate::disk::{RevisionChangeset, RevisionRecord, RevisionState};
use crate::memory::RevisionMemoryCacheDelegate;
use bytes::Bytes;
use diesel::{sql_types::Integer, update, SqliteConnection};
use flowy_collaboration::{
entities::revision::{RevId, RevType, Revision, RevisionRange},
entities::revision::{RevType, Revision, RevisionRange},
util::md5,
};
use flowy_database::{

View File

@ -7,7 +7,6 @@ use flowy_collaboration::{
};
use flowy_error::{FlowyError, FlowyResult};
use lib_infra::future::FutureResult;
use lib_ot::core::{Attributes, Delta};
use std::sync::Arc;
pub trait RevisionCloudService: Send + Sync {

View File

@ -88,7 +88,7 @@ async fn create_view(sdk: &FlowySDKTest, app_id: &str) -> View {
name: "View A".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
data_type: ViewDataType::Block,
data_type: ViewDataType::TextBlock,
ext_data: "".to_string(),
plugin_type: 0,
};

View File

@ -1,10 +1,11 @@
use crate::entities::revision::{md5, RepeatedRevision, Revision};
use crate::errors::{internal_error, CollaborateError, CollaborateResult};
use crate::util::{cal_diff, make_delta_from_revisions};
use flowy_grid_data_model::entities::{GridBlockMeta, RowMeta, RowMetaChangeset, RowOrder};
use flowy_grid_data_model::entities::{GridBlockMeta, RowMeta, RowMetaChangeset};
use lib_infra::uuid;
use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
pub type GridBlockMetaDelta = PlainTextDelta;
@ -49,6 +50,25 @@ impl GridBlockMetaPad {
})
}
pub fn get_rows(&self, row_ids: Vec<String>) -> CollaborateResult<Vec<RowMeta>> {
let row_map = self
.rows
.iter()
.map(|row| (&row.id, row.clone()))
.collect::<HashMap<&String, Arc<RowMeta>>>();
Ok(row_ids
.iter()
.flat_map(|row_id| match row_map.get(row_id) {
None => {
tracing::error!("Can't find the row with id: {}", row_id);
None
}
Some(row) => Some((**row).clone()),
})
.collect::<Vec<RowMeta>>())
}
pub fn update_row(&mut self, changeset: RowMetaChangeset) -> CollaborateResult<Option<GridBlockMetaChange>> {
let row_id = changeset.row_id.clone();
self.modify_row(&row_id, |row| {
@ -123,12 +143,6 @@ impl GridBlockMetaPad {
}
}
fn json_from_grid(block_meta: &Arc<GridBlockMeta>) -> CollaborateResult<String> {
let json = serde_json::to_string(block_meta)
.map_err(|err| internal_error(format!("Serialize grid to json str failed. {:?}", err)))?;
Ok(json)
}
pub struct GridBlockMetaChange {
pub delta: GridBlockMetaDelta,
/// md5: the md5 of the grid after applying the change.
@ -165,9 +179,8 @@ impl std::default::Default for GridBlockMetaPad {
#[cfg(test)]
mod tests {
use crate::client_grid::{GridBlockMetaDelta, GridMetaPad};
use crate::client_grid::{GridBlockMetaDelta, GridBlockMetaPad};
use flowy_grid_data_model::entities::{RowMeta, RowMetaChangeset};
use std::str::FromStr;
#[test]
fn block_meta_add_row() {
@ -227,7 +240,7 @@ mod tests {
cell_by_field_id: Default::default(),
};
let _ = pad.add_row(row.clone()).unwrap().unwrap();
let _ = pad.add_row(row).unwrap().unwrap();
let change = pad.update_row(changeset).unwrap().unwrap();
assert_eq!(
@ -241,8 +254,8 @@ mod tests {
);
}
fn test_pad() -> GridMetaPad {
fn test_pad() -> GridBlockMetaPad {
let delta = GridBlockMetaDelta::from_delta_str(r#"[{"insert":"{\"block_id\":\"1\",\"rows\":[]}"}]"#).unwrap();
GridMetaPad::from_delta(delta).unwrap()
GridBlockMetaPad::from_delta(delta).unwrap()
}
}

View File

@ -0,0 +1,114 @@
use crate::client_grid::{make_block_meta_delta, make_grid_delta, GridBlockMetaDelta, GridMetaDelta};
use crate::errors::{CollaborateError, CollaborateResult};
use flowy_grid_data_model::entities::{Field, FieldType, GridBlock, GridBlockMeta, GridMeta, RowMeta};
pub struct GridBuilder {
grid_id: String,
fields: Vec<Field>,
grid_block: GridBlock,
grid_block_meta: GridBlockMeta,
}
impl GridBuilder {
pub fn new(grid_id: &str) -> Self {
let grid_block = GridBlock::new();
let grid_block_meta = GridBlockMeta {
block_id: grid_block.id.clone(),
rows: vec![],
};
Self {
grid_id: grid_id.to_owned(),
fields: vec![],
grid_block,
grid_block_meta,
}
}
pub fn add_field(mut self, name: &str, desc: &str, field_type: FieldType) -> Self {
let field = Field::new(name, desc, field_type);
self.fields.push(field);
self
}
pub fn add_empty_row(mut self) -> Self {
let row = RowMeta::new(&self.grid_block.id, vec![]);
self.grid_block_meta.rows.push(row);
self
}
pub fn build(self) -> CollaborateResult<BuildGridInfo> {
let block_id = self.grid_block.id.clone();
let grid_meta = GridMeta {
grid_id: self.grid_id,
fields: self.fields,
blocks: vec![self.grid_block],
};
// let _ = check_rows(&self.fields, &self.rows)?;
let grid_delta = make_grid_delta(&grid_meta);
let grid_block_meta_delta = make_block_meta_delta(&self.grid_block_meta);
Ok(BuildGridInfo {
grid_delta,
block_id,
grid_block_meta_delta,
})
}
}
pub struct BuildGridInfo {
pub grid_delta: GridMetaDelta,
pub block_id: String,
pub grid_block_meta_delta: GridBlockMetaDelta,
}
#[allow(dead_code)]
fn check_rows(fields: &[Field], rows: &[RowMeta]) -> CollaborateResult<()> {
let field_ids = fields.iter().map(|field| &field.id).collect::<Vec<&String>>();
for row in rows {
let cell_field_ids = row.cell_by_field_id.keys().into_iter().collect::<Vec<&String>>();
if cell_field_ids != field_ids {
let msg = format!("{:?} contains invalid cells", row);
return Err(CollaborateError::internal().context(msg));
}
}
Ok(())
}
pub fn make_default_grid(grid_id: &str) -> BuildGridInfo {
GridBuilder::new(grid_id)
.add_field("Name", "", FieldType::RichText)
.add_field("Tags", "", FieldType::SingleSelect)
.add_empty_row()
.add_empty_row()
.add_empty_row()
.build()
.unwrap()
}
#[cfg(test)]
mod tests {
use crate::client_grid::GridBuilder;
use flowy_grid_data_model::entities::{FieldType, GridBlockMeta, GridMeta};
#[test]
fn create_default_grid_test() {
let info = GridBuilder::new("1")
.add_field("Name", "", FieldType::RichText)
.add_field("Tags", "", FieldType::SingleSelect)
.add_empty_row()
.add_empty_row()
.add_empty_row()
.build()
.unwrap();
let grid_meta: GridMeta = serde_json::from_str(&info.grid_delta.to_str().unwrap()).unwrap();
assert_eq!(grid_meta.fields.len(), 2);
assert_eq!(grid_meta.blocks.len(), 1);
let grid_block_meta: GridBlockMeta =
serde_json::from_str(&info.grid_block_meta_delta.to_str().unwrap()).unwrap();
assert_eq!(grid_block_meta.rows.len(), 3);
assert_eq!(grid_meta.blocks[0].id, grid_block_meta.block_id);
}
}

View File

@ -2,24 +2,23 @@ use crate::entities::revision::{md5, RepeatedRevision, Revision};
use crate::errors::{internal_error, CollaborateError, CollaborateResult};
use crate::util::{cal_diff, make_delta_from_revisions};
use flowy_grid_data_model::entities::{
Field, FieldChangeset, FieldOrder, Grid, GridBlock, GridBlockChangeset, GridMeta, RepeatedField,
RepeatedFieldOrder, RowMeta, RowOrder,
Field, FieldChangeset, GridBlock, GridBlockChangeset, GridMeta, RepeatedField, RepeatedFieldOrder,
};
use lib_infra::uuid;
use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
use std::collections::HashMap;
use std::sync::Arc;
pub type GridDelta = PlainTextDelta;
pub type GridMetaDelta = PlainTextDelta;
pub type GridDeltaBuilder = PlainTextDeltaBuilder;
pub struct GridMetaPad {
pub(crate) grid_meta: Arc<GridMeta>,
pub(crate) delta: GridDelta,
pub(crate) delta: GridMetaDelta,
}
impl GridMetaPad {
pub fn from_delta(delta: GridDelta) -> CollaborateResult<Self> {
pub fn from_delta(delta: GridMetaDelta) -> CollaborateResult<Self> {
let s = delta.to_str()?;
let grid: GridMeta = serde_json::from_str(&s)
.map_err(|e| CollaborateError::internal().context(format!("Deserialize delta to grid failed: {}", e)))?;
@ -31,7 +30,7 @@ impl GridMetaPad {
}
pub fn from_revisions(_grid_id: &str, revisions: Vec<Revision>) -> CollaborateResult<Self> {
let grid_delta: GridDelta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
let grid_delta: GridMetaDelta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
Self::from_delta(grid_delta)
}
@ -64,7 +63,7 @@ impl GridMetaPad {
.iter()
.flat_map(|field_order| match field_by_field_id.get(&field_order.field_id) {
None => {
tracing::error!("Can't find the field with {}", field_order.field_id);
tracing::error!("Can't find the field with id: {}", field_order.field_id);
None
}
Some(field) => Some((*field).clone()),
@ -123,6 +122,10 @@ impl GridMetaPad {
})
}
pub fn get_blocks(&self) -> Vec<GridBlock> {
self.grid_meta.blocks.clone()
}
pub fn update_block(&mut self, change: GridBlockChangeset) -> CollaborateResult<Option<GridChange>> {
let block_id = change.block_id.clone();
self.modify_block(&block_id, |block| {
@ -204,12 +207,12 @@ fn json_from_grid(grid: &Arc<GridMeta>) -> CollaborateResult<String> {
}
pub struct GridChange {
pub delta: GridDelta,
pub delta: GridMetaDelta,
/// md5: the md5 of the grid after applying the change.
pub md5: String,
}
pub fn make_grid_delta(grid_meta: &GridMeta) -> GridDelta {
pub fn make_grid_delta(grid_meta: &GridMeta) -> GridMetaDelta {
let json = serde_json::to_string(&grid_meta).unwrap();
PlainTextDeltaBuilder::new().insert(&json).build()
}

View File

@ -1,5 +1,7 @@
mod block_pad;
mod grid_builder;
mod grid_pad;
pub use block_pad::*;
pub use grid_builder::*;
pub use grid_pad::*;

View File

@ -17,41 +17,42 @@ use tokio::{
task::spawn_blocking,
};
pub trait DocumentCloudPersistence: Send + Sync + Debug {
fn read_document(&self, doc_id: &str) -> BoxResultFuture<TextBlockInfo, CollaborateError>;
pub trait TextBlockCloudPersistence: Send + Sync + Debug {
fn read_text_block(&self, doc_id: &str) -> BoxResultFuture<TextBlockInfo, CollaborateError>;
fn create_document(
fn create_text_block(
&self,
doc_id: &str,
repeated_revision: RepeatedRevisionPB,
) -> BoxResultFuture<Option<TextBlockInfo>, CollaborateError>;
fn read_document_revisions(
fn read_text_block_revisions(
&self,
doc_id: &str,
rev_ids: Option<Vec<i64>>,
) -> BoxResultFuture<Vec<RevisionPB>, CollaborateError>;
fn save_document_revisions(&self, repeated_revision: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError>;
fn save_text_block_revisions(&self, repeated_revision: RepeatedRevisionPB)
-> BoxResultFuture<(), CollaborateError>;
fn reset_document(
fn reset_text_block(
&self,
doc_id: &str,
repeated_revision: RepeatedRevisionPB,
) -> BoxResultFuture<(), CollaborateError>;
}
impl RevisionSyncPersistence for Arc<dyn DocumentCloudPersistence> {
impl RevisionSyncPersistence for Arc<dyn TextBlockCloudPersistence> {
fn read_revisions(
&self,
object_id: &str,
rev_ids: Option<Vec<i64>>,
) -> BoxResultFuture<Vec<RevisionPB>, CollaborateError> {
(**self).read_document_revisions(object_id, rev_ids)
(**self).read_text_block_revisions(object_id, rev_ids)
}
fn save_revisions(&self, repeated_revision: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError> {
(**self).save_document_revisions(repeated_revision)
(**self).save_text_block_revisions(repeated_revision)
}
fn reset_object(
@ -59,17 +60,17 @@ impl RevisionSyncPersistence for Arc<dyn DocumentCloudPersistence> {
object_id: &str,
repeated_revision: RepeatedRevisionPB,
) -> BoxResultFuture<(), CollaborateError> {
(**self).reset_document(object_id, repeated_revision)
(**self).reset_text_block(object_id, repeated_revision)
}
}
pub struct ServerDocumentManager {
document_handlers: Arc<RwLock<HashMap<String, Arc<OpenDocumentHandler>>>>,
persistence: Arc<dyn DocumentCloudPersistence>,
persistence: Arc<dyn TextBlockCloudPersistence>,
}
impl ServerDocumentManager {
pub fn new(persistence: Arc<dyn DocumentCloudPersistence>) -> Self {
pub fn new(persistence: Arc<dyn TextBlockCloudPersistence>) -> Self {
Self {
document_handlers: Arc::new(RwLock::new(HashMap::new())),
persistence,
@ -151,7 +152,7 @@ impl ServerDocumentManager {
}
let mut write_guard = self.document_handlers.write().await;
match self.persistence.read_document(doc_id).await {
match self.persistence.read_text_block(doc_id).await {
Ok(doc) => {
let handler = self.create_document_handler(doc).await.map_err(internal_error).unwrap();
write_guard.insert(doc_id.to_owned(), handler.clone());
@ -168,7 +169,7 @@ impl ServerDocumentManager {
doc_id: &str,
repeated_revision: RepeatedRevisionPB,
) -> Result<Arc<OpenDocumentHandler>, CollaborateError> {
match self.persistence.create_document(doc_id, repeated_revision).await? {
match self.persistence.create_text_block(doc_id, repeated_revision).await? {
None => Err(CollaborateError::internal().context("Create document info from revisions failed")),
Some(doc) => {
let handler = self.create_document_handler(doc).await?;
@ -205,7 +206,7 @@ struct OpenDocumentHandler {
}
impl OpenDocumentHandler {
fn new(doc: TextBlockInfo, persistence: Arc<dyn DocumentCloudPersistence>) -> Result<Self, CollaborateError> {
fn new(doc: TextBlockInfo, persistence: Arc<dyn TextBlockCloudPersistence>) -> Result<Self, CollaborateError> {
let doc_id = doc.block_id.clone();
let (sender, receiver) = mpsc::channel(1000);
let users = DashMap::new();

View File

@ -83,24 +83,24 @@ impl std::convert::From<View> for Trash {
#[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum ViewDataType {
Block = 0,
TextBlock = 0,
Grid = 1,
}
impl std::default::Default for ViewDataType {
fn default() -> Self {
ViewDataType::Block
ViewDataType::TextBlock
}
}
impl std::convert::From<i32> for ViewDataType {
fn from(val: i32) -> Self {
match val {
0 => ViewDataType::Block,
0 => ViewDataType::TextBlock,
1 => ViewDataType::Grid,
_ => {
log::error!("Invalid view type: {}", val);
ViewDataType::Block
ViewDataType::TextBlock
}
}
}

View File

@ -165,7 +165,7 @@ impl View {
self.data_type
}
pub fn clear_data_type(&mut self) {
self.data_type = ViewDataType::Block;
self.data_type = ViewDataType::TextBlock;
}
// Param is passed by value, moved
@ -409,7 +409,7 @@ impl ::protobuf::Message for View {
if !self.desc.is_empty() {
my_size += ::protobuf::rt::string_size(4, &self.desc);
}
if self.data_type != ViewDataType::Block {
if self.data_type != ViewDataType::TextBlock {
my_size += ::protobuf::rt::enum_size(5, self.data_type);
}
if self.version != 0 {
@ -452,7 +452,7 @@ impl ::protobuf::Message for View {
if !self.desc.is_empty() {
os.write_string(4, &self.desc)?;
}
if self.data_type != ViewDataType::Block {
if self.data_type != ViewDataType::TextBlock {
os.write_enum(5, ::protobuf::ProtobufEnum::value(&self.data_type))?;
}
if self.version != 0 {
@ -596,7 +596,7 @@ impl ::protobuf::Clear for View {
self.belong_to_id.clear();
self.name.clear();
self.desc.clear();
self.data_type = ViewDataType::Block;
self.data_type = ViewDataType::TextBlock;
self.version = 0;
self.belongings.clear();
self.modified_time = 0;
@ -952,7 +952,7 @@ impl CreateViewPayload {
self.data_type
}
pub fn clear_data_type(&mut self) {
self.data_type = ViewDataType::Block;
self.data_type = ViewDataType::TextBlock;
}
// Param is passed by value, moved
@ -1060,7 +1060,7 @@ impl ::protobuf::Message for CreateViewPayload {
if !self.desc.is_empty() {
my_size += ::protobuf::rt::string_size(3, &self.desc);
}
if self.data_type != ViewDataType::Block {
if self.data_type != ViewDataType::TextBlock {
my_size += ::protobuf::rt::enum_size(5, self.data_type);
}
if !self.ext_data.is_empty() {
@ -1091,7 +1091,7 @@ impl ::protobuf::Message for CreateViewPayload {
if !self.desc.is_empty() {
os.write_string(3, &self.desc)?;
}
if self.data_type != ViewDataType::Block {
if self.data_type != ViewDataType::TextBlock {
os.write_enum(5, ::protobuf::ProtobufEnum::value(&self.data_type))?;
}
if !self.ext_data.is_empty() {
@ -1200,7 +1200,7 @@ impl ::protobuf::Clear for CreateViewPayload {
self.name.clear();
self.desc.clear();
self.one_of_thumbnail = ::std::option::Option::None;
self.data_type = ViewDataType::Block;
self.data_type = ViewDataType::TextBlock;
self.ext_data.clear();
self.plugin_type = 0;
self.unknown_fields.clear();
@ -1358,7 +1358,7 @@ impl CreateViewParams {
self.data_type
}
pub fn clear_data_type(&mut self) {
self.data_type = ViewDataType::Block;
self.data_type = ViewDataType::TextBlock;
}
// Param is passed by value, moved
@ -1524,7 +1524,7 @@ impl ::protobuf::Message for CreateViewParams {
if !self.thumbnail.is_empty() {
my_size += ::protobuf::rt::string_size(4, &self.thumbnail);
}
if self.data_type != ViewDataType::Block {
if self.data_type != ViewDataType::TextBlock {
my_size += ::protobuf::rt::enum_size(5, self.data_type);
}
if !self.ext_data.is_empty() {
@ -1557,7 +1557,7 @@ impl ::protobuf::Message for CreateViewParams {
if !self.thumbnail.is_empty() {
os.write_string(4, &self.thumbnail)?;
}
if self.data_type != ViewDataType::Block {
if self.data_type != ViewDataType::TextBlock {
os.write_enum(5, ::protobuf::ProtobufEnum::value(&self.data_type))?;
}
if !self.ext_data.is_empty() {
@ -1675,7 +1675,7 @@ impl ::protobuf::Clear for CreateViewParams {
self.name.clear();
self.desc.clear();
self.thumbnail.clear();
self.data_type = ViewDataType::Block;
self.data_type = ViewDataType::TextBlock;
self.ext_data.clear();
self.view_id.clear();
self.data.clear();
@ -2821,7 +2821,7 @@ impl ::protobuf::reflect::ProtobufValue for UpdateViewParams {
#[derive(Clone,PartialEq,Eq,Debug,Hash)]
pub enum ViewDataType {
Block = 0,
TextBlock = 0,
Grid = 1,
}
@ -2832,7 +2832,7 @@ impl ::protobuf::ProtobufEnum for ViewDataType {
fn from_i32(value: i32) -> ::std::option::Option<ViewDataType> {
match value {
0 => ::std::option::Option::Some(ViewDataType::Block),
0 => ::std::option::Option::Some(ViewDataType::TextBlock),
1 => ::std::option::Option::Some(ViewDataType::Grid),
_ => ::std::option::Option::None
}
@ -2840,7 +2840,7 @@ impl ::protobuf::ProtobufEnum for ViewDataType {
fn values() -> &'static [Self] {
static values: &'static [ViewDataType] = &[
ViewDataType::Block,
ViewDataType::TextBlock,
ViewDataType::Grid,
];
values
@ -2859,7 +2859,7 @@ impl ::std::marker::Copy for ViewDataType {
impl ::std::default::Default for ViewDataType {
fn default() -> Self {
ViewDataType::Block
ViewDataType::TextBlock
}
}
@ -2904,8 +2904,8 @@ static file_descriptor_proto_data: &'static [u8] = b"\
\x17\n\x07view_id\x18\x01\x20\x01(\tR\x06viewId\x12\x14\n\x04name\x18\
\x02\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x03\x20\x01(\tH\x01R\
\x04desc\x12\x1e\n\tthumbnail\x18\x04\x20\x01(\tH\x02R\tthumbnailB\r\n\
\x0bone_of_nameB\r\n\x0bone_of_descB\x12\n\x10one_of_thumbnail*#\n\x0cVi\
ewDataType\x12\t\n\x05Block\x10\0\x12\x08\n\x04Grid\x10\x01b\x06proto3\
\x0bone_of_nameB\r\n\x0bone_of_descB\x12\n\x10one_of_thumbnail*'\n\x0cVi\
ewDataType\x12\r\n\tTextBlock\x10\0\x12\x08\n\x04Grid\x10\x01b\x06proto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -56,6 +56,6 @@ message UpdateViewParams {
oneof one_of_thumbnail { string thumbnail = 4; };
}
enum ViewDataType {
Block = 0;
TextBlock = 0;
Grid = 1;
}

View File

@ -49,7 +49,7 @@ fn create_default_view(app_id: String, time: chrono::DateTime<Utc>) -> View {
let view_id = uuid::Uuid::new_v4();
let name = "Read me".to_string();
let desc = "".to_string();
let data_type = ViewDataType::Block;
let data_type = ViewDataType::TextBlock;
View {
id: view_id.to_string(),

View File

@ -30,6 +30,15 @@ pub struct GridBlock {
pub row_count: i32,
}
impl GridBlock {
pub fn new() -> Self {
GridBlock {
id: uuid::Uuid::new_v4().to_string(),
..Default::default()
}
}
}
pub struct GridBlockChangeset {
pub block_id: String,
pub start_row_index: Option<i32>,
@ -73,9 +82,9 @@ pub struct Field {
}
impl Field {
pub fn new(id: &str, name: &str, desc: &str, field_type: FieldType) -> Self {
pub fn new(name: &str, desc: &str, field_type: FieldType) -> Self {
Self {
id: id.to_owned(),
id: uuid::Uuid::new_v4().to_string(),
name: name.to_string(),
desc: desc.to_string(),
field_type,
@ -224,14 +233,14 @@ pub struct RowMeta {
}
impl RowMeta {
pub fn new(id: &str, block_id: &str, cells: Vec<CellMeta>) -> Self {
pub fn new(block_id: &str, cells: Vec<CellMeta>) -> Self {
let cell_by_field_id = cells
.into_iter()
.map(|cell| (cell.id.clone(), cell))
.collect::<HashMap<String, CellMeta>>();
Self {
id: id.to_owned(),
id: uuid::Uuid::new_v4().to_string(),
block_id: block_id.to_owned(),
cell_by_field_id,
height: DEFAULT_ROW_HEIGHT,

View File

@ -32,7 +32,9 @@ fn grid_default_serde_test() {
}
fn create_field(field_id: &str) -> Field {
Field::new(field_id, "Text Field", "", FieldType::RichText)
let mut field = Field::new("Text Field", "", FieldType::RichText);
field.id = field_id.to_string();
field
}
#[allow(dead_code)]