feat: save text cell data

This commit is contained in:
appflowy 2022-03-20 17:17:06 +08:00
parent c77a31806d
commit 1f30a77f1d
32 changed files with 473 additions and 405 deletions

View File

@ -61,12 +61,6 @@ abstract class PluginConfig {
}
abstract class PluginDisplay<T> with NavigationItem {
@override
Widget get leftBarItem;
@override
Widget? get rightBarItem;
List<NavigationItem> get navigationItems;
PublishNotifier<T>? get notifier => null;

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:app_flowy/user/application/user_settings_service.dart';
import 'package:equatable/equatable.dart';
import 'package:flowy_infra/theme.dart';
@ -11,7 +13,7 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
AppearanceSettings setting;
AppTheme _theme;
Locale _locale;
CancelableOperation? _saveOperation;
Timer? _saveOperation;
AppearanceSettingModel(this.setting)
: _theme = AppTheme.fromName(name: setting.theme),
@ -21,12 +23,10 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
Locale get locale => _locale;
Future<void> save() async {
_saveOperation?.cancel;
_saveOperation = CancelableOperation.fromFuture(
Future.delayed(const Duration(seconds: 1), () async {
await UserSettingsService().setAppearanceSettings(setting);
}),
);
_saveOperation?.cancel();
_saveOperation = Timer(const Duration(seconds: 2), () async {
await UserSettingsService().setAppearanceSettings(setting);
});
}
@override

View File

@ -17,6 +17,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
initial: (_InitialCell value) async {},
updateText: (_UpdateText value) {
service.updateCell(data: value.text);
emit(state.copyWith(content: value.text));
},
);
},

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
@ -32,7 +33,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
delete: (_Delete value) {},
rename: (_Rename value) {},
updateDesc: (_Desc value) {},
didLoadRows: (_DidLoadRows value) {
rowsDidUpdate: (_RowsDidUpdate value) {
emit(state.copyWith(rows: value.rows));
},
);
@ -47,10 +48,19 @@ class GridBloc extends Bloc<GridEvent, GridState> {
return super.close();
}
Future<void> _startGridListening() async {
_blockService.didLoadRowscallback = (rows) {
add(GridEvent.didLoadRows(rows));
};
Future<void> _initGridBlockService(Grid grid, List<Field> fields) async {
_blockService = GridBlockService(
gridId: grid.id,
fields: fields,
blockOrders: grid.blockOrders,
);
_blockService.rowsUpdateNotifier.addPublishListener((result) {
result.fold(
(rows) => add(GridEvent.rowsDidUpdate(rows)),
(err) => Log.error('$err'),
);
});
_gridListener.start();
}
@ -70,36 +80,18 @@ class GridBloc extends Bloc<GridEvent, GridState> {
final result = await service.getFields(gridId: grid.id, fieldOrders: grid.fieldOrders);
return Future(
() => result.fold(
(fields) => _loadGridBlocks(grid, fields.items, emit),
(fields) {
_initGridBlockService(grid, fields.items);
emit(state.copyWith(
grid: Some(grid),
fields: Some(fields.items),
loadingState: GridLoadingState.finish(left(unit)),
));
},
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
),
);
}
Future<void> _loadGridBlocks(Grid grid, List<Field> fields, Emitter<GridState> emit) async {
final result = await service.getGridBlocks(gridId: grid.id, blockOrders: grid.blockOrders);
result.fold(
(repeatedGridBlock) {
final gridBlocks = repeatedGridBlock.items;
final gridId = view.id;
_blockService = GridBlockService(
gridId: gridId,
fields: fields,
gridBlocks: gridBlocks,
);
final rows = _blockService.rows();
_startGridListening();
emit(state.copyWith(
grid: Some(grid),
fields: Some(fields),
rows: rows,
loadingState: GridLoadingState.finish(left(unit)),
));
},
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)), rows: [])),
);
}
}
@freezed
@ -109,7 +101,7 @@ abstract class GridEvent with _$GridEvent {
const factory GridEvent.updateDesc(String gridId, String desc) = _Desc;
const factory GridEvent.delete(String gridId) = _Delete;
const factory GridEvent.createRow() = _CreateRow;
const factory GridEvent.didLoadRows(List<GridRowData> rows) = _DidLoadRows;
const factory GridEvent.rowsDidUpdate(List<GridRowData> rows) = _RowsDidUpdate;
}
@freezed

View File

@ -1,5 +1,7 @@
import 'dart:collection';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/dart-notify/subject.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
@ -9,40 +11,38 @@ import 'package:flowy_infra/notifier.dart';
import 'dart:async';
import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart';
import 'grid_service.dart';
typedef DidLoadRowsCallback = void Function(List<GridRowData>);
typedef GridBlockUpdateNotifiedValue = Either<GridBlockId, FlowyError>;
typedef RowsUpdateNotifierValue = Either<List<GridRowData>, FlowyError>;
class GridBlockService {
String gridId;
List<Field> fields;
LinkedHashMap<String, GridBlock> blockMap = LinkedHashMap();
late GridBlockListener _blockListener;
DidLoadRowsCallback? didLoadRowscallback;
PublishNotifier<RowsUpdateNotifierValue> rowsUpdateNotifier = PublishNotifier<RowsUpdateNotifierValue>();
GridBlockService({required this.gridId, required this.fields, required List<GridBlock> gridBlocks}) {
for (final gridBlock in gridBlocks) {
blockMap[gridBlock.blockId] = gridBlock;
}
GridBlockService({required this.gridId, required this.fields, required List<GridBlockOrder> blockOrders}) {
_loadGridBlocks(blockOrders: blockOrders);
_blockListener = GridBlockListener(gridId: gridId);
_blockListener.blockUpdateNotifier.addPublishListener((result) {
result.fold((blockId) {
//
}, (err) => null);
_blockListener.rowsUpdateNotifier.addPublishListener((result) {
result.fold(
(blockId) => _loadGridBlocks(blockOrders: [GridBlockOrder.create()..blockId = blockId.value]),
(err) => Log.error(err),
);
});
_blockListener.start();
}
List<GridRowData> rows() {
List<GridRowData> buildRows() {
List<GridRowData> rows = [];
blockMap.forEach((_, GridBlock gridBlock) {
rows.addAll(gridBlock.rowOrders.map(
(rowOrder) => GridRowData(
gridId: gridId,
fields: fields,
blockId: gridBlock.blockId,
blockId: gridBlock.id,
rowId: rowOrder.rowId,
height: rowOrder.height.toDouble(),
),
@ -54,11 +54,29 @@ class GridBlockService {
Future<void> stop() async {
await _blockListener.stop();
}
void _loadGridBlocks({required List<GridBlockOrder> blockOrders}) {
final payload = QueryGridBlocksPayload.create()
..gridId = gridId
..blockOrders.addAll(blockOrders);
GridEventGetGridBlocks(payload).send().then((result) {
result.fold(
(repeatedBlocks) {
for (final gridBlock in repeatedBlocks.items) {
blockMap[gridBlock.id] = gridBlock;
}
rowsUpdateNotifier.value = left(buildRows());
},
(err) => rowsUpdateNotifier.value = right(err),
);
});
}
}
class GridBlockListener {
final String gridId;
PublishNotifier<GridBlockUpdateNotifiedValue> blockUpdateNotifier = PublishNotifier<GridBlockUpdateNotifiedValue>();
PublishNotifier<Either<GridBlockId, FlowyError>> rowsUpdateNotifier = PublishNotifier(comparable: null);
StreamSubscription<SubscribeObject>? _subscription;
late GridNotificationParser _parser;
@ -77,10 +95,10 @@ class GridBlockListener {
void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.GridDidUpdateBlock:
case GridNotification.BlockDidUpdateRow:
result.fold(
(payload) => blockUpdateNotifier.value = left(GridBlockId.fromBuffer(payload)),
(error) => blockUpdateNotifier.value = right(error),
(payload) => rowsUpdateNotifier.value = left(GridBlockId.fromBuffer(payload)),
(error) => rowsUpdateNotifier.value = right(error),
);
break;
@ -91,6 +109,6 @@ class GridBlockListener {
Future<void> stop() async {
await _subscription?.cancel();
blockUpdateNotifier.dispose();
rowsUpdateNotifier.dispose();
}
}

View File

@ -13,9 +13,9 @@ class GridService {
return GridEventGetGridData(payload).send();
}
Future<Either<Row, FlowyError>> createRow({required String gridId, Option<String>? upperRowId}) {
Future<Either<Row, FlowyError>> createRow({required String gridId, Option<String>? startRowId}) {
CreateRowPayload payload = CreateRowPayload.create()..gridId = gridId;
upperRowId?.fold(() => null, (id) => payload.startRowId = id);
startRowId?.fold(() => null, (id) => payload.startRowId = id);
return GridEventCreateRow(payload).send();
}

View File

@ -19,7 +19,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
await event.map(
initial: (_InitialRow value) async {
_startRowListening();
await _loadCellDatas(emit);
await _loadRow(emit);
},
createRow: (_CreateRow value) {
rowService.createRow();
@ -58,20 +58,20 @@ class RowBloc extends Bloc<RowEvent, RowState> {
listener.start();
}
Future<void> _loadCellDatas(Emitter<RowState> emit) async {
final result = await rowService.getRow();
result.fold(
(row) {
emit(state.copyWith(
cellDatas: makeGridCellDatas(row),
rowHeight: row.height.toDouble(),
));
},
(e) => Log.error(e),
);
Future<void> _loadRow(Emitter<RowState> emit) async {
final Future<List<GridCellData>> cellDatas = rowService.getRow().then((result) {
return result.fold(
(row) => _makeCellDatas(row),
(e) {
Log.error(e);
return [];
},
);
});
emit(state.copyWith(cellDatas: cellDatas));
}
List<GridCellData> makeGridCellDatas(Row row) {
List<GridCellData> _makeCellDatas(Row row) {
return rowService.rowData.fields.map((field) {
final cell = row.cellByFieldId[field.id];
final rowData = rowService.rowData;
@ -96,18 +96,18 @@ abstract class RowEvent with _$RowEvent {
}
@freezed
abstract class RowState with _$RowState {
class RowState with _$RowState {
const factory RowState({
required String rowId,
required double rowHeight,
required List<GridCellData> cellDatas,
required Future<List<GridCellData>> cellDatas,
required bool active,
}) = _RowState;
factory RowState.initial(GridRowData data) => RowState(
rowId: data.rowId,
active: false,
rowHeight: data.height,
cellDatas: [],
cellDatas: Future(() => []),
active: false,
);
}

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/view/view_ext.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
@ -64,7 +66,7 @@ class ViewSectionNotifier with ChangeNotifier {
bool isDisposed = false;
List<View> _views;
View? _selectedView;
CancelableOperation? _notifyListenerOperation;
Timer? _notifyListenerOperation;
ViewSectionNotifier({
required BuildContext context,
@ -120,9 +122,7 @@ class ViewSectionNotifier with ChangeNotifier {
void _notifyListeners() {
_notifyListenerOperation?.cancel();
_notifyListenerOperation = CancelableOperation.fromFuture(
Future.delayed(const Duration(milliseconds: 30), () {}),
).then((_) {
_notifyListenerOperation = Timer(const Duration(milliseconds: 30), () {
if (!isDisposed) {
notifyListeners();
}

View File

@ -40,7 +40,7 @@ class BlankPagePlugin extends Plugin {
PluginType get ty => _pluginType;
}
class BlankPagePluginDisplay extends PluginDisplay {
class BlankPagePluginDisplay extends PluginDisplay with NavigationItem {
@override
Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr(), fontSize: 12);

View File

@ -10,14 +10,13 @@ import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/workspace/application/doc/share_bloc.dart';
import 'package:app_flowy/workspace/application/view/view_listener.dart';
import 'package:app_flowy/workspace/application/view/view_service.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:app_flowy/workspace/presentation/plugins/widgets/left_bar_item.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flowy_sdk/log.dart';
@ -89,7 +88,7 @@ class DocumentPlugin implements Plugin {
PluginId get id => _view.id;
}
class DocumentPluginDisplay extends PluginDisplay<int> {
class DocumentPluginDisplay extends PluginDisplay<int> with NavigationItem {
final PublishNotifier<int> _displayNotifier = PublishNotifier<int>();
final View _view;
@ -99,91 +98,16 @@ class DocumentPluginDisplay extends PluginDisplay<int> {
Widget buildWidget() => DocumentPage(view: _view, key: ValueKey(_view.id));
@override
Widget get leftBarItem => DocumentLeftBarItem(view: _view);
Widget get leftBarItem => ViewLeftBarItem(view: _view);
@override
Widget? get rightBarItem => DocumentShareButton(view: _view);
@override
List<NavigationItem> get navigationItems => _makeNavigationItems();
List<NavigationItem> get navigationItems => [this];
@override
PublishNotifier<int>? get notifier => _displayNotifier;
List<NavigationItem> _makeNavigationItems() {
return [
this,
];
}
}
class DocumentLeftBarItem extends StatefulWidget {
final View view;
DocumentLeftBarItem({required this.view, Key? key}) : super(key: ValueKey(view.hashCode));
@override
State<DocumentLeftBarItem> createState() => _DocumentLeftBarItemState();
}
class _DocumentLeftBarItemState extends State<DocumentLeftBarItem> {
final _controller = TextEditingController();
final _focusNode = FocusNode();
late ViewService service;
@override
void initState() {
service = ViewService(/*view: widget.view*/);
_focusNode.addListener(_handleFocusChanged);
super.initState();
}
@override
void dispose() {
_controller.dispose();
_focusNode.removeListener(_handleFocusChanged);
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_controller.text = widget.view.name;
final theme = context.watch<AppTheme>();
return IntrinsicWidth(
key: ValueKey(_controller.text),
child: TextField(
controller: _controller,
focusNode: _focusNode,
scrollPadding: EdgeInsets.zero,
decoration: const InputDecoration(
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
isDense: true,
),
style: TextStyle(
color: theme.textColor,
fontSize: 14,
fontWeight: FontWeight.w500,
overflow: TextOverflow.ellipsis,
),
// cursorColor: widget.cursorColor,
// obscureText: widget.enableObscure,
),
);
}
void _handleFocusChanged() {
if (_controller.text.isEmpty) {
_controller.text = widget.view.name;
return;
}
if (_controller.text != widget.view.name) {
service.updateView(viewId: widget.view.id, name: _controller.text);
}
}
}
class DocumentShareButton extends StatelessWidget {

View File

@ -1,5 +1,5 @@
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:app_flowy/workspace/presentation/plugins/widgets/left_bar_item.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:app_flowy/plugin/plugin.dart';
import 'package:flutter/material.dart';
@ -28,7 +28,7 @@ class GridPluginBuilder implements PluginBuilder {
class GridPluginConfig implements PluginConfig {
@override
bool get creatable => false;
bool get creatable => true;
}
class GridPlugin extends Plugin {
@ -56,7 +56,7 @@ class GridPluginDisplay extends PluginDisplay {
GridPluginDisplay({required View view, Key? key}) : _view = view;
@override
Widget get leftBarItem => const FlowyText.medium("Grid demo", fontSize: 12);
Widget get leftBarItem => ViewLeftBarItem(view: _view);
@override
Widget buildWidget() => GridPage(view: _view);

View File

@ -84,42 +84,38 @@ class _FlowyGridState extends State<FlowyGrid> {
@override
Widget build(BuildContext context) {
return BlocBuilder<GridBloc, GridState>(
buildWhen: (previous, current) => previous.fields != current.fields,
builder: (context, state) {
return state.fields.fold(
() => const Center(child: CircularProgressIndicator.adaptive()),
(fields) => _renderGrid(context, fields),
(fields) => _wrapScrollbar(fields, [
_buildHeader(fields),
_buildRows(context),
const GridFooter(),
]),
);
},
);
}
Widget _renderGrid(BuildContext context, List<Field> fields) {
return Stack(
children: [
StyledSingleChildScrollView(
controller: _scrollController.horizontalController,
axis: Axis.horizontal,
child: SizedBox(
width: GridLayout.headerWidth(fields),
child: CustomScrollView(
physics: StyledScrollPhysics(),
controller: _scrollController.verticalController,
slivers: <Widget>[
_buildHeader(fields),
_buildRows(context),
const GridFooter(),
],
),
Widget _wrapScrollbar(List<Field> fields, List<Widget> children) {
return ScrollbarListStack(
axis: Axis.vertical,
controller: _scrollController.verticalController,
barSize: GridSize.scrollBarSize,
child: StyledSingleChildScrollView(
controller: _scrollController.horizontalController,
axis: Axis.horizontal,
child: SizedBox(
width: GridLayout.headerWidth(fields),
child: CustomScrollView(
physics: StyledScrollPhysics(),
controller: _scrollController.verticalController,
slivers: <Widget>[...children],
),
),
ScrollbarListStack(
axis: Axis.vertical,
controller: _scrollController.verticalController,
barSize: GridSize.scrollBarSize,
child: Container(),
).padding(right: 0, top: GridSize.headerHeight, bottom: GridSize.scrollBarSize),
],
);
),
).padding(right: 0, top: GridSize.headerHeight, bottom: GridSize.scrollBarSize);
}
Widget _buildHeader(List<Field> fields) {
@ -131,15 +127,21 @@ class _FlowyGridState extends State<FlowyGrid> {
}
Widget _buildRows(BuildContext context) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final rowData = context.read<GridBloc>().state.rows[index];
return GridRowWidget(data: rowData);
},
childCount: context.read<GridBloc>().state.rows.length,
addRepaintBoundaries: true,
),
return BlocBuilder<GridBloc, GridState>(
buildWhen: (previous, current) => previous.rows.length != current.rows.length,
builder: (context, state) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final rowData = context.read<GridBloc>().state.rows[index];
return GridRowWidget(data: rowData);
},
childCount: context.read<GridBloc>().state.rows.length,
addRepaintBoundaries: true,
addAutomaticKeepAlives: true,
),
);
},
);
}
}

View File

@ -11,7 +11,7 @@ import 'cell_container.dart';
class GridRowWidget extends StatefulWidget {
final GridRowData data;
GridRowWidget({required this.data, Key? key}) : super(key: ObjectKey(data.rowId));
GridRowWidget({required this.data, Key? key}) : super(key: ValueKey(data.rowId));
@override
State<GridRowWidget> createState() => _GridRowWidgetState();
@ -30,28 +30,25 @@ class _GridRowWidgetState extends State<GridRowWidget> {
Widget build(BuildContext context) {
return BlocProvider.value(
value: _rowBloc,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
child: MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (p) => _rowBloc.add(const RowEvent.activeRow()),
onExit: (p) => _rowBloc.add(const RowEvent.disactiveRow()),
child: BlocBuilder<RowBloc, RowState>(
buildWhen: (p, c) => p.rowHeight != c.rowHeight,
builder: (context, state) {
return SizedBox(
height: _rowBloc.state.rowHeight,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const LeadingRow(),
_buildCells(),
const TrailingRow(),
],
),
);
},
),
child: MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (p) => _rowBloc.add(const RowEvent.activeRow()),
onExit: (p) => _rowBloc.add(const RowEvent.disactiveRow()),
child: BlocBuilder<RowBloc, RowState>(
buildWhen: (p, c) => p.rowHeight != c.rowHeight,
builder: (context, state) {
return SizedBox(
height: _rowBloc.state.rowHeight,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
_RowLeading(),
_RowCells(),
_RowTrailing(),
],
),
);
},
),
),
);
@ -62,69 +59,37 @@ class _GridRowWidgetState extends State<GridRowWidget> {
_rowBloc.close();
super.dispose();
}
Widget _buildCells() {
return BlocBuilder<RowBloc, RowState>(
buildWhen: (p, c) => p.cellDatas != c.cellDatas,
builder: (context, state) {
return Row(
children: state.cellDatas
.map(
(cellData) => CellContainer(
width: cellData.field.width.toDouble(),
child: buildGridCell(cellData),
),
)
.toList(),
);
},
);
}
}
class LeadingRow extends StatelessWidget {
const LeadingRow({Key? key}) : super(key: key);
class _RowLeading extends StatelessWidget {
const _RowLeading({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocSelector<RowBloc, RowState, bool>(
selector: (state) => state.active,
builder: (context, isActive) {
return SizedBox(
width: GridSize.leadingHeaderPadding,
child: isActive
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
AppendRowButton(),
],
)
: null,
);
return SizedBox(width: GridSize.leadingHeaderPadding, child: isActive ? _activeWidget() : null);
},
);
}
Widget _activeWidget() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
AppendRowButton(),
],
);
}
}
class TrailingRow extends StatelessWidget {
const TrailingRow({Key? key}) : super(key: key);
class _RowTrailing extends StatelessWidget {
const _RowTrailing({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final borderSide = BorderSide(color: theme.shader4, width: 0.4);
return BlocBuilder<RowBloc, RowState>(
builder: (context, state) {
return Container(
width: GridSize.trailHeaderPadding,
decoration: BoxDecoration(
border: Border(bottom: borderSide),
),
padding: GridSize.cellContentInsets,
);
},
);
return const SizedBox();
}
}
@ -143,3 +108,37 @@ class AppendRowButton extends StatelessWidget {
);
}
}
class _RowCells extends StatelessWidget {
const _RowCells({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocBuilder<RowBloc, RowState>(
buildWhen: (previous, current) => previous.cellDatas != current.cellDatas,
builder: (context, state) {
return FutureBuilder(
future: state.cellDatas,
builder: builder,
);
},
);
}
Widget builder(context, AsyncSnapshot<dynamic> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
List<GridCellData> cellDatas = snapshot.data;
return Row(children: cellDatas.map(_toCell).toList());
default:
return const SizedBox();
}
}
Widget _toCell(GridCellData data) {
return CellContainer(
width: data.field.width.toDouble(),
child: buildGridCell(data),
);
}
}

View File

@ -1,6 +1,9 @@
import 'dart:async';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/cell_bloc/text_cell_bloc.dart';
import 'package:app_flowy/workspace/application/grid/row_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -19,27 +22,40 @@ class GridTextCell extends StatefulWidget {
class _GridTextCellState extends State<GridTextCell> {
late TextEditingController _controller;
Timer? _delayOperation;
final _focusNode = FocusNode();
late TextCellBloc _cellBloc;
TextCellBloc? _cellBloc;
@override
void initState() {
_cellBloc = getIt<TextCellBloc>(param1: widget.cellData);
_controller = TextEditingController(text: _cellBloc.state.content);
_focusNode.addListener(_focusChanged);
_controller = TextEditingController(text: _cellBloc!.state.content);
_focusNode.addListener(save);
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _cellBloc,
value: _cellBloc!,
child: BlocBuilder<TextCellBloc, TextCellState>(
buildWhen: (previous, current) {
return _controller.text != current.content;
},
builder: (context, state) {
return TextField(
controller: _controller,
focusNode: _focusNode,
onChanged: (value) {},
onChanged: (value) {
Log.info("On change");
save();
},
onEditingComplete: () {
Log.info("On complete");
},
onSubmitted: (value) {
Log.info("On submit");
},
maxLines: 1,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
decoration: const InputDecoration(
@ -55,13 +71,18 @@ class _GridTextCellState extends State<GridTextCell> {
@override
Future<void> dispose() async {
_cellBloc.close();
_focusNode.removeListener(_focusChanged);
_cellBloc?.close();
_cellBloc = null;
_focusNode.removeListener(save);
_focusNode.dispose();
super.dispose();
}
void _focusChanged() {
_cellBloc.add(TextCellEvent.updateText(_controller.text));
Future<void> save() async {
_delayOperation?.cancel();
_delayOperation = Timer(const Duration(seconds: 2), () {
_cellBloc?.add(TextCellEvent.updateText(_controller.text));
});
// and later, before the timer goes off...
}
}

View File

@ -42,6 +42,7 @@ class GridHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocProvider(
create: (context) => getIt<ColumnBloc>(param1: fields)..add(const ColumnEvent.initial()),
child: BlocBuilder<ColumnBloc, ColumnState>(
@ -55,13 +56,16 @@ class GridHeader extends StatelessWidget {
)
.toList();
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const LeadingHeaderCell(),
...headers,
const TrailingHeaderCell(),
],
return Container(
color: theme.surface,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const LeadingHeaderCell(),
...headers,
const TrailingHeaderCell(),
],
),
);
},
),

View File

@ -0,0 +1,74 @@
import 'package:app_flowy/workspace/application/view/view_service.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ViewLeftBarItem extends StatefulWidget {
final View view;
ViewLeftBarItem({required this.view, Key? key}) : super(key: ValueKey(view.hashCode));
@override
State<ViewLeftBarItem> createState() => _ViewLeftBarItemState();
}
class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
final _controller = TextEditingController();
final _focusNode = FocusNode();
late ViewService serviceService;
@override
void initState() {
serviceService = ViewService(/*view: widget.view*/);
_focusNode.addListener(_handleFocusChanged);
super.initState();
}
@override
void dispose() {
_controller.dispose();
_focusNode.removeListener(_handleFocusChanged);
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_controller.text = widget.view.name;
final theme = context.watch<AppTheme>();
return IntrinsicWidth(
key: ValueKey(_controller.text),
child: TextField(
controller: _controller,
focusNode: _focusNode,
scrollPadding: EdgeInsets.zero,
decoration: const InputDecoration(
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
isDense: true,
),
style: TextStyle(
color: theme.textColor,
fontSize: 14,
fontWeight: FontWeight.w500,
overflow: TextOverflow.ellipsis,
),
// cursorColor: widget.cursorColor,
// obscureText: widget.enableObscure,
),
);
}
void _handleFocusChanged() {
if (_controller.text.isEmpty) {
_controller.text = widget.view.name;
return;
}
if (_controller.text != widget.view.name) {
serviceService.updateView(viewId: widget.view.id, name: _controller.text);
}
}
}

View File

@ -1,10 +1,29 @@
import 'package:flutter/material.dart';
abstract class Comparable<T> {
bool compare(T? previous, T? current);
}
class ObjectComparable<T> extends Comparable<T> {
@override
bool compare(T? previous, T? current) {
return previous == current;
}
}
class PublishNotifier<T> extends ChangeNotifier {
T? _value;
Comparable<T>? comparable = ObjectComparable();
PublishNotifier({this.comparable});
set value(T newValue) {
if (_value != newValue) {
if (comparable != null) {
if (comparable!.compare(_value, newValue)) {
_value = newValue;
notifyListeners();
}
} else {
_value = newValue;
notifyListeners();
}

View File

@ -609,19 +609,19 @@ class GridBlockOrder extends $pb.GeneratedMessage {
class GridBlock extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridBlock', createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
..pc<RowOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'rowOrders', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
..hasRequiredFields = false
;
GridBlock._() : super();
factory GridBlock({
$core.String? blockId,
$core.String? id,
$core.Iterable<RowOrder>? rowOrders,
}) {
final _result = create();
if (blockId != null) {
_result.blockId = blockId;
if (id != null) {
_result.id = id;
}
if (rowOrders != null) {
_result.rowOrders.addAll(rowOrders);
@ -650,13 +650,13 @@ class GridBlock extends $pb.GeneratedMessage {
static GridBlock? _defaultInstance;
@$pb.TagNumber(1)
$core.String get blockId => $_getSZ(0);
$core.String get id => $_getSZ(0);
@$pb.TagNumber(1)
set blockId($core.String v) { $_setString(0, v); }
set id($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasBlockId() => $_has(0);
$core.bool hasId() => $_has(0);
@$pb.TagNumber(1)
void clearBlockId() => clearField(1);
void clearId() => clearField(1);
@$pb.TagNumber(2)
$core.List<RowOrder> get rowOrders => $_getList(1);

View File

@ -135,13 +135,13 @@ final $typed_data.Uint8List gridBlockOrderDescriptor = $convert.base64Decode('Cg
const GridBlock$json = const {
'1': 'GridBlock',
'2': const [
const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
const {'1': 'row_orders', '3': 2, '4': 3, '5': 11, '6': '.RowOrder', '10': 'rowOrders'},
],
};
/// Descriptor for `GridBlock`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List gridBlockDescriptor = $convert.base64Decode('CglHcmlkQmxvY2sSGQoIYmxvY2tfaWQYASABKAlSB2Jsb2NrSWQSKAoKcm93X29yZGVycxgCIAMoCzIJLlJvd09yZGVyUglyb3dPcmRlcnM=');
final $typed_data.Uint8List gridBlockDescriptor = $convert.base64Decode('CglHcmlkQmxvY2sSDgoCaWQYASABKAlSAmlkEigKCnJvd19vcmRlcnMYAiADKAsyCS5Sb3dPcmRlclIJcm93T3JkZXJz');
@$core.Deprecated('Use cellDescriptor instead')
const Cell$json = const {
'1': 'Cell',

View File

@ -11,15 +11,15 @@ import 'package:protobuf/protobuf.dart' as $pb;
class GridNotification extends $pb.ProtobufEnum {
static const GridNotification Unknown = GridNotification._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
static const GridNotification GridDidUpdateBlock = GridNotification._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateBlock');
static const GridNotification GridDidCreateBlock = GridNotification._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidCreateBlock');
static const GridNotification GridDidUpdateCells = GridNotification._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateCells');
static const GridNotification GridDidUpdateFields = GridNotification._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateFields');
static const GridNotification BlockDidUpdateRow = GridNotification._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'BlockDidUpdateRow');
static const GridNotification GridDidUpdateCells = GridNotification._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateCells');
static const GridNotification GridDidUpdateFields = GridNotification._(40, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GridDidUpdateFields');
static const $core.List<GridNotification> values = <GridNotification> [
Unknown,
GridDidUpdateBlock,
GridDidCreateBlock,
BlockDidUpdateRow,
GridDidUpdateCells,
GridDidUpdateFields,
];

View File

@ -13,12 +13,12 @@ const GridNotification$json = const {
'1': 'GridNotification',
'2': const [
const {'1': 'Unknown', '2': 0},
const {'1': 'GridDidUpdateBlock', '2': 10},
const {'1': 'GridDidCreateBlock', '2': 11},
const {'1': 'GridDidUpdateCells', '2': 20},
const {'1': 'GridDidUpdateFields', '2': 30},
const {'1': 'BlockDidUpdateRow', '2': 20},
const {'1': 'GridDidUpdateCells', '2': 30},
const {'1': 'GridDidUpdateFields', '2': 40},
],
};
/// Descriptor for `GridNotification`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List gridNotificationDescriptor = $convert.base64Decode('ChBHcmlkTm90aWZpY2F0aW9uEgsKB1Vua25vd24QABIWChJHcmlkRGlkVXBkYXRlQmxvY2sQChIWChJHcmlkRGlkQ3JlYXRlQmxvY2sQCxIWChJHcmlkRGlkVXBkYXRlQ2VsbHMQFBIXChNHcmlkRGlkVXBkYXRlRmllbGRzEB4=');
final $typed_data.Uint8List gridNotificationDescriptor = $convert.base64Decode('ChBHcmlkTm90aWZpY2F0aW9uEgsKB1Vua25vd24QABIWChJHcmlkRGlkQ3JlYXRlQmxvY2sQCxIVChFCbG9ja0RpZFVwZGF0ZVJvdxAUEhYKEkdyaWREaWRVcGRhdGVDZWxscxAeEhcKE0dyaWREaWRVcGRhdGVGaWVsZHMQKA==');

View File

@ -5,11 +5,13 @@ const OBSERVABLE_CATEGORY: &str = "Grid";
#[derive(ProtoBuf_Enum, Debug)]
pub enum GridNotification {
Unknown = 0,
GridDidUpdateBlock = 10,
GridDidCreateBlock = 11,
GridDidUpdateCells = 20,
GridDidUpdateFields = 30,
BlockDidUpdateRow = 20,
GridDidUpdateCells = 30,
GridDidUpdateFields = 40,
}
impl std::default::Default for GridNotification {

View File

@ -83,7 +83,7 @@ impl GridManager {
Ok(())
}
#[tracing::instrument(level = "debug", skip(self), err)]
// #[tracing::instrument(level = "debug", skip(self), err)]
pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult<Arc<ClientGridEditor>> {
match self.editor_map.get(grid_id) {
None => Err(FlowyError::internal().context("Should call open_grid function first")),

View File

@ -26,10 +26,10 @@
#[derive(Clone,PartialEq,Eq,Debug,Hash)]
pub enum GridNotification {
Unknown = 0,
GridDidUpdateBlock = 10,
GridDidCreateBlock = 11,
GridDidUpdateCells = 20,
GridDidUpdateFields = 30,
BlockDidUpdateRow = 20,
GridDidUpdateCells = 30,
GridDidUpdateFields = 40,
}
impl ::protobuf::ProtobufEnum for GridNotification {
@ -40,10 +40,10 @@ impl ::protobuf::ProtobufEnum for GridNotification {
fn from_i32(value: i32) -> ::std::option::Option<GridNotification> {
match value {
0 => ::std::option::Option::Some(GridNotification::Unknown),
10 => ::std::option::Option::Some(GridNotification::GridDidUpdateBlock),
11 => ::std::option::Option::Some(GridNotification::GridDidCreateBlock),
20 => ::std::option::Option::Some(GridNotification::GridDidUpdateCells),
30 => ::std::option::Option::Some(GridNotification::GridDidUpdateFields),
20 => ::std::option::Option::Some(GridNotification::BlockDidUpdateRow),
30 => ::std::option::Option::Some(GridNotification::GridDidUpdateCells),
40 => ::std::option::Option::Some(GridNotification::GridDidUpdateFields),
_ => ::std::option::Option::None
}
}
@ -51,8 +51,8 @@ impl ::protobuf::ProtobufEnum for GridNotification {
fn values() -> &'static [Self] {
static values: &'static [GridNotification] = &[
GridNotification::Unknown,
GridNotification::GridDidUpdateBlock,
GridNotification::GridDidCreateBlock,
GridNotification::BlockDidUpdateRow,
GridNotification::GridDidUpdateCells,
GridNotification::GridDidUpdateFields,
];
@ -83,10 +83,10 @@ impl ::protobuf::reflect::ProtobufValue for GridNotification {
}
static file_descriptor_proto_data: &'static [u8] = b"\
\n\x17dart_notification.proto*\x80\x01\n\x10GridNotification\x12\x0b\n\
\x07Unknown\x10\0\x12\x16\n\x12GridDidUpdateBlock\x10\n\x12\x16\n\x12Gri\
dDidCreateBlock\x10\x0b\x12\x16\n\x12GridDidUpdateCells\x10\x14\x12\x17\
\n\x13GridDidUpdateFields\x10\x1eb\x06proto3\
\n\x17dart_notification.proto*\x7f\n\x10GridNotification\x12\x0b\n\x07Un\
known\x10\0\x12\x16\n\x12GridDidCreateBlock\x10\x0b\x12\x15\n\x11BlockDi\
dUpdateRow\x10\x14\x12\x16\n\x12GridDidUpdateCells\x10\x1e\x12\x17\n\x13\
GridDidUpdateFields\x10(b\x06proto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -2,8 +2,8 @@ syntax = "proto3";
enum GridNotification {
Unknown = 0;
GridDidUpdateBlock = 10;
GridDidCreateBlock = 11;
GridDidUpdateCells = 20;
GridDidUpdateFields = 30;
BlockDidUpdateRow = 20;
GridDidUpdateCells = 30;
GridDidUpdateFields = 40;
}

View File

@ -46,7 +46,9 @@ impl GridBlockMetaEditorManager {
Ok(manager)
}
// #[tracing::instrument(level = "trace", skip(self))]
pub(crate) async fn get_editor(&self, block_id: &str) -> FlowyResult<Arc<ClientGridBlockMetaEditor>> {
debug_assert!(!block_id.is_empty());
match self.editor_map.get(block_id) {
None => {
tracing::error!("The is a fatal error, block is not exist");
@ -68,7 +70,7 @@ impl GridBlockMetaEditorManager {
.insert(row_meta.id.clone(), row_meta.block_id.clone());
let editor = self.get_editor(&row_meta.block_id).await?;
let row_count = editor.create_row(row_meta, start_row_id).await?;
self.notify_did_update_block(block_id).await?;
self.notify_block_did_update_row(&block_id).await?;
Ok(row_count)
}
@ -85,7 +87,7 @@ impl GridBlockMetaEditorManager {
row_count = editor.create_row(row.clone(), None).await?;
}
changesets.push(GridBlockMetaChangeset::from_row_count(&block_id, row_count));
let _ = self.notify_did_update_block(&block_id).await?;
let _ = self.notify_block_did_update_row(&block_id).await?;
}
Ok(changesets)
@ -108,7 +110,7 @@ impl GridBlockMetaEditorManager {
pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
let editor = self.get_editor_from_row_id(&changeset.row_id).await?;
let _ = editor.update_row(changeset.clone()).await?;
let _ = self.notify_did_update_block(&editor.block_id).await?;
let _ = self.notify_block_did_update_row(&editor.block_id).await?;
Ok(())
}
@ -183,11 +185,9 @@ impl GridBlockMetaEditorManager {
}
}
async fn notify_did_update_block(&self, block_id: &str) -> FlowyResult<()> {
let block_id = GridBlockId {
value: block_id.to_owned(),
};
send_dart_notification(&self.grid_id, GridNotification::GridDidUpdateBlock)
async fn notify_block_did_update_row(&self, block_id: &str) -> FlowyResult<()> {
let block_id: GridBlockId = block_id.into();
send_dart_notification(&self.grid_id, GridNotification::BlockDidUpdateRow)
.payload(block_id)
.send();
Ok(())
@ -277,7 +277,7 @@ impl ClientGridBlockMetaEditor {
let mut row_count = 0;
let _ = self
.modify(|pad| {
let change = pad.add_row(row, start_row_id)?;
let change = pad.add_row_meta(row, start_row_id)?;
row_count = pad.number_of_rows();
Ok(change)
})
@ -298,13 +298,22 @@ impl ClientGridBlockMetaEditor {
Ok(row_count)
}
pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<RowMeta> {
let row_id = changeset.row_id.clone();
let _ = self.modify(|pad| Ok(pad.update_row(changeset)?)).await?;
Ok(())
let mut row_metas = self.get_row_metas(Some(vec![row_id.clone()])).await?;
debug_assert_eq!(row_metas.len(), 1);
if row_metas.is_empty() {
return Err(FlowyError::record_not_found().context(format!("Can't find the row with id: {}", &row_id)));
} else {
let a = (**row_metas.pop().as_ref().unwrap()).clone();
Ok(a)
}
}
pub async fn get_row_metas(&self, row_ids: Option<Vec<String>>) -> FlowyResult<Vec<Arc<RowMeta>>> {
let row_metas = self.pad.read().await.get_rows(row_ids)?;
let row_metas = self.pad.read().await.get_row_metas(row_ids)?;
Ok(row_metas)
}
@ -313,7 +322,7 @@ impl ClientGridBlockMetaEditor {
.pad
.read()
.await
.get_rows(row_ids)?
.get_row_metas(row_ids)?
.iter()
.map(RowOrder::from)
.collect::<Vec<RowOrder>>();

View File

@ -43,10 +43,10 @@ pub(crate) fn make_row_ids_per_block(row_orders: &[RowOrder]) -> Vec<RowIdsPerBl
pub(crate) fn make_grid_blocks(block_meta_snapshots: Vec<GridBlockMetaData>) -> FlowyResult<RepeatedGridBlock> {
Ok(block_meta_snapshots
.into_iter()
.map(|row_metas_per_block| {
let row_orders = make_row_orders_from_row_metas(&row_metas_per_block.row_metas);
.map(|block_meta_data| {
let row_orders = make_row_orders_from_row_metas(&block_meta_data.row_metas);
GridBlock {
block_id: row_metas_per_block.block_id,
id: block_meta_data.block_id,
row_orders,
}
})

View File

@ -67,6 +67,7 @@ impl RevisionManager {
}
}
#[tracing::instrument(level = "debug", skip_all, fields(object_id) err)]
pub async fn load<B>(&mut self, cloud: Option<Arc<dyn RevisionCloudService>>) -> FlowyResult<B::Output>
where
B: RevisionObjectBuilder,
@ -80,6 +81,7 @@ impl RevisionManager {
.load()
.await?;
self.rev_id_counter.set(rev_id);
tracing::Span::current().record("object_id", &self.object_id.as_str());
B::build_object(&self.object_id, revisions)
}

View File

@ -181,7 +181,7 @@ pub struct GridBlockOrder {
#[derive(Debug, Default, ProtoBuf)]
pub struct GridBlock {
#[pb(index = 1)]
pub block_id: String,
pub id: String,
#[pb(index = 2)]
pub row_orders: Vec<RowOrder>,
@ -190,7 +190,7 @@ pub struct GridBlock {
impl GridBlock {
pub fn new(block_id: &str, row_orders: Vec<RowOrder>) -> Self {
Self {
block_id: block_id.to_owned(),
id: block_id.to_owned(),
row_orders,
}
}
@ -269,6 +269,12 @@ impl AsRef<str> for GridBlockId {
}
}
impl std::convert::From<&str> for GridBlockId {
fn from(s: &str) -> Self {
GridBlockId { value: s.to_owned() }
}
}
#[derive(ProtoBuf, Default)]
pub struct CreateRowPayload {
#[pb(index = 1)]

View File

@ -2111,7 +2111,7 @@ impl ::protobuf::reflect::ProtobufValue for GridBlockOrder {
#[derive(PartialEq,Clone,Default)]
pub struct GridBlock {
// message fields
pub block_id: ::std::string::String,
pub id: ::std::string::String,
pub row_orders: ::protobuf::RepeatedField<RowOrder>,
// special fields
pub unknown_fields: ::protobuf::UnknownFields,
@ -2129,30 +2129,30 @@ impl GridBlock {
::std::default::Default::default()
}
// string block_id = 1;
// string id = 1;
pub fn get_block_id(&self) -> &str {
&self.block_id
pub fn get_id(&self) -> &str {
&self.id
}
pub fn clear_block_id(&mut self) {
self.block_id.clear();
pub fn clear_id(&mut self) {
self.id.clear();
}
// Param is passed by value, moved
pub fn set_block_id(&mut self, v: ::std::string::String) {
self.block_id = v;
pub fn set_id(&mut self, v: ::std::string::String) {
self.id = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_block_id(&mut self) -> &mut ::std::string::String {
&mut self.block_id
pub fn mut_id(&mut self) -> &mut ::std::string::String {
&mut self.id
}
// Take field
pub fn take_block_id(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.block_id, ::std::string::String::new())
pub fn take_id(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.id, ::std::string::String::new())
}
// repeated .RowOrder row_orders = 2;
@ -2196,7 +2196,7 @@ impl ::protobuf::Message for GridBlock {
let (field_number, wire_type) = is.read_tag_unpack()?;
match field_number {
1 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.block_id)?;
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
},
2 => {
::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.row_orders)?;
@ -2213,8 +2213,8 @@ impl ::protobuf::Message for GridBlock {
#[allow(unused_variables)]
fn compute_size(&self) -> u32 {
let mut my_size = 0;
if !self.block_id.is_empty() {
my_size += ::protobuf::rt::string_size(1, &self.block_id);
if !self.id.is_empty() {
my_size += ::protobuf::rt::string_size(1, &self.id);
}
for value in &self.row_orders {
let len = value.compute_size();
@ -2226,8 +2226,8 @@ impl ::protobuf::Message for GridBlock {
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
if !self.block_id.is_empty() {
os.write_string(1, &self.block_id)?;
if !self.id.is_empty() {
os.write_string(1, &self.id)?;
}
for v in &self.row_orders {
os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?;
@ -2273,9 +2273,9 @@ impl ::protobuf::Message for GridBlock {
descriptor.get(|| {
let mut fields = ::std::vec::Vec::new();
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"block_id",
|m: &GridBlock| { &m.block_id },
|m: &mut GridBlock| { &mut m.block_id },
"id",
|m: &GridBlock| { &m.id },
|m: &mut GridBlock| { &mut m.id },
));
fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<RowOrder>>(
"row_orders",
@ -2298,7 +2298,7 @@ impl ::protobuf::Message for GridBlock {
impl ::protobuf::Clear for GridBlock {
fn clear(&mut self) {
self.block_id.clear();
self.id.clear();
self.row_orders.clear();
self.unknown_fields.clear();
}
@ -4091,24 +4091,24 @@ static file_descriptor_proto_data: &'static [u8] = b"\
\x05.CellR\x05value:\x028\x01\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\
\x01\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11RepeatedGridBlock\x12\x20\n\
\x05items\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05items\"+\n\x0eGridBlockO\
rder\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\"P\n\tGridBloc\
k\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12(\n\nrow_order\
s\x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\";\n\x04Cell\x12\x19\n\
\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\
\x20\x01(\tR\x07content\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\x01\
\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\n\
\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05valu\
e\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\
\x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid\
_id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\
\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"d\n\x11QueryField\
Payload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfie\
ld_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"\
e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrd\
erR\x0bblockOrders\"\\\n\x0fQueryRowPayload\x12\x17\n\x07grid_id\x18\x01\
\x20\x01(\tR\x06gridId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07bloc\
kId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowIdb\x06proto3\
rder\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\"E\n\tGridBloc\
k\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\
\x20\x03(\x0b2\t.RowOrderR\trowOrders\";\n\x04Cell\x12\x19\n\x08field_id\
\x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\x20\x01(\tR\
\x07content\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\x01\x20\x03(\x0b\
2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\n\x04name\x18\x01\
\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05value\x18\x01\x20\
\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\x18\x01\x20\x01\
(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\
\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\x01(\tH\0R\nstart\
RowIdB\x15\n\x13one_of_start_row_id\"d\n\x11QueryFieldPayload\x12\x17\n\
\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_orders\x18\x02\
\x20\x01(\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\x16QueryGridB\
locksPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x122\n\
\x0cblock_orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\x0bblockOrder\
s\"\\\n\x0fQueryRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06g\
ridId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12\x15\n\x06\
row_id\x18\x03\x20\x01(\tR\x05rowIdb\x06proto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -44,7 +44,7 @@ message GridBlockOrder {
string block_id = 1;
}
message GridBlock {
string block_id = 1;
string id = 1;
repeated RowOrder row_orders = 2;
}
message Cell {

View File

@ -47,20 +47,21 @@ impl GridBlockMetaPad {
Self::from_delta(block_delta)
}
pub fn add_row(
#[tracing::instrument(level = "trace", skip(self, row), err)]
pub fn add_row_meta(
&mut self,
row: RowMeta,
start_row_id: Option<String>,
) -> CollaborateResult<Option<GridBlockMetaChange>> {
self.modify(|rows| {
if let Some(upper_row_id) = start_row_id {
if upper_row_id.is_empty() {
if let Some(start_row_id) = start_row_id {
if start_row_id.is_empty() {
rows.insert(0, Arc::new(row));
return Ok(Some(()));
}
if let Some(index) = rows.iter().position(|row| row.id == upper_row_id) {
rows.insert(index, Arc::new(row));
if let Some(index) = rows.iter().position(|row| row.id == start_row_id) {
rows.insert(index + 1, Arc::new(row));
return Ok(Some(()));
}
}
@ -77,7 +78,7 @@ impl GridBlockMetaPad {
})
}
pub fn get_rows(&self, row_ids: Option<Vec<String>>) -> CollaborateResult<Vec<Arc<RowMeta>>> {
pub fn get_row_metas(&self, row_ids: Option<Vec<String>>) -> CollaborateResult<Vec<Arc<RowMeta>>> {
match row_ids {
None => Ok(self.row_metas.to_vec()),
Some(row_ids) => {
@ -229,7 +230,7 @@ mod tests {
visibility: false,
};
let change = pad.add_row(row, None).unwrap().unwrap();
let change = pad.add_row_meta(row, None).unwrap().unwrap();
assert_eq!(
change.delta.to_delta_str(),
r#"[{"retain":29},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false}"},{"retain":2}]"#
@ -243,19 +244,19 @@ mod tests {
let row_2 = test_row_meta("2", &pad);
let row_3 = test_row_meta("3", &pad);
let change = pad.add_row(row_1.clone(), None).unwrap().unwrap();
let change = pad.add_row_meta(row_1.clone(), None).unwrap().unwrap();
assert_eq!(
change.delta.to_delta_str(),
r#"[{"retain":29},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false}"},{"retain":2}]"#
);
let change = pad.add_row(row_2.clone(), None).unwrap().unwrap();
let change = pad.add_row_meta(row_2.clone(), None).unwrap().unwrap();
assert_eq!(
change.delta.to_delta_str(),
r#"[{"retain":106},{"insert":",{\"id\":\"2\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false}"},{"retain":2}]"#
);
let change = pad.add_row(row_3.clone(), Some("2".to_string())).unwrap().unwrap();
let change = pad.add_row_meta(row_3.clone(), Some("2".to_string())).unwrap().unwrap();
assert_eq!(
change.delta.to_delta_str(),
r#"[{"retain":114},{"insert":"3\",\"block_id\":\"1\",\"cell_by_field_id\":{},\"height\":0,\"visibility\":false},{\"id\":\""},{"retain":72}]"#
@ -283,9 +284,9 @@ mod tests {
let row_2 = test_row_meta("2", &pad);
let row_3 = test_row_meta("3", &pad);
let _ = pad.add_row(row_1.clone(), None).unwrap().unwrap();
let _ = pad.add_row(row_2.clone(), None).unwrap().unwrap();
let _ = pad.add_row(row_3.clone(), Some("1".to_string())).unwrap().unwrap();
let _ = pad.add_row_meta(row_1.clone(), None).unwrap().unwrap();
let _ = pad.add_row_meta(row_2.clone(), None).unwrap().unwrap();
let _ = pad.add_row_meta(row_3.clone(), Some("1".to_string())).unwrap().unwrap();
assert_eq!(*pad.row_metas[0], row_3);
assert_eq!(*pad.row_metas[1], row_1);
@ -299,9 +300,9 @@ mod tests {
let row_2 = test_row_meta("2", &pad);
let row_3 = test_row_meta("3", &pad);
let _ = pad.add_row(row_1.clone(), None).unwrap().unwrap();
let _ = pad.add_row(row_2.clone(), None).unwrap().unwrap();
let _ = pad.add_row(row_3.clone(), Some("".to_string())).unwrap().unwrap();
let _ = pad.add_row_meta(row_1.clone(), None).unwrap().unwrap();
let _ = pad.add_row_meta(row_2.clone(), None).unwrap().unwrap();
let _ = pad.add_row_meta(row_3.clone(), Some("".to_string())).unwrap().unwrap();
assert_eq!(*pad.row_metas[0], row_3);
assert_eq!(*pad.row_metas[1], row_1);
@ -320,7 +321,7 @@ mod tests {
visibility: false,
};
let _ = pad.add_row(row.clone(), None).unwrap().unwrap();
let _ = pad.add_row_meta(row.clone(), None).unwrap().unwrap();
let change = pad.delete_rows(&[row.id]).unwrap().unwrap();
assert_eq!(
change.delta.to_delta_str(),
@ -348,7 +349,7 @@ mod tests {
cell_by_field_id: Default::default(),
};
let _ = pad.add_row(row, None).unwrap().unwrap();
let _ = pad.add_row_meta(row, None).unwrap().unwrap();
let change = pad.update_row(changeset).unwrap().unwrap();
assert_eq!(