feat: switch database layout (#2677)

* chore: rename update at and create at

* chore: support switching view layout

* chore: implement ui

* chore: update layout type

* refactor: board/calendar/grid setting button

* chore: update UI after switch to other layout type

* fix: no date display in calendar

* chore: update patch

* chore: fix create ref view in document

* chore: fix flutter analyze

* ci: warnings

* chore: rename board and grid keys

* fix: calendar row event update

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
Nathan.fooo 2023-06-01 20:23:27 +08:00 committed by GitHub
parent 2ef72f3203
commit 33e0f8d26d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
98 changed files with 1600 additions and 1122 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 6C3 3.79086 4.79086 2 7 2H17C19.2091 2 21 3.79086 21 6C21 8.20914 19.2091 10 17 10H7C4.79086 10 3 8.20914 3 6Z" stroke="#000000" stroke-width="2"/>
<path d="M3 16C3 14.8954 3.89543 14 5 14H8C9.10457 14 10 14.8954 10 16V19C10 20.1046 9.10457 21 8 21H5C3.89543 21 3 20.1046 3 19V16Z" stroke="#000000" stroke-width="2"/>
<path d="M14 17.5C14 15.567 15.567 14 17.5 14C19.433 14 21 15.567 21 17.5C21 19.433 19.433 21 17.5 21C15.567 21 14 19.433 14 17.5Z" stroke="#000000" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 725 B

View File

@ -232,7 +232,8 @@
"deleteFilter": "Delete filter",
"filterBy": "Filter by...",
"typeAValue": "Type a value...",
"layout": "Layout"
"layout": "Layout",
"databaseLayout": "Layout"
},
"textFilter": {
"contains": "Contains",
@ -439,7 +440,8 @@
"firstDayOfWeek": "Start week on",
"layoutDateField": "Layout calendar by",
"noDateTitle": "No Date",
"noDateHint": "Unscheduled events will show up here"
"noDateHint": "Unscheduled events will show up here",
"clickToAdd": "Click to add to the calendar"
}
}
}

View File

@ -28,16 +28,17 @@ class BlankPluginConfig implements PluginConfig {
class BlankPagePlugin extends Plugin {
@override
PluginDisplay get display => BlankPagePluginDisplay();
PluginWidgetBuilder get widgetBuilder => BlankPagePluginWidgetBuilder();
@override
PluginId get id => "BlankStack";
@override
PluginType get ty => PluginType.blank;
PluginType get pluginType => PluginType.blank;
}
class BlankPagePluginDisplay extends PluginDisplay with NavigationItem {
class BlankPagePluginWidgetBuilder extends PluginWidgetBuilder
with NavigationItem {
@override
Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr());

View File

@ -39,18 +39,18 @@ class GroupCallbacks {
});
}
class LayoutCallbacks {
final void Function(LayoutSettingPB) onLayoutChanged;
final void Function(LayoutSettingPB) onLoadLayout;
class DatabaseLayoutSettingCallbacks {
final void Function(DatabaseLayoutSettingPB) onLayoutChanged;
final void Function(DatabaseLayoutSettingPB) onLoadLayout;
LayoutCallbacks({
DatabaseLayoutSettingCallbacks({
required this.onLayoutChanged,
required this.onLoadLayout,
});
}
class CalendarLayoutCallbacks {
final void Function(LayoutSettingPB) onCalendarLayoutChanged;
final void Function(DatabaseLayoutSettingPB) onCalendarLayoutChanged;
CalendarLayoutCallbacks({required this.onCalendarLayoutChanged});
}
@ -59,14 +59,14 @@ class DatabaseCallbacks {
OnDatabaseChanged? onDatabaseChanged;
OnFieldsChanged? onFieldsChanged;
OnFiltersChanged? onFiltersChanged;
OnRowsChanged? onRowsChanged;
OnNumOfRowsChanged? onNumOfRowsChanged;
OnRowsDeleted? onRowsDeleted;
OnRowsUpdated? onRowsUpdated;
OnRowsCreated? onRowsCreated;
DatabaseCallbacks({
this.onDatabaseChanged,
this.onRowsChanged,
this.onNumOfRowsChanged,
this.onFieldsChanged,
this.onFiltersChanged,
this.onRowsUpdated,
@ -79,13 +79,13 @@ class DatabaseController {
final String viewId;
final DatabaseViewBackendService _databaseViewBackendSvc;
final FieldController fieldController;
DatabaseLayoutPB? databaseLayout;
late DatabaseViewCache _viewCache;
final DatabaseLayoutPB layoutType;
// Callbacks
DatabaseCallbacks? _databaseCallbacks;
GroupCallbacks? _groupCallbacks;
LayoutCallbacks? _layoutCallbacks;
DatabaseLayoutSettingCallbacks? _layoutCallbacks;
CalendarLayoutCallbacks? _calendarLayoutCallbacks;
// Getters
@ -93,15 +93,15 @@ class DatabaseController {
// Listener
final DatabaseGroupListener groupListener;
final DatabaseLayoutListener layoutListener;
final DatabaseLayoutSettingListener layoutListener;
final DatabaseCalendarLayoutListener calendarLayoutListener;
DatabaseController({required ViewPB view, required this.layoutType})
DatabaseController({required ViewPB view})
: viewId = view.id,
_databaseViewBackendSvc = DatabaseViewBackendService(viewId: view.id),
fieldController = FieldController(viewId: view.id),
groupListener = DatabaseGroupListener(view.id),
layoutListener = DatabaseLayoutListener(view.id),
layoutListener = DatabaseLayoutSettingListener(view.id),
calendarLayoutListener = DatabaseCalendarLayoutListener(view.id) {
_viewCache = DatabaseViewCache(
viewId: viewId,
@ -111,14 +111,11 @@ class DatabaseController {
_listenOnFieldsChanged();
_listenOnGroupChanged();
_listenOnLayoutChanged();
if (layoutType == DatabaseLayoutPB.Calendar) {
_listenOnCalendarLayoutChanged();
}
}
void setListener({
DatabaseCallbacks? onDatabaseChanged,
LayoutCallbacks? onLayoutChanged,
DatabaseLayoutSettingCallbacks? onLayoutChanged,
GroupCallbacks? onGroupChanged,
CalendarLayoutCallbacks? onCalendarLayoutChanged,
}) {
@ -132,6 +129,12 @@ class DatabaseController {
return _databaseViewBackendSvc.openGrid().then((result) {
return result.fold(
(database) async {
databaseLayout = database.layoutType;
if (databaseLayout == DatabaseLayoutPB.Calendar) {
_listenOnCalendarLayoutChanged();
}
_databaseCallbacks?.onDatabaseChanged?.call(database);
_viewCache.rowCache.setInitialRows(database.rows);
return await fieldController
@ -242,20 +245,20 @@ class DatabaseController {
}
Future<void> _loadLayoutSetting() async {
_databaseViewBackendSvc.getLayoutSetting(layoutType).then((result) {
if (databaseLayout != null) {
_databaseViewBackendSvc.getLayoutSetting(databaseLayout!).then((result) {
result.fold(
(l) {
_layoutCallbacks?.onLoadLayout(l);
},
(l) => _layoutCallbacks?.onLoadLayout(l),
(r) => Log.error(r),
);
});
}
}
void _listenOnRowsChanged() {
final callbacks = DatabaseViewCallbacks(
onRowsChanged: (rows, rowByRowId, reason) {
_databaseCallbacks?.onRowsChanged?.call(rows, rowByRowId, reason);
onNumOfRowsChanged: (rows, rowByRowId, reason) {
_databaseCallbacks?.onNumOfRowsChanged?.call(rows, rowByRowId, reason);
},
onRowsDeleted: (ids) {
_databaseCallbacks?.onRowsDeleted?.call(ids);

View File

@ -97,10 +97,10 @@ class DatabaseViewBackendService {
});
}
Future<Either<LayoutSettingPB, FlowyError>> getLayoutSetting(
Future<Either<DatabaseLayoutSettingPB, FlowyError>> getLayoutSetting(
DatabaseLayoutPB layoutType,
) {
final payload = DatabaseLayoutIdPB.create()
final payload = DatabaseLayoutMetaPB.create()
..viewId = viewId
..layout = layoutType;
return DatabaseEventGetLayoutSetting(payload).send();

View File

@ -15,7 +15,7 @@ typedef OnDatabaseChanged = void Function(DatabasePB);
typedef OnRowsCreated = void Function(List<RowId> ids);
typedef OnRowsUpdated = void Function(List<RowId> ids);
typedef OnRowsDeleted = void Function(List<RowId> ids);
typedef OnRowsChanged = void Function(
typedef OnNumOfRowsChanged = void Function(
UnmodifiableListView<RowInfo> rows,
UnmodifiableMapView<RowId, RowInfo> rowByRowId,
RowsChangedReason reason,

View File

@ -6,7 +6,7 @@ import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:dartz/dartz.dart';
typedef NewLayoutFieldValue = Either<LayoutSettingPB, FlowyError>;
typedef NewLayoutFieldValue = Either<DatabaseLayoutSettingPB, FlowyError>;
class DatabaseCalendarLayoutListener {
final String viewId;
@ -33,7 +33,7 @@ class DatabaseCalendarLayoutListener {
case DatabaseNotification.DidSetNewLayoutField:
result.fold(
(payload) => _newLayoutFieldNotifier?.value =
left(LayoutSettingPB.fromBuffer(payload)),
left(DatabaseLayoutSettingPB.fromBuffer(payload)),
(error) => _newLayoutFieldNotifier?.value = right(error),
);
break;

View File

@ -0,0 +1,56 @@
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'layout_service.dart';
part 'layout_bloc.freezed.dart';
class DatabaseLayoutBloc
extends Bloc<DatabaseLayoutEvent, DatabaseLayoutState> {
final DatabaseLayoutBackendService layoutService;
DatabaseLayoutBloc({
required String viewId,
required DatabaseLayoutPB databaseLayout,
}) : layoutService = DatabaseLayoutBackendService(viewId),
super(DatabaseLayoutState.initial(viewId, databaseLayout)) {
on<DatabaseLayoutEvent>(
(event, emit) async {
event.when(
initial: () {},
updateLayout: (DatabaseLayoutPB layout) {
layoutService.updateLayout(
fieldId: viewId,
layout: layout,
);
emit(state.copyWith(databaseLayout: layout));
},
);
},
);
}
}
@freezed
class DatabaseLayoutEvent with _$DatabaseLayoutEvent {
const factory DatabaseLayoutEvent.initial() = _Initial;
const factory DatabaseLayoutEvent.updateLayout(DatabaseLayoutPB layout) =
_UpdateLayout;
}
@freezed
class DatabaseLayoutState with _$DatabaseLayoutState {
const factory DatabaseLayoutState({
required String viewId,
required DatabaseLayoutPB databaseLayout,
}) = _DatabaseLayoutState;
factory DatabaseLayoutState.initial(
String viewId,
DatabaseLayoutPB databaseLayout,
) =>
DatabaseLayoutState(
viewId: viewId,
databaseLayout: databaseLayout,
);
}

View File

@ -0,0 +1,50 @@
import 'dart:typed_data';
import 'package:appflowy/core/notification/grid_notification.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:dartz/dartz.dart';
/// Listener for database layout changes.
class DatabaseLayoutListener {
final String viewId;
PublishNotifier<Either<DatabaseLayoutPB, FlowyError>>? _layoutNotifier =
PublishNotifier();
DatabaseNotificationListener? _listener;
DatabaseLayoutListener(this.viewId);
void start({
required void Function(Either<DatabaseLayoutPB, FlowyError>)
onLayoutChanged,
}) {
_layoutNotifier?.addPublishListener(onLayoutChanged);
_listener = DatabaseNotificationListener(
objectId: viewId,
handler: _handler,
);
}
void _handler(
DatabaseNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case DatabaseNotification.DidUpdateDatabaseLayout:
result.fold(
(payload) => _layoutNotifier?.value =
left(DatabaseLayoutMetaPB.fromBuffer(payload).layout),
(error) => _layoutNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_layoutNotifier?.dispose();
_layoutNotifier = null;
}
}

View File

@ -0,0 +1,35 @@
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:dartz/dartz.dart';
class DatabaseLayoutBackendService {
final String viewId;
DatabaseLayoutBackendService(this.viewId);
Future<Either<ViewPB, FlowyError>> updateLayout({
required String fieldId,
required DatabaseLayoutPB layout,
}) {
var payload = UpdateViewPayloadPB.create()
..viewId = viewId
..layout = _viewLayoutFromDatabaseLayout(layout);
return FolderEventUpdateView(payload).send();
}
}
ViewLayoutPB _viewLayoutFromDatabaseLayout(DatabaseLayoutPB databaseLayout) {
switch (databaseLayout) {
case DatabaseLayoutPB.Board:
return ViewLayoutPB.Board;
case DatabaseLayoutPB.Calendar:
return ViewLayoutPB.Calendar;
case DatabaseLayoutPB.Grid:
return ViewLayoutPB.Grid;
default:
throw UnimplementedError;
}
}

View File

@ -8,15 +8,15 @@ import 'package:dartz/dartz.dart';
typedef LayoutSettingsValue<T> = Either<T, FlowyError>;
class DatabaseLayoutListener {
class DatabaseLayoutSettingListener {
final String viewId;
PublishNotifier<LayoutSettingsValue<LayoutSettingPB>>? _settingNotifier =
PublishNotifier();
PublishNotifier<LayoutSettingsValue<DatabaseLayoutSettingPB>>?
_settingNotifier = PublishNotifier();
DatabaseNotificationListener? _listener;
DatabaseLayoutListener(this.viewId);
DatabaseLayoutSettingListener(this.viewId);
void start({
required void Function(LayoutSettingsValue<LayoutSettingPB>)
required void Function(LayoutSettingsValue<DatabaseLayoutSettingPB>)
onLayoutChanged,
}) {
_settingNotifier?.addPublishListener(onLayoutChanged);
@ -34,7 +34,7 @@ class DatabaseLayoutListener {
case DatabaseNotification.DidUpdateLayoutSettings:
result.fold(
(payload) => _settingNotifier?.value =
left(LayoutSettingPB.fromBuffer(payload)),
left(DatabaseLayoutSettingPB.fromBuffer(payload)),
(error) => _settingNotifier?.value = right(error),
);
break;

View File

@ -1,3 +1,5 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dartz/dartz.dart';
@ -40,7 +42,31 @@ class DatabaseSettingState with _$DatabaseSettingState {
}
enum DatabaseSettingAction {
showFilters,
sortBy,
showProperties,
showLayout,
showGroup,
}
extension DatabaseSettingActionExtension on DatabaseSettingAction {
String iconName() {
switch (this) {
case DatabaseSettingAction.showProperties:
return 'grid/setting/properties';
case DatabaseSettingAction.showLayout:
return 'grid/setting/database_layout';
case DatabaseSettingAction.showGroup:
return 'grid/setting/group';
}
}
String title() {
switch (this) {
case DatabaseSettingAction.showProperties:
return LocaleKeys.grid_settings_Properties.tr();
case DatabaseSettingAction.showLayout:
return LocaleKeys.grid_settings_databaseLayout.tr();
case DatabaseSettingAction.showGroup:
return LocaleKeys.grid_settings_group.tr();
}
}
}

View File

@ -9,21 +9,21 @@ import 'view_listener.dart';
class DatabaseViewCallbacks {
/// Will get called when number of rows were changed that includes
/// update/delete/insert rows. The [onRowsChanged] will return all
/// update/delete/insert rows. The [onNumOfRowsChanged] will return all
/// the rows of the current database
final OnRowsChanged? onRowsChanged;
final OnNumOfRowsChanged? onNumOfRowsChanged;
// Will get called when creating new rows
final OnRowsCreated? onRowsCreated;
/// Will get called when number of rows were updated
/// Will get called when rows were updated
final OnRowsUpdated? onRowsUpdated;
/// Will get called when number of rows were deleted
final OnRowsDeleted? onRowsDeleted;
const DatabaseViewCallbacks({
this.onRowsChanged,
this.onNumOfRowsChanged,
this.onRowsCreated,
this.onRowsUpdated,
this.onRowsDeleted,
@ -101,7 +101,7 @@ class DatabaseViewCache {
);
_rowCache.onRowsChanged(
(reason) => _callbacks?.onRowsChanged?.call(
(reason) => _callbacks?.onNumOfRowsChanged?.call(
rowInfos,
_rowCache.rowByRowId,
reason,

View File

@ -20,18 +20,17 @@ import 'group_controller.dart';
part 'board_bloc.freezed.dart';
class BoardBloc extends Bloc<BoardEvent, BoardState> {
final DatabaseController _databaseController;
final DatabaseController databaseController;
late final AppFlowyBoardController boardController;
final LinkedHashMap<String, GroupController> groupControllers =
LinkedHashMap();
FieldController get fieldController => _databaseController.fieldController;
String get viewId => _databaseController.viewId;
FieldController get fieldController => databaseController.fieldController;
String get viewId => databaseController.viewId;
BoardBloc({required ViewPB view})
: _databaseController = DatabaseController(
: databaseController = DatabaseController(
view: view,
layoutType: DatabaseLayoutPB.Board,
),
super(BoardState.initial(view.id)) {
boardController = AppFlowyBoardController(
@ -41,7 +40,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
toGroupId,
toIndex,
) {
_databaseController.moveGroup(
databaseController.moveGroup(
fromGroupId: fromGroupId,
toGroupId: toGroupId,
);
@ -54,7 +53,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
if (fromRow != null) {
_databaseController.moveGroupRow(
databaseController.moveGroupRow(
fromRow: fromRow,
toRow: toRow,
groupId: groupId,
@ -70,7 +69,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex);
final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex);
if (fromRow != null) {
_databaseController.moveGroupRow(
databaseController.moveGroupRow(
fromRow: fromRow,
toRow: toRow,
groupId: toGroupId,
@ -88,7 +87,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
},
createBottomRow: (groupId) async {
final startRowId = groupControllers[groupId]?.lastRow()?.id;
final result = await _databaseController.createRow(
final result = await databaseController.createRow(
groupId: groupId,
startRowId: startRowId,
);
@ -98,8 +97,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
);
},
createHeaderRow: (String groupId) async {
final result =
await _databaseController.createRow(groupId: groupId);
final result = await databaseController.createRow(groupId: groupId);
result.fold(
(_) {},
(err) => Log.error(err),
@ -170,7 +168,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
@override
Future<void> close() async {
await _databaseController.dispose();
await databaseController.dispose();
for (final controller in groupControllers.values) {
controller.dispose();
}
@ -198,7 +196,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
}
RowCache? getRowCache() {
return _databaseController.rowCache;
return databaseController.rowCache;
}
void _startListening() {
@ -237,7 +235,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
},
);
_databaseController.setListener(
databaseController.setListener(
onDatabaseChanged: onDatabaseChanged,
onGroupChanged: onGroupChanged,
);
@ -256,7 +254,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
}
Future<void> _openGrid(Emitter<BoardState> emit) async {
final result = await _databaseController.open();
final result = await databaseController.open();
result.fold(
(grid) => emit(
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),

View File

@ -49,18 +49,19 @@ class BoardPlugin extends Plugin {
notifier = ViewPluginNotifier(view: view);
@override
PluginDisplay get display => GridPluginDisplay(notifier: notifier);
PluginWidgetBuilder get widgetBuilder =>
BoardPluginWidgetBuilder(notifier: notifier);
@override
PluginId get id => notifier.view.id;
@override
PluginType get ty => _pluginType;
PluginType get pluginType => _pluginType;
}
class GridPluginDisplay extends PluginDisplay {
class BoardPluginWidgetBuilder extends PluginWidgetBuilder {
final ViewPluginNotifier notifier;
GridPluginDisplay({required this.notifier, Key? key});
BoardPluginWidgetBuilder({required this.notifier, Key? key});
ViewPB get view => notifier.view;

View File

@ -340,15 +340,7 @@ class _ToolbarBlocAdaptor extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<BoardBloc, BoardState>(
builder: (context, state) {
final bloc = context.read<BoardBloc>();
final toolbarContext = BoardToolbarContext(
viewId: bloc.viewId,
fieldController: bloc.fieldController,
);
return BoardToolbar(toolbarContext: toolbarContext);
},
builder: (context, state) => const BoardToolbar(),
);
}
}

View File

@ -1,192 +0,0 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/board/application/toolbar/board_setting_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_group.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import 'board_toolbar.dart';
class BoardSettingContext {
final String viewId;
final FieldController fieldController;
BoardSettingContext({
required this.viewId,
required this.fieldController,
});
factory BoardSettingContext.from(BoardToolbarContext toolbarContext) =>
BoardSettingContext(
viewId: toolbarContext.viewId,
fieldController: toolbarContext.fieldController,
);
}
class BoardSettingList extends StatelessWidget {
final BoardSettingContext settingContext;
final Function(BoardSettingAction, BoardSettingContext) onAction;
const BoardSettingList({
required this.settingContext,
required this.onAction,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => BoardSettingBloc(viewId: settingContext.viewId),
child: BlocListener<BoardSettingBloc, BoardSettingState>(
listenWhen: (previous, current) =>
previous.selectedAction != current.selectedAction,
listener: (context, state) {
state.selectedAction.foldLeft(null, (_, action) {
onAction(action, settingContext);
});
},
child: BlocBuilder<BoardSettingBloc, BoardSettingState>(
builder: (context, state) {
return _renderList();
},
),
),
);
}
Widget _renderList() {
final cells = BoardSettingAction.values.map((action) {
return _SettingItem(action: action);
}).toList();
return SizedBox(
width: 140,
child: ListView.separated(
shrinkWrap: true,
controller: ScrollController(),
itemCount: cells.length,
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
physics: StyledScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return cells[index];
},
),
);
}
}
class _SettingItem extends StatelessWidget {
final BoardSettingAction action;
const _SettingItem({
required this.action,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final isSelected = context
.read<BoardSettingBloc>()
.state
.selectedAction
.foldLeft(false, (_, selectedAction) => selectedAction == action);
return SizedBox(
height: 30,
child: FlowyButton(
hoverColor: AFThemeExtension.of(context).lightGreyHover,
isSelected: isSelected,
text: FlowyText.medium(
action.title(),
color: AFThemeExtension.of(context).textColor,
),
onTap: () {
context
.read<BoardSettingBloc>()
.add(BoardSettingEvent.performAction(action));
},
leftIcon: svgWidget(
action.iconName(),
color: Theme.of(context).iconTheme.color,
),
),
);
}
}
extension _GridSettingExtension on BoardSettingAction {
String iconName() {
switch (this) {
case BoardSettingAction.properties:
return 'grid/setting/properties';
case BoardSettingAction.groups:
return 'grid/setting/group';
}
}
String title() {
switch (this) {
case BoardSettingAction.properties:
return LocaleKeys.grid_settings_Properties.tr();
case BoardSettingAction.groups:
return LocaleKeys.grid_settings_group.tr();
}
}
}
class BoardSettingListPopover extends StatefulWidget {
final PopoverController popoverController;
final BoardSettingContext settingContext;
const BoardSettingListPopover({
Key? key,
required this.popoverController,
required this.settingContext,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _BoardSettingListPopoverState();
}
class _BoardSettingListPopoverState extends State<BoardSettingListPopover> {
BoardSettingAction? _action;
@override
Widget build(BuildContext context) {
if (_action != null) {
switch (_action!) {
case BoardSettingAction.groups:
return GridGroupList(
viewId: widget.settingContext.viewId,
fieldController: widget.settingContext.fieldController,
onDismissed: () {
widget.popoverController.close();
},
);
case BoardSettingAction.properties:
return GridPropertyList(
viewId: widget.settingContext.viewId,
fieldController: widget.settingContext.fieldController,
);
}
}
return BoardSettingList(
settingContext: widget.settingContext,
onAction: (action, settingContext) {
setState(() => _action = action);
},
).padding(all: 6.0);
}
}

View File

@ -1,28 +1,10 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
import 'package:flutter/material.dart';
import 'board_setting.dart';
class BoardToolbarContext {
final String viewId;
final FieldController fieldController;
BoardToolbarContext({
required this.viewId,
required this.fieldController,
});
}
import 'package:flutter_bloc/flutter_bloc.dart';
class BoardToolbar extends StatelessWidget {
final BoardToolbarContext toolbarContext;
const BoardToolbar({
required this.toolbarContext,
Key? key,
}) : super(key: key);
@ -33,58 +15,11 @@ class BoardToolbar extends StatelessWidget {
child: Row(
children: [
const Spacer(),
_SettingButton(
settingContext: BoardSettingContext.from(toolbarContext),
SettingButton(
databaseController: context.read<BoardBloc>().databaseController,
),
],
),
);
}
}
class _SettingButton extends StatefulWidget {
final BoardSettingContext settingContext;
const _SettingButton({required this.settingContext, Key? key})
: super(key: key);
@override
State<_SettingButton> createState() => _SettingButtonState();
}
class _SettingButtonState extends State<_SettingButton> {
late PopoverController popoverController;
@override
void initState() {
popoverController = PopoverController();
super.initState();
}
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
controller: popoverController,
direction: PopoverDirection.leftWithTopAligned,
offset: const Offset(-8, 0),
triggerActions: PopoverTriggerFlags.none,
constraints: BoxConstraints.loose(const Size(260, 400)),
margin: EdgeInsets.zero,
child: FlowyTextButton(
LocaleKeys.settings_title.tr(),
fontColor: AFThemeExtension.of(context).textColor,
fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.typeOptionContentInsets,
onPressed: () {
popoverController.show();
},
),
popupBuilder: (BuildContext popoverContext) {
return BoardSettingListPopover(
settingContext: widget.settingContext,
popoverController: popoverController,
);
},
);
}
}

View File

@ -18,20 +18,17 @@ import '../../application/row/row_cache.dart';
part 'calendar_bloc.freezed.dart';
class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
final DatabaseController _databaseController;
final DatabaseController databaseController;
Map<String, FieldInfo> fieldInfoByFieldId = {};
// Getters
String get viewId => _databaseController.viewId;
FieldController get fieldController => _databaseController.fieldController;
CellCache get cellCache => _databaseController.rowCache.cellCache;
RowCache get rowCache => _databaseController.rowCache;
String get viewId => databaseController.viewId;
FieldController get fieldController => databaseController.fieldController;
CellCache get cellCache => databaseController.rowCache.cellCache;
RowCache get rowCache => databaseController.rowCache;
CalendarBloc({required ViewPB view})
: _databaseController = DatabaseController(
view: view,
layoutType: DatabaseLayoutPB.Calendar,
),
: databaseController = DatabaseController(view: view),
super(CalendarState.initial()) {
on<CalendarEvent>(
(event, emit) async {
@ -110,12 +107,12 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
@override
Future<void> close() async {
await _databaseController.dispose();
await databaseController.dispose();
return super.close();
}
FieldInfo? _getCalendarFieldInfo(String fieldId) {
final fieldInfos = _databaseController.fieldController.fieldInfos;
final fieldInfos = databaseController.fieldController.fieldInfos;
final index = fieldInfos.indexWhere(
(element) => element.field.id == fieldId,
);
@ -127,7 +124,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
}
FieldInfo? _getTitleFieldInfo() {
final fieldInfos = _databaseController.fieldController.fieldInfos;
final fieldInfos = databaseController.fieldController.fieldInfos;
final index = fieldInfos.indexWhere(
(element) => element.field.isPrimary,
);
@ -139,7 +136,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
}
Future<void> _openDatabase(Emitter<CalendarState> emit) async {
final result = await _databaseController.open();
final result = await databaseController.open();
result.fold(
(database) => emit(
state.copyWith(loadingState: DatabaseLoadingState.finish(left(unit))),
@ -157,7 +154,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
final dateField = _getCalendarFieldInfo(settings.fieldId);
final titleField = _getTitleFieldInfo();
if (dateField != null && titleField != null) {
final newRow = await _databaseController.createRow(
final newRow = await databaseController.createRow(
withCells: (builder) {
builder.insertDate(dateField, date);
builder.insertText(titleField, title);
@ -210,7 +207,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
Future<void> _updateCalendarLayoutSetting(
CalendarLayoutSettingPB layoutSetting,
) async {
return _databaseController.updateCalenderLayoutSetting(layoutSetting);
return databaseController.updateCalenderLayoutSetting(layoutSetting);
}
Future<CalendarEventData<CalendarDayEvent>?> _loadEvent(RowId rowId) async {
@ -331,7 +328,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
},
);
final onLayoutChanged = LayoutCallbacks(
final onLayoutChanged = DatabaseLayoutSettingCallbacks(
onLayoutChanged: _didReceiveLayoutSetting,
onLoadLayout: _didReceiveLayoutSetting,
);
@ -340,14 +337,14 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
onCalendarLayoutChanged: _didReceiveNewLayoutField,
);
_databaseController.setListener(
databaseController.setListener(
onDatabaseChanged: onDatabaseChanged,
onLayoutChanged: onLayoutChanged,
onCalendarLayoutChanged: onCalendarLayoutFieldChanged,
);
}
void _didReceiveLayoutSetting(LayoutSettingPB layoutSetting) {
void _didReceiveLayoutSetting(DatabaseLayoutSettingPB layoutSetting) {
if (layoutSetting.hasCalendar()) {
if (isClosed) {
return;
@ -356,7 +353,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
}
}
void _didReceiveNewLayoutField(LayoutSettingPB layoutSetting) {
void _didReceiveNewLayoutField(DatabaseLayoutSettingPB layoutSetting) {
if (layoutSetting.hasCalendar()) {
if (isClosed) return;
add(CalendarEvent.didReceiveNewLayoutField(layoutSetting.calendar));

View File

@ -49,18 +49,19 @@ class CalendarPlugin extends Plugin {
notifier = ViewPluginNotifier(view: view);
@override
PluginDisplay get display => CalendarPluginDisplay(notifier: notifier);
PluginWidgetBuilder get widgetBuilder =>
CalendarPluginWidgetBuilder(notifier: notifier);
@override
PluginId get id => notifier.view.id;
@override
PluginType get ty => _pluginType;
PluginType get pluginType => _pluginType;
}
class CalendarPluginDisplay extends PluginDisplay {
class CalendarPluginWidgetBuilder extends PluginWidgetBuilder {
final ViewPluginNotifier notifier;
CalendarPluginDisplay({required this.notifier, Key? key});
CalendarPluginWidgetBuilder({required this.notifier, Key? key});
ViewPB get view => notifier.view;

View File

@ -2,7 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/calendar/application/calendar_setting_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart';
import 'package:appflowy/plugins/database_view/widgets/field/grid_property.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
@ -41,7 +41,7 @@ class CalendarSetting extends StatelessWidget {
state.selectedAction.foldLeft(null, (previous, action) => action);
switch (action) {
case CalendarSettingAction.properties:
return GridPropertyList(
return DatabasePropertyList(
viewId: settingContext.viewId,
fieldController: settingContext.fieldController,
);

View File

@ -1,6 +1,7 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:calendar_view/calendar_view.dart';
import 'package:easy_localization/easy_localization.dart';
@ -17,13 +18,15 @@ class CalendarToolbar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const SizedBox(
return SizedBox(
height: 40,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
_UnscheduleEventsButton(),
_SettingButton(),
const _UnscheduleEventsButton(),
SettingButton(
databaseController: context.read<CalendarBloc>().databaseController,
),
],
),
);
@ -115,12 +118,16 @@ class _UnscheduleEventsButtonState extends State<_UnscheduleEventsButton> {
),
popupBuilder: (context) {
final cells = <Widget>[
FlowyText.medium(
LocaleKeys.calendar_settings_noDateHint.tr(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
child: FlowyText.medium(
// LocaleKeys.calendar_settings_noDateHint.tr(),
LocaleKeys.calendar_settings_clickToAdd.tr(),
color: Theme.of(context).hintColor,
overflow: TextOverflow.ellipsis,
),
const VSpace(10),
),
const VSpace(6),
...unscheduledEvents.map(
(e) => _UnscheduledEventItem(
event: e,
@ -164,7 +171,11 @@ class _UnscheduledEventItem extends StatelessWidget {
return SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyButton(
text: FlowyText.medium(event.title),
text: FlowyText.medium(
event.title.isEmpty
? LocaleKeys.calendar_defaultNewCalendarTitle.tr()
: event.title,
),
onTap: onPressed,
),
);

View File

@ -0,0 +1,54 @@
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/application/view/view_listener.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import '../../workspace/presentation/home/home_stack.dart';
/// [DatabaseViewPlugin] is used to build the grid, calendar, and board.
/// It is a wrapper of the [Plugin] class. The underlying [Plugin] is
/// determined by the [ViewPB.pluginType] field.
///
class DatabaseViewPlugin extends Plugin {
final ViewListener _viewListener;
ViewPB _view;
Plugin _innerPlugin;
DatabaseViewPlugin({
required ViewPB view,
}) : _view = view,
_innerPlugin = _makeInnerPlugin(view),
_viewListener = ViewListener(view: view) {
_listenOnLayoutChanged();
}
@override
PluginId get id => _innerPlugin.id;
@override
PluginType get pluginType => _innerPlugin.pluginType;
@override
PluginWidgetBuilder get widgetBuilder => _innerPlugin.widgetBuilder;
void _listenOnLayoutChanged() {
_viewListener.start(
onViewUpdated: (result) {
result.fold(
(updatedView) {
if (_view.layout != updatedView.layout) {
_innerPlugin = _makeInnerPlugin(updatedView);
getIt<HomeStackManager>().setPlugin(_innerPlugin);
}
_view = updatedView;
},
(r) => null,
);
},
);
}
}
Plugin _makeInnerPlugin(ViewPB view) {
return makePlugin(pluginType: view.pluginType, data: view);
}

View File

@ -87,7 +87,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
add(GridEvent.didReceiveGridUpdate(database));
}
},
onRowsChanged: (rowInfos, _, reason) {
onNumOfRowsChanged: (rowInfos, _, reason) {
if (!isClosed) {
add(GridEvent.didReceiveRowUpdate(rowInfos, reason));
}

View File

@ -49,20 +49,21 @@ class GridPlugin extends Plugin {
notifier = ViewPluginNotifier(view: view);
@override
PluginDisplay get display => GridPluginDisplay(notifier: notifier);
PluginWidgetBuilder get widgetBuilder =>
GridPluginWidgetBuilder(notifier: notifier);
@override
PluginId get id => notifier.view.id;
@override
PluginType get ty => _pluginType;
PluginType get pluginType => _pluginType;
}
class GridPluginDisplay extends PluginDisplay {
class GridPluginWidgetBuilder extends PluginWidgetBuilder {
final ViewPluginNotifier notifier;
ViewPB get view => notifier.view;
GridPluginDisplay({required this.notifier, Key? key});
GridPluginWidgetBuilder({required this.notifier, Key? key});
@override
Widget get leftBarItem => ViewLeftBarItem(view: view);

View File

@ -1,6 +1,5 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
@ -36,10 +35,7 @@ class GridPage extends StatefulWidget {
required this.view,
this.onDeleted,
Key? key,
}) : databaseController = DatabaseController(
view: view,
layoutType: DatabaseLayoutPB.Grid,
),
}) : databaseController = DatabaseController(view: view),
super(key: key);
final ViewPB view;

View File

@ -0,0 +1,133 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/layout/layout_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import '../../layout/sizes.dart';
class DatabaseLayoutList extends StatefulWidget {
final String viewId;
final DatabaseLayoutPB currentLayout;
const DatabaseLayoutList({
required this.viewId,
required this.currentLayout,
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _DatabaseLayoutListState();
}
class _DatabaseLayoutListState extends State<DatabaseLayoutList> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DatabaseLayoutBloc(
viewId: widget.viewId,
databaseLayout: widget.currentLayout,
)..add(const DatabaseLayoutEvent.initial()),
child: BlocBuilder<DatabaseLayoutBloc, DatabaseLayoutState>(
builder: (context, state) {
final cells = DatabaseLayoutPB.values.map((layout) {
final isSelected = state.databaseLayout == layout;
return DatabaseViewLayoutCell(
databaseLayout: layout,
isSelected: isSelected,
onTap: (selectedLayout) {
context
.read<DatabaseLayoutBloc>()
.add(DatabaseLayoutEvent.updateLayout(selectedLayout));
},
);
}).toList();
return ListView.separated(
controller: ScrollController(),
shrinkWrap: true,
itemCount: cells.length,
itemBuilder: (BuildContext context, int index) => cells[index],
separatorBuilder: (BuildContext context, int index) =>
VSpace(GridSize.typeOptionSeparatorHeight),
padding: const EdgeInsets.symmetric(vertical: 6.0),
);
},
),
);
}
}
extension DatabaseLayoutExtension on DatabaseLayoutPB {
String layoutName() {
switch (this) {
case DatabaseLayoutPB.Board:
return LocaleKeys.board_menuName.tr();
case DatabaseLayoutPB.Calendar:
return LocaleKeys.calendar_menuName.tr();
case DatabaseLayoutPB.Grid:
return LocaleKeys.grid_menuName.tr();
default:
return "";
}
}
String iconName() {
switch (this) {
case DatabaseLayoutPB.Board:
return 'editor/board';
case DatabaseLayoutPB.Calendar:
return "editor/grid";
case DatabaseLayoutPB.Grid:
return "editor/grid";
default:
return "";
}
}
}
class DatabaseViewLayoutCell extends StatelessWidget {
final bool isSelected;
final DatabaseLayoutPB databaseLayout;
final void Function(DatabaseLayoutPB) onTap;
const DatabaseViewLayoutCell({
required this.databaseLayout,
required this.isSelected,
required this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
Widget? checkmark;
if (isSelected) {
checkmark = svgWidget("grid/checkmark");
}
return SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyButton(
hoverColor: AFThemeExtension.of(context).lightGreyHover,
text: FlowyText.medium(
databaseLayout.layoutName(),
color: AFThemeExtension.of(context).textColor,
),
leftIcon: svgWidget(
databaseLayout.iconName(),
color: Theme.of(context).iconTheme.color,
),
rightIcon: checkmark,
onTap: () => onTap(databaseLayout),
).padding(horizontal: 6.0),
);
}
}

View File

@ -1,6 +1,6 @@
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/application/setting/setting_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
@ -9,36 +9,29 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import '../../layout/sizes.dart';
class GridSettingContext {
final String viewId;
final FieldController fieldController;
GridSettingContext({
required this.viewId,
required this.fieldController,
});
}
class GridSettingList extends StatelessWidget {
final GridSettingContext settingContext;
final Function(DatabaseSettingAction, GridSettingContext) onAction;
const GridSettingList({
required this.settingContext,
class DatabaseSettingList extends StatelessWidget {
final DatabaseController databaseContoller;
final Function(DatabaseSettingAction, DatabaseController) onAction;
const DatabaseSettingList({
required this.databaseContoller,
required this.onAction,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final cells = DatabaseSettingAction.values
.where((value) => value.enable())
.map((action) {
final cells = DatabaseSettingAction.values.where((element) {
if (element == DatabaseSettingAction.showGroup) {
return databaseContoller.databaseLayout == DatabaseLayoutPB.Board;
} else {
return true;
}
}).map((action) {
return _SettingItem(
action: action,
onAction: (action) => onAction(action, settingContext),
onAction: (action) => onAction(action, databaseContoller),
);
}).toList();
@ -78,9 +71,7 @@ class _SettingItem extends StatelessWidget {
hoverColor: AFThemeExtension.of(context).lightGreyHover,
text: FlowyText.medium(
action.title(),
color: action.enable()
? AFThemeExtension.of(context).textColor
: Theme.of(context).disabledColor,
color: AFThemeExtension.of(context).textColor,
),
onTap: () => onAction(action),
leftIcon: svgWidget(
@ -91,36 +82,3 @@ class _SettingItem extends StatelessWidget {
);
}
}
extension _GridSettingExtension on DatabaseSettingAction {
String iconName() {
switch (this) {
case DatabaseSettingAction.showFilters:
return 'grid/setting/filter';
case DatabaseSettingAction.sortBy:
return 'grid/setting/sort';
case DatabaseSettingAction.showProperties:
return 'grid/setting/properties';
}
}
String title() {
switch (this) {
case DatabaseSettingAction.showFilters:
return LocaleKeys.grid_settings_filter.tr();
case DatabaseSettingAction.sortBy:
return LocaleKeys.grid_settings_sortBy.tr();
case DatabaseSettingAction.showProperties:
return LocaleKeys.grid_settings_Properties.tr();
}
}
bool enable() {
switch (this) {
case DatabaseSettingAction.showProperties:
return true;
default:
return false;
}
}
}

View File

@ -1,9 +1,11 @@
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../layout/sizes.dart';
import 'filter_button.dart';
import 'setting_button.dart';
import '../../../../widgets/setting/setting_button.dart';
import 'sort_button.dart';
class GridToolbarContext {
@ -29,7 +31,9 @@ class GridToolbar extends StatelessWidget {
const Spacer(),
const FilterButton(),
const SortButton(),
const SettingButton(),
SettingButton(
databaseController: context.read<GridBloc>().databaseController,
),
],
),
);

View File

@ -1,102 +0,0 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/setting/setting_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import '../../layout/sizes.dart';
import 'grid_property.dart';
import 'grid_setting.dart';
class SettingButton extends StatefulWidget {
const SettingButton({Key? key}) : super(key: key);
@override
State<SettingButton> createState() => _SettingButtonState();
}
class _SettingButtonState extends State<SettingButton> {
late PopoverController _popoverController;
@override
void initState() {
_popoverController = PopoverController();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocSelector<GridBloc, GridState, GridSettingContext>(
selector: (state) {
final fieldController =
context.read<GridBloc>().databaseController.fieldController;
return GridSettingContext(
viewId: state.viewId,
fieldController: fieldController,
);
},
builder: (context, settingContext) {
return SizedBox(
height: 26,
child: AppFlowyPopover(
controller: _popoverController,
constraints: BoxConstraints.loose(const Size(260, 400)),
direction: PopoverDirection.bottomWithLeftAligned,
offset: const Offset(0, 8),
margin: EdgeInsets.zero,
triggerActions: PopoverTriggerFlags.none,
child: FlowyTextButton(
LocaleKeys.settings_title.tr(),
fontColor: AFThemeExtension.of(context).textColor,
fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.typeOptionContentInsets,
onPressed: () => _popoverController.show(),
),
popupBuilder: (BuildContext context) {
return _GridSettingListPopover(settingContext: settingContext);
},
),
);
},
);
}
}
class _GridSettingListPopover extends StatefulWidget {
final GridSettingContext settingContext;
const _GridSettingListPopover({Key? key, required this.settingContext})
: super(key: key);
@override
State<StatefulWidget> createState() => _GridSettingListPopoverState();
}
class _GridSettingListPopoverState extends State<_GridSettingListPopover> {
DatabaseSettingAction? _action;
@override
Widget build(BuildContext context) {
if (_action == DatabaseSettingAction.showProperties) {
return GridPropertyList(
viewId: widget.settingContext.viewId,
fieldController: widget.settingContext.fieldController,
);
}
return GridSettingList(
settingContext: widget.settingContext,
onAction: (action, settingContext) {
setState(() {
_action = action;
});
},
).padding(all: 6.0);
}
}

View File

@ -11,23 +11,23 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import '../../layout/sizes.dart';
import '../header/field_editor.dart';
import '../../grid/presentation/layout/sizes.dart';
import '../../grid/presentation/widgets/header/field_editor.dart';
class GridPropertyList extends StatefulWidget {
class DatabasePropertyList extends StatefulWidget {
final String viewId;
final FieldController fieldController;
const GridPropertyList({
const DatabasePropertyList({
required this.viewId,
required this.fieldController,
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _GridPropertyListState();
State<StatefulWidget> createState() => _DatabasePropertyListState();
}
class _GridPropertyListState extends State<GridPropertyList> {
class _DatabasePropertyListState extends State<DatabasePropertyList> {
late PopoverMutex _popoverMutex;
@override

View File

@ -11,11 +11,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class GridGroupList extends StatelessWidget {
class DatabaseGroupList extends StatelessWidget {
final String viewId;
final FieldController fieldController;
final VoidCallback onDismissed;
const GridGroupList({
const DatabaseGroupList({
required this.viewId,
required this.fieldController,
required this.onDismissed,

View File

@ -0,0 +1,116 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/application/setting/setting_bloc.dart';
import 'package:appflowy/plugins/database_view/widgets/group/database_group.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:styled_widget/styled_widget.dart';
import '../../grid/presentation/layout/sizes.dart';
import '../../grid/presentation/widgets/toolbar/grid_layout.dart';
import '../field/grid_property.dart';
import '../../grid/presentation/widgets/toolbar/grid_setting.dart';
class SettingButton extends StatefulWidget {
final DatabaseController databaseController;
const SettingButton({
required this.databaseController,
Key? key,
}) : super(key: key);
@override
State<SettingButton> createState() => _SettingButtonState();
}
class _SettingButtonState extends State<SettingButton> {
late PopoverController _popoverController;
@override
void initState() {
_popoverController = PopoverController();
super.initState();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 26,
child: AppFlowyPopover(
controller: _popoverController,
constraints: BoxConstraints.loose(const Size(200, 400)),
direction: PopoverDirection.bottomWithLeftAligned,
offset: const Offset(0, 8),
margin: EdgeInsets.zero,
triggerActions: PopoverTriggerFlags.none,
child: FlowyTextButton(
LocaleKeys.settings_title.tr(),
fontColor: AFThemeExtension.of(context).textColor,
fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.typeOptionContentInsets,
onPressed: () => _popoverController.show(),
),
popupBuilder: (BuildContext context) {
return _DatabaseSettingListPopover(
databaseController: widget.databaseController,
);
},
),
);
}
}
class _DatabaseSettingListPopover extends StatefulWidget {
final DatabaseController databaseController;
const _DatabaseSettingListPopover({
required this.databaseController,
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _DatabaseSettingListPopoverState();
}
class _DatabaseSettingListPopoverState
extends State<_DatabaseSettingListPopover> {
DatabaseSettingAction? _action;
@override
Widget build(BuildContext context) {
if (_action == null) {
return DatabaseSettingList(
databaseContoller: widget.databaseController,
onAction: (action, settingContext) {
setState(() {
_action = action;
});
},
).padding(all: 6.0);
} else {
switch (_action!) {
case DatabaseSettingAction.showLayout:
return DatabaseLayoutList(
viewId: widget.databaseController.viewId,
currentLayout: widget.databaseController.databaseLayout!,
);
case DatabaseSettingAction.showGroup:
return DatabaseGroupList(
viewId: widget.databaseController.viewId,
fieldController: widget.databaseController.fieldController,
onDismissed: () {
// widget.popoverController.close();
},
);
case DatabaseSettingAction.showProperties:
return DatabasePropertyList(
viewId: widget.databaseController.viewId,
fieldController: widget.databaseController.fieldController,
);
}
}
}
}

View File

@ -61,32 +61,36 @@ class DocumentPlugin extends Plugin<int> {
}
@override
PluginDisplay get display {
return DocumentPluginDisplay(
PluginWidgetBuilder get widgetBuilder {
return DocumentPluginWidgetBuilder(
notifier: notifier,
documentAppearanceCubit: _documentAppearanceCubit,
);
}
@override
PluginType get ty => _pluginType;
PluginType get pluginType => _pluginType;
@override
PluginId get id => notifier.view.id;
}
class DocumentPluginDisplay extends PluginDisplay with NavigationItem {
class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
with NavigationItem {
final ViewPluginNotifier notifier;
ViewPB get view => notifier.view;
int? deletedViewIndex;
DocumentAppearanceCubit documentAppearanceCubit;
DocumentPluginDisplay({
DocumentPluginWidgetBuilder({
required this.notifier,
required this.documentAppearanceCubit,
Key? key,
});
@override
EdgeInsets get contentPadding => EdgeInsets.zero;
@override
Widget buildWidget(PluginContext context) {
notifier.isDeleted.addListener(() {

View File

@ -1,6 +1,6 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
@ -38,13 +38,18 @@ class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
late Future<dartz.Either<FlowyError, ViewPB>> future;
final focusNode = FocusNode();
String get appId => widget.node.attributes[DatabaseBlockKeys.kAppID];
String get viewId => widget.node.attributes[DatabaseBlockKeys.kViewID];
String get parentViewId => widget.node.attributes[DatabaseBlockKeys.parentID];
String get childViewId => widget.node.attributes[DatabaseBlockKeys.viewID];
@override
void initState() {
super.initState();
future = AppBackendService().getChildView(viewId, appId).then(
future = ViewBackendService()
.getChildView(
parentViewId: parentViewId,
childViewId: childViewId,
)
.then(
(value) => value.swap(),
);
}
@ -153,6 +158,7 @@ class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
switch (action.inner) {
case _ActionType.viewDatabase:
getIt<MenuSharedState>().latestOpenView = viewPB;
getIt<HomeStackManager>().setPlugin(viewPB.plugin());
break;
case _ActionType.delete:

View File

@ -2,20 +2,20 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/database_view_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/board/board_node_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/grid/grid_node_widget.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
class DatabaseBlockKeys {
const DatabaseBlockKeys._();
static const String kAppID = 'app_id';
static const String kViewID = 'view_id';
static const String parentID = 'parent_id';
static const String viewID = 'view_id';
}
extension InsertDatabase on EditorState {
Future<void> insertPage(ViewPB appPB, ViewPB viewPB) async {
Future<void> insertPage(ViewPB parentView, ViewPB childView) async {
final selection = this.selection;
if (selection == null || !selection.isCollapsed) {
return;
@ -26,23 +26,22 @@ extension InsertDatabase on EditorState {
}
// get the database that the view is associated with
final database = await DatabaseViewBackendService(viewId: viewPB.id)
final database = await DatabaseViewBackendService(viewId: childView.id)
.openGrid()
.then((value) => value.swap().toOption().toNullable());
if (database == null) {
throw StateError(
'The database associated with ${viewPB.id} could not be found while attempting to create a referenced ${viewPB.layout.name}.',
'The database associated with ${childView.id} could not be found while attempting to create a referenced ${childView.layout.name}.',
);
}
final prefix = _referencedDatabasePrefix(viewPB.layout);
final ref = await AppBackendService().createView(
parentViewId: appPB.id,
name: "$prefix ${viewPB.name}",
layoutType: viewPB.layout,
ext: {
'database_id': database.id,
},
final prefix = _referencedDatabasePrefix(childView.layout);
final ref = await ViewBackendService.createDatabaseReferenceView(
parentViewId: parentView.id,
name: "$prefix ${childView.name}",
layoutType: childView.layout,
databaseId: database.id,
).then((value) => value.swap().toOption().toNullable());
// TODO(a-wallen): Show error dialog here.
@ -54,10 +53,10 @@ extension InsertDatabase on EditorState {
transaction.insertNode(
selection.end.path,
Node(
type: _convertPageType(viewPB),
type: _convertPageType(childView),
attributes: {
DatabaseBlockKeys.kAppID: appPB.id,
DatabaseBlockKeys.kViewID: ref.id,
DatabaseBlockKeys.parentID: parentView.id,
DatabaseBlockKeys.viewID: ref.id,
},
),
);

View File

@ -1,5 +1,5 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/image.dart';
@ -69,7 +69,7 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
final Map<int, (ViewPB, ViewPB)> _items = {};
Future<List<(ViewPB, List<ViewPB>)>> fetchItems() async {
final items = await AppBackendService().fetchViews(widget.layoutType);
final items = await ViewBackendService().fetchViews(widget.layoutType);
int index = 0;
for (final (app, children) in items) {

View File

@ -37,8 +37,8 @@ class BoardBlockComponentBuilder extends BlockComponentBuilder {
@override
bool validate(Node node) =>
node.children.isEmpty &&
node.attributes[DatabaseBlockKeys.kAppID] is String &&
node.attributes[DatabaseBlockKeys.kViewID] is String;
node.attributes[DatabaseBlockKeys.parentID] is String &&
node.attributes[DatabaseBlockKeys.viewID] is String;
}
class BoardBlockComponentWidget extends BlockComponentStatefulWidget {

View File

@ -1,10 +1,10 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/prelude.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:easy_localization/easy_localization.dart';
SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) =>
@ -22,9 +22,9 @@ SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) =>
}
final appId = documentBloc.view.parentViewId;
final service = AppBackendService();
final service = ViewBackendService();
final result = (await service.createView(
final result = (await ViewBackendService.createView(
parentViewId: appId,
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layoutType: ViewLayoutPB.Board,
@ -42,7 +42,10 @@ SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) =>
return;
}
final view = (await service.getChildView(result.viewId, result.id))
final view = (await service.getChildView(
parentViewId: result.viewId,
childViewId: result.id,
))
.getLeftOrNull();
// As this.
if (view == null) {

View File

@ -37,8 +37,8 @@ class GridBlockComponentBuilder extends BlockComponentBuilder {
@override
bool validate(Node node) =>
node.children.isEmpty &&
node.attributes[DatabaseBlockKeys.kAppID] is String &&
node.attributes[DatabaseBlockKeys.kViewID] is String;
node.attributes[DatabaseBlockKeys.parentID] is String &&
node.attributes[DatabaseBlockKeys.viewID] is String;
}
class GridBlockComponentWidget extends BlockComponentStatefulWidget {

View File

@ -2,7 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart';
import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
@ -22,9 +22,9 @@ SelectionMenuItem gridViewMenuItem(DocumentBloc documentBloc) =>
}
final appId = documentBloc.view.parentViewId;
final service = AppBackendService();
final service = ViewBackendService();
final result = (await service.createView(
final result = (await ViewBackendService.createView(
parentViewId: appId,
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layoutType: ViewLayoutPB.Grid,
@ -42,7 +42,10 @@ SelectionMenuItem gridViewMenuItem(DocumentBloc documentBloc) =>
return;
}
final view = (await service.getChildView(result.viewId, result.id))
final view = (await service.getChildView(
parentViewId: result.viewId,
childViewId: result.id,
))
.getLeftOrNull();
// As this.
if (view == null) {

View File

@ -38,16 +38,16 @@ class TrashPlugin extends Plugin {
TrashPlugin({required PluginType pluginType}) : _pluginType = pluginType;
@override
PluginDisplay get display => TrashPluginDisplay();
PluginWidgetBuilder get widgetBuilder => TrashPluginDisplay();
@override
PluginId get id => "TrashStack";
@override
PluginType get ty => _pluginType;
PluginType get pluginType => _pluginType;
}
class TrashPluginDisplay extends PluginDisplay {
class TrashPluginDisplay extends PluginWidgetBuilder {
@override
Widget get leftBarItem => FlowyText.medium(LocaleKeys.trash_text.tr());

View File

@ -22,11 +22,11 @@ typedef PluginId = String;
abstract class Plugin<T> {
PluginId get id;
PluginDisplay get display;
PluginWidgetBuilder get widgetBuilder;
PluginNotifier? get notifier => null;
PluginType get ty;
PluginType get pluginType;
void dispose() {
notifier?.dispose();
@ -37,7 +37,7 @@ abstract class PluginNotifier<T> {
/// Notify if the plugin get deleted
ValueNotifier<T> get isDeleted;
/// Notify if the [PluginDisplay]'s content was changed
/// Notify if the [PluginWidgetBuilder]'s content was changed
ValueNotifier<int> get isDisplayChanged;
void dispose() {}
@ -50,8 +50,11 @@ abstract class PluginBuilder {
String get menuIcon;
/// The type of this [Plugin]. Each [Plugin] should have a unique [PluginType]
PluginType get pluginType;
/// The layoutType is used in the backend to determine the layout of the view.
/// Currrently, AppFlowy supports 4 layout types: Document, Grid, Board, Calendar.
ViewLayoutPB? get layoutType => ViewLayoutPB.Document;
}
@ -60,9 +63,12 @@ abstract class PluginConfig {
bool get creatable => true;
}
abstract class PluginDisplay with NavigationItem {
abstract class PluginWidgetBuilder with NavigationItem {
List<NavigationItem> get navigationItems;
EdgeInsets get contentPadding =>
const EdgeInsets.symmetric(horizontal: 40, vertical: 28);
Widget buildWidget(PluginContext context);
}
@ -78,6 +84,8 @@ void registerPlugin({required PluginBuilder builder, PluginConfig? config}) {
.registerPlugin(builder.pluginType, builder, config: config);
}
/// Make the correct plugin from the [pluginType] and [data]. If the plugin
/// is not registered, it will return a blank plugin.
Plugin makePlugin({required PluginType pluginType, dynamic data}) {
final plugin = getIt<PluginSandbox>().buildPlugin(pluginType, data);
return plugin;

View File

@ -1,5 +1,6 @@
import 'dart:collection';
import 'package:appflowy/plugins/blank/blank.dart';
import 'package:flutter/services.dart';
import '../plugin.dart';
@ -28,9 +29,11 @@ class PluginSandbox {
return index;
}
/// Build a plugin from [data] with [pluginType]
/// If the [pluginType] is not registered, it will return a blank plugin
Plugin buildPlugin(PluginType pluginType, dynamic data) {
final plugin = _pluginBuilders[pluginType]!.build(data);
return plugin;
final builder = _pluginBuilders[pluginType] ?? BlankPluginBuilder();
return builder.build(data);
}
void registerPlugin(

View File

@ -3,7 +3,7 @@ import 'dart:collection';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/app/app_listener.dart';
import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
import 'package:expandable/expandable.dart';
import 'package:appflowy_backend/log.dart';
@ -17,11 +17,11 @@ import 'package:dartz/dartz.dart';
part 'app_bloc.freezed.dart';
class AppBloc extends Bloc<AppEvent, AppState> {
final AppBackendService appService;
final ViewBackendService appService;
final AppListener appListener;
AppBloc({required ViewPB view})
: appService = AppBackendService(),
: appService = ViewBackendService(),
appListener = AppListener(viewId: view.id),
super(AppState.initial(view)) {
on<AppEvent>((event, emit) async {
@ -77,8 +77,10 @@ class AppBloc extends Bloc<AppEvent, AppState> {
}
Future<void> _renameView(Rename e, Emitter<AppState> emit) async {
final result =
await appService.updateApp(appId: state.view.id, name: e.newName);
final result = await ViewBackendService.updateView(
viewId: state.view.id,
name: e.newName,
);
result.fold(
(l) => emit(state.copyWith(successOrFailure: left(unit))),
(error) => emit(state.copyWith(successOrFailure: right(error))),
@ -87,7 +89,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
// Delete the current app
Future<void> _deleteApp(Emitter<AppState> emit) async {
final result = await appService.delete(viewId: state.view.id);
final result = await ViewBackendService.delete(viewId: state.view.id);
result.fold(
(unit) => emit(state.copyWith(successOrFailure: left(unit))),
(error) => emit(state.copyWith(successOrFailure: right(error))),
@ -95,7 +97,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
}
Future<void> _deleteView(Emitter<AppState> emit, String viewId) async {
final result = await appService.deleteView(viewId: viewId);
final result = await ViewBackendService.deleteView(viewId: viewId);
result.fold(
(unit) => emit(state.copyWith(successOrFailure: left(unit))),
(error) => emit(state.copyWith(successOrFailure: right(error))),
@ -103,7 +105,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
}
Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
final result = await appService.createView(
final result = await ViewBackendService.createView(
parentViewId: state.view.id,
name: value.name,
desc: value.desc ?? "",
@ -132,7 +134,8 @@ class AppBloc extends Bloc<AppEvent, AppState> {
}
Future<void> _loadViews(Emitter<AppState> emit) async {
final viewsOrFailed = await appService.getViews(viewId: state.view.id);
final viewsOrFailed =
await ViewBackendService.getViews(viewId: state.view.id);
viewsOrFailed.fold(
(views) => emit(state.copyWith(views: views)),
(error) {

View File

@ -1,143 +0,0 @@
import 'dart:async';
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
class AppBackendService {
Future<Either<ViewPB, FlowyError>> createView({
required String parentViewId,
required String name,
String? desc,
required ViewLayoutPB layoutType,
/// The initial data should be a JSON that represent the DocumentDataPB.
/// Currently, only support create document with initial data.
List<int>? initialDataBytes,
/// The [ext] is used to pass through the custom configuration
/// to the backend.
/// Linking the view to the existing database, it needs to pass
/// the database id. For example: "database_id": "xxx"
///
Map<String, String> ext = const {},
}) {
final payload = CreateViewPayloadPB.create()
..parentViewId = parentViewId
..name = name
..desc = desc ?? ""
..layout = layoutType
..initialData = initialDataBytes ?? [];
if (ext.isNotEmpty) {
payload.ext.addAll(ext);
}
return FolderEventCreateView(payload).send();
}
Future<Either<List<ViewPB>, FlowyError>> getViews({required String viewId}) {
final payload = ViewIdPB.create()..value = viewId;
return FolderEventReadView(payload).send().then((result) {
return result.fold(
(app) => left(app.childViews),
(error) => right(error),
);
});
}
Future<Either<Unit, FlowyError>> delete({required String viewId}) {
final request = RepeatedViewIdPB.create()..items.add(viewId);
return FolderEventDeleteView(request).send();
}
Future<Either<Unit, FlowyError>> deleteView({required String viewId}) {
final request = RepeatedViewIdPB.create()..items.add(viewId);
return FolderEventDeleteView(request).send();
}
Future<Either<ViewPB, FlowyError>> updateApp({
required String appId,
String? name,
}) {
var payload = UpdateViewPayloadPB.create()..viewId = appId;
if (name != null) {
payload.name = name;
}
return FolderEventUpdateView(payload).send();
}
Future<Either<Unit, FlowyError>> moveView({
required String viewId,
required int fromIndex,
required int toIndex,
}) {
final payload = MoveFolderItemPayloadPB.create()
..itemId = viewId
..from = fromIndex
..to = toIndex
..ty = MoveFolderItemType.MoveView;
return FolderEventMoveItem(payload).send();
}
Future<List<(ViewPB, List<ViewPB>)>> fetchViews(
ViewLayoutPB layoutType,
) async {
final result = <(ViewPB, List<ViewPB>)>[];
return FolderEventReadCurrentWorkspace().send().then((value) async {
final workspaces = value.getLeftOrNull<WorkspaceSettingPB>();
if (workspaces != null) {
final views = workspaces.workspace.views;
for (var view in views) {
final childViews = await getViews(viewId: view.id).then(
(value) => value
.getLeftOrNull<List<ViewPB>>()
?.where((e) => e.layout == layoutType)
.toList(),
);
if (childViews != null && childViews.isNotEmpty) {
result.add((view, childViews));
}
}
}
return result;
});
}
Future<Either<ViewPB, FlowyError>> getView(
String viewID,
) async {
final payload = ViewIdPB.create()..value = viewID;
return FolderEventReadView(payload).send();
}
Future<Either<ViewPB, FlowyError>> getChildView(
String viewID,
String childViewID,
) async {
final payload = ViewIdPB.create()..value = viewID;
return FolderEventReadView(payload).send().then((result) {
return result.fold(
(app) => left(
app.childViews.firstWhere((e) => e.id == childViewID),
),
(error) => right(error),
);
});
}
}
extension AppFlowy on Either {
T? getLeftOrNull<T>() {
if (isLeft()) {
final result = fold<T?>((l) => l, (r) => null);
return result;
}
return null;
}
}

View File

@ -1,3 +1,2 @@
export 'app_bloc.dart';
export 'app_listener.dart';
export 'app_service.dart';

View File

@ -1,7 +1,7 @@
import 'dart:async';
import 'package:appflowy/workspace/application/app/app_bloc.dart';
import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -13,12 +13,10 @@ class ViewSectionBloc extends Bloc<ViewSectionEvent, ViewSectionState> {
void Function()? _viewsListener;
void Function()? _selectedViewlistener;
final AppViewDataContext _appViewData;
late final AppBackendService _appService;
ViewSectionBloc({
required AppViewDataContext appViewData,
}) : _appService = AppBackendService(),
_appViewData = appViewData,
}) : _appViewData = appViewData,
super(ViewSectionState.initial(appViewData)) {
on<ViewSectionEvent>((event, emit) async {
await event.map(
@ -69,7 +67,7 @@ class ViewSectionBloc extends Bloc<ViewSectionEvent, ViewSectionState> {
views.insert(value.toIndex, views.removeAt(value.fromIndex));
emit(state.copyWith(views: views));
final result = await _appService.moveView(
final result = await ViewBackendService.moveView(
viewId: viewId,
fromIndex: value.fromIndex,
toIndex: value.toIndex,

View File

@ -9,13 +9,13 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'view_bloc.freezed.dart';
class ViewBloc extends Bloc<ViewEvent, ViewState> {
final ViewService service;
final ViewBackendService viewBackendSvc;
final ViewListener listener;
final ViewPB view;
ViewBloc({
required this.view,
}) : service = ViewService(),
}) : viewBackendSvc = ViewBackendService(),
listener = ViewListener(view: view),
super(ViewState.init(view)) {
on<ViewEvent>((event, emit) async {
@ -42,7 +42,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
);
},
rename: (e) async {
final result = await service.updateView(
final result = await ViewBackendService.updateView(
viewId: view.id,
name: e.newName,
);
@ -54,7 +54,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
);
},
delete: (e) async {
final result = await service.delete(viewId: view.id);
final result = await ViewBackendService.delete(viewId: view.id);
emit(
result.fold(
(l) => state.copyWith(successOrFailure: left(unit)),
@ -63,7 +63,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
);
},
duplicate: (e) async {
final result = await service.duplicate(view: view);
final result = await ViewBackendService.duplicate(view: view);
emit(
result.fold(
(l) => state.copyWith(successOrFailure: left(unit)),

View File

@ -1,3 +1,4 @@
import 'package:appflowy/plugins/database_view/database_view.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:flowy_infra/image.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
@ -56,7 +57,14 @@ extension ViewExtension on ViewPB {
}
Plugin plugin() {
final plugin = makePlugin(pluginType: pluginType, data: this);
return plugin;
switch (layout) {
case ViewLayoutPB.Board:
case ViewLayoutPB.Calendar:
case ViewLayoutPB.Grid:
return DatabaseViewPlugin(view: this);
case ViewLayoutPB.Document:
return makePlugin(pluginType: pluginType, data: this);
}
throw UnimplementedError;
}
}

View File

@ -1,34 +1,165 @@
import 'dart:async';
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
class ViewService {
Future<Either<ViewPB, FlowyError>> updateView({
required String viewId,
String? name,
class ViewBackendService {
static Future<Either<ViewPB, FlowyError>> createView({
required ViewLayoutPB layoutType,
required String parentViewId,
required String name,
String? desc,
/// The initial data should be a JSON that represent the DocumentDataPB.
/// Currently, only support create document with initial data.
List<int>? initialDataBytes,
/// The [ext] is used to pass through the custom configuration
/// to the backend.
/// Linking the view to the existing database, it needs to pass
/// the database id. For example: "database_id": "xxx"
///
Map<String, String> ext = const {},
}) {
final request = UpdateViewPayloadPB.create()..viewId = viewId;
final payload = CreateViewPayloadPB.create()
..parentViewId = parentViewId
..name = name
..desc = desc ?? ""
..layout = layoutType
..initialData = initialDataBytes ?? [];
if (name != null) {
request.name = name;
if (ext.isNotEmpty) {
payload.ext.addAll(ext);
}
if (desc != null) {
request.desc = desc;
return FolderEventCreateView(payload).send();
}
return FolderEventUpdateView(request).send();
static Future<Either<ViewPB, FlowyError>> createDatabaseReferenceView({
required String parentViewId,
required String databaseId,
required ViewLayoutPB layoutType,
required String name,
}) {
return ViewBackendService.createView(
layoutType: layoutType,
parentViewId: parentViewId,
name: name,
ext: {
'database_id': databaseId,
},
);
}
Future<Either<Unit, FlowyError>> delete({required String viewId}) {
static Future<Either<List<ViewPB>, FlowyError>> getViews({
required String viewId,
}) {
final payload = ViewIdPB.create()..value = viewId;
return FolderEventReadView(payload).send().then((result) {
return result.fold(
(app) => left(app.childViews),
(error) => right(error),
);
});
}
static Future<Either<Unit, FlowyError>> delete({required String viewId}) {
final request = RepeatedViewIdPB.create()..items.add(viewId);
return FolderEventDeleteView(request).send();
}
Future<Either<Unit, FlowyError>> duplicate({required ViewPB view}) {
static Future<Either<Unit, FlowyError>> deleteView({required String viewId}) {
final request = RepeatedViewIdPB.create()..items.add(viewId);
return FolderEventDeleteView(request).send();
}
static Future<Either<Unit, FlowyError>> duplicate({required ViewPB view}) {
return FolderEventDuplicateView(view).send();
}
static Future<Either<ViewPB, FlowyError>> updateView({
required String viewId,
String? name,
}) {
var payload = UpdateViewPayloadPB.create()..viewId = viewId;
if (name != null) {
payload.name = name;
}
return FolderEventUpdateView(payload).send();
}
static Future<Either<Unit, FlowyError>> moveView({
required String viewId,
required int fromIndex,
required int toIndex,
}) {
final payload = MoveFolderItemPayloadPB.create()
..itemId = viewId
..from = fromIndex
..to = toIndex
..ty = MoveFolderItemType.MoveView;
return FolderEventMoveItem(payload).send();
}
Future<List<(ViewPB, List<ViewPB>)>> fetchViews(
ViewLayoutPB layoutType,
) async {
final result = <(ViewPB, List<ViewPB>)>[];
return FolderEventReadCurrentWorkspace().send().then((value) async {
final workspaces = value.getLeftOrNull<WorkspaceSettingPB>();
if (workspaces != null) {
final views = workspaces.workspace.views;
for (var view in views) {
final childViews = await getViews(viewId: view.id).then(
(value) => value
.getLeftOrNull<List<ViewPB>>()
?.where((e) => e.layout == layoutType)
.toList(),
);
if (childViews != null && childViews.isNotEmpty) {
result.add((view, childViews));
}
}
}
return result;
});
}
Future<Either<ViewPB, FlowyError>> getView(
String viewID,
) async {
final payload = ViewIdPB.create()..value = viewID;
return FolderEventReadView(payload).send();
}
Future<Either<ViewPB, FlowyError>> getChildView({
required String parentViewId,
required String childViewId,
}) async {
final payload = ViewIdPB.create()..value = parentViewId;
return FolderEventReadView(payload).send().then((result) {
return result.fold(
(app) => left(
app.childViews.firstWhere((e) => e.id == childViewId),
),
(error) => right(error),
);
});
}
}
extension AppFlowy on Either {
T? getLeftOrNull<T>() {
if (isLeft()) {
final result = fold<T?>((l) => l, (r) => null);
return result;
}
return null;
}
}

View File

@ -76,7 +76,7 @@ class _HomeScreenState extends State<HomeScreen> {
if (view != null) {
// Only open the last opened view if the [HomeStackManager] current opened plugin is blank and the last opened view is not null.
// All opened widgets that display on the home screen are in the form of plugins. There is a list of built-in plugins defined in the [PluginType] enum, including board, grid and trash.
if (getIt<HomeStackManager>().plugin.ty ==
if (getIt<HomeStackManager>().plugin.pluginType ==
PluginType.blank) {
final plugin = makePlugin(
pluginType: view.pluginType,

View File

@ -112,16 +112,14 @@ abstract mixin class NavigationItem {
class HomeStackNotifier extends ChangeNotifier {
Plugin _plugin;
Widget get titleWidget => _plugin.display.leftBarItem;
Widget get titleWidget => _plugin.widgetBuilder.leftBarItem;
HomeStackNotifier({Plugin? plugin})
: _plugin = plugin ?? makePlugin(pluginType: PluginType.blank);
/// This is the only place where the plugin is set.
/// No need compare the old plugin with the new plugin. Just set it.
set plugin(Plugin newPlugin) {
if (newPlugin.id == _plugin.id) {
return;
}
_plugin.notifier?.isDisplayChanged.addListener(notifyListeners);
_plugin.dispose();
@ -139,7 +137,7 @@ class HomeStackManager {
HomeStackManager();
Widget title() {
return _notifier.plugin.display.leftBarItem;
return _notifier.plugin.widgetBuilder.leftBarItem;
}
Plugin get plugin => _notifier.plugin;
@ -172,20 +170,22 @@ class HomeStackManager {
child: Consumer(
builder: (_, HomeStackNotifier notifier, __) {
return FadingIndexedStack(
index: getIt<PluginSandbox>().indexOf(notifier.plugin.ty),
index: getIt<PluginSandbox>().indexOf(notifier.plugin.pluginType),
children: getIt<PluginSandbox>().supportPluginTypes.map(
(pluginType) {
if (pluginType == notifier.plugin.ty) {
final pluginWidget = notifier.plugin.display
.buildWidget(PluginContext(onDeleted: onDeleted));
if (pluginType == PluginType.editor) {
return pluginWidget;
}
return pluginWidget.padding(horizontal: 40, vertical: 28);
}
if (pluginType == notifier.plugin.pluginType) {
final builder = notifier.plugin.widgetBuilder;
final pluginWidget = builder.buildWidget(
PluginContext(onDeleted: onDeleted),
);
return Padding(
padding: builder.contentPadding,
child: pluginWidget,
);
} else {
return const BlankPage();
}
},
).toList(),
);
@ -219,7 +219,7 @@ class HomeTopBar extends StatelessWidget {
value: Provider.of<HomeStackNotifier>(context, listen: false),
child: Consumer(
builder: (_, HomeStackNotifier notifier, __) =>
notifier.plugin.display.rightBarItem ??
notifier.plugin.widgetBuilder.rightBarItem ??
const SizedBox.shrink(),
),
),

View File

@ -21,8 +21,8 @@ class NavigationNotifier with ChangeNotifier {
void update(HomeStackNotifier notifier) {
bool shouldNotify = false;
if (navigationItems != notifier.plugin.display.navigationItems) {
navigationItems = notifier.plugin.display.navigationItems;
if (navigationItems != notifier.plugin.widgetBuilder.navigationItems) {
navigationItems = notifier.plugin.widgetBuilder.navigationItems;
shouldNotify = true;
}
@ -41,7 +41,7 @@ class FlowyNavigation extends StatelessWidget {
create: (_) {
final notifier = Provider.of<HomeStackNotifier>(context, listen: false);
return NavigationNotifier(
navigationItems: notifier.plugin.display.navigationItems,
navigationItems: notifier.plugin.widgetBuilder.navigationItems,
);
},
update: (_, notifier, controller) => controller!..update(notifier),

View File

@ -17,7 +17,6 @@ class ViewLeftBarItem extends StatefulWidget {
class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
final _controller = TextEditingController();
final _focusNode = FocusNode();
late final ViewService _viewService;
late final ViewListener _viewListener;
late ViewPB view;
@ -25,7 +24,6 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
void initState() {
super.initState();
view = widget.view;
_viewService = ViewService();
_focusNode.addListener(_handleFocusChanged);
_viewListener = ViewListener(view: widget.view);
_viewListener.start(
@ -86,7 +84,7 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
}
if (_controller.text != view.name) {
_viewService.updateView(viewId: view.id, name: _controller.text);
ViewBackendService.updateView(viewId: view.id, name: _controller.text);
}
}
}

View File

@ -9,8 +9,7 @@ import 'package:appflowy/plugins/database_view/application/row/row_data_controll
import 'package:appflowy/plugins/database_view/board/board.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
@ -30,21 +29,16 @@ class AppFlowyBoardTest {
Future<BoardTestContext> createTestBoard() async {
final app = await unitTest.createTestApp();
final builder = BoardPluginBuilder();
return AppBackendService()
.createView(
return ViewBackendService.createView(
parentViewId: app.id,
name: "Test Board",
layoutType: builder.layoutType!,
)
.then((result) {
).then((result) {
return result.fold(
(view) async {
final context = BoardTestContext(
view,
DatabaseController(
view: view,
layoutType: DatabaseLayoutPB.Board,
),
DatabaseController(view: view),
);
final result = await context._boardDataController.open();
result.fold((l) => null, (r) => throw Exception(r));

View File

@ -2,7 +2,6 @@ import 'package:appflowy/plugins/database_view/application/filter/filter_service
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/checkbox_filter.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/text_filter.pb.dart';
import 'package:flutter_test/flutter_test.dart';
@ -55,7 +54,6 @@ void main() {
final service = FilterBackendService(viewId: context.gridView.id);
final gridController = DatabaseController(
view: context.gridView,
layoutType: DatabaseLayoutPB.Grid,
);
final gridBloc = GridBloc(
view: context.gridView,
@ -80,7 +78,6 @@ void main() {
final service = FilterBackendService(viewId: context.gridView.id);
final gridController = DatabaseController(
view: context.gridView,
layoutType: DatabaseLayoutPB.Grid,
);
final gridBloc = GridBloc(
view: context.gridView,
@ -126,7 +123,6 @@ void main() {
final service = FilterBackendService(viewId: context.gridView.id);
final gridController = DatabaseController(
view: context.gridView,
layoutType: DatabaseLayoutPB.Grid,
);
final gridBloc = GridBloc(
view: context.gridView,
@ -148,7 +144,6 @@ void main() {
final service = FilterBackendService(viewId: context.gridView.id);
final gridController = DatabaseController(
view: context.gridView,
layoutType: DatabaseLayoutPB.Grid,
);
final gridBloc = GridBloc(
view: context.gridView,

View File

@ -1,28 +1,22 @@
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/grid/grid.dart';
import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import '../util.dart';
Future<GridTestContext> createTestFilterGrid(AppFlowyGridTest gridTest) async {
final app = await gridTest.unitTest.createTestApp();
final builder = GridPluginBuilder();
final context = await AppBackendService()
.createView(
final context = await ViewBackendService.createView(
parentViewId: app.id,
name: "Filter Grid",
layoutType: builder.layoutType!,
)
.then((result) {
).then((result) {
return result.fold(
(view) async {
final context = GridTestContext(
view,
DatabaseController(
view: view,
layoutType: DatabaseLayoutPB.Grid,
),
DatabaseController(view: view),
);
final result = await context.gridController.open();

View File

@ -1,6 +1,5 @@
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:bloc_test/bloc_test.dart';
import 'util.dart';
@ -23,10 +22,7 @@ void main() {
"create a row",
build: () => GridBloc(
view: context.gridView,
databaseController: DatabaseController(
view: context.gridView,
layoutType: DatabaseLayoutPB.Grid,
),
databaseController: DatabaseController(view: context.gridView),
)..add(const GridEvent.initial()),
act: (bloc) => bloc.add(const GridEvent.createRow()),
wait: const Duration(milliseconds: 300),
@ -39,10 +35,7 @@ void main() {
"delete the last row",
build: () => GridBloc(
view: context.gridView,
databaseController: DatabaseController(
view: context.gridView,
layoutType: DatabaseLayoutPB.Grid,
),
databaseController: DatabaseController(view: context.gridView),
)..add(const GridEvent.initial()),
act: (bloc) async {
await gridResponseFuture();
@ -65,10 +58,7 @@ void main() {
'reorder rows',
build: () => GridBloc(
view: context.gridView,
databaseController: DatabaseController(
view: context.gridView,
layoutType: DatabaseLayoutPB.Grid,
),
databaseController: DatabaseController(view: context.gridView),
)..add(const GridEvent.initial()),
act: (bloc) async {
await gridResponseFuture();

View File

@ -9,9 +9,8 @@ import 'package:appflowy/plugins/database_view/application/row/row_data_controll
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/grid.dart';
import 'package:appflowy/workspace/application/app/app_service.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
@ -170,21 +169,16 @@ class AppFlowyGridTest {
Future<GridTestContext> createTestGrid() async {
final app = await unitTest.createTestApp();
final builder = GridPluginBuilder();
final context = await AppBackendService()
.createView(
final context = await ViewBackendService.createView(
parentViewId: app.id,
name: "Test Grid",
layoutType: builder.layoutType!,
)
.then((result) {
).then((result) {
return result.fold(
(view) async {
final context = GridTestContext(
view,
DatabaseController(
view: view,
layoutType: DatabaseLayoutPB.Grid,
),
DatabaseController(view: view),
);
final result = await context.gridController.open();
result.fold((l) => null, (r) => throw Exception(r));

View File

@ -99,7 +99,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "appflowy-integrate"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"collab",
@ -1024,7 +1024,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"bytes",
@ -1042,7 +1042,7 @@ dependencies = [
[[package]]
name = "collab-client-ws"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"bytes",
"collab-sync",
@ -1060,7 +1060,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"async-trait",
@ -1086,7 +1086,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"proc-macro2",
"quote",
@ -1098,7 +1098,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"collab",
@ -1115,7 +1115,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"collab",
@ -1134,7 +1134,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"bincode",
"chrono",
@ -1154,7 +1154,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"async-trait",
@ -1184,7 +1184,7 @@ dependencies = [
[[package]]
name = "collab-sync"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"bytes",
"collab",

View File

@ -34,12 +34,12 @@ default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
[patch.crates-io]
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
#collab = { path = "../../AppFlowy-Collab/collab" }
#collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }

View File

@ -85,7 +85,7 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "appflowy-integrate"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"collab",
@ -887,7 +887,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"bytes",
@ -905,7 +905,7 @@ dependencies = [
[[package]]
name = "collab-client-ws"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"bytes",
"collab-sync",
@ -923,7 +923,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"async-trait",
@ -949,7 +949,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"proc-macro2",
"quote",
@ -961,7 +961,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"collab",
@ -978,7 +978,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"collab",
@ -997,7 +997,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"bincode",
"chrono",
@ -1017,7 +1017,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"anyhow",
"async-trait",
@ -1047,7 +1047,7 @@ dependencies = [
[[package]]
name = "collab-sync"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf"
dependencies = [
"bytes",
"collab",

View File

@ -33,11 +33,11 @@ opt-level = 3
incremental = false
[patch.crates-io]
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" }
#collab = { path = "../AppFlowy-Collab/collab" }
#collab-folder = { path = "../AppFlowy-Collab/collab-folder" }

View File

@ -18,7 +18,7 @@ use flowy_error::FlowyError;
use flowy_folder2::deps::{FolderCloudService, FolderUser};
use flowy_folder2::entities::ViewLayoutPB;
use flowy_folder2::manager::Folder2Manager;
use flowy_folder2::view_ext::{FolderOperationHandler, FolderOperationHandlers};
use flowy_folder2::view_operation::{FolderOperationHandler, FolderOperationHandlers, View};
use flowy_folder2::ViewLayout;
use flowy_user::services::UserSession;
use lib_dispatch::prelude::ToBytes;
@ -111,7 +111,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
_name: &str,
data: Vec<u8>,
layout: ViewLayout,
_ext: HashMap<String, String>,
_meta: HashMap<String, String>,
) -> FutureResult<(), FlowyError> {
debug_assert_eq!(layout, ViewLayout::Document);
let view_id = view_id.to_string();
@ -130,7 +130,6 @@ impl FolderOperationHandler for DocumentFolderOperation {
view_id: &str,
_name: &str,
layout: ViewLayout,
_ext: HashMap<String, String>,
) -> FutureResult<(), FlowyError> {
debug_assert_eq!(layout, ViewLayout::Document);
@ -200,9 +199,9 @@ impl FolderOperationHandler for DatabaseFolderOperation {
name: &str,
data: Vec<u8>,
layout: ViewLayout,
ext: HashMap<String, String>,
meta: HashMap<String, String>,
) -> FutureResult<(), FlowyError> {
match CreateDatabaseExtParams::from_map(ext) {
match CreateDatabaseExtParams::from_map(meta) {
None => {
let database_manager = self.0.clone();
let view_id = view_id.to_string();
@ -217,17 +216,11 @@ impl FolderOperationHandler for DatabaseFolderOperation {
let database_manager = self.0.clone();
let layout = layout_type_from_view_layout(layout.into());
let name = name.to_string();
let target_view_id = view_id.to_string();
let database_view_id = view_id.to_string();
FutureResult::new(async move {
database_manager
.create_linked_view(
name,
layout,
params.database_id,
target_view_id,
params.duplicated_view_id,
)
.create_linked_view(name, layout, params.database_id, database_view_id)
.await?;
Ok(())
})
@ -245,7 +238,6 @@ impl FolderOperationHandler for DatabaseFolderOperation {
view_id: &str,
name: &str,
layout: ViewLayout,
_meta: HashMap<String, String>,
) -> FutureResult<(), FlowyError> {
let name = name.to_string();
let database_manager = self.0.clone();
@ -296,12 +288,37 @@ impl FolderOperationHandler for DatabaseFolderOperation {
Ok(())
})
}
fn did_update_view(&self, old: &View, new: &View) -> FutureResult<(), FlowyError> {
let database_layout = match new.layout {
ViewLayout::Document => {
return FutureResult::new(async {
Err(FlowyError::internal().context("Can't handle document layout type"))
});
},
ViewLayout::Grid => DatabaseLayoutPB::Grid,
ViewLayout::Board => DatabaseLayoutPB::Board,
ViewLayout::Calendar => DatabaseLayoutPB::Calendar,
};
let database_manager = self.0.clone();
let view_id = new.id.clone();
if old.layout != new.layout {
FutureResult::new(async move {
database_manager
.update_database_layout(&view_id, database_layout)
.await?;
Ok(())
})
} else {
FutureResult::new(async move { Ok(()) })
}
}
}
#[derive(Debug, serde::Deserialize)]
struct CreateDatabaseExtParams {
database_id: String,
duplicated_view_id: Option<String>,
}
impl CreateDatabaseExtParams {

View File

@ -134,3 +134,18 @@ pub struct MoveCalendarEventPB {
#[pb(index = 2)]
pub timestamp: i64,
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct NoDateCalendarEventPB {
#[pb(index = 1)]
pub row_id: String,
#[pb(index = 2)]
pub title: String,
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct RepeatedNoDateCalendarEventPB {
#[pb(index = 1)]
pub items: Vec<NoDateCalendarEventPB>,
}

View File

@ -3,10 +3,11 @@ use collab_database::user::DatabaseRecord;
use collab_database::views::DatabaseLayout;
use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode;
use flowy_error::{ErrorCode, FlowyError};
use crate::entities::parser::NotEmptyStr;
use crate::entities::{DatabaseLayoutPB, FieldIdPB, RowPB};
use crate::services::database::CreateDatabaseViewParams;
/// [DatabasePB] describes how many fields and blocks the grid has
#[derive(Debug, Clone, Default, ProtoBuf)]
@ -19,12 +20,34 @@ pub struct DatabasePB {
#[pb(index = 3)]
pub rows: Vec<RowPB>,
#[pb(index = 4)]
pub layout_type: DatabaseLayoutPB,
}
#[derive(ProtoBuf, Default)]
pub struct CreateDatabasePayloadPB {
pub struct CreateDatabaseViewPayloadPB {
#[pb(index = 1)]
pub name: String,
#[pb(index = 2)]
pub view_id: String,
#[pb(index = 3)]
pub layout_type: DatabaseLayoutPB,
}
impl TryInto<CreateDatabaseViewParams> for CreateDatabaseViewPayloadPB {
type Error = FlowyError;
fn try_into(self) -> Result<CreateDatabaseViewParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
Ok(CreateDatabaseViewParams {
name: self.name,
view_id: view_id.0,
layout_type: self.layout_type.into(),
})
}
}
#[derive(Clone, ProtoBuf, Default, Debug)]
@ -198,7 +221,7 @@ impl TryInto<DatabaseGroupIdParams> for DatabaseGroupIdPB {
}
}
#[derive(Clone, ProtoBuf, Default, Debug)]
pub struct DatabaseLayoutIdPB {
pub struct DatabaseLayoutMetaPB {
#[pb(index = 1)]
pub view_id: String,
@ -207,18 +230,18 @@ pub struct DatabaseLayoutIdPB {
}
#[derive(Clone, Debug)]
pub struct DatabaseLayoutId {
pub struct DatabaseLayoutMeta {
pub view_id: String,
pub layout: DatabaseLayout,
}
impl TryInto<DatabaseLayoutId> for DatabaseLayoutIdPB {
impl TryInto<DatabaseLayoutMeta> for DatabaseLayoutMetaPB {
type Error = ErrorCode;
fn try_into(self) -> Result<DatabaseLayoutId, Self::Error> {
fn try_into(self) -> Result<DatabaseLayoutMeta, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
let layout = self.layout.into();
Ok(DatabaseLayoutId {
Ok(DatabaseLayoutMeta {
view_id: view_id.0,
layout,
})

View File

@ -121,7 +121,7 @@ pub struct UpdatedRowPB {
#[pb(index = 1)]
pub row: RowPB,
// represents as the cells that were updated in this row.
// Indicates the field ids of the cells that were updated in this row.
#[pb(index = 2)]
pub field_ids: Vec<String>,
}

View File

@ -18,10 +18,10 @@ use crate::services::setting::CalendarLayoutSetting;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct DatabaseViewSettingPB {
#[pb(index = 1)]
pub current_layout: DatabaseLayoutPB,
pub layout_type: DatabaseLayoutPB,
#[pb(index = 2)]
pub layout_setting: LayoutSettingPB,
pub layout_setting: DatabaseLayoutSettingPB,
#[pb(index = 3)]
pub filters: RepeatedFilterPB,
@ -72,8 +72,8 @@ pub struct DatabaseSettingChangesetPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub layout_type: DatabaseLayoutPB,
#[pb(index = 2, one_of)]
pub layout_type: Option<DatabaseLayoutPB>,
#[pb(index = 3, one_of)]
pub update_filter: Option<UpdateFilterPayloadPB>,
@ -121,7 +121,7 @@ impl TryInto<DatabaseSettingChangesetParams> for DatabaseSettingChangesetPB {
Ok(DatabaseSettingChangesetParams {
view_id,
layout_type: self.layout_type.into(),
layout_type: self.layout_type.map(|ty| ty.into()),
insert_filter,
delete_filter,
alert_sort,
@ -132,7 +132,7 @@ impl TryInto<DatabaseSettingChangesetParams> for DatabaseSettingChangesetPB {
pub struct DatabaseSettingChangesetParams {
pub view_id: String,
pub layout_type: DatabaseLayout,
pub layout_type: Option<DatabaseLayout>,
pub insert_filter: Option<UpdateFilterParams>,
pub delete_filter: Option<DeleteFilterParams>,
pub alert_sort: Option<UpdateSortParams>,
@ -146,19 +146,24 @@ impl DatabaseSettingChangesetParams {
}
#[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)]
pub struct LayoutSettingPB {
#[pb(index = 1, one_of)]
pub struct DatabaseLayoutSettingPB {
#[pb(index = 1)]
pub layout_type: DatabaseLayoutPB,
#[pb(index = 2, one_of)]
pub calendar: Option<CalendarLayoutSettingPB>,
}
#[derive(Debug, Clone, Default)]
pub struct LayoutSettingParams {
pub layout_type: DatabaseLayout,
pub calendar: Option<CalendarLayoutSetting>,
}
impl From<LayoutSettingParams> for LayoutSettingPB {
impl From<LayoutSettingParams> for DatabaseLayoutSettingPB {
fn from(data: LayoutSettingParams) -> Self {
Self {
layout_type: data.layout_type.into(),
calendar: data.calendar.map(|calendar| calendar.into()),
}
}
@ -169,13 +174,17 @@ pub struct LayoutSettingChangesetPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2, one_of)]
#[pb(index = 2)]
pub layout_type: DatabaseLayoutPB,
#[pb(index = 3, one_of)]
pub calendar: Option<CalendarLayoutSettingPB>,
}
#[derive(Debug)]
pub struct LayoutSettingChangeset {
pub view_id: String,
pub layout_type: DatabaseLayout,
pub calendar: Option<CalendarLayoutSetting>,
}
@ -189,6 +198,7 @@ impl TryInto<LayoutSettingChangeset> for LayoutSettingChangesetPB {
Ok(LayoutSettingChangeset {
view_id,
layout_type: self.layout_type.into(),
calendar: self.calendar.map(|calendar| calendar.into()),
})
}

View File

@ -2,7 +2,6 @@ use collab_database::database::gen_row_id;
use std::sync::Arc;
use collab_database::rows::RowId;
use collab_database::views::DatabaseLayout;
use lib_infra::util::timestamp;
use flowy_error::{FlowyError, FlowyResult};
@ -25,7 +24,7 @@ pub(crate) async fn get_database_data_handler(
) -> DataResult<DatabasePB, FlowyError> {
let view_id: DatabaseViewIdPB = data.into_inner();
let database_editor = manager.get_database_with_view_id(view_id.as_ref()).await?;
let data = database_editor.get_database_data(view_id.as_ref()).await;
let data = database_editor.get_database_data(view_id.as_ref()).await?;
data_result_ok(data)
}
@ -64,6 +63,12 @@ pub(crate) async fn update_database_setting_handler(
if let Some(delete_sort) = params.delete_sort {
editor.delete_sort(delete_sort).await?;
}
if let Some(layout_type) = params.layout_type {
editor
.update_view_layout(&params.view_id, layout_type)
.await?;
}
Ok(())
}
@ -626,24 +631,25 @@ pub(crate) async fn set_layout_setting_handler(
let params: LayoutSettingChangeset = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
let layout_params = LayoutSettingParams {
layout_type: params.layout_type,
calendar: params.calendar,
};
database_editor
.set_layout_setting(&params.view_id, DatabaseLayout::Calendar, layout_params)
.set_layout_setting(&params.view_id, layout_params)
.await;
Ok(())
}
pub(crate) async fn get_layout_setting_handler(
data: AFPluginData<DatabaseLayoutIdPB>,
data: AFPluginData<DatabaseLayoutMetaPB>,
manager: AFPluginState<Arc<DatabaseManager2>>,
) -> DataResult<LayoutSettingPB, FlowyError> {
let params: DatabaseLayoutId = data.into_inner().try_into()?;
) -> DataResult<DatabaseLayoutSettingPB, FlowyError> {
let params: DatabaseLayoutMeta = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
let layout_setting_pb = database_editor
.get_layout_setting(&params.view_id, params.layout)
.await
.map(LayoutSettingPB::from)
.map(DatabaseLayoutSettingPB::from)
.unwrap_or_default();
data_result_ok(layout_setting_pb)
}
@ -661,6 +667,19 @@ pub(crate) async fn get_calendar_events_handler(
data_result_ok(RepeatedCalendarEventPB { items: events })
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn get_no_date_calendar_events_handler(
data: AFPluginData<CalendarEventRequestPB>,
manager: AFPluginState<Arc<DatabaseManager2>>,
) -> DataResult<RepeatedNoDateCalendarEventPB, FlowyError> {
let params: CalendarEventRequestParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
let _events = database_editor
.get_all_no_date_calendar_events(&params.view_id)
.await;
todo!()
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn get_calendar_event_handler(
data: AFPluginData<RowIdPB>,
@ -699,3 +718,12 @@ pub(crate) async fn move_calendar_event_handler(
.await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip_all, err)]
pub(crate) async fn create_database_view(
_data: AFPluginData<CreateDatabaseViewPayloadPB>,
_manager: AFPluginState<Arc<DatabaseManager2>>,
) -> FlowyResult<()> {
// let data: CreateDatabaseViewParams = data.into_inner().try_into()?;
Ok(())
}

View File

@ -60,12 +60,13 @@ pub fn init(database_manager: Arc<DatabaseManager2>) -> AFPlugin {
.event(DatabaseEvent::GetDatabases, get_databases_handler)
// Calendar
.event(DatabaseEvent::GetAllCalendarEvents, get_calendar_events_handler)
.event(DatabaseEvent::GetNoDateCalendarEvents, get_no_date_calendar_events_handler)
.event(DatabaseEvent::GetCalendarEvent, get_calendar_event_handler)
.event(DatabaseEvent::MoveCalendarEvent, move_calendar_event_handler)
// Layout setting
.event(DatabaseEvent::SetLayoutSetting, set_layout_setting_handler)
.event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler)
// import
.event(DatabaseEvent::CreateDatabaseView, create_database_view)
}
/// [DatabaseEvent] defines events that are used to interact with the Grid. You could check [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/backend/protobuf)
@ -265,15 +266,24 @@ pub enum DatabaseEvent {
#[event(input = "LayoutSettingChangesetPB")]
SetLayoutSetting = 121,
#[event(input = "DatabaseLayoutIdPB", output = "LayoutSettingPB")]
#[event(input = "DatabaseLayoutMetaPB", output = "DatabaseLayoutSettingPB")]
GetLayoutSetting = 122,
#[event(input = "CalendarEventRequestPB", output = "RepeatedCalendarEventPB")]
GetAllCalendarEvents = 123,
#[event(
input = "CalendarEventRequestPB",
output = "RepeatedNoDateCalendarEventPB"
)]
GetNoDateCalendarEvents = 124,
#[event(input = "RowIdPB", output = "CalendarEventPB")]
GetCalendarEvent = 124,
GetCalendarEvent = 125,
#[event(input = "MoveCalendarEventPB")]
MoveCalendarEvent = 125,
MoveCalendarEvent = 126,
#[event(input = "CreateDatabaseViewPayloadPB")]
CreateDatabaseView = 130,
}

View File

@ -171,8 +171,7 @@ impl DatabaseManager2 {
name: String,
layout: DatabaseLayoutPB,
database_id: String,
target_view_id: String,
duplicated_view_id: Option<String>,
database_view_id: String,
) -> FlowyResult<()> {
self.with_user_database(
Err(FlowyError::internal().context("Create database view failed")),
@ -180,15 +179,8 @@ impl DatabaseManager2 {
let database = user_database
.get_database(&database_id)
.ok_or_else(FlowyError::record_not_found)?;
match duplicated_view_id {
None => {
let params = CreateViewParams::new(database_id, target_view_id, name, layout.into());
let params = CreateViewParams::new(database_id, database_view_id, name, layout.into());
database.create_linked_view(params)?;
},
Some(duplicated_view_id) => {
database.duplicate_linked_view(&duplicated_view_id);
},
}
Ok(())
},
)?;
@ -228,6 +220,15 @@ impl DatabaseManager2 {
database.export_csv(style).await
}
pub async fn update_database_layout(
&self,
view_id: &str,
layout: DatabaseLayoutPB,
) -> FlowyResult<()> {
let database = self.get_database_with_view_id(view_id).await?;
database.update_view_layout(view_id, layout.into()).await
}
fn with_user_database<F, Output>(&self, default_value: Output, f: F) -> Output
where
F: FnOnce(&InnerUserDatabase) -> Output,

View File

@ -35,6 +35,8 @@ pub enum DatabaseNotification {
DidUpdateLayoutSettings = 80,
// Trigger when the layout field of the database is changed
DidSetNewLayoutField = 81,
// Trigger when the layout of the database is changed
DidUpdateDatabaseLayout = 82,
}
impl std::default::Default for DatabaseNotification {

View File

@ -18,8 +18,9 @@ use crate::entities::{
CalendarEventPB, CellChangesetNotifyPB, CellPB, ChecklistCellDataPB, DatabaseFieldChangesetPB,
DatabasePB, DatabaseViewSettingPB, DeleteFilterParams, DeleteGroupParams, DeleteSortParams,
FieldChangesetParams, FieldIdPB, FieldPB, FieldType, GroupPB, IndexFieldPB, InsertedRowPB,
LayoutSettingParams, RepeatedFilterPB, RepeatedGroupPB, RepeatedSortPB, RowPB, RowsChangePB,
SelectOptionCellDataPB, SelectOptionPB, UpdateFilterParams, UpdateSortParams,
LayoutSettingParams, NoDateCalendarEventPB, RepeatedFilterPB, RepeatedGroupPB, RepeatedSortPB,
RowPB, RowsChangePB, SelectOptionCellDataPB, SelectOptionPB, UpdateFilterParams,
UpdateSortParams, UpdatedRowPB,
};
use crate::notification::{send_notification, DatabaseNotification};
use crate::services::cell::{
@ -38,6 +39,7 @@ use crate::services::filter::Filter;
use crate::services::group::{
default_group_setting, GroupSetting, GroupSettingChangeset, RowChangeset,
};
use crate::services::share::csv::{CSVExport, CSVFormat};
use crate::services::sort::Sort;
@ -76,6 +78,17 @@ impl DatabaseEditor {
pub async fn close(&self) {}
pub async fn update_view_layout(
&self,
view_id: &str,
layout_type: DatabaseLayout,
) -> FlowyResult<()> {
let view_editor = self.database_views.get_view_editor(view_id).await?;
view_editor.v_update_layout_type(layout_type).await?;
Ok(())
}
pub async fn subscribe_view_changed(
&self,
view_id: &str,
@ -442,7 +455,7 @@ impl DatabaseEditor {
let (field, cell) = {
let database = self.database.lock();
let field = database.fields.get_field(field_id);
let cell = database.get_cell(field_id, &row_id);
let cell = database.get_cell(field_id, &row_id).cell;
(field, cell)
};
@ -484,12 +497,7 @@ impl DatabaseEditor {
Err(FlowyError::internal().context(msg))
},
}?;
(
field,
database
.get_cell(field_id, &row_id)
.map(|row_cell| row_cell.cell),
)
(field, database.get_cell(field_id, &row_id).cell)
};
let new_cell =
apply_cell_changeset(cell_changeset, cell, &field, Some(self.cell_cache.clone()))?;
@ -529,6 +537,15 @@ impl DatabaseEditor {
let option_row = self.database.lock().get_row(&row_id);
if let Some(new_row) = option_row {
let updated_row = UpdatedRowPB {
row: RowPB::from(&new_row),
field_ids: vec![field_id.to_string()],
};
let changes = RowsChangePB::from_update(view_id.to_string(), updated_row);
send_notification(view_id, DatabaseNotification::DidUpdateViewRows)
.payload(changes)
.send();
for view in self.database_views.editors().await {
view.v_did_update_row(&old_row, &new_row, field_id).await;
}
@ -646,10 +663,10 @@ impl DatabaseEditor {
match field {
None => SelectOptionCellDataPB::default(),
Some(field) => {
let row_cell = self.database.lock().get_cell(field_id, &row_id);
let ids = match row_cell {
let cell = self.database.lock().get_cell(field_id, &row_id).cell;
let ids = match cell {
None => SelectOptionIds::new(),
Some(row_cell) => SelectOptionIds::from(&row_cell.cell),
Some(cell) => SelectOptionIds::from(&cell),
};
match select_type_option_from_field(&field) {
Ok(type_option) => type_option.get_selected_options(ids).into(),
@ -661,9 +678,9 @@ impl DatabaseEditor {
pub async fn get_checklist_option(&self, row_id: RowId, field_id: &str) -> ChecklistCellDataPB {
let row_cell = self.database.lock().get_cell(field_id, &row_id);
let cell_data = match row_cell {
let cell_data = match row_cell.cell {
None => ChecklistCellData::default(),
Some(row_cell) => ChecklistCellData::from(&row_cell.cell),
Some(cell) => ChecklistCellData::from(&cell),
};
ChecklistCellDataPB::from(cell_data)
}
@ -763,14 +780,9 @@ impl DatabaseEditor {
Ok(())
}
pub async fn set_layout_setting(
&self,
view_id: &str,
layout_ty: DatabaseLayout,
layout_setting: LayoutSettingParams,
) {
pub async fn set_layout_setting(&self, view_id: &str, layout_setting: LayoutSettingParams) {
if let Ok(view) = self.database_views.get_view_editor(view_id).await {
let _ = view.v_set_layout_settings(&layout_ty, layout_setting).await;
let _ = view.v_set_layout_settings(layout_setting).await;
}
}
@ -795,6 +807,15 @@ impl DatabaseEditor {
}
}
#[tracing::instrument(level = "trace", skip_all)]
pub async fn get_all_no_date_calendar_events(
&self,
view_id: &str,
) -> FlowyResult<Vec<NoDateCalendarEventPB>> {
let _database_view = self.database_views.get_view_editor(view_id).await?;
todo!()
}
#[tracing::instrument(level = "trace", skip_all)]
pub async fn get_calendar_event(&self, view_id: &str, row_id: RowId) -> Option<CalendarEventPB> {
let view = self.database_views.get_view_editor(view_id).await.ok()?;
@ -858,8 +879,13 @@ impl DatabaseEditor {
Ok(database_view_setting_pb_from_view(view))
}
pub async fn get_database_data(&self, view_id: &str) -> DatabasePB {
let rows = self.get_rows(view_id).await.unwrap_or_default();
pub async fn get_database_data(&self, view_id: &str) -> FlowyResult<DatabasePB> {
let database_view = self.database_views.get_view_editor(view_id).await?;
let view = database_view
.get_view()
.await
.ok_or(FlowyError::record_not_found())?;
let rows = database_view.v_get_rows().await;
let (database_id, fields) = {
let database = self.database.lock();
let database_id = database.get_database_id();
@ -876,11 +902,12 @@ impl DatabaseEditor {
.into_iter()
.map(|row| RowPB::from(row.as_ref()))
.collect::<Vec<RowPB>>();
DatabasePB {
Ok(DatabasePB {
id: database_id,
fields,
rows,
}
layout_type: view.layout.into(),
})
}
pub async fn export_csv(&self, style: CSVFormat) -> FlowyResult<String> {
@ -946,7 +973,7 @@ struct DatabaseViewDataImpl {
}
impl DatabaseViewData for DatabaseViewDataImpl {
fn get_view_setting(&self, view_id: &str) -> Fut<Option<DatabaseView>> {
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>> {
let view = self.database.lock().get_view(view_id);
to_fut(async move { view })
}
@ -966,6 +993,26 @@ impl DatabaseViewData for DatabaseViewDataImpl {
to_fut(async move { field })
}
fn create_field(
&self,
view_id: &str,
name: &str,
field_type: FieldType,
type_option_data: TypeOptionData,
) -> Fut<Field> {
let (_, field) = self.database.lock().create_default_field(
view_id,
name.to_string(),
field_type.clone().into(),
|field| {
field
.type_options
.insert(field_type.to_string(), type_option_data);
},
);
to_fut(async move { field })
}
fn get_primary_field(&self) -> Fut<Option<Arc<Field>>> {
let field = self
.database
@ -1002,18 +1049,13 @@ impl DatabaseViewData for DatabaseViewDataImpl {
to_fut(async move { cells.into_iter().map(Arc::new).collect() })
}
fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut<Option<Arc<RowCell>>> {
fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut<Arc<RowCell>> {
let cell = self.database.lock().get_cell(field_id, row_id);
to_fut(async move { cell.map(Arc::new) })
to_fut(async move { Arc::new(cell) })
}
fn get_layout_for_view(&self, view_id: &str) -> DatabaseLayout {
self
.database
.lock()
.views
.get_view_layout(view_id)
.unwrap_or_default()
self.database.lock().views.get_database_view_layout(view_id)
}
fn get_group_setting(&self, view_id: &str) -> Vec<GroupSetting> {
@ -1077,11 +1119,7 @@ impl DatabaseViewData for DatabaseViewDataImpl {
}
fn get_layout_setting(&self, view_id: &str, layout_ty: &DatabaseLayout) -> Option<LayoutSetting> {
self
.database
.lock()
.views
.get_layout_setting(view_id, layout_ty)
self.database.lock().get_layout_setting(view_id, layout_ty)
}
fn insert_layout_setting(
@ -1096,6 +1134,17 @@ impl DatabaseViewData for DatabaseViewDataImpl {
.insert_layout_setting(view_id, layout_ty, layout_setting);
}
fn get_layout_type(&self, view_id: &str) -> DatabaseLayout {
self.database.lock().views.get_database_view_layout(view_id)
}
fn update_layout_type(&self, view_id: &str, layout_type: &DatabaseLayout) {
self
.database
.lock()
.update_layout_type(view_id, layout_type);
}
fn get_task_scheduler(&self) -> Arc<RwLock<TaskDispatcher>> {
self.task_scheduler.clone()
}

View File

@ -1,5 +1,5 @@
use collab_database::rows::RowId;
use collab_database::views::RowOrder;
use collab_database::views::{DatabaseLayout, RowOrder};
#[derive(Debug, Clone)]
pub enum DatabaseRowEvent {
@ -25,3 +25,10 @@ pub struct UpdatedRow {
// represents as the cells that were updated in this row.
pub field_ids: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct CreateDatabaseViewParams {
pub name: String,
pub view_id: String,
pub layout_type: DatabaseLayout,
}

View File

@ -1,6 +1,6 @@
use crate::entities::{
CalendarLayoutSettingPB, DatabaseLayoutPB, DatabaseViewSettingPB, FilterPB, GroupSettingPB,
LayoutSettingPB, SortPB,
CalendarLayoutSettingPB, DatabaseLayoutPB, DatabaseLayoutSettingPB, DatabaseViewSettingPB,
FilterPB, GroupSettingPB, SortPB,
};
use crate::services::filter::Filter;
use crate::services::group::GroupSetting;
@ -9,17 +9,18 @@ use crate::services::sort::Sort;
use collab_database::views::DatabaseView;
pub(crate) fn database_view_setting_pb_from_view(view: DatabaseView) -> DatabaseViewSettingPB {
let layout_type: DatabaseLayoutPB = view.layout.clone().into();
let layout_setting = if let Some(layout_setting) = view.layout_settings.get(&view.layout) {
let calendar_setting =
CalendarLayoutSettingPB::from(CalendarLayoutSetting::from(layout_setting.clone()));
LayoutSettingPB {
DatabaseLayoutSettingPB {
layout_type: layout_type.clone(),
calendar: Some(calendar_setting),
}
} else {
LayoutSettingPB::default()
DatabaseLayoutSettingPB::default()
};
let current_layout: DatabaseLayoutPB = view.layout.into();
let filters = view
.filters
.into_iter()
@ -47,7 +48,7 @@ pub(crate) fn database_view_setting_pb_from_view(view: DatabaseView) -> Database
.collect::<Vec<SortPB>>();
DatabaseViewSettingPB {
current_layout,
layout_type,
filters: filters.into(),
group_settings: group_settings.into(),
sorts: sorts.into(),

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use collab_database::database::{gen_database_filter_id, gen_database_sort_id};
use collab_database::fields::Field;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cells, Row, RowCell, RowId};
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting, RowOrder};
use tokio::sync::{broadcast, RwLock};
@ -13,9 +13,9 @@ use flowy_task::TaskDispatcher;
use lib_infra::future::Fut;
use crate::entities::{
CalendarEventPB, DeleteFilterParams, DeleteGroupParams, DeleteSortParams, FieldType,
GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedRowPB, LayoutSettingPB,
LayoutSettingParams, RowPB, RowsChangePB, SortChangesetNotificationPB, SortPB,
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams,
DeleteGroupParams, DeleteSortParams, FieldType, GroupChangesPB, GroupPB, GroupRowsNotificationPB,
InsertedRowPB, LayoutSettingParams, RowPB, RowsChangePB, SortChangesetNotificationPB, SortPB,
UpdateFilterParams, UpdateSortParams,
};
use crate::notification::{send_notification, DatabaseNotification};
@ -31,7 +31,7 @@ use crate::services::database_view::{
notify_did_update_setting, notify_did_update_sort, DatabaseViewChangedNotifier,
DatabaseViewChangedReceiverRunner,
};
use crate::services::field::TypeOptionCellDataHandler;
use crate::services::field::{DateTypeOption, TypeOptionCellDataHandler};
use crate::services::filter::{
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
};
@ -42,13 +42,21 @@ use crate::services::setting::CalendarLayoutSetting;
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
pub trait DatabaseViewData: Send + Sync + 'static {
fn get_view_setting(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
/// If the field_ids is None, then it will return all the field revisions
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
/// Returns the field with the field_id
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>>;
fn create_field(
&self,
view_id: &str,
name: &str,
field_type: FieldType,
type_option_data: TypeOptionData,
) -> Fut<Field>;
fn get_primary_field(&self) -> Fut<Option<Arc<Field>>>;
/// Returns the index of the row with row_id
@ -62,7 +70,7 @@ pub trait DatabaseViewData: Send + Sync + 'static {
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>>;
fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut<Option<Arc<RowCell>>>;
fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut<Arc<RowCell>>;
fn get_layout_for_view(&self, view_id: &str) -> DatabaseLayout;
@ -99,6 +107,12 @@ pub trait DatabaseViewData: Send + Sync + 'static {
layout_setting: LayoutSetting,
);
/// Return the database layout type for the view with given view_id
/// The default layout type is [DatabaseLayout::Grid]
fn get_layout_type(&self, view_id: &str) -> DatabaseLayout;
fn update_layout_type(&self, view_id: &str, layout_type: &DatabaseLayout);
/// Returns a `TaskDispatcher` used to poll a `Task`
fn get_task_scheduler(&self) -> Arc<RwLock<TaskDispatcher>>;
@ -167,6 +181,10 @@ impl DatabaseViewEditor {
self.filter_controller.close().await;
}
pub async fn get_view(&self) -> Option<DatabaseView> {
self.delegate.get_view(&self.view_id).await
}
pub async fn v_will_create_row(&self, cells: &mut Cells, group_id: &Option<String>) {
if group_id.is_none() {
return;
@ -398,7 +416,7 @@ impl DatabaseViewEditor {
if !is_grouping_field {
self.v_update_grouping_field(field_id).await?;
if let Some(view) = self.delegate.get_view_setting(&self.view_id).await {
if let Some(view) = self.delegate.get_view(&self.view_id).await {
let setting = database_view_setting_pb_from_view(view);
notify_did_update_setting(&self.view_id, setting).await;
}
@ -571,16 +589,11 @@ impl DatabaseViewEditor {
},
}
tracing::debug!("{:?}", layout_setting);
layout_setting
}
/// Update the calendar settings and send the notification to refresh the UI
pub async fn v_set_layout_settings(
&self,
_layout_ty: &DatabaseLayout,
params: LayoutSettingParams,
) -> FlowyResult<()> {
pub async fn v_set_layout_settings(&self, params: LayoutSettingParams) -> FlowyResult<()> {
// Maybe it needs no send notification to refresh the UI
if let Some(new_calendar_setting) = params.calendar {
if let Some(field) = self
@ -593,16 +606,19 @@ impl DatabaseViewEditor {
return Err(FlowyError::unexpect_calendar_field_type());
}
let layout_ty = DatabaseLayout::Calendar;
let old_calender_setting = self.v_get_layout_settings(&layout_ty).await.calendar;
let old_calender_setting = self
.v_get_layout_settings(&params.layout_type)
.await
.calendar;
self.delegate.insert_layout_setting(
&self.view_id,
&layout_ty,
&params.layout_type,
new_calendar_setting.clone().into(),
);
let new_field_id = new_calendar_setting.field_id.clone();
let layout_setting_pb: LayoutSettingPB = LayoutSettingParams {
let layout_setting_pb: DatabaseLayoutSettingPB = LayoutSettingParams {
layout_type: params.layout_type,
calendar: Some(new_calendar_setting),
}
.into();
@ -620,8 +636,6 @@ impl DatabaseViewEditor {
.payload(layout_setting_pb)
.send();
}
} else {
tracing::warn!("Calendar setting should not be empty")
}
}
}
@ -788,6 +802,66 @@ impl DatabaseViewEditor {
Some(events)
}
#[tracing::instrument(level = "trace", skip_all)]
pub async fn v_update_layout_type(&self, layout_type: DatabaseLayout) -> FlowyResult<()> {
self
.delegate
.update_layout_type(&self.view_id, &layout_type);
// Update the layout type in the database might add a new field to the database. If the new
// layout type is a calendar and there is not date field in the database, it will add a new
// date field to the database and create the corresponding layout setting.
//
let fields = self.delegate.get_fields(&self.view_id, None).await;
let date_field_id = match fields
.into_iter()
.find(|field| FieldType::from(field.field_type) == FieldType::DateTime)
{
None => {
tracing::trace!("Create a new date field after layout type change");
let default_date_type_option = DateTypeOption::default();
let field = self
.delegate
.create_field(
&self.view_id,
"Date",
FieldType::DateTime,
default_date_type_option.into(),
)
.await;
field.id
},
Some(date_field) => date_field.id.clone(),
};
let layout_setting = self.v_get_layout_settings(&layout_type).await;
match layout_type {
DatabaseLayout::Grid => {},
DatabaseLayout::Board => {},
DatabaseLayout::Calendar => {
if layout_setting.calendar.is_none() {
let layout_setting = CalendarLayoutSetting::new(date_field_id.clone());
self
.v_set_layout_settings(LayoutSettingParams {
layout_type,
calendar: Some(layout_setting),
})
.await?;
}
},
}
let payload = DatabaseLayoutMetaPB {
view_id: self.view_id.clone(),
layout: layout_type.into(),
};
send_notification(&self.view_id, DatabaseNotification::DidUpdateDatabaseLayout)
.payload(payload)
.send();
Ok(())
}
pub async fn handle_row_event(&self, event: Cow<'_, DatabaseRowEvent>) {
let changeset = match event.into_owned() {
DatabaseRowEvent::InsertRow(row) => {

View File

@ -52,11 +52,9 @@ pub async fn new_group_controller(
let layout = delegate.get_layout_for_view(&view_id);
// If the view is a board and the grouping field is empty, we need to find a new grouping field
if layout.is_board() {
if grouping_field.is_none() {
if layout.is_board() && grouping_field.is_none() {
grouping_field = find_new_grouping_field(&fields, &layout);
}
}
if let Some(grouping_field) = grouping_field {
let rows = delegate.get_rows(&view_id).await;
@ -104,21 +102,20 @@ pub(crate) async fn get_cell_for_row(
row_id: &RowId,
) -> Option<RowSingleCellData> {
let field = delegate.get_field(field_id).await?;
let cell = delegate.get_cell_in_row(field_id, row_id).await?;
let row_cell = delegate.get_cell_in_row(field_id, row_id).await;
let field_type = FieldType::from(field.field_type);
let handler = delegate.get_type_option_cell_handler(&field, &field_type)?;
if let Some(handler) = delegate.get_type_option_cell_handler(&field, &field_type) {
return match handler.get_cell_data(&cell, &field_type, &field) {
Ok(cell_data) => Some(RowSingleCellData {
row_id: cell.row_id.clone(),
let cell_data = match &row_cell.cell {
None => None,
Some(cell) => handler.get_cell_data(&cell, &field_type, &field).ok(),
};
Some(RowSingleCellData {
row_id: row_cell.row_id.clone(),
field_id: field.id.clone(),
field_type: field_type.clone(),
cell_data,
}),
Err(_) => None,
};
}
None
})
}
// Returns the list of cells corresponding to the given field.
@ -133,17 +130,18 @@ pub(crate) async fn get_cells_for_field(
let cells = delegate.get_cells_for_field(view_id, field_id).await;
return cells
.iter()
.flat_map(
|cell| match handler.get_cell_data(cell, &field_type, &field) {
Ok(cell_data) => Some(RowSingleCellData {
row_id: cell.row_id.clone(),
.map(|row_cell| {
let cell_data = match &row_cell.cell {
None => None,
Some(cell) => handler.get_cell_data(&cell, &field_type, &field).ok(),
};
RowSingleCellData {
row_id: row_cell.row_id.clone(),
field_id: field.id.clone(),
field_type: field_type.clone(),
cell_data,
}),
Err(_) => None,
},
)
}
})
.collect();
}
}

View File

@ -19,7 +19,7 @@ use std::str::FromStr;
/// The [DateTypeOption] is used by [FieldType::Date], [FieldType::LastEditedTime], and [FieldType::CreatedTime].
/// So, storing the field type is necessary to distinguish the field type.
/// Most of the cases, each [FieldType] has its own [TypeOption] implementation.
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DateTypeOption {
pub date_format: DateFormat,
pub time_format: TimeFormat,
@ -27,6 +27,17 @@ pub struct DateTypeOption {
pub field_type: FieldType,
}
impl Default for DateTypeOption {
fn default() -> Self {
Self {
date_format: Default::default(),
time_format: Default::default(),
timezone_id: Default::default(),
field_type: FieldType::DateTime,
}
}
}
impl TypeOption for DateTypeOption {
type CellData = DateCellData;
type CellChangeset = DateCellChangeset;

View File

@ -498,14 +498,14 @@ pub struct RowSingleCellData {
pub row_id: RowId,
pub field_id: String,
pub field_type: FieldType,
pub cell_data: BoxCellData,
pub cell_data: Option<BoxCellData>,
}
macro_rules! into_cell_data {
($func_name:ident,$return_ty:ty) => {
#[allow(dead_code)]
pub fn $func_name(self) -> Option<$return_ty> {
self.cell_data.unbox_or_none()
self.cell_data?.unbox_or_none()
}
};
}

View File

@ -254,6 +254,7 @@ where
continue;
}
}
match self.context.get_mut_no_status_group() {
None => {},
Some(no_status_group) => no_status_group.add_row((*row).clone()),
@ -349,12 +350,12 @@ where
deleted_group: None,
row_changesets: vec![],
};
let cell_rev = match context.row.cells.get(&self.grouping_field_id) {
Some(cell_rev) => Some(cell_rev.clone()),
let cell = match context.row.cells.get(&self.grouping_field_id) {
Some(cell) => Some(cell.clone()),
None => self.placeholder_cell(),
};
if let Some(cell) = cell_rev {
if let Some(cell) = cell {
let cell_bytes = get_cell_protobuf(&cell, context.field, None);
let cell_data = cell_bytes.parser::<P>()?;
result.deleted_group = self.delete_group_when_move_row(context.row, &cell_data);

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupRowsNotificationPB, SelectOptionCellDataPB};
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
use crate::services::cell::insert_select_option_cell;
use crate::services::field::{MultiSelectTypeOption, SelectOptionCellDataParser};
use crate::services::group::action::GroupCustomize;
@ -10,7 +10,7 @@ use crate::services::group::{
move_group_row, remove_select_option_row, GeneratedGroups, GroupContext,
};
use collab_database::fields::Field;
use collab_database::rows::{Cells, Row};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row};
use std::sync::Arc;
use serde::{Deserialize, Serialize};
@ -39,6 +39,14 @@ impl GroupCustomize for MultiSelectGroupController {
.any(|option| option.id == content)
}
fn placeholder_cell(&self) -> Option<Cell> {
Some(
new_cell_builder(FieldType::MultiSelect)
.insert_str_value("data", "")
.build(),
)
}
fn add_or_remove_row_when_cell_changed(
&mut self,
row: &Row,

View File

@ -1,9 +1,9 @@
use crate::entities::{GroupRowsNotificationPB, SelectOptionCellDataPB};
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
use crate::services::cell::insert_select_option_cell;
use crate::services::field::{SelectOptionCellDataParser, SingleSelectTypeOption};
use crate::services::group::action::GroupCustomize;
use collab_database::fields::Field;
use collab_database::rows::{Cells, Row};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row};
use std::sync::Arc;
use crate::services::group::controller::{
@ -39,6 +39,14 @@ impl GroupCustomize for SingleSelectGroupController {
.any(|option| option.id == content)
}
fn placeholder_cell(&self) -> Option<Cell> {
Some(
new_cell_builder(FieldType::SingleSelect)
.insert_str_value("data", "")
.build(),
)
}
fn add_or_remove_row_when_cell_changed(
&mut self,
row: &Row,

View File

@ -3,10 +3,10 @@ mod configuration;
mod controller;
mod controller_impls;
mod entities;
mod group_util;
mod group_builder;
pub(crate) use configuration::*;
pub(crate) use controller::*;
pub(crate) use controller_impls::*;
pub(crate) use entities::*;
pub(crate) use group_util::*;
pub(crate) use group_builder::*;

View File

@ -74,8 +74,8 @@ async fn text_cell_data_test() {
.get_cells_for_field(&test.view_id, &text_field.id)
.await;
for (i, cell) in cells.into_iter().enumerate() {
let text = StrCellData::from(cell.as_ref());
for (i, row_cell) in cells.into_iter().enumerate() {
let text = StrCellData::from(row_cell.cell.as_ref().unwrap());
match i {
0 => assert_eq!(text.as_str(), "A"),
1 => assert_eq!(text.as_str(), ""),
@ -97,13 +97,15 @@ async fn url_cell_data_test() {
.get_cells_for_field(&test.view_id, &url_field.id)
.await;
for (i, cell) in cells.into_iter().enumerate() {
let cell = URLCellData::from(cell.as_ref());
for (i, row_cell) in cells.into_iter().enumerate() {
if let Some(cell) = row_cell.cell.as_ref() {
let cell = URLCellData::from(cell);
if i == 0 {
assert_eq!(cell.url.as_str(), "https://www.appflowy.io/");
}
}
}
}
#[tokio::test]
async fn update_updated_at_field_on_other_cell_update() {
@ -135,8 +137,10 @@ async fn update_updated_at_field_on_other_cell_update() {
.get_cells_for_field(&test.view_id, &updated_at_field.id)
.await;
assert!(!cells.is_empty());
for (i, cell) in cells.into_iter().enumerate() {
let timestamp = DateCellData::from(cell.as_ref()).timestamp.unwrap();
for (i, row_cell) in cells.into_iter().enumerate() {
let timestamp = DateCellData::from(row_cell.cell.as_ref().unwrap())
.timestamp
.unwrap();
println!(
"{}, bf: {}, af: {}",
timestamp, before_update_timestamp, after_update_timestamp

View File

@ -6,7 +6,7 @@ use collab_database::fields::Field;
use collab_database::rows::{CreateRowParams, Row, RowId};
use strum::EnumCount;
use flowy_database2::entities::{DatabaseLayoutPB, FieldType, FilterPB, RowPB, SelectOptionPB};
use flowy_database2::entities::{FieldType, FilterPB, RowPB, SelectOptionPB};
use flowy_database2::services::cell::{CellBuilder, ToCellChangeset};
use flowy_database2::services::database::DatabaseEditor;
use flowy_database2::services::field::checklist_type_option::{
@ -21,7 +21,9 @@ use flowy_error::FlowyResult;
use flowy_test::folder_event::ViewTest;
use flowy_test::FlowyCoreTest;
use crate::database::mock_data::{make_test_board, make_test_calendar, make_test_grid};
use crate::database::mock_data::{
make_no_date_test_grid, make_test_board, make_test_calendar, make_test_grid,
};
pub struct DatabaseEditorTest {
pub sdk: FlowyCoreTest,
@ -36,35 +38,42 @@ pub struct DatabaseEditorTest {
impl DatabaseEditorTest {
pub async fn new_grid() -> Self {
Self::new(DatabaseLayoutPB::Grid).await
let sdk = FlowyCoreTest::new();
let _ = sdk.init_user().await;
let params = make_test_grid();
let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await;
Self::new(sdk, view_test).await
}
pub async fn new_no_date_grid() -> Self {
let sdk = FlowyCoreTest::new();
let _ = sdk.init_user().await;
let params = make_no_date_test_grid();
let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await;
Self::new(sdk, view_test).await
}
pub async fn new_board() -> Self {
Self::new(DatabaseLayoutPB::Board).await
let sdk = FlowyCoreTest::new();
let _ = sdk.init_user().await;
let params = make_test_board();
let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await;
Self::new(sdk, view_test).await
}
pub async fn new_calendar() -> Self {
Self::new(DatabaseLayoutPB::Calendar).await
}
pub async fn new(layout: DatabaseLayoutPB) -> Self {
let sdk = FlowyCoreTest::new();
let _ = sdk.init_user().await;
let test = match layout {
DatabaseLayoutPB::Grid => {
let params = make_test_grid();
ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await
},
DatabaseLayoutPB::Board => {
let data = make_test_board();
ViewTest::new_board_view(&sdk, data.to_json_bytes().unwrap()).await
},
DatabaseLayoutPB::Calendar => {
let data = make_test_calendar();
ViewTest::new_calendar_view(&sdk, data.to_json_bytes().unwrap()).await
},
};
let params = make_test_calendar();
let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await;
Self::new(sdk, view_test).await
}
pub async fn new(sdk: FlowyCoreTest, test: ViewTest) -> Self {
let editor = sdk
.database_manager
.get_database_with_view_id(&test.child_view.id)

View File

@ -265,7 +265,7 @@ impl DatabaseFilterTest {
assert_eq!(expected_setting, setting);
}
FilterScript::AssertNumberOfVisibleRows { expected } => {
let grid = self.editor.get_database_data(&self.view_id).await;
let grid = self.editor.get_database_data(&self.view_id).await.unwrap();
assert_eq!(grid.rows.len(), expected);
}
FilterScript::Wait { millisecond } => {

View File

@ -8,7 +8,9 @@ use crate::database::database_editor::DatabaseEditorTest;
pub enum LayoutScript {
AssertCalendarLayoutSetting { expected: CalendarLayoutSetting },
GetCalendarEvents,
AssertDefaultAllCalendarEvents,
AssertAllCalendarEventsCount { expected: usize },
UpdateDatabaseLayout { layout: DatabaseLayout },
}
pub struct DatabaseLayoutTest {
@ -16,6 +18,11 @@ pub struct DatabaseLayoutTest {
}
impl DatabaseLayoutTest {
pub async fn new_no_date_grid() -> Self {
let database_test = DatabaseEditorTest::new_no_date_grid().await;
Self { database_test }
}
pub async fn new_calendar() -> Self {
let database_test = DatabaseEditorTest::new_calendar().await;
Self { database_test }
@ -33,6 +40,22 @@ impl DatabaseLayoutTest {
pub async fn run_script(&mut self, script: LayoutScript) {
match script {
LayoutScript::UpdateDatabaseLayout { layout } => {
self
.database_test
.editor
.update_view_layout(&self.database_test.view_id, layout)
.await
.unwrap();
},
LayoutScript::AssertAllCalendarEventsCount { expected } => {
let events = self
.database_test
.editor
.get_all_calendar_events(&self.database_test.view_id)
.await;
assert_eq!(events.len(), expected);
},
LayoutScript::AssertCalendarLayoutSetting { expected } => {
let view_id = self.database_test.view_id.clone();
let layout_ty = DatabaseLayout::Calendar;
@ -53,7 +76,7 @@ impl DatabaseLayoutTest {
);
assert_eq!(calendar_setting.show_weekends, expected.show_weekends);
},
LayoutScript::GetCalendarEvents => {
LayoutScript::AssertDefaultAllCalendarEvents => {
let events = self
.database_test
.editor

View File

@ -1,3 +1,4 @@
use collab_database::views::DatabaseLayout;
use flowy_database2::services::setting::CalendarLayoutSetting;
use crate::database::layout_test::script::DatabaseLayoutTest;
@ -17,6 +18,18 @@ async fn calendar_initial_layout_setting_test() {
#[tokio::test]
async fn calendar_get_events_test() {
let mut test = DatabaseLayoutTest::new_calendar().await;
let scripts = vec![GetCalendarEvents];
let scripts = vec![AssertDefaultAllCalendarEvents];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_to_calendar_layout_test() {
let mut test = DatabaseLayoutTest::new_no_date_grid().await;
let scripts = vec![
UpdateDatabaseLayout {
layout: DatabaseLayout::Calendar,
},
AssertAllCalendarEventsCount { expected: 3 },
];
test.run_scripts(scripts).await;
}

View File

@ -240,3 +240,80 @@ pub fn make_test_grid() -> DatabaseData {
DatabaseData { view, fields, rows }
}
pub fn make_no_date_test_grid() -> DatabaseData {
let mut fields = vec![];
let mut rows = vec![];
// Iterate through the FieldType to create the corresponding Field.
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => {
let text_field = FieldBuilder::from_field_type(field_type.clone())
.name("Name")
.visibility(true)
.primary(true)
.build();
fields.push(text_field);
},
FieldType::Number => {
// Number
let mut type_option = NumberTypeOption::default();
type_option.set_format(NumberFormat::USD);
let number_field = FieldBuilder::new(field_type.clone(), type_option)
.name("Price")
.visibility(true)
.build();
fields.push(number_field);
},
_ => {},
}
}
for i in 0..3 {
let mut row_builder = TestRowBuilder::new(i.into(), &fields);
match i {
0 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("A"),
FieldType::Number => row_builder.insert_number_cell("1"),
_ => "".to_owned(),
};
}
},
1 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell(""),
FieldType::Number => row_builder.insert_number_cell("2"),
_ => "".to_owned(),
};
}
},
2 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("C"),
FieldType::Number => row_builder.insert_number_cell("3"),
_ => "".to_owned(),
};
}
},
_ => {},
}
let row = row_builder.build();
rows.push(row);
}
let view = DatabaseView {
id: gen_database_view_id(),
database_id: gen_database_id(),
name: "".to_string(),
layout: DatabaseLayout::Grid,
..Default::default()
};
DatabaseData { view, fields, rows }
}

View File

@ -1,5 +1,5 @@
use crate::entities::parser::view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail};
use crate::view_ext::gen_view_id;
use crate::view_operation::gen_view_id;
use collab_folder::core::{View, ViewLayout};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
@ -31,7 +31,7 @@ pub struct ViewPB {
pub fn view_pb_without_child_views(view: View) -> ViewPB {
ViewPB {
id: view.id,
parent_view_id: view.bid,
parent_view_id: view.parent_view_id,
name: view.name,
create_time: view.created_at,
child_views: Default::default(),
@ -43,7 +43,7 @@ pub fn view_pb_without_child_views(view: View) -> ViewPB {
pub fn view_pb_with_child_views(view: View, child_views: Vec<View>) -> ViewPB {
ViewPB {
id: view.id,
parent_view_id: view.bid,
parent_view_id: view.parent_view_id,
name: view.name,
create_time: view.created_at,
child_views: child_views
@ -219,6 +219,9 @@ pub struct UpdateViewPayloadPB {
#[pb(index = 4, one_of)]
pub thumbnail: Option<String>,
#[pb(index = 5, one_of)]
pub layout: Option<ViewLayoutPB>,
}
#[derive(Clone, Debug)]
@ -227,6 +230,7 @@ pub struct UpdateViewParams {
pub name: Option<String>,
pub desc: Option<String>,
pub thumbnail: Option<String>,
pub layout: Option<ViewLayout>,
}
impl TryInto<UpdateViewParams> for UpdateViewPayloadPB {
@ -255,6 +259,7 @@ impl TryInto<UpdateViewParams> for UpdateViewPayloadPB {
name,
desc,
thumbnail,
layout: self.layout.map(|ty| ty.into()),
})
}
}

View File

@ -5,7 +5,7 @@ pub mod manager;
mod notification;
pub mod protobuf;
mod user_default;
pub mod view_ext;
pub mod view_operation;
pub mod deps;
mod share;

View File

@ -29,7 +29,9 @@ use crate::notification::{
};
use crate::share::ImportParams;
use crate::user_default::DefaultFolderBuilder;
use crate::view_ext::{create_view, gen_view_id, FolderOperationHandler, FolderOperationHandlers};
use crate::view_operation::{
create_view, gen_view_id, FolderOperationHandler, FolderOperationHandlers,
};
pub struct Folder2Manager {
mutex_folder: Arc<MutexFolder>,
@ -200,18 +202,12 @@ impl Folder2Manager {
let view_layout: ViewLayout = params.layout.clone().into();
let handler = self.get_handler(&view_layout)?;
let user_id = self.user.user_id()?;
let ext = params.meta.clone();
let meta = params.meta.clone();
match params.initial_data.is_empty() {
true => {
tracing::trace!("Create view with build-in data");
handler
.create_built_in_view(
user_id,
&params.view_id,
&params.name,
view_layout.clone(),
ext,
)
.create_built_in_view(user_id, &params.view_id, &params.name, view_layout.clone())
.await?;
},
false => {
@ -223,7 +219,7 @@ impl Folder2Manager {
&params.name,
params.initial_data.clone(),
view_layout.clone(),
ext,
meta,
)
.await?;
},
@ -233,7 +229,7 @@ impl Folder2Manager {
folder.insert_view(view.clone());
});
notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.bid.clone()]);
notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id.clone()]);
Ok(view)
}
@ -336,7 +332,7 @@ impl Folder2Manager {
match view {
None => tracing::error!("Couldn't find the view. It should not be empty"),
Some(view) => {
notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.bid]);
notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id]);
},
}
Ok(())
@ -350,18 +346,23 @@ impl Folder2Manager {
#[tracing::instrument(level = "trace", skip(self), err)]
pub async fn update_view_with_params(&self, params: UpdateViewParams) -> FlowyResult<()> {
let _ = self
.mutex_folder
.lock()
.as_ref()
.ok_or_else(folder_not_init_error)?
.views
.update_view(&params.view_id, |update| {
let value = self.with_folder(None, |folder| {
let old_view = folder.views.get_view(&params.view_id);
let new_view = folder.views.update_view(&params.view_id, |update| {
update
.set_name_if_not_none(params.name)
.set_desc_if_not_none(params.desc)
.set_layout_if_not_none(params.layout)
.done()
});
Some((old_view, new_view))
});
if let Some((Some(old_view), Some(new_view))) = value {
if let Ok(handler) = self.get_handler(&old_view.layout) {
handler.did_update_view(&old_view, &new_view).await?;
}
}
if let Ok(view_pb) = self.get_view(&params.view_id).await {
notify_parent_view_did_change(
@ -388,7 +389,7 @@ impl Folder2Manager {
// meta.insert("database_id".to_string(), database_id);
// }
let duplicate_params = CreateViewParams {
parent_view_id: view.bid.clone(),
parent_view_id: view.parent_view_id.clone(),
name: format!("{} (copy)", &view.name),
desc: view.desc,
layout: view.layout.into(),
@ -501,7 +502,7 @@ impl Folder2Manager {
self.with_folder((), |folder| {
folder.insert_view(view.clone());
});
notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.bid.clone()]);
notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id.clone()]);
Ok(view)
}
@ -529,11 +530,11 @@ fn listen_on_view_change(mut rx: ViewChangeReceiver, weak_mutex_folder: &Weak<Mu
tracing::trace!("Did receive view change: {:?}", value);
match value {
ViewChange::DidCreateView { view } => {
notify_parent_view_did_change(folder.clone(), vec![view.bid]);
notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
},
ViewChange::DidDeleteView { views: _ } => {},
ViewChange::DidUpdate { view } => {
notify_parent_view_did_change(folder.clone(), vec![view.bid]);
notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
},
};
}
@ -580,7 +581,7 @@ fn listen_on_trash_change(mut rx: TrashChangeReceiver, weak_mutex_folder: &Weak<
if let Some(folder) = folder.lock().as_ref() {
let views = folder.views.get_views(&ids);
for view in views {
unique_ids.insert(view.bid);
unique_ids.insert(view.parent_view_id);
}
let repeated_trash: RepeatedTrashPB = folder.trash.get_all_trash().into();

View File

@ -1,6 +1,6 @@
use crate::entities::{CreateViewParams, ViewLayoutPB};
use crate::manager::Folder2Manager;
use crate::view_ext::gen_view_id;
use crate::view_operation::gen_view_id;
use std::collections::HashMap;
#[cfg(feature = "test_helper")]

View File

@ -1,11 +1,9 @@
use std::collections::HashMap;
use chrono::Utc;
use collab_folder::core::{FolderData, RepeatedView, View, ViewIdentifier, ViewLayout, Workspace};
use nanoid::nanoid;
use crate::entities::{view_pb_with_child_views, WorkspacePB};
use crate::view_ext::{gen_view_id, FolderOperationHandlers};
use crate::view_operation::{gen_view_id, FolderOperationHandlers};
pub struct DefaultFolderBuilder();
impl DefaultFolderBuilder {
@ -21,7 +19,7 @@ impl DefaultFolderBuilder {
let child_view_layout = ViewLayout::Document;
let child_view = View {
id: child_view_id.clone(),
bid: view_id.clone(),
parent_view_id: view_id.clone(),
name: "Read me".to_string(),
desc: "".to_string(),
created_at: time,
@ -39,14 +37,13 @@ impl DefaultFolderBuilder {
&child_view.id,
&child_view.name,
child_view_layout.clone(),
HashMap::default(),
)
.await
.unwrap();
let view = View {
id: view_id,
bid: workspace_id.clone(),
parent_view_id: workspace_id.clone(),
name: "⭐️ Getting started".to_string(),
desc: "".to_string(),
children: RepeatedView::new(vec![ViewIdentifier {

View File

@ -1,6 +1,6 @@
use crate::entities::{CreateViewParams, ViewLayoutPB};
use bytes::Bytes;
use collab_folder::core::{View, ViewLayout};
use collab_folder::core::ViewLayout;
use flowy_error::FlowyError;
use lib_infra::future::FutureResult;
use lib_infra::util::timestamp;
@ -9,6 +9,7 @@ use std::collections::HashMap;
use std::sync::Arc;
pub type ViewData = Bytes;
pub use collab_folder::core::View;
/// The handler will be used to handler the folder operation for a specific
/// view layout. Each [ViewLayout] will have a handler. So when creating a new
@ -22,7 +23,18 @@ pub trait FolderOperationHandler {
/// Returns the [ViewData] that can be used to create the same view.
fn duplicate_view(&self, view_id: &str) -> FutureResult<ViewData, FlowyError>;
/// Create a view with custom data
/// Create a view with the data.
///
/// # Arguments
///
/// * `user_id`: the user id
/// * `view_id`: the view id
/// * `name`: the name of the view
/// * `data`: initial data of the view. The data should be parsed by the [FolderOperationHandler]
/// implementation. For example, the data of the database will be [DatabaseData].
/// * `layout`: the layout of the view
/// * `meta`: use to carry extra information. For example, the database view will use this
/// to carry the reference database id.
fn create_view_with_view_data(
&self,
user_id: i64,
@ -30,7 +42,7 @@ pub trait FolderOperationHandler {
name: &str,
data: Vec<u8>,
layout: ViewLayout,
ext: HashMap<String, String>,
meta: HashMap<String, String>,
) -> FutureResult<(), FlowyError>;
/// Create a view with the pre-defined data.
@ -42,7 +54,6 @@ pub trait FolderOperationHandler {
view_id: &str,
name: &str,
layout: ViewLayout,
meta: HashMap<String, String>,
) -> FutureResult<(), FlowyError>;
/// Create a view by importing data
@ -60,6 +71,11 @@ pub trait FolderOperationHandler {
name: &str,
path: String,
) -> FutureResult<(), FlowyError>;
/// Called when the view is updated. The handler is the `old` registered handler.
fn did_update_view(&self, _old: &View, _new: &View) -> FutureResult<(), FlowyError> {
FutureResult::new(async move { Ok(()) })
}
}
pub type FolderOperationHandlers =
@ -80,7 +96,7 @@ pub(crate) fn create_view(params: CreateViewParams, layout: ViewLayout) -> View
let time = timestamp();
View {
id: params.view_id,
bid: params.parent_view_id,
parent_view_id: params.parent_view_id,
name: params.name,
desc: params.desc,
children: Default::default(),

View File

@ -274,6 +274,7 @@ pub async fn update_view(
name,
desc,
thumbnail: None,
layout: None,
};
EventBuilder::new(sdk.clone())
.event(UpdateView)