mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: streamline create row logic (#4807)
This commit is contained in:
parent
25d4b4f718
commit
191a077a86
@ -28,16 +28,10 @@ class RowBackendService {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, String>? cellDataByFieldId;
|
|
||||||
|
|
||||||
if (withCells != null) {
|
if (withCells != null) {
|
||||||
final rowBuilder = RowDataBuilder();
|
final rowBuilder = RowDataBuilder();
|
||||||
withCells(rowBuilder);
|
withCells(rowBuilder);
|
||||||
cellDataByFieldId = rowBuilder.build();
|
payload.data.addAll(rowBuilder.build());
|
||||||
}
|
|
||||||
|
|
||||||
if (cellDataByFieldId != null) {
|
|
||||||
payload.data = RowDataPB(cellDataByFieldId: cellDataByFieldId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DatabaseEventCreateRow(payload).send();
|
return DatabaseEventCreateRow(payload).send();
|
||||||
|
@ -30,7 +30,7 @@ export async function createRow(viewId: string, params?: {
|
|||||||
object_id: params?.rowId,
|
object_id: params?.rowId,
|
||||||
},
|
},
|
||||||
group_id: params?.groupId,
|
group_id: params?.groupId,
|
||||||
data: params?.data ? { cell_data_by_field_id: params.data } : undefined,
|
data: params?.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await DatabaseEventCreateRow(payload);
|
const result = await DatabaseEventCreateRow(payload);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@ -200,7 +201,7 @@ impl EventIntegrationTest {
|
|||||||
&self,
|
&self,
|
||||||
view_id: &str,
|
view_id: &str,
|
||||||
row_position: OrderObjectPositionPB,
|
row_position: OrderObjectPositionPB,
|
||||||
data: Option<RowDataPB>,
|
data: Option<HashMap<String, String>>,
|
||||||
) -> RowMetaPB {
|
) -> RowMetaPB {
|
||||||
EventBuilder::new(self.clone())
|
EventBuilder::new(self.clone())
|
||||||
.event(DatabaseEvent::CreateRow)
|
.event(DatabaseEvent::CreateRow)
|
||||||
@ -208,7 +209,7 @@ impl EventIntegrationTest {
|
|||||||
view_id: view_id.to_string(),
|
view_id: view_id.to_string(),
|
||||||
row_position,
|
row_position,
|
||||||
group_id: None,
|
group_id: None,
|
||||||
data,
|
data: data.unwrap_or_default(),
|
||||||
})
|
})
|
||||||
.async_send()
|
.async_send()
|
||||||
.await
|
.await
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use collab_database::rows::{Row, RowDetail, RowId};
|
use collab_database::rows::{Row, RowDetail, RowId};
|
||||||
use collab_database::views::{OrderObjectPosition, RowOrder};
|
use collab_database::views::RowOrder;
|
||||||
|
|
||||||
use flowy_derive::ProtoBuf;
|
use flowy_derive::ProtoBuf;
|
||||||
use flowy_error::ErrorCode;
|
use flowy_error::ErrorCode;
|
||||||
|
use lib_infra::validator_fn::required_not_empty_str;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::entities::parser::NotEmptyStr;
|
use crate::entities::parser::NotEmptyStr;
|
||||||
use crate::entities::position_entities::OrderObjectPositionPB;
|
use crate::entities::position_entities::OrderObjectPositionPB;
|
||||||
@ -335,46 +337,25 @@ impl TryInto<RowIdParams> for RowIdPB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ProtoBuf, Default)]
|
#[derive(ProtoBuf, Default, Validate)]
|
||||||
pub struct CreateRowPayloadPB {
|
pub struct CreateRowPayloadPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
#[validate(custom = "required_not_empty_str")]
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
|
|
||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
pub row_position: OrderObjectPositionPB,
|
pub row_position: OrderObjectPositionPB,
|
||||||
|
|
||||||
#[pb(index = 3, one_of)]
|
#[pb(index = 3, one_of)]
|
||||||
|
#[validate(custom = "required_not_empty_str")]
|
||||||
pub group_id: Option<String>,
|
pub group_id: Option<String>,
|
||||||
|
|
||||||
#[pb(index = 4, one_of)]
|
#[pb(index = 4)]
|
||||||
pub data: Option<RowDataPB>,
|
pub data: HashMap<String, String>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(ProtoBuf, Default)]
|
|
||||||
pub struct RowDataPB {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
pub cell_data_by_field_id: HashMap<String, String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CreateRowParams {
|
pub struct CreateRowParams {
|
||||||
pub view_id: String,
|
pub collab_params: collab_database::rows::CreateRowParams,
|
||||||
pub row_position: OrderObjectPosition,
|
pub open_after_create: bool,
|
||||||
pub group_id: Option<String>,
|
|
||||||
pub cell_data_by_field_id: Option<HashMap<String, String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<CreateRowParams> for CreateRowPayloadPB {
|
|
||||||
type Error = ErrorCode;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<CreateRowParams, Self::Error> {
|
|
||||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
|
|
||||||
let position = self.row_position.try_into()?;
|
|
||||||
Ok(CreateRowParams {
|
|
||||||
view_id: view_id.0,
|
|
||||||
row_position: position,
|
|
||||||
group_id: self.group_id,
|
|
||||||
cell_data_by_field_id: self.data.map(|data| data.cell_data_by_field_id),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use collab_database::database::gen_row_id;
|
|
||||||
use collab_database::rows::RowId;
|
use collab_database::rows::RowId;
|
||||||
use lib_infra::box_any::BoxAny;
|
use lib_infra::box_any::BoxAny;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use lib_dispatch::prelude::{af_spawn, data_result_ok, AFPluginData, AFPluginState, DataResult};
|
use lib_dispatch::prelude::{af_spawn, data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||||
use lib_infra::util::timestamp;
|
|
||||||
|
|
||||||
use crate::entities::*;
|
use crate::entities::*;
|
||||||
use crate::manager::DatabaseManager;
|
use crate::manager::DatabaseManager;
|
||||||
use crate::services::cell::CellBuilder;
|
|
||||||
use crate::services::field::{
|
use crate::services::field::{
|
||||||
type_option_data_from_pb, ChecklistCellChangeset, DateCellChangeset, RelationCellChangeset,
|
type_option_data_from_pb, ChecklistCellChangeset, DateCellChangeset, RelationCellChangeset,
|
||||||
SelectOptionCellChangeset,
|
SelectOptionCellChangeset,
|
||||||
@ -393,7 +390,7 @@ pub(crate) async fn duplicate_row_handler(
|
|||||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||||
database_editor
|
database_editor
|
||||||
.duplicate_row(¶ms.view_id, ¶ms.row_id)
|
.duplicate_row(¶ms.view_id, ¶ms.row_id)
|
||||||
.await;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,27 +414,12 @@ pub(crate) async fn create_row_handler(
|
|||||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||||
) -> DataResult<RowMetaPB, FlowyError> {
|
) -> DataResult<RowMetaPB, FlowyError> {
|
||||||
let manager = upgrade_manager(manager)?;
|
let manager = upgrade_manager(manager)?;
|
||||||
let params: CreateRowParams = data.into_inner().try_into()?;
|
let params = data.try_into_inner()?;
|
||||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||||
let fields = database_editor.get_fields(¶ms.view_id, None);
|
|
||||||
let cells =
|
match database_editor.create_row(params).await? {
|
||||||
CellBuilder::with_cells(params.cell_data_by_field_id.unwrap_or_default(), &fields).build();
|
|
||||||
let view_id = params.view_id;
|
|
||||||
let group_id = params.group_id;
|
|
||||||
let params = collab_database::rows::CreateRowParams {
|
|
||||||
id: gen_row_id(),
|
|
||||||
cells,
|
|
||||||
height: 60,
|
|
||||||
visibility: true,
|
|
||||||
row_position: params.row_position,
|
|
||||||
timestamp: timestamp(),
|
|
||||||
};
|
|
||||||
match database_editor
|
|
||||||
.create_row(&view_id, group_id, params)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
None => Err(FlowyError::internal().with_context("Create row fail")),
|
|
||||||
Some(row) => data_result_ok(RowMetaPB::from(row)),
|
Some(row) => data_result_ok(RowMetaPB::from(row)),
|
||||||
|
None => Err(FlowyError::internal().with_context("Error creating row")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use collab_database::database::MutexDatabase;
|
use collab_database::database::MutexDatabase;
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetail, RowId};
|
use collab_database::rows::{Cell, Cells, Row, RowCell, RowDetail, RowId};
|
||||||
use collab_database::views::{
|
use collab_database::views::{
|
||||||
DatabaseLayout, DatabaseView, FilterMap, LayoutSetting, OrderObjectPosition,
|
DatabaseLayout, DatabaseView, FilterMap, LayoutSetting, OrderObjectPosition,
|
||||||
};
|
};
|
||||||
@ -457,20 +457,33 @@ impl DatabaseEditor {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// consider returning a result. But most of the time, it should be fine to just ignore the error.
|
pub async fn duplicate_row(&self, view_id: &str, row_id: &RowId) -> FlowyResult<()> {
|
||||||
pub async fn duplicate_row(&self, view_id: &str, row_id: &RowId) {
|
let (row_detail, index) = {
|
||||||
let params = self.database.lock().duplicate_row(row_id);
|
let database = self.database.lock();
|
||||||
match params {
|
|
||||||
None => warn!("Failed to duplicate row: {}", row_id),
|
let params = database
|
||||||
Some(params) => {
|
.duplicate_row(row_id)
|
||||||
let result = self.create_row(view_id, None, params).await;
|
.ok_or_else(|| FlowyError::internal().with_context("error while copying row"))?;
|
||||||
if let Some(row_detail) = result.unwrap_or(None) {
|
|
||||||
for view in self.database_views.editors().await {
|
let (index, row_order) = database
|
||||||
view.v_did_duplicate_row(&row_detail).await;
|
.create_row_in_view(view_id, params)
|
||||||
}
|
.ok_or_else(|| {
|
||||||
}
|
FlowyError::internal().with_context("error while inserting duplicated row")
|
||||||
},
|
})?;
|
||||||
|
|
||||||
|
tracing::trace!("duplicated row: {:?} at {}", row_order, index);
|
||||||
|
let row_detail = database.get_row_detail(&row_order.id);
|
||||||
|
|
||||||
|
(row_detail, index)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(row_detail) = row_detail {
|
||||||
|
for view in self.database_views.editors().await {
|
||||||
|
view.v_did_create_row(&row_detail, index).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn move_row(
|
pub async fn move_row(
|
||||||
@ -506,18 +519,21 @@ impl DatabaseEditor {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_row(
|
pub async fn create_row(&self, params: CreateRowPayloadPB) -> FlowyResult<Option<RowDetail>> {
|
||||||
&self,
|
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
||||||
view_id: &str,
|
|
||||||
group_id: Option<String>,
|
let CreateRowParams {
|
||||||
mut params: CreateRowParams,
|
collab_params,
|
||||||
) -> FlowyResult<Option<RowDetail>> {
|
open_after_create: _,
|
||||||
for view in self.database_views.editors().await {
|
} = view_editor.v_will_create_row(params).await?;
|
||||||
view.v_will_create_row(&mut params.cells, &group_id).await;
|
|
||||||
}
|
let result = self
|
||||||
let result = self.database.lock().create_row_in_view(view_id, params);
|
.database
|
||||||
|
.lock()
|
||||||
|
.create_row_in_view(&view_editor.view_id, collab_params);
|
||||||
|
|
||||||
if let Some((index, row_order)) = result {
|
if let Some((index, row_order)) = result {
|
||||||
tracing::trace!("create row: {:?} at {}", row_order, index);
|
tracing::trace!("created row: {:?} at {}", row_order, index);
|
||||||
let row_detail = self.database.lock().get_row_detail(&row_order.id);
|
let row_detail = self.database.lock().get_row_detail(&row_order.id);
|
||||||
if let Some(row_detail) = row_detail {
|
if let Some(row_detail) = row_detail {
|
||||||
for view in self.database_views.editors().await {
|
for view in self.database_views.editors().await {
|
||||||
@ -1380,9 +1396,9 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
|
|||||||
to_fut(async move { view })
|
to_fut(async move { view })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>> {
|
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Field>> {
|
||||||
let fields = self.database.lock().get_fields_in_view(view_id, field_ids);
|
let fields = self.database.lock().get_fields_in_view(view_id, field_ids);
|
||||||
to_fut(async move { fields.into_iter().map(Arc::new).collect() })
|
to_fut(async move { fields })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_field(&self, field_id: &str) -> Option<Field> {
|
fn get_field(&self, field_id: &str) -> Option<Field> {
|
||||||
|
@ -2,10 +2,11 @@ use std::borrow::Cow;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::database::{gen_database_calculation_id, gen_database_sort_id};
|
use collab_database::database::{gen_database_calculation_id, gen_database_sort_id, gen_row_id};
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
||||||
use collab_database::views::{DatabaseLayout, DatabaseView};
|
use collab_database::views::{DatabaseLayout, DatabaseView};
|
||||||
|
use lib_infra::util::timestamp;
|
||||||
use tokio::sync::{broadcast, RwLock};
|
use tokio::sync::{broadcast, RwLock};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@ -13,14 +14,15 @@ use flowy_error::{FlowyError, FlowyResult};
|
|||||||
use lib_dispatch::prelude::af_spawn;
|
use lib_dispatch::prelude::af_spawn;
|
||||||
|
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteSortPayloadPB, FieldType,
|
CalendarEventPB, CreateRowParams, CreateRowPayloadPB, DatabaseLayoutMetaPB,
|
||||||
FieldVisibility, GroupChangesPB, GroupPB, InsertedRowPB, LayoutSettingChangeset,
|
DatabaseLayoutSettingPB, DeleteSortPayloadPB, FieldType, FieldVisibility, GroupChangesPB,
|
||||||
LayoutSettingParams, RemoveCalculationChangesetPB, ReorderSortPayloadPB, RowMetaPB, RowsChangePB,
|
GroupPB, InsertedRowPB, LayoutSettingChangeset, LayoutSettingParams,
|
||||||
|
RemoveCalculationChangesetPB, ReorderSortPayloadPB, RowMetaPB, RowsChangePB,
|
||||||
SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB, UpdateSortPayloadPB,
|
SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB, UpdateSortPayloadPB,
|
||||||
};
|
};
|
||||||
use crate::notification::{send_notification, DatabaseNotification};
|
use crate::notification::{send_notification, DatabaseNotification};
|
||||||
use crate::services::calculations::{Calculation, CalculationChangeset, CalculationsController};
|
use crate::services::calculations::{Calculation, CalculationChangeset, CalculationsController};
|
||||||
use crate::services::cell::CellCache;
|
use crate::services::cell::{CellBuilder, CellCache};
|
||||||
use crate::services::database::{database_view_setting_pb_from_view, DatabaseRowEvent, UpdatedRow};
|
use crate::services::database::{database_view_setting_pb_from_view, DatabaseRowEvent, UpdatedRow};
|
||||||
use crate::services::database_view::view_filter::make_filter_controller;
|
use crate::services::database_view::view_filter::make_filter_controller;
|
||||||
use crate::services::database_view::view_group::{
|
use crate::services::database_view::view_group::{
|
||||||
@ -115,17 +117,42 @@ impl DatabaseViewEditor {
|
|||||||
self.delegate.get_view(&self.view_id).await
|
self.delegate.get_view(&self.view_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn v_will_create_row(&self, cells: &mut Cells, group_id: &Option<String>) {
|
pub async fn v_will_create_row(
|
||||||
if group_id.is_none() {
|
&self,
|
||||||
return;
|
params: CreateRowPayloadPB,
|
||||||
|
) -> FlowyResult<CreateRowParams> {
|
||||||
|
let mut result = CreateRowParams {
|
||||||
|
collab_params: collab_database::rows::CreateRowParams {
|
||||||
|
id: gen_row_id(),
|
||||||
|
cells: Cells::new(),
|
||||||
|
height: 60,
|
||||||
|
visibility: true,
|
||||||
|
row_position: params.row_position.try_into()?,
|
||||||
|
timestamp: timestamp(),
|
||||||
|
},
|
||||||
|
open_after_create: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// fill in cells from the frontend
|
||||||
|
let fields = self.delegate.get_fields(¶ms.view_id, None).await;
|
||||||
|
let mut cells = CellBuilder::with_cells(params.data, &fields).build();
|
||||||
|
|
||||||
|
// fill in cells according to group_id if supplied
|
||||||
|
if let Some(group_id) = params.group_id {
|
||||||
|
let _ = self
|
||||||
|
.mut_group_controller(|group_controller, field| {
|
||||||
|
group_controller.will_create_row(&mut cells, &field, &group_id);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
let group_id = group_id.as_ref().unwrap();
|
|
||||||
let _ = self
|
// fill in cells according to active filters
|
||||||
.mut_group_controller(|group_controller, field| {
|
// TODO(RS)
|
||||||
group_controller.will_create_row(cells, &field, group_id);
|
|
||||||
Ok(())
|
result.collab_params.cells = cells;
|
||||||
})
|
|
||||||
.await;
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn v_did_update_row_meta(&self, row_id: &RowId, row_detail: &RowDetail) {
|
pub async fn v_did_update_row_meta(&self, row_id: &RowId, row_detail: &RowDetail) {
|
||||||
@ -160,13 +187,6 @@ impl DatabaseViewEditor {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn v_did_duplicate_row(&self, row_detail: &RowDetail) {
|
|
||||||
self
|
|
||||||
.calculations_controller
|
|
||||||
.did_receive_row_changed(row_detail.row.clone())
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[tracing::instrument(level = "trace", skip_all)]
|
||||||
pub async fn v_did_delete_row(&self, row: &Row) {
|
pub async fn v_did_delete_row(&self, row: &Row) {
|
||||||
let deleted_row = row.clone();
|
let deleted_row = row.clone();
|
||||||
@ -796,12 +816,8 @@ impl DatabaseViewEditor {
|
|||||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||||
pub async fn v_grouping_by_field(&self, field_id: &str) -> FlowyResult<()> {
|
pub async fn v_grouping_by_field(&self, field_id: &str) -> FlowyResult<()> {
|
||||||
if let Some(field) = self.delegate.get_field(field_id) {
|
if let Some(field) = self.delegate.get_field(field_id) {
|
||||||
let new_group_controller = new_group_controller_with_field(
|
let new_group_controller =
|
||||||
self.view_id.clone(),
|
new_group_controller_with_field(self.view_id.clone(), self.delegate.clone(), field).await?;
|
||||||
self.delegate.clone(),
|
|
||||||
Arc::new(field),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let new_groups = new_group_controller
|
let new_groups = new_group_controller
|
||||||
.get_all_groups()
|
.get_all_groups()
|
||||||
|
@ -50,7 +50,7 @@ impl FilterDelegate for DatabaseViewFilterDelegateImpl {
|
|||||||
self.0.get_field(field_id)
|
self.0.get_field(field_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>> {
|
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Field>> {
|
||||||
self.0.get_fields(view_id, field_ids)
|
self.0.get_fields(view_id, field_ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ use crate::services::group::{
|
|||||||
pub async fn new_group_controller_with_field(
|
pub async fn new_group_controller_with_field(
|
||||||
view_id: String,
|
view_id: String,
|
||||||
delegate: Arc<dyn DatabaseViewOperation>,
|
delegate: Arc<dyn DatabaseViewOperation>,
|
||||||
grouping_field: Arc<Field>,
|
grouping_field: Field,
|
||||||
) -> FlowyResult<Box<dyn GroupController>> {
|
) -> FlowyResult<Box<dyn GroupController>> {
|
||||||
let setting_reader = GroupSettingReaderImpl(delegate.clone());
|
let setting_reader = GroupSettingReaderImpl(delegate.clone());
|
||||||
let rows = delegate.get_rows(&view_id).await;
|
let rows = delegate.get_rows(&view_id).await;
|
||||||
|
@ -27,7 +27,7 @@ pub trait DatabaseViewOperation: Send + Sync + 'static {
|
|||||||
/// Get the view of the database with the view_id
|
/// Get the view of the database with the view_id
|
||||||
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
|
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
|
||||||
/// If the field_ids is None, then it will return all the field revisions
|
/// If the field_ids is None, then it will return all the field revisions
|
||||||
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
|
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Field>>;
|
||||||
|
|
||||||
/// Returns the field with the field_id
|
/// Returns the field with the field_id
|
||||||
fn get_field(&self, field_id: &str) -> Option<Field>;
|
fn get_field(&self, field_id: &str) -> Option<Field>;
|
||||||
|
@ -74,7 +74,7 @@ impl SortDelegate for DatabaseViewSortDelegateImpl {
|
|||||||
self.delegate.get_field(field_id)
|
self.delegate.get_field(field_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>> {
|
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Field>> {
|
||||||
self.delegate.get_fields(view_id, field_ids)
|
self.delegate.get_fields(view_id, field_ids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ use crate::services::filter::{Filter, FilterChangeset, FilterInner, FilterResult
|
|||||||
|
|
||||||
pub trait FilterDelegate: Send + Sync + 'static {
|
pub trait FilterDelegate: Send + Sync + 'static {
|
||||||
fn get_field(&self, field_id: &str) -> Option<Field>;
|
fn get_field(&self, field_id: &str) -> Option<Field>;
|
||||||
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
|
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Field>>;
|
||||||
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
|
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
|
||||||
fn get_row(&self, view_id: &str, rows_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>>;
|
fn get_row(&self, view_id: &str, rows_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>>;
|
||||||
fn save_filters(&self, view_id: &str, filters: &[Filter]);
|
fn save_filters(&self, view_id: &str, filters: &[Filter]);
|
||||||
@ -116,14 +116,14 @@ impl FilterController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_field_map(&self) -> HashMap<String, Arc<Field>> {
|
async fn get_field_map(&self) -> HashMap<String, Field> {
|
||||||
self
|
self
|
||||||
.delegate
|
.delegate
|
||||||
.get_fields(&self.view_id, None)
|
.get_fields(&self.view_id, None)
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|field| (field.id.clone(), field))
|
.map(|field| (field.id.clone(), field))
|
||||||
.collect::<HashMap<String, Arc<Field>>>()
|
.collect::<HashMap<String, Field>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(
|
#[tracing::instrument(
|
||||||
@ -333,7 +333,7 @@ impl FilterController {
|
|||||||
fn filter_row(
|
fn filter_row(
|
||||||
row: &Row,
|
row: &Row,
|
||||||
result_by_row_id: &DashMap<RowId, bool>,
|
result_by_row_id: &DashMap<RowId, bool>,
|
||||||
field_by_field_id: &HashMap<String, Arc<Field>>,
|
field_by_field_id: &HashMap<String, Field>,
|
||||||
cell_data_cache: &CellCache,
|
cell_data_cache: &CellCache,
|
||||||
filters: &Vec<Filter>,
|
filters: &Vec<Filter>,
|
||||||
) -> Option<(RowId, bool)> {
|
) -> Option<(RowId, bool)> {
|
||||||
@ -360,7 +360,7 @@ fn filter_row(
|
|||||||
/// Recursively applies a `Filter` to a `Row`'s cells.
|
/// Recursively applies a `Filter` to a `Row`'s cells.
|
||||||
fn apply_filter(
|
fn apply_filter(
|
||||||
row: &Row,
|
row: &Row,
|
||||||
field_by_field_id: &HashMap<String, Arc<Field>>,
|
field_by_field_id: &HashMap<String, Field>,
|
||||||
cell_data_cache: &CellCache,
|
cell_data_cache: &CellCache,
|
||||||
filter: &Filter,
|
filter: &Filter,
|
||||||
) -> Option<bool> {
|
) -> Option<bool> {
|
||||||
@ -405,14 +405,10 @@ fn apply_filter(
|
|||||||
}
|
}
|
||||||
let cell = row.cells.get(field_id).cloned();
|
let cell = row.cells.get(field_id).cloned();
|
||||||
let field_type = FieldType::from(field.field_type);
|
let field_type = FieldType::from(field.field_type);
|
||||||
if let Some(handler) = TypeOptionCellExt::new(field.as_ref(), Some(cell_data_cache.clone()))
|
if let Some(handler) = TypeOptionCellExt::new(field, Some(cell_data_cache.clone()))
|
||||||
.get_type_option_cell_data_handler(&field_type)
|
.get_type_option_cell_data_handler(&field_type)
|
||||||
{
|
{
|
||||||
Some(handler.handle_cell_filter(
|
Some(handler.handle_cell_filter(field, &cell.unwrap_or_default(), condition_and_content))
|
||||||
field.as_ref(),
|
|
||||||
&cell.unwrap_or_default(),
|
|
||||||
condition_and_content,
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
Some(true)
|
Some(true)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ pub struct GroupContext<C> {
|
|||||||
configuration_phantom: PhantomData<C>,
|
configuration_phantom: PhantomData<C>,
|
||||||
|
|
||||||
/// The grouping field
|
/// The grouping field
|
||||||
field: Arc<Field>,
|
field: Field,
|
||||||
|
|
||||||
/// Cache all the groups. Cache the group by its id.
|
/// Cache all the groups. Cache the group by its id.
|
||||||
/// We use the id of the [Field] as the [No Status] group id.
|
/// We use the id of the [Field] as the [No Status] group id.
|
||||||
@ -94,7 +94,7 @@ where
|
|||||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
view_id: String,
|
view_id: String,
|
||||||
field: Arc<Field>,
|
field: Field,
|
||||||
reader: Arc<dyn GroupSettingReader>,
|
reader: Arc<dyn GroupSettingReader>,
|
||||||
writer: Arc<dyn GroupSettingWriter>,
|
writer: Arc<dyn GroupSettingWriter>,
|
||||||
) -> FlowyResult<Self> {
|
) -> FlowyResult<Self> {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
@ -75,7 +74,7 @@ where
|
|||||||
I: GroupOperationInterceptor<GroupTypeOption = T> + Send + Sync,
|
I: GroupOperationInterceptor<GroupTypeOption = T> + Send + Sync,
|
||||||
{
|
{
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
grouping_field: &Arc<Field>,
|
grouping_field: &Field,
|
||||||
mut configuration: GroupContext<C>,
|
mut configuration: GroupContext<C>,
|
||||||
operation_interceptor: I,
|
operation_interceptor: I,
|
||||||
) -> FlowyResult<Self> {
|
) -> FlowyResult<Self> {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
||||||
@ -26,7 +24,7 @@ pub struct DefaultGroupController {
|
|||||||
const DEFAULT_GROUP_CONTROLLER: &str = "DefaultGroupController";
|
const DEFAULT_GROUP_CONTROLLER: &str = "DefaultGroupController";
|
||||||
|
|
||||||
impl DefaultGroupController {
|
impl DefaultGroupController {
|
||||||
pub fn new(field: &Arc<Field>) -> Self {
|
pub fn new(field: &Field) -> Self {
|
||||||
let group = GroupData::new(
|
let group = GroupData::new(
|
||||||
DEFAULT_GROUP_CONTROLLER.to_owned(),
|
DEFAULT_GROUP_CONTROLLER.to_owned(),
|
||||||
field.id.clone(),
|
field.id.clone(),
|
||||||
|
@ -96,7 +96,7 @@ impl RowChangeset {
|
|||||||
)]
|
)]
|
||||||
pub async fn make_group_controller<R, W, TW>(
|
pub async fn make_group_controller<R, W, TW>(
|
||||||
view_id: String,
|
view_id: String,
|
||||||
grouping_field: Arc<Field>,
|
grouping_field: Field,
|
||||||
row_details: Vec<Arc<RowDetail>>,
|
row_details: Vec<Arc<RowDetail>>,
|
||||||
setting_reader: R,
|
setting_reader: R,
|
||||||
setting_writer: W,
|
setting_writer: W,
|
||||||
@ -200,10 +200,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
pub fn find_new_grouping_field(
|
pub fn find_new_grouping_field(fields: &[Field], _layout: &DatabaseLayout) -> Option<Field> {
|
||||||
fields: &[Arc<Field>],
|
|
||||||
_layout: &DatabaseLayout,
|
|
||||||
) -> Option<Arc<Field>> {
|
|
||||||
let mut groupable_field_revs = fields
|
let mut groupable_field_revs = fields
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|field_rev| {
|
.flat_map(|field_rev| {
|
||||||
@ -213,7 +210,7 @@ pub fn find_new_grouping_field(
|
|||||||
false => None,
|
false => None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<Arc<Field>>>();
|
.collect::<Vec<Field>>();
|
||||||
|
|
||||||
if groupable_field_revs.is_empty() {
|
if groupable_field_revs.is_empty() {
|
||||||
// If there is not groupable fields then we use the primary field.
|
// If there is not groupable fields then we use the primary field.
|
||||||
|
@ -30,7 +30,7 @@ pub trait SortDelegate: Send + Sync {
|
|||||||
/// Returns all the rows after applying grid's filter
|
/// Returns all the rows after applying grid's filter
|
||||||
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
|
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
|
||||||
fn get_field(&self, field_id: &str) -> Option<Field>;
|
fn get_field(&self, field_id: &str) -> Option<Field>;
|
||||||
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
|
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Field>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SortController {
|
pub struct SortController {
|
||||||
@ -290,7 +290,7 @@ fn cmp_row(
|
|||||||
left: &Row,
|
left: &Row,
|
||||||
right: &Row,
|
right: &Row,
|
||||||
sort: &Arc<Sort>,
|
sort: &Arc<Sort>,
|
||||||
fields: &[Arc<Field>],
|
fields: &[Field],
|
||||||
cell_data_cache: &CellCache,
|
cell_data_cache: &CellCache,
|
||||||
) -> Ordering {
|
) -> Ordering {
|
||||||
match fields
|
match fields
|
||||||
@ -335,18 +335,16 @@ fn cmp_row(
|
|||||||
fn cmp_cell(
|
fn cmp_cell(
|
||||||
left_cell: Option<&Cell>,
|
left_cell: Option<&Cell>,
|
||||||
right_cell: Option<&Cell>,
|
right_cell: Option<&Cell>,
|
||||||
field: &Arc<Field>,
|
field: &Field,
|
||||||
field_type: FieldType,
|
field_type: FieldType,
|
||||||
cell_data_cache: &CellCache,
|
cell_data_cache: &CellCache,
|
||||||
sort_condition: SortCondition,
|
sort_condition: SortCondition,
|
||||||
) -> Ordering {
|
) -> Ordering {
|
||||||
match TypeOptionCellExt::new(field.as_ref(), Some(cell_data_cache.clone()))
|
match TypeOptionCellExt::new(field, Some(cell_data_cache.clone()))
|
||||||
.get_type_option_cell_data_handler(&field_type)
|
.get_type_option_cell_data_handler(&field_type)
|
||||||
{
|
{
|
||||||
None => default_order(),
|
None => default_order(),
|
||||||
Some(handler) => {
|
Some(handler) => handler.handle_cell_compare(left_cell, right_cell, field, sort_condition),
|
||||||
handler.handle_cell_compare(left_cell, right_cell, field.as_ref(), sort_condition)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use collab_database::database::gen_row_id;
|
|
||||||
use collab_database::rows::RowId;
|
use collab_database::rows::RowId;
|
||||||
|
|
||||||
use lib_infra::util::timestamp;
|
use flowy_database2::entities::CreateRowPayloadPB;
|
||||||
|
|
||||||
use crate::database::database_editor::DatabaseEditorTest;
|
use crate::database::database_editor::DatabaseEditorTest;
|
||||||
|
|
||||||
@ -30,17 +29,11 @@ impl DatabaseRowTest {
|
|||||||
pub async fn run_script(&mut self, script: RowScript) {
|
pub async fn run_script(&mut self, script: RowScript) {
|
||||||
match script {
|
match script {
|
||||||
RowScript::CreateEmptyRow => {
|
RowScript::CreateEmptyRow => {
|
||||||
let params = collab_database::rows::CreateRowParams {
|
let params = CreateRowPayloadPB {
|
||||||
id: gen_row_id(),
|
view_id: self.view_id.clone(),
|
||||||
timestamp: timestamp(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let row_detail = self
|
let row_detail = self.editor.create_row(params).await.unwrap().unwrap();
|
||||||
.editor
|
|
||||||
.create_row(&self.view_id, None, params)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
self
|
self
|
||||||
.row_by_row_id
|
.row_by_row_id
|
||||||
.insert(row_detail.row.id.to_string(), row_detail.into());
|
.insert(row_detail.row.id.to_string(), row_detail.into());
|
||||||
|
@ -3,12 +3,8 @@ use std::vec;
|
|||||||
|
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use chrono::{offset, Duration};
|
use chrono::{offset, Duration};
|
||||||
use collab_database::database::gen_row_id;
|
|
||||||
use collab_database::rows::CreateRowParams;
|
|
||||||
|
|
||||||
use collab_database::views::OrderObjectPosition;
|
use flowy_database2::entities::{CreateRowPayloadPB, FieldType};
|
||||||
use flowy_database2::entities::FieldType;
|
|
||||||
use flowy_database2::services::cell::CellBuilder;
|
|
||||||
use flowy_database2::services::field::DateCellData;
|
use flowy_database2::services::field::DateCellData;
|
||||||
|
|
||||||
use crate::database::group_test::script::DatabaseGroupTest;
|
use crate::database::group_test::script::DatabaseGroupTest;
|
||||||
@ -26,19 +22,17 @@ async fn group_by_date_test() {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.timestamp()
|
.timestamp()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let mut cells = HashMap::new();
|
let mut cells = HashMap::new();
|
||||||
cells.insert(date_field.id.clone(), timestamp);
|
cells.insert(date_field.id.clone(), timestamp);
|
||||||
let cells = CellBuilder::with_cells(cells, &[date_field.clone()]).build();
|
|
||||||
|
|
||||||
let params = CreateRowParams {
|
let params = CreateRowPayloadPB {
|
||||||
id: gen_row_id(),
|
view_id: test.view_id.clone(),
|
||||||
cells,
|
data: cells,
|
||||||
height: 60,
|
..Default::default()
|
||||||
visibility: true,
|
|
||||||
row_position: OrderObjectPosition::default(),
|
|
||||||
timestamp: 0,
|
|
||||||
};
|
};
|
||||||
let res = test.editor.create_row(&test.view_id, None, params).await;
|
|
||||||
|
let res = test.editor.create_row(params).await;
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use collab_database::database::gen_row_id;
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{CreateRowParams, RowId};
|
use collab_database::rows::RowId;
|
||||||
|
|
||||||
use flowy_database2::entities::{FieldType, GroupPB, RowMetaPB};
|
use flowy_database2::entities::{CreateRowPayloadPB, FieldType, GroupPB, RowMetaPB};
|
||||||
use flowy_database2::services::cell::{
|
use flowy_database2::services::cell::{
|
||||||
delete_select_option_cell, insert_date_cell, insert_select_option_cell, insert_url_cell,
|
delete_select_option_cell, insert_date_cell, insert_select_option_cell, insert_url_cell,
|
||||||
};
|
};
|
||||||
@ -10,7 +9,6 @@ use flowy_database2::services::field::{
|
|||||||
edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction,
|
edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction,
|
||||||
SingleSelectTypeOption,
|
SingleSelectTypeOption,
|
||||||
};
|
};
|
||||||
use lib_infra::util::timestamp;
|
|
||||||
|
|
||||||
use crate::database::database_editor::DatabaseEditorTest;
|
use crate::database::database_editor::DatabaseEditorTest;
|
||||||
|
|
||||||
@ -138,16 +136,13 @@ impl DatabaseGroupTest {
|
|||||||
},
|
},
|
||||||
GroupScript::CreateRow { group_index } => {
|
GroupScript::CreateRow { group_index } => {
|
||||||
let group = self.group_at_index(group_index).await;
|
let group = self.group_at_index(group_index).await;
|
||||||
let params = CreateRowParams {
|
let params = CreateRowPayloadPB {
|
||||||
id: gen_row_id(),
|
view_id: self.view_id.clone(),
|
||||||
timestamp: timestamp(),
|
row_position: Default::default(),
|
||||||
..Default::default()
|
group_id: Some(group.group_id),
|
||||||
|
data: Default::default(),
|
||||||
};
|
};
|
||||||
let _ = self
|
let _ = self.editor.create_row(params).await.unwrap();
|
||||||
.editor
|
|
||||||
.create_row(&self.view_id, Some(group.group_id.clone()), params)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
},
|
},
|
||||||
GroupScript::DeleteRow {
|
GroupScript::DeleteRow {
|
||||||
group_index,
|
group_index,
|
||||||
|
@ -8,7 +8,7 @@ use futures::stream::StreamExt;
|
|||||||
use tokio::sync::broadcast::Receiver;
|
use tokio::sync::broadcast::Receiver;
|
||||||
|
|
||||||
use flowy_database2::entities::{
|
use flowy_database2::entities::{
|
||||||
DeleteSortPayloadPB, FieldType, ReorderSortPayloadPB, UpdateSortPayloadPB,
|
CreateRowPayloadPB, DeleteSortPayloadPB, FieldType, ReorderSortPayloadPB, UpdateSortPayloadPB,
|
||||||
};
|
};
|
||||||
use flowy_database2::services::cell::stringify_cell_data;
|
use flowy_database2::services::cell::stringify_cell_data;
|
||||||
use flowy_database2::services::database_view::DatabaseViewChanged;
|
use flowy_database2::services::database_view::DatabaseViewChanged;
|
||||||
@ -155,15 +155,10 @@ impl DatabaseSortTest {
|
|||||||
);
|
);
|
||||||
self
|
self
|
||||||
.editor
|
.editor
|
||||||
.create_row(
|
.create_row(CreateRowPayloadPB {
|
||||||
&self.view_id,
|
view_id: self.view_id.clone(),
|
||||||
None,
|
..Default::default()
|
||||||
collab_database::rows::CreateRowParams {
|
})
|
||||||
id: collab_database::database::gen_row_id(),
|
|
||||||
timestamp: collab_database::database::timestamp(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user