mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 22:57:12 -04:00
chore: auto resize row height
This commit is contained in:
parent
f3c82f5c30
commit
d4de5767a6
10 changed files with 192 additions and 94 deletions
|
@ -21,6 +21,17 @@ class RowService {
|
||||||
return GridEventCreateRow(payload).send();
|
return GridEventCreateRow(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Either<Unit, FlowyError>> moveRow(String rowId, int fromIndex, int toIndex) {
|
||||||
|
final payload = MoveItemPayload.create()
|
||||||
|
..gridId = gridId
|
||||||
|
..itemId = rowId
|
||||||
|
..ty = MoveItemType.MoveRow
|
||||||
|
..fromIndex = fromIndex
|
||||||
|
..toIndex = toIndex;
|
||||||
|
|
||||||
|
return GridEventMoveItem(payload).send();
|
||||||
|
}
|
||||||
|
|
||||||
Future<Either<Row, FlowyError>> getRow() {
|
Future<Either<Row, FlowyError>> getRow() {
|
||||||
final payload = RowIdentifierPayload.create()
|
final payload = RowIdentifierPayload.create()
|
||||||
..gridId = gridId
|
..gridId = gridId
|
||||||
|
|
|
@ -107,7 +107,7 @@ class _FlowyGridState extends State<FlowyGrid> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const _GridToolbarAdaptor(),
|
const _GridToolbarAdaptor(),
|
||||||
_gridHeader(context, state.gridId, contentWidth),
|
_gridHeader(context, state.gridId),
|
||||||
Flexible(child: child),
|
Flexible(child: child),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -147,16 +147,12 @@ class _FlowyGridState extends State<FlowyGrid> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _gridHeader(BuildContext context, String gridId, double contentWidth) {
|
Widget _gridHeader(BuildContext context, String gridId) {
|
||||||
final fieldCache = context.read<GridBloc>().fieldCache;
|
final fieldCache = context.read<GridBloc>().fieldCache;
|
||||||
|
return GridHeaderSliverAdaptor(
|
||||||
return SizedBox(
|
gridId: gridId,
|
||||||
width: contentWidth,
|
fieldCache: fieldCache,
|
||||||
child: GridHeaderSliverAdaptor(
|
anchorScrollController: headerScrollController,
|
||||||
gridId: gridId,
|
|
||||||
fieldCache: fieldCache,
|
|
||||||
anchorScrollController: headerScrollController,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,7 @@ class CellContainer extends StatelessWidget {
|
||||||
child: Consumer<CellStateNotifier>(
|
child: Consumer<CellStateNotifier>(
|
||||||
builder: (context, state, _) {
|
builder: (context, state, _) {
|
||||||
return Container(
|
return Container(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(maxWidth: width, maxHeight: 42),
|
||||||
maxWidth: width,
|
|
||||||
),
|
|
||||||
decoration: _makeBoxDecoration(context, state),
|
decoration: _makeBoxDecoration(context, state),
|
||||||
padding: GridSize.cellContentInsets,
|
padding: GridSize.cellContentInsets,
|
||||||
child: Center(child: child),
|
child: Center(child: child),
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
|
import 'package:flowy_sdk/log.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'field_type_extension.dart';
|
import 'field_type_extension.dart';
|
||||||
|
@ -28,49 +29,19 @@ class GridFieldCell extends StatelessWidget {
|
||||||
final button = FlowyButton(
|
final button = FlowyButton(
|
||||||
hoverColor: theme.shader6,
|
hoverColor: theme.shader6,
|
||||||
onTap: () => _showActionSheet(context),
|
onTap: () => _showActionSheet(context),
|
||||||
// rightIcon: svgWidget("editor/details", color: theme.iconColor),
|
|
||||||
leftIcon: svgWidget(state.field.fieldType.iconName(), color: theme.iconColor),
|
leftIcon: svgWidget(state.field.fieldType.iconName(), color: theme.iconColor),
|
||||||
text: FlowyText.medium(state.field.name, fontSize: 12),
|
text: FlowyText.medium(state.field.name, fontSize: 12),
|
||||||
padding: GridSize.cellContentInsets,
|
padding: GridSize.cellContentInsets,
|
||||||
);
|
);
|
||||||
|
|
||||||
final line = InkWell(
|
const line = Positioned(top: 0, bottom: 0, right: 0, child: _DragToExpandLine());
|
||||||
onTap: () {},
|
|
||||||
child: GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onHorizontalDragCancel: () {},
|
|
||||||
onHorizontalDragUpdate: (value) {
|
|
||||||
context.read<FieldCellBloc>().add(FieldCellEvent.updateWidth(value.delta.dx));
|
|
||||||
},
|
|
||||||
child: FlowyHover(
|
|
||||||
style: HoverStyle(
|
|
||||||
hoverColor: theme.main1,
|
|
||||||
borderRadius: BorderRadius.zero,
|
|
||||||
contentMargin: const EdgeInsets.only(left: 5),
|
|
||||||
),
|
|
||||||
builder: (_, onHover) => const SizedBox(width: 2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final borderSide = BorderSide(color: theme.shader4, width: 0.4);
|
return _CellContainer(
|
||||||
final decoration = BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
top: borderSide,
|
|
||||||
right: borderSide,
|
|
||||||
bottom: borderSide,
|
|
||||||
));
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: state.field.width.toDouble(),
|
width: state.field.width.toDouble(),
|
||||||
decoration: decoration,
|
child: Stack(
|
||||||
child: ConstrainedBox(
|
alignment: Alignment.centerRight,
|
||||||
constraints: const BoxConstraints.expand(),
|
fit: StackFit.expand,
|
||||||
child: Stack(
|
children: [button, line],
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [button, Positioned(top: 0, bottom: 0, right: 0, child: line)],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -98,3 +69,65 @@ class GridFieldCell extends StatelessWidget {
|
||||||
).show(context);
|
).show(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CellContainer extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
final double width;
|
||||||
|
const _CellContainer({
|
||||||
|
required this.child,
|
||||||
|
required this.width,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
final borderSide = BorderSide(color: theme.shader4, width: 0.4);
|
||||||
|
final decoration = BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: borderSide,
|
||||||
|
right: borderSide,
|
||||||
|
bottom: borderSide,
|
||||||
|
));
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: width,
|
||||||
|
decoration: decoration,
|
||||||
|
child: ConstrainedBox(constraints: const BoxConstraints.expand(), child: child),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DragToExpandLine extends StatelessWidget {
|
||||||
|
const _DragToExpandLine({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {},
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onHorizontalDragCancel: () {},
|
||||||
|
onHorizontalDragUpdate: (value) {
|
||||||
|
// context.read<FieldCellBloc>().add(FieldCellEvent.updateWidth(value.delta.dx));
|
||||||
|
Log.info(value);
|
||||||
|
},
|
||||||
|
onHorizontalDragEnd: (end) {
|
||||||
|
Log.info(end);
|
||||||
|
},
|
||||||
|
child: FlowyHover(
|
||||||
|
style: HoverStyle(
|
||||||
|
hoverColor: theme.main1,
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
|
contentMargin: const EdgeInsets.only(left: 5),
|
||||||
|
),
|
||||||
|
builder: (_, onHover) => const SizedBox(width: 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,15 +32,21 @@ class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) {
|
||||||
getIt<GridHeaderBloc>(param1: widget.gridId, param2: widget.fieldCache)..add(const GridHeaderEvent.initial()),
|
final bloc = getIt<GridHeaderBloc>(param1: widget.gridId, param2: widget.fieldCache);
|
||||||
|
bloc.add(const GridHeaderEvent.initial());
|
||||||
|
return bloc;
|
||||||
|
},
|
||||||
child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
|
child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
|
||||||
buildWhen: (previous, current) => previous.fields.length != current.fields.length,
|
buildWhen: (previous, current) => previous.fields.length != current.fields.length,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
controller: widget.anchorScrollController,
|
controller: widget.anchorScrollController,
|
||||||
child: SizedBox(height: GridSize.headerHeight, child: _GridHeader(gridId: widget.gridId)),
|
child: SizedBox(
|
||||||
|
height: GridSize.headerHeight,
|
||||||
|
child: _GridHeader(gridId: widget.gridId),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// return SliverPersistentHeader(
|
// return SliverPersistentHeader(
|
||||||
|
@ -54,32 +60,6 @@ class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SliverHeaderDelegateImplementation extends SliverPersistentHeaderDelegate {
|
|
||||||
final String gridId;
|
|
||||||
final List<Field> fields;
|
|
||||||
|
|
||||||
SliverHeaderDelegateImplementation({required this.gridId, required this.fields});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
|
|
||||||
return _GridHeader(gridId: gridId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get maxExtent => GridSize.headerHeight;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get minExtent => GridSize.headerHeight;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
|
|
||||||
if (oldDelegate is SliverHeaderDelegateImplementation) {
|
|
||||||
return fields.length != oldDelegate.fields.length;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GridHeader extends StatefulWidget {
|
class _GridHeader extends StatefulWidget {
|
||||||
final String gridId;
|
final String gridId;
|
||||||
const _GridHeader({Key? key, required this.gridId}) : super(key: key);
|
const _GridHeader({Key? key, required this.gridId}) : super(key: key);
|
||||||
|
@ -177,3 +157,29 @@ class CreateFieldButton extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SliverHeaderDelegateImplementation extends SliverPersistentHeaderDelegate {
|
||||||
|
final String gridId;
|
||||||
|
final List<Field> fields;
|
||||||
|
|
||||||
|
SliverHeaderDelegateImplementation({required this.gridId, required this.fields});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||||
|
return _GridHeader(gridId: gridId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get maxExtent => GridSize.headerHeight;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get minExtent => GridSize.headerHeight;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
|
||||||
|
if (oldDelegate is SliverHeaderDelegateImplementation) {
|
||||||
|
return fields.length != oldDelegate.fields.length;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -44,10 +44,11 @@ class _GridRowWidgetState extends State<GridRowWidget> {
|
||||||
child: BlocBuilder<RowBloc, RowState>(
|
child: BlocBuilder<RowBloc, RowState>(
|
||||||
buildWhen: (p, c) => p.rowData.height != c.rowData.height,
|
buildWhen: (p, c) => p.rowData.height != c.rowData.height,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SizedBox(
|
return LimitedBox(
|
||||||
height: _rowBloc.state.rowData.height,
|
maxHeight: 200,
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: const [
|
children: const [
|
||||||
_RowLeading(),
|
_RowLeading(),
|
||||||
_RowCells(),
|
_RowCells(),
|
||||||
|
@ -147,7 +148,11 @@ class _RowCells extends StatelessWidget {
|
||||||
buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap,
|
buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final List<Widget> children = state.cellDataMap.fold(() => [], _toCells);
|
final List<Widget> children = state.cellDataMap.fold(() => [], _toCells);
|
||||||
return Row(children: children);
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,16 +51,16 @@ impl ClientGridBlockMetaEditor {
|
||||||
let mut row_count = 0;
|
let mut row_count = 0;
|
||||||
let mut row_index = None;
|
let mut row_index = None;
|
||||||
let _ = self
|
let _ = self
|
||||||
.modify(|pad| {
|
.modify(|block_pad| {
|
||||||
if let Some(start_row_id) = start_row_id.as_ref() {
|
if let Some(start_row_id) = start_row_id.as_ref() {
|
||||||
match pad.index_of_row(start_row_id) {
|
match block_pad.index_of_row(start_row_id) {
|
||||||
None => {}
|
None => {}
|
||||||
Some(index) => row_index = Some(index + 1),
|
Some(index) => row_index = Some(index + 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let change = pad.add_row_meta(row, start_row_id)?;
|
let change = block_pad.add_row_meta(row, start_row_id)?;
|
||||||
row_count = pad.number_of_rows();
|
row_count = block_pad.number_of_rows();
|
||||||
Ok(change)
|
Ok(change)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -71,9 +71,9 @@ impl ClientGridBlockMetaEditor {
|
||||||
pub async fn delete_rows(&self, ids: Vec<Cow<'_, String>>) -> FlowyResult<i32> {
|
pub async fn delete_rows(&self, ids: Vec<Cow<'_, String>>) -> FlowyResult<i32> {
|
||||||
let mut row_count = 0;
|
let mut row_count = 0;
|
||||||
let _ = self
|
let _ = self
|
||||||
.modify(|pad| {
|
.modify(|block_pad| {
|
||||||
let changeset = pad.delete_rows(ids)?;
|
let changeset = block_pad.delete_rows(ids)?;
|
||||||
row_count = pad.number_of_rows();
|
row_count = block_pad.number_of_rows();
|
||||||
Ok(changeset)
|
Ok(changeset)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -81,7 +81,14 @@ impl ClientGridBlockMetaEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
|
pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
|
||||||
let _ = self.modify(|pad| Ok(pad.update_row(changeset)?)).await?;
|
let _ = self.modify(|block_pad| Ok(block_pad.update_row(changeset)?)).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn move_row(&self, row_id: &str, from: usize, to: usize) -> FlowyResult<()> {
|
||||||
|
let _ = self
|
||||||
|
.modify(|block_pad| Ok(block_pad.move_row(row_id, from, to)?))
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -154,6 +154,32 @@ impl GridBlockMetaEditorManager {
|
||||||
Ok(changesets)
|
Ok(changesets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn move_row(&self, row_id: &str, from: usize, to: usize) -> FlowyResult<()> {
|
||||||
|
let editor = self.get_editor_from_row_id(row_id).await?;
|
||||||
|
let _ = editor.move_row(row_id, from, to).await?;
|
||||||
|
|
||||||
|
match editor.get_row_metas(Some(vec![Cow::Borrowed(row_id)])).await?.pop() {
|
||||||
|
None => {}
|
||||||
|
Some(row_meta) => {
|
||||||
|
let row_order = RowOrder::from(&row_meta);
|
||||||
|
let insert_row = IndexRowOrder {
|
||||||
|
row_order: row_order.clone(),
|
||||||
|
index: Some(to as i32),
|
||||||
|
};
|
||||||
|
let notified_changeset = GridRowsChangeset {
|
||||||
|
block_id: editor.block_id.clone(),
|
||||||
|
inserted_rows: vec![insert_row],
|
||||||
|
deleted_rows: vec![row_order],
|
||||||
|
updated_rows: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = self.notify_did_update_rows(notified_changeset).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn update_cell(&self, changeset: CellChangeset) -> FlowyResult<()> {
|
pub async fn update_cell(&self, changeset: CellChangeset) -> FlowyResult<()> {
|
||||||
let row_id = changeset.row_id.clone();
|
let row_id = changeset.row_id.clone();
|
||||||
let editor = self.get_editor_from_row_id(&row_id).await?;
|
let editor = self.get_editor_from_row_id(&row_id).await?;
|
||||||
|
|
|
@ -388,7 +388,7 @@ impl ClientGridEditor {
|
||||||
self.move_field(¶ms.item_id, params.from_index, params.to_index)
|
self.move_field(¶ms.item_id, params.from_index, params.to_index)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
MoveItemType::MoveRow => self.move_row(params.from_index, params.to_index, ¶ms.item_id).await,
|
MoveItemType::MoveRow => self.move_row(¶ms.item_id, params.from_index, params.to_index).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,9 +411,12 @@ impl ClientGridEditor {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn move_row(&self, from: i32, to: i32, row_id: &str) -> FlowyResult<()> {
|
pub async fn move_row(&self, row_id: &str, from: i32, to: i32) -> FlowyResult<()> {
|
||||||
// GridRowsChangeset
|
let _ = self
|
||||||
todo!()
|
.block_meta_manager
|
||||||
|
.move_row(row_id, from as usize, to as usize)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delta_bytes(&self) -> Bytes {
|
pub async fn delta_bytes(&self) -> Bytes {
|
||||||
|
|
|
@ -149,6 +149,19 @@ impl GridBlockMetaPad {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_row(&mut self, row_id: &str, from: usize, to: usize) -> CollaborateResult<Option<GridBlockMetaChange>> {
|
||||||
|
self.modify(|row_metas| {
|
||||||
|
if let Some(position) = row_metas.iter().position(|row_meta| row_meta.id == row_id) {
|
||||||
|
debug_assert_eq!(from, position);
|
||||||
|
let row_meta = row_metas.remove(position);
|
||||||
|
row_metas.insert(to, row_meta);
|
||||||
|
Ok(Some(()))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn modify<F>(&mut self, f: F) -> CollaborateResult<Option<GridBlockMetaChange>>
|
pub fn modify<F>(&mut self, f: F) -> CollaborateResult<Option<GridBlockMetaChange>>
|
||||||
where
|
where
|
||||||
F: for<'a> FnOnce(&'a mut Vec<Arc<RowMeta>>) -> CollaborateResult<Option<()>>,
|
F: for<'a> FnOnce(&'a mut Vec<Arc<RowMeta>>) -> CollaborateResult<Option<()>>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue