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:
Mathias Mogensen 2023-10-26 03:38:37 +02:00 committed by GitHub
parent f40ae9ff25
commit aa27c4e6d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 516 additions and 221 deletions

View File

@ -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;
}

View File

@ -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(),
);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -53,4 +53,10 @@ extension FieldTypeListExtension on FieldType {
}
throw UnimplementedError;
}
bool get canEditHeader => switch (this) {
FieldType.MultiSelect => true,
FieldType.SingleSelect => true,
_ => false,
};
}

View File

@ -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,

View File

@ -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) {

View File

@ -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';

View File

@ -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:

View File

@ -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'}

View File

@ -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>

View File

@ -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>

View File

@ -744,7 +744,8 @@
},
"board": {
"column": {
"create_new_card": "New"
"createNewCard": "New",
"renameGroupTooltip": "Press to rename group"
},
"menuName": "Board",
"referencedBoardPrefix": "View of"

View File

@ -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,
})

View File

@ -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());

View File

@ -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,
}

View File

@ -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(())
}

View File

@ -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(&params.view_id).await?;

View File

@ -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)
}

View File

@ -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]

View File

@ -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;

View File

@ -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();

View File

@ -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>,
}

View File

@ -147,7 +147,7 @@ pub enum ErrorCode {
GroupIdIsEmpty = 46,
#[error("Invalid date time format")]
InvalidDateTimeFormat = 47,
InvalidDateTimeFormat = 48,
#[error("Invalid params")]
InvalidParams = 49,