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 {
|
class GroupBackendService {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
|
||||||
GroupBackendService(this.viewId);
|
GroupBackendService(this.viewId);
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> groupByField({
|
Future<Either<Unit, FlowyError>> groupByField({
|
||||||
@ -19,10 +20,15 @@ class GroupBackendService {
|
|||||||
|
|
||||||
Future<Either<Unit, FlowyError>> updateGroup({
|
Future<Either<Unit, FlowyError>> updateGroup({
|
||||||
required String groupId,
|
required String groupId,
|
||||||
|
required String fieldId,
|
||||||
String? name,
|
String? name,
|
||||||
bool? visible,
|
bool? visible,
|
||||||
}) {
|
}) {
|
||||||
final payload = UpdateGroupPB.create()..groupId = groupId;
|
final payload = UpdateGroupPB.create()
|
||||||
|
..fieldId = fieldId
|
||||||
|
..viewId = viewId
|
||||||
|
..groupId = groupId;
|
||||||
|
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
payload.name = name;
|
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/defines.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.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/plugins/database_view/application/row/row_service.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:dartz/dartz.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-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
@ -22,6 +24,7 @@ import 'group_controller.dart';
|
|||||||
part 'board_bloc.freezed.dart';
|
part 'board_bloc.freezed.dart';
|
||||||
|
|
||||||
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||||
|
late final GroupBackendService groupBackendSvc;
|
||||||
final DatabaseController databaseController;
|
final DatabaseController databaseController;
|
||||||
late final AppFlowyBoardController boardController;
|
late final AppFlowyBoardController boardController;
|
||||||
final LinkedHashMap<String, GroupController> groupControllers =
|
final LinkedHashMap<String, GroupController> groupControllers =
|
||||||
@ -34,23 +37,15 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
required ViewPB view,
|
required ViewPB view,
|
||||||
required this.databaseController,
|
required this.databaseController,
|
||||||
}) : super(BoardState.initial(view.id)) {
|
}) : super(BoardState.initial(view.id)) {
|
||||||
|
groupBackendSvc = GroupBackendService(viewId);
|
||||||
boardController = AppFlowyBoardController(
|
boardController = AppFlowyBoardController(
|
||||||
onMoveGroup: (
|
onMoveGroup: (fromGroupId, fromIndex, toGroupId, toIndex) {
|
||||||
fromGroupId,
|
|
||||||
fromIndex,
|
|
||||||
toGroupId,
|
|
||||||
toIndex,
|
|
||||||
) {
|
|
||||||
databaseController.moveGroup(
|
databaseController.moveGroup(
|
||||||
fromGroupId: fromGroupId,
|
fromGroupId: fromGroupId,
|
||||||
toGroupId: toGroupId,
|
toGroupId: toGroupId,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onMoveGroupItem: (
|
onMoveGroupItem: (groupId, fromIndex, toIndex) {
|
||||||
groupId,
|
|
||||||
fromIndex,
|
|
||||||
toIndex,
|
|
||||||
) {
|
|
||||||
final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
|
final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
|
||||||
final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
|
final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
|
||||||
if (fromRow != null) {
|
if (fromRow != null) {
|
||||||
@ -61,12 +56,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMoveGroupItemToGroup: (
|
onMoveGroupItemToGroup: (fromGroupId, fromIndex, toGroupId, toIndex) {
|
||||||
fromGroupId,
|
|
||||||
fromIndex,
|
|
||||||
toGroupId,
|
|
||||||
toIndex,
|
|
||||||
) {
|
|
||||||
final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex);
|
final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex);
|
||||||
final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex);
|
final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex);
|
||||||
if (fromRow != null) {
|
if (fromRow != null) {
|
||||||
@ -107,12 +97,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
didCreateRow: (group, row, int? index) {
|
didCreateRow: (group, row, int? index) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
editingRow: Some(
|
isEditingRow: true,
|
||||||
BoardEditingRow(
|
editingRow: BoardEditingRow(
|
||||||
group: group,
|
group: group,
|
||||||
row: row,
|
row: row,
|
||||||
index: index,
|
index: index,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -121,23 +110,26 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
startEditingRow: (group, row) {
|
startEditingRow: (group, row) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
editingRow: Some(
|
editingRow: BoardEditingRow(
|
||||||
BoardEditingRow(
|
group: group,
|
||||||
group: group,
|
row: row,
|
||||||
row: row,
|
index: null,
|
||||||
index: null,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_groupItemStartEditing(group, row, true);
|
_groupItemStartEditing(group, row, true);
|
||||||
},
|
},
|
||||||
endEditingRow: (rowId) {
|
endEditingRow: (rowId) {
|
||||||
state.editingRow.fold(() => null, (editingRow) {
|
if (state.editingRow != null && state.isEditingRow) {
|
||||||
assert(editingRow.row.id == rowId);
|
assert(state.editingRow!.row.id == rowId);
|
||||||
_groupItemStartEditing(editingRow.group, editingRow.row, false);
|
_groupItemStartEditing(
|
||||||
emit(state.copyWith(editingRow: none()));
|
state.editingRow!.group,
|
||||||
});
|
state.editingRow!.row,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(state.copyWith(isEditingRow: false));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
didReceiveGridUpdate: (DatabasePB grid) {
|
didReceiveGridUpdate: (DatabasePB grid) {
|
||||||
emit(state.copyWith(grid: Some(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.initial() = _InitialBoard;
|
||||||
const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow;
|
const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow;
|
||||||
const factory BoardEvent.createHeaderRow(String groupId) = _CreateHeaderRow;
|
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(
|
const factory BoardEvent.didCreateRow(
|
||||||
GroupPB group,
|
GroupPB group,
|
||||||
RowMetaPB row,
|
RowMetaPB row,
|
||||||
@ -327,7 +337,10 @@ class BoardState with _$BoardState {
|
|||||||
required String viewId,
|
required String viewId,
|
||||||
required Option<DatabasePB> grid,
|
required Option<DatabasePB> grid,
|
||||||
required List<String> groupIds,
|
required List<String> groupIds,
|
||||||
required Option<BoardEditingRow> editingRow,
|
required bool isEditingHeader,
|
||||||
|
String? editingHeaderId,
|
||||||
|
required bool isEditingRow,
|
||||||
|
BoardEditingRow? editingRow,
|
||||||
required LoadingState loadingState,
|
required LoadingState loadingState,
|
||||||
required Option<FlowyError> noneOrError,
|
required Option<FlowyError> noneOrError,
|
||||||
}) = _BoardState;
|
}) = _BoardState;
|
||||||
@ -336,7 +349,8 @@ class BoardState with _$BoardState {
|
|||||||
grid: none(),
|
grid: none(),
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
groupIds: [],
|
groupIds: [],
|
||||||
editingRow: none(),
|
isEditingHeader: false,
|
||||||
|
isEditingRow: false,
|
||||||
noneOrError: none(),
|
noneOrError: none(),
|
||||||
loadingState: const LoadingState.loading(),
|
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/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.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/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/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.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/plugins/database_view/widgets/row/row_detail.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.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_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -82,7 +82,7 @@ class BoardPage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider<BoardBloc>(
|
||||||
create: (context) => BoardBloc(
|
create: (context) => BoardBloc(
|
||||||
view: view,
|
view: view,
|
||||||
databaseController: databaseController,
|
databaseController: databaseController,
|
||||||
@ -133,18 +133,20 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
scrollManager = AppFlowyBoardScrollController();
|
scrollManager = AppFlowyBoardScrollController();
|
||||||
renderHook.addSelectOptionHook((options, groupId, _) {
|
renderHook.addSelectOptionHook((options, groupId, _) {
|
||||||
// The cell should hide if the option id is equal to the groupId.
|
// The cell should hide if the option id is equal to the groupId.
|
||||||
final isInGroup =
|
final isInGroup =
|
||||||
options.where((element) => element.id == groupId).isNotEmpty;
|
options.where((element) => element.id == groupId).isNotEmpty;
|
||||||
|
|
||||||
if (isInGroup || options.isEmpty) {
|
if (isInGroup || options.isEmpty) {
|
||||||
return const SizedBox();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -155,92 +157,51 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
widget.onEditStateChanged?.call();
|
widget.onEditStateChanged?.call();
|
||||||
},
|
},
|
||||||
child: BlocBuilder<BoardBloc, BoardState>(
|
child: BlocBuilder<BoardBloc, BoardState>(
|
||||||
|
// Only rebuild when groups are added/removed/rearranged
|
||||||
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: GridSize.contentInsets,
|
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) {
|
void _handleEditStateChanged(BoardState state, BuildContext context) {
|
||||||
state.editingRow.fold(
|
if (state.isEditingRow && state.editingRow != null) {
|
||||||
() => null,
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
(editingRow) {
|
if (state.editingRow!.index == null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
scrollManager.scrollToBottom(state.editingRow!.group.groupId);
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) {
|
Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) {
|
||||||
// final boardCustomData = columnData.customData as BoardCustomData;
|
|
||||||
// final group = boardCustomData.group;
|
|
||||||
|
|
||||||
return AppFlowyGroupFooter(
|
return AppFlowyGroupFooter(
|
||||||
icon: SizedBox(
|
icon: SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
@ -251,7 +212,7 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: FlowyText.medium(
|
title: FlowyText.medium(
|
||||||
LocaleKeys.board_column_create_new_card.tr(),
|
LocaleKeys.board_column_createNewCard.tr(),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
height: 50,
|
height: 50,
|
||||||
@ -269,25 +230,21 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
AppFlowyGroupData afGroupData,
|
AppFlowyGroupData afGroupData,
|
||||||
AppFlowyGroupItem afGroupItem,
|
AppFlowyGroupItem afGroupItem,
|
||||||
) {
|
) {
|
||||||
|
final boardBloc = context.read<BoardBloc>();
|
||||||
final groupItem = afGroupItem as GroupItem;
|
final groupItem = afGroupItem as GroupItem;
|
||||||
final groupData = afGroupData.customData as GroupData;
|
final groupData = afGroupData.customData as GroupData;
|
||||||
final rowMeta = groupItem.row;
|
final rowMeta = groupItem.row;
|
||||||
final rowCache = context.read<BoardBloc>().getRowCache();
|
final rowCache = boardBloc.getRowCache();
|
||||||
|
|
||||||
/// Return placeholder widget if the rowCache is null.
|
/// 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 cellCache = rowCache.cellCache;
|
||||||
final fieldController = context.read<BoardBloc>().fieldController;
|
final fieldController = boardBloc.fieldController;
|
||||||
final viewId = context.read<BoardBloc>().viewId;
|
final viewId = boardBloc.viewId;
|
||||||
|
|
||||||
final cellBuilder = CardCellBuilder<String>(cellCache);
|
final cellBuilder = CardCellBuilder<String>(cellCache);
|
||||||
bool isEditing = false;
|
final isEditing = boardBloc.state.isEditingRow &&
|
||||||
context.read<BoardBloc>().state.editingRow.fold(
|
boardBloc.state.editingRow?.row.id == groupItem.row.id;
|
||||||
() => null,
|
|
||||||
(editingRow) {
|
|
||||||
isEditing = editingRow.row.id == groupItem.row.id;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
final groupItemId = groupItem.row.id + groupData.group.groupId;
|
final groupItemId = groupItem.row.id + groupData.group.groupId;
|
||||||
return AppFlowyGroupCard(
|
return AppFlowyGroupCard(
|
||||||
@ -305,26 +262,17 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
cellBuilder: cellBuilder,
|
cellBuilder: cellBuilder,
|
||||||
renderHook: renderHook,
|
renderHook: renderHook,
|
||||||
openCard: (context) => _openCard(
|
openCard: (context) => _openCard(
|
||||||
viewId,
|
context: context,
|
||||||
groupData.group.groupId,
|
viewId: viewId,
|
||||||
fieldController,
|
groupId: groupData.group.groupId,
|
||||||
rowMeta,
|
fieldController: fieldController,
|
||||||
rowCache,
|
rowMeta: rowMeta,
|
||||||
context,
|
rowCache: rowCache,
|
||||||
),
|
),
|
||||||
onStartEditing: () {
|
onStartEditing: () => boardBloc
|
||||||
context.read<BoardBloc>().add(
|
.add(BoardEvent.startEditingRow(groupData.group, groupItem.row)),
|
||||||
BoardEvent.startEditingRow(
|
onEndEditing: () =>
|
||||||
groupData.group,
|
boardBloc.add(BoardEvent.endEditingRow(groupItem.row.id)),
|
||||||
groupItem.row,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onEndEditing: () {
|
|
||||||
context
|
|
||||||
.read<BoardBloc>()
|
|
||||||
.add(BoardEvent.endEditingRow(groupItem.row.id));
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -342,19 +290,19 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openCard(
|
void _openCard({
|
||||||
String viewId,
|
required BuildContext context,
|
||||||
String groupId,
|
required String viewId,
|
||||||
FieldController fieldController,
|
required String groupId,
|
||||||
RowMetaPB rowMetaPB,
|
required FieldController fieldController,
|
||||||
RowCache rowCache,
|
required RowMetaPB rowMeta,
|
||||||
BuildContext context,
|
required RowCache rowCache,
|
||||||
) {
|
}) {
|
||||||
final rowInfo = RowInfo(
|
final rowInfo = RowInfo(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
fields: UnmodifiableListView(fieldController.fieldInfos),
|
fields: UnmodifiableListView(fieldController.fieldInfos),
|
||||||
rowMeta: rowMetaPB,
|
rowMeta: rowMeta,
|
||||||
rowId: rowMetaPB.id,
|
rowId: rowMeta.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
final dataController = RowController(
|
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;
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get canEditHeader => switch (this) {
|
||||||
|
FieldType.MultiSelect => true,
|
||||||
|
FieldType.SingleSelect => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider<SelectOptionTypeOptionBloc>(
|
||||||
create: (context) => SelectOptionTypeOptionBloc(
|
create: (context) => SelectOptionTypeOptionBloc(
|
||||||
options: options,
|
options: options,
|
||||||
typeOptionAction: typeOptionAction,
|
typeOptionAction: typeOptionAction,
|
||||||
|
@ -12,9 +12,9 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
|||||||
required this.cellController,
|
required this.cellController,
|
||||||
}) : super(TextCellState.initial(cellController)) {
|
}) : super(TextCellState.initial(cellController)) {
|
||||||
on<TextCellEvent>(
|
on<TextCellEvent>(
|
||||||
(event, emit) async {
|
(event, emit) {
|
||||||
await event.when(
|
event.when(
|
||||||
initial: () async {
|
initial: () {
|
||||||
_startListening();
|
_startListening();
|
||||||
},
|
},
|
||||||
updateText: (text) {
|
updateText: (text) {
|
||||||
|
@ -3,7 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
|
|
||||||
const _localFmt = 'M/d/y';
|
const _localFmt = 'M/d/y';
|
||||||
const _usFmt = 'y/M/d';
|
const _usFmt = 'y/M/d';
|
||||||
const _isoFmt = 'ymd';
|
const _isoFmt = 'y-M-d';
|
||||||
const _friendlyFmt = 'MMM d, y';
|
const _friendlyFmt = 'MMM d, y';
|
||||||
const _dmyFmt = 'd/M/y';
|
const _dmyFmt = 'd/M/y';
|
||||||
|
|
||||||
|
@ -732,10 +732,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: intl_utils
|
name: intl_utils
|
||||||
sha256: "0d38f605f292321c0729f8c0632b845be77aa12d272b7bc5e3022bb670a7309e"
|
sha256: "5cad11e11ff7662c3cd0ef04729248591d71ed023d4ef0903a137528b4568adf"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.4"
|
version: "2.8.5"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -23,7 +23,7 @@ function ConfirmDialog({ open, title, subtitle, onOk, onClose }: Props) {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button variant={'outlined'} onClick={onClose}>
|
<Button variant={'outlined'} onClick={onClose}>
|
||||||
{t('button.Cancel')}
|
{t('button.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={'contained'}
|
variant={'contained'}
|
||||||
|
@ -91,7 +91,7 @@ export const BoardGroup = ({
|
|||||||
<span className={'h-5 w-5'}>
|
<span className={'h-5 w-5'}>
|
||||||
<AddSvg></AddSvg>
|
<AddSvg></AddSvg>
|
||||||
</span>
|
</span>
|
||||||
<span>{t('board.column.create_new_card')}</span>
|
<span>{t('board.column.createNewCard')}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,7 +43,7 @@ function RenameDialog({
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose}>{t('button.Cancel')}</Button>
|
<Button onClick={onClose}>{t('button.cancel')}</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
@ -53,7 +53,7 @@ function RenameDialog({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('button.OK')}
|
{t('button.ok')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -744,7 +744,8 @@
|
|||||||
},
|
},
|
||||||
"board": {
|
"board": {
|
||||||
"column": {
|
"column": {
|
||||||
"create_new_card": "New"
|
"createNewCard": "New",
|
||||||
|
"renameGroupTooltip": "Press to rename group"
|
||||||
},
|
},
|
||||||
"menuName": "Board",
|
"menuName": "Board",
|
||||||
"referencedBoardPrefix": "View of"
|
"referencedBoardPrefix": "View of"
|
||||||
|
@ -408,6 +408,7 @@ impl EventIntegrationTest {
|
|||||||
&self,
|
&self,
|
||||||
view_id: &str,
|
view_id: &str,
|
||||||
group_id: &str,
|
group_id: &str,
|
||||||
|
field_id: &str,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
visible: Option<bool>,
|
visible: Option<bool>,
|
||||||
) -> Option<FlowyError> {
|
) -> Option<FlowyError> {
|
||||||
@ -416,6 +417,7 @@ impl EventIntegrationTest {
|
|||||||
.payload(UpdateGroupPB {
|
.payload(UpdateGroupPB {
|
||||||
view_id: view_id.to_string(),
|
view_id: view_id.to_string(),
|
||||||
group_id: group_id.to_string(),
|
group_id: group_id.to_string(),
|
||||||
|
field_id: field_id.to_string(),
|
||||||
name,
|
name,
|
||||||
visible,
|
visible,
|
||||||
})
|
})
|
||||||
|
@ -748,7 +748,8 @@ async fn rename_group_event_test() {
|
|||||||
let error = test
|
let error = test
|
||||||
.update_group(
|
.update_group(
|
||||||
&board_view.id,
|
&board_view.id,
|
||||||
&groups[0].group_id,
|
&groups[1].group_id,
|
||||||
|
&groups[1].field_id,
|
||||||
Some("new name".to_owned()),
|
Some("new name".to_owned()),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@ -756,7 +757,7 @@ async fn rename_group_event_test() {
|
|||||||
assert!(error.is_none());
|
assert!(error.is_none());
|
||||||
|
|
||||||
let groups = test.get_groups(&board_view.id).await;
|
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]
|
#[tokio::test]
|
||||||
@ -772,7 +773,13 @@ async fn hide_group_event_test2() {
|
|||||||
assert_eq!(groups.len(), 4);
|
assert_eq!(groups.len(), 4);
|
||||||
|
|
||||||
let error = test
|
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;
|
.await;
|
||||||
assert!(error.is_none());
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
@ -145,10 +145,13 @@ pub struct UpdateGroupPB {
|
|||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
pub group_id: String,
|
pub group_id: String,
|
||||||
|
|
||||||
#[pb(index = 3, one_of)]
|
#[pb(index = 3)]
|
||||||
pub name: Option<String>,
|
pub field_id: String,
|
||||||
|
|
||||||
#[pb(index = 4, one_of)]
|
#[pb(index = 4, one_of)]
|
||||||
|
pub name: Option<String>,
|
||||||
|
|
||||||
|
#[pb(index = 5, one_of)]
|
||||||
pub visible: Option<bool>,
|
pub visible: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,10 +165,14 @@ impl TryInto<UpdateGroupParams> for UpdateGroupPB {
|
|||||||
let group_id = NotEmptyStr::parse(self.group_id)
|
let group_id = NotEmptyStr::parse(self.group_id)
|
||||||
.map_err(|_| ErrorCode::GroupIdIsEmpty)?
|
.map_err(|_| ErrorCode::GroupIdIsEmpty)?
|
||||||
.0;
|
.0;
|
||||||
|
let field_id = NotEmptyStr::parse(self.field_id)
|
||||||
|
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
||||||
|
.0;
|
||||||
|
|
||||||
Ok(UpdateGroupParams {
|
Ok(UpdateGroupParams {
|
||||||
view_id,
|
view_id,
|
||||||
group_id,
|
group_id,
|
||||||
|
field_id,
|
||||||
name: self.name,
|
name: self.name,
|
||||||
visible: self.visible,
|
visible: self.visible,
|
||||||
})
|
})
|
||||||
@ -175,6 +182,7 @@ impl TryInto<UpdateGroupParams> for UpdateGroupPB {
|
|||||||
pub struct UpdateGroupParams {
|
pub struct UpdateGroupParams {
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
pub group_id: String,
|
pub group_id: String,
|
||||||
|
pub field_id: String,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub visible: Option<bool>,
|
pub visible: Option<bool>,
|
||||||
}
|
}
|
||||||
@ -183,6 +191,7 @@ impl From<UpdateGroupParams> for GroupChangeset {
|
|||||||
fn from(params: UpdateGroupParams) -> Self {
|
fn from(params: UpdateGroupParams) -> Self {
|
||||||
Self {
|
Self {
|
||||||
group_id: params.group_id,
|
group_id: params.group_id,
|
||||||
|
field_id: params.field_id,
|
||||||
name: params.name,
|
name: params.name,
|
||||||
visible: params.visible,
|
visible: params.visible,
|
||||||
}
|
}
|
||||||
|
@ -694,12 +694,19 @@ pub(crate) async fn update_group_handler(
|
|||||||
let params: UpdateGroupParams = data.into_inner().try_into()?;
|
let params: UpdateGroupParams = data.into_inner().try_into()?;
|
||||||
let view_id = params.view_id.clone();
|
let view_id = params.view_id.clone();
|
||||||
let database_editor = manager.get_database_with_view_id(&view_id).await?;
|
let database_editor = manager.get_database_with_view_id(&view_id).await?;
|
||||||
let group_setting_changeset = GroupSettingChangeset {
|
let group_changeset = GroupChangeset::from(params);
|
||||||
update_groups: vec![GroupChangeset::from(params)],
|
|
||||||
};
|
|
||||||
database_editor
|
database_editor
|
||||||
.update_group_setting(&view_id, group_setting_changeset)
|
.update_group(&view_id, group_changeset.clone())
|
||||||
.await?;
|
.await?;
|
||||||
|
database_editor
|
||||||
|
.update_group_setting(
|
||||||
|
&view_id,
|
||||||
|
GroupSettingChangeset {
|
||||||
|
update_groups: vec![group_changeset],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ use crate::services::field_settings::{
|
|||||||
};
|
};
|
||||||
use crate::services::filter::Filter;
|
use crate::services::filter::Filter;
|
||||||
use crate::services::group::{
|
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::share::csv::{CSVExport, CSVFormat};
|
||||||
use crate::services::sort::Sort;
|
use crate::services::sort::Sort;
|
||||||
@ -188,6 +188,31 @@ impl DatabaseEditor {
|
|||||||
Ok(())
|
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)]
|
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||||
pub async fn create_or_update_filter(&self, params: UpdateFilterParams) -> FlowyResult<()> {
|
pub async fn create_or_update_filter(&self, params: UpdateFilterParams) -> FlowyResult<()> {
|
||||||
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
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,
|
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
|
||||||
};
|
};
|
||||||
use crate::services::group::{
|
use crate::services::group::{
|
||||||
GroupController, GroupSetting, GroupSettingChangeset, MoveGroupRowContext, RowChangeset,
|
GroupChangeset, GroupController, GroupSetting, GroupSettingChangeset, MoveGroupRowContext,
|
||||||
|
RowChangeset,
|
||||||
};
|
};
|
||||||
use crate::services::setting::CalendarLayoutSetting;
|
use crate::services::setting::CalendarLayoutSetting;
|
||||||
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
|
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
|
||||||
@ -479,6 +480,27 @@ impl DatabaseViewEditor {
|
|||||||
Ok(())
|
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> {
|
pub async fn v_get_all_sorts(&self) -> Vec<Sort> {
|
||||||
self.delegate.get_all_sorts(&self.view_id)
|
self.delegate.get_all_sorts(&self.view_id)
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,11 @@ pub trait GroupController: GroupControllerOperation + Send + Sync {
|
|||||||
|
|
||||||
/// Called after the row was created.
|
/// Called after the row was created.
|
||||||
fn did_create_row(&mut self, row_detail: &RowDetail, group_id: &str);
|
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]
|
/// The [GroupsBuilder] trait is used to generate the groups for different [FieldType]
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
use std::sync::Arc;
|
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 collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
|
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
|
||||||
use crate::services::cell::insert_select_option_cell;
|
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::action::GroupCustomize;
|
||||||
use crate::services::group::controller::{
|
use crate::services::group::controller::{
|
||||||
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
|
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
|
||||||
@ -105,6 +107,29 @@ impl GroupController for MultiSelectGroupController {
|
|||||||
group.add_row(row_detail.clone())
|
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;
|
pub struct MultiSelectGroupGenerator;
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
use std::sync::Arc;
|
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 collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
|
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
|
||||||
use crate::services::cell::insert_select_option_cell;
|
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::action::GroupCustomize;
|
||||||
use crate::services::group::controller::{
|
use crate::services::group::controller::{
|
||||||
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
|
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
|
||||||
@ -99,11 +101,35 @@ impl GroupController for SingleSelectGroupController {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn did_create_row(&mut self, row_detail: &RowDetail, group_id: &str) {
|
fn did_create_row(&mut self, row_detail: &RowDetail, group_id: &str) {
|
||||||
if let Some(group) = self.context.get_mut_group(group_id) {
|
if let Some(group) = self.context.get_mut_group(group_id) {
|
||||||
group.add_row(row_detail.clone())
|
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();
|
pub struct SingleSelectGroupGenerator();
|
||||||
|
@ -18,8 +18,10 @@ pub struct GroupSettingChangeset {
|
|||||||
pub update_groups: Vec<GroupChangeset>,
|
pub update_groups: Vec<GroupChangeset>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct GroupChangeset {
|
pub struct GroupChangeset {
|
||||||
pub group_id: String,
|
pub group_id: String,
|
||||||
|
pub field_id: String,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub visible: Option<bool>,
|
pub visible: Option<bool>,
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ pub enum ErrorCode {
|
|||||||
GroupIdIsEmpty = 46,
|
GroupIdIsEmpty = 46,
|
||||||
|
|
||||||
#[error("Invalid date time format")]
|
#[error("Invalid date time format")]
|
||||||
InvalidDateTimeFormat = 47,
|
InvalidDateTimeFormat = 48,
|
||||||
|
|
||||||
#[error("Invalid params")]
|
#[error("Invalid params")]
|
||||||
InvalidParams = 49,
|
InvalidParams = 49,
|
||||||
|
Loading…
Reference in New Issue
Block a user