mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: enable relation to (#4866)
* chore: enable relation to * chore: fix database name and improve UI * chore: remove database view id from relation type option * chore: add remove row id test * chore: improve appearance of untitled rows * chore: empty in row detail * fix: cannot add events after closing --------- Co-authored-by: Richard Shiue <71320345+richardshiue@users.noreply.github.com>
This commit is contained in:
parent
8d01d54e7f
commit
bb414c3fd6
@ -1,10 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||||
|
import 'package:appflowy/plugins/database/application/field/type_option/relation_type_option_cubit.dart';
|
||||||
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
|
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
|
||||||
|
import 'package:appflowy/plugins/database/domain/field_service.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
|
import 'package:collection/collection.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';
|
||||||
|
|
||||||
@ -35,12 +39,14 @@ class RelationCellBloc extends Bloc<RelationCellEvent, RelationCellState> {
|
|||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
didUpdateCell: (RelationCellDataPB? cellData) async {
|
didUpdateCell: (RelationCellDataPB? cellData) async {
|
||||||
if (cellData == null || cellData.rowIds.isEmpty) {
|
if (cellData == null ||
|
||||||
|
cellData.rowIds.isEmpty ||
|
||||||
|
state.relatedDatabaseMeta == null) {
|
||||||
emit(state.copyWith(rows: const []));
|
emit(state.copyWith(rows: const []));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final payload = RepeatedRowIdPB(
|
final payload = RepeatedRowIdPB(
|
||||||
databaseId: state.relatedDatabaseId,
|
databaseId: state.relatedDatabaseMeta!.databaseId,
|
||||||
rowIds: cellData.rowIds,
|
rowIds: cellData.rowIds,
|
||||||
);
|
);
|
||||||
final result =
|
final result =
|
||||||
@ -54,8 +60,16 @@ class RelationCellBloc extends Bloc<RelationCellEvent, RelationCellState> {
|
|||||||
);
|
);
|
||||||
emit(state.copyWith(rows: rows));
|
emit(state.copyWith(rows: rows));
|
||||||
},
|
},
|
||||||
didUpdateRelationDatabaseId: (databaseId) {
|
didUpdateRelationTypeOption: (typeOption) async {
|
||||||
emit(state.copyWith(relatedDatabaseId: databaseId));
|
if (typeOption.databaseId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final meta = await _loadDatabaseMeta(typeOption.databaseId);
|
||||||
|
emit(state.copyWith(relatedDatabaseMeta: meta));
|
||||||
|
_loadCellData();
|
||||||
|
},
|
||||||
|
selectDatabaseId: (databaseId) async {
|
||||||
|
await _updateTypeOption(databaseId);
|
||||||
},
|
},
|
||||||
selectRow: (rowId) async {
|
selectRow: (rowId) async {
|
||||||
await _handleSelectRow(rowId);
|
await _handleSelectRow(rowId);
|
||||||
@ -73,29 +87,30 @@ class RelationCellBloc extends Bloc<RelationCellEvent, RelationCellState> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onCellFieldChanged: (field) {
|
onCellFieldChanged: (field) {
|
||||||
if (!isClosed) {
|
// hack: SingleFieldListener receives notification before
|
||||||
// hack: SingleFieldListener receives notification before
|
// FieldController's copy is updated.
|
||||||
// FieldController's copy is updated.
|
Future.delayed(const Duration(milliseconds: 50), () {
|
||||||
Future.delayed(const Duration(milliseconds: 50), () {
|
if (!isClosed) {
|
||||||
final RelationTypeOptionPB typeOption =
|
final RelationTypeOptionPB typeOption =
|
||||||
cellController.getTypeOption(RelationTypeOptionDataParser());
|
cellController.getTypeOption(RelationTypeOptionDataParser());
|
||||||
add(
|
add(RelationCellEvent.didUpdateRelationTypeOption(typeOption));
|
||||||
RelationCellEvent.didUpdateRelationDatabaseId(
|
}
|
||||||
typeOption.databaseId,
|
});
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _init() {
|
void _init() {
|
||||||
final RelationTypeOptionPB typeOption =
|
final typeOption =
|
||||||
cellController.getTypeOption(RelationTypeOptionDataParser());
|
cellController.getTypeOption(RelationTypeOptionDataParser());
|
||||||
add(RelationCellEvent.didUpdateRelationDatabaseId(typeOption.databaseId));
|
add(RelationCellEvent.didUpdateRelationTypeOption(typeOption));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadCellData() {
|
||||||
final cellData = cellController.getCellData();
|
final cellData = cellController.getCellData();
|
||||||
add(RelationCellEvent.didUpdateCell(cellData));
|
if (!isClosed) {
|
||||||
|
add(RelationCellEvent.didUpdateCell(cellData));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleSelectRow(String rowId) async {
|
Future<void> _handleSelectRow(String rowId) async {
|
||||||
@ -115,25 +130,66 @@ class RelationCellBloc extends Bloc<RelationCellEvent, RelationCellState> {
|
|||||||
final result = await DatabaseEventUpdateRelationCell(payload).send();
|
final result = await DatabaseEventUpdateRelationCell(payload).send();
|
||||||
result.fold((l) => null, (err) => Log.error(err));
|
result.fold((l) => null, (err) => Log.error(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<DatabaseMeta?> _loadDatabaseMeta(String databaseId) async {
|
||||||
|
final getDatabaseResult = await DatabaseEventGetDatabases().send();
|
||||||
|
final databaseMeta = getDatabaseResult.fold<DatabaseMetaPB?>(
|
||||||
|
(s) => s.items.firstWhereOrNull(
|
||||||
|
(metaPB) => metaPB.databaseId == databaseId,
|
||||||
|
),
|
||||||
|
(f) => null,
|
||||||
|
);
|
||||||
|
if (databaseMeta != null) {
|
||||||
|
final result =
|
||||||
|
await ViewBackendService.getView(databaseMeta.inlineViewId);
|
||||||
|
return result.fold(
|
||||||
|
(s) => DatabaseMeta(
|
||||||
|
databaseId: databaseId,
|
||||||
|
inlineViewId: databaseMeta.inlineViewId,
|
||||||
|
databaseName: s.name,
|
||||||
|
),
|
||||||
|
(f) => null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateTypeOption(String databaseId) async {
|
||||||
|
final newDateTypeOption = RelationTypeOptionPB(
|
||||||
|
databaseId: databaseId,
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await FieldBackendService.updateFieldTypeOption(
|
||||||
|
viewId: cellController.viewId,
|
||||||
|
fieldId: cellController.fieldInfo.id,
|
||||||
|
typeOptionData: newDateTypeOption.writeToBuffer(),
|
||||||
|
);
|
||||||
|
result.fold((s) => null, (err) => Log.error(err));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class RelationCellEvent with _$RelationCellEvent {
|
class RelationCellEvent with _$RelationCellEvent {
|
||||||
const factory RelationCellEvent.didUpdateRelationDatabaseId(
|
const factory RelationCellEvent.didUpdateRelationTypeOption(
|
||||||
String databaseId,
|
RelationTypeOptionPB typeOption,
|
||||||
) = _DidUpdateRelationDatabaseId;
|
) = _DidUpdateRelationTypeOption;
|
||||||
const factory RelationCellEvent.didUpdateCell(RelationCellDataPB? data) =
|
const factory RelationCellEvent.didUpdateCell(RelationCellDataPB? data) =
|
||||||
_DidUpdateCell;
|
_DidUpdateCell;
|
||||||
|
const factory RelationCellEvent.selectDatabaseId(
|
||||||
|
String databaseId,
|
||||||
|
) = _SelectDatabaseId;
|
||||||
const factory RelationCellEvent.selectRow(String rowId) = _SelectRowId;
|
const factory RelationCellEvent.selectRow(String rowId) = _SelectRowId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class RelationCellState with _$RelationCellState {
|
class RelationCellState with _$RelationCellState {
|
||||||
const factory RelationCellState({
|
const factory RelationCellState({
|
||||||
required String relatedDatabaseId,
|
required DatabaseMeta? relatedDatabaseMeta,
|
||||||
required List<RelatedRowDataPB> rows,
|
required List<RelatedRowDataPB> rows,
|
||||||
}) = _RelationCellState;
|
}) = _RelationCellState;
|
||||||
|
|
||||||
factory RelationCellState.initial() =>
|
factory RelationCellState.initial() => const RelationCellState(
|
||||||
const RelationCellState(relatedDatabaseId: "", rows: []);
|
relatedDatabaseMeta: null,
|
||||||
|
rows: [],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'relation_type_option_cubit.freezed.dart';
|
||||||
|
|
||||||
|
class RelationDatabaseListCubit extends Cubit<RelationDatabaseListState> {
|
||||||
|
RelationDatabaseListCubit() : super(RelationDatabaseListState.initial()) {
|
||||||
|
_loadDatabaseMetas();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadDatabaseMetas() async {
|
||||||
|
final getDatabaseResult = await DatabaseEventGetDatabases().send();
|
||||||
|
final metaPBs = getDatabaseResult.fold<List<DatabaseMetaPB>>(
|
||||||
|
(s) => s.items,
|
||||||
|
(f) => [],
|
||||||
|
);
|
||||||
|
final futures = metaPBs.map((meta) {
|
||||||
|
return ViewBackendService.getView(meta.inlineViewId).then(
|
||||||
|
(result) => result.fold(
|
||||||
|
(s) => DatabaseMeta(
|
||||||
|
databaseId: meta.databaseId,
|
||||||
|
inlineViewId: meta.inlineViewId,
|
||||||
|
databaseName: s.name,
|
||||||
|
),
|
||||||
|
(f) => null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
final databaseMetas = await Future.wait(futures);
|
||||||
|
emit(
|
||||||
|
RelationDatabaseListState(
|
||||||
|
databaseMetas: databaseMetas.nonNulls.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class DatabaseMeta with _$DatabaseMeta {
|
||||||
|
factory DatabaseMeta({
|
||||||
|
/// id of the database
|
||||||
|
required String databaseId,
|
||||||
|
|
||||||
|
/// id of the inline view
|
||||||
|
required String inlineViewId,
|
||||||
|
|
||||||
|
/// name of the database, currently identical to the name of the inline view
|
||||||
|
required String databaseName,
|
||||||
|
}) = _DatabaseMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class RelationDatabaseListState with _$RelationDatabaseListState {
|
||||||
|
factory RelationDatabaseListState({
|
||||||
|
required List<DatabaseMeta> databaseMetas,
|
||||||
|
}) = _RelationDatabaseListState;
|
||||||
|
|
||||||
|
factory RelationDatabaseListState.initial() =>
|
||||||
|
RelationDatabaseListState(databaseMetas: []);
|
||||||
|
}
|
@ -4,7 +4,7 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
|||||||
import 'package:appflowy_result/appflowy_result.dart';
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
|
|
||||||
class DatabaseBackendService {
|
class DatabaseBackendService {
|
||||||
static Future<FlowyResult<List<DatabaseDescriptionPB>, FlowyError>>
|
static Future<FlowyResult<List<DatabaseMetaPB>, FlowyError>>
|
||||||
getAllDatabases() {
|
getAllDatabases() {
|
||||||
return DatabaseEventGetDatabases().send().then((result) {
|
return DatabaseEventGetDatabases().send().then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
|
@ -20,6 +20,7 @@ const List<FieldType> _supportedFieldTypes = [
|
|||||||
FieldType.URL,
|
FieldType.URL,
|
||||||
FieldType.LastEditedTime,
|
FieldType.LastEditedTime,
|
||||||
FieldType.CreatedTime,
|
FieldType.CreatedTime,
|
||||||
|
FieldType.Relation,
|
||||||
];
|
];
|
||||||
|
|
||||||
class FieldTypeList extends StatelessWidget with FlowyOverlayDelegate {
|
class FieldTypeList extends StatelessWidget with FlowyOverlayDelegate {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/database/application/field/type_option/relation_type_option_cubit.dart';
|
||||||
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
|
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
|
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:appflowy_result/appflowy_result.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:protobuf/protobuf.dart';
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
import 'builder.dart';
|
import 'builder.dart';
|
||||||
@ -27,55 +27,76 @@ class RelationTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
|||||||
}) {
|
}) {
|
||||||
final typeOption = _parseTypeOptionData(field.typeOptionData);
|
final typeOption = _parseTypeOptionData(field.typeOptionData);
|
||||||
|
|
||||||
return Column(
|
return BlocProvider(
|
||||||
mainAxisSize: MainAxisSize.min,
|
create: (_) => RelationDatabaseListCubit(),
|
||||||
children: [
|
child: Builder(
|
||||||
Container(
|
builder: (context) {
|
||||||
padding: const EdgeInsets.only(left: 14, right: 8),
|
return Column(
|
||||||
height: GridSize.popoverItemHeight,
|
mainAxisSize: MainAxisSize.min,
|
||||||
alignment: Alignment.centerLeft,
|
children: [
|
||||||
child: FlowyText.regular(
|
Container(
|
||||||
LocaleKeys.grid_relation_relatedDatabasePlaceLabel.tr(),
|
padding: const EdgeInsets.only(left: 14, right: 8),
|
||||||
color: Theme.of(context).hintColor,
|
height: GridSize.popoverItemHeight,
|
||||||
fontSize: 11,
|
alignment: Alignment.centerLeft,
|
||||||
),
|
child: FlowyText.regular(
|
||||||
),
|
LocaleKeys.grid_relation_relatedDatabasePlaceLabel.tr(),
|
||||||
AppFlowyPopover(
|
color: Theme.of(context).hintColor,
|
||||||
mutex: popoverMutex,
|
fontSize: 11,
|
||||||
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
),
|
||||||
offset: const Offset(6, 0),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
height: GridSize.popoverItemHeight,
|
|
||||||
child: FlowyButton(
|
|
||||||
text: FlowyText(
|
|
||||||
typeOption.databaseId.isEmpty
|
|
||||||
? LocaleKeys.grid_relation_relatedDatabasePlaceholder.tr()
|
|
||||||
: typeOption.databaseId,
|
|
||||||
color: typeOption.databaseId.isEmpty
|
|
||||||
? Theme.of(context).hintColor
|
|
||||||
: null,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
AppFlowyPopover(
|
||||||
),
|
mutex: popoverMutex,
|
||||||
),
|
triggerActions:
|
||||||
popupBuilder: (context) {
|
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||||
return _DatabaseList(
|
offset: const Offset(6, 0),
|
||||||
onSelectDatabase: (newDatabaseId) {
|
child: Container(
|
||||||
final newTypeOption = _updateTypeOption(
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
typeOption: typeOption,
|
height: GridSize.popoverItemHeight,
|
||||||
databaseId: newDatabaseId,
|
child: FlowyButton(
|
||||||
);
|
text: BlocBuilder<RelationDatabaseListCubit,
|
||||||
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
RelationDatabaseListState>(
|
||||||
PopoverContainer.of(context).close();
|
builder: (context, state) {
|
||||||
},
|
final databaseMeta =
|
||||||
currentDatabaseId:
|
state.databaseMetas.firstWhereOrNull(
|
||||||
typeOption.databaseId.isEmpty ? null : typeOption.databaseId,
|
(meta) => meta.databaseId == typeOption.databaseId,
|
||||||
);
|
);
|
||||||
},
|
return FlowyText(
|
||||||
),
|
databaseMeta == null
|
||||||
],
|
? LocaleKeys
|
||||||
|
.grid_relation_relatedDatabasePlaceholder
|
||||||
|
.tr()
|
||||||
|
: databaseMeta.databaseName,
|
||||||
|
color: databaseMeta == null
|
||||||
|
? Theme.of(context).hintColor
|
||||||
|
: null,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
popupBuilder: (popoverContext) {
|
||||||
|
return BlocProvider.value(
|
||||||
|
value: context.read<RelationDatabaseListCubit>(),
|
||||||
|
child: _DatabaseList(
|
||||||
|
onSelectDatabase: (newDatabaseId) {
|
||||||
|
final newTypeOption = _updateTypeOption(
|
||||||
|
typeOption: typeOption,
|
||||||
|
databaseId: newDatabaseId,
|
||||||
|
);
|
||||||
|
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
||||||
|
PopoverContainer.of(context).close();
|
||||||
|
},
|
||||||
|
currentDatabaseId: typeOption.databaseId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,65 +115,45 @@ class RelationTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DatabaseList extends StatefulWidget {
|
class _DatabaseList extends StatelessWidget {
|
||||||
const _DatabaseList({
|
const _DatabaseList({
|
||||||
required this.onSelectDatabase,
|
required this.onSelectDatabase,
|
||||||
required this.currentDatabaseId,
|
required this.currentDatabaseId,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? currentDatabaseId;
|
final String currentDatabaseId;
|
||||||
final void Function(String databaseId) onSelectDatabase;
|
final void Function(String databaseId) onSelectDatabase;
|
||||||
|
|
||||||
@override
|
|
||||||
State<_DatabaseList> createState() => _DatabaseListState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DatabaseListState extends State<_DatabaseList> {
|
|
||||||
late Future<FlowyResult<RepeatedDatabaseDescriptionPB, FlowyError>> future;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
future = DatabaseEventGetDatabases().send();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder(
|
return BlocBuilder<RelationDatabaseListCubit, RelationDatabaseListState>(
|
||||||
future: future,
|
builder: (context, state) {
|
||||||
builder: (context, snapshot) {
|
final children = state.databaseMetas.map((meta) {
|
||||||
final data = snapshot.data;
|
return SizedBox(
|
||||||
if (!snapshot.hasData ||
|
height: GridSize.popoverItemHeight,
|
||||||
snapshot.connectionState != ConnectionState.done ||
|
child: FlowyButton(
|
||||||
data!.isFailure()) {
|
onTap: () => onSelectDatabase(meta.databaseId),
|
||||||
return const SizedBox.shrink();
|
text: FlowyText.medium(
|
||||||
}
|
meta.databaseName,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
final databaseIds = data
|
),
|
||||||
.fold<List<DatabaseDescriptionPB>>((l) => l.items, (r) => [])
|
rightIcon: meta.databaseId == currentDatabaseId
|
||||||
.map((databaseDescription) {
|
? FlowySvg(
|
||||||
final databaseId = databaseDescription.databaseId;
|
FlowySvgs.check_s,
|
||||||
return FlowyButton(
|
color: Theme.of(context).colorScheme.primary,
|
||||||
onTap: () => widget.onSelectDatabase(databaseId),
|
)
|
||||||
text: FlowyText.medium(
|
: null,
|
||||||
databaseId,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
rightIcon: databaseId == widget.currentDatabaseId
|
|
||||||
? FlowySvg(
|
|
||||||
FlowySvgs.check_s,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
separatorBuilder: (_, __) =>
|
separatorBuilder: (_, __) =>
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
itemCount: databaseIds.length,
|
itemCount: children.length,
|
||||||
itemBuilder: (context, index) => databaseIds[index],
|
itemBuilder: (context, index) => children[index],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
@ -52,15 +53,19 @@ class _RelationCellState extends State<RelationCardCell> {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final children = state.rows
|
final children = state.rows.map(
|
||||||
.map(
|
(row) {
|
||||||
(row) => FlowyText.medium(
|
final isEmpty = row.name.isEmpty;
|
||||||
row.name,
|
return Text(
|
||||||
|
isEmpty ? LocaleKeys.grid_row_titlePlaceholder.tr() : row.name,
|
||||||
|
style: widget.style.textStyle.copyWith(
|
||||||
|
color: isEmpty ? Theme.of(context).hintColor : null,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
)
|
overflow: TextOverflow.ellipsis,
|
||||||
.toList();
|
);
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
alignment: AlignmentDirectional.topStart,
|
alignment: AlignmentDirectional.topStart,
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart';
|
import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart';
|
||||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../editable_cell_skeleton/relation.dart';
|
import '../editable_cell_skeleton/relation.dart';
|
||||||
@ -29,10 +31,6 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin {
|
|||||||
value: bloc,
|
value: bloc,
|
||||||
child: RelationCellEditor(
|
child: RelationCellEditor(
|
||||||
selectedRowIds: state.rows.map((row) => row.rowId).toList(),
|
selectedRowIds: state.rows.map((row) => row.rowId).toList(),
|
||||||
databaseId: state.relatedDatabaseId,
|
|
||||||
onSelectRow: (rowId) {
|
|
||||||
bloc.add(RelationCellEvent.selectRow(rowId));
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -42,15 +40,17 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin {
|
|||||||
child: Wrap(
|
child: Wrap(
|
||||||
runSpacing: 4.0,
|
runSpacing: 4.0,
|
||||||
spacing: 4.0,
|
spacing: 4.0,
|
||||||
children: state.rows
|
children: state.rows.map(
|
||||||
.map(
|
(row) {
|
||||||
(row) => FlowyText.medium(
|
final isEmpty = row.name.isEmpty;
|
||||||
row.name,
|
return FlowyText.medium(
|
||||||
decoration: TextDecoration.underline,
|
isEmpty ? LocaleKeys.grid_row_titlePlaceholder.tr() : row.name,
|
||||||
overflow: TextOverflow.ellipsis,
|
color: isEmpty ? Theme.of(context).hintColor : null,
|
||||||
),
|
decoration: TextDecoration.underline,
|
||||||
)
|
overflow: TextOverflow.ellipsis,
|
||||||
.toList(),
|
);
|
||||||
|
},
|
||||||
|
).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart';
|
import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart';
|
||||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../editable_cell_skeleton/relation.dart';
|
import '../editable_cell_skeleton/relation.dart';
|
||||||
@ -26,36 +29,43 @@ class DesktopRowDetailRelationCellSkin extends IEditableRelationCellSkin {
|
|||||||
popupBuilder: (context) {
|
popupBuilder: (context) {
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: bloc,
|
value: bloc,
|
||||||
child: BlocBuilder<RelationCellBloc, RelationCellState>(
|
child: RelationCellEditor(
|
||||||
builder: (context, state) => RelationCellEditor(
|
selectedRowIds: state.rows.map((row) => row.rowId).toList(),
|
||||||
selectedRowIds: state.rows.map((row) => row.rowId).toList(),
|
|
||||||
databaseId: state.relatedDatabaseId,
|
|
||||||
onSelectRow: (rowId) {
|
|
||||||
context
|
|
||||||
.read<RelationCellBloc>()
|
|
||||||
.add(RelationCellEvent.selectRow(rowId));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
child: Wrap(
|
child: state.rows.isEmpty
|
||||||
runSpacing: 4.0,
|
? _buildPlaceholder(context)
|
||||||
spacing: 4.0,
|
: _buildRows(context, state.rows),
|
||||||
children: state.rows
|
|
||||||
.map(
|
|
||||||
(row) => FlowyText.medium(
|
|
||||||
row.name,
|
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildPlaceholder(BuildContext context) {
|
||||||
|
return FlowyText(
|
||||||
|
LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRows(BuildContext context, List<RelatedRowDataPB> rows) {
|
||||||
|
return Wrap(
|
||||||
|
runSpacing: 4.0,
|
||||||
|
spacing: 4.0,
|
||||||
|
children: rows.map(
|
||||||
|
(row) {
|
||||||
|
final isEmpty = row.name.isEmpty;
|
||||||
|
return FlowyText.medium(
|
||||||
|
isEmpty ? LocaleKeys.grid_row_titlePlaceholder.tr() : row.name,
|
||||||
|
color: isEmpty ? Theme.of(context).hintColor : null,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/database/application/field/type_option/relation_type_option_cubit.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -13,38 +14,24 @@ import '../../application/cell/bloc/relation_row_search_bloc.dart';
|
|||||||
class RelationCellEditor extends StatelessWidget {
|
class RelationCellEditor extends StatelessWidget {
|
||||||
const RelationCellEditor({
|
const RelationCellEditor({
|
||||||
super.key,
|
super.key,
|
||||||
required this.databaseId,
|
|
||||||
required this.selectedRowIds,
|
required this.selectedRowIds,
|
||||||
required this.onSelectRow,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String databaseId;
|
|
||||||
final List<String> selectedRowIds;
|
final List<String> selectedRowIds;
|
||||||
final void Function(String rowId) onSelectRow;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (databaseId.isEmpty) {
|
return BlocBuilder<RelationCellBloc, RelationCellState>(
|
||||||
// no i18n here because UX needs thorough checking.
|
builder: (context, cellState) {
|
||||||
return const Center(
|
if (cellState.relatedDatabaseMeta == null) {
|
||||||
child: FlowyText(
|
return const _RelationCellEditorDatabaseList();
|
||||||
'''
|
}
|
||||||
No database has been selected,
|
|
||||||
please select one first in the field editor.
|
|
||||||
''',
|
|
||||||
maxLines: null,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return BlocProvider<RelationRowSearchBloc>(
|
return BlocProvider<RelationRowSearchBloc>(
|
||||||
create: (context) => RelationRowSearchBloc(
|
create: (context) => RelationRowSearchBloc(
|
||||||
databaseId: databaseId,
|
databaseId: cellState.relatedDatabaseMeta!.databaseId,
|
||||||
),
|
),
|
||||||
child: BlocBuilder<RelationCellBloc, RelationCellState>(
|
child: BlocBuilder<RelationRowSearchBloc, RelationRowSearchState>(
|
||||||
builder: (context, cellState) {
|
|
||||||
return BlocBuilder<RelationRowSearchBloc, RelationRowSearchState>(
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final children = state.filteredRows
|
final children = state.filteredRows
|
||||||
.map(
|
.map(
|
||||||
@ -68,7 +55,9 @@ please select one first in the field editor.
|
|||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
onTap: () => onSelectRow(row.rowId),
|
onTap: () => context
|
||||||
|
.read<RelationCellBloc>()
|
||||||
|
.add(RelationCellEvent.selectRow(row.rowId)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -78,7 +67,6 @@ please select one first in the field editor.
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const VSpace(6.0),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6.0) +
|
padding: const EdgeInsets.symmetric(horizontal: 6.0) +
|
||||||
GridSize.typeOptionContentInsets,
|
GridSize.typeOptionContentInsets,
|
||||||
@ -90,15 +78,13 @@ please select one first in the field editor.
|
|||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
const HSpace(2.0),
|
Padding(
|
||||||
FlowyButton(
|
padding: const EdgeInsets.symmetric(
|
||||||
useIntrinsicWidth: true,
|
|
||||||
margin: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 4,
|
horizontal: 4,
|
||||||
vertical: 2,
|
vertical: 2,
|
||||||
),
|
),
|
||||||
text: FlowyText.regular(
|
child: FlowyText.regular(
|
||||||
cellState.relatedDatabaseId,
|
cellState.relatedDatabaseMeta!.databaseName,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@ -106,10 +92,16 @@ please select one first in the field editor.
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||||
child: FlowyTextField(
|
child: FlowyTextField(
|
||||||
|
hintText: LocaleKeys
|
||||||
|
.grid_relation_rowSearchTextFieldPlaceholder
|
||||||
|
.tr(),
|
||||||
|
hintStyle: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(color: Theme.of(context).hintColor),
|
||||||
onChanged: (text) => context
|
onChanged: (text) => context
|
||||||
.read<RelationRowSearchBloc>()
|
.read<RelationRowSearchBloc>()
|
||||||
.add(RelationRowSearchEvent.updateFilter(text)),
|
.add(RelationRowSearchEvent.updateFilter(text)),
|
||||||
@ -140,6 +132,62 @@ please select one first in the field editor.
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RelationCellEditorDatabaseList extends StatelessWidget {
|
||||||
|
const _RelationCellEditorDatabaseList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => RelationDatabaseListCubit(),
|
||||||
|
child: BlocBuilder<RelationDatabaseListCubit, RelationDatabaseListState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(6, 6, 6, 0),
|
||||||
|
child: FlowyText(
|
||||||
|
LocaleKeys.grid_relation_noDatabaseSelected.tr(),
|
||||||
|
maxLines: null,
|
||||||
|
fontSize: 10,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: ListView.separated(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
separatorBuilder: (context, index) =>
|
||||||
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
|
itemCount: state.databaseMetas.length,
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final databaseMeta = state.databaseMetas[index];
|
||||||
|
return SizedBox(
|
||||||
|
height: GridSize.popoverItemHeight,
|
||||||
|
child: FlowyButton(
|
||||||
|
onTap: () => context.read<RelationCellBloc>().add(
|
||||||
|
RelationCellEvent.selectDatabaseId(
|
||||||
|
databaseMeta.databaseId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
text: FlowyText.medium(
|
||||||
|
databaseMeta.databaseName,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -736,6 +736,8 @@
|
|||||||
"relatedDatabasePlaceLabel": "Related Database",
|
"relatedDatabasePlaceLabel": "Related Database",
|
||||||
"relatedDatabasePlaceholder": "None",
|
"relatedDatabasePlaceholder": "None",
|
||||||
"inRelatedDatabase": "In",
|
"inRelatedDatabase": "In",
|
||||||
|
"rowSearchTextFieldPlaceholder": "Search",
|
||||||
|
"noDatabaseSelected": "No database selected, please select one first from the list below:",
|
||||||
"emptySearchResult": "No records found"
|
"emptySearchResult": "No records found"
|
||||||
},
|
},
|
||||||
"menuName": "Grid",
|
"menuName": "Grid",
|
||||||
|
@ -812,6 +812,30 @@ async fn update_relation_cell_test() {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(cell.row_ids.len(), 3);
|
assert_eq!(cell.row_ids.len(), 3);
|
||||||
|
|
||||||
|
// update the relation cell
|
||||||
|
let changeset = RelationCellChangesetPB {
|
||||||
|
view_id: grid_view.id.clone(),
|
||||||
|
cell_id: CellIdPB {
|
||||||
|
view_id: grid_view.id.clone(),
|
||||||
|
field_id: relation_field.id.clone(),
|
||||||
|
row_id: database.rows[0].id.clone(),
|
||||||
|
},
|
||||||
|
removed_row_ids: vec![
|
||||||
|
"row1rowid".to_string(),
|
||||||
|
"row3rowid".to_string(),
|
||||||
|
"row4rowid".to_string(),
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
test.update_relation_cell(changeset).await;
|
||||||
|
|
||||||
|
// get the cell
|
||||||
|
let cell = test
|
||||||
|
.get_relation_cell(&grid_view.id, &relation_field.id, &database.rows[0].id)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(cell.row_ids.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use collab::core::collab_state::SyncState;
|
use collab::core::collab_state::SyncState;
|
||||||
use collab_database::rows::RowId;
|
use collab_database::rows::RowId;
|
||||||
use collab_database::user::DatabaseMeta;
|
|
||||||
use collab_database::views::DatabaseLayout;
|
use collab_database::views::DatabaseLayout;
|
||||||
|
|
||||||
use flowy_derive::ProtoBuf;
|
use flowy_derive::ProtoBuf;
|
||||||
@ -203,23 +202,18 @@ impl TryInto<MoveGroupRowParams> for MoveGroupRowPayloadPB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, ProtoBuf)]
|
#[derive(Debug, Default, ProtoBuf)]
|
||||||
pub struct DatabaseDescriptionPB {
|
pub struct DatabaseMetaPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
pub database_id: String,
|
pub database_id: String,
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DatabaseMeta> for DatabaseDescriptionPB {
|
#[pb(index = 2)]
|
||||||
fn from(data: DatabaseMeta) -> Self {
|
pub inline_view_id: String,
|
||||||
Self {
|
|
||||||
database_id: data.database_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, ProtoBuf)]
|
#[derive(Debug, Default, ProtoBuf)]
|
||||||
pub struct RepeatedDatabaseDescriptionPB {
|
pub struct RepeatedDatabaseDescriptionPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
pub items: Vec<DatabaseDescriptionPB>,
|
pub items: Vec<DatabaseMetaPB>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||||
|
@ -3,6 +3,7 @@ use std::sync::{Arc, Weak};
|
|||||||
use collab_database::rows::RowId;
|
use collab_database::rows::RowId;
|
||||||
use lib_infra::box_any::BoxAny;
|
use lib_infra::box_any::BoxAny;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use lib_dispatch::prelude::{af_spawn, data_result_ok, AFPluginData, AFPluginState, DataResult};
|
use lib_dispatch::prelude::{af_spawn, data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||||
@ -741,7 +742,22 @@ pub(crate) async fn get_databases_handler(
|
|||||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||||
) -> DataResult<RepeatedDatabaseDescriptionPB, FlowyError> {
|
) -> DataResult<RepeatedDatabaseDescriptionPB, FlowyError> {
|
||||||
let manager = upgrade_manager(manager)?;
|
let manager = upgrade_manager(manager)?;
|
||||||
let data = manager.get_all_databases_description().await;
|
let metas = manager.get_all_databases_meta().await;
|
||||||
|
|
||||||
|
let mut items = Vec::with_capacity(metas.len());
|
||||||
|
for meta in metas {
|
||||||
|
match manager.get_database_inline_view_id(&meta.database_id).await {
|
||||||
|
Ok(view_id) => items.push(DatabaseMetaPB {
|
||||||
|
database_id: meta.database_id,
|
||||||
|
inline_view_id: view_id,
|
||||||
|
}),
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = RepeatedDatabaseDescriptionPB { items };
|
||||||
data_result_ok(data)
|
data_result_ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,10 @@ use std::sync::{Arc, Weak};
|
|||||||
|
|
||||||
use collab::core::collab::{CollabDocState, MutexCollab};
|
use collab::core::collab::{CollabDocState, MutexCollab};
|
||||||
use collab_database::blocks::BlockEvent;
|
use collab_database::blocks::BlockEvent;
|
||||||
use collab_database::database::{DatabaseData, MutexDatabase};
|
use collab_database::database::{get_inline_view_id, DatabaseData, MutexDatabase};
|
||||||
use collab_database::error::DatabaseError;
|
use collab_database::error::DatabaseError;
|
||||||
use collab_database::user::{
|
use collab_database::user::{
|
||||||
CollabDocStateByOid, CollabFuture, DatabaseCollabService, WorkspaceDatabase,
|
CollabDocStateByOid, CollabFuture, DatabaseCollabService, DatabaseMeta, WorkspaceDatabase,
|
||||||
};
|
};
|
||||||
use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLayout};
|
use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLayout};
|
||||||
use collab_entity::CollabType;
|
use collab_entity::CollabType;
|
||||||
@ -24,10 +24,7 @@ use flowy_error::{internal_error, FlowyError, FlowyResult};
|
|||||||
use lib_dispatch::prelude::af_spawn;
|
use lib_dispatch::prelude::af_spawn;
|
||||||
use lib_infra::priority_task::TaskDispatcher;
|
use lib_infra::priority_task::TaskDispatcher;
|
||||||
|
|
||||||
use crate::entities::{
|
use crate::entities::{DatabaseLayoutPB, DatabaseSnapshotPB, DidFetchRowPB};
|
||||||
DatabaseDescriptionPB, DatabaseLayoutPB, DatabaseSnapshotPB, DidFetchRowPB,
|
|
||||||
RepeatedDatabaseDescriptionPB,
|
|
||||||
};
|
|
||||||
use crate::notification::{send_notification, DatabaseNotification};
|
use crate::notification::{send_notification, DatabaseNotification};
|
||||||
use crate::services::database::DatabaseEditor;
|
use crate::services::database::DatabaseEditor;
|
||||||
use crate::services::database_view::DatabaseLayoutDepsResolver;
|
use crate::services::database_view::DatabaseLayoutDepsResolver;
|
||||||
@ -164,16 +161,27 @@ impl DatabaseManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_databases_description(&self) -> RepeatedDatabaseDescriptionPB {
|
pub async fn get_database_inline_view_id(&self, database_id: &str) -> FlowyResult<String> {
|
||||||
|
let wdb = self.get_workspace_database().await?;
|
||||||
|
let database_collab = wdb.get_database_collab(database_id).await.ok_or_else(|| {
|
||||||
|
FlowyError::record_not_found().with_context(format!("The database:{} not found", database_id))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let inline_view_id = get_inline_view_id(&database_collab.lock()).ok_or_else(|| {
|
||||||
|
FlowyError::record_not_found().with_context(format!(
|
||||||
|
"Can't find the inline view for database:{}",
|
||||||
|
database_id
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
Ok(inline_view_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_databases_meta(&self) -> Vec<DatabaseMeta> {
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
if let Ok(wdb) = self.get_workspace_database().await {
|
if let Ok(wdb) = self.get_workspace_database().await {
|
||||||
items = wdb
|
items = wdb.get_all_database_meta()
|
||||||
.get_all_database_meta()
|
|
||||||
.into_iter()
|
|
||||||
.map(DatabaseDescriptionPB::from)
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
RepeatedDatabaseDescriptionPB { items }
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn track_database(
|
pub async fn track_database(
|
||||||
|
@ -103,7 +103,7 @@ pub(crate) async fn create_orphan_view_handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(data, folder), err)]
|
#[tracing::instrument(level = "debug", skip(data, folder), err)]
|
||||||
pub(crate) async fn read_view_handler(
|
pub(crate) async fn get_view_handler(
|
||||||
data: AFPluginData<ViewIdPB>,
|
data: AFPluginData<ViewIdPB>,
|
||||||
folder: AFPluginState<Weak<FolderManager>>,
|
folder: AFPluginState<Weak<FolderManager>>,
|
||||||
) -> DataResult<ViewPB, FlowyError> {
|
) -> DataResult<ViewPB, FlowyError> {
|
||||||
|
@ -17,7 +17,7 @@ pub fn init(folder: Weak<FolderManager>) -> AFPlugin {
|
|||||||
.event(FolderEvent::ReadWorkspaceViews, get_workspace_views_handler)
|
.event(FolderEvent::ReadWorkspaceViews, get_workspace_views_handler)
|
||||||
.event(FolderEvent::CreateView, create_view_handler)
|
.event(FolderEvent::CreateView, create_view_handler)
|
||||||
.event(FolderEvent::CreateOrphanView, create_orphan_view_handler)
|
.event(FolderEvent::CreateOrphanView, create_orphan_view_handler)
|
||||||
.event(FolderEvent::GetView, read_view_handler)
|
.event(FolderEvent::GetView, get_view_handler)
|
||||||
.event(FolderEvent::UpdateView, update_view_handler)
|
.event(FolderEvent::UpdateView, update_view_handler)
|
||||||
.event(FolderEvent::DeleteView, delete_view_handler)
|
.event(FolderEvent::DeleteView, delete_view_handler)
|
||||||
.event(FolderEvent::DuplicateView, duplicate_view_handler)
|
.event(FolderEvent::DuplicateView, duplicate_view_handler)
|
||||||
|
@ -164,8 +164,8 @@ where
|
|||||||
let new_object_id = &new_user_session.user_workspace.workspace_database_object_id;
|
let new_object_id = &new_user_session.user_workspace.workspace_database_object_id;
|
||||||
|
|
||||||
let array = DatabaseMetaList::from_collab(&database_with_views_collab);
|
let array = DatabaseMetaList::from_collab(&database_with_views_collab);
|
||||||
for database_metas in array.get_all_database_meta() {
|
for database_meta in array.get_all_database_meta() {
|
||||||
array.update_database(&database_metas.database_id, |update| {
|
array.update_database(&database_meta.database_id, |update| {
|
||||||
let new_linked_views = update
|
let new_linked_views = update
|
||||||
.linked_views
|
.linked_views
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -75,7 +75,7 @@ pub async fn sync_supabase_user_data_to_cloud(
|
|||||||
fn sync_view(
|
fn sync_view(
|
||||||
uid: i64,
|
uid: i64,
|
||||||
folder: Arc<MutexFolder>,
|
folder: Arc<MutexFolder>,
|
||||||
database_records: Vec<Arc<DatabaseMeta>>,
|
database_metas: Vec<Arc<DatabaseMeta>>,
|
||||||
workspace_id: String,
|
workspace_id: String,
|
||||||
device_id: String,
|
device_id: String,
|
||||||
view: Arc<View>,
|
view: Arc<View>,
|
||||||
@ -84,7 +84,7 @@ fn sync_view(
|
|||||||
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + Sync>> {
|
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + Sync>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let collab_type = collab_type_from_view_layout(&view.layout);
|
let collab_type = collab_type_from_view_layout(&view.layout);
|
||||||
let object_id = object_id_from_view(&view, &database_records)?;
|
let object_id = object_id_from_view(&view, &database_metas)?;
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"sync view: {:?}:{} with object_id: {}",
|
"sync view: {:?}:{} with object_id: {}",
|
||||||
view.layout,
|
view.layout,
|
||||||
@ -180,7 +180,7 @@ fn sync_view(
|
|||||||
if let Err(err) = Box::pin(sync_view(
|
if let Err(err) = Box::pin(sync_view(
|
||||||
uid,
|
uid,
|
||||||
folder.clone(),
|
folder.clone(),
|
||||||
database_records.clone(),
|
database_metas.clone(),
|
||||||
workspace_id.clone(),
|
workspace_id.clone(),
|
||||||
device_id.to_string(),
|
device_id.to_string(),
|
||||||
child_view,
|
child_view,
|
||||||
|
@ -281,10 +281,10 @@ where
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let array = DatabaseMetaList::from_collab(&database_view_tracker_collab);
|
let array = DatabaseMetaList::from_collab(&database_view_tracker_collab);
|
||||||
for database_metas in array.get_all_database_meta() {
|
for database_meta in array.get_all_database_meta() {
|
||||||
database_view_ids_by_database_id.insert(
|
database_view_ids_by_database_id.insert(
|
||||||
old_to_new_id_map.renew_id(&database_metas.database_id),
|
old_to_new_id_map.renew_id(&database_meta.database_id),
|
||||||
database_metas
|
database_meta
|
||||||
.linked_views
|
.linked_views
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|view_id| old_to_new_id_map.renew_id(&view_id))
|
.map(|view_id| old_to_new_id_map.renew_id(&view_id))
|
||||||
|
Loading…
Reference in New Issue
Block a user