mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Feat: rename stack inline (#3781)
* feat: rename stack in-line * feat: rename stack in-line * chore: compiler issues * fix: conflicts and cleaning * fix: code lost after merge * test: fix failing rust tests * fix: tauri localization wrong keys --------- Co-authored-by: Richard Shiue <71320345+richardshiue@users.noreply.github.com>
This commit is contained in:
parent
f40ae9ff25
commit
aa27c4e6d4
@ -5,6 +5,7 @@ import 'package:dartz/dartz.dart';
|
||||
|
||||
class GroupBackendService {
|
||||
final String viewId;
|
||||
|
||||
GroupBackendService(this.viewId);
|
||||
|
||||
Future<Either<Unit, FlowyError>> groupByField({
|
||||
@ -19,10 +20,15 @@ class GroupBackendService {
|
||||
|
||||
Future<Either<Unit, FlowyError>> updateGroup({
|
||||
required String groupId,
|
||||
required String fieldId,
|
||||
String? name,
|
||||
bool? visible,
|
||||
}) {
|
||||
final payload = UpdateGroupPB.create()..groupId = groupId;
|
||||
final payload = UpdateGroupPB.create()
|
||||
..fieldId = fieldId
|
||||
..viewId = viewId
|
||||
..groupId = groupId;
|
||||
|
||||
if (name != null) {
|
||||
payload.name = name;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import 'dart:collection';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/application/defines.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/group/group_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
@ -11,6 +12,7 @@ import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@ -22,6 +24,7 @@ import 'group_controller.dart';
|
||||
part 'board_bloc.freezed.dart';
|
||||
|
||||
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
late final GroupBackendService groupBackendSvc;
|
||||
final DatabaseController databaseController;
|
||||
late final AppFlowyBoardController boardController;
|
||||
final LinkedHashMap<String, GroupController> groupControllers =
|
||||
@ -34,23 +37,15 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
required ViewPB view,
|
||||
required this.databaseController,
|
||||
}) : super(BoardState.initial(view.id)) {
|
||||
groupBackendSvc = GroupBackendService(viewId);
|
||||
boardController = AppFlowyBoardController(
|
||||
onMoveGroup: (
|
||||
fromGroupId,
|
||||
fromIndex,
|
||||
toGroupId,
|
||||
toIndex,
|
||||
) {
|
||||
onMoveGroup: (fromGroupId, fromIndex, toGroupId, toIndex) {
|
||||
databaseController.moveGroup(
|
||||
fromGroupId: fromGroupId,
|
||||
toGroupId: toGroupId,
|
||||
);
|
||||
},
|
||||
onMoveGroupItem: (
|
||||
groupId,
|
||||
fromIndex,
|
||||
toIndex,
|
||||
) {
|
||||
onMoveGroupItem: (groupId, fromIndex, toIndex) {
|
||||
final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
|
||||
final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
|
||||
if (fromRow != null) {
|
||||
@ -61,12 +56,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
}
|
||||
},
|
||||
onMoveGroupItemToGroup: (
|
||||
fromGroupId,
|
||||
fromIndex,
|
||||
toGroupId,
|
||||
toIndex,
|
||||
) {
|
||||
onMoveGroupItemToGroup: (fromGroupId, fromIndex, toGroupId, toIndex) {
|
||||
final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex);
|
||||
final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex);
|
||||
if (fromRow != null) {
|
||||
@ -107,12 +97,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
didCreateRow: (group, row, int? index) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
editingRow: Some(
|
||||
BoardEditingRow(
|
||||
group: group,
|
||||
row: row,
|
||||
index: index,
|
||||
),
|
||||
isEditingRow: true,
|
||||
editingRow: BoardEditingRow(
|
||||
group: group,
|
||||
row: row,
|
||||
index: index,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -121,23 +110,26 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
startEditingRow: (group, row) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
editingRow: Some(
|
||||
BoardEditingRow(
|
||||
group: group,
|
||||
row: row,
|
||||
index: null,
|
||||
),
|
||||
editingRow: BoardEditingRow(
|
||||
group: group,
|
||||
row: row,
|
||||
index: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
_groupItemStartEditing(group, row, true);
|
||||
},
|
||||
endEditingRow: (rowId) {
|
||||
state.editingRow.fold(() => null, (editingRow) {
|
||||
assert(editingRow.row.id == rowId);
|
||||
_groupItemStartEditing(editingRow.group, editingRow.row, false);
|
||||
emit(state.copyWith(editingRow: none()));
|
||||
});
|
||||
if (state.editingRow != null && state.isEditingRow) {
|
||||
assert(state.editingRow!.row.id == rowId);
|
||||
_groupItemStartEditing(
|
||||
state.editingRow!.group,
|
||||
state.editingRow!.row,
|
||||
false,
|
||||
);
|
||||
|
||||
emit(state.copyWith(isEditingRow: false));
|
||||
}
|
||||
},
|
||||
didReceiveGridUpdate: (DatabasePB grid) {
|
||||
emit(state.copyWith(grid: Some(grid)));
|
||||
@ -152,6 +144,20 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
startEditingHeader: (String groupId) {
|
||||
emit(
|
||||
state.copyWith(isEditingHeader: true, editingHeaderId: groupId),
|
||||
);
|
||||
},
|
||||
endEditingHeader: (String groupId, String groupName) async {
|
||||
await groupBackendSvc.updateGroup(
|
||||
fieldId: groupControllers.values.first.group.fieldId,
|
||||
groupId: groupId,
|
||||
name: groupName,
|
||||
);
|
||||
|
||||
emit(state.copyWith(isEditingHeader: false));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -303,6 +309,10 @@ class BoardEvent with _$BoardEvent {
|
||||
const factory BoardEvent.initial() = _InitialBoard;
|
||||
const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow;
|
||||
const factory BoardEvent.createHeaderRow(String groupId) = _CreateHeaderRow;
|
||||
const factory BoardEvent.startEditingHeader(String groupId) =
|
||||
_StartEditingHeader;
|
||||
const factory BoardEvent.endEditingHeader(String groupId, String groupName) =
|
||||
_EndEditingHeader;
|
||||
const factory BoardEvent.didCreateRow(
|
||||
GroupPB group,
|
||||
RowMetaPB row,
|
||||
@ -327,7 +337,10 @@ class BoardState with _$BoardState {
|
||||
required String viewId,
|
||||
required Option<DatabasePB> grid,
|
||||
required List<String> groupIds,
|
||||
required Option<BoardEditingRow> editingRow,
|
||||
required bool isEditingHeader,
|
||||
String? editingHeaderId,
|
||||
required bool isEditingRow,
|
||||
BoardEditingRow? editingRow,
|
||||
required LoadingState loadingState,
|
||||
required Option<FlowyError> noneOrError,
|
||||
}) = _BoardState;
|
||||
@ -336,7 +349,8 @@ class BoardState with _$BoardState {
|
||||
grid: none(),
|
||||
viewId: viewId,
|
||||
groupIds: [],
|
||||
editingRow: none(),
|
||||
isEditingHeader: false,
|
||||
isEditingRow: false,
|
||||
noneOrError: none(),
|
||||
loadingState: const LoadingState.loading(),
|
||||
);
|
||||
|
@ -8,11 +8,11 @@ import 'package:appflowy/plugins/database_view/application/database_controller.d
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/board/presentation/widgets/board_column_header.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -82,7 +82,7 @@ class BoardPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
return BlocProvider<BoardBloc>(
|
||||
create: (context) => BoardBloc(
|
||||
view: view,
|
||||
databaseController: databaseController,
|
||||
@ -133,18 +133,20 @@ class _BoardContentState extends State<BoardContent> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
scrollManager = AppFlowyBoardScrollController();
|
||||
renderHook.addSelectOptionHook((options, groupId, _) {
|
||||
// The cell should hide if the option id is equal to the groupId.
|
||||
final isInGroup =
|
||||
options.where((element) => element.id == groupId).isNotEmpty;
|
||||
|
||||
if (isInGroup || options.isEmpty) {
|
||||
return const SizedBox();
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -155,92 +157,51 @@ class _BoardContentState extends State<BoardContent> {
|
||||
widget.onEditStateChanged?.call();
|
||||
},
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
// Only rebuild when groups are added/removed/rearranged
|
||||
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: GridSize.contentInsets,
|
||||
child: _buildBoard(context),
|
||||
child: AppFlowyBoard(
|
||||
boardScrollController: scrollManager,
|
||||
scrollController: ScrollController(),
|
||||
controller: context.read<BoardBloc>().boardController,
|
||||
headerBuilder: (_, groupData) => BlocProvider<BoardBloc>.value(
|
||||
value: context.read<BoardBloc>(),
|
||||
child: BoardColumnHeader(
|
||||
groupData: groupData,
|
||||
margin: config.headerPadding,
|
||||
),
|
||||
),
|
||||
footerBuilder: _buildFooter,
|
||||
cardBuilder: (_, column, columnItem) => _buildCard(
|
||||
context,
|
||||
column,
|
||||
columnItem,
|
||||
),
|
||||
groupConstraints: const BoxConstraints.tightFor(width: 300),
|
||||
config: AppFlowyBoardConfig(
|
||||
groupBackgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBoard(BuildContext context) {
|
||||
return AppFlowyBoard(
|
||||
boardScrollController: scrollManager,
|
||||
scrollController: ScrollController(),
|
||||
controller: context.read<BoardBloc>().boardController,
|
||||
headerBuilder: _buildHeader,
|
||||
footerBuilder: _buildFooter,
|
||||
cardBuilder: (_, column, columnItem) => _buildCard(
|
||||
context,
|
||||
column,
|
||||
columnItem,
|
||||
),
|
||||
groupConstraints: const BoxConstraints.tightFor(width: 300),
|
||||
config: AppFlowyBoardConfig(
|
||||
groupBackgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleEditStateChanged(BoardState state, BuildContext context) {
|
||||
state.editingRow.fold(
|
||||
() => null,
|
||||
(editingRow) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (editingRow.index != null) {
|
||||
} else {
|
||||
scrollManager.scrollToBottom(editingRow.group.groupId);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _buildHeader(
|
||||
BuildContext context,
|
||||
AppFlowyGroupData groupData,
|
||||
) {
|
||||
final boardCustomData = groupData.customData as GroupData;
|
||||
return AppFlowyGroupHeader(
|
||||
title: Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: FlowyText.medium(
|
||||
groupData.headerData.groupName,
|
||||
fontSize: 14,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
),
|
||||
icon: _buildHeaderIcon(boardCustomData),
|
||||
addIcon: SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: FlowySvg(
|
||||
FlowySvgs.add_s,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
),
|
||||
onAddButtonClick: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.createHeaderRow(groupData.id),
|
||||
);
|
||||
},
|
||||
height: 50,
|
||||
margin: config.headerPadding,
|
||||
);
|
||||
if (state.isEditingRow && state.editingRow != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (state.editingRow!.index == null) {
|
||||
scrollManager.scrollToBottom(state.editingRow!.group.groupId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) {
|
||||
// final boardCustomData = columnData.customData as BoardCustomData;
|
||||
// final group = boardCustomData.group;
|
||||
|
||||
return AppFlowyGroupFooter(
|
||||
icon: SizedBox(
|
||||
height: 20,
|
||||
@ -251,7 +212,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
),
|
||||
),
|
||||
title: FlowyText.medium(
|
||||
LocaleKeys.board_column_create_new_card.tr(),
|
||||
LocaleKeys.board_column_createNewCard.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
height: 50,
|
||||
@ -269,25 +230,21 @@ class _BoardContentState extends State<BoardContent> {
|
||||
AppFlowyGroupData afGroupData,
|
||||
AppFlowyGroupItem afGroupItem,
|
||||
) {
|
||||
final boardBloc = context.read<BoardBloc>();
|
||||
final groupItem = afGroupItem as GroupItem;
|
||||
final groupData = afGroupData.customData as GroupData;
|
||||
final rowMeta = groupItem.row;
|
||||
final rowCache = context.read<BoardBloc>().getRowCache();
|
||||
final rowCache = boardBloc.getRowCache();
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(groupItem));
|
||||
if (rowCache == null) return SizedBox.shrink(key: ObjectKey(groupItem));
|
||||
final cellCache = rowCache.cellCache;
|
||||
final fieldController = context.read<BoardBloc>().fieldController;
|
||||
final viewId = context.read<BoardBloc>().viewId;
|
||||
final fieldController = boardBloc.fieldController;
|
||||
final viewId = boardBloc.viewId;
|
||||
|
||||
final cellBuilder = CardCellBuilder<String>(cellCache);
|
||||
bool isEditing = false;
|
||||
context.read<BoardBloc>().state.editingRow.fold(
|
||||
() => null,
|
||||
(editingRow) {
|
||||
isEditing = editingRow.row.id == groupItem.row.id;
|
||||
},
|
||||
);
|
||||
final isEditing = boardBloc.state.isEditingRow &&
|
||||
boardBloc.state.editingRow?.row.id == groupItem.row.id;
|
||||
|
||||
final groupItemId = groupItem.row.id + groupData.group.groupId;
|
||||
return AppFlowyGroupCard(
|
||||
@ -305,26 +262,17 @@ class _BoardContentState extends State<BoardContent> {
|
||||
cellBuilder: cellBuilder,
|
||||
renderHook: renderHook,
|
||||
openCard: (context) => _openCard(
|
||||
viewId,
|
||||
groupData.group.groupId,
|
||||
fieldController,
|
||||
rowMeta,
|
||||
rowCache,
|
||||
context,
|
||||
context: context,
|
||||
viewId: viewId,
|
||||
groupId: groupData.group.groupId,
|
||||
fieldController: fieldController,
|
||||
rowMeta: rowMeta,
|
||||
rowCache: rowCache,
|
||||
),
|
||||
onStartEditing: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.startEditingRow(
|
||||
groupData.group,
|
||||
groupItem.row,
|
||||
),
|
||||
);
|
||||
},
|
||||
onEndEditing: () {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditingRow(groupItem.row.id));
|
||||
},
|
||||
onStartEditing: () => boardBloc
|
||||
.add(BoardEvent.startEditingRow(groupData.group, groupItem.row)),
|
||||
onEndEditing: () =>
|
||||
boardBloc.add(BoardEvent.endEditingRow(groupItem.row.id)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -342,19 +290,19 @@ class _BoardContentState extends State<BoardContent> {
|
||||
);
|
||||
}
|
||||
|
||||
void _openCard(
|
||||
String viewId,
|
||||
String groupId,
|
||||
FieldController fieldController,
|
||||
RowMetaPB rowMetaPB,
|
||||
RowCache rowCache,
|
||||
BuildContext context,
|
||||
) {
|
||||
void _openCard({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required String groupId,
|
||||
required FieldController fieldController,
|
||||
required RowMetaPB rowMeta,
|
||||
required RowCache rowCache,
|
||||
}) {
|
||||
final rowInfo = RowInfo(
|
||||
viewId: viewId,
|
||||
fields: UnmodifiableListView(fieldController.fieldInfos),
|
||||
rowMeta: rowMetaPB,
|
||||
rowId: rowMetaPB.id,
|
||||
rowMeta: rowMeta,
|
||||
rowId: rowMeta.id,
|
||||
);
|
||||
|
||||
final dataController = RowController(
|
||||
@ -375,41 +323,3 @@ class _BoardContentState extends State<BoardContent> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget? _buildHeaderIcon(GroupData customData) {
|
||||
Widget? widget;
|
||||
switch (customData.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
final group = customData.asCheckboxGroup()!;
|
||||
widget = FlowySvg(
|
||||
group.isCheck ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||
blendMode: BlendMode.dst,
|
||||
);
|
||||
break;
|
||||
case FieldType.DateTime:
|
||||
case FieldType.LastEditedTime:
|
||||
case FieldType.CreatedTime:
|
||||
break;
|
||||
case FieldType.MultiSelect:
|
||||
break;
|
||||
case FieldType.Number:
|
||||
break;
|
||||
case FieldType.RichText:
|
||||
break;
|
||||
case FieldType.SingleSelect:
|
||||
break;
|
||||
case FieldType.URL:
|
||||
break;
|
||||
case FieldType.Checklist:
|
||||
break;
|
||||
}
|
||||
|
||||
if (widget != null) {
|
||||
widget = SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: widget,
|
||||
);
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
@ -0,0 +1,228 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/card/define.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardColumnHeader extends StatefulWidget {
|
||||
const BoardColumnHeader({
|
||||
super.key,
|
||||
required this.groupData,
|
||||
this.margin,
|
||||
});
|
||||
|
||||
final AppFlowyGroupData groupData;
|
||||
final EdgeInsets? margin;
|
||||
|
||||
@override
|
||||
State<BoardColumnHeader> createState() => _BoardColumnHeaderState();
|
||||
}
|
||||
|
||||
class _BoardColumnHeaderState extends State<BoardColumnHeader> {
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
|
||||
late final TextEditingController _controller =
|
||||
TextEditingController.fromValue(
|
||||
TextEditingValue(
|
||||
selection: TextSelection.collapsed(
|
||||
offset: widget.groupData.headerData.groupName.length,
|
||||
),
|
||||
text: widget.groupData.headerData.groupName,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode.addListener(() {
|
||||
if (!_focusNode.hasFocus) {
|
||||
_saveEdit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final boardCustomData = widget.groupData.customData as GroupData;
|
||||
|
||||
return BlocProvider<BoardBloc>.value(
|
||||
value: context.read<BoardBloc>(),
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (context, state) {
|
||||
if (state.isEditingHeader) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_focusNode.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
Widget title = Expanded(
|
||||
child: FlowyText.medium(
|
||||
widget.groupData.headerData.groupName,
|
||||
fontSize: 14,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
);
|
||||
|
||||
if (!boardCustomData.group.isDefault &&
|
||||
boardCustomData.fieldType.canEditHeader) {
|
||||
title = Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: FlowyTooltip(
|
||||
message: LocaleKeys.board_column_renameGroupTooltip.tr(),
|
||||
child: FlowyHover(
|
||||
style: HoverStyle(
|
||||
hoverColor: Colors.transparent,
|
||||
foregroundColorOnHover:
|
||||
AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () => context.read<BoardBloc>().add(
|
||||
BoardEvent.startEditingHeader(
|
||||
widget.groupData.id,
|
||||
),
|
||||
),
|
||||
child: FlowyText.medium(
|
||||
widget.groupData.headerData.groupName,
|
||||
fontSize: 14,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (state.isEditingHeader &&
|
||||
state.editingHeaderId == widget.groupData.id) {
|
||||
title = _buildTextField(context);
|
||||
}
|
||||
|
||||
return AppFlowyGroupHeader(
|
||||
title: title,
|
||||
icon: _buildHeaderIcon(boardCustomData),
|
||||
addIcon: SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: FlowySvg(
|
||||
FlowySvgs.add_s,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
),
|
||||
onAddButtonClick: () => context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.createHeaderRow(widget.groupData.id)),
|
||||
height: 50,
|
||||
margin: widget.margin ?? EdgeInsets.zero,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextField(BuildContext context) {
|
||||
return Expanded(
|
||||
child: RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey: (event) {
|
||||
if (event is RawKeyDownEvent &&
|
||||
[LogicalKeyboardKey.enter, LogicalKeyboardKey.escape]
|
||||
.contains(event.logicalKey)) {
|
||||
_saveEdit();
|
||||
}
|
||||
},
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
onEditingComplete: _saveEdit,
|
||||
maxLines: 1,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontSize: 14),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.surface,
|
||||
hoverColor: Colors.transparent,
|
||||
// Magic number 4 makes the textField take up the same space as FlowyText
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: CardSizes.cardCellVPadding + 4,
|
||||
horizontal: 8,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _saveEdit() {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.endEditingHeader(
|
||||
widget.groupData.id,
|
||||
_controller.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget? _buildHeaderIcon(GroupData customData) {
|
||||
Widget? widget;
|
||||
switch (customData.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
final group = customData.asCheckboxGroup()!;
|
||||
widget = FlowySvg(
|
||||
group.isCheck ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||
blendMode: BlendMode.dst,
|
||||
);
|
||||
break;
|
||||
case FieldType.DateTime:
|
||||
case FieldType.LastEditedTime:
|
||||
case FieldType.CreatedTime:
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.Number:
|
||||
case FieldType.RichText:
|
||||
case FieldType.SingleSelect:
|
||||
case FieldType.URL:
|
||||
case FieldType.Checklist:
|
||||
break;
|
||||
}
|
||||
|
||||
if (widget != null) {
|
||||
widget = SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: widget,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
@ -53,4 +53,10 @@ extension FieldTypeListExtension on FieldType {
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
||||
bool get canEditHeader => switch (this) {
|
||||
FieldType.MultiSelect => true,
|
||||
FieldType.SingleSelect => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
return BlocProvider<SelectOptionTypeOptionBloc>(
|
||||
create: (context) => SelectOptionTypeOptionBloc(
|
||||
options: options,
|
||||
typeOptionAction: typeOptionAction,
|
||||
|
@ -12,9 +12,9 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
required this.cellController,
|
||||
}) : super(TextCellState.initial(cellController)) {
|
||||
on<TextCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
(event, emit) {
|
||||
event.when(
|
||||
initial: () {
|
||||
_startListening();
|
||||
},
|
||||
updateText: (text) {
|
||||
|
@ -3,7 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
const _localFmt = 'M/d/y';
|
||||
const _usFmt = 'y/M/d';
|
||||
const _isoFmt = 'ymd';
|
||||
const _isoFmt = 'y-M-d';
|
||||
const _friendlyFmt = 'MMM d, y';
|
||||
const _dmyFmt = 'd/M/y';
|
||||
|
||||
|
@ -732,10 +732,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl_utils
|
||||
sha256: "0d38f605f292321c0729f8c0632b845be77aa12d272b7bc5e3022bb670a7309e"
|
||||
sha256: "5cad11e11ff7662c3cd0ef04729248591d71ed023d4ef0903a137528b4568adf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.8.4"
|
||||
version: "2.8.5"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -23,7 +23,7 @@ function ConfirmDialog({ open, title, subtitle, onOk, onClose }: Props) {
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant={'outlined'} onClick={onClose}>
|
||||
{t('button.Cancel')}
|
||||
{t('button.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={'contained'}
|
||||
|
@ -91,7 +91,7 @@ export const BoardGroup = ({
|
||||
<span className={'h-5 w-5'}>
|
||||
<AddSvg></AddSvg>
|
||||
</span>
|
||||
<span>{t('board.column.create_new_card')}</span>
|
||||
<span>{t('board.column.createNewCard')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -43,7 +43,7 @@ function RenameDialog({
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>{t('button.Cancel')}</Button>
|
||||
<Button onClick={onClose}>{t('button.cancel')}</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
try {
|
||||
@ -53,7 +53,7 @@ function RenameDialog({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('button.OK')}
|
||||
{t('button.ok')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
@ -744,7 +744,8 @@
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "New"
|
||||
"createNewCard": "New",
|
||||
"renameGroupTooltip": "Press to rename group"
|
||||
},
|
||||
"menuName": "Board",
|
||||
"referencedBoardPrefix": "View of"
|
||||
|
@ -408,6 +408,7 @@ impl EventIntegrationTest {
|
||||
&self,
|
||||
view_id: &str,
|
||||
group_id: &str,
|
||||
field_id: &str,
|
||||
name: Option<String>,
|
||||
visible: Option<bool>,
|
||||
) -> Option<FlowyError> {
|
||||
@ -416,6 +417,7 @@ impl EventIntegrationTest {
|
||||
.payload(UpdateGroupPB {
|
||||
view_id: view_id.to_string(),
|
||||
group_id: group_id.to_string(),
|
||||
field_id: field_id.to_string(),
|
||||
name,
|
||||
visible,
|
||||
})
|
||||
|
@ -748,7 +748,8 @@ async fn rename_group_event_test() {
|
||||
let error = test
|
||||
.update_group(
|
||||
&board_view.id,
|
||||
&groups[0].group_id,
|
||||
&groups[1].group_id,
|
||||
&groups[1].field_id,
|
||||
Some("new name".to_owned()),
|
||||
None,
|
||||
)
|
||||
@ -756,7 +757,7 @@ async fn rename_group_event_test() {
|
||||
assert!(error.is_none());
|
||||
|
||||
let groups = test.get_groups(&board_view.id).await;
|
||||
assert_eq!(groups[0].group_name, "new name".to_owned());
|
||||
assert_eq!(groups[1].group_name, "new name".to_owned());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -772,7 +773,13 @@ async fn hide_group_event_test2() {
|
||||
assert_eq!(groups.len(), 4);
|
||||
|
||||
let error = test
|
||||
.update_group(&board_view.id, &groups[0].group_id, None, Some(false))
|
||||
.update_group(
|
||||
&board_view.id,
|
||||
&groups[0].group_id,
|
||||
&groups[0].field_id,
|
||||
None,
|
||||
Some(false),
|
||||
)
|
||||
.await;
|
||||
assert!(error.is_none());
|
||||
|
||||
|
@ -145,10 +145,13 @@ pub struct UpdateGroupPB {
|
||||
#[pb(index = 2)]
|
||||
pub group_id: String,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub name: Option<String>,
|
||||
#[pb(index = 3)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub name: Option<String>,
|
||||
|
||||
#[pb(index = 5, one_of)]
|
||||
pub visible: Option<bool>,
|
||||
}
|
||||
|
||||
@ -162,10 +165,14 @@ impl TryInto<UpdateGroupParams> for UpdateGroupPB {
|
||||
let group_id = NotEmptyStr::parse(self.group_id)
|
||||
.map_err(|_| ErrorCode::GroupIdIsEmpty)?
|
||||
.0;
|
||||
let field_id = NotEmptyStr::parse(self.field_id)
|
||||
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
||||
.0;
|
||||
|
||||
Ok(UpdateGroupParams {
|
||||
view_id,
|
||||
group_id,
|
||||
field_id,
|
||||
name: self.name,
|
||||
visible: self.visible,
|
||||
})
|
||||
@ -175,6 +182,7 @@ impl TryInto<UpdateGroupParams> for UpdateGroupPB {
|
||||
pub struct UpdateGroupParams {
|
||||
pub view_id: String,
|
||||
pub group_id: String,
|
||||
pub field_id: String,
|
||||
pub name: Option<String>,
|
||||
pub visible: Option<bool>,
|
||||
}
|
||||
@ -183,6 +191,7 @@ impl From<UpdateGroupParams> for GroupChangeset {
|
||||
fn from(params: UpdateGroupParams) -> Self {
|
||||
Self {
|
||||
group_id: params.group_id,
|
||||
field_id: params.field_id,
|
||||
name: params.name,
|
||||
visible: params.visible,
|
||||
}
|
||||
|
@ -694,12 +694,19 @@ pub(crate) async fn update_group_handler(
|
||||
let params: UpdateGroupParams = data.into_inner().try_into()?;
|
||||
let view_id = params.view_id.clone();
|
||||
let database_editor = manager.get_database_with_view_id(&view_id).await?;
|
||||
let group_setting_changeset = GroupSettingChangeset {
|
||||
update_groups: vec![GroupChangeset::from(params)],
|
||||
};
|
||||
let group_changeset = GroupChangeset::from(params);
|
||||
database_editor
|
||||
.update_group_setting(&view_id, group_setting_changeset)
|
||||
.update_group(&view_id, group_changeset.clone())
|
||||
.await?;
|
||||
database_editor
|
||||
.update_group_setting(
|
||||
&view_id,
|
||||
GroupSettingChangeset {
|
||||
update_groups: vec![group_changeset],
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ use crate::services::field_settings::{
|
||||
};
|
||||
use crate::services::filter::Filter;
|
||||
use crate::services::group::{
|
||||
default_group_setting, GroupSetting, GroupSettingChangeset, RowChangeset,
|
||||
default_group_setting, GroupChangeset, GroupSetting, GroupSettingChangeset, RowChangeset,
|
||||
};
|
||||
use crate::services::share::csv::{CSVExport, CSVFormat};
|
||||
use crate::services::sort::Sort;
|
||||
@ -188,6 +188,31 @@ impl DatabaseEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_group(
|
||||
&self,
|
||||
view_id: &str,
|
||||
group_changeset: GroupChangeset,
|
||||
) -> FlowyResult<()> {
|
||||
let view_editor = self.database_views.get_view_editor(view_id).await?;
|
||||
let type_option = view_editor.update_group(group_changeset.clone()).await?;
|
||||
|
||||
if let Some(type_option_data) = type_option {
|
||||
let field = self.get_field(&group_changeset.field_id);
|
||||
if field.is_some() {
|
||||
let _ = self
|
||||
.update_field_type_option(
|
||||
view_id,
|
||||
&group_changeset.field_id,
|
||||
type_option_data,
|
||||
field.unwrap(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub async fn create_or_update_filter(&self, params: UpdateFilterParams) -> FlowyResult<()> {
|
||||
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
||||
|
@ -37,7 +37,8 @@ use crate::services::filter::{
|
||||
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
|
||||
};
|
||||
use crate::services::group::{
|
||||
GroupController, GroupSetting, GroupSettingChangeset, MoveGroupRowContext, RowChangeset,
|
||||
GroupChangeset, GroupController, GroupSetting, GroupSettingChangeset, MoveGroupRowContext,
|
||||
RowChangeset,
|
||||
};
|
||||
use crate::services::setting::CalendarLayoutSetting;
|
||||
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
|
||||
@ -479,6 +480,27 @@ impl DatabaseViewEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_group(
|
||||
&self,
|
||||
changeset: GroupChangeset,
|
||||
) -> FlowyResult<Option<TypeOptionData>> {
|
||||
match changeset.name {
|
||||
Some(group_name) => {
|
||||
let result = self
|
||||
.mut_group_controller(|controller, _| {
|
||||
Ok(controller.update_group_name(&changeset.group_id, &group_name))
|
||||
})
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Some(r) => Ok(r),
|
||||
None => Ok(None),
|
||||
}
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn v_get_all_sorts(&self) -> Vec<Sort> {
|
||||
self.delegate.get_all_sorts(&self.view_id)
|
||||
}
|
||||
|
@ -39,6 +39,11 @@ pub trait GroupController: GroupControllerOperation + Send + Sync {
|
||||
|
||||
/// Called after the row was created.
|
||||
fn did_create_row(&mut self, row_detail: &RowDetail, group_id: &str);
|
||||
|
||||
/// Update group name handler
|
||||
fn update_group_name(&mut self, _group_id: &str, _group_name: &str) -> Option<TypeOptionData> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The [GroupsBuilder] trait is used to generate the groups for different [FieldType]
|
||||
|
@ -1,12 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
|
||||
use crate::services::cell::insert_select_option_cell;
|
||||
use crate::services::field::{MultiSelectTypeOption, SelectOptionCellDataParser};
|
||||
use crate::services::field::{
|
||||
MultiSelectTypeOption, SelectOption, SelectOptionCellDataParser, SelectTypeOptionSharedAction,
|
||||
};
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
use crate::services::group::controller::{
|
||||
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
|
||||
@ -105,6 +107,29 @@ impl GroupController for MultiSelectGroupController {
|
||||
group.add_row(row_detail.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn update_group_name(&mut self, group_id: &str, group_name: &str) -> Option<TypeOptionData> {
|
||||
match &self.type_option {
|
||||
Some(type_option) => {
|
||||
let select_option = type_option
|
||||
.options
|
||||
.iter()
|
||||
.find(|option| option.id == group_id)
|
||||
.unwrap();
|
||||
|
||||
let new_select_option = SelectOption {
|
||||
name: group_name.to_owned(),
|
||||
..select_option.to_owned()
|
||||
};
|
||||
|
||||
let mut new_type_option = type_option.clone();
|
||||
new_type_option.insert_option(new_select_option);
|
||||
|
||||
Some(new_type_option.to_type_option_data())
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MultiSelectGroupGenerator;
|
||||
|
@ -1,12 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
|
||||
use crate::services::cell::insert_select_option_cell;
|
||||
use crate::services::field::{SelectOptionCellDataParser, SingleSelectTypeOption};
|
||||
use crate::services::field::{
|
||||
SelectOption, SelectOptionCellDataParser, SelectTypeOptionSharedAction, SingleSelectTypeOption,
|
||||
};
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
use crate::services::group::controller::{
|
||||
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
|
||||
@ -99,11 +101,35 @@ impl GroupController for SingleSelectGroupController {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn did_create_row(&mut self, row_detail: &RowDetail, group_id: &str) {
|
||||
if let Some(group) = self.context.get_mut_group(group_id) {
|
||||
group.add_row(row_detail.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn update_group_name(&mut self, group_id: &str, group_name: &str) -> Option<TypeOptionData> {
|
||||
match &self.type_option {
|
||||
Some(type_option) => {
|
||||
let select_option = type_option
|
||||
.options
|
||||
.iter()
|
||||
.find(|option| option.id == group_id)
|
||||
.unwrap();
|
||||
|
||||
let new_select_option = SelectOption {
|
||||
name: group_name.to_owned(),
|
||||
..select_option.to_owned()
|
||||
};
|
||||
|
||||
let mut new_type_option = type_option.clone();
|
||||
new_type_option.insert_option(new_select_option);
|
||||
|
||||
Some(new_type_option.to_type_option_data())
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SingleSelectGroupGenerator();
|
||||
|
@ -18,8 +18,10 @@ pub struct GroupSettingChangeset {
|
||||
pub update_groups: Vec<GroupChangeset>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct GroupChangeset {
|
||||
pub group_id: String,
|
||||
pub field_id: String,
|
||||
pub name: Option<String>,
|
||||
pub visible: Option<bool>,
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ pub enum ErrorCode {
|
||||
GroupIdIsEmpty = 46,
|
||||
|
||||
#[error("Invalid date time format")]
|
||||
InvalidDateTimeFormat = 47,
|
||||
InvalidDateTimeFormat = 48,
|
||||
|
||||
#[error("Invalid params")]
|
||||
InvalidParams = 49,
|
||||
|
Loading…
Reference in New Issue
Block a user