diff --git a/frontend/appflowy_flutter/assets/translations/en.json b/frontend/appflowy_flutter/assets/translations/en.json index c77f4c5e92..1a799f8ec5 100644 --- a/frontend/appflowy_flutter/assets/translations/en.json +++ b/frontend/appflowy_flutter/assets/translations/en.json @@ -372,6 +372,7 @@ }, "calendar": { "menuName": "Calendar", + "defaultNewCalendarTitle": "Untitled", "navigation": { "today": "Today", "jumpToday": "Jump to Today", @@ -379,4 +380,4 @@ "nextMonth": "Next Month" } } -} +} \ No newline at end of file diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_cache.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_cache.dart index d62a9ca858..9809a8b13d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_cache.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_cache.dart @@ -2,9 +2,9 @@ part of 'cell_service.dart'; typedef CellByFieldId = LinkedHashMap; -class GridBaseCell { +class DatabaseCell { dynamic object; - GridBaseCell({ + DatabaseCell({ required this.object, }); } @@ -44,7 +44,7 @@ class CellCache { } } - void insert(CellCacheKey key, T value) { + void insert(CellCacheKey key, T value) { var map = _cellDataByFieldId[key.fieldId]; if (map == null) { _cellDataByFieldId[key.fieldId] = {}; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller.dart index a5e036b677..9fa76e7963 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller.dart @@ -170,7 +170,7 @@ class CellController extends Equatable { _loadDataOperation = Timer(const Duration(milliseconds: 10), () { _cellDataLoader.loadData().then((data) { if (data != null) { - _cellCache.insert(_cacheKey, GridBaseCell(object: data)); + _cellCache.insert(_cacheKey, DatabaseCell(object: data)); } else { _cellCache.remove(_cacheKey); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller_builder.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller_builder.dart index d80142e3f8..42c29dc3e4 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller_builder.dart @@ -13,7 +13,7 @@ typedef SelectOptionCellController = CellController; typedef ChecklistCellController = CellController; -typedef DateCellController = CellController; +typedef DateCellController = CellController; typedef URLCellController = CellController; class CellControllerBuilder { diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_persistence.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_persistence.dart index f2b06c51c7..f0cb6531fa 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_persistence.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_persistence.dart @@ -27,24 +27,28 @@ class TextCellDataPersistence implements CellDataPersistence { } @freezed -class CalendarData with _$CalendarData { - const factory CalendarData({required DateTime date, String? time}) = - _CalendarData; +class DateCellData with _$DateCellData { + const factory DateCellData({ + required DateTime date, + String? time, + required bool includeTime, + }) = _DateCellData; } -class DateCellDataPersistence implements CellDataPersistence { +class DateCellDataPersistence implements CellDataPersistence { final CellIdentifier cellId; DateCellDataPersistence({ required this.cellId, }); @override - Future> save(CalendarData data) { + Future> save(DateCellData data) { var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId); final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString(); payload.date = date; payload.isUtc = data.date.isUtc; + payload.includeTime = data.includeTime; if (data.time != null) { payload.time = data.time!; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart new file mode 100644 index 0000000000..5f3fe587b5 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart @@ -0,0 +1,290 @@ +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; +import 'package:appflowy/plugins/database_view/application/view/view_cache.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/calendar_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:collection/collection.dart'; +import 'dart:async'; +import 'package:dartz/dartz.dart'; +import 'database_service.dart'; +import 'defines.dart'; +import 'layout/layout_setting_listener.dart'; +import 'row/row_cache.dart'; +import 'group/group_listener.dart'; + +typedef OnRowsChanged = void Function( + List rowInfos, + RowsChangedReason, +); + +typedef OnGroupByField = void Function(List); +typedef OnUpdateGroup = void Function(List); +typedef OnDeleteGroup = void Function(List); +typedef OnInsertGroup = void Function(InsertedGroupPB); + +class GroupCallbacks { + final OnGroupByField? onGroupByField; + final OnUpdateGroup? onUpdateGroup; + final OnDeleteGroup? onDeleteGroup; + final OnInsertGroup? onInsertGroup; + + GroupCallbacks({ + this.onGroupByField, + this.onUpdateGroup, + this.onDeleteGroup, + this.onInsertGroup, + }); +} + +class LayoutCallbacks { + final void Function(LayoutSettingPB) onLayoutChanged; + final void Function(LayoutSettingPB) onLoadLayout; + + LayoutCallbacks({ + required this.onLayoutChanged, + required this.onLoadLayout, + }); +} + +class DatabaseCallbacks { + OnDatabaseChanged? onDatabaseChanged; + OnRowsChanged? onRowsChanged; + OnFieldsChanged? onFieldsChanged; + OnFiltersChanged? onFiltersChanged; + DatabaseCallbacks({ + this.onDatabaseChanged, + this.onRowsChanged, + this.onFieldsChanged, + this.onFiltersChanged, + }); +} + +class DatabaseController { + final String viewId; + final DatabaseBackendService _databaseBackendSvc; + final FieldController fieldController; + late DatabaseViewCache _viewCache; + final LayoutTypePB layoutType; + + // Callbacks + DatabaseCallbacks? _databaseCallbacks; + GroupCallbacks? _groupCallbacks; + LayoutCallbacks? _layoutCallbacks; + + // Getters + List get rowInfos => _viewCache.rowInfos; + RowCache get rowCache => _viewCache.rowCache; + + // Listener + final DatabaseGroupListener groupListener; + final DatabaseLayoutListener layoutListener; + + DatabaseController({required ViewPB view, required this.layoutType}) + : viewId = view.id, + _databaseBackendSvc = DatabaseBackendService(viewId: view.id), + fieldController = FieldController(viewId: view.id), + groupListener = DatabaseGroupListener(view.id), + layoutListener = DatabaseLayoutListener(view.id) { + _viewCache = DatabaseViewCache( + viewId: viewId, + fieldController: fieldController, + ); + _listenOnRowsChanged(); + _listenOnFieldsChanged(); + _listenOnGroupChanged(); + _listenOnLayoutChanged(); + } + + void addListener({ + DatabaseCallbacks? onDatabaseChanged, + LayoutCallbacks? onLayoutChanged, + GroupCallbacks? onGroupChanged, + }) { + _layoutCallbacks = onLayoutChanged; + _databaseCallbacks = onDatabaseChanged; + _groupCallbacks = onGroupChanged; + } + + Future> open() async { + return _databaseBackendSvc.openGrid().then((result) { + return result.fold( + (database) async { + _databaseCallbacks?.onDatabaseChanged?.call(database); + _viewCache.rowCache.setInitialRows(database.rows); + return await fieldController + .loadFields( + fieldIds: database.fields, + ) + .then( + (result) { + return result.fold( + (l) => Future(() async { + await _loadGroups(); + await _loadLayoutSetting(); + return left(l); + }), + (err) => right(err), + ); + }, + ); + }, + (err) => right(err), + ); + }); + } + + Future> createRow({ + String? startRowId, + String? groupId, + void Function(RowDataBuilder builder)? withCells, + }) { + Map? cellDataByFieldId; + + if (withCells != null) { + final rowBuilder = RowDataBuilder(); + withCells(rowBuilder); + cellDataByFieldId = rowBuilder.build(); + } + + return _databaseBackendSvc.createRow( + startRowId: startRowId, + groupId: groupId, + cellDataByFieldId: cellDataByFieldId, + ); + } + + Future> moveRow(RowPB fromRow, + {RowPB? toRow, String? groupId}) { + return _databaseBackendSvc.moveRow( + fromRowId: fromRow.id, + toGroupId: groupId, + toRowId: toRow?.id, + ); + } + + Future> moveGroup( + {required String fromGroupId, required String toGroupId}) { + return _databaseBackendSvc.moveGroup( + fromGroupId: fromGroupId, + toGroupId: toGroupId, + ); + } + + Future updateCalenderLayoutSetting( + CalendarLayoutSettingsPB layoutSetting) async { + await _databaseBackendSvc + .updateLayoutSetting(calendarLayoutSetting: layoutSetting) + .then((result) { + result.fold((l) => null, (r) => Log.error(r)); + }); + } + + Future dispose() async { + await _databaseBackendSvc.closeView(); + await fieldController.dispose(); + await groupListener.stop(); + } + + Future _loadGroups() async { + final result = await _databaseBackendSvc.loadGroups(); + return Future( + () => result.fold( + (groups) { + _groupCallbacks?.onGroupByField?.call(groups.items); + }, + (err) => Log.error(err), + ), + ); + } + + Future _loadLayoutSetting() async { + _databaseBackendSvc.getLayoutSetting(layoutType).then((result) { + result.fold( + (l) { + _layoutCallbacks?.onLoadLayout(l); + }, + (r) => Log.error(r), + ); + }); + } + + void _listenOnRowsChanged() { + _viewCache.addListener(onRowsChanged: (reason) { + _databaseCallbacks?.onRowsChanged?.call(rowInfos, reason); + }); + } + + void _listenOnFieldsChanged() { + fieldController.addListener( + onReceiveFields: (fields) { + _databaseCallbacks?.onFieldsChanged?.call(UnmodifiableListView(fields)); + }, + onFilters: (filters) { + _databaseCallbacks?.onFiltersChanged?.call(filters); + }, + ); + } + + void _listenOnGroupChanged() { + groupListener.start( + onNumOfGroupsChanged: (result) { + result.fold((changeset) { + if (changeset.updateGroups.isNotEmpty) { + _groupCallbacks?.onUpdateGroup?.call(changeset.updateGroups); + } + + if (changeset.deletedGroups.isNotEmpty) { + _groupCallbacks?.onDeleteGroup?.call(changeset.deletedGroups); + } + + for (final insertedGroup in changeset.insertedGroups) { + _groupCallbacks?.onInsertGroup?.call(insertedGroup); + } + }, (r) => Log.error(r)); + }, + onGroupByNewField: (result) { + result.fold((groups) { + _groupCallbacks?.onGroupByField?.call(groups); + }, (r) => Log.error(r)); + }, + ); + } + + void _listenOnLayoutChanged() { + layoutListener.start(onLayoutChanged: (result) { + result.fold((l) { + _layoutCallbacks?.onLayoutChanged(l); + }, (r) => Log.error(r)); + }); + } +} + +class RowDataBuilder { + final _cellDataByFieldId = {}; + + void insertText(FieldInfo fieldInfo, String text) { + assert(fieldInfo.fieldType == FieldType.RichText); + _cellDataByFieldId[fieldInfo.field.id] = text; + } + + void insertNumber(FieldInfo fieldInfo, int num) { + assert(fieldInfo.fieldType == FieldType.Number); + _cellDataByFieldId[fieldInfo.field.id] = num.toString(); + } + + void insertDate(FieldInfo fieldInfo, DateTime date) { + assert(fieldInfo.fieldType == FieldType.DateTime); + final timestamp = (date.millisecondsSinceEpoch ~/ 1000); + _cellDataByFieldId[fieldInfo.field.id] = timestamp.toString(); + } + + Map build() { + return _cellDataByFieldId; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/database_service.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/database_service.dart index 1dcf433d01..0d7fdd543f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/database_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/database_service.dart @@ -1,4 +1,7 @@ +import 'package:appflowy_backend/protobuf/flowy-database/calendar_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pb.dart'; import 'package:dartz/dartz.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; @@ -20,25 +23,56 @@ class DatabaseBackendService { return DatabaseEventGetDatabase(payload).send(); } - Future> createRow({Option? startRowId}) { - var payload = CreateRowPayloadPB.create()..viewId = viewId; - startRowId?.fold(() => null, (id) => payload.startRowId = id); - return DatabaseEventCreateRow(payload).send(); - } - - Future> createBoardCard( - String groupId, + Future> createRow({ String? startRowId, - ) { - CreateBoardCardPayloadPB payload = CreateBoardCardPayloadPB.create() - ..viewId = viewId - ..groupId = groupId; - + String? groupId, + Map? cellDataByFieldId, + }) { + var payload = CreateRowPayloadPB.create()..viewId = viewId; if (startRowId != null) { payload.startRowId = startRowId; } - return DatabaseEventCreateBoardCard(payload).send(); + if (groupId != null) { + payload.groupId = groupId; + } + + if (cellDataByFieldId != null && cellDataByFieldId.isNotEmpty) { + payload.data = RowDataPB(cellDataByFieldId: cellDataByFieldId); + } + + return DatabaseEventCreateRow(payload).send(); + } + + Future> moveRow({ + required String fromRowId, + required String? toGroupId, + required String? toRowId, + }) { + var payload = MoveGroupRowPayloadPB.create() + ..viewId = viewId + ..fromRowId = fromRowId; + if (toGroupId != null) { + payload.toGroupId = toGroupId; + } + + if (toRowId != null) { + payload.toRowId = toRowId; + } + + return DatabaseEventMoveGroupRow(payload).send(); + } + + Future> moveGroup({ + required String fromGroupId, + required String toGroupId, + }) { + final payload = MoveGroupPayloadPB.create() + ..viewId = viewId + ..fromGroupId = fromGroupId + ..toGroupId = toGroupId; + + return DatabaseEventMoveGroup(payload).send(); } Future, FlowyError>> getFields( @@ -53,6 +87,28 @@ class DatabaseBackendService { }); } + Future> getLayoutSetting( + LayoutTypePB layoutType) { + final payload = DatabaseLayoutIdPB.create() + ..viewId = viewId + ..layout = layoutType; + return DatabaseEventGetLayoutSetting(payload).send(); + } + + Future> updateLayoutSetting( + {CalendarLayoutSettingsPB? calendarLayoutSetting}) { + final layoutSetting = LayoutSettingPB.create(); + if (calendarLayoutSetting != null) { + layoutSetting.calendar = calendarLayoutSetting; + } + + final payload = UpdateLayoutSettingPB.create() + ..viewId = viewId + ..layoutSetting = layoutSetting; + + return DatabaseEventSetLayoutSetting(payload).send(); + } + Future> closeView() { final request = ViewIdPB(value: viewId); return FolderEventCloseView(request).send(); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_listener.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/group/group_listener.dart similarity index 65% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_listener.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/application/group/group_listener.dart index 0f88552050..1f6fd1efa3 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_listener.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/group/group_listener.dart @@ -11,20 +11,20 @@ import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart typedef GroupUpdateValue = Either; typedef GroupByNewFieldValue = Either, FlowyError>; -class BoardListener { +class DatabaseGroupListener { final String viewId; - PublishNotifier? _groupUpdateNotifier = PublishNotifier(); - PublishNotifier? _groupByNewFieldNotifier = + PublishNotifier? _numOfGroupsNotifier = PublishNotifier(); + PublishNotifier? _groupByFieldNotifier = PublishNotifier(); DatabaseNotificationListener? _listener; - BoardListener(this.viewId); + DatabaseGroupListener(this.viewId); void start({ - required void Function(GroupUpdateValue) onBoardChanged, + required void Function(GroupUpdateValue) onNumOfGroupsChanged, required void Function(GroupByNewFieldValue) onGroupByNewField, }) { - _groupUpdateNotifier?.addPublishListener(onBoardChanged); - _groupByNewFieldNotifier?.addPublishListener(onGroupByNewField); + _numOfGroupsNotifier?.addPublishListener(onNumOfGroupsChanged); + _groupByFieldNotifier?.addPublishListener(onGroupByNewField); _listener = DatabaseNotificationListener( objectId: viewId, handler: _handler, @@ -38,16 +38,16 @@ class BoardListener { switch (ty) { case DatabaseNotification.DidUpdateGroups: result.fold( - (payload) => _groupUpdateNotifier?.value = + (payload) => _numOfGroupsNotifier?.value = left(GroupChangesetPB.fromBuffer(payload)), - (error) => _groupUpdateNotifier?.value = right(error), + (error) => _numOfGroupsNotifier?.value = right(error), ); break; case DatabaseNotification.DidGroupByField: result.fold( - (payload) => _groupByNewFieldNotifier?.value = + (payload) => _groupByFieldNotifier?.value = left(GroupChangesetPB.fromBuffer(payload).initialGroups), - (error) => _groupByNewFieldNotifier?.value = right(error), + (error) => _groupByFieldNotifier?.value = right(error), ); break; default: @@ -57,10 +57,10 @@ class BoardListener { Future stop() async { await _listener?.stop(); - _groupUpdateNotifier?.dispose(); - _groupUpdateNotifier = null; + _numOfGroupsNotifier?.dispose(); + _numOfGroupsNotifier = null; - _groupByNewFieldNotifier?.dispose(); - _groupByNewFieldNotifier = null; + _groupByFieldNotifier?.dispose(); + _groupByFieldNotifier = null; } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_setting_listener.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_setting_listener.dart new file mode 100644 index 0000000000..b0bb555d51 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_setting_listener.dart @@ -0,0 +1,51 @@ +import 'dart:typed_data'; + +import 'package:appflowy/core/grid_notification.dart'; +import 'package:flowy_infra/notifier.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart'; +import 'package:dartz/dartz.dart'; + +typedef LayoutSettingsValue = Either; + +class DatabaseLayoutListener { + final String viewId; + PublishNotifier>? _settingNotifier = + PublishNotifier(); + DatabaseNotificationListener? _listener; + DatabaseLayoutListener(this.viewId); + + void start({ + required void Function(LayoutSettingsValue) + onLayoutChanged, + }) { + _settingNotifier?.addPublishListener(onLayoutChanged); + _listener = DatabaseNotificationListener( + objectId: viewId, + handler: _handler, + ); + } + + void _handler( + DatabaseNotification ty, + Either result, + ) { + switch (ty) { + case DatabaseNotification.DidUpdateLayoutSettings: + result.fold( + (payload) => _settingNotifier?.value = + left(LayoutSettingPB.fromBuffer(payload)), + (error) => _settingNotifier?.value = right(error), + ); + break; + default: + break; + } + } + + Future stop() async { + await _listener?.stop(); + _settingNotifier?.dispose(); + _settingNotifier = null; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart index 24aca7b186..7f0265ccc7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart @@ -61,7 +61,7 @@ class RowCache { }); } - void initializeRows(List rows) { + void setInitialRows(List rows) { for (final row in rows) { final rowInfo = buildGridRow(row); _rowList.add(rowInfo); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_data_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_data_controller.dart index bbb13d16a9..c810ea7c8e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_data_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_data_controller.dart @@ -4,25 +4,27 @@ import 'row_cache.dart'; typedef OnRowChanged = void Function(CellByFieldId, RowsChangedReason); -class RowDataController { - final RowInfo rowInfo; +class RowController { + final String rowId; + final String viewId; final List _onRowChangedListeners = []; final RowCache _rowCache; get cellCache => _rowCache.cellCache; - RowDataController({ - required this.rowInfo, + RowController({ + required this.rowId, + required this.viewId, required RowCache rowCache, }) : _rowCache = rowCache; CellByFieldId loadData() { - return _rowCache.loadGridCells(rowInfo.rowPB.id); + return _rowCache.loadGridCells(rowId); } void addListener({OnRowChanged? onRowChanged}) { _onRowChangedListeners.add(_rowCache.addListener( - rowId: rowInfo.rowPB.id, + rowId: rowId, onCellUpdated: onRowChanged, )); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_service.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_service.dart index d0e74186e7..33e59220df 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_service.dart @@ -1,8 +1,6 @@ -import 'package:appflowy_backend/protobuf/flowy-database/database_entities.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-database/group_changeset.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart'; class RowBackendService { @@ -44,52 +42,3 @@ class RowBackendService { return DatabaseEventDuplicateRow(payload).send(); } } - -class GroupBackendService { - final String viewId; - - GroupBackendService({ - required this.viewId, - }); - - Future> moveRow({ - required String fromRowId, - required String toRowId, - }) { - var payload = MoveRowPayloadPB.create() - ..viewId = viewId - ..fromRowId = fromRowId - ..toRowId = toRowId; - - return DatabaseEventMoveRow(payload).send(); - } - - Future> moveGroupRow({ - required String fromRowId, - required String toGroupId, - required String? toRowId, - }) { - var payload = MoveGroupRowPayloadPB.create() - ..viewId = viewId - ..fromRowId = fromRowId - ..toGroupId = toGroupId; - - if (toRowId != null) { - payload.toRowId = toRowId; - } - - return DatabaseEventMoveGroupRow(payload).send(); - } - - Future> moveGroup({ - required String fromGroupId, - required String toGroupId, - }) { - final payload = MoveGroupPayloadPB.create() - ..viewId = viewId - ..fromGroupId = fromGroupId - ..toGroupId = toGroupId; - - return DatabaseEventMoveGroup(payload).send(); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart index a0be622128..1ee14a95d0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart @@ -13,25 +13,25 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../../application/field/field_controller.dart'; import '../../application/row/row_cache.dart'; -import '../../application/row/row_service.dart'; -import 'board_data_controller.dart'; +import '../../application/database_controller.dart'; import 'group_controller.dart'; part 'board_bloc.freezed.dart'; class BoardBloc extends Bloc { - final BoardDataController _boardDataController; + final DatabaseController _databaseController; late final AppFlowyBoardController boardController; - final GroupBackendService _groupBackendSvc; final LinkedHashMap groupControllers = LinkedHashMap(); - FieldController get fieldController => _boardDataController.fieldController; - String get viewId => _boardDataController.viewId; + FieldController get fieldController => _databaseController.fieldController; + String get viewId => _databaseController.viewId; BoardBloc({required ViewPB view}) - : _groupBackendSvc = GroupBackendService(viewId: view.id), - _boardDataController = BoardDataController(view: view), + : _databaseController = DatabaseController( + view: view, + layoutType: LayoutTypePB.Board, + ), super(BoardState.initial(view.id)) { boardController = AppFlowyBoardController( onMoveGroup: ( @@ -40,7 +40,10 @@ class BoardBloc extends Bloc { toGroupId, toIndex, ) { - _moveGroup(fromGroupId, toGroupId); + _databaseController.moveGroup( + fromGroupId: fromGroupId, + toGroupId: toGroupId, + ); }, onMoveGroupItem: ( groupId, @@ -49,7 +52,13 @@ class BoardBloc extends Bloc { ) { final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex); final toRow = groupControllers[groupId]?.rowAtIndex(toIndex); - _moveRow(fromRow, groupId, toRow); + if (fromRow != null) { + _databaseController.moveRow( + fromRow, + toRow: toRow, + groupId: groupId, + ); + } }, onMoveGroupItemToGroup: ( fromGroupId, @@ -59,7 +68,13 @@ class BoardBloc extends Bloc { ) { final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex); final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex); - _moveRow(fromRow, toGroupId, toRow); + if (fromRow != null) { + _databaseController.moveRow( + fromRow, + toRow: toRow, + groupId: toGroupId, + ); + } }, ); @@ -72,8 +87,8 @@ class BoardBloc extends Bloc { }, createBottomRow: (groupId) async { final startRowId = groupControllers[groupId]?.lastRow()?.id; - final result = await _boardDataController.createBoardCard( - groupId, + final result = await _databaseController.createRow( + groupId: groupId, startRowId: startRowId, ); result.fold( @@ -82,7 +97,8 @@ class BoardBloc extends Bloc { ); }, createHeaderRow: (String groupId) async { - final result = await _boardDataController.createBoardCard(groupId); + final result = + await _databaseController.createRow(groupId: groupId); result.fold( (_) {}, (err) => Log.error(err), @@ -141,44 +157,11 @@ class BoardBloc extends Bloc { } boardController.enableGroupDragging(!isEdit); - // boardController.updateGroupItem( - // group.groupId, - // GroupItem( - // row: row, - // fieldInfo: fieldInfo, - // isDraggable: !isEdit, - // ), - // ); - } - - void _moveRow(RowPB? fromRow, String columnId, RowPB? toRow) { - if (fromRow != null) { - _groupBackendSvc - .moveGroupRow( - fromRowId: fromRow.id, - toGroupId: columnId, - toRowId: toRow?.id, - ) - .then((result) { - result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r))); - }); - } - } - - void _moveGroup(String fromGroupId, String toGroupId) { - _groupBackendSvc - .moveGroup( - fromGroupId: fromGroupId, - toGroupId: toGroupId, - ) - .then((result) { - result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r))); - }); } @override Future close() async { - await _boardDataController.dispose(); + await _databaseController.dispose(); for (final controller in groupControllers.values) { controller.dispose(); } @@ -204,34 +187,36 @@ class BoardBloc extends Bloc { } RowCache? getRowCache(String blockId) { - return _boardDataController.rowCache; + return _databaseController.rowCache; } void _startListening() { - _boardDataController.addListener( - onDatabaseChanged: (grid) { + final onDatabaseChanged = DatabaseCallbacks( + onDatabaseChanged: (database) { if (!isClosed) { - add(BoardEvent.didReceiveGridUpdate(grid)); + add(BoardEvent.didReceiveGridUpdate(database)); } }, - didLoadGroups: (groups) { + ); + final onGroupChanged = GroupCallbacks( + onGroupByField: (groups) { if (isClosed) return; initializeGroups(groups); add(BoardEvent.didReceiveGroups(groups)); }, - onDeletedGroup: (groupIds) { + onDeleteGroup: (groupIds) { if (isClosed) return; boardController.removeGroups(groupIds); }, - onInsertedGroup: (insertedGroup) { + onInsertGroup: (insertGroups) { if (isClosed) return; - final group = insertedGroup.group; + final group = insertGroups.group; final newGroup = initializeGroupData(group); final controller = initializeGroupController(group); groupControllers[controller.group.groupId] = (controller); boardController.addGroup(newGroup); }, - onUpdatedGroup: (updatedGroups) { + onUpdateGroup: (updatedGroups) { if (isClosed) return; for (final group in updatedGroups) { final columnController = @@ -239,15 +224,11 @@ class BoardBloc extends Bloc { columnController?.updateGroupName(group.desc); } }, - onError: (err) { - Log.error(err); - }, - onResetGroups: (groups) { - if (isClosed) return; + ); - initializeGroups(groups); - add(BoardEvent.didReceiveGroups(groups)); - }, + _databaseController.addListener( + onDatabaseChanged: onDatabaseChanged, + onGroupChanged: onGroupChanged, ); } @@ -264,7 +245,7 @@ class BoardBloc extends Bloc { } Future _openGrid(Emitter emit) async { - final result = await _boardDataController.openGrid(); + final result = await _databaseController.open(); result.fold( (grid) => emit( state.copyWith(loadingState: GridLoadingState.finish(left(unit))), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_data_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_data_controller.dart deleted file mode 100644 index 5017b2c4e5..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_data_controller.dart +++ /dev/null @@ -1,144 +0,0 @@ -import 'dart:collection'; - -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'dart:async'; -import 'package:dartz/dartz.dart'; -import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart'; - -import '../../application/database_service.dart'; -import '../../application/defines.dart'; -import '../../application/field/field_controller.dart'; -import '../../application/row/row_cache.dart'; -import '../../application/view/view_cache.dart'; -import 'board_listener.dart'; - -typedef DidLoadGroups = void Function(List); -typedef OnUpdatedGroup = void Function(List); -typedef OnDeletedGroup = void Function(List); -typedef OnInsertedGroup = void Function(InsertedGroupPB); -typedef OnResetGroups = void Function(List); - -class BoardDataController { - final String viewId; - final DatabaseBackendService _databaseSvc; - final FieldController fieldController; - final BoardListener _listener; - late DatabaseViewCache _viewCache; - - OnFieldsChanged? _onFieldsChanged; - OnDatabaseChanged? _onDatabaseChanged; - DidLoadGroups? _didLoadGroup; - OnRowsChanged? _onRowsChanged; - OnError? _onError; - - List get rowInfos => _viewCache.rowInfos; - RowCache get rowCache => _viewCache.rowCache; - - BoardDataController({required ViewPB view}) - : viewId = view.id, - _listener = BoardListener(view.id), - _databaseSvc = DatabaseBackendService(viewId: view.id), - fieldController = FieldController(viewId: view.id) { - // - _viewCache = DatabaseViewCache( - viewId: view.id, - fieldController: fieldController, - ); - _viewCache.addListener(onRowsChanged: (reason) { - _onRowsChanged?.call(rowInfos, reason); - }); - } - - void addListener({ - required OnDatabaseChanged onDatabaseChanged, - OnFieldsChanged? onFieldsChanged, - required DidLoadGroups didLoadGroups, - OnRowsChanged? onRowsChanged, - required OnUpdatedGroup onUpdatedGroup, - required OnDeletedGroup onDeletedGroup, - required OnInsertedGroup onInsertedGroup, - required OnResetGroups onResetGroups, - required OnError? onError, - }) { - _onDatabaseChanged = onDatabaseChanged; - _onFieldsChanged = onFieldsChanged; - _didLoadGroup = didLoadGroups; - _onRowsChanged = onRowsChanged; - _onError = onError; - - fieldController.addListener(onReceiveFields: (fields) { - _onFieldsChanged?.call(UnmodifiableListView(fields)); - }); - - _listener.start( - onBoardChanged: (result) { - result.fold( - (changeset) { - if (changeset.updateGroups.isNotEmpty) { - onUpdatedGroup.call(changeset.updateGroups); - } - - if (changeset.deletedGroups.isNotEmpty) { - onDeletedGroup.call(changeset.deletedGroups); - } - - for (final insertedGroup in changeset.insertedGroups) { - onInsertedGroup.call(insertedGroup); - } - }, - (e) => _onError?.call(e), - ); - }, - onGroupByNewField: (result) { - result.fold( - (groups) => onResetGroups(groups), - (e) => _onError?.call(e), - ); - }, - ); - } - - Future> openGrid() async { - final result = await _databaseSvc.openGrid(); - return result.fold( - (grid) async { - _onDatabaseChanged?.call(grid); - return fieldController.loadFields(fieldIds: grid.fields).then((result) { - return result.fold( - (l) => Future(() async { - await _loadGroups(); - _viewCache.rowCache.initializeRows(grid.rows); - return left(l); - }), - (err) => right(err), - ); - }); - }, - (err) => right(err), - ); - } - - Future> createBoardCard(String groupId, - {String? startRowId}) { - return _databaseSvc.createBoardCard(groupId, startRowId); - } - - Future dispose() async { - await _viewCache.dispose(); - await _databaseSvc.closeView(); - await fieldController.dispose(); - } - - Future _loadGroups() async { - final result = await _databaseSvc.loadGroups(); - return Future( - () => result.fold( - (groups) { - _didLoadGroup?.call(groups.items); - }, - (err) => _onError?.call(err), - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/card_data_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/card_data_controller.dart deleted file mode 100644 index d6b49a6302..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/card_data_controller.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart'; -import 'package:flutter/foundation.dart'; - -import '../../../application/cell/cell_service.dart'; -import '../../../application/row/row_cache.dart'; -import '../../presentation/card/card_cell_builder.dart'; - -typedef OnCardChanged = void Function(CellByFieldId, RowsChangedReason); - -class CardDataController extends BoardCellBuilderDelegate { - final RowPB rowPB; - final RowCache _rowCache; - final List _onCardChangedListeners = []; - - CardDataController({ - required this.rowPB, - required RowCache rowCache, - }) : _rowCache = rowCache; - - CellByFieldId loadData() { - return _rowCache.loadGridCells(rowPB.id); - } - - void addListener({OnCardChanged? onRowChanged}) { - _onCardChangedListeners.add(_rowCache.addListener( - rowId: rowPB.id, - onCellUpdated: onRowChanged, - )); - } - - void dispose() { - for (final fn in _onCardChangedListeners) { - _rowCache.removeRowListener(fn); - } - } - - @override - CellCache get cellCache => _rowCache.cellCache; -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/group_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/application/group_controller.dart index edc4160b39..a1f87796d2 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/group_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/application/group_controller.dart @@ -1,7 +1,11 @@ import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart'; -import 'group_listener.dart'; +import 'dart:typed_data'; + +import 'package:appflowy/core/grid_notification.dart'; +import 'package:flowy_infra/notifier.dart'; +import 'package:dartz/dartz.dart'; typedef OnGroupError = void Function(FlowyError); @@ -14,14 +18,14 @@ abstract class GroupControllerDelegate { class GroupController { final GroupPB group; - final GroupListener _listener; + final SingleGroupListener _listener; final GroupControllerDelegate delegate; GroupController({ required String viewId, required this.group, required this.delegate, - }) : _listener = GroupListener(group); + }) : _listener = SingleGroupListener(group); RowPB? rowAtIndex(int index) { if (index < group.rows.length) { @@ -81,3 +85,45 @@ class GroupController { _listener.stop(); } } + +typedef UpdateGroupNotifiedValue = Either; + +class SingleGroupListener { + final GroupPB group; + PublishNotifier? _groupNotifier = PublishNotifier(); + DatabaseNotificationListener? _listener; + SingleGroupListener(this.group); + + void start({ + required void Function(UpdateGroupNotifiedValue) onGroupChanged, + }) { + _groupNotifier?.addPublishListener(onGroupChanged); + _listener = DatabaseNotificationListener( + objectId: group.groupId, + handler: _handler, + ); + } + + void _handler( + DatabaseNotification ty, + Either result, + ) { + switch (ty) { + case DatabaseNotification.DidUpdateGroupRow: + result.fold( + (payload) => _groupNotifier?.value = + left(GroupRowsNotificationPB.fromBuffer(payload)), + (error) => _groupNotifier?.value = right(error), + ); + break; + default: + break; + } + } + + Future stop() async { + await _listener?.stop(); + _groupNotifier?.dispose(); + _groupNotifier = null; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/group_listener.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/application/group_listener.dart deleted file mode 100644 index 4fb304c08e..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/group_listener.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'dart:typed_data'; - -import 'package:appflowy/core/grid_notification.dart'; -import 'package:flowy_infra/notifier.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-database/notification.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-database/group.pb.dart'; -import 'package:dartz/dartz.dart'; -import 'package:appflowy_backend/protobuf/flowy-database/group_changeset.pb.dart'; - -typedef UpdateGroupNotifiedValue = Either; - -class GroupListener { - final GroupPB group; - PublishNotifier? _groupNotifier = PublishNotifier(); - DatabaseNotificationListener? _listener; - GroupListener(this.group); - - void start({ - required void Function(UpdateGroupNotifiedValue) onGroupChanged, - }) { - _groupNotifier?.addPublishListener(onGroupChanged); - _listener = DatabaseNotificationListener( - objectId: group.groupId, - handler: _handler, - ); - } - - void _handler( - DatabaseNotification ty, - Either result, - ) { - switch (ty) { - case DatabaseNotification.DidUpdateGroupRow: - result.fold( - (payload) => _groupNotifier?.value = - left(GroupRowsNotificationPB.fromBuffer(payload)), - (error) => _groupNotifier?.value = right(error), - ); - break; - default: - break; - } - } - - Future stop() async { - await _listener?.stop(); - _groupNotifier?.dispose(); - _groupNotifier = null; - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart index c3d0b41f93..cbd85594a0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart @@ -6,8 +6,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/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/cell/cell_builder.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row_detail.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart'; @@ -17,13 +16,14 @@ import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Card; import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../widgets/card/cells/card_cell.dart'; +import '../../widgets/card/card_cell_builder.dart'; +import '../../widgets/row/cell_builder.dart'; import '../application/board_bloc.dart'; -import '../application/card/card_data_controller.dart'; -import 'card/card.dart'; -import 'card/card_cell_builder.dart'; +import '../../widgets/card/card.dart'; import 'toolbar/board_toolbar.dart'; class BoardPage extends StatelessWidget { @@ -78,6 +78,7 @@ class BoardContent extends StatefulWidget { class _BoardContentState extends State { late AppFlowyBoardScrollController scrollManager; + final cardConfiguration = CardConfiguration(); final config = AppFlowyBoardConfig( groupBackgroundColor: HexColor.fromHex('#F7F8FC'), @@ -86,6 +87,16 @@ class _BoardContentState extends State { @override void initState() { scrollManager = AppFlowyBoardScrollController(); + cardConfiguration.addSelectOptionHook((options, groupId) { + // The cell should hide if the option id is equal to the groupId. + final isInGroup = + options.where((element) => element.id == groupId).isNotEmpty; + if (isInGroup || options.isEmpty) { + return const SizedBox(); + } + return null; + }); + super.initState(); } @@ -225,15 +236,11 @@ class _BoardContentState extends State { /// Return placeholder widget if the rowCache is null. if (rowCache == null) return SizedBox(key: ObjectKey(groupItem)); - + final cellCache = rowCache.cellCache; final fieldController = context.read().fieldController; final viewId = context.read().viewId; - final cardController = CardDataController( - rowCache: rowCache, - rowPB: rowPB, - ); - final cellBuilder = BoardCellBuilder(cardController); + final cellBuilder = CardCellBuilder(cellCache); bool isEditing = false; context.read().state.editingRow.fold( () => null, @@ -247,13 +254,15 @@ class _BoardContentState extends State { key: ValueKey(groupItemId), margin: config.cardPadding, decoration: _makeBoxDecoration(context), - child: BoardCard( + child: Card( + row: rowPB, viewId: viewId, - groupId: groupData.group.groupId, + rowCache: rowCache, + cardData: groupData.group.groupId, fieldId: groupItem.fieldInfo.id, isEditing: isEditing, cellBuilder: cellBuilder, - dataController: cardController, + configuration: cardConfiguration, openCard: (context) => _openCard( viewId, fieldController, @@ -303,8 +312,9 @@ class _BoardContentState extends State { rowPB: rowPB, ); - final dataController = RowDataController( - rowInfo: rowInfo, + final dataController = RowController( + rowId: rowInfo.rowPB.id, + viewId: rowInfo.viewId, rowCache: rowCache, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_toolbar.dart index 89434f0b1e..57f12e218f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_toolbar.dart @@ -5,7 +5,6 @@ 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:flowy_infra_ui/style_widget/button.dart'; import 'package:flutter/material.dart'; import 'board_setting.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart index e4a2239ab7..bf0e1ab8f6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart @@ -1,5 +1,6 @@ +import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; -import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; +import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; @@ -9,20 +10,24 @@ import 'package:dartz/dartz.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'calendar_data_controller.dart'; +import '../../application/database_controller.dart'; +import '../../application/row/row_cache.dart'; part 'calendar_bloc.freezed.dart'; class CalendarBloc extends Bloc { - final CalendarDataController _databaseDataController; - final EventController calendarEventsController = EventController(); + final DatabaseController _databaseController; - FieldController get fieldController => - _databaseDataController.fieldController; - String get databaseId => _databaseDataController.databaseId; + // Getters + String get viewId => _databaseController.viewId; + CellCache get cellCache => _databaseController.rowCache.cellCache; + RowCache get rowCache => _databaseController.rowCache; CalendarBloc({required ViewPB view}) - : _databaseDataController = CalendarDataController(view: view), + : _databaseController = DatabaseController( + view: view, + layoutType: LayoutTypePB.Calendar, + ), super(CalendarState.initial(view.id)) { on( (event, emit) async { @@ -30,23 +35,57 @@ class CalendarBloc extends Bloc { initial: () async { _startListening(); await _openDatabase(emit); + _loadAllEvents(); }, - didReceiveCalendarSettings: (CalendarSettingsPB settings) { + didReceiveCalendarSettings: (CalendarLayoutSettingsPB settings) { emit(state.copyWith(settings: Some(settings))); }, didReceiveDatabaseUpdate: (DatabasePB database) { emit(state.copyWith(database: Some(database))); }, - didReceiveError: (FlowyError error) { - emit(state.copyWith(noneOrError: Some(error))); + didLoadAllEvents: (events) { + emit(state.copyWith(events: events)); + }, + createEvent: (DateTime date, String title) async { + await _createEvent(date, title); + }, + didReceiveEvent: (CalendarEventData newEvent) { + emit(state.copyWith(events: [...state.events, newEvent])); + }, + didUpdateFieldInfos: (Map fieldInfoByFieldId) { + emit(state.copyWith(fieldInfoByFieldId: fieldInfoByFieldId)); }, ); }, ); } + FieldInfo? _getCalendarFieldInfo(String fieldId) { + final fieldInfos = _databaseController.fieldController.fieldInfos; + final index = fieldInfos.indexWhere( + (element) => element.field.id == fieldId, + ); + if (index != -1) { + return fieldInfos[index]; + } else { + return null; + } + } + + FieldInfo? _getTitleFieldInfo() { + final fieldInfos = _databaseController.fieldController.fieldInfos; + final index = fieldInfos.indexWhere( + (element) => element.field.isPrimary, + ); + if (index != -1) { + return fieldInfos[index]; + } else { + return null; + } + } + Future _openDatabase(Emitter emit) async { - final result = await _databaseDataController.openDatabase(); + final result = await _databaseController.open(); result.fold( (database) => emit( state.copyWith(loadingState: DatabaseLoadingState.finish(left(unit))), @@ -57,60 +96,145 @@ class CalendarBloc extends Bloc { ); } - RowCache? getRowCache(String blockId) { - return _databaseDataController.rowCache; - } + Future _createEvent(DateTime date, String title) async { + state.settings.fold( + () => null, + (settings) async { + final dateField = _getCalendarFieldInfo(settings.layoutFieldId); + final titleField = _getTitleFieldInfo(); + if (dateField != null && titleField != null) { + final result = await _databaseController.createRow( + withCells: (builder) { + builder.insertDate(dateField, date); + builder.insertText(titleField, title); + }, + ); - void _startListening() { - _databaseDataController.addListener( - onDatabaseChanged: (database) { - if (!isClosed) return; - - add(CalendarEvent.didReceiveDatabaseUpdate(database)); - }, - onSettingsChanged: (CalendarSettingsPB settings) { - if (isClosed) return; - add(CalendarEvent.didReceiveCalendarSettings(settings)); - }, - onArrangeWithNewField: (field) { - if (isClosed) return; - _initializeEvents(field); - // add(CalendarEvent.) - }, - onError: (err) { - Log.error(err); + result.fold( + (newRow) => _loadEvent(newRow.id), + (err) => Log.error(err), + ); + } }, ); } - void _initializeEvents(FieldPB dateField) { - calendarEventsController.removeWhere((element) => true); + Future _loadEvent(String rowId) async { + final payload = RowIdPB(viewId: viewId, rowId: rowId); + DatabaseEventGetCalendarEvent(payload).send().then((result) { + result.fold( + (eventPB) { + final calendarEvent = _calendarEventDataFromEventPB(eventPB); + if (calendarEvent != null) { + add(CalendarEvent.didReceiveEvent(calendarEvent)); + } + }, + (r) => Log.error(r), + ); + }); + } - const events = >[]; + Future _loadAllEvents() async { + final payload = CalendarEventRequestPB.create()..viewId = viewId; + DatabaseEventGetAllCalendarEvents(payload).send().then((result) { + result.fold( + (events) { + if (!isClosed) { + final calendarEvents = >[]; + for (final eventPB in events.items) { + final calendarEvent = _calendarEventDataFromEventPB(eventPB); + if (calendarEvent != null) { + calendarEvents.add(calendarEvent); + } + } - // final List> events = rows.map((row) { - // final event = CalendarEventData( - // title: "", - // date: row -> dateField -> value, - // event: row, - // ); + add(CalendarEvent.didLoadAllEvents(calendarEvents)); + } + }, + (r) => Log.error(r), + ); + }); + } - // return event; - // }).toList(); + CalendarEventData? _calendarEventDataFromEventPB( + CalendarEventPB eventPB) { + final fieldInfo = state.fieldInfoByFieldId[eventPB.titleFieldId]; + if (fieldInfo != null) { + final cellId = CellIdentifier( + viewId: viewId, + rowId: eventPB.rowId, + fieldInfo: fieldInfo, + ); - calendarEventsController.addAll(events); + final eventData = CalendarCardData( + event: eventPB, + cellId: cellId, + ); + + final date = DateTime.fromMillisecondsSinceEpoch( + eventPB.timestamp.toInt() * 1000, + isUtc: true, + ); + return CalendarEventData( + title: eventPB.title, + date: date, + event: eventData, + ); + } else { + return null; + } + } + + void _startListening() { + final onDatabaseChanged = DatabaseCallbacks( + onDatabaseChanged: (database) { + if (isClosed) return; + }, + onFieldsChanged: (fieldInfos) { + if (isClosed) return; + final fieldInfoByFieldId = { + for (var fieldInfo in fieldInfos) fieldInfo.field.id: fieldInfo + }; + add(CalendarEvent.didUpdateFieldInfos(fieldInfoByFieldId)); + }, + ); + + final onLayoutChanged = LayoutCallbacks( + onLayoutChanged: _didReceiveLayoutSetting, + onLoadLayout: _didReceiveLayoutSetting, + ); + + _databaseController.addListener( + onDatabaseChanged: onDatabaseChanged, + onLayoutChanged: onLayoutChanged, + ); + } + + void _didReceiveLayoutSetting(LayoutSettingPB layoutSetting) { + if (layoutSetting.hasCalendar()) { + if (isClosed) return; + add(CalendarEvent.didReceiveCalendarSettings(layoutSetting.calendar)); + } } } +typedef Events = List>; + @freezed class CalendarEvent with _$CalendarEvent { const factory CalendarEvent.initial() = _InitialCalendar; const factory CalendarEvent.didReceiveCalendarSettings( - CalendarSettingsPB settings) = _DidReceiveCalendarSettings; - const factory CalendarEvent.didReceiveError(FlowyError error) = - _DidReceiveError; + CalendarLayoutSettingsPB settings) = _ReceiveCalendarSettings; + const factory CalendarEvent.didLoadAllEvents(Events events) = + _ReceiveCalendarEvents; + const factory CalendarEvent.didReceiveEvent( + CalendarEventData event) = _ReceiveEvent; + const factory CalendarEvent.didUpdateFieldInfos( + Map fieldInfoByFieldId) = _DidUpdateFieldInfos; + const factory CalendarEvent.createEvent(DateTime date, String title) = + _CreateEvent; const factory CalendarEvent.didReceiveDatabaseUpdate(DatabasePB database) = - _DidReceiveDatabaseUpdate; + _ReceiveDatabaseUpdate; } @freezed @@ -118,9 +242,9 @@ class CalendarState with _$CalendarState { const factory CalendarState({ required String databaseId, required Option database, - required Option dateField, - required Option> unscheduledRows, - required Option settings, + required Events events, + required Map fieldInfoByFieldId, + required Option settings, required DatabaseLoadingState loadingState, required Option noneOrError, }) = _CalendarState; @@ -128,8 +252,8 @@ class CalendarState with _$CalendarState { factory CalendarState.initial(String databaseId) => CalendarState( database: none(), databaseId: databaseId, - dateField: none(), - unscheduledRows: none(), + fieldInfoByFieldId: {}, + events: [], settings: none(), noneOrError: none(), loadingState: const _Loading(), @@ -153,7 +277,8 @@ class CalendarEditingRow { }); } -class CalendarData { - final RowInfo rowInfo; - CalendarData(this.rowInfo); +class CalendarCardData { + final CalendarEventPB event; + final CellIdentifier cellId; + CalendarCardData({required this.cellId, required this.event}); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_data_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_data_controller.dart deleted file mode 100644 index 6ba517fbd2..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_data_controller.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -import 'package:appflowy/plugins/database_view/application/database_service.dart'; -import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; -import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; -import 'package:appflowy/plugins/database_view/application/view/view_cache.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; -import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart'; -import 'package:dartz/dartz.dart'; - -import 'calendar_listener.dart'; - -typedef OnFieldsChanged = void Function(UnmodifiableListView); -typedef OnDatabaseChanged = void Function(DatabasePB); -typedef OnSettingsChanged = void Function(CalendarSettingsPB); -typedef OnArrangeWithNewField = void Function(FieldPB); - -typedef OnRowsChanged = void Function(List, RowsChangedReason); -typedef OnError = void Function(FlowyError); - -class CalendarDataController { - final String databaseId; - final DatabaseBackendService _databaseBackendSvc; - final FieldController fieldController; - final CalendarListener _listener; - late DatabaseViewCache _viewCache; - - OnFieldsChanged? _onFieldsChanged; - OnDatabaseChanged? _onDatabaseChanged; - OnRowsChanged? _onRowsChanged; - OnSettingsChanged? _onSettingsChanged; - OnArrangeWithNewField? _onArrangeWithNewField; - OnError? _onError; - - List get rowInfos => _viewCache.rowInfos; - RowCache get rowCache => _viewCache.rowCache; - - CalendarDataController({required ViewPB view}) - : databaseId = view.id, - _listener = CalendarListener(view.id), - _databaseBackendSvc = DatabaseBackendService(viewId: view.id), - fieldController = FieldController(viewId: view.id) { - _viewCache = DatabaseViewCache( - viewId: view.id, - fieldController: fieldController, - ); - _viewCache.addListener(onRowsChanged: (reason) { - _onRowsChanged?.call(rowInfos, reason); - }); - } - - void addListener({ - required OnDatabaseChanged onDatabaseChanged, - OnFieldsChanged? onFieldsChanged, - OnRowsChanged? onRowsChanged, - required OnSettingsChanged? onSettingsChanged, - required OnArrangeWithNewField? onArrangeWithNewField, - required OnError? onError, - }) { - _onDatabaseChanged = onDatabaseChanged; - _onFieldsChanged = onFieldsChanged; - _onRowsChanged = onRowsChanged; - _onSettingsChanged = onSettingsChanged; - _onArrangeWithNewField = onArrangeWithNewField; - _onError = onError; - - fieldController.addListener(onReceiveFields: (fields) { - _onFieldsChanged?.call(UnmodifiableListView(fields)); - }); - - _listener.start( - onCalendarSettingsChanged: (result) { - result.fold( - (settings) => _onSettingsChanged?.call(settings), - (e) => _onError?.call(e), - ); - }, - onArrangeWithNewField: (result) { - result.fold( - (settings) => _onArrangeWithNewField?.call(settings), - (e) => _onError?.call(e), - ); - }, - ); - } - - Future> openDatabase() async { - final result = await _databaseBackendSvc.openGrid(); - return result.fold( - (database) async { - _onDatabaseChanged?.call(database); - return fieldController - .loadFields(fieldIds: database.fields) - .then((result) { - return result.fold( - (l) => Future(() async { - _viewCache.rowCache.initializeRows(database.rows); - return left(l); - }), - (err) => right(err), - ); - }); - }, - (err) => right(err), - ); - } - - Future dispose() async { - await _viewCache.dispose(); - await _databaseBackendSvc.closeView(); - await fieldController.dispose(); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_listener.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_listener.dart deleted file mode 100644 index 7317a3377f..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_listener.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'dart:typed_data'; - -import 'package:appflowy/core/grid_notification.dart'; -import 'package:flowy_infra/notifier.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart'; -import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart'; -import 'package:dartz/dartz.dart'; - -typedef CalendarSettingsValue = Either; -typedef ArrangeWithNewField = Either; - -class CalendarListener { - final String viewId; - PublishNotifier? _calendarSettingsNotifier = - PublishNotifier(); - PublishNotifier? _arrangeWithNewFieldNotifier = - PublishNotifier(); - DatabaseNotificationListener? _listener; - CalendarListener(this.viewId); - - void start({ - required void Function(CalendarSettingsValue) onCalendarSettingsChanged, - required void Function(ArrangeWithNewField) onArrangeWithNewField, - }) { - _calendarSettingsNotifier?.addPublishListener(onCalendarSettingsChanged); - _arrangeWithNewFieldNotifier?.addPublishListener(onArrangeWithNewField); - _listener = DatabaseNotificationListener( - objectId: viewId, - handler: _handler, - ); - } - - void _handler( - DatabaseNotification ty, - Either result, - ) { - switch (ty) { - case DatabaseNotification.DidUpdateCalendarSettings: - result.fold( - (payload) => _calendarSettingsNotifier?.value = - left(CalendarSettingsPB.fromBuffer(payload)), - (error) => _calendarSettingsNotifier?.value = right(error), - ); - break; - case DatabaseNotification.DidArrangeCalendarWithNewField: - result.fold( - (payload) => _arrangeWithNewFieldNotifier?.value = - left(FieldPB.fromBuffer(payload)), - (error) => _arrangeWithNewFieldNotifier?.value = right(error), - ); - break; - default: - break; - } - } - - Future stop() async { - await _listener?.stop(); - _calendarSettingsNotifier?.dispose(); - _calendarSettingsNotifier = null; - - _arrangeWithNewFieldNotifier?.dispose(); - _arrangeWithNewFieldNotifier = null; - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/calendar.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/calendar.dart index 3b228f94f8..a7ce510565 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/calendar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/calendar.dart @@ -77,8 +77,7 @@ class CalendarPluginDisplay extends PluginDisplay { }); }); - return CalendarPage(key: ValueKey(view.id)); - // return CalendarPage(key: ValueKey(view.id), view: view); + return CalendarPage(key: ValueKey(view.id), view: view); } @override diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart index 1927eb7ee0..0eac7870a3 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart @@ -1,56 +1,83 @@ import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart'; +import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart'; +import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:calendar_view/calendar_view.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flutter/material.dart'; -import 'package:styled_widget/styled_widget.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; import '../../grid/presentation/layout/sizes.dart'; +import '../../widgets/row/cell_builder.dart'; +import '../../widgets/row/row_detail.dart'; import 'layout/sizes.dart'; import 'toolbar/calendar_toolbar.dart'; -class CalendarPage extends StatelessWidget { - const CalendarPage({super.key}); +class CalendarPage extends StatefulWidget { + final ViewPB view; + const CalendarPage({required this.view, super.key}); @override - Widget build(BuildContext context) { - return const CalendarContent(); - } + State createState() => _CalendarPageState(); } -class CalendarContent extends StatefulWidget { - const CalendarContent({super.key}); - - @override - State createState() => _CalendarContentState(); -} - -class _CalendarContentState extends State { - late EventController _eventController; +class _CalendarPageState extends State { + final _eventController = EventController(); GlobalKey? _calendarState; + late CalendarBloc _calendarBloc; @override void initState() { - _eventController = EventController(); _calendarState = GlobalKey(); + _calendarBloc = CalendarBloc(view: widget.view) + ..add(const CalendarEvent.initial()); + super.initState(); } + @override + void dispose() { + _calendarBloc.close(); + super.dispose(); + } + @override Widget build(BuildContext context) { return CalendarControllerProvider( controller: _eventController, - child: Column( - children: [ - // const _ToolbarBlocAdaptor(), - _toolbar(), - _buildCalendar(_eventController), + child: MultiBlocProvider( + providers: [ + BlocProvider.value( + value: _calendarBloc, + ) ], + child: BlocListener( + listenWhen: (previous, current) => previous.events != current.events, + listener: (context, state) { + if (state.events.isNotEmpty) { + _eventController.removeWhere((element) => true); + _eventController.addAll(state.events); + } + }, + child: BlocBuilder( + builder: (context, state) { + return Column( + children: [ + // const _ToolbarBlocAdaptor(), + _toolbar(), + _buildCalendar(_eventController), + ], + ); + }, + ), + ), ), ); } @@ -125,9 +152,190 @@ class _CalendarContentState extends State { ); } - Widget _calendarDayBuilder(date, event, isToday, isInMonth) { + Widget _calendarDayBuilder( + DateTime date, + List> calenderEvents, + isToday, + isInMonth, + ) { + final builder = CardCellBuilder(_calendarBloc.cellCache); + final cells = calenderEvents.map((value) => value.event!).map((event) { + final child = builder.buildCell(cellId: event.cellId); + + return FlowyHover( + child: GestureDetector( + onTap: () { + final dataController = RowController( + rowId: event.cellId.rowId, + viewId: widget.view.id, + rowCache: _calendarBloc.rowCache, + ); + + FlowyOverlay.show( + context: context, + builder: (BuildContext context) { + return RowDetailPage( + cellBuilder: + GridCellBuilder(cellCache: _calendarBloc.cellCache), + dataController: dataController, + ); + }, + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: child, + ), + ), + ); + }).toList(); + + return _CalendarCard( + isToday: isToday, + isInMonth: isInMonth, + date: date, + children: cells, + onCreateEvent: (date) { + _calendarBloc.add( + CalendarEvent.createEvent( + date, + LocaleKeys.calendar_defaultNewCalendarTitle.tr(), + ), + ); + }, + ); + } +} + +class _CalendarCard extends StatelessWidget { + final bool isToday; + final bool isInMonth; + final DateTime date; + final List children; + final void Function(DateTime) onCreateEvent; + + const _CalendarCard({ + required this.isToday, + required this.isInMonth, + required this.date, + required this.children, + required this.onCreateEvent, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Color backgroundColor = Theme.of(context).colorScheme.surface; + if (!isInMonth) { + backgroundColor = AFThemeExtension.of(context).lightGreyHover; + } + + return ChangeNotifierProvider( + create: (_) => _CardEnterNotifier(), + builder: ((context, child) { + return Container( + color: backgroundColor, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (p) => notifyEnter(context, true), + onExit: (p) => notifyEnter(context, false), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + _Header( + date: date, + isInMonth: isInMonth, + isToday: isToday, + onCreate: () => onCreateEvent(date), + ), + ...children + ], + ), + ), + ), + ); + }), + ); + } + + notifyEnter(BuildContext context, bool isEnter) { + Provider.of<_CardEnterNotifier>( + context, + listen: false, + ).onEnter = isEnter; + } +} + +class _Header extends StatelessWidget { + final bool isToday; + final bool isInMonth; + final DateTime date; + final VoidCallback onCreate; + const _Header({ + required this.isToday, + required this.isInMonth, + required this.date, + required this.onCreate, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer<_CardEnterNotifier>( + builder: (context, notifier, _) { + final badge = _DayBadge( + isToday: isToday, + isInMonth: isInMonth, + date: date, + ); + return Row( + children: [ + if (notifier.onEnter) _NewEventButton(onClick: onCreate), + const Spacer(), + badge, + ], + ); + }, + ); + } +} + +class _NewEventButton extends StatelessWidget { + final VoidCallback onClick; + const _NewEventButton({ + required this.onClick, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return FlowyIconButton( + onPressed: onClick, + iconPadding: EdgeInsets.zero, + icon: svgWidget( + "home/add", + color: Theme.of(context).colorScheme.onSurface, + ), + width: 22, + ); + } +} + +class _DayBadge extends StatelessWidget { + final bool isToday; + final bool isInMonth; + final DateTime date; + const _DayBadge({ + required this.isToday, + required this.isInMonth, + required this.date, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { Color dayTextColor = Theme.of(context).colorScheme.onSurface; - Color cellBackgroundColor = Theme.of(context).colorScheme.surface; String dayString = date.day == 1 ? DateFormat('MMM d', context.locale.toLanguageTag()).format(date) : date.day.toString(); @@ -137,8 +345,8 @@ class _CalendarContentState extends State { } if (!isInMonth) { dayTextColor = Theme.of(context).disabledColor; - cellBackgroundColor = AFThemeExtension.of(context).lightGreyHover; } + Widget day = Container( decoration: BoxDecoration( color: isToday ? Theme.of(context).colorScheme.primary : null, @@ -151,12 +359,21 @@ class _CalendarContentState extends State { ), ); - return Container( - color: cellBackgroundColor, - child: Align( - alignment: Alignment.topRight, - child: day.padding(all: 6.0), - ), - ); + return day; } } + +class _CardEnterNotifier extends ChangeNotifier { + bool _onEnter = false; + + _CardEnterNotifier(); + + set onEnter(bool value) { + if (_onEnter != value) { + _onEnter = value; + notifyListeners(); + } + } + + bool get onEnter => _onEnter; +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart index 5df542893b..c0cb7eb245 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart @@ -9,7 +9,7 @@ import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../../application/field/field_controller.dart'; -import 'grid_data_controller.dart'; +import '../../application/database_controller.dart'; import 'dart:collection'; part 'grid_bloc.freezed.dart'; @@ -66,10 +66,10 @@ class GridBloc extends Bloc { } void _startListening() { - databaseController.addListener( - onGridChanged: (grid) { + final onDatabaseChanged = DatabaseCallbacks( + onDatabaseChanged: (database) { if (!isClosed) { - add(GridEvent.didReceiveGridUpdate(grid)); + add(GridEvent.didReceiveGridUpdate(database)); } }, onRowsChanged: (rowInfos, reason) { @@ -83,10 +83,11 @@ class GridBloc extends Bloc { } }, ); + databaseController.addListener(onDatabaseChanged: onDatabaseChanged); } Future _openGrid(Emitter emit) async { - final result = await databaseController.openGrid(); + final result = await databaseController.open(); result.fold( (grid) { emit( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_data_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_data_controller.dart deleted file mode 100644 index 4a6499edf2..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_data_controller.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; -import 'package:appflowy/plugins/database_view/application/view/view_cache.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:collection/collection.dart'; -import 'dart:async'; -import 'package:dartz/dartz.dart'; -import '../../application/database_service.dart'; -import '../../application/defines.dart'; -import '../../application/row/row_cache.dart'; - -typedef OnRowsChanged = void Function( - List rowInfos, - RowsChangedReason, -); -typedef ListenOnRowChangedCondition = bool Function(); - -class DatabaseController { - final String viewId; - final DatabaseBackendService _databaseBackendSvc; - final FieldController fieldController; - late DatabaseViewCache _viewCache; - - OnRowsChanged? _onRowChanged; - OnDatabaseChanged? _onGridChanged; - List get rowInfos => _viewCache.rowInfos; - RowCache get rowCache => _viewCache.rowCache; - - DatabaseController({required ViewPB view}) - : viewId = view.id, - _databaseBackendSvc = DatabaseBackendService(viewId: view.id), - fieldController = FieldController(viewId: view.id) { - _viewCache = DatabaseViewCache( - viewId: viewId, - fieldController: fieldController, - ); - _viewCache.addListener(onRowsChanged: (reason) { - _onRowChanged?.call(rowInfos, reason); - }); - } - - void addListener({ - OnDatabaseChanged? onGridChanged, - OnRowsChanged? onRowsChanged, - OnFieldsChanged? onFieldsChanged, - OnFiltersChanged? onFiltersChanged, - }) { - _onGridChanged = onGridChanged; - _onRowChanged = onRowsChanged; - - fieldController.addListener( - onReceiveFields: (fields) { - onFieldsChanged?.call(UnmodifiableListView(fields)); - }, - onFilters: onFiltersChanged, - ); - } - - Future> openGrid() async { - return _databaseBackendSvc.openGrid().then((result) { - return result.fold( - (grid) async { - _onGridChanged?.call(grid); - _viewCache.rowCache.initializeRows(grid.rows); - final result = await fieldController.loadFields( - fieldIds: grid.fields, - ); - return result; - }, - (err) => right(err), - ); - }); - } - - Future createRow() async { - await _databaseBackendSvc.createRow(); - } - - Future dispose() async { - await _databaseBackendSvc.closeView(); - await fieldController.dispose(); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_bloc.dart index 7a5d413880..aa1071f01c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_bloc.dart @@ -14,11 +14,11 @@ part 'row_bloc.freezed.dart'; class RowBloc extends Bloc { final RowBackendService _rowBackendSvc; - final RowDataController _dataController; + final RowController _dataController; RowBloc({ required RowInfo rowInfo, - required RowDataController dataController, + required RowController dataController, }) : _rowBackendSvc = RowBackendService(viewId: rowInfo.viewId), _dataController = dataController, super(RowState.initial(rowInfo, dataController.loadData())) { diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart index 83eb0f7782..9a53a04a77 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart @@ -7,7 +7,7 @@ import '../../../application/row/row_data_controller.dart'; part 'row_detail_bloc.freezed.dart'; class RowDetailBloc extends Bloc { - final RowDataController dataController; + final RowController dataController; RowDetailBloc({ required this.dataController, @@ -27,7 +27,7 @@ class RowDetailBloc extends Bloc { }, deleteField: (_DeleteField value) { final fieldService = FieldBackendService( - viewId: dataController.rowInfo.viewId, + viewId: dataController.viewId, fieldId: value.fieldId, ); fieldService.deleteField(); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart index 0c4af9382c..2b8f9a060c 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart @@ -1,4 +1,6 @@ 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-database/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'; @@ -16,17 +18,16 @@ import '../../application/row/row_data_controller.dart'; import '../../application/setting/setting_bloc.dart'; import '../application/filter/filter_menu_bloc.dart'; import '../application/grid_bloc.dart'; -import '../application/grid_data_controller.dart'; +import '../../application/database_controller.dart'; import '../application/sort/sort_menu_bloc.dart'; import 'grid_scroll.dart'; import 'layout/layout.dart'; import 'layout/sizes.dart'; import 'widgets/accessory_menu.dart'; -import 'widgets/cell/cell_builder.dart'; -import 'widgets/row/grid_row.dart'; +import 'widgets/row/row.dart'; import 'widgets/footer/grid_footer.dart'; import 'widgets/header/grid_header.dart'; -import 'widgets/row/row_detail.dart'; +import '../../widgets/row/row_detail.dart'; import 'widgets/shortcuts.dart'; import 'widgets/toolbar/grid_toolbar.dart'; @@ -35,7 +36,10 @@ class GridPage extends StatefulWidget { required this.view, this.onDeleted, Key? key, - }) : databaseController = DatabaseController(view: view), + }) : databaseController = DatabaseController( + view: view, + layoutType: LayoutTypePB.Grid, + ), super(key: key); final ViewPB view; @@ -275,14 +279,15 @@ class _GridRowsState extends State<_GridRows> { final fieldController = context.read().databaseController.fieldController; - final dataController = RowDataController( - rowInfo: rowInfo, + final dataController = RowController( + rowId: rowInfo.rowPB.id, + viewId: rowInfo.viewId, rowCache: rowCache, ); return SizeTransition( sizeFactor: animation, - child: GridRowWidget( + child: GridRow( rowInfo: rowInfo, dataController: dataController, cellBuilder: GridCellBuilder(cellCache: dataController.cellCache), @@ -307,8 +312,9 @@ class _GridRowsState extends State<_GridRows> { RowCache rowCache, GridCellBuilder cellBuilder, ) { - final dataController = RowDataController( - rowInfo: rowInfo, + final dataController = RowController( + viewId: rowInfo.viewId, + rowId: rowInfo.rowPB.id, rowCache: rowCache, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/prelude.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/prelude.dart deleted file mode 100644 index 7b34d8fcdf..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/prelude.dart +++ /dev/null @@ -1,7 +0,0 @@ -export 'cell_builder.dart'; -export 'text_cell.dart'; -export 'number_cell.dart'; -export 'date_cell/date_cell.dart'; -export 'checkbox_cell.dart'; -export 'select_option_cell/select_option_cell.dart'; -export 'url_cell/url_cell.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart index b3e71db5fd..f9cd9d104d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart @@ -5,7 +5,6 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/protobuf/flowy-database/checkbox_filter.pbenum.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart index 85a0ec45f7..2178d255f9 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart @@ -4,7 +4,6 @@ import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/protobuf/flowy-database/checklist_filter.pbenum.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart index 1120a178f7..c644454d3a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart @@ -8,7 +8,7 @@ import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.d import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../cell/select_option_cell/extension.dart'; +import '../../../../../../widgets/row/cells/select_option_cell/extension.dart'; import '../../filter_info.dart'; import 'select_option_loader.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart index f1aa4591d0..b05dfdaf63 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart @@ -1,8 +1,6 @@ import 'package:appflowy/plugins/database_view/grid/application/filter/select_option_filter_bloc.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_option_filter.pb.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart index c3f84eb0ce..94ccd5164e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart @@ -4,7 +4,6 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/protobuf/flowy-database/text_filter.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/filter_menu.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/filter_menu.dart index 4d6214710a..a7f17b1e8a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/filter_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/filter_menu.dart @@ -5,7 +5,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart index 58c44099c8..91d5368286 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart @@ -4,7 +4,6 @@ import 'package:appflowy_popover/appflowy_popover.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:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart index 197b15a60e..da6c28d606 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart @@ -1,9 +1,6 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.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/widget/spacing.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:flutter/material.dart'; import '../../layout/sizes.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart index 68c24d291b..29e6203dc3 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart @@ -5,7 +5,6 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:dartz/dartz.dart' show Either; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart index 7a1ececb57..0e4c6e28f5 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart @@ -9,7 +9,6 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart index 6027492815..8e29b785e7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart @@ -6,8 +6,6 @@ import 'package:easy_localization/easy_localization.dart' hide DateFormat; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/protobuf/flowy-database/date_type_option_entities.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/number.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/number.dart index 067bb2d4c8..92018009af 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/number.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/number.dart @@ -4,8 +4,6 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/typ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/protobuf/flowy-database/format.pbenum.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart index 91a646574f..27965ca186 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart @@ -2,8 +2,6 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/sel import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -11,7 +9,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import '../../../layout/sizes.dart'; -import '../../cell/select_option_cell/extension.dart'; +import '../../../../../widgets/row/cells/select_option_cell/extension.dart'; import '../../common/type_option_separator.dart'; import 'select_option_editor.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart index f4c931d0d3..c248ec01df 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart @@ -1,5 +1,5 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/edit_select_option_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/extension.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row_action_sheet.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/action.dart similarity index 90% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row_action_sheet.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/action.dart index d270b3a084..2aecd5f868 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row_action_sheet.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/action.dart @@ -12,9 +12,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import '../../layout/sizes.dart'; -class GridRowActionSheet extends StatelessWidget { +class RowActions extends StatelessWidget { final RowInfo rowData; - const GridRowActionSheet({required this.rowData, Key? key}) : super(key: key); + const RowActions({required this.rowData, Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -24,9 +24,7 @@ class GridRowActionSheet extends StatelessWidget { builder: (context, state) { final cells = _RowAction.values .where((value) => value.enable()) - .map( - (action) => _RowActionCell(action: action), - ) + .map((action) => _ActionCell(action: action)) .toList(); // @@ -49,9 +47,9 @@ class GridRowActionSheet extends StatelessWidget { } } -class _RowActionCell extends StatelessWidget { +class _ActionCell extends StatelessWidget { final _RowAction action; - const _RowActionCell({required this.action, Key? key}) : super(key: key); + const _ActionCell({required this.action, Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/grid_row.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart similarity index 93% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/grid_row.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart index c18038f253..09aff68112 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/grid_row.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart @@ -2,30 +2,29 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_service.dar import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; +import '../../../../widgets/row/accessory/cell_accessory.dart'; import '../../layout/sizes.dart'; -import '../cell/cell_accessory.dart'; -import '../cell/cell_container.dart'; -import '../cell/prelude.dart'; -import 'row_action_sheet.dart'; +import '../../../../widgets/row/cells/cell_container.dart'; +import 'action.dart'; import "package:appflowy/generated/locale_keys.g.dart"; import 'package:easy_localization/easy_localization.dart'; -class GridRowWidget extends StatefulWidget { +class GridRow extends StatefulWidget { final RowInfo rowInfo; - final RowDataController dataController; + final RowController dataController; final GridCellBuilder cellBuilder; final void Function(BuildContext, GridCellBuilder) openDetailPage; - const GridRowWidget({ + const GridRow({ required this.rowInfo, required this.dataController, required this.cellBuilder, @@ -34,10 +33,10 @@ class GridRowWidget extends StatefulWidget { }) : super(key: key); @override - State createState() => _GridRowWidgetState(); + State createState() => _GridRowState(); } -class _GridRowWidgetState extends State { +class _GridRowState extends State { late RowBloc _rowBloc; @override @@ -111,8 +110,7 @@ class _RowLeadingState extends State<_RowLeading> { direction: PopoverDirection.rightWithCenterAligned, margin: const EdgeInsets.all(6), popupBuilder: (BuildContext popoverContext) { - return GridRowActionSheet( - rowData: context.read().state.rowInfo); + return RowActions(rowData: context.read().state.rowInfo); }, child: Consumer( builder: (context, state, _) { diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart index a0c175659e..0bcaee5175 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart @@ -9,9 +9,6 @@ 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:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'dart:math' as math; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart index 19935e5471..453e277b1d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart @@ -4,7 +4,6 @@ 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:flowy_infra_ui/style_widget/button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart index f5406997f4..59cd2cb99b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart @@ -6,9 +6,6 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.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'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/setting_button.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/setting_button.dart index 9de1d2091a..62a9bea883 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/setting_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/setting_button.dart @@ -5,7 +5,6 @@ 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:flowy_infra_ui/style_widget/button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart index 5910bc7cc4..8469383cf1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart @@ -4,7 +4,6 @@ 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:flowy_infra_ui/style_widget/button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_checkbox_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/checkbox_card_cell_bloc.dart similarity index 61% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_checkbox_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/checkbox_card_cell_bloc.dart index 57cc336ed7..c6e13befad 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_checkbox_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/checkbox_card_cell_bloc.dart @@ -3,16 +3,16 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; import '../../../application/cell/cell_controller_builder.dart'; -part 'board_checkbox_cell_bloc.freezed.dart'; +part 'checkbox_card_cell_bloc.freezed.dart'; -class BoardCheckboxCellBloc - extends Bloc { +class CheckboxCardCellBloc + extends Bloc { final CheckboxCellController cellController; void Function()? _onCellChangedFn; - BoardCheckboxCellBloc({ + CheckboxCardCellBloc({ required this.cellController, - }) : super(BoardCheckboxCellState.initial(cellController)) { - on( + }) : super(CheckboxCardCellState.initial(cellController)) { + on( (event, emit) async { await event.when( initial: () async { @@ -43,7 +43,7 @@ class BoardCheckboxCellBloc _onCellChangedFn = cellController.startListening( onCellChanged: ((cellContent) { if (!isClosed) { - add(BoardCheckboxCellEvent.didReceiveCellUpdate(cellContent ?? "")); + add(CheckboxCardCellEvent.didReceiveCellUpdate(cellContent ?? "")); } }), ); @@ -51,21 +51,21 @@ class BoardCheckboxCellBloc } @freezed -class BoardCheckboxCellEvent with _$BoardCheckboxCellEvent { - const factory BoardCheckboxCellEvent.initial() = _InitialCell; - const factory BoardCheckboxCellEvent.select() = _Selected; - const factory BoardCheckboxCellEvent.didReceiveCellUpdate( - String cellContent) = _DidReceiveCellUpdate; +class CheckboxCardCellEvent with _$CheckboxCardCellEvent { + const factory CheckboxCardCellEvent.initial() = _InitialCell; + const factory CheckboxCardCellEvent.select() = _Selected; + const factory CheckboxCardCellEvent.didReceiveCellUpdate(String cellContent) = + _DidReceiveCellUpdate; } @freezed -class BoardCheckboxCellState with _$BoardCheckboxCellState { - const factory BoardCheckboxCellState({ +class CheckboxCardCellState with _$CheckboxCardCellState { + const factory CheckboxCardCellState({ required bool isSelected, }) = _CheckboxCellState; - factory BoardCheckboxCellState.initial(TextCellController context) { - return BoardCheckboxCellState( + factory CheckboxCardCellState.initial(TextCellController context) { + return CheckboxCardCellState( isSelected: _isSelected(context.getCellData())); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_date_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/date_card_cell_bloc.dart similarity index 69% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_date_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/date_card_cell_bloc.dart index 288f3f26ad..68433184e7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_date_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/date_card_cell_bloc.dart @@ -5,15 +5,15 @@ import 'dart:async'; import '../../../application/cell/cell_controller_builder.dart'; import '../../../application/field/field_controller.dart'; -part 'board_date_cell_bloc.freezed.dart'; +part 'date_card_cell_bloc.freezed.dart'; -class BoardDateCellBloc extends Bloc { +class DateCardCellBloc extends Bloc { final DateCellController cellController; void Function()? _onCellChangedFn; - BoardDateCellBloc({required this.cellController}) - : super(BoardDateCellState.initial(cellController)) { - on( + DateCardCellBloc({required this.cellController}) + : super(DateCardCellState.initial(cellController)) { + on( (event, emit) async { event.when( initial: () => _startListening(), @@ -40,7 +40,7 @@ class BoardDateCellBloc extends Bloc { _onCellChangedFn = cellController.startListening( onCellChanged: ((data) { if (!isClosed) { - add(BoardDateCellEvent.didReceiveCellUpdate(data)); + add(DateCardCellEvent.didReceiveCellUpdate(data)); } }), ); @@ -48,24 +48,24 @@ class BoardDateCellBloc extends Bloc { } @freezed -class BoardDateCellEvent with _$BoardDateCellEvent { - const factory BoardDateCellEvent.initial() = _InitialCell; - const factory BoardDateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = +class DateCardCellEvent with _$DateCardCellEvent { + const factory DateCardCellEvent.initial() = _InitialCell; + const factory DateCardCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate; } @freezed -class BoardDateCellState with _$BoardDateCellState { - const factory BoardDateCellState({ +class DateCardCellState with _$DateCardCellState { + const factory DateCardCellState({ required DateCellDataPB? data, required String dateStr, required FieldInfo fieldInfo, - }) = _BoardDateCellState; + }) = _DateCardCellState; - factory BoardDateCellState.initial(DateCellController context) { + factory DateCardCellState.initial(DateCellController context) { final cellData = context.getCellData(); - return BoardDateCellState( + return DateCardCellState( fieldInfo: context.fieldInfo, data: cellData, dateStr: _dateStrFromCellData(cellData), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_number_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/number_card_cell_bloc.dart similarity index 60% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_number_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/number_card_cell_bloc.dart index bcad0469a0..54c6d242d1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_number_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/number_card_cell_bloc.dart @@ -4,16 +4,16 @@ import 'dart:async'; import '../../../application/cell/cell_controller_builder.dart'; -part 'board_number_cell_bloc.freezed.dart'; +part 'number_card_cell_bloc.freezed.dart'; -class BoardNumberCellBloc - extends Bloc { +class NumberCardCellBloc + extends Bloc { final NumberCellController cellController; void Function()? _onCellChangedFn; - BoardNumberCellBloc({ + NumberCardCellBloc({ required this.cellController, - }) : super(BoardNumberCellState.initial(cellController)) { - on( + }) : super(NumberCardCellState.initial(cellController)) { + on( (event, emit) async { await event.when( initial: () async { @@ -41,7 +41,7 @@ class BoardNumberCellBloc _onCellChangedFn = cellController.startListening( onCellChanged: ((cellContent) { if (!isClosed) { - add(BoardNumberCellEvent.didReceiveCellUpdate(cellContent ?? "")); + add(NumberCardCellEvent.didReceiveCellUpdate(cellContent ?? "")); } }), ); @@ -49,20 +49,20 @@ class BoardNumberCellBloc } @freezed -class BoardNumberCellEvent with _$BoardNumberCellEvent { - const factory BoardNumberCellEvent.initial() = _InitialCell; - const factory BoardNumberCellEvent.didReceiveCellUpdate(String cellContent) = +class NumberCardCellEvent with _$NumberCardCellEvent { + const factory NumberCardCellEvent.initial() = _InitialCell; + const factory NumberCardCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate; } @freezed -class BoardNumberCellState with _$BoardNumberCellState { - const factory BoardNumberCellState({ +class NumberCardCellState with _$NumberCardCellState { + const factory NumberCardCellState({ required String content, - }) = _BoardNumberCellState; + }) = _NumberCardCellState; - factory BoardNumberCellState.initial(TextCellController context) => - BoardNumberCellState( + factory NumberCardCellState.initial(TextCellController context) => + NumberCardCellState( content: context.getCellData() ?? "", ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_select_option_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/select_option_card_cell_bloc.dart similarity index 66% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_select_option_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/select_option_card_cell_bloc.dart index 747c055cda..549ef8e24d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_select_option_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/select_option_card_cell_bloc.dart @@ -4,17 +4,17 @@ import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.d import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -part 'board_select_option_cell_bloc.freezed.dart'; +part 'select_option_card_cell_bloc.freezed.dart'; -class BoardSelectOptionCellBloc - extends Bloc { +class SelectOptionCardCellBloc + extends Bloc { final SelectOptionCellController cellController; void Function()? _onCellChangedFn; - BoardSelectOptionCellBloc({ + SelectOptionCardCellBloc({ required this.cellController, - }) : super(BoardSelectOptionCellState.initial(cellController)) { - on( + }) : super(SelectOptionCardCellState.initial(cellController)) { + on( (event, emit) async { await event.when( initial: () async { @@ -42,7 +42,7 @@ class BoardSelectOptionCellBloc _onCellChangedFn = cellController.startListening( onCellChanged: ((selectOptionContext) { if (!isClosed) { - add(BoardSelectOptionCellEvent.didReceiveOptions( + add(SelectOptionCardCellEvent.didReceiveOptions( selectOptionContext?.selectOptions ?? [], )); } @@ -52,23 +52,23 @@ class BoardSelectOptionCellBloc } @freezed -class BoardSelectOptionCellEvent with _$BoardSelectOptionCellEvent { - const factory BoardSelectOptionCellEvent.initial() = _InitialCell; - const factory BoardSelectOptionCellEvent.didReceiveOptions( +class SelectOptionCardCellEvent with _$SelectOptionCardCellEvent { + const factory SelectOptionCardCellEvent.initial() = _InitialCell; + const factory SelectOptionCardCellEvent.didReceiveOptions( List selectedOptions, ) = _DidReceiveOptions; } @freezed -class BoardSelectOptionCellState with _$BoardSelectOptionCellState { - const factory BoardSelectOptionCellState({ +class SelectOptionCardCellState with _$SelectOptionCardCellState { + const factory SelectOptionCardCellState({ required List selectedOptions, - }) = _BoardSelectOptionCellState; + }) = _SelectOptionCardCellState; - factory BoardSelectOptionCellState.initial( + factory SelectOptionCardCellState.initial( SelectOptionCellController context) { final data = context.getCellData(); - return BoardSelectOptionCellState( + return SelectOptionCardCellState( selectedOptions: data?.selectOptions ?? [], ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_text_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/text_card_cell_bloc.dart similarity index 63% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_text_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/text_card_cell_bloc.dart index 7e862e3e81..34266943b5 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_text_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/text_card_cell_bloc.dart @@ -3,15 +3,15 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; -part 'board_text_cell_bloc.freezed.dart'; +part 'text_card_cell_bloc.freezed.dart'; -class BoardTextCellBloc extends Bloc { +class TextCardCellBloc extends Bloc { final TextCellController cellController; void Function()? _onCellChangedFn; - BoardTextCellBloc({ + TextCardCellBloc({ required this.cellController, - }) : super(BoardTextCellState.initial(cellController)) { - on( + }) : super(TextCardCellState.initial(cellController)) { + on( (event, emit) async { await event.when( initial: () async { @@ -48,7 +48,7 @@ class BoardTextCellBloc extends Bloc { _onCellChangedFn = cellController.startListening( onCellChanged: ((cellContent) { if (!isClosed) { - add(BoardTextCellEvent.didReceiveCellUpdate(cellContent ?? "")); + add(TextCardCellEvent.didReceiveCellUpdate(cellContent ?? "")); } }), ); @@ -56,23 +56,23 @@ class BoardTextCellBloc extends Bloc { } @freezed -class BoardTextCellEvent with _$BoardTextCellEvent { - const factory BoardTextCellEvent.initial() = _InitialCell; - const factory BoardTextCellEvent.updateText(String text) = _UpdateContent; - const factory BoardTextCellEvent.enableEdit(bool enabled) = _EnableEdit; - const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) = +class TextCardCellEvent with _$TextCardCellEvent { + const factory TextCardCellEvent.initial() = _InitialCell; + const factory TextCardCellEvent.updateText(String text) = _UpdateContent; + const factory TextCardCellEvent.enableEdit(bool enabled) = _EnableEdit; + const factory TextCardCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate; } @freezed -class BoardTextCellState with _$BoardTextCellState { - const factory BoardTextCellState({ +class TextCardCellState with _$TextCardCellState { + const factory TextCardCellState({ required String content, required bool enableEdit, - }) = _BoardTextCellState; + }) = _TextCardCellState; - factory BoardTextCellState.initial(TextCellController context) => - BoardTextCellState( + factory TextCardCellState.initial(TextCellController context) => + TextCardCellState( content: context.getCellData() ?? "", enableEdit: false, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_url_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/url_card_cell_bloc.dart similarity index 67% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_url_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/url_card_cell_bloc.dart index 906b0c6f61..616619c672 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/board_url_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/url_card_cell_bloc.dart @@ -4,15 +4,15 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; -part 'board_url_cell_bloc.freezed.dart'; +part 'url_card_cell_bloc.freezed.dart'; -class BoardURLCellBloc extends Bloc { +class URLCardCellBloc extends Bloc { final URLCellController cellController; void Function()? _onCellChangedFn; - BoardURLCellBloc({ + URLCardCellBloc({ required this.cellController, - }) : super(BoardURLCellState.initial(cellController)) { - on( + }) : super(URLCardCellState.initial(cellController)) { + on( (event, emit) async { event.when( initial: () { @@ -46,7 +46,7 @@ class BoardURLCellBloc extends Bloc { _onCellChangedFn = cellController.startListening( onCellChanged: ((cellData) { if (!isClosed) { - add(BoardURLCellEvent.didReceiveCellUpdate(cellData)); + add(URLCardCellEvent.didReceiveCellUpdate(cellData)); } }), ); @@ -54,23 +54,23 @@ class BoardURLCellBloc extends Bloc { } @freezed -class BoardURLCellEvent with _$BoardURLCellEvent { - const factory BoardURLCellEvent.initial() = _InitialCell; - const factory BoardURLCellEvent.updateURL(String url) = _UpdateURL; - const factory BoardURLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) = +class URLCardCellEvent with _$URLCardCellEvent { + const factory URLCardCellEvent.initial() = _InitialCell; + const factory URLCardCellEvent.updateURL(String url) = _UpdateURL; + const factory URLCardCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate; } @freezed -class BoardURLCellState with _$BoardURLCellState { - const factory BoardURLCellState({ +class URLCardCellState with _$URLCardCellState { + const factory URLCardCellState({ required String content, required String url, - }) = _BoardURLCellState; + }) = _URLCardCellState; - factory BoardURLCellState.initial(URLCellController context) { + factory URLCardCellState.initial(URLCellController context) { final cellData = context.getCellData(); - return BoardURLCellState( + return URLCardCellState( content: cellData?.content ?? "", url: cellData?.url ?? "", ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_number_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/board_number_cell.dart similarity index 75% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_number_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/board_number_cell.dart index d2d0aa69d7..78a19bacdf 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_number_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/board_number_cell.dart @@ -2,7 +2,7 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_ import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../application/card/board_number_cell_bloc.dart'; +import 'bloc/number_card_cell_bloc.dart'; import 'define.dart'; class BoardNumberCell extends StatefulWidget { @@ -16,19 +16,19 @@ class BoardNumberCell extends StatefulWidget { }) : super(key: key); @override - State createState() => _BoardNumberCellState(); + State createState() => _NumberCardCellState(); } -class _BoardNumberCellState extends State { - late BoardNumberCellBloc _cellBloc; +class _NumberCardCellState extends State { + late NumberCardCellBloc _cellBloc; @override void initState() { final cellController = widget.cellControllerBuilder.build() as NumberCellController; - _cellBloc = BoardNumberCellBloc(cellController: cellController) - ..add(const BoardNumberCellEvent.initial()); + _cellBloc = NumberCardCellBloc(cellController: cellController) + ..add(const NumberCardCellEvent.initial()); super.initState(); } @@ -36,7 +36,7 @@ class _BoardNumberCellState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _cellBloc, - child: BlocBuilder( + child: BlocBuilder( buildWhen: (previous, current) => previous.content != current.content, builder: (context, state) { if (state.content.isEmpty) { @@ -46,7 +46,7 @@ class _BoardNumberCellState extends State { alignment: Alignment.centerLeft, child: Padding( padding: EdgeInsets.symmetric( - vertical: BoardSizes.cardCellVPadding, + vertical: CardSizes.cardCellVPadding, ), child: FlowyText.medium( state.content, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/card.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card.dart similarity index 79% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/card.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card.dart index 8dc418487d..196b4b40ec 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/card.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card.dart @@ -1,47 +1,52 @@ -import 'package:appflowy/plugins/database_view/board/application/card/card_bloc.dart'; -import 'package:appflowy/plugins/database_view/board/application/card/card_data_controller.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row_action_sheet.dart'; +import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/action.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'board_cell.dart'; +import 'card_bloc.dart'; +import 'cells/card_cell.dart'; import 'card_cell_builder.dart'; import 'container/accessory.dart'; import 'container/card_container.dart'; -class BoardCard extends StatefulWidget { +class Card extends StatefulWidget { + final RowPB row; final String viewId; - final String groupId; final String fieldId; + final CustomCardData? cardData; final bool isEditing; - final CardDataController dataController; - final BoardCellBuilder cellBuilder; + final RowCache rowCache; + final CardCellBuilder cellBuilder; final void Function(BuildContext) openCard; final VoidCallback onStartEditing; final VoidCallback onEndEditing; + final CardConfiguration? configuration; - const BoardCard({ + const Card({ + required this.row, required this.viewId, - required this.groupId, required this.fieldId, required this.isEditing, - required this.dataController, + required this.rowCache, required this.cellBuilder, required this.openCard, required this.onStartEditing, required this.onEndEditing, + this.cardData, + this.configuration, Key? key, }) : super(key: key); @override - State createState() => _BoardCardState(); + State> createState() => _CardState(); } -class _BoardCardState extends State { - late BoardCardBloc _cardBloc; +class _CardState extends State> { + late CardBloc _cardBloc; late EditableRowNotifier rowNotifier; late PopoverController popoverController; AccessoryType? accessoryType; @@ -49,11 +54,12 @@ class _BoardCardState extends State { @override void initState() { rowNotifier = EditableRowNotifier(isEditing: widget.isEditing); - _cardBloc = BoardCardBloc( + _cardBloc = CardBloc( viewId: widget.viewId, groupFieldId: widget.fieldId, - dataController: widget.dataController, isEditing: widget.isEditing, + row: widget.row, + rowCache: widget.rowCache, )..add(const BoardCardEvent.initial()); rowNotifier.isEditing.addListener(() { @@ -75,7 +81,7 @@ class _BoardCardState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _cardBloc, - child: BlocBuilder( + child: BlocBuilder( buildWhen: (previous, current) { // Rebuild when: // 1.If the length of the cells is not the same @@ -110,11 +116,12 @@ class _BoardCardState extends State { }, openAccessory: _handleOpenAccessory, openCard: (context) => widget.openCard(context), - child: _CellColumn( - groupId: widget.groupId, + child: _CardContent( rowNotifier: rowNotifier, cellBuilder: widget.cellBuilder, cells: state.cells, + cardConfiguration: widget.configuration, + cardData: widget.cardData, ), ), ); @@ -142,8 +149,8 @@ class _BoardCardState extends State { case AccessoryType.edit: throw UnimplementedError(); case AccessoryType.more: - return GridRowActionSheet( - rowData: context.read().rowInfo(), + return RowActions( + rowData: context.read().rowInfo(), ); } } @@ -156,16 +163,18 @@ class _BoardCardState extends State { } } -class _CellColumn extends StatelessWidget { - final String groupId; - final BoardCellBuilder cellBuilder; +class _CardContent extends StatelessWidget { + final CardCellBuilder cellBuilder; final EditableRowNotifier rowNotifier; final List cells; - const _CellColumn({ - required this.groupId, + final CardConfiguration? cardConfiguration; + final CustomCardData? cardData; + const _CardContent({ required this.rowNotifier, required this.cellBuilder, required this.cells, + required this.cardData, + this.cardConfiguration, Key? key, }) : super(key: key); @@ -188,7 +197,7 @@ class _CellColumn extends StatelessWidget { cells.asMap().forEach( (int index, BoardCellEquatable cell) { final isEditing = index == 0 ? rowNotifier.isEditing.value : false; - final cellNotifier = EditableCellNotifier(isEditing: isEditing); + final cellNotifier = EditableCardNotifier(isEditing: isEditing); if (index == 0) { // Only use the first cell to receive user's input when click the edit @@ -200,9 +209,10 @@ class _CellColumn extends StatelessWidget { key: cell.identifier.key(), padding: const EdgeInsets.only(left: 4, right: 4), child: cellBuilder.buildCell( - groupId, - cell.identifier, - cellNotifier, + cellId: cell.identifier, + cellNotifier: cellNotifier, + cardConfiguration: cardConfiguration, + cardData: cardData, ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/card_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card_bloc.dart similarity index 79% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/card_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card_bloc.dart index 65530b950d..d10c4c713a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/card/card_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card_bloc.dart @@ -1,34 +1,36 @@ import 'dart:collection'; import 'package:equatable/equatable.dart'; import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; -import '../../../application/cell/cell_service.dart'; -import '../../../application/row/row_cache.dart'; -import '../../../application/row/row_service.dart'; -import 'card_data_controller.dart'; + +import '../../application/cell/cell_service.dart'; +import '../../application/row/row_cache.dart'; +import '../../application/row/row_service.dart'; part 'card_bloc.freezed.dart'; -class BoardCardBloc extends Bloc { +class CardBloc extends Bloc { + final RowPB row; final String groupFieldId; final RowBackendService _rowBackendSvc; - final CardDataController _dataController; + final RowCache _rowCache; + VoidCallback? _rowCallback; - BoardCardBloc({ + CardBloc({ + required this.row, required this.groupFieldId, required String viewId, - required CardDataController dataController, + required RowCache rowCache, required bool isEditing, - }) : _rowBackendSvc = RowBackendService( - viewId: viewId, - ), - _dataController = dataController, + }) : _rowBackendSvc = RowBackendService(viewId: viewId), + _rowCache = rowCache, super( BoardCardState.initial( - dataController.rowPB, - _makeCells(groupFieldId, dataController.loadData()), + row, + _makeCells(groupFieldId, rowCache.loadGridCells(row.id)), isEditing, ), ) { @@ -54,7 +56,10 @@ class BoardCardBloc extends Bloc { @override Future close() async { - _dataController.dispose(); + if (_rowCallback != null) { + _rowCache.removeRowListener(_rowCallback!); + _rowCallback = null; + } return super.close(); } @@ -69,8 +74,9 @@ class BoardCardBloc extends Bloc { } Future _startListening() async { - _dataController.addListener( - onRowChanged: (cellMap, reason) { + _rowCallback = _rowCache.addListener( + rowId: row.id, + onCellUpdated: (cellMap, reason) { if (!isClosed) { final cells = _makeCells(groupFieldId, cellMap); add(BoardCardEvent.didReceiveCells(cells, reason)); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/card_cell_builder.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card_cell_builder.dart similarity index 54% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/card_cell_builder.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card_cell_builder.dart index e7db611b95..4bc655a305 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/card_cell_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card_cell_builder.dart @@ -2,83 +2,78 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_ import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:flutter/material.dart'; -import '../../../application/cell/cell_service.dart'; -import 'board_cell.dart'; -import 'board_checkbox_cell.dart'; -import 'board_checklist_cell.dart'; -import 'board_date_cell.dart'; -import 'board_number_cell.dart'; -import 'board_select_option_cell.dart'; -import 'board_text_cell.dart'; -import 'board_url_cell.dart'; +import '../../application/cell/cell_service.dart'; +import 'cells/card_cell.dart'; +import 'cells/checkbox_card_cell.dart'; +import 'cells/checklist_card_cell.dart'; +import 'cells/date_card_cell.dart'; +import 'cells/number_card_cell.dart'; +import 'cells/select_option_card_cell.dart'; +import 'cells/text_card_cell.dart'; +import 'cells/url_card_cell.dart'; -abstract class BoardCellBuilderDelegate { - CellCache get cellCache; -} +// T represents as the Generic card data +class CardCellBuilder { + final CellCache cellCache; -class BoardCellBuilder { - final BoardCellBuilderDelegate delegate; + CardCellBuilder(this.cellCache); - BoardCellBuilder(this.delegate); - - Widget buildCell( - String groupId, - CellIdentifier cellId, - EditableCellNotifier cellNotifier, - ) { + Widget buildCell({ + CustomCardData? cardData, + required CellIdentifier cellId, + EditableCardNotifier? cellNotifier, + CardConfiguration? cardConfiguration, + }) { final cellControllerBuilder = CellControllerBuilder( cellId: cellId, - cellCache: delegate.cellCache, + cellCache: cellCache, ); final key = cellId.key(); switch (cellId.fieldType) { case FieldType.Checkbox: - return BoardCheckboxCell( - groupId: groupId, + return CheckboxCardCell( cellControllerBuilder: cellControllerBuilder, key: key, ); case FieldType.DateTime: - return BoardDateCell( - groupId: groupId, + return DateCardCell( cellControllerBuilder: cellControllerBuilder, key: key, ); case FieldType.SingleSelect: - return BoardSelectOptionCell( - groupId: groupId, + return SelectOptionCardCell( + renderHook: cardConfiguration?.renderHook[FieldType.SingleSelect], cellControllerBuilder: cellControllerBuilder, + cardData: cardData, key: key, ); case FieldType.MultiSelect: - return BoardSelectOptionCell( - groupId: groupId, + return SelectOptionCardCell( + renderHook: cardConfiguration?.renderHook[FieldType.MultiSelect], cellControllerBuilder: cellControllerBuilder, + cardData: cardData, editableNotifier: cellNotifier, key: key, ); case FieldType.Checklist: - return BoardChecklistCell( + return ChecklistCardCell( cellControllerBuilder: cellControllerBuilder, key: key, ); case FieldType.Number: - return BoardNumberCell( - groupId: groupId, + return NumberCardCell( cellControllerBuilder: cellControllerBuilder, key: key, ); case FieldType.RichText: - return BoardTextCell( - groupId: groupId, + return TextCardCell( cellControllerBuilder: cellControllerBuilder, editableNotifier: cellNotifier, key: key, ); case FieldType.URL: - return BoardUrlCell( - groupId: groupId, + return URLCardCell( cellControllerBuilder: cellControllerBuilder, key: key, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/card_cell.dart similarity index 63% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/card_cell.dart index bc34d856c6..6bc9ee9eac 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/card_cell.dart @@ -1,14 +1,39 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:flutter/material.dart'; -abstract class FocusableBoardCell { - set becomeFocus(bool isFocus); +typedef CellRenderHook = Widget? Function(C cellData, T cardData); +typedef RenderHookByFieldType = Map>; + +class CardConfiguration { + final RenderHookByFieldType renderHook = {}; + CardConfiguration(); + + void addSelectOptionHook( + CellRenderHook, CustomCardData> hook, + ) { + selectOptionHook(cellData, cardData) { + if (cellData is List) { + hook(cellData, cardData); + } + } + + renderHook[FieldType.SingleSelect] = selectOptionHook; + renderHook[FieldType.MultiSelect] = selectOptionHook; + } } -class EditableCellNotifier { +abstract class CardCell extends StatefulWidget { + final T? cardData; + + const CardCell({super.key, this.cardData}); +} + +class EditableCardNotifier { final ValueNotifier isCellEditing; - EditableCellNotifier({bool isEditing = false}) + EditableCardNotifier({bool isEditing = false}) : isCellEditing = ValueNotifier(isEditing); void dispose() { @@ -17,7 +42,7 @@ class EditableCellNotifier { } class EditableRowNotifier { - final Map _cells = {}; + final Map _cells = {}; final ValueNotifier isEditing; EditableRowNotifier({required bool isEditing}) @@ -25,7 +50,7 @@ class EditableRowNotifier { void bindCell( CellIdentifier cellIdentifier, - EditableCellNotifier notifier, + EditableCardNotifier notifier, ) { assert( _cells.values.isEmpty, @@ -80,7 +105,7 @@ abstract class EditableCell { // the row notifier receive its cells event. For example: begin editing the // cell or end editing the cell. // - EditableCellNotifier? get editableNotifier; + EditableCardNotifier? get editableNotifier; } class EditableCellId { diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_checkbox_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checkbox_card_cell.dart similarity index 65% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_checkbox_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checkbox_card_cell.dart index dee8b7b91f..ad58b9259d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_checkbox_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checkbox_card_cell.dart @@ -1,33 +1,33 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/plugins/database_view/board/application/card/board_checkbox_cell_bloc.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class BoardCheckboxCell extends StatefulWidget { - final String groupId; +import '../bloc/checkbox_card_cell_bloc.dart'; +import 'card_cell.dart'; + +class CheckboxCardCell extends CardCell { final CellControllerBuilder cellControllerBuilder; - const BoardCheckboxCell({ - required this.groupId, + const CheckboxCardCell({ required this.cellControllerBuilder, Key? key, }) : super(key: key); @override - State createState() => _BoardCheckboxCellState(); + State createState() => _CheckboxCardCellState(); } -class _BoardCheckboxCellState extends State { - late BoardCheckboxCellBloc _cellBloc; +class _CheckboxCardCellState extends State { + late CheckboxCardCellBloc _cellBloc; @override void initState() { final cellController = widget.cellControllerBuilder.build() as CheckboxCellController; - _cellBloc = BoardCheckboxCellBloc(cellController: cellController); - _cellBloc.add(const BoardCheckboxCellEvent.initial()); + _cellBloc = CheckboxCardCellBloc(cellController: cellController); + _cellBloc.add(const CheckboxCardCellEvent.initial()); super.initState(); } @@ -35,7 +35,7 @@ class _BoardCheckboxCellState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _cellBloc, - child: BlocBuilder( + child: BlocBuilder( buildWhen: (previous, current) => previous.isSelected != current.isSelected, builder: (context, state) { @@ -49,8 +49,8 @@ class _BoardCheckboxCellState extends State { icon: icon, width: 20, onPressed: () => context - .read() - .add(const BoardCheckboxCellEvent.select()), + .read() + .add(const CheckboxCardCellEvent.select()), ), ); }, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checklist_card_cell.dart similarity index 53% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_checklist_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checklist_card_cell.dart index ab4f6ad572..639fa50913 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_checklist_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checklist_card_cell.dart @@ -1,27 +1,28 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../grid/application/cell/checklist_cell_bloc.dart'; -import '../../../grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart'; +import '../../row/cells/checklist_cell/checklist_cell_bloc.dart'; +import 'card_cell.dart'; -class BoardChecklistCell extends StatefulWidget { +class ChecklistCardCell extends CardCell { final CellControllerBuilder cellControllerBuilder; - const BoardChecklistCell({required this.cellControllerBuilder, Key? key}) + const ChecklistCardCell({required this.cellControllerBuilder, Key? key}) : super(key: key); @override - State createState() => _BoardChecklistCellState(); + State createState() => _ChecklistCardCellState(); } -class _BoardChecklistCellState extends State { - late ChecklistCellBloc _cellBloc; +class _ChecklistCardCellState extends State { + late ChecklistCardCellBloc _cellBloc; @override void initState() { final cellController = widget.cellControllerBuilder.build() as ChecklistCellController; - _cellBloc = ChecklistCellBloc(cellController: cellController); + _cellBloc = ChecklistCardCellBloc(cellController: cellController); _cellBloc.add(const ChecklistCellEvent.initial()); super.initState(); } @@ -30,7 +31,7 @@ class _BoardChecklistCellState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _cellBloc, - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) => ChecklistProgressBar(percent: state.percent), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_date_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/date_card_cell.dart similarity index 68% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_date_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/date_card_cell.dart index 4fd733af63..c954cb9d7b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_date_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/date_card_cell.dart @@ -1,35 +1,34 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/plugins/database_view/board/application/card/board_date_cell_bloc.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'define.dart'; +import '../bloc/date_card_cell_bloc.dart'; +import '../define.dart'; +import 'card_cell.dart'; -class BoardDateCell extends StatefulWidget { - final String groupId; +class DateCardCell extends CardCell { final CellControllerBuilder cellControllerBuilder; - const BoardDateCell({ - required this.groupId, + const DateCardCell({ required this.cellControllerBuilder, Key? key, }) : super(key: key); @override - State createState() => _BoardDateCellState(); + State createState() => _DateCardCellState(); } -class _BoardDateCellState extends State { - late BoardDateCellBloc _cellBloc; +class _DateCardCellState extends State { + late DateCardCellBloc _cellBloc; @override void initState() { final cellController = widget.cellControllerBuilder.build() as DateCellController; - _cellBloc = BoardDateCellBloc(cellController: cellController) - ..add(const BoardDateCellEvent.initial()); + _cellBloc = DateCardCellBloc(cellController: cellController) + ..add(const DateCardCellEvent.initial()); super.initState(); } @@ -37,7 +36,7 @@ class _BoardDateCellState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _cellBloc, - child: BlocBuilder( + child: BlocBuilder( buildWhen: (previous, current) => previous.dateStr != current.dateStr, builder: (context, state) { if (state.dateStr.isEmpty) { @@ -47,7 +46,7 @@ class _BoardDateCellState extends State { alignment: Alignment.centerLeft, child: Padding( padding: EdgeInsets.symmetric( - vertical: BoardSizes.cardCellVPadding, + vertical: CardSizes.cardCellVPadding, ), child: FlowyText.regular( state.dateStr, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/number_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/number_card_cell.dart new file mode 100644 index 0000000000..88de135e2c --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/number_card_cell.dart @@ -0,0 +1,68 @@ +import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../bloc/number_card_cell_bloc.dart'; +import '../define.dart'; +import 'card_cell.dart'; + +class NumberCardCell extends CardCell { + final CellControllerBuilder cellControllerBuilder; + + const NumberCardCell({ + required this.cellControllerBuilder, + Key? key, + }) : super(key: key); + + @override + State createState() => _NumberCardCellState(); +} + +class _NumberCardCellState extends State { + late NumberCardCellBloc _cellBloc; + + @override + void initState() { + final cellController = + widget.cellControllerBuilder.build() as NumberCellController; + + _cellBloc = NumberCardCellBloc(cellController: cellController) + ..add(const NumberCardCellEvent.initial()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: _cellBloc, + child: BlocBuilder( + buildWhen: (previous, current) => previous.content != current.content, + builder: (context, state) { + if (state.content.isEmpty) { + return const SizedBox(); + } else { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.symmetric( + vertical: CardSizes.cardCellVPadding, + ), + child: FlowyText.medium( + state.content, + fontSize: 14, + ), + ), + ); + } + }, + ), + ); + } + + @override + Future dispose() async { + _cellBloc.close(); + super.dispose(); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_select_option_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/select_option_card_cell.dart similarity index 62% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_select_option_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/select_option_card_cell.dart index d3ae3dd55d..6eba4acef9 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_select_option_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/select_option_card_cell.dart @@ -1,32 +1,35 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../grid/presentation/widgets/cell/select_option_cell/extension.dart'; -import '../../../grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart'; -import '../../application/card/board_select_option_cell_bloc.dart'; -import 'board_cell.dart'; +import '../bloc/select_option_card_cell_bloc.dart'; +import 'card_cell.dart'; -class BoardSelectOptionCell extends StatefulWidget with EditableCell { - final String groupId; +class SelectOptionCardCell extends CardCell with EditableCell { final CellControllerBuilder cellControllerBuilder; - @override - final EditableCellNotifier? editableNotifier; + final CellRenderHook, T>? renderHook; - const BoardSelectOptionCell({ - required this.groupId, + @override + final EditableCardNotifier? editableNotifier; + + SelectOptionCardCell({ required this.cellControllerBuilder, + required T? cardData, + this.renderHook, this.editableNotifier, Key? key, - }) : super(key: key); + }) : super(key: key, cardData: cardData); @override - State createState() => _BoardSelectOptionCellState(); + State createState() => _SelectOptionCardCellState(); } -class _BoardSelectOptionCellState extends State { - late BoardSelectOptionCellBloc _cellBloc; +class _SelectOptionCardCellState extends State { + late SelectOptionCardCellBloc _cellBloc; late PopoverController _popover; @override @@ -34,8 +37,8 @@ class _BoardSelectOptionCellState extends State { _popover = PopoverController(); final cellController = widget.cellControllerBuilder.build() as SelectOptionCellController; - _cellBloc = BoardSelectOptionCellBloc(cellController: cellController) - ..add(const BoardSelectOptionCellEvent.initial()); + _cellBloc = SelectOptionCardCellBloc(cellController: cellController) + ..add(const SelectOptionCardCellEvent.initial()); super.initState(); } @@ -43,12 +46,17 @@ class _BoardSelectOptionCellState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _cellBloc, - child: BlocBuilder( + child: BlocBuilder( buildWhen: (previous, current) { return previous.selectedOptions != current.selectedOptions; }, builder: (context, state) { - // Returns SizedBox if the content of the cell is empty - if (_isEmpty(state)) return const SizedBox(); + Widget? custom = widget.renderHook?.call( + state.selectedOptions, + widget.cardData, + ); + if (custom != null) { + return custom; + } final children = state.selectedOptions.map( (option) { @@ -73,14 +81,6 @@ class _BoardSelectOptionCellState extends State { ); } - bool _isEmpty(BoardSelectOptionCellState state) { - // The cell should hide if the option id is equal to the groupId. - final isInGroup = state.selectedOptions - .where((element) => element.id == widget.groupId) - .isNotEmpty; - return isInGroup || state.selectedOptions.isEmpty; - } - Widget _wrapPopover(Widget child) { final constraints = BoxConstraints.loose(Size( SelectOptionCellEditor.editorPanelWidth, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/text_card_cell.dart similarity index 76% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_text_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/text_card_cell.dart index 8bac91820c..8ffc834247 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/text_card_cell.dart @@ -1,33 +1,31 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/cell/cell_builder.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:textstyle_extensions/textstyle_extensions.dart'; -import '../../application/card/board_text_cell_bloc.dart'; -import 'board_cell.dart'; -import 'define.dart'; +import '../../row/cell_builder.dart'; +import '../bloc/text_card_cell_bloc.dart'; +import '../define.dart'; +import 'card_cell.dart'; -class BoardTextCell extends StatefulWidget with EditableCell { - final String groupId; +class TextCardCell extends CardCell with EditableCell { @override - final EditableCellNotifier? editableNotifier; + final EditableCardNotifier? editableNotifier; final CellControllerBuilder cellControllerBuilder; - const BoardTextCell({ - required this.groupId, + const TextCardCell({ required this.cellControllerBuilder, this.editableNotifier, Key? key, }) : super(key: key); @override - State createState() => _BoardTextCellState(); + State createState() => _TextCardCellState(); } -class _BoardTextCellState extends State { - late BoardTextCellBloc _cellBloc; +class _TextCardCellState extends State { + late TextCardCellBloc _cellBloc; late TextEditingController _controller; bool focusWhenInit = false; final focusNode = SingleListenerFocusNode(); @@ -36,8 +34,8 @@ class _BoardTextCellState extends State { void initState() { final cellController = widget.cellControllerBuilder.build() as TextCellController; - _cellBloc = BoardTextCellBloc(cellController: cellController) - ..add(const BoardTextCellEvent.initial()); + _cellBloc = TextCardCellBloc(cellController: cellController) + ..add(const TextCardCellEvent.initial()); _controller = TextEditingController(text: _cellBloc.state.content); focusWhenInit = widget.editableNotifier?.isCellEditing.value ?? false; if (focusWhenInit) { @@ -51,7 +49,7 @@ class _BoardTextCellState extends State { if (!focusNode.hasFocus) { focusWhenInit = false; widget.editableNotifier?.isCellEditing.value = false; - _cellBloc.add(const BoardTextCellEvent.enableEdit(false)); + _cellBloc.add(const TextCardCellEvent.enableEdit(false)); } }); _bindEditableNotifier(); @@ -68,12 +66,12 @@ class _BoardTextCellState extends State { focusNode.requestFocus(); }); } - _cellBloc.add(BoardTextCellEvent.enableEdit(isEditing)); + _cellBloc.add(TextCardCellEvent.enableEdit(isEditing)); }); } @override - void didUpdateWidget(covariant BoardTextCell oldWidget) { + void didUpdateWidget(covariant TextCardCell oldWidget) { _bindEditableNotifier(); super.didUpdateWidget(oldWidget); } @@ -82,13 +80,13 @@ class _BoardTextCellState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _cellBloc, - child: BlocListener( + child: BlocListener( listener: (context, state) { if (_controller.text != state.content) { _controller.text = state.content; } }, - child: BlocBuilder( + child: BlocBuilder( buildWhen: (previous, current) { if (previous.content != current.content && _controller.text == current.content && @@ -120,7 +118,7 @@ class _BoardTextCellState extends State { } Future focusChanged() async { - _cellBloc.add(BoardTextCellEvent.updateText(_controller.text)); + _cellBloc.add(TextCardCellEvent.updateText(_controller.text)); } @override @@ -131,10 +129,10 @@ class _BoardTextCellState extends State { super.dispose(); } - Widget _buildText(BoardTextCellState state) { + Widget _buildText(TextCardCellState state) { return Padding( padding: EdgeInsets.symmetric( - vertical: BoardSizes.cardCellVPadding, + vertical: CardSizes.cardCellVPadding, ), child: FlowyText.medium( state.content, @@ -156,7 +154,7 @@ class _BoardTextCellState extends State { decoration: InputDecoration( // Magic number 4 makes the textField take up the same space as FlowyText contentPadding: EdgeInsets.symmetric( - vertical: BoardSizes.cardCellVPadding + 4, + vertical: CardSizes.cardCellVPadding + 4, ), border: InputBorder.none, isDense: true, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/url_card_cell.dart similarity index 74% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_url_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/url_card_cell.dart index 62192d4f0f..af25ce2dbc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/board_url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/url_card_cell.dart @@ -4,32 +4,31 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:textstyle_extensions/textstyle_extensions.dart'; -import '../../application/card/board_url_cell_bloc.dart'; -import 'define.dart'; +import '../bloc/url_card_cell_bloc.dart'; +import '../define.dart'; +import 'card_cell.dart'; -class BoardUrlCell extends StatefulWidget { - final String groupId; +class URLCardCell extends CardCell { final CellControllerBuilder cellControllerBuilder; - const BoardUrlCell({ - required this.groupId, + const URLCardCell({ required this.cellControllerBuilder, Key? key, }) : super(key: key); @override - State createState() => _BoardUrlCellState(); + State createState() => _URLCardCellState(); } -class _BoardUrlCellState extends State { - late BoardURLCellBloc _cellBloc; +class _URLCardCellState extends State { + late URLCardCellBloc _cellBloc; @override void initState() { final cellController = widget.cellControllerBuilder.build() as URLCellController; - _cellBloc = BoardURLCellBloc(cellController: cellController); - _cellBloc.add(const BoardURLCellEvent.initial()); + _cellBloc = URLCardCellBloc(cellController: cellController); + _cellBloc.add(const URLCardCellEvent.initial()); super.initState(); } @@ -37,7 +36,7 @@ class _BoardUrlCellState extends State { Widget build(BuildContext context) { return BlocProvider.value( value: _cellBloc, - child: BlocBuilder( + child: BlocBuilder( buildWhen: (previous, current) => previous.content != current.content, builder: (context, state) { if (state.content.isEmpty) { @@ -47,7 +46,7 @@ class _BoardUrlCellState extends State { alignment: Alignment.centerLeft, child: Padding( padding: EdgeInsets.symmetric( - vertical: BoardSizes.cardCellVPadding, + vertical: CardSizes.cardCellVPadding, ), child: RichText( textAlign: TextAlign.left, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/container/accessory.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/container/accessory.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/container/accessory.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/container/accessory.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/container/card_container.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/container/card_container.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/container/card_container.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/container/card_container.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/define.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/define.dart similarity index 70% rename from frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/define.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/define.dart index 5fc55743db..f07f867e7e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/card/define.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/define.dart @@ -1,3 +1,3 @@ -class BoardSizes { +class CardSizes { static double get cardCellVPadding => 6; } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_accessory.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_accessory.dart similarity index 99% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_accessory.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_accessory.dart index 142dc81448..39a8bd41f6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_accessory.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_accessory.dart @@ -9,7 +9,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:textstyle_extensions/textstyle_extensions.dart'; -import 'cell_builder.dart'; +import '../cell_builder.dart'; class GridCellAccessoryBuildContext { final BuildContext anchorContext; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_decoration.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_decoration.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_decoration.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_decoration.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_shortcuts.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_shortcuts.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_shortcuts.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_shortcuts.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_builder.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart similarity index 93% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_builder.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart index 4d0d36a9c9..fb7d605d2d 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart @@ -3,16 +3,16 @@ import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart' import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart'; -import '../../../../application/cell/cell_service.dart'; -import 'cell_accessory.dart'; -import 'cell_shortcuts.dart'; -import 'checkbox_cell.dart'; -import 'checklist_cell/checklist_cell.dart'; -import 'date_cell/date_cell.dart'; -import 'number_cell.dart'; -import 'select_option_cell/select_option_cell.dart'; -import 'text_cell.dart'; -import 'url_cell/url_cell.dart'; +import '../../application/cell/cell_service.dart'; +import 'accessory/cell_accessory.dart'; +import 'accessory/cell_shortcuts.dart'; +import 'cells/checkbox_cell/checkbox_cell.dart'; +import 'cells/checklist_cell/checklist_cell.dart'; +import 'cells/date_cell/date_cell.dart'; +import 'cells/number_cell/number_cell.dart'; +import 'cells/select_option_cell/select_option_cell.dart'; +import 'cells/text_cell/text_cell.dart'; +import 'cells/url_cell/url_cell.dart'; class GridCellBuilder { final CellCache cellCache; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_container.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/cell_container.dart similarity index 95% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_container.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/cell_container.dart index d3fdb1aec6..d17301d0c6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/cell_container.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/cell_container.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; -import '../../layout/sizes.dart'; -import '../row/grid_row.dart'; -import 'cell_accessory.dart'; -import 'cell_builder.dart'; -import 'cell_shortcuts.dart'; +import '../../../grid/presentation/layout/sizes.dart'; +import '../../../grid/presentation/widgets/row/row.dart'; +import '../accessory/cell_accessory.dart'; +import '../accessory/cell_shortcuts.dart'; +import '../cell_builder.dart'; class CellContainer extends StatelessWidget { final GridCellWidget child; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checkbox_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart similarity index 87% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checkbox_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart index 4dcf818e59..1a3382e2a6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checkbox_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart @@ -1,12 +1,12 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../application/cell/checkbox_cell_bloc.dart'; -import '../../layout/sizes.dart'; -import 'cell_builder.dart'; +import 'checkbox_cell_bloc.dart'; +import '../../../../grid/presentation/layout/sizes.dart'; +import '../../cell_builder.dart'; class GridCheckboxCell extends GridCellWidget { final CellControllerBuilder cellControllerBuilder; @@ -26,7 +26,8 @@ class _CheckboxCellState extends GridCellState { void initState() { final cellController = widget.cellControllerBuilder.build() as CheckboxCellController; - _cellBloc = getIt(param1: cellController) + _cellBloc = CheckboxCellBloc( + service: CellBackendService(), cellController: cellController) ..add(const CheckboxCellEvent.initial()); super.initState(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/checkbox_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell_bloc.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/checkbox_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell_bloc.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checklist_cell/checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart similarity index 87% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checklist_cell/checklist_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart index f8e825398d..7d096d9883 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checklist_cell/checklist_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart @@ -1,12 +1,12 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/checklist_cell_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../cell_builder.dart'; +import '../../cell_builder.dart'; +import 'checklist_cell_bloc.dart'; import 'checklist_cell_editor.dart'; import 'checklist_progress_bar.dart'; @@ -20,7 +20,7 @@ class GridChecklistCell extends GridCellWidget { } class GridChecklistCellState extends GridCellState { - late ChecklistCellBloc _cellBloc; + late ChecklistCardCellBloc _cellBloc; late final PopoverController _popover; @override @@ -28,7 +28,7 @@ class GridChecklistCellState extends GridCellState { _popover = PopoverController(); final cellController = widget.cellControllerBuilder.build() as ChecklistCellController; - _cellBloc = ChecklistCellBloc(cellController: cellController); + _cellBloc = ChecklistCardCellBloc(cellController: cellController); _cellBloc.add(const ChecklistCellEvent.initial()); super.initState(); } @@ -54,7 +54,7 @@ class GridChecklistCellState extends GridCellState { onClose: () => widget.onCellEditing.value = false, child: Padding( padding: GridSize.cellContentInsets, - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) => ChecklistProgressBar(percent: state.percent), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/checklist_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_bloc.dart similarity index 92% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/checklist_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_bloc.dart index 3b6a61e223..ebf16a1e8c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/checklist_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_bloc.dart @@ -1,18 +1,19 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; import 'checklist_cell_editor_bloc.dart'; -import 'select_option_service.dart'; part 'checklist_cell_bloc.freezed.dart'; -class ChecklistCellBloc extends Bloc { +class ChecklistCardCellBloc + extends Bloc { final ChecklistCellController cellController; final SelectOptionBackendService _selectOptionSvc; void Function()? _onCellChangedFn; - ChecklistCellBloc({ + ChecklistCardCellBloc({ required this.cellController, }) : _selectOptionSvc = SelectOptionBackendService(cellId: cellController.cellId), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checklist_cell/checklist_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart similarity index 92% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checklist_cell/checklist_cell_editor.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart index c98d9f3c35..9d30b122a3 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checklist_cell/checklist_cell_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart @@ -2,16 +2,12 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../../application/cell/checklist_cell_editor_bloc.dart'; -import '../../../layout/sizes.dart'; -import '../../header/type_option/select_option_editor.dart'; +import '../../../../grid/presentation/layout/sizes.dart'; +import '../../../../grid/presentation/widgets/header/type_option/select_option_editor.dart'; +import 'checklist_cell_editor_bloc.dart'; import 'checklist_progress_bar.dart'; class GridChecklistCellEditor extends StatefulWidget { diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/checklist_cell_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart similarity index 98% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/checklist_cell_editor_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart index d0b70ffe8d..fa81a062db 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/checklist_cell_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart @@ -1,12 +1,12 @@ import 'dart:async'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_service.dart'; import 'package:dartz/dartz.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'select_option_service.dart'; part 'checklist_cell_editor_bloc.freezed.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart similarity index 96% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart index 5a3517e6a8..b8599f60c4 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/checklist_cell_editor_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/date_cal_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart similarity index 63% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/date_cal_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart index f0a83077d3..058782ebcb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/date_cal_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart @@ -15,19 +15,20 @@ import 'package:table_calendar/table_calendar.dart'; import 'dart:async'; import 'package:dartz/dartz.dart'; import 'package:protobuf/protobuf.dart'; -import 'package:fixnum/fixnum.dart' as $fixnum; + part 'date_cal_bloc.freezed.dart'; -class DateCalBloc extends Bloc { +class DateCellCalendarBloc + extends Bloc { final DateCellController cellController; void Function()? _onCellChangedFn; - DateCalBloc({ + DateCellCalendarBloc({ required DateTypeOptionPB dateTypeOptionPB, required DateCellDataPB? cellData, required this.cellController, - }) : super(DateCalState.initial(dateTypeOptionPB, cellData)) { - on( + }) : super(DateCellCalendarState.initial(dateTypeOptionPB, cellData)) { + on( (event, emit) async { await event.when( initial: () async => _startListening(), @@ -41,13 +42,13 @@ class DateCalBloc extends Bloc { emit(state.copyWith(focusedDay: focusedDay)); }, didReceiveCellUpdate: (DateCellDataPB? cellData) { - final calData = calDataFromCellData(cellData); - final time = calData.foldRight( + final dateCellData = calDataFromCellData(cellData); + final time = dateCellData.foldRight( "", (dateData, previous) => dateData.time ?? ''); - emit(state.copyWith(calData: calData, time: time)); + emit(state.copyWith(dateCellData: dateCellData, time: time)); }, setIncludeTime: (includeTime) async { - await _updateTypeOption(emit, includeTime: includeTime); + await _updateDateData(emit, includeTime: includeTime); }, setDateFormat: (dateFormat) async { await _updateTypeOption(emit, dateFormat: dateFormat); @@ -56,24 +57,28 @@ class DateCalBloc extends Bloc { await _updateTypeOption(emit, timeFormat: timeFormat); }, setTime: (time) async { - if (state.calData.isSome()) { + if (state.dateCellData.isSome()) { await _updateDateData(emit, time: time); } }, didUpdateCalData: - (Option data, Option timeFormatError) { + (Option data, Option timeFormatError) { emit(state.copyWith( - calData: data, timeFormatError: timeFormatError)); + dateCellData: data, timeFormatError: timeFormatError)); }, ); }, ); } - Future _updateDateData(Emitter emit, - {DateTime? date, String? time}) { - final CalendarData newDateData = state.calData.fold( - () => CalendarData(date: date ?? DateTime.now(), time: time), + Future _updateDateData(Emitter emit, + {DateTime? date, String? time, bool? includeTime}) { + final DateCellData newDateData = state.dateCellData.fold( + () => DateCellData( + date: date ?? DateTime.now(), + time: time, + includeTime: includeTime ?? false, + ), (dateData) { var newDateData = dateData; if (date != null && !isSameDay(newDateData.date, date)) { @@ -83,6 +88,11 @@ class DateCalBloc extends Bloc { if (newDateData.time != time) { newDateData = newDateData.copyWith(time: time); } + + if (includeTime != null && newDateData.includeTime != includeTime) { + newDateData = newDateData.copyWith(includeTime: includeTime); + } + return newDateData; }, ); @@ -91,15 +101,16 @@ class DateCalBloc extends Bloc { } Future _saveDateData( - Emitter emit, CalendarData newCalData) async { - if (state.calData == Some(newCalData)) { + Emitter emit, DateCellData newCalData) async { + if (state.dateCellData == Some(newCalData)) { return; } updateCalData( - Option calData, Option timeFormatError) { + Option dateCellData, Option timeFormatError) { if (!isClosed) { - add(DateCalEvent.didUpdateCalData(calData, timeFormatError)); + add(DateCellCalendarEvent.didUpdateCalData( + dateCellData, timeFormatError)); } } @@ -109,7 +120,7 @@ class DateCalBloc extends Bloc { (err) { switch (ErrorCode.valueOf(err.code)!) { case ErrorCode.InvalidDateTimeFormat: - updateCalData(state.calData, Some(timeFormatPrompt(err))); + updateCalData(state.dateCellData, Some(timeFormatPrompt(err))); break; default: Log.error(err); @@ -148,17 +159,16 @@ class DateCalBloc extends Bloc { _onCellChangedFn = cellController.startListening( onCellChanged: ((cell) { if (!isClosed) { - add(DateCalEvent.didReceiveCellUpdate(cell)); + add(DateCellCalendarEvent.didReceiveCellUpdate(cell)); } }), ); } Future? _updateTypeOption( - Emitter emit, { + Emitter emit, { DateFormat? dateFormat, TimeFormat? timeFormat, - bool? includeTime, }) async { state.dateTypeOptionPB.freeze(); final newDateTypeOption = state.dateTypeOptionPB.rebuild((typeOption) { @@ -169,10 +179,6 @@ class DateCalBloc extends Bloc { if (timeFormat != null) { typeOption.timeFormat = timeFormat; } - - if (includeTime != null) { - typeOption.includeTime = includeTime; - } }); final result = await FieldBackendService.updateFieldTypeOption( @@ -191,48 +197,51 @@ class DateCalBloc extends Bloc { } @freezed -class DateCalEvent with _$DateCalEvent { - const factory DateCalEvent.initial() = _Initial; - const factory DateCalEvent.selectDay(DateTime day) = _SelectDay; - const factory DateCalEvent.setCalFormat(CalendarFormat format) = +class DateCellCalendarEvent with _$DateCellCalendarEvent { + const factory DateCellCalendarEvent.initial() = _Initial; + const factory DateCellCalendarEvent.selectDay(DateTime day) = _SelectDay; + const factory DateCellCalendarEvent.setCalFormat(CalendarFormat format) = _CalendarFormat; - const factory DateCalEvent.setFocusedDay(DateTime day) = _FocusedDay; - const factory DateCalEvent.setTimeFormat(TimeFormat timeFormat) = _TimeFormat; - const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat; - const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime; - const factory DateCalEvent.setTime(String time) = _Time; - const factory DateCalEvent.didReceiveCellUpdate(DateCellDataPB? data) = - _DidReceiveCellUpdate; - const factory DateCalEvent.didUpdateCalData( - Option data, Option timeFormatError) = + const factory DateCellCalendarEvent.setFocusedDay(DateTime day) = _FocusedDay; + const factory DateCellCalendarEvent.setTimeFormat(TimeFormat timeFormat) = + _TimeFormat; + const factory DateCellCalendarEvent.setDateFormat(DateFormat dateFormat) = + _DateFormat; + const factory DateCellCalendarEvent.setIncludeTime(bool includeTime) = + _IncludeTime; + const factory DateCellCalendarEvent.setTime(String time) = _Time; + const factory DateCellCalendarEvent.didReceiveCellUpdate( + DateCellDataPB? data) = _DidReceiveCellUpdate; + const factory DateCellCalendarEvent.didUpdateCalData( + Option data, Option timeFormatError) = _DidUpdateCalData; } @freezed -class DateCalState with _$DateCalState { - const factory DateCalState({ +class DateCellCalendarState with _$DateCellCalendarState { + const factory DateCellCalendarState({ required DateTypeOptionPB dateTypeOptionPB, required CalendarFormat format, required DateTime focusedDay, required Option timeFormatError, - required Option calData, + required Option dateCellData, required String? time, required String timeHintText, - }) = _DateCalState; + }) = _DateCellCalendarState; - factory DateCalState.initial( + factory DateCellCalendarState.initial( DateTypeOptionPB dateTypeOptionPB, DateCellDataPB? cellData, ) { - Option calData = calDataFromCellData(cellData); + Option dateCellData = calDataFromCellData(cellData); final time = - calData.foldRight("", (dateData, previous) => dateData.time ?? ''); - return DateCalState( + dateCellData.foldRight("", (dateData, previous) => dateData.time ?? ''); + return DateCellCalendarState( dateTypeOptionPB: dateTypeOptionPB, format: CalendarFormat.month, focusedDay: DateTime.now(), time: time, - calData: calData, + dateCellData: dateCellData, timeFormatError: none(), timeHintText: _timeHintText(dateTypeOptionPB), ); @@ -245,30 +254,33 @@ String _timeHintText(DateTypeOptionPB typeOption) { return LocaleKeys.document_date_timeHintTextInTwelveHour.tr(); case TimeFormat.TwentyFourHour: return LocaleKeys.document_date_timeHintTextInTwentyFourHour.tr(); + default: + return ""; } - return ""; } -Option calDataFromCellData(DateCellDataPB? cellData) { +Option calDataFromCellData(DateCellDataPB? cellData) { String? time = timeFromCellData(cellData); - Option calData = none(); + Option dateData = none(); if (cellData != null) { final timestamp = cellData.timestamp * 1000; - final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt()); - calData = Some(CalendarData(date: date, time: time)); + final date = DateTime.fromMillisecondsSinceEpoch( + timestamp.toInt(), + isUtc: true, + ); + dateData = Some(DateCellData( + date: date, + time: time, + includeTime: cellData.includeTime, + )); } - return calData; -} - -$fixnum.Int64 timestampFromDateTime(DateTime dateTime) { - final timestamp = (dateTime.millisecondsSinceEpoch ~/ 1000); - return $fixnum.Int64(timestamp); + return dateData; } String? timeFromCellData(DateCellDataPB? cellData) { - String? time; - if (cellData?.hasTime() ?? false) { - time = cellData?.time; + if (cellData == null || !cellData.hasTime()) { + return null; } - return time; + + return cellData.time; } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/date_cell/date_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart similarity index 91% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/date_cell/date_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart index b90f01b2f4..b88110e6e0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/date_cell/date_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart @@ -1,13 +1,12 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/date_cell_bloc.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:appflowy/startup/startup.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; -import '../../../layout/sizes.dart'; -import '../cell_builder.dart'; +import '../../../../grid/presentation/layout/sizes.dart'; +import '../../cell_builder.dart'; +import 'date_cell_bloc.dart'; import 'date_editor.dart'; class DateCellStyle extends GridCellStyle { @@ -50,7 +49,7 @@ class _DateCellState extends GridCellState { _popover = PopoverController(); final cellController = widget.cellControllerBuilder.build() as DateCellController; - _cellBloc = getIt(param1: cellController) + _cellBloc = DateCellBloc(cellController: cellController) ..add(const DateCellEvent.initial()); super.initState(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/date_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell_bloc.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/date_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell_bloc.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/date_cell/date_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart similarity index 87% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/date_cell/date_editor.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart index 49db79edad..5a49415d4c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/date_cell/date_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart @@ -1,7 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/date_cal_bloc.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; @@ -11,9 +10,7 @@ import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart'; import 'package:appflowy_backend/protobuf/flowy-database/date_type_option.pb.dart'; @@ -21,9 +18,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:table_calendar/table_calendar.dart'; import 'package:textstyle_extensions/textstyle_extensions.dart'; -import '../../../layout/sizes.dart'; -import '../../common/type_option_separator.dart'; -import '../../header/type_option/date.dart'; +import '../../../../grid/presentation/layout/sizes.dart'; +import '../../../../grid/presentation/widgets/common/type_option_separator.dart'; +import '../../../../grid/presentation/widgets/header/type_option/date.dart'; +import 'date_cal_bloc.dart'; final kToday = DateTime.now(); final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day); @@ -92,17 +90,17 @@ class _CellCalendarWidget extends StatefulWidget { class _CellCalendarWidgetState extends State<_CellCalendarWidget> { late PopoverMutex popoverMutex; - late DateCalBloc bloc; + late DateCellCalendarBloc bloc; @override void initState() { popoverMutex = PopoverMutex(); - bloc = DateCalBloc( + bloc = DateCellCalendarBloc( dateTypeOptionPB: widget.dateTypeOptionPB, cellData: widget.cellContext.getCellData(), cellController: widget.cellContext, - )..add(const DateCalEvent.initial()); + )..add(const DateCellCalendarEvent.initial()); super.initState(); } @@ -110,18 +108,20 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { Widget build(BuildContext context) { return BlocProvider.value( value: bloc, - child: BlocBuilder( + child: BlocBuilder( buildWhen: (p, c) => p != c, builder: (context, state) { + bool includeTime = state.dateCellData + .fold(() => false, (dateData) => dateData.includeTime); List children = [ Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: _buildCalendar(context), ), - if (state.dateTypeOptionPB.includeTime) ...[ + if (includeTime) ...[ const VSpace(12.0), _TimeTextField( - bloc: context.read(), + bloc: context.read(), popoverMutex: popoverMutex, ), ], @@ -151,7 +151,7 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { } Widget _buildCalendar(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { final textStyle = Theme.of(context).textTheme.bodyMedium!; final boxDecoration = BoxDecoration( @@ -208,23 +208,25 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { textStyle.textColor(Theme.of(context).disabledColor), ), selectedDayPredicate: (day) { - return state.calData.fold( + return state.dateCellData.fold( () => false, (dateData) => isSameDay(dateData.date, day), ); }, onDaySelected: (selectedDay, focusedDay) { context - .read() - .add(DateCalEvent.selectDay(selectedDay)); + .read() + .add(DateCellCalendarEvent.selectDay(selectedDay)); }, onFormatChanged: (format) { - context.read().add(DateCalEvent.setCalFormat(format)); + context + .read() + .add(DateCellCalendarEvent.setCalFormat(format)); }, onPageChanged: (focusedDay) { context - .read() - .add(DateCalEvent.setFocusedDay(focusedDay)); + .read() + .add(DateCellCalendarEvent.setFocusedDay(focusedDay)); }, ); }, @@ -237,8 +239,11 @@ class _IncludeTimeButton extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocSelector( - selector: (state) => state.dateTypeOptionPB.includeTime, + return BlocSelector( + selector: (state) => state.dateCellData.fold( + () => false, + (dateData) => dateData.includeTime, + ), builder: (context, includeTime) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), @@ -258,8 +263,8 @@ class _IncludeTimeButton extends StatelessWidget { Toggle( value: includeTime, onChanged: (value) => context - .read() - .add(DateCalEvent.setIncludeTime(!value)), + .read() + .add(DateCellCalendarEvent.setIncludeTime(!value)), style: ToggleStyle.big, padding: EdgeInsets.zero, ), @@ -274,7 +279,7 @@ class _IncludeTimeButton extends StatelessWidget { } class _TimeTextField extends StatefulWidget { - final DateCalBloc bloc; + final DateCellCalendarBloc bloc; final PopoverMutex popoverMutex; const _TimeTextField({ @@ -298,7 +303,7 @@ class _TimeTextFieldState extends State<_TimeTextField> { _focusNode.addListener(() { if (mounted) { - widget.bloc.add(DateCalEvent.setTime(_controller.text)); + widget.bloc.add(DateCellCalendarEvent.setTime(_controller.text)); } }); @@ -340,7 +345,7 @@ class _TimeTextFieldState extends State<_TimeTextField> { errorText: widget.bloc.state.timeFormatError .fold(() => "", (error) => error), onEditingComplete: (value) => - widget.bloc.add(DateCalEvent.setTime(value)), + widget.bloc.add(DateCellCalendarEvent.setTime(value)), ), ), ); @@ -364,7 +369,8 @@ class _DateTypeOptionButton extends StatelessWidget { Widget build(BuildContext context) { final title = "${LocaleKeys.grid_field_dateFormat.tr()} & ${LocaleKeys.grid_field_timeFormat.tr()}"; - return BlocSelector( + return BlocSelector( selector: (state) => state.dateTypeOptionPB, builder: (context, dateTypeOptionPB) { return AppFlowyPopover( @@ -391,7 +397,7 @@ class _DateTypeOptionButton extends StatelessWidget { return _CalDateTimeSetting( dateTypeOptionPB: dateTypeOptionPB, onEvent: (event) { - context.read().add(event); + context.read().add(event); popoverMutex.close(); }, ); @@ -404,7 +410,7 @@ class _DateTypeOptionButton extends StatelessWidget { class _CalDateTimeSetting extends StatefulWidget { final DateTypeOptionPB dateTypeOptionPB; - final Function(DateCalEvent) onEvent; + final Function(DateCellCalendarEvent) onEvent; const _CalDateTimeSetting({ required this.dateTypeOptionPB, required this.onEvent, @@ -430,7 +436,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> { return DateFormatList( selectedFormat: widget.dateTypeOptionPB.dateFormat, onSelected: (format) { - widget.onEvent(DateCalEvent.setDateFormat(format)); + widget.onEvent(DateCellCalendarEvent.setDateFormat(format)); timeSettingPopoverMutex.close(); }, ); @@ -448,7 +454,7 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> { return TimeFormatList( selectedFormat: widget.dateTypeOptionPB.timeFormat, onSelected: (format) { - widget.onEvent(DateCalEvent.setTimeFormat(format)); + widget.onEvent(DateCellCalendarEvent.setTimeFormat(format)); timeSettingPopoverMutex.close(); }); }, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/number_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart similarity index 89% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/number_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart index 88287aa361..6b85dc3f19 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/number_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart @@ -1,12 +1,11 @@ import 'dart:async'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/startup/startup.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../application/cell/number_cell_bloc.dart'; -import '../../layout/sizes.dart'; -import 'cell_builder.dart'; +import 'number_cell_bloc.dart'; +import '../../../../grid/presentation/layout/sizes.dart'; +import '../../cell_builder.dart'; class GridNumberCell extends GridCellWidget { final CellControllerBuilder cellControllerBuilder; @@ -26,8 +25,9 @@ class _NumberCellState extends GridFocusNodeCellState { @override void initState() { - final cellController = widget.cellControllerBuilder.build(); - _cellBloc = getIt(param1: cellController) + final cellController = + widget.cellControllerBuilder.build() as NumberCellController; + _cellBloc = NumberCellBloc(cellController: cellController) ..add(const NumberCellEvent.initial()); _controller = TextEditingController(text: _cellBloc.state.cellContent); super.initState(); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/number_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell_bloc.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/number_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell_bloc.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/extension.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/extension.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart similarity index 95% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart index d01144edb0..70f864ad80 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart @@ -1,15 +1,14 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/startup/startup.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../../application/cell/select_option_cell_bloc.dart'; -import '../../../layout/sizes.dart'; -import '../cell_builder.dart'; +import '../../../../grid/presentation/layout/sizes.dart'; +import '../../cell_builder.dart'; import 'extension.dart'; +import 'select_option_cell_bloc.dart'; import 'select_option_editor.dart'; class SelectOptionCellStyle extends GridCellStyle { @@ -48,7 +47,7 @@ class _SingleSelectCellState extends GridCellState { void initState() { final cellController = widget.cellControllerBuilder.build() as SelectOptionCellController; - _cellBloc = getIt(param1: cellController) + _cellBloc = SelectOptionCellBloc(cellController: cellController) ..add(const SelectOptionCellEvent.initial()); _popover = PopoverController(); super.initState(); @@ -111,7 +110,7 @@ class _MultiSelectCellState extends GridCellState { void initState() { final cellController = widget.cellControllerBuilder.build() as SelectOptionCellController; - _cellBloc = getIt(param1: cellController) + _cellBloc = SelectOptionCellBloc(cellController: cellController) ..add(const SelectOptionCellEvent.initial()); _popover = PopoverController(); super.initState(); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/select_option_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell_bloc.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/select_option_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell_bloc.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart similarity index 95% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart index f269794a51..f65566320d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart @@ -1,14 +1,10 @@ import 'dart:collection'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/select_option_editor_bloc.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -16,10 +12,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:textfield_tags/textfield_tags.dart'; -import '../../../layout/sizes.dart'; -import '../../common/type_option_separator.dart'; -import '../../header/type_option/select_option_editor.dart'; +import '../../../../grid/presentation/layout/sizes.dart'; +import '../../../../grid/presentation/widgets/common/type_option_separator.dart'; +import '../../../../grid/presentation/widgets/header/type_option/select_option_editor.dart'; import 'extension.dart'; +import 'select_option_editor_bloc.dart'; import 'text_field.dart'; const double _editorPanelWidth = 300; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/select_option_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor_bloc.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/select_option_editor_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor_bloc.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/select_option_service.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_service.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/select_option_service.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_service.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/text_field.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/text_field.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart similarity index 88% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/text_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart index f29741c81f..8ca3b664f6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart @@ -1,11 +1,10 @@ import 'dart:async'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/text_cell_bloc.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/text_cell/text_cell_bloc.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:appflowy/startup/startup.dart'; -import '../../layout/sizes.dart'; -import 'cell_builder.dart'; +import '../../../../grid/presentation/layout/sizes.dart'; +import '../../cell_builder.dart'; class GridTextCellStyle extends GridCellStyle { String? placeholder; @@ -40,8 +39,9 @@ class _GridTextCellState extends GridFocusNodeCellState { @override void initState() { - final cellController = widget.cellControllerBuilder.build(); - _cellBloc = getIt(param1: cellController); + final cellController = + widget.cellControllerBuilder.build() as TextCellController; + _cellBloc = TextCellBloc(cellController: cellController); _cellBloc.add(const TextCellEvent.initial()); _controller = TextEditingController(text: _cellBloc.state.content); super.initState(); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/text_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell_bloc.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/text_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell_bloc.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/url_cell/cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/cell_editor.dart similarity index 97% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/url_cell/cell_editor.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/cell_editor.dart index ec97c7edf5..637b731847 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/url_cell/cell_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/cell_editor.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../../application/cell/url_cell_editor_bloc.dart'; +import 'url_cell_editor_bloc.dart'; class URLCellEditor extends StatefulWidget { final VoidCallback onExit; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/url_cell/url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell.dart similarity index 97% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/url_cell/url_cell.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell.dart index b67fb94e01..b3c0553846 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/cell/url_cell/url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell.dart @@ -10,11 +10,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../../../../application/cell/url_cell_bloc.dart'; -import '../../../layout/sizes.dart'; -import '../cell_accessory.dart'; -import '../cell_builder.dart'; +import '../../../../grid/presentation/layout/sizes.dart'; +import '../../accessory/cell_accessory.dart'; +import '../../cell_builder.dart'; import 'cell_editor.dart'; +import 'url_cell_bloc.dart'; class GridURLCellStyle extends GridCellStyle { String? placeholder; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/url_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell_bloc.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/url_cell_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell_bloc.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/url_cell_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell_editor_bloc.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/application/cell/url_cell_editor_bloc.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell_editor_bloc.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row_detail.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart similarity index 83% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row_detail.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart index afcd61f87a..b92f993382 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row_detail.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart @@ -6,10 +6,7 @@ import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; + import 'package:easy_localization/easy_localization.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; @@ -17,14 +14,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; -import '../../layout/sizes.dart'; -import '../cell/cell_accessory.dart'; -import '../cell/prelude.dart'; -import '../header/field_cell.dart'; -import '../header/field_editor.dart'; +import '../../grid/presentation/layout/sizes.dart'; +import 'accessory/cell_accessory.dart'; +import 'cell_builder.dart'; +import 'cells/date_cell/date_cell.dart'; +import 'cells/select_option_cell/select_option_cell.dart'; +import 'cells/text_cell/text_cell.dart'; +import 'cells/url_cell/url_cell.dart'; +import '../../grid/presentation/widgets/header/field_cell.dart'; +import '../../grid/presentation/widgets/header/field_editor.dart'; class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate { - final RowDataController dataController; + final RowController dataController; final GridCellBuilder cellBuilder; const RowDetailPage({ @@ -42,6 +43,11 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate { } class _RowDetailPageState extends State { + final padding = const EdgeInsets.symmetric( + horizontal: 40, + vertical: 20, + ); + @override Widget build(BuildContext context) { return FlowyDialog( @@ -54,19 +60,14 @@ class _RowDetailPageState extends State { return bloc; }, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20), + padding: padding, child: Column( children: [ - SizedBox( - height: 30, - child: Row( - children: const [Spacer(), _CloseButton()], - ), - ), + const _Header(), Expanded( - child: _PropertyList( + child: _PropertyColumn( cellBuilder: widget.cellBuilder, - viewId: widget.dataController.rowInfo.viewId, + viewId: widget.dataController.viewId, ), ), ], @@ -77,6 +78,20 @@ class _RowDetailPageState extends State { } } +class _Header extends StatelessWidget { + const _Header({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 30, + child: Row( + children: const [Spacer(), _CloseButton()], + ), + ); + } +} + class _CloseButton extends StatelessWidget { const _CloseButton({Key? key}) : super(key: key); @@ -84,9 +99,7 @@ class _CloseButton extends StatelessWidget { Widget build(BuildContext context) { return FlowyIconButton( width: 24, - onPressed: () { - FlowyOverlay.pop(context); - }, + onPressed: () => FlowyOverlay.pop(context), iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2), icon: svgWidget( "home/close", @@ -96,11 +109,11 @@ class _CloseButton extends StatelessWidget { } } -class _PropertyList extends StatelessWidget { +class _PropertyColumn extends StatelessWidget { final String viewId; final GridCellBuilder cellBuilder; final ScrollController _scrollController; - _PropertyList({ + _PropertyColumn({ required this.viewId, required this.cellBuilder, Key? key, @@ -114,11 +127,11 @@ class _PropertyList extends StatelessWidget { builder: (context, state) { return Column( children: [ - Expanded(child: _wrapScrollbar(buildRowCells(state))), + Expanded(child: _wrapScrollbar(buildPropertyCells(state))), const VSpace(10), - _CreateFieldButton( + _CreatePropertyButton( viewId: viewId, - onClosed: _handleDidCreateField, + onClosed: _scrollToNewProperty, ), ], ); @@ -126,12 +139,12 @@ class _PropertyList extends StatelessWidget { ); } - Widget buildRowCells(RowDetailState state) { + Widget buildPropertyCells(RowDetailState state) { return ListView.separated( controller: _scrollController, itemCount: state.gridCells.length, itemBuilder: (BuildContext context, int index) { - return _RowDetailCell( + return _PropertyCell( cellId: state.gridCells[index], cellBuilder: cellBuilder, ); @@ -152,7 +165,7 @@ class _PropertyList extends StatelessWidget { ); } - void _handleDidCreateField() { + void _scrollToNewProperty() { WidgetsBinding.instance.addPostFrameCallback((_) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, @@ -163,21 +176,21 @@ class _PropertyList extends StatelessWidget { } } -class _CreateFieldButton extends StatefulWidget { +class _CreatePropertyButton extends StatefulWidget { final String viewId; final VoidCallback onClosed; - const _CreateFieldButton({ + const _CreatePropertyButton({ required this.viewId, required this.onClosed, Key? key, }) : super(key: key); @override - State<_CreateFieldButton> createState() => _CreateFieldButtonState(); + State<_CreatePropertyButton> createState() => _CreatePropertyButtonState(); } -class _CreateFieldButtonState extends State<_CreateFieldButton> { +class _CreatePropertyButtonState extends State<_CreatePropertyButton> { late PopoverController popoverController; @override @@ -234,20 +247,20 @@ class _CreateFieldButtonState extends State<_CreateFieldButton> { } } -class _RowDetailCell extends StatefulWidget { +class _PropertyCell extends StatefulWidget { final CellIdentifier cellId; final GridCellBuilder cellBuilder; - const _RowDetailCell({ + const _PropertyCell({ required this.cellId, required this.cellBuilder, Key? key, }) : super(key: key); @override - State createState() => _RowDetailCellState(); + State createState() => _PropertyCellState(); } -class _RowDetailCellState extends State<_RowDetailCell> { +class _PropertyCellState extends State<_PropertyCell> { final PopoverController popover = PopoverController(); @override diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/doc_service.dart b/frontend/appflowy_flutter/lib/plugins/document/application/doc_service.dart index 0523df505b..6597dfc3f1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/doc_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/doc_service.dart @@ -36,7 +36,7 @@ class DocumentService { } Future> closeDocument({required String docId}) { - final request = ViewIdPB(value: docId); - return FolderEventCloseView(request).send(); + final payload = ViewIdPB(value: docId); + return FolderEventCloseView(payload).send(); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_menu_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_menu_item.dart index a368951f30..cccb671e7c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_menu_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/board/board_menu_item.dart @@ -7,7 +7,7 @@ import 'package:flowy_infra/image.dart'; import 'package:flutter/material.dart'; SelectionMenuItem boardMenuItem = SelectionMenuItem( - name: () => LocaleKeys.document_plugins_referencedBoard.tr(), + name: LocaleKeys.document_plugins_referencedBoard.tr(), icon: (editorState, onSelected) { return svgWidget( 'editor/board', diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/cover_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/cover_node_widget.dart index 11788cd1d9..452b3356ab 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/cover_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/cover_node_widget.dart @@ -8,8 +8,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flutter/material.dart'; @@ -196,7 +194,7 @@ class _CoverImageState extends State<_CoverImage> { Widget build(BuildContext context) { return Stack( children: [ - _buildCoverImage(context), + _buildCoverImage(context, widget.editorState), _buildCoverOverlayButtons(context), ], ); @@ -253,7 +251,7 @@ class _CoverImageState extends State<_CoverImage> { ); } - Widget _buildCoverImage(BuildContext context) { + Widget _buildCoverImage(BuildContext context, EditorState editorState) { final screenSize = MediaQuery.of(context).size; const height = 200.0; final Widget coverImage; @@ -283,12 +281,17 @@ class _CoverImageState extends State<_CoverImage> { coverImage = const SizedBox(); // just an empty sizebox break; } - return UnconstrainedBox( - child: Container( - padding: const EdgeInsets.only(bottom: 10), - height: height, - width: screenSize.width, - child: coverImage, +//OverflowBox needs to be wraped by a widget with constraints(or from its parent) first,otherwise it will occur an erorr + return SizedBox( + height: height, + child: OverflowBox( + maxWidth: screenSize.width, + child: Container( + padding: const EdgeInsets.only(bottom: 10), + height: double.infinity, + width: double.infinity, + child: coverImage, + ), ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/grid/grid_menu_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/grid/grid_menu_item.dart index e69b406c7c..737c17f4f5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/grid/grid_menu_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/grid/grid_menu_item.dart @@ -7,7 +7,7 @@ import 'package:flowy_infra/image.dart'; import 'package:flutter/material.dart'; SelectionMenuItem gridMenuItem = SelectionMenuItem( - name: () => LocaleKeys.document_plugins_referencedGrid.tr(), + name: LocaleKeys.document_plugins_referencedGrid.tr(), icon: (editorState, onSelected) { return svgWidget( 'editor/grid', diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart index c3d4cbeb35..7053e6f2ac 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart @@ -37,7 +37,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) { }; SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem( - name: () => 'Horizontal rule', + name: 'Horizontal rule', icon: (editorState, onSelected) => Icon( Icons.horizontal_rule, color: onSelected diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/openai_client.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/openai_client.dart index 37a5279662..24c664abe4 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/openai_client.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/openai_client.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:appflowy/plugins/document/presentation/plugins/openai/service/text_edit.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'text_completion.dart'; import 'package:dartz/dartz.dart'; @@ -37,10 +38,21 @@ abstract class OpenAIRepository { Future> getCompletions({ required String prompt, String? suffix, - int maxTokens = 500, + int maxTokens = 2048, double temperature = .3, }); + Future getStreamedCompletions({ + required String prompt, + required Future Function() onStart, + required Future Function(TextCompletionResponse response) onProcess, + required Future Function() onEnd, + required void Function(OpenAIError error) onError, + String? suffix, + int maxTokens = 2048, + double temperature = 0.3, + }); + /// Get edits from GPT-3 /// /// [input] is the input text @@ -72,7 +84,7 @@ class HttpOpenAIRepository implements OpenAIRepository { Future> getCompletions({ required String prompt, String? suffix, - int maxTokens = 500, + int maxTokens = 2048, double temperature = 0.3, }) async { final parameters = { @@ -103,6 +115,76 @@ class HttpOpenAIRepository implements OpenAIRepository { } } + @override + Future getStreamedCompletions({ + required String prompt, + required Future Function() onStart, + required Future Function(TextCompletionResponse response) onProcess, + required Future Function() onEnd, + required void Function(OpenAIError error) onError, + String? suffix, + int maxTokens = 2048, + double temperature = 0.3, + }) async { + final parameters = { + 'model': 'text-davinci-003', + 'prompt': prompt, + 'suffix': suffix, + 'max_tokens': maxTokens, + 'temperature': temperature, + 'stream': true, + }; + + final request = http.Request('POST', OpenAIRequestType.textCompletion.uri); + request.headers.addAll(headers); + request.body = jsonEncode(parameters); + + final response = await client.send(request); + + // NEED TO REFACTOR. + // WHY OPENAI USE TWO LINES TO INDICATE THE START OF THE STREAMING RESPONSE? + // AND WHY OPENAI USE [DONE] TO INDICATE THE END OF THE STREAMING RESPONSE? + int syntax = 0; + var previousSyntax = ''; + if (response.statusCode == 200) { + await for (final chunk in response.stream + .transform(const Utf8Decoder()) + .transform(const LineSplitter())) { + syntax += 1; + if (syntax == 3) { + await onStart(); + continue; + } else if (syntax < 3) { + continue; + } + final data = chunk.trim().split('data: '); + Log.editor.info(data.toString()); + if (data.length > 1) { + if (data[1] != '[DONE]') { + final response = TextCompletionResponse.fromJson( + json.decode(data[1]), + ); + if (response.choices.isNotEmpty) { + final text = response.choices.first.text; + if (text == previousSyntax && text == '\n') { + continue; + } + await onProcess(response); + previousSyntax = response.choices.first.text; + } + } else { + onEnd(); + } + } + } + } else { + final body = await response.stream.bytesToString(); + onError( + OpenAIError.fromJson(json.decode(body)['error']), + ); + } + } + @override Future> getEdits({ required String input, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/text_completion.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/text_completion.dart index b2c2a55cc6..067049adbf 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/text_completion.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/text_completion.dart @@ -8,7 +8,7 @@ class TextCompletionChoice with _$TextCompletionChoice { required String text, required int index, // ignore: invalid_annotation_target - @JsonKey(name: 'finish_reason') required String finishReason, + @JsonKey(name: 'finish_reason') String? finishReason, }) = _TextCompletionChoice; factory TextCompletionChoice.fromJson(Map json) => diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/util/editor_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/util/editor_extension.dart index fd27c65cef..09238cabac 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/util/editor_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/util/editor_extension.dart @@ -11,6 +11,10 @@ extension TextRobot on EditorState { TextRobotInputType inputType = TextRobotInputType.word, Duration delay = const Duration(milliseconds: 10), }) async { + if (text == '\n') { + await insertNewLineAtCurrentSelection(); + return; + } final lines = text.split('\n'); for (final line in lines) { if (line.isEmpty) { @@ -28,13 +32,21 @@ extension TextRobot on EditorState { } break; case TextRobotInputType.word: - final words = line.split(' ').map((e) => '$e '); - for (final word in words) { + final words = line.split(' '); + if (words.length == 1 || + (words.length == 2 && + (words.first.isEmpty || words.last.isEmpty))) { await insertTextAtCurrentSelection( - word, + line, ); - await Future.delayed(delay, () {}); + } else { + for (final word in words.map((e) => '$e ')) { + await insertTextAtCurrentSelection( + word, + ); + } } + await Future.delayed(delay, () {}); break; } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart index acbad03ea4..c5d7a39946 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart @@ -61,19 +61,16 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> { void initState() { super.initState(); - focusNode.addListener(() { - if (focusNode.hasFocus) { - widget.editorState.service.selectionService.clearSelection(); - } else { - widget.editorState.service.keyboardService?.enable(); - } - }); + textFieldFocusNode.addListener(_onFocusChanged); textFieldFocusNode.requestFocus(); } @override void dispose() { controller.dispose(); + textFieldFocusNode.removeListener(_onFocusChanged); + widget.editorState.service.selectionService.currentSelection + .removeListener(_onCancelWhenSelectionChanged); super.dispose(); } @@ -242,30 +239,41 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> { loading.start(); await _updateEditingText(); final result = await UserBackendService.getCurrentUserProfile(); + result.fold((userProfile) async { + BarrierDialog? barrierDialog; final openAIRepository = HttpOpenAIRepository( client: http.Client(), apiKey: userProfile.openaiKey, ); - final completions = await openAIRepository.getCompletions( + await openAIRepository.getStreamedCompletions( prompt: controller.text, + onStart: () async { + loading.stop(); + barrierDialog = BarrierDialog(context); + barrierDialog?.show(); + await _makeSurePreviousNodeIsEmptyTextNode(); + }, + onProcess: (response) async { + if (response.choices.isNotEmpty) { + final text = response.choices.first.text; + await widget.editorState.autoInsertText( + text, + inputType: TextRobotInputType.word, + delay: Duration.zero, + ); + } + }, + onEnd: () async { + await barrierDialog?.dismiss(); + widget.editorState.service.selectionService.currentSelection + .addListener(_onCancelWhenSelectionChanged); + }, + onError: (error) async { + loading.stop(); + await _showError(error.message); + }, ); - completions.fold((error) async { - loading.stop(); - await _showError(error.message); - }, (textCompletion) async { - loading.stop(); - await _makeSurePreviousNodeIsEmptyTextNode(); - // Open AI result uses two '\n' as the begin syntax. - var texts = textCompletion.choices.first.text.split('\n'); - if (texts.length > 2) { - texts.removeRange(0, 2); - await widget.editorState.autoInsertText( - texts.join('\n'), - ); - } - focusNode.requestFocus(); - }); }, (error) async { loading.stop(); await _showError( @@ -345,4 +353,16 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> { ), ); } + + void _onFocusChanged() { + if (textFieldFocusNode.hasFocus) { + widget.editorState.service.keyboardService?.disable( + disposition: UnfocusDisposition.previouslyFocusedChild, + ); + } else { + widget.editorState.service.keyboardService?.enable(); + } + } + + void _onCancelWhenSelectionChanged() {} } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/loading.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/loading.dart index 31e97bddcf..e34fe1e8d6 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/loading.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/loading.dart @@ -32,3 +32,28 @@ class Loading { return Navigator.of(loadingContext).pop(); } } + +class BarrierDialog { + BarrierDialog( + this.context, + ); + + late BuildContext loadingContext; + final BuildContext context; + + Future show() async { + return showDialog( + context: context, + barrierDismissible: false, + barrierColor: Colors.transparent, + builder: (BuildContext context) { + loadingContext = context; + return Container(); + }, + ); + } + + Future dismiss() async { + return Navigator.of(loadingContext).pop(); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/trash/src/trash_cell.dart b/frontend/appflowy_flutter/lib/plugins/trash/src/trash_cell.dart index d492b25481..058f6f842a 100644 --- a/frontend/appflowy_flutter/lib/plugins/trash/src/trash_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/trash/src/trash_cell.dart @@ -61,10 +61,12 @@ class TrashCell extends StatelessWidget { } String dateFormatter($fixnum.Int64 inputTimestamps) { - var outputFormat = DateFormat('MM/dd/yyyy hh:mm a'); - var date = - DateTime.fromMillisecondsSinceEpoch(inputTimestamps.toInt() * 1000); - var outputDate = outputFormat.format(date); + final outputFormat = DateFormat('MM/dd/yyyy hh:mm a'); + final date = DateTime.fromMillisecondsSinceEpoch( + inputTimestamps.toInt() * 1000, + isUtc: true, + ); + final outputDate = outputFormat.format(date); return outputDate; } } diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart index f0a34ff713..b8e219125e 100644 --- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart +++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart @@ -1,15 +1,8 @@ import 'package:appflowy/core/network_monitor.dart'; -import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/field/field_action_sheet_bloc.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_service.dart'; import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/checkbox_cell_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/date_cell_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/number_cell_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/select_option_cell_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/text_cell_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart'; import 'package:appflowy/user/application/user_listener.dart'; import 'package:appflowy/user/application/user_service.dart'; @@ -161,38 +154,6 @@ void _resolveGridDeps(GetIt getIt) { (data, _) => FieldActionSheetBloc(fieldCellContext: data), ); - getIt.registerFactoryParam( - (context, _) => TextCellBloc( - cellController: context, - ), - ); - - getIt.registerFactoryParam( - (context, _) => SelectOptionCellBloc( - cellController: context, - ), - ); - - getIt.registerFactoryParam( - (context, _) => NumberCellBloc( - cellController: context, - ), - ); - - getIt.registerFactoryParam( - (context, _) => DateCellBloc( - cellController: context, - ), - ); - - getIt.registerFactoryParam( - (cellData, _) => CheckboxCellBloc( - service: CellBackendService(), - cellController: cellData, - ), - ); - getIt.registerFactoryParam( (viewId, cache) => DatabasePropertyBloc(viewId: viewId, fieldController: cache), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_panel.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_panel.dart index 7314a09749..0250f443d0 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_panel.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_panel.dart @@ -6,7 +6,6 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/container.dart'; import 'package:flutter/material.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart index edef7928ff..d4e93b3548 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart @@ -5,7 +5,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart index 45cac7b6cb..713e120092 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart @@ -1,11 +1,9 @@ import 'package:appflowy/util/file_picker/file_picker_service.dart'; import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/services.dart'; import '../../../../generated/locale_keys.g.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart index f861b35a08..2b075d16fc 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart @@ -4,9 +4,6 @@ import 'package:appflowy/workspace/application/settings/settings_file_exporter_c import 'package:dartz/dartz.dart' as dartz; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/left_bar_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/left_bar_item.dart index 29788a8a32..e697262492 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/left_bar_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/left_bar_item.dart @@ -55,18 +55,26 @@ class _ViewLeftBarItemState extends State { return IntrinsicWidth( key: ValueKey(_controller.text), - child: TextField( - controller: _controller, - focusNode: _focusNode, - scrollPadding: EdgeInsets.zero, - decoration: const InputDecoration( - contentPadding: EdgeInsets.symmetric(vertical: 4.0), - border: InputBorder.none, - isDense: true, + child: GestureDetector( + onDoubleTap: () { + _controller.selection = TextSelection( + baseOffset: 0, + extentOffset: _controller.text.length, + ); + }, + child: TextField( + controller: _controller, + focusNode: _focusNode, + scrollPadding: EdgeInsets.zero, + decoration: const InputDecoration( + contentPadding: EdgeInsets.symmetric(vertical: 4.0), + border: InputBorder.none, + isDense: true, + ), + style: Theme.of(context).textTheme.bodyMedium, + // cursorColor: widget.cursorColor, + // obscureText: widget.enableObscure, ), - style: Theme.of(context).textTheme.bodyMedium, - // cursorColor: widget.cursorColor, - // obscureText: widget.enableObscure, ), ); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart index f7c48fdaaa..df38504c4b 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart @@ -1,7 +1,6 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; import 'package:styled_widget/styled_widget.dart'; diff --git a/frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj b/frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj index 219b81efe0..5634f236ea 100644 --- a/frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj +++ b/frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj @@ -416,6 +416,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { + ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; @@ -549,6 +550,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { + ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; @@ -573,6 +575,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { + ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/example/lib/plugin/AI/auto_completion.dart b/frontend/appflowy_flutter/packages/appflowy_editor/example/lib/plugin/AI/auto_completion.dart index c2e9447b6b..e90f8bd3d2 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/example/lib/plugin/AI/auto_completion.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/example/lib/plugin/AI/auto_completion.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; SelectionMenuItem autoCompletionMenuItem = SelectionMenuItem( - name: () => 'Auto generate content', + name: 'Auto generate content', icon: (editorState, onSelected) => Icon( Icons.rocket, size: 18.0, diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/example/lib/plugin/AI/continue_to_write.dart b/frontend/appflowy_flutter/packages/appflowy_editor/example/lib/plugin/AI/continue_to_write.dart index e3e407d481..4e0b2ec100 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/example/lib/plugin/AI/continue_to_write.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/example/lib/plugin/AI/continue_to_write.dart @@ -4,7 +4,7 @@ import 'package:example/plugin/AI/text_robot.dart'; import 'package:flutter/material.dart'; SelectionMenuItem continueToWriteMenuItem = SelectionMenuItem( - name: () => 'Continue To Write', + name: 'Continue To Write', icon: (editorState, onSelected) => Icon( Icons.print, size: 18.0, diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/example/macos/Podfile.lock b/frontend/appflowy_flutter/packages/appflowy_editor/example/macos/Podfile.lock index 06431f182e..cacc879cd6 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/example/macos/Podfile.lock +++ b/frontend/appflowy_flutter/packages/appflowy_editor/example/macos/Podfile.lock @@ -38,10 +38,10 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: flowy_infra_ui: c34d49d615ed9fe552cd47f90d7850815a74e9e9 FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 - path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 + path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 rich_clipboard_macos: 43364b66b9dc69d203eb8dd6d758e2d12e02723c - shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca - url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2 + shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 + url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/core/legacy/built_in_attribute_keys.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/core/legacy/built_in_attribute_keys.dart index 3f334bdd0a..6421e7efd0 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/core/legacy/built_in_attribute_keys.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/core/legacy/built_in_attribute_keys.dart @@ -37,7 +37,6 @@ class BuiltInAttributeKey { static String checkbox = 'checkbox'; static String code = 'code'; static String number = 'number'; - static String defaultFormating = 'defaultFormating'; static List partialStyleKeys = [ BuiltInAttributeKey.bold, diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart index 0ac4092eb4..2c92b43a31 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart @@ -95,11 +95,18 @@ class _FlowyRichTextState extends State with SelectableMixin { textPosition, Rect.zero) ?? Offset.zero; } + if (widget.cursorHeight != null && cursorHeight != null) { + cursorOffset = Offset( + cursorOffset.dx, + cursorOffset.dy + (cursorHeight - widget.cursorHeight!) / 2, + ); + cursorHeight = widget.cursorHeight; + } final rect = Rect.fromLTWH( cursorOffset.dx - (widget.cursorWidth / 2.0), cursorOffset.dy, widget.cursorWidth, - widget.cursorHeight ?? cursorHeight ?? 16.0, + cursorHeight ?? 16.0, ); return rect; } diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart index 912d9447ff..1d3a73f93e 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart @@ -47,7 +47,7 @@ class _SelectionMenuItemWidgetState extends State { : MaterialStateProperty.all(Colors.transparent), ), label: Text( - widget.item.name(), + widget.item.name, textAlign: TextAlign.left, style: TextStyle( color: (widget.isSelected || _onHover) diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart index 498438e6be..7e0ce3eeea 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart @@ -156,7 +156,7 @@ List get defaultSelectionMenuItems => _defaultSelectionMenuItems; final List _defaultSelectionMenuItems = [ SelectionMenuItem( - name: () => AppFlowyEditorLocalizations.current.text, + name: AppFlowyEditorLocalizations.current.text, icon: (editorState, onSelected) => _selectionMenuIcon('text', editorState, onSelected), keywords: ['text'], @@ -165,7 +165,7 @@ final List _defaultSelectionMenuItems = [ }, ), SelectionMenuItem( - name: () => AppFlowyEditorLocalizations.current.heading1, + name: AppFlowyEditorLocalizations.current.heading1, icon: (editorState, onSelected) => _selectionMenuIcon('h1', editorState, onSelected), keywords: ['heading 1, h1'], @@ -174,7 +174,7 @@ final List _defaultSelectionMenuItems = [ }, ), SelectionMenuItem( - name: () => AppFlowyEditorLocalizations.current.heading2, + name: AppFlowyEditorLocalizations.current.heading2, icon: (editorState, onSelected) => _selectionMenuIcon('h2', editorState, onSelected), keywords: ['heading 2, h2'], @@ -183,7 +183,7 @@ final List _defaultSelectionMenuItems = [ }, ), SelectionMenuItem( - name: () => AppFlowyEditorLocalizations.current.heading3, + name: AppFlowyEditorLocalizations.current.heading3, icon: (editorState, onSelected) => _selectionMenuIcon('h3', editorState, onSelected), keywords: ['heading 3, h3'], @@ -192,14 +192,14 @@ final List _defaultSelectionMenuItems = [ }, ), SelectionMenuItem( - name: () => AppFlowyEditorLocalizations.current.image, + name: AppFlowyEditorLocalizations.current.image, icon: (editorState, onSelected) => _selectionMenuIcon('image', editorState, onSelected), keywords: ['image'], handler: showImageUploadMenu, ), SelectionMenuItem( - name: () => AppFlowyEditorLocalizations.current.bulletedList, + name: AppFlowyEditorLocalizations.current.bulletedList, icon: (editorState, onSelected) => _selectionMenuIcon('bulleted_list', editorState, onSelected), keywords: ['bulleted list', 'list', 'unordered list'], @@ -208,7 +208,7 @@ final List _defaultSelectionMenuItems = [ }, ), SelectionMenuItem( - name: () => AppFlowyEditorLocalizations.current.numberedList, + name: AppFlowyEditorLocalizations.current.numberedList, icon: (editorState, onSelected) => _selectionMenuIcon('number', editorState, onSelected), keywords: ['numbered list', 'list', 'ordered list'], @@ -217,7 +217,7 @@ final List _defaultSelectionMenuItems = [ }, ), SelectionMenuItem( - name: () => AppFlowyEditorLocalizations.current.checkbox, + name: AppFlowyEditorLocalizations.current.checkbox, icon: (editorState, onSelected) => _selectionMenuIcon('checkbox', editorState, onSelected), keywords: ['todo list', 'list', 'checkbox list'], @@ -226,7 +226,7 @@ final List _defaultSelectionMenuItems = [ }, ), SelectionMenuItem( - name: () => AppFlowyEditorLocalizations.current.quote, + name: AppFlowyEditorLocalizations.current.quote, icon: (editorState, onSelected) => _selectionMenuIcon('quote', editorState, onSelected), keywords: ['quote', 'refer'], diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart index 0d96853d74..3a88cfcd4a 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart @@ -20,14 +20,14 @@ class SelectionMenuItem { required SelectionMenuItemHandler handler, }) { this.handler = (editorState, menuService, context) { - _deleteToSlash(editorState); + _deleteSlash(editorState); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { handler(editorState, menuService, context); }); }; } - final String Function() name; + final String name; final Widget Function(EditorState editorState, bool onSelected) icon; /// Customizes keywords for item. @@ -36,20 +36,23 @@ class SelectionMenuItem { final List keywords; late final SelectionMenuItemHandler handler; - void _deleteToSlash(EditorState editorState) { + void _deleteSlash(EditorState editorState) { final selectionService = editorState.service.selectionService; final selection = selectionService.currentSelection.value; final nodes = selectionService.currentSelectedNodes; if (selection != null && nodes.length == 1) { final node = nodes.first as TextNode; final end = selection.start.offset; - final start = node.toPlainText().substring(0, end).lastIndexOf('/'); + final lastSlashIndex = + node.toPlainText().substring(0, end).lastIndexOf('/'); + // delete all the texts after '/' along with '/' final transaction = editorState.transaction ..deleteText( node, - start, - selection.start.offset - start, + lastSlashIndex, + end - lastSlashIndex, ); + editorState.apply(transaction); } } @@ -81,7 +84,7 @@ class SelectionMenuItem { updateSelection, }) { return SelectionMenuItem( - name: () => name, + name: name, icon: (editorState, onSelected) => Icon( iconData, color: onSelected diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart index 94caff83b1..b38d838fed 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart @@ -265,135 +265,6 @@ ShortcutEventHandler markdownLinkOrImageHandler = (editorState, event) { return KeyEventResult.handled; }; -// convert **abc** to bold abc. -ShortcutEventHandler doubleAsterisksToBold = (editorState, event) { - final selectionService = editorState.service.selectionService; - final selection = selectionService.currentSelection.value; - final textNodes = selectionService.currentSelectedNodes.whereType(); - if (selection == null || !selection.isSingle || textNodes.length != 1) { - return KeyEventResult.ignored; - } - - final textNode = textNodes.first; - final text = textNode.toPlainText().substring(0, selection.end.offset); - - // make sure the last two characters are **. - if (text.length < 2 || text[selection.end.offset - 1] != '*') { - return KeyEventResult.ignored; - } - - // find all the index of `*`. - final asteriskIndexes = []; - for (var i = 0; i < text.length; i++) { - if (text[i] == '*') { - asteriskIndexes.add(i); - } - } - - if (asteriskIndexes.length < 3) { - return KeyEventResult.ignored; - } - - // make sure the second to last and third to last asterisks are connected. - final thirdToLastAsteriskIndex = asteriskIndexes[asteriskIndexes.length - 3]; - final secondToLastAsteriskIndex = asteriskIndexes[asteriskIndexes.length - 2]; - final lastAsterisIndex = asteriskIndexes[asteriskIndexes.length - 1]; - if (secondToLastAsteriskIndex != thirdToLastAsteriskIndex + 1 || - lastAsterisIndex == secondToLastAsteriskIndex + 1) { - return KeyEventResult.ignored; - } - - // delete the last three asterisks. - // update the style of the text surround by `** **` to bold. - // and update the cursor position. - final transaction = editorState.transaction - ..deleteText(textNode, lastAsterisIndex, 1) - ..deleteText(textNode, thirdToLastAsteriskIndex, 2) - ..formatText( - textNode, - thirdToLastAsteriskIndex, - selection.end.offset - thirdToLastAsteriskIndex - 3, - { - BuiltInAttributeKey.bold: true, - BuiltInAttributeKey.defaultFormating: true, - }, - ) - ..afterSelection = Selection.collapsed( - Position( - path: textNode.path, - offset: selection.end.offset - 3, - ), - ); - editorState.apply(transaction); - - return KeyEventResult.handled; -}; - -// convert __abc__ to bold abc. -ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) { - final selectionService = editorState.service.selectionService; - final selection = selectionService.currentSelection.value; - final textNodes = selectionService.currentSelectedNodes.whereType(); - if (selection == null || !selection.isSingle || textNodes.length != 1) { - return KeyEventResult.ignored; - } - - final textNode = textNodes.first; - final text = textNode.toPlainText().substring(0, selection.end.offset); - - // make sure the last two characters are __. - if (text.length < 2 || text[selection.end.offset - 1] != '_') { - return KeyEventResult.ignored; - } - - // find all the index of `_`. - final underscoreIndexes = []; - for (var i = 0; i < text.length; i++) { - if (text[i] == '_') { - underscoreIndexes.add(i); - } - } - - if (underscoreIndexes.length < 3) { - return KeyEventResult.ignored; - } - - // make sure the second to last and third to last underscores are connected. - final thirdToLastUnderscoreIndex = - underscoreIndexes[underscoreIndexes.length - 3]; - final secondToLastUnderscoreIndex = - underscoreIndexes[underscoreIndexes.length - 2]; - final lastAsterisIndex = underscoreIndexes[underscoreIndexes.length - 1]; - if (secondToLastUnderscoreIndex != thirdToLastUnderscoreIndex + 1 || - lastAsterisIndex == secondToLastUnderscoreIndex + 1) { - return KeyEventResult.ignored; - } - - // delete the last three underscores. - // update the style of the text surround by `__ __` to bold. - // and update the cursor position. - final transaction = editorState.transaction - ..deleteText(textNode, lastAsterisIndex, 1) - ..deleteText(textNode, thirdToLastUnderscoreIndex, 2) - ..formatText( - textNode, - thirdToLastUnderscoreIndex, - selection.end.offset - thirdToLastUnderscoreIndex - 3, - { - BuiltInAttributeKey.bold: true, - BuiltInAttributeKey.defaultFormating: true, - }, - ) - ..afterSelection = Selection.collapsed( - Position( - path: textNode.path, - offset: selection.end.offset - 3, - ), - ); - editorState.apply(transaction); - return KeyEventResult.handled; -}; - ShortcutEventHandler underscoreToItalicHandler = (editorState, event) { // Obtain the selection and selected nodes of the current document through the 'selectionService' // to determine whether the selection is collapsed and whether the selected node is a text node. @@ -438,3 +309,117 @@ ShortcutEventHandler underscoreToItalicHandler = (editorState, event) { return KeyEventResult.handled; }; + +ShortcutEventHandler doubleAsteriskToBoldHanlder = (editorState, event) { + final selectionService = editorState.service.selectionService; + final selection = selectionService.currentSelection.value; + final textNodes = selectionService.currentSelectedNodes.whereType(); + + if (selection == null || !selection.isSingle || textNodes.length != 1) { + return KeyEventResult.ignored; + } + + final textNode = textNodes.first; + final text = textNode.toPlainText(); + +// make sure the last two characters are '**' + if (text.length < 2 || text[selection.end.offset - 1] != '*') { + return KeyEventResult.ignored; + } + +// find all the index of '*' + final asteriskIndexList = []; + for (var i = 0; i < text.length; i++) { + if (text[i] == '*') { + asteriskIndexList.add(i); + } + } + + if (asteriskIndexList.length < 3) return KeyEventResult.ignored; + +// make sure the second to last and third to last asterisk are connected + final thirdToLastAsteriskIndex = + asteriskIndexList[asteriskIndexList.length - 3]; + final secondToLastAsteriskIndex = + asteriskIndexList[asteriskIndexList.length - 2]; + final lastAsteriskIndex = asteriskIndexList[asteriskIndexList.length - 1]; + if (secondToLastAsteriskIndex != thirdToLastAsteriskIndex + 1 || + lastAsteriskIndex == secondToLastAsteriskIndex + 1) { + return KeyEventResult.ignored; + } + +//delete the last three asterisks +//update the style of the text surround by '** **' to bold +//update the cursor position + final transaction = editorState.transaction + ..deleteText(textNode, lastAsteriskIndex, 1) + ..deleteText(textNode, thirdToLastAsteriskIndex, 2) + ..formatText(textNode, thirdToLastAsteriskIndex, + selection.end.offset - thirdToLastAsteriskIndex - 2, { + BuiltInAttributeKey.bold: true, + }) + ..afterSelection = Selection.collapsed( + Position(path: textNode.path, offset: selection.end.offset - 3)); + + editorState.apply(transaction); + + return KeyEventResult.handled; +}; + +//Implement in the same way as doubleAsteriskToBoldHanlder +ShortcutEventHandler doubleUnderscoreToBoldHanlder = (editorState, event) { + final selectionService = editorState.service.selectionService; + final selection = selectionService.currentSelection.value; + final textNodes = selectionService.currentSelectedNodes.whereType(); + + if (selection == null || !selection.isSingle || textNodes.length != 1) { + return KeyEventResult.ignored; + } + + final textNode = textNodes.first; + final text = textNode.toPlainText(); + +// make sure the last two characters are '__' + if (text.length < 2 || text[selection.end.offset - 1] != '_') { + return KeyEventResult.ignored; + } + +// find all the index of '_' + final underscoreIndexList = []; + for (var i = 0; i < text.length; i++) { + if (text[i] == '_') { + underscoreIndexList.add(i); + } + } + + if (underscoreIndexList.length < 3) return KeyEventResult.ignored; + +// make sure the second to last and third to last underscore are connected + final thirdToLastUnderscoreIndex = + underscoreIndexList[underscoreIndexList.length - 3]; + final secondToLastUnderscoreIndex = + underscoreIndexList[underscoreIndexList.length - 2]; + final lastUnderscoreIndex = + underscoreIndexList[underscoreIndexList.length - 1]; + if (secondToLastUnderscoreIndex != thirdToLastUnderscoreIndex + 1 || + lastUnderscoreIndex == secondToLastUnderscoreIndex + 1) { + return KeyEventResult.ignored; + } + +//delete the last three underscores +//update the style of the text surround by '__ __' to bold +//update the cursor position + final transaction = editorState.transaction + ..deleteText(textNode, lastUnderscoreIndex, 1) + ..deleteText(textNode, thirdToLastUnderscoreIndex, 2) + ..formatText(textNode, thirdToLastUnderscoreIndex, + selection.end.offset - thirdToLastUnderscoreIndex - 2, { + BuiltInAttributeKey.bold: true, + }) + ..afterSelection = Selection.collapsed( + Position(path: textNode.path, offset: selection.end.offset - 3)); + + editorState.apply(transaction); + + return KeyEventResult.handled; +}; diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart index 5821733564..1b3f1abc81 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart @@ -56,7 +56,9 @@ ShortcutEventHandler whiteSpaceHandler = (editorState, event) { } else if (numberMatch != null) { final matchText = numberMatch.group(0); final numText = numberMatch.group(1); - if (matchText != null && numText != null) { + if (matchText != null && + numText != null && + matchText.length == selection.startIndex) { return _toNumberList(editorState, textNode, matchText, numText); } } diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/keyboard_service.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/keyboard_service.dart index fad31c711e..a25b7a6f1e 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/keyboard_service.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/keyboard_service.dart @@ -35,7 +35,10 @@ abstract class AppFlowyKeyboardService { /// you can disable the keyboard service of flowy_editor. /// But you need to call the `enable` function to restore after exiting /// your custom component, otherwise the keyboard service will fails. - void disable({bool showCursor = false}); + void disable({ + bool showCursor = false, + UnfocusDisposition disposition = UnfocusDisposition.scope, + }); } /// Process keyboard events @@ -102,10 +105,13 @@ class _AppFlowyKeyboardState extends State } @override - void disable({bool showCursor = false}) { + void disable({ + bool showCursor = false, + UnfocusDisposition disposition = UnfocusDisposition.scope, + }) { isFocus = false; this.showCursor = showCursor; - _focusNode.unfocus(); + _focusNode.unfocus(disposition: disposition); } @override diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/selection_service.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/selection_service.dart index 6e63de8747..b522aa9cef 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/selection_service.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/selection_service.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:appflowy_editor/src/infra/log.dart'; import 'package:appflowy_editor/src/service/context_menu/built_in_context_menu_item.dart'; @@ -121,6 +123,9 @@ class _AppFlowySelectionState extends State EditorState get editorState => widget.editorState; + // Toolbar + Timer? _toolbarTimer; + @override void initState() { super.initState(); @@ -144,6 +149,7 @@ class _AppFlowySelectionState extends State clearSelection(); WidgetsBinding.instance.removeObserver(this); currentSelection.removeListener(_onSelectionChange); + _clearToolbar(); super.dispose(); } @@ -236,7 +242,7 @@ class _AppFlowySelectionState extends State // clear cursor areas // hide toolbar - editorState.service.toolbarService?.hide(); + // editorState.service.toolbarService?.hide(); // clear context menu _clearContextMenu(); @@ -482,13 +488,8 @@ class _AppFlowySelectionState extends State Overlay.of(context)?.insertAll(_selectionAreas); - if (toolbarOffset != null && layerLink != null) { - editorState.service.toolbarService?.showInOffset( - toolbarOffset, - alignment!, - layerLink, - ); - } + // show toolbar + _showToolbarWithDelay(toolbarOffset, layerLink, alignment!); } void _updateCursorAreas(Position position) { @@ -502,6 +503,7 @@ class _AppFlowySelectionState extends State currentSelectedNodes = [node]; _showCursor(node, position); + _clearToolbar(); } void _showCursor(Node node, Position position) { @@ -628,6 +630,40 @@ class _AppFlowySelectionState extends State _scrollUpOrDownIfNeeded(); } + void _showToolbarWithDelay( + Offset? toolbarOffset, + LayerLink? layerLink, + Alignment alignment, { + Duration delay = const Duration(milliseconds: 400), + }) { + if (toolbarOffset == null && layerLink == null) { + _clearToolbar(); + return; + } + if (_toolbarTimer?.isActive ?? false) { + _toolbarTimer?.cancel(); + } + _toolbarTimer = Timer( + delay, + () { + if (toolbarOffset != null && layerLink != null) { + editorState.service.toolbarService?.showInOffset( + toolbarOffset, + alignment, + layerLink, + ); + } + }, + ); + } + + void _clearToolbar() { + editorState.service.toolbarService?.hide(); + if (_toolbarTimer?.isActive ?? false) { + _toolbarTimer?.cancel(); + } + } + void _showDebugLayerIfNeeded({Offset? offset}) { // remove false to show debug overlay. // if (kDebugMode && false) { diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart index 6c1c7bc259..d6338b6fe3 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart @@ -1,5 +1,3 @@ -// List<> - import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart'; import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart'; import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart'; @@ -284,16 +282,6 @@ List builtInShortcutEvents = [ command: 'tab', handler: tabHandler, ), - ShortcutEvent( - key: 'Double stars to bold', - command: 'shift+asterisk', - handler: doubleAsterisksToBold, - ), - ShortcutEvent( - key: 'Double underscores to bold', - command: 'shift+underscore', - handler: doubleUnderscoresToBold, - ), ShortcutEvent( key: 'Backquote to code', command: 'backquote', @@ -319,6 +307,16 @@ List builtInShortcutEvents = [ command: 'shift+underscore', handler: underscoreToItalicHandler, ), + ShortcutEvent( + key: 'Double asterisk to bold', + command: 'shift+digit 8', + handler: doubleAsteriskToBoldHanlder, + ), + ShortcutEvent( + key: 'Double underscore to bold', + command: 'shift+underscore', + handler: doubleUnderscoreToBoldHanlder, + ), // https://github.com/flutter/flutter/issues/104944 // Workaround: Using space editing on the web platform often results in errors, // so adding a shortcut event to handle the space input instead of using the diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/toolbar_service.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/toolbar_service.dart index 762d550803..9fd8ca3648 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/toolbar_service.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/toolbar_service.dart @@ -7,7 +7,11 @@ import 'package:appflowy_editor/src/extensions/object_extensions.dart'; abstract class AppFlowyToolbarService { /// Show the toolbar widget beside the offset. - void showInOffset(Offset offset, Alignment alignment, LayerLink layerLink); + void showInOffset( + Offset offset, + Alignment alignment, + LayerLink layerLink, + ); /// Hide the toolbar widget. void hide(); @@ -45,7 +49,11 @@ class _FlowyToolbarState extends State } @override - void showInOffset(Offset offset, Alignment alignment, LayerLink layerLink) { + void showInOffset( + Offset offset, + Alignment alignment, + LayerLink layerLink, + ) { hide(); final items = _filterItems(toolbarItems); if (items.isEmpty) { diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/test/core/document/node_test.dart b/frontend/appflowy_flutter/packages/appflowy_editor/test/core/document/node_test.dart index 4e407fd321..853df05671 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/test/core/document/node_test.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/test/core/document/node_test.dart @@ -228,5 +228,23 @@ void main() async { final textNode = TextNode.empty()..delta = (Delta()..insert('AppFlowy')); expect(textNode.toPlainText(), 'AppFlowy'); }); + test('test node id', () { + final nodeA = Node( + type: 'example', + children: LinkedList(), + attributes: {}, + ); + final nodeAId = nodeA.id; + expect(nodeAId, 'example'); + final nodeB = Node( + type: 'example', + children: LinkedList(), + attributes: { + 'subtype': 'exampleSubtype', + }, + ); + final nodeBId = nodeB.id; + expect(nodeBId, 'example/exampleSubtype'); + }); }); } diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/test/infra/test_raw_key_event.dart b/frontend/appflowy_flutter/packages/appflowy_editor/test/infra/test_raw_key_event.dart index d00e458e5c..0a3d6cd74f 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/test/infra/test_raw_key_event.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/test/infra/test_raw_key_event.dart @@ -142,15 +142,15 @@ extension on LogicalKeyboardKey { if (this == LogicalKeyboardKey.keyZ) { return PhysicalKeyboardKey.keyZ; } - if (this == LogicalKeyboardKey.asterisk) { + if (this == LogicalKeyboardKey.tilde) { + return PhysicalKeyboardKey.backquote; + } + if (this == LogicalKeyboardKey.digit8) { return PhysicalKeyboardKey.digit8; } if (this == LogicalKeyboardKey.underscore) { return PhysicalKeyboardKey.minus; } - if (this == LogicalKeyboardKey.tilde) { - return PhysicalKeyboardKey.backquote; - } throw UnimplementedError(); } } diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/test/render/rich_text/checkbox_text_test.dart b/frontend/appflowy_flutter/packages/appflowy_editor/test/render/rich_text/checkbox_text_test.dart index e396dbbf03..eceb429894 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/test/render/rich_text/checkbox_text_test.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/test/render/rich_text/checkbox_text_test.dart @@ -70,7 +70,7 @@ void main() async { }); // https://github.com/AppFlowy-IO/AppFlowy/issues/1763 - // [Bug] Mouse unable to click a certain area #1763 + // // [Bug] Mouse unable to click a certain area #1763 testWidgets('insert a new checkbox after an exsiting checkbox', (tester) async { // Before diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/test/render/rich_text/toolbar_rich_text_test.dart b/frontend/appflowy_flutter/packages/appflowy_editor/test/render/rich_text/toolbar_rich_text_test.dart index b9e774b351..54f9ed0455 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/test/render/rich_text/toolbar_rich_text_test.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/test/render/rich_text/toolbar_rich_text_test.dart @@ -25,6 +25,7 @@ void main() async { await editor.updateSelection(h1); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final h1Button = find.byWidgetPredicate((widget) { @@ -52,6 +53,7 @@ void main() async { end: Position(path: [0], offset: singleLineText.length)); await editor.updateSelection(h2); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final h2Button = find.byWidgetPredicate((widget) { @@ -77,6 +79,7 @@ void main() async { end: Position(path: [0], offset: singleLineText.length)); await editor.updateSelection(h3); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final h3Button = find.byWidgetPredicate((widget) { @@ -104,6 +107,7 @@ void main() async { end: Position(path: [0], offset: singleLineText.length)); await editor.updateSelection(underline); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final underlineButton = find.byWidgetPredicate((widget) { if (widget is ToolbarItemWidget) { @@ -132,6 +136,7 @@ void main() async { end: Position(path: [0], offset: singleLineText.length)); await editor.updateSelection(bold); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final boldButton = find.byWidgetPredicate((widget) { if (widget is ToolbarItemWidget) { @@ -159,6 +164,7 @@ void main() async { end: Position(path: [0], offset: singleLineText.length)); await editor.updateSelection(italic); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final italicButton = find.byWidgetPredicate((widget) { if (widget is ToolbarItemWidget) { @@ -187,6 +193,7 @@ void main() async { await editor.updateSelection(strikeThrough); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final strikeThroughButton = find.byWidgetPredicate((widget) { if (widget is ToolbarItemWidget) { @@ -214,6 +221,7 @@ void main() async { end: Position(path: [0], offset: singleLineText.length)); await editor.updateSelection(code); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final codeButton = find.byWidgetPredicate((widget) { if (widget is ToolbarItemWidget) { @@ -250,6 +258,7 @@ void main() async { end: Position(path: [0], offset: singleLineText.length)); await editor.updateSelection(quote); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final quoteButton = find.byWidgetPredicate((widget) { if (widget is ToolbarItemWidget) { @@ -276,6 +285,7 @@ void main() async { end: Position(path: [0], offset: singleLineText.length)); await editor.updateSelection(bulletList); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final bulletListButton = find.byWidgetPredicate((widget) { if (widget is ToolbarItemWidget) { @@ -306,6 +316,7 @@ void main() async { end: Position(path: [0], offset: singleLineText.length)); await editor.updateSelection(selection); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final highlightButton = find.byWidgetPredicate((widget) { if (widget is ToolbarItemWidget) { @@ -343,6 +354,7 @@ void main() async { ); await editor.updateSelection(selection); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); final colorButton = find.byWidgetPredicate((widget) { if (widget is ToolbarItemWidget) { diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart b/frontend/appflowy_flutter/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart index 99a4674efd..6b392cdebb 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart @@ -10,40 +10,44 @@ void main() async { }); group('selection_menu_widget.dart', () { - for (var i = 0; i < defaultSelectionMenuItems.length; i += 1) { - testWidgets('Selects number.$i item in selection menu with enter', - (tester) async { - final editor = await _prepare(tester); - for (var j = 0; j < i; j++) { - await editor.pressLogicKey(LogicalKeyboardKey.arrowDown); - } + // const i = defaultSelectionMenuItems.length; + // + // Because the `defaultSelectionMenuItems` uses localization, + // and the MaterialApp has not been initialized at the time of getting the value, + // it will crash. + // + // Use const value temporarily instead. + const i = 7; + testWidgets('Selects number.$i item in selection menu with keyboard', + (tester) async { + final editor = await _prepare(tester); + for (var j = 0; j < i; j++) { + await editor.pressLogicKey(LogicalKeyboardKey.arrowDown); + } - await editor.pressLogicKey(LogicalKeyboardKey.enter); - expect( - find.byType(SelectionMenuWidget, skipOffstage: false), - findsNothing, - ); - if (defaultSelectionMenuItems[i].name() != 'Image') { - await _testDefaultSelectionMenuItems(i, editor); - } - }); + await editor.pressLogicKey(LogicalKeyboardKey.enter); + expect( + find.byType(SelectionMenuWidget, skipOffstage: false), + findsNothing, + ); + if (defaultSelectionMenuItems[i].name != 'Image') { + await _testDefaultSelectionMenuItems(i, editor); + } + }); - testWidgets('Selects number.$i item in selection menu with click', - (tester) async { - final editor = await _prepare(tester); - - await tester.tap(find.byType(SelectionMenuItemWidget).at(i)); - await tester.pumpAndSettle(); - - expect( - find.byType(SelectionMenuWidget, skipOffstage: false), - findsNothing, - ); - if (defaultSelectionMenuItems[i].name() != 'Image') { - await _testDefaultSelectionMenuItems(i, editor); - } - }); - } + testWidgets('Selects number.$i item in selection menu with clicking', + (tester) async { + final editor = await _prepare(tester); + await tester.tap(find.byType(SelectionMenuItemWidget).at(i)); + await tester.pumpAndSettle(); + expect( + find.byType(SelectionMenuWidget, skipOffstage: false), + findsNothing, + ); + if (defaultSelectionMenuItems[i].name != 'Image') { + await _testDefaultSelectionMenuItems(i, editor); + } + }); testWidgets('Search item in selection menu util no results', (tester) async { @@ -136,7 +140,7 @@ Future _prepare(WidgetTester tester) async { ); for (final item in defaultSelectionMenuItems) { - expect(find.text(item.name()), findsOneWidget); + expect(find.text(item.name), findsOneWidget); } return Future.value(editor); @@ -146,28 +150,31 @@ Future _testDefaultSelectionMenuItems( int index, EditorWidgetTester editor) async { expect(editor.documentLength, 4); expect(editor.documentSelection, Selection.single(path: [2], startOffset: 0)); + expect((editor.nodeAtPath([0]) as TextNode).toPlainText(), + 'Welcome to Appflowy 😁'); expect((editor.nodeAtPath([1]) as TextNode).toPlainText(), 'Welcome to Appflowy 😁'); final node = editor.nodeAtPath([2]); final item = defaultSelectionMenuItems[index]; - final itemName = item.name(); - if (itemName == 'Text') { + if (item.name == 'Text') { expect(node?.subtype == null, true); - } else if (itemName == 'Heading 1') { + expect(node?.toString(), null); + } else if (item.name == 'Heading 1') { expect(node?.subtype, BuiltInAttributeKey.heading); expect(node?.attributes.heading, BuiltInAttributeKey.h1); - } else if (itemName == 'Heading 2') { + expect(node?.toString(), null); + } else if (item.name == 'Heading 2') { expect(node?.subtype, BuiltInAttributeKey.heading); expect(node?.attributes.heading, BuiltInAttributeKey.h2); - } else if (itemName == 'Heading 3') { + expect(node?.toString(), null); + } else if (item.name == 'Heading 3') { expect(node?.subtype, BuiltInAttributeKey.heading); expect(node?.attributes.heading, BuiltInAttributeKey.h3); - } else if (itemName == 'Bulleted list') { + expect(node?.toString(), null); + } else if (item.name == 'Bulleted list') { expect(node?.subtype, BuiltInAttributeKey.bulletedList); - } else if (itemName == 'Checkbox') { + } else if (item.name == 'Checkbox') { expect(node?.subtype, BuiltInAttributeKey.checkbox); expect(node?.attributes.check, false); - } else if (itemName == 'Quote') { - expect(node?.subtype, BuiltInAttributeKey.quote); } } diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/format_style_handler_test.dart b/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/format_style_handler_test.dart index 0cb4c71600..222a7efe1b 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/format_style_handler_test.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/format_style_handler_test.dart @@ -245,6 +245,7 @@ Future _testLinkMenuInSingleTextSelection(WidgetTester tester) async { await editor.updateSelection(selection); // show toolbar + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); // trigger the link menu diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_handler_test.dart b/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_handler_test.dart deleted file mode 100644 index f39d58e6a3..0000000000 --- a/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_handler_test.dart +++ /dev/null @@ -1,277 +0,0 @@ -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_editor/src/extensions/text_node_extensions.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import '../../infra/test_editor.dart'; - -void main() async { - setUpAll(() { - TestWidgetsFlutterBinding.ensureInitialized(); - }); - - group('markdown_syntax_to_styled_text_handler.dart', () { - group('convert double asterisks to bold', () { - Future insertAsterisk( - EditorWidgetTester editor, { - int repeat = 1, - }) async { - for (var i = 0; i < repeat; i++) { - await editor.pressLogicKey( - LogicalKeyboardKey.asterisk, - isShiftPressed: true, - ); - } - } - - testWidgets('**AppFlowy** to bold AppFlowy', (tester) async { - const text = '**AppFlowy*'; - final editor = tester.editor..insertTextNode(''); - await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 0), - ); - final textNode = editor.nodeAtPath([0]) as TextNode; - for (var i = 0; i < text.length; i++) { - await editor.insertText(textNode, text[i], i); - } - await insertAsterisk(editor); - final allBold = textNode.allSatisfyBoldInSelection( - Selection.single( - path: [0], - startOffset: 0, - endOffset: textNode.toPlainText().length, - ), - ); - expect(allBold, true); - expect(textNode.toPlainText(), 'AppFlowy'); - }); - - testWidgets('App**Flowy** to bold AppFlowy', (tester) async { - const text = 'App**Flowy*'; - final editor = tester.editor..insertTextNode(''); - await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 0), - ); - final textNode = editor.nodeAtPath([0]) as TextNode; - for (var i = 0; i < text.length; i++) { - await editor.insertText(textNode, text[i], i); - } - await insertAsterisk(editor); - final allBold = textNode.allSatisfyBoldInSelection( - Selection.single( - path: [0], - startOffset: 3, - endOffset: textNode.toPlainText().length, - ), - ); - expect(allBold, true); - expect(textNode.toPlainText(), 'AppFlowy'); - }); - - testWidgets('***AppFlowy** to bold *AppFlowy', (tester) async { - const text = '***AppFlowy*'; - final editor = tester.editor..insertTextNode(''); - await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 0), - ); - final textNode = editor.nodeAtPath([0]) as TextNode; - for (var i = 0; i < text.length; i++) { - await editor.insertText(textNode, text[i], i); - } - await insertAsterisk(editor); - final allBold = textNode.allSatisfyBoldInSelection( - Selection.single( - path: [0], - startOffset: 1, - endOffset: textNode.toPlainText().length, - ), - ); - expect(allBold, true); - expect(textNode.toPlainText(), '*AppFlowy'); - }); - - testWidgets('**AppFlowy** application to bold AppFlowy only', - (tester) async { - const boldText = '**AppFlowy*'; - final editor = tester.editor..insertTextNode(''); - await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 0), - ); - final textNode = editor.nodeAtPath([0]) as TextNode; - - for (var i = 0; i < boldText.length; i++) { - await editor.insertText(textNode, boldText[i], i); - } - await insertAsterisk(editor); - final boldTextLength = boldText.replaceAll('*', '').length; - final appFlowyBold = textNode.allSatisfyBoldInSelection( - Selection.single( - path: [0], - startOffset: 0, - endOffset: boldTextLength, - ), - ); - expect(appFlowyBold, true); - expect(textNode.toPlainText(), 'AppFlowy'); - }); - - testWidgets('**** nothing changes', (tester) async { - const text = '***'; - final editor = tester.editor..insertTextNode(''); - await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 0), - ); - final textNode = editor.nodeAtPath([0]) as TextNode; - for (var i = 0; i < text.length; i++) { - await editor.insertText(textNode, text[i], i); - } - await insertAsterisk(editor); - final allBold = textNode.allSatisfyBoldInSelection( - Selection.single( - path: [0], - startOffset: 0, - endOffset: textNode.toPlainText().length, - ), - ); - expect(allBold, false); - expect(textNode.toPlainText(), text); - }); - }); - - group('convert double underscores to bold', () { - Future insertUnderscore( - EditorWidgetTester editor, { - int repeat = 1, - }) async { - for (var i = 0; i < repeat; i++) { - await editor.pressLogicKey( - LogicalKeyboardKey.underscore, - isShiftPressed: true, - ); - } - } - - testWidgets('__AppFlowy__ to bold AppFlowy', (tester) async { - const text = '__AppFlowy_'; - final editor = tester.editor..insertTextNode(''); - await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 0), - ); - final textNode = editor.nodeAtPath([0]) as TextNode; - for (var i = 0; i < text.length; i++) { - await editor.insertText(textNode, text[i], i); - } - await insertUnderscore(editor); - final allBold = textNode.allSatisfyBoldInSelection( - Selection.single( - path: [0], - startOffset: 0, - endOffset: textNode.toPlainText().length, - ), - ); - expect(allBold, true); - expect(textNode.toPlainText(), 'AppFlowy'); - }); - - testWidgets('App__Flowy__ to bold AppFlowy', (tester) async { - const text = 'App__Flowy_'; - final editor = tester.editor..insertTextNode(''); - await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 0), - ); - final textNode = editor.nodeAtPath([0]) as TextNode; - for (var i = 0; i < text.length; i++) { - await editor.insertText(textNode, text[i], i); - } - await insertUnderscore(editor); - final allBold = textNode.allSatisfyBoldInSelection( - Selection.single( - path: [0], - startOffset: 3, - endOffset: textNode.toPlainText().length, - ), - ); - expect(allBold, true); - expect(textNode.toPlainText(), 'AppFlowy'); - }); - - testWidgets('___AppFlowy__ to bold _AppFlowy', (tester) async { - const text = '___AppFlowy_'; - final editor = tester.editor..insertTextNode(''); - await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 0), - ); - final textNode = editor.nodeAtPath([0]) as TextNode; - for (var i = 0; i < text.length; i++) { - await editor.insertText(textNode, text[i], i); - } - await insertUnderscore(editor); - final allBold = textNode.allSatisfyBoldInSelection( - Selection.single( - path: [0], - startOffset: 1, - endOffset: textNode.toPlainText().length, - ), - ); - expect(allBold, true); - expect(textNode.toPlainText(), '_AppFlowy'); - }); - - testWidgets('__AppFlowy__ application to bold AppFlowy only', - (tester) async { - const boldText = '__AppFlowy_'; - final editor = tester.editor..insertTextNode(''); - await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 0), - ); - final textNode = editor.nodeAtPath([0]) as TextNode; - - for (var i = 0; i < boldText.length; i++) { - await editor.insertText(textNode, boldText[i], i); - } - await insertUnderscore(editor); - final boldTextLength = boldText.replaceAll('_', '').length; - final appFlowyBold = textNode.allSatisfyBoldInSelection( - Selection.single( - path: [0], - startOffset: 0, - endOffset: boldTextLength, - ), - ); - expect(appFlowyBold, true); - expect(textNode.toPlainText(), 'AppFlowy'); - }); - - testWidgets('____ nothing changes', (tester) async { - const text = '___'; - final editor = tester.editor..insertTextNode(''); - await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 0), - ); - final textNode = editor.nodeAtPath([0]) as TextNode; - for (var i = 0; i < text.length; i++) { - await editor.insertText(textNode, text[i], i); - } - await insertUnderscore(editor); - final allBold = textNode.allSatisfyBoldInSelection( - Selection.single( - path: [0], - startOffset: 0, - endOffset: textNode.toPlainText().length, - ), - ); - expect(allBold, false); - expect(textNode.toPlainText(), text); - }); - }); - }); -} diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart b/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart index 662c7982b4..219fd07568 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_test.dart @@ -257,4 +257,192 @@ void main() async { }); }); }); + + group('convert double asterisk to bold', () { + Future insertAsterisk( + EditorWidgetTester editor, { + int repeat = 1, + }) async { + for (var i = 0; i < repeat; i++) { + await editor.pressLogicKey( + LogicalKeyboardKey.digit8, + isShiftPressed: true, + ); + } + } + + testWidgets('**AppFlowy** to bold AppFlowy', ((widgetTester) async { + const text = '**AppFlowy*'; + final editor = widgetTester.editor..insertTextNode(''); + + await editor.startTesting(); + await editor.updateSelection(Selection.single(path: [0], startOffset: 0)); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + + await insertAsterisk(editor); + + final allBold = textNode.allSatisfyBoldInSelection(Selection.single( + path: [0], startOffset: 0, endOffset: textNode.toPlainText().length)); + + expect(allBold, true); + expect(textNode.toPlainText(), 'AppFlowy'); + })); + + testWidgets('App**Flowy** to bold AppFlowy', ((widgetTester) async { + const text = 'App**Flowy*'; + final editor = widgetTester.editor..insertTextNode(''); + + await editor.startTesting(); + await editor.updateSelection(Selection.single(path: [0], startOffset: 0)); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + + await insertAsterisk(editor); + + final allBold = textNode.allSatisfyBoldInSelection(Selection.single( + path: [0], startOffset: 3, endOffset: textNode.toPlainText().length)); + + expect(allBold, true); + expect(textNode.toPlainText(), 'AppFlowy'); + })); + + testWidgets('***AppFlowy** to bold *AppFlowy', ((widgetTester) async { + const text = '***AppFlowy*'; + final editor = widgetTester.editor..insertTextNode(''); + + await editor.startTesting(); + await editor.updateSelection(Selection.single(path: [0], startOffset: 0)); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + + await insertAsterisk(editor); + + final allBold = textNode.allSatisfyBoldInSelection(Selection.single( + path: [0], startOffset: 1, endOffset: textNode.toPlainText().length)); + + expect(allBold, true); + expect(textNode.toPlainText(), '*AppFlowy'); + })); + + testWidgets('**** nothing changes', ((widgetTester) async { + const text = '***'; + final editor = widgetTester.editor..insertTextNode(''); + + await editor.startTesting(); + await editor.updateSelection(Selection.single(path: [0], startOffset: 0)); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + + await insertAsterisk(editor); + + final allBold = textNode.allSatisfyBoldInSelection(Selection.single( + path: [0], startOffset: 0, endOffset: textNode.toPlainText().length)); + + expect(allBold, false); + expect(textNode.toPlainText(), text); + })); + }); + + group('convert double underscore to bold', () { + Future insertUnderscore( + EditorWidgetTester editor, { + int repeat = 1, + }) async { + for (var i = 0; i < repeat; i++) { + await editor.pressLogicKey( + LogicalKeyboardKey.underscore, + isShiftPressed: true, + ); + } + } + + testWidgets('__AppFlowy__ to bold AppFlowy', ((widgetTester) async { + const text = '__AppFlowy_'; + final editor = widgetTester.editor..insertTextNode(''); + + await editor.startTesting(); + await editor.updateSelection(Selection.single(path: [0], startOffset: 0)); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + + await insertUnderscore(editor); + + final allBold = textNode.allSatisfyBoldInSelection(Selection.single( + path: [0], startOffset: 0, endOffset: textNode.toPlainText().length)); + + expect(allBold, true); + expect(textNode.toPlainText(), 'AppFlowy'); + })); + + testWidgets('App__Flowy__ to bold AppFlowy', ((widgetTester) async { + const text = 'App__Flowy_'; + final editor = widgetTester.editor..insertTextNode(''); + + await editor.startTesting(); + await editor.updateSelection(Selection.single(path: [0], startOffset: 0)); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + + await insertUnderscore(editor); + + final allBold = textNode.allSatisfyBoldInSelection(Selection.single( + path: [0], startOffset: 3, endOffset: textNode.toPlainText().length)); + + expect(allBold, true); + expect(textNode.toPlainText(), 'AppFlowy'); + })); + + testWidgets('__*AppFlowy__ to bold *AppFlowy', ((widgetTester) async { + const text = '__*AppFlowy_'; + final editor = widgetTester.editor..insertTextNode(''); + + await editor.startTesting(); + await editor.updateSelection(Selection.single(path: [0], startOffset: 0)); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + + await insertUnderscore(editor); + + final allBold = textNode.allSatisfyBoldInSelection(Selection.single( + path: [0], startOffset: 1, endOffset: textNode.toPlainText().length)); + + expect(allBold, true); + expect(textNode.toPlainText(), '*AppFlowy'); + })); + + testWidgets('____ nothing changes', ((widgetTester) async { + const text = '___'; + final editor = widgetTester.editor..insertTextNode(''); + + await editor.startTesting(); + await editor.updateSelection(Selection.single(path: [0], startOffset: 0)); + final textNode = editor.nodeAtPath([0]) as TextNode; + for (var i = 0; i < text.length; i++) { + await editor.insertText(textNode, text[i], i); + } + + await insertUnderscore(editor); + + final allBold = textNode.allSatisfyBoldInSelection(Selection.single( + path: [0], startOffset: 0, endOffset: textNode.toPlainText().length)); + + expect(allBold, false); + expect(textNode.toPlainText(), text); + })); + }); } diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart b/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart index c00036ba11..a6e08d5fa8 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart @@ -29,7 +29,7 @@ void main() async { ); for (final item in defaultSelectionMenuItems) { - expect(find.text(item.name()), findsOneWidget); + expect(find.text(item.name), findsOneWidget); } await editor.updateSelection(Selection.single(path: [1], startOffset: 0)); diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/test/service/toolbar_service_test.dart b/frontend/appflowy_flutter/packages/appflowy_editor/test/service/toolbar_service_test.dart index 6c2ab09157..86cd29705d 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/test/service/toolbar_service_test.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/test/service/toolbar_service_test.dart @@ -24,6 +24,7 @@ void main() async { ); await editor.updateSelection(selection); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); // no link item @@ -72,6 +73,7 @@ void main() async { await editor.updateSelection( Selection.single(path: [0], startOffset: 0, endOffset: text.length), ); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); void testHighlight(bool expectedValue) { @@ -138,6 +140,7 @@ void main() async { await editor.updateSelection( Selection.single(path: [0], startOffset: 0, endOffset: text.length), ); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); var itemWidget = _itemWidgetForId(tester, 'appflowy.toolbar.h1'); expect(itemWidget.isHighlight, true); @@ -145,6 +148,7 @@ void main() async { await editor.updateSelection( Selection.single(path: [1], startOffset: 0, endOffset: text.length), ); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); itemWidget = _itemWidgetForId(tester, 'appflowy.toolbar.quote'); expect(itemWidget.isHighlight, true); @@ -152,6 +156,7 @@ void main() async { await editor.updateSelection( Selection.single(path: [2], startOffset: 0, endOffset: text.length), ); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); itemWidget = _itemWidgetForId(tester, 'appflowy.toolbar.bulleted_list'); expect(itemWidget.isHighlight, true); @@ -183,6 +188,7 @@ void main() async { await editor.updateSelection( Selection.single(path: [2], startOffset: text.length, endOffset: 0), ); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); expect( _itemWidgetForId(tester, 'appflowy.toolbar.h1').isHighlight, @@ -199,6 +205,7 @@ void main() async { end: Position(path: [1], offset: 0), ), ); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); expect( _itemWidgetForId(tester, 'appflowy.toolbar.bold').isHighlight, @@ -211,6 +218,7 @@ void main() async { end: Position(path: [0], offset: 0), ), ); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); expect(find.byType(ToolbarWidget), findsOneWidget); expect( _itemWidgetForId(tester, 'appflowy.toolbar.bold').isHighlight, diff --git a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/callout/callout_node_widget.dart b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/callout/callout_node_widget.dart index 13335688dc..e3e75bed68 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/callout/callout_node_widget.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/callout/callout_node_widget.dart @@ -4,8 +4,6 @@ import 'package:appflowy_editor_plugins/src/extensions/theme_extension.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/color_picker.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/code_block/code_block_node_widget.dart b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/code_block/code_block_node_widget.dart index eec5c8ae3d..2d039e67ba 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/code_block/code_block_node_widget.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/code_block/code_block_node_widget.dart @@ -102,6 +102,8 @@ class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge> key: _richTextKey, textNode: widget.textNode, editorState: widget.editorState, + lineHeight: 1.0, + cursorHeight: 15.0, textSpanDecorator: (textSpan) => TextSpan( style: widget.editorState.editorStyle.textStyle, children: codeTextSpan, diff --git a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/code_block/code_block_shortcut_event.dart b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/code_block/code_block_shortcut_event.dart index d23d22a78d..d883c1a632 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/code_block/code_block_shortcut_event.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/code_block/code_block_shortcut_event.dart @@ -79,7 +79,7 @@ ShortcutEventHandler _pasteHandler = (editorState, event) { }; SelectionMenuItem codeBlockMenuItem = SelectionMenuItem( - name: () => 'Code Block', + name: 'Code Block', icon: (editorState, onSelected) => Icon( Icons.abc, color: onSelected diff --git a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/divider/divider_shortcut_event.dart b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/divider/divider_shortcut_event.dart index 2d4ad39bf1..96baec98b5 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/divider/divider_shortcut_event.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/divider/divider_shortcut_event.dart @@ -34,7 +34,7 @@ ShortcutEventHandler _insertDividerHandler = (editorState, event) { }; SelectionMenuItem dividerMenuItem = SelectionMenuItem( - name: () => 'Divider', + name: 'Divider', icon: (editorState, onSelected) => Icon( Icons.horizontal_rule, color: onSelected diff --git a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/emoji_picker/emoji_menu_item.dart b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/emoji_picker/emoji_menu_item.dart index 810d497541..34720d16f4 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/emoji_picker/emoji_menu_item.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/emoji_picker/emoji_menu_item.dart @@ -5,7 +5,7 @@ import 'package:flutter/services.dart'; import 'emoji_picker.dart'; SelectionMenuItem emojiMenuItem = SelectionMenuItem( - name: () => 'Emoji', + name: 'Emoji', icon: (editorState, onSelected) => Icon( Icons.emoji_emotions_outlined, color: onSelected diff --git a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/math_ equation/math_equation_node_widget.dart b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/math_ equation/math_equation_node_widget.dart index 5485baa6c2..667ae2374a 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/math_ equation/math_equation_node_widget.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor_plugins/lib/src/math_ equation/math_equation_node_widget.dart @@ -7,7 +7,7 @@ const String kMathEquationType = 'math_equation'; const String kMathEquationAttr = 'math_equation'; SelectionMenuItem mathEquationMenuItem = SelectionMenuItem( - name: () => 'Math Equation', + name: 'Math Equation', icon: (editorState, onSelected) => Icon( Icons.text_fields_rounded, color: onSelected diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/flowy_infra_ui.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/flowy_infra_ui.dart index 3cf675207b..adb43b4837 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/flowy_infra_ui.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/flowy_infra_ui.dart @@ -12,3 +12,11 @@ export 'src/flowy_overlay/flowy_dialog.dart'; export 'src/flowy_overlay/appflowy_popover.dart'; export 'style_widget/text.dart'; export 'style_widget/text_field.dart'; + +export 'style_widget/button.dart'; +export 'style_widget/icon_button.dart'; +export 'style_widget/scrolling/styled_scroll_bar.dart'; +export '/widget/spacing.dart'; +export 'style_widget/scrolling/styled_list.dart'; +export 'style_widget/button.dart'; +export 'style_widget/color_picker.dart'; diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/color_picker.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/color_picker.dart index 5ad7b9632a..246c45c7ae 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/color_picker.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/color_picker.dart @@ -1,8 +1,5 @@ import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.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/widget/spacing.dart'; import 'package:flutter/material.dart'; class ColorOption { diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_multi_select_field_test.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_multi_select_field_test.dart index af8a06d06d..0a97a1ec2b 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_multi_select_field_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_multi_select_field_test.dart @@ -1,7 +1,7 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database_view/application/setting/group_bloc.dart'; import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/application/cell/select_option_editor_bloc.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart index 885c9bd6f4..64d7fd785d 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart @@ -6,10 +6,11 @@ import 'package:appflowy/plugins/database_view/application/field/field_service.d import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart'; -import 'package:appflowy/plugins/database_view/board/application/board_data_controller.dart'; 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-database/setting_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; @@ -38,9 +39,14 @@ class AppFlowyBoardTest { .then((result) { return result.fold( (view) async { - final context = - BoardTestContext(view, BoardDataController(view: view)); - final result = await context._boardDataController.openGrid(); + final context = BoardTestContext( + view, + DatabaseController( + view: view, + layoutType: LayoutTypePB.Board, + ), + ); + final result = await context._boardDataController.open(); result.fold((l) => null, (r) => throw Exception(r)); return context; }, @@ -62,7 +68,7 @@ Duration boardResponseDuration({int milliseconds = 200}) { class BoardTestContext { final ViewPB gridView; - final BoardDataController _boardDataController; + final DatabaseController _boardDataController; BoardTestContext(this.gridView, this._boardDataController); @@ -107,8 +113,9 @@ class BoardTestContext { final RowInfo rowInfo = rowInfos.last; final rowCache = _boardDataController.rowCache; - final rowDataController = RowDataController( - rowInfo: rowInfo, + final rowDataController = RowController( + viewId: rowInfo.viewId, + rowId: rowInfo.rowPB.id, rowCache: rowCache, ); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/select_option_cell_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/select_option_cell_test.dart index 261734207d..d4ec928c70 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/select_option_cell_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/cell/select_option_cell_test.dart @@ -1,4 +1,4 @@ -import 'package:appflowy/plugins/database_view/grid/application/cell/select_option_editor_bloc.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor_bloc.dart'; import 'package:dartz/dartz.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart index 4b66bec059..f6342904c2 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart @@ -1,7 +1,8 @@ import 'package:appflowy/plugins/database_view/application/filter/filter_service.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/application/grid_data_controller.dart'; +import 'package:appflowy/plugins/database_view/application/database_controller.dart'; import 'package:appflowy_backend/protobuf/flowy-database/checkbox_filter.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database/text_filter.pb.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -50,7 +51,10 @@ void main() { test('filter rows with condition: text is empty', () async { final context = await gridTest.createTestGrid(); final service = FilterBackendService(viewId: context.gridView.id); - final gridController = DatabaseController(view: context.gridView); + final gridController = DatabaseController( + view: context.gridView, + layoutType: LayoutTypePB.Grid, + ); final gridBloc = GridBloc( view: context.gridView, databaseController: gridController, @@ -71,7 +75,10 @@ void main() { () async { final context = await gridTest.createTestGrid(); final service = FilterBackendService(viewId: context.gridView.id); - final gridController = DatabaseController(view: context.gridView); + final gridController = DatabaseController( + view: context.gridView, + layoutType: LayoutTypePB.Grid, + ); final gridBloc = GridBloc( view: context.gridView, databaseController: gridController, @@ -112,7 +119,10 @@ void main() { final context = await gridTest.createTestGrid(); final checkboxField = context.checkboxFieldContext(); final service = FilterBackendService(viewId: context.gridView.id); - final gridController = DatabaseController(view: context.gridView); + final gridController = DatabaseController( + view: context.gridView, + layoutType: LayoutTypePB.Grid, + ); final gridBloc = GridBloc( view: context.gridView, databaseController: gridController, @@ -131,7 +141,10 @@ void main() { final context = await gridTest.createTestGrid(); final checkboxField = context.checkboxFieldContext(); final service = FilterBackendService(viewId: context.gridView.id); - final gridController = DatabaseController(view: context.gridView); + final gridController = DatabaseController( + view: context.gridView, + layoutType: LayoutTypePB.Grid, + ); final gridBloc = GridBloc( view: context.gridView, databaseController: gridController, diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart index fe955226e3..92a1da6e8f 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart @@ -1,6 +1,7 @@ -import 'package:appflowy/plugins/database_view/grid/application/grid_data_controller.dart'; +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-database/setting_entities.pbenum.dart'; import '../util.dart'; @@ -16,8 +17,13 @@ Future createTestFilterGrid(AppFlowyGridTest gridTest) async { .then((result) { return result.fold( (view) async { - final context = GridTestContext(view, DatabaseController(view: view)); - final result = await context.gridController.openGrid(); + final context = GridTestContext( + view, + DatabaseController( + view: view, + layoutType: LayoutTypePB.Grid, + )); + final result = await context.gridController.open(); await editCells(context); await gridResponseFuture(milliseconds: 500); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart index 5065406a99..a2ccbbd164 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart @@ -1,5 +1,6 @@ import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/application/grid_data_controller.dart'; +import 'package:appflowy/plugins/database_view/application/database_controller.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pbenum.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:bloc_test/bloc_test.dart'; import 'util.dart'; @@ -20,7 +21,10 @@ void main() { "create a row", build: () => GridBloc( view: context.gridView, - databaseController: DatabaseController(view: context.gridView)) + databaseController: DatabaseController( + view: context.gridView, + layoutType: LayoutTypePB.Grid, + )) ..add(const GridEvent.initial()), act: (bloc) => bloc.add(const GridEvent.createRow()), wait: const Duration(milliseconds: 300), @@ -33,7 +37,10 @@ void main() { "delete the last row", build: () => GridBloc( view: context.gridView, - databaseController: DatabaseController(view: context.gridView)) + databaseController: DatabaseController( + view: context.gridView, + layoutType: LayoutTypePB.Grid, + )) ..add(const GridEvent.initial()), act: (bloc) async { await gridResponseFuture(); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart index e81c233155..fe1ec655a9 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart @@ -6,12 +6,16 @@ import 'package:appflowy/plugins/database_view/application/field/field_service.d import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart'; -import 'package:appflowy/plugins/database_view/grid/application/grid_data_controller.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/plugins/database_view/grid/grid.dart'; import 'package:appflowy/workspace/application/app/app_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database/setting_entities.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; +import 'package:dartz/dartz.dart'; import '../../util.dart'; @@ -31,7 +35,7 @@ class GridTestContext { return gridController.fieldController; } - Future createRow() async { + Future> createRow() async { return gridController.createRow(); } @@ -68,8 +72,9 @@ class GridTestContext { final RowInfo rowInfo = rowInfos[rowIndex]; final rowCache = gridController.rowCache; - final rowDataController = RowDataController( - rowInfo: rowInfo, + final rowDataController = RowController( + rowId: rowInfo.rowPB.id, + viewId: rowInfo.viewId, rowCache: rowCache, ); @@ -169,8 +174,13 @@ class AppFlowyGridTest { .then((result) { return result.fold( (view) async { - final context = GridTestContext(view, DatabaseController(view: view)); - final result = await context.gridController.openGrid(); + final context = GridTestContext( + view, + DatabaseController( + view: view, + layoutType: LayoutTypePB.Grid, + )); + final result = await context.gridController.open(); result.fold((l) => null, (r) => throw Exception(r)); return context; }, diff --git a/frontend/appflowy_flutter/test/unit_test/select_option_split_text_input.dart b/frontend/appflowy_flutter/test/unit_test/select_option_split_text_input.dart index 75d5053112..9ddad505dc 100644 --- a/frontend/appflowy_flutter/test/unit_test/select_option_split_text_input.dart +++ b/frontend/appflowy_flutter/test/unit_test/select_option_split_text_input.dart @@ -1,4 +1,4 @@ -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/text_field.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/frontend/appflowy_flutter/test/widget_test/select_option_text_field_test.dart b/frontend/appflowy_flutter/test/widget_test/select_option_text_field_test.dart index 76cae8d2d2..0ae2ac2f3f 100644 --- a/frontend/appflowy_flutter/test/widget_test/select_option_text_field_test.dart +++ b/frontend/appflowy_flutter/test/widget_test/select_option_text_field_test.dart @@ -1,6 +1,6 @@ import 'dart:collection'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/cell/select_option_cell/text_field.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart'; import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/frontend/appflowy_tauri/jest.config.cjs b/frontend/appflowy_tauri/jest.config.cjs deleted file mode 100644 index b5ea02d82e..0000000000 --- a/frontend/appflowy_tauri/jest.config.cjs +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - globals: { - window: {}, - }, -}; diff --git a/frontend/appflowy_tauri/package.json b/frontend/appflowy_tauri/package.json index 80a652f162..8ad6f3a410 100644 --- a/frontend/appflowy_tauri/package.json +++ b/frontend/appflowy_tauri/package.json @@ -12,32 +12,39 @@ "test:errors": "tsc --noEmit", "test:prettier": "yarn prettier --list-different src", "tauri:clean": "cargo make --cwd .. tauri_clean", - "tauri:dev": "tauri dev", - "test": "jest" + "tauri:dev": "tauri dev" }, "dependencies": { + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6", + "@mui/icons-material": "^5.11.11", + "@mui/material": "^5.11.12", "@reduxjs/toolkit": "^1.9.2", "@tauri-apps/api": "^1.2.0", "google-protobuf": "^3.21.2", "i18next": "^22.4.10", "i18next-browser-languagedetector": "^7.0.1", - "jest": "^29.4.3", + "is-hotkey": "^0.2.0", + "jest": "^29.5.0", "nanoid": "^4.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^3.1.4", "react-i18next": "^12.2.0", "react-redux": "^8.0.5", "react-router-dom": "^6.8.0", "react18-input-otp": "^1.1.2", "redux": "^4.2.1", "rxjs": "^7.8.0", + "slate": "^0.91.4", + "slate-react": "^0.91.9", "ts-results": "^3.3.0", "utf8": "^3.0.0" }, "devDependencies": { "@tauri-apps/cli": "^1.2.2", "@types/google-protobuf": "^3.15.6", - "@types/jest": "^29.4.0", + "@types/is-hotkey": "^0.1.7", "@types/node": "^18.7.10", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", @@ -52,7 +59,6 @@ "prettier": "2.8.4", "prettier-plugin-tailwindcss": "^0.2.2", "tailwindcss": "^3.2.7", - "ts-jest": "^29.0.5", "typescript": "^4.6.4", "vite": "^4.0.0" } diff --git a/frontend/appflowy_tauri/pnpm-lock.yaml b/frontend/appflowy_tauri/pnpm-lock.yaml new file mode 100644 index 0000000000..a6c00ac119 --- /dev/null +++ b/frontend/appflowy_tauri/pnpm-lock.yaml @@ -0,0 +1,4740 @@ +lockfileVersion: 5.4 + +specifiers: + '@emotion/react': ^11.10.6 + '@emotion/styled': ^11.10.6 + '@mui/icons-material': ^5.11.11 + '@mui/material': ^5.11.12 + '@reduxjs/toolkit': ^1.9.2 + '@tauri-apps/api': ^1.2.0 + '@tauri-apps/cli': ^1.2.2 + '@types/google-protobuf': ^3.15.6 + '@types/is-hotkey': ^0.1.7 + '@types/jest': ^29.4.0 + '@types/mocha': ^10.0.1 + '@types/node': ^18.7.10 + '@types/react': ^18.0.15 + '@types/react-dom': ^18.0.6 + '@types/utf8': ^3.0.1 + '@typescript-eslint/eslint-plugin': ^5.51.0 + '@typescript-eslint/parser': ^5.51.0 + '@vitejs/plugin-react': ^3.0.0 + autoprefixer: ^10.4.13 + eslint: ^8.34.0 + eslint-plugin-react: ^7.32.2 + google-protobuf: ^3.21.2 + i18next: ^22.4.10 + i18next-browser-languagedetector: ^7.0.1 + is-hotkey: ^0.2.0 + jest: ^29.4.3 + nanoid: ^4.0.0 + postcss: ^8.4.21 + prettier: 2.8.4 + prettier-plugin-tailwindcss: ^0.2.2 + react: ^18.2.0 + react-dom: ^18.2.0 + react-error-boundary: ^3.1.4 + react-i18next: ^12.2.0 + react-redux: ^8.0.5 + react-router-dom: ^6.8.0 + react18-input-otp: ^1.1.2 + redux: ^4.2.1 + rxjs: ^7.8.0 + slate: ^0.91.4 + slate-react: ^0.91.9 + tailwindcss: ^3.2.7 + ts-jest: ^29.0.5 + ts-results: ^3.3.0 + typescript: ^4.6.4 + utf8: ^3.0.0 + vite: ^4.0.0 + +dependencies: + '@emotion/react': 11.10.6_pmekkgnqduwlme35zpnqhenc34 + '@emotion/styled': 11.10.6_oouaibmszuch5k64ms7uxp2aia + '@mui/icons-material': 5.11.11_ao76n7r2cajsoyr3cbwrn7geoi + '@mui/material': 5.11.12_xqeqsl5kvjjtyxwyi3jhw3yuli + '@reduxjs/toolkit': 1.9.3_k4ae6lp43ej6mezo3ztvx6pykq + '@tauri-apps/api': 1.2.0 + google-protobuf: 3.21.2 + i18next: 22.4.10 + i18next-browser-languagedetector: 7.0.1 + is-hotkey: 0.2.0 + jest: 29.4.3_@types+node@18.14.6 + nanoid: 4.0.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-error-boundary: 3.1.4_react@18.2.0 + react-i18next: 12.2.0_3yopsigl4h4eb2nqrqfsy65uwi + react-redux: 8.0.5_ctrls2ti7t7iutxbwkm5ipogyy + react-router-dom: 6.8.2_biqbaboplfbrettd7655fr4n2y + react18-input-otp: 1.1.2_biqbaboplfbrettd7655fr4n2y + redux: 4.2.1 + rxjs: 7.8.0 + slate: 0.91.4 + slate-react: 0.91.9_6tgy34rvmll7duwkm4ydcekf3u + ts-results: 3.3.0 + utf8: 3.0.0 + +devDependencies: + '@tauri-apps/cli': 1.2.3 + '@types/google-protobuf': 3.15.6 + '@types/is-hotkey': 0.1.7 + '@types/jest': 29.4.0 + '@types/mocha': 10.0.1 + '@types/node': 18.14.6 + '@types/react': 18.0.28 + '@types/react-dom': 18.0.11 + '@types/utf8': 3.0.1 + '@typescript-eslint/eslint-plugin': 5.54.0_6mj2wypvdnknez7kws2nfdgupi + '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + '@vitejs/plugin-react': 3.1.0_vite@4.1.4 + autoprefixer: 10.4.13_postcss@8.4.21 + eslint: 8.35.0 + eslint-plugin-react: 7.32.2_eslint@8.35.0 + postcss: 8.4.21 + prettier: 2.8.4 + prettier-plugin-tailwindcss: 0.2.4_prettier@2.8.4 + tailwindcss: 3.2.7_postcss@8.4.21 + ts-jest: 29.0.5_orzzknleilowtsz34rkaotjvzm + typescript: 4.9.5 + vite: 4.1.4_@types+node@18.14.6 + +packages: + + /@ampproject/remapping/2.2.0: + resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.1.1 + '@jridgewell/trace-mapping': 0.3.17 + + /@babel/code-frame/7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.18.6 + + /@babel/compat-data/7.21.0: + resolution: {integrity: sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==} + engines: {node: '>=6.9.0'} + + /@babel/core/7.21.0: + resolution: {integrity: sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.0 + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.21.1 + '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.21.0 + '@babel/helper-module-transforms': 7.21.2 + '@babel/helpers': 7.21.0 + '@babel/parser': 7.21.2 + '@babel/template': 7.20.7 + '@babel/traverse': 7.21.2 + '@babel/types': 7.21.2 + convert-source-map: 1.9.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + + /@babel/generator/7.21.1: + resolution: {integrity: sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.21.2 + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.17 + jsesc: 2.5.2 + + /@babel/helper-compilation-targets/7.20.7_@babel+core@7.21.0: + resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/compat-data': 7.21.0 + '@babel/core': 7.21.0 + '@babel/helper-validator-option': 7.21.0 + browserslist: 4.21.5 + lru-cache: 5.1.1 + semver: 6.3.0 + + /@babel/helper-environment-visitor/7.18.9: + resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} + engines: {node: '>=6.9.0'} + + /@babel/helper-function-name/7.21.0: + resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.20.7 + '@babel/types': 7.21.2 + + /@babel/helper-hoist-variables/7.18.6: + resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.21.2 + + /@babel/helper-module-imports/7.18.6: + resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.21.2 + + /@babel/helper-module-transforms/7.21.2: + resolution: {integrity: sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-simple-access': 7.20.2 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-validator-identifier': 7.19.1 + '@babel/template': 7.20.7 + '@babel/traverse': 7.21.2 + '@babel/types': 7.21.2 + transitivePeerDependencies: + - supports-color + + /@babel/helper-plugin-utils/7.20.2: + resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==} + engines: {node: '>=6.9.0'} + + /@babel/helper-simple-access/7.20.2: + resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.21.2 + + /@babel/helper-split-export-declaration/7.18.6: + resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.21.2 + + /@babel/helper-string-parser/7.19.4: + resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-identifier/7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-option/7.21.0: + resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} + engines: {node: '>=6.9.0'} + + /@babel/helpers/7.21.0: + resolution: {integrity: sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.20.7 + '@babel/traverse': 7.21.2 + '@babel/types': 7.21.2 + transitivePeerDependencies: + - supports-color + + /@babel/highlight/7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + chalk: 2.4.2 + js-tokens: 4.0.0 + + /@babel/parser/7.21.2: + resolution: {integrity: sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.21.2 + + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.21.0: + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-bigint/7.8.3_@babel+core@7.21.0: + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.21.0: + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.21.0: + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.21.0: + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.21.0: + resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.21.0: + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.21.0: + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.21.0: + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.21.0: + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.21.0: + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.21.0: + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.21.0: + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-syntax-typescript/7.20.0_@babel+core@7.21.0: + resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + + /@babel/plugin-transform-react-jsx-self/7.21.0_@babel+core@7.21.0: + resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-react-jsx-source/7.19.6_@babel+core@7.21.0: + resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.21.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/runtime/7.21.0: + resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + dev: false + + /@babel/template/7.20.7: + resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/parser': 7.21.2 + '@babel/types': 7.21.2 + + /@babel/traverse/7.21.2: + resolution: {integrity: sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.21.1 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.21.0 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.21.2 + '@babel/types': 7.21.2 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + /@babel/types/7.21.2: + resolution: {integrity: sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.19.4 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + + /@bcoe/v8-coverage/0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + /@emotion/babel-plugin/11.10.6: + resolution: {integrity: sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==} + dependencies: + '@babel/helper-module-imports': 7.18.6 + '@babel/runtime': 7.21.0 + '@emotion/hash': 0.9.0 + '@emotion/memoize': 0.8.0 + '@emotion/serialize': 1.1.1 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.1.3 + dev: false + + /@emotion/cache/11.10.5: + resolution: {integrity: sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==} + dependencies: + '@emotion/memoize': 0.8.0 + '@emotion/sheet': 1.2.1 + '@emotion/utils': 1.2.0 + '@emotion/weak-memoize': 0.3.0 + stylis: 4.1.3 + dev: false + + /@emotion/hash/0.9.0: + resolution: {integrity: sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==} + dev: false + + /@emotion/is-prop-valid/1.2.0: + resolution: {integrity: sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==} + dependencies: + '@emotion/memoize': 0.8.0 + dev: false + + /@emotion/memoize/0.8.0: + resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==} + dev: false + + /@emotion/react/11.10.6_pmekkgnqduwlme35zpnqhenc34: + resolution: {integrity: sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/babel-plugin': 11.10.6 + '@emotion/cache': 11.10.5 + '@emotion/serialize': 1.1.1 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.0_react@18.2.0 + '@emotion/utils': 1.2.0 + '@emotion/weak-memoize': 0.3.0 + '@types/react': 18.0.28 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + dev: false + + /@emotion/serialize/1.1.1: + resolution: {integrity: sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==} + dependencies: + '@emotion/hash': 0.9.0 + '@emotion/memoize': 0.8.0 + '@emotion/unitless': 0.8.0 + '@emotion/utils': 1.2.0 + csstype: 3.1.1 + dev: false + + /@emotion/sheet/1.2.1: + resolution: {integrity: sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==} + dev: false + + /@emotion/styled/11.10.6_oouaibmszuch5k64ms7uxp2aia: + resolution: {integrity: sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/babel-plugin': 11.10.6 + '@emotion/is-prop-valid': 1.2.0 + '@emotion/react': 11.10.6_pmekkgnqduwlme35zpnqhenc34 + '@emotion/serialize': 1.1.1 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.0_react@18.2.0 + '@emotion/utils': 1.2.0 + '@types/react': 18.0.28 + react: 18.2.0 + dev: false + + /@emotion/unitless/0.8.0: + resolution: {integrity: sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==} + dev: false + + /@emotion/use-insertion-effect-with-fallbacks/1.0.0_react@18.2.0: + resolution: {integrity: sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + + /@emotion/utils/1.2.0: + resolution: {integrity: sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==} + dev: false + + /@emotion/weak-memoize/0.3.0: + resolution: {integrity: sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==} + dev: false + + /@esbuild/android-arm/0.16.17: + resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64/0.16.17: + resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64/0.16.17: + resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64/0.16.17: + resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64/0.16.17: + resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64/0.16.17: + resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64/0.16.17: + resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm/0.16.17: + resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64/0.16.17: + resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32/0.16.17: + resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64/0.16.17: + resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el/0.16.17: + resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64/0.16.17: + resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64/0.16.17: + resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x/0.16.17: + resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64/0.16.17: + resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64/0.16.17: + resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64/0.16.17: + resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64/0.16.17: + resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64/0.16.17: + resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32/0.16.17: + resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64/0.16.17: + resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@eslint/eslintrc/2.0.0: + resolution: {integrity: sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.4.1 + globals: 13.20.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js/8.35.0: + resolution: {integrity: sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@humanwhocodes/config-array/0.11.8: + resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema/1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + + /@istanbuljs/load-nyc-config/1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + /@istanbuljs/schema/0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + /@jest/console/29.4.3: + resolution: {integrity: sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + chalk: 4.1.2 + jest-message-util: 29.4.3 + jest-util: 29.4.3 + slash: 3.0.0 + + /@jest/core/29.4.3: + resolution: {integrity: sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 29.4.3 + '@jest/reporters': 29.4.3 + '@jest/test-result': 29.4.3 + '@jest/transform': 29.4.3 + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.8.0 + exit: 0.1.2 + graceful-fs: 4.2.10 + jest-changed-files: 29.4.3 + jest-config: 29.4.3_@types+node@18.14.6 + jest-haste-map: 29.4.3 + jest-message-util: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.4.3 + jest-resolve-dependencies: 29.4.3 + jest-runner: 29.4.3 + jest-runtime: 29.4.3 + jest-snapshot: 29.4.3 + jest-util: 29.4.3 + jest-validate: 29.4.3 + jest-watcher: 29.4.3 + micromatch: 4.0.5 + pretty-format: 29.4.3 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - supports-color + - ts-node + + /@jest/environment/29.4.3: + resolution: {integrity: sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/fake-timers': 29.4.3 + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + jest-mock: 29.4.3 + + /@jest/expect-utils/29.4.3: + resolution: {integrity: sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.4.3 + + /@jest/expect/29.4.3: + resolution: {integrity: sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + expect: 29.4.3 + jest-snapshot: 29.4.3 + transitivePeerDependencies: + - supports-color + + /@jest/fake-timers/29.4.3: + resolution: {integrity: sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.4.3 + '@sinonjs/fake-timers': 10.0.2 + '@types/node': 18.14.6 + jest-message-util: 29.4.3 + jest-mock: 29.4.3 + jest-util: 29.4.3 + + /@jest/globals/29.4.3: + resolution: {integrity: sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.4.3 + '@jest/expect': 29.4.3 + '@jest/types': 29.4.3 + jest-mock: 29.4.3 + transitivePeerDependencies: + - supports-color + + /@jest/reporters/29.4.3: + resolution: {integrity: sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.4.3 + '@jest/test-result': 29.4.3 + '@jest/transform': 29.4.3 + '@jest/types': 29.4.3 + '@jridgewell/trace-mapping': 0.3.17 + '@types/node': 18.14.6 + chalk: 4.1.2 + collect-v8-coverage: 1.0.1 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.10 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-instrument: 5.2.1 + istanbul-lib-report: 3.0.0 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.5 + jest-message-util: 29.4.3 + jest-util: 29.4.3 + jest-worker: 29.4.3 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.1.0 + transitivePeerDependencies: + - supports-color + + /@jest/schemas/29.4.3: + resolution: {integrity: sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.25.24 + + /@jest/source-map/29.4.3: + resolution: {integrity: sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + callsites: 3.1.0 + graceful-fs: 4.2.10 + + /@jest/test-result/29.4.3: + resolution: {integrity: sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.4.3 + '@jest/types': 29.4.3 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.1 + + /@jest/test-sequencer/29.4.3: + resolution: {integrity: sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.4.3 + graceful-fs: 4.2.10 + jest-haste-map: 29.4.3 + slash: 3.0.0 + + /@jest/transform/29.4.3: + resolution: {integrity: sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.21.0 + '@jest/types': 29.4.3 + '@jridgewell/trace-mapping': 0.3.17 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.10 + jest-haste-map: 29.4.3 + jest-regex-util: 29.4.3 + jest-util: 29.4.3 + micromatch: 4.0.5 + pirates: 4.0.5 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + /@jest/types/29.4.3: + resolution: {integrity: sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.4.3 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.14.6 + '@types/yargs': 17.0.22 + chalk: 4.1.2 + + /@jridgewell/gen-mapping/0.1.1: + resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + + /@jridgewell/gen-mapping/0.3.2: + resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/trace-mapping': 0.3.17 + + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + + /@jridgewell/set-array/1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + + /@jridgewell/sourcemap-codec/1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + + /@jridgewell/trace-mapping/0.3.17: + resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + + /@juggle/resize-observer/3.4.0: + resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} + dev: false + + /@mui/base/5.0.0-alpha.119_zula6vjvt3wdocc4mwcxqa6nzi: + resolution: {integrity: sha512-XA5zhlYfXi67u613eIF0xRmktkatx6ERy3h+PwrMN5IcWFbgiL1guz8VpdXON+GWb8+G7B8t5oqTFIaCqaSAeA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/is-prop-valid': 1.2.0 + '@mui/types': 7.2.3_@types+react@18.0.28 + '@mui/utils': 5.11.12_react@18.2.0 + '@popperjs/core': 2.11.6 + '@types/react': 18.0.28 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-is: 18.2.0 + dev: false + + /@mui/core-downloads-tracker/5.11.12: + resolution: {integrity: sha512-LHh8HZQ5nPVcW5QnyLwkAZ40txc/S2bzKMQ3bTO+5mjuwAJ2AzQrjZINLVy1geY7ei1pHXVqO1hcWHg/QdT44w==} + dev: false + + /@mui/icons-material/5.11.11_ao76n7r2cajsoyr3cbwrn7geoi: + resolution: {integrity: sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@mui/material': ^5.0.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@mui/material': 5.11.12_xqeqsl5kvjjtyxwyi3jhw3yuli + '@types/react': 18.0.28 + react: 18.2.0 + dev: false + + /@mui/material/5.11.12_xqeqsl5kvjjtyxwyi3jhw3yuli: + resolution: {integrity: sha512-M6BiIeJjySeEzWeiFJQ9pIjJy6mx5mHPWeMT99wjQdAmA2GxCQhE9A0fh6jQP4jMmYzxhOIhjsGcp0vSdpseXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/react': 11.10.6_pmekkgnqduwlme35zpnqhenc34 + '@emotion/styled': 11.10.6_oouaibmszuch5k64ms7uxp2aia + '@mui/base': 5.0.0-alpha.119_zula6vjvt3wdocc4mwcxqa6nzi + '@mui/core-downloads-tracker': 5.11.12 + '@mui/system': 5.11.12_d2lgyfpecxdc2bsiwyag5wf7ti + '@mui/types': 7.2.3_@types+react@18.0.28 + '@mui/utils': 5.11.12_react@18.2.0 + '@types/react': 18.0.28 + '@types/react-transition-group': 4.4.5 + clsx: 1.2.1 + csstype: 3.1.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-is: 18.2.0 + react-transition-group: 4.4.5_biqbaboplfbrettd7655fr4n2y + dev: false + + /@mui/private-theming/5.11.12_pmekkgnqduwlme35zpnqhenc34: + resolution: {integrity: sha512-hnJ0svNI1TPeWZ18E6DvES8PB4NyMLwal6EyXf69rTrYqT6wZPLjB+HiCYfSOCqU/fwArhupSqIIkQpDs8CkAw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@mui/utils': 5.11.12_react@18.2.0 + '@types/react': 18.0.28 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/styled-engine/5.11.11_xqp3pgpqjlfxxa3zxu4zoc4fba: + resolution: {integrity: sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/cache': 11.10.5 + '@emotion/react': 11.10.6_pmekkgnqduwlme35zpnqhenc34 + '@emotion/styled': 11.10.6_oouaibmszuch5k64ms7uxp2aia + csstype: 3.1.1 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/system/5.11.12_d2lgyfpecxdc2bsiwyag5wf7ti: + resolution: {integrity: sha512-sYjsXkiwKpZDC3aS6O/6KTjji0jGINLQcrD5EJ5NTkIDiLf19I4HJhnufgKqlTWNfoDBlRohuTf3TzfM06c4ug==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/react': 11.10.6_pmekkgnqduwlme35zpnqhenc34 + '@emotion/styled': 11.10.6_oouaibmszuch5k64ms7uxp2aia + '@mui/private-theming': 5.11.12_pmekkgnqduwlme35zpnqhenc34 + '@mui/styled-engine': 5.11.11_xqp3pgpqjlfxxa3zxu4zoc4fba + '@mui/types': 7.2.3_@types+react@18.0.28 + '@mui/utils': 5.11.12_react@18.2.0 + '@types/react': 18.0.28 + clsx: 1.2.1 + csstype: 3.1.1 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/types/7.2.3_@types+react@18.0.28: + resolution: {integrity: sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==} + peerDependencies: + '@types/react': '*' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.28 + dev: false + + /@mui/utils/5.11.12_react@18.2.0: + resolution: {integrity: sha512-5vH9B/v8pzkpEPO2HvGM54ToXV6cFdAn8UrvdN8TMEEwpn/ycW0jLiyBcgUlPsQ+xha7hqXCPQYHaYFDIcwaiw==} + engines: {node: '>=12.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 + dependencies: + '@babel/runtime': 7.21.0 + '@types/prop-types': 15.7.5 + '@types/react-is': 17.0.3 + prop-types: 15.8.1 + react: 18.2.0 + react-is: 18.2.0 + dev: false + + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@popperjs/core/2.11.6: + resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} + dev: false + + /@reduxjs/toolkit/1.9.3_k4ae6lp43ej6mezo3ztvx6pykq: + resolution: {integrity: sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 + react-redux: ^7.2.1 || ^8.0.2 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + dependencies: + immer: 9.0.19 + react: 18.2.0 + react-redux: 8.0.5_ctrls2ti7t7iutxbwkm5ipogyy + redux: 4.2.1 + redux-thunk: 2.4.2_redux@4.2.1 + reselect: 4.1.7 + dev: false + + /@remix-run/router/1.3.3: + resolution: {integrity: sha512-YRHie1yQEj0kqqCTCJEfHqYSSNlZQ696QJG+MMiW4mxSl9I0ojz/eRhJS4fs88Z5i6D1SmoF9d3K99/QOhI8/w==} + engines: {node: '>=14'} + dev: false + + /@sinclair/typebox/0.25.24: + resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} + + /@sinonjs/commons/2.0.0: + resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} + dependencies: + type-detect: 4.0.8 + + /@sinonjs/fake-timers/10.0.2: + resolution: {integrity: sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==} + dependencies: + '@sinonjs/commons': 2.0.0 + + /@tauri-apps/api/1.2.0: + resolution: {integrity: sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==} + engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} + dev: false + + /@tauri-apps/cli-darwin-arm64/1.2.3: + resolution: {integrity: sha512-phJN3fN8FtZZwqXg08bcxfq1+X1JSDglLvRxOxB7VWPq+O5SuB8uLyssjJsu+PIhyZZnIhTGdjhzLSFhSXfLsw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-darwin-x64/1.2.3: + resolution: {integrity: sha512-jFZ/y6z8z6v4yliIbXKBXA7BJgtZVMsITmEXSuD6s5+eCOpDhQxbRkr6CA+FFfr+/r96rWSDSgDenDQuSvPAKw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-linux-arm-gnueabihf/1.2.3: + resolution: {integrity: sha512-C7h5vqAwXzY0kRGSU00Fj8PudiDWFCiQqqUNI1N+fhCILrzWZB9TPBwdx33ZfXKt/U4+emdIoo/N34v3TiAOmQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-linux-arm64-gnu/1.2.3: + resolution: {integrity: sha512-buf1c8sdkuUzVDkGPQpyUdAIIdn5r0UgXU6+H5fGPq/Xzt5K69JzXaeo6fHsZEZghbV0hOK+taKV4J0m30UUMQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-linux-arm64-musl/1.2.3: + resolution: {integrity: sha512-x88wPS9W5xAyk392vc4uNHcKBBvCp0wf4H9JFMF9OBwB7vfd59LbQCFcPSu8f0BI7bPrOsyHqspWHuFL8ojQEA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-linux-x64-gnu/1.2.3: + resolution: {integrity: sha512-ZMz1jxEVe0B4/7NJnlPHmwmSIuwiD6ViXKs8F+OWWz2Y4jn5TGxWKFg7DLx5OwQTRvEIZxxT7lXHi5CuTNAxKg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-linux-x64-musl/1.2.3: + resolution: {integrity: sha512-B/az59EjJhdbZDzawEVox0LQu2ZHCZlk8rJf85AMIktIUoAZPFbwyiUv7/zjzA/sY6Nb58OSJgaPL2/IBy7E0A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-win32-ia32-msvc/1.2.3: + resolution: {integrity: sha512-ypdO1OdC5ugNJAKO2m3sb1nsd+0TSvMS9Tr5qN/ZSMvtSduaNwrcZ3D7G/iOIanrqu/Nl8t3LYlgPZGBKlw7Ng==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-win32-x64-msvc/1.2.3: + resolution: {integrity: sha512-CsbHQ+XhnV/2csOBBDVfH16cdK00gNyNYUW68isedmqcn8j+s0e9cQ1xXIqi+Hue3awp8g3ImYN5KPepf3UExw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli/1.2.3: + resolution: {integrity: sha512-erxtXuPhMEGJPBtnhPILD4AjuT81GZsraqpFvXAmEJZ2p8P6t7MVBifCL8LznRknznM3jn90D3M8RNBP3wcXTw==} + engines: {node: '>= 10'} + hasBin: true + optionalDependencies: + '@tauri-apps/cli-darwin-arm64': 1.2.3 + '@tauri-apps/cli-darwin-x64': 1.2.3 + '@tauri-apps/cli-linux-arm-gnueabihf': 1.2.3 + '@tauri-apps/cli-linux-arm64-gnu': 1.2.3 + '@tauri-apps/cli-linux-arm64-musl': 1.2.3 + '@tauri-apps/cli-linux-x64-gnu': 1.2.3 + '@tauri-apps/cli-linux-x64-musl': 1.2.3 + '@tauri-apps/cli-win32-ia32-msvc': 1.2.3 + '@tauri-apps/cli-win32-x64-msvc': 1.2.3 + dev: true + + /@types/babel__core/7.20.0: + resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==} + dependencies: + '@babel/parser': 7.21.2 + '@babel/types': 7.21.2 + '@types/babel__generator': 7.6.4 + '@types/babel__template': 7.4.1 + '@types/babel__traverse': 7.18.3 + + /@types/babel__generator/7.6.4: + resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} + dependencies: + '@babel/types': 7.21.2 + + /@types/babel__template/7.4.1: + resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} + dependencies: + '@babel/parser': 7.21.2 + '@babel/types': 7.21.2 + + /@types/babel__traverse/7.18.3: + resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==} + dependencies: + '@babel/types': 7.21.2 + + /@types/google-protobuf/3.15.6: + resolution: {integrity: sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==} + dev: true + + /@types/graceful-fs/4.1.6: + resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} + dependencies: + '@types/node': 18.14.6 + + /@types/hoist-non-react-statics/3.3.1: + resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==} + dependencies: + '@types/react': 18.0.28 + hoist-non-react-statics: 3.3.2 + dev: false + + /@types/is-hotkey/0.1.7: + resolution: {integrity: sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ==} + + /@types/istanbul-lib-coverage/2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + + /@types/istanbul-lib-report/3.0.0: + resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + + /@types/istanbul-reports/3.0.1: + resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + dependencies: + '@types/istanbul-lib-report': 3.0.0 + + /@types/jest/29.4.0: + resolution: {integrity: sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==} + dependencies: + expect: 29.4.3 + pretty-format: 29.4.3 + dev: true + + /@types/json-schema/7.0.11: + resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + dev: true + + /@types/lodash/4.14.191: + resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} + dev: false + + /@types/mocha/10.0.1: + resolution: {integrity: sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==} + dev: true + + /@types/node/18.14.6: + resolution: {integrity: sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==} + + /@types/parse-json/4.0.0: + resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} + dev: false + + /@types/prettier/2.7.2: + resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==} + + /@types/prop-types/15.7.5: + resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + + /@types/react-dom/18.0.11: + resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} + dependencies: + '@types/react': 18.0.28 + + /@types/react-is/17.0.3: + resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==} + dependencies: + '@types/react': 18.0.28 + dev: false + + /@types/react-transition-group/4.4.5: + resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==} + dependencies: + '@types/react': 18.0.28 + dev: false + + /@types/react/18.0.28: + resolution: {integrity: sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.2 + csstype: 3.1.1 + + /@types/scheduler/0.16.2: + resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + + /@types/semver/7.3.13: + resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} + dev: true + + /@types/stack-utils/2.0.1: + resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + + /@types/use-sync-external-store/0.0.3: + resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} + dev: false + + /@types/utf8/3.0.1: + resolution: {integrity: sha512-1EkWuw7rT3BMz2HpmcEOr/HL61mWNA6Ulr/KdbXR9AI0A55wD4Qfv8hizd8Q1DnknSIzzDvQmvvY/guvX7jjZA==} + dev: true + + /@types/yargs-parser/21.0.0: + resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} + + /@types/yargs/17.0.22: + resolution: {integrity: sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==} + dependencies: + '@types/yargs-parser': 21.0.0 + + /@typescript-eslint/eslint-plugin/5.54.0_6mj2wypvdnknez7kws2nfdgupi: + resolution: {integrity: sha512-+hSN9BdSr629RF02d7mMtXhAJvDTyCbprNYJKrXETlul/Aml6YZwd90XioVbjejQeHbb3R8Dg0CkRgoJDxo8aw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + '@typescript-eslint/scope-manager': 5.54.0 + '@typescript-eslint/type-utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + '@typescript-eslint/utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + debug: 4.3.4 + eslint: 8.35.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + natural-compare-lite: 1.4.0 + regexpp: 3.2.0 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu: + resolution: {integrity: sha512-aAVL3Mu2qTi+h/r04WI/5PfNWvO6pdhpeMRWk9R7rEV4mwJNzoWf5CCU5vDKBsPIFQFjEq1xg7XBI2rjiMXQbQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.54.0 + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5 + debug: 4.3.4 + eslint: 8.35.0 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager/5.54.0: + resolution: {integrity: sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/visitor-keys': 5.54.0 + dev: true + + /@typescript-eslint/type-utils/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu: + resolution: {integrity: sha512-WI+WMJ8+oS+LyflqsD4nlXMsVdzTMYTxl16myXPaCXnSgc7LWwMsjxQFZCK/rVmTZ3FN71Ct78ehO9bRC7erYQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5 + '@typescript-eslint/utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + debug: 4.3.4 + eslint: 8.35.0 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types/5.54.0: + resolution: {integrity: sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/typescript-estree/5.54.0_typescript@4.9.5: + resolution: {integrity: sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/visitor-keys': 5.54.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu: + resolution: {integrity: sha512-cuwm8D/Z/7AuyAeJ+T0r4WZmlnlxQ8wt7C7fLpFlKMR+dY6QO79Cq1WpJhvZbMA4ZeZGHiRWnht7ZJ8qkdAunw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.11 + '@types/semver': 7.3.13 + '@typescript-eslint/scope-manager': 5.54.0 + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5 + eslint: 8.35.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.35.0 + semver: 7.3.8 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys/5.54.0: + resolution: {integrity: sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.54.0 + eslint-visitor-keys: 3.3.0 + dev: true + + /@vitejs/plugin-react/3.1.0_vite@4.1.4: + resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.1.0-beta.0 + dependencies: + '@babel/core': 7.21.0 + '@babel/plugin-transform-react-jsx-self': 7.21.0_@babel+core@7.21.0 + '@babel/plugin-transform-react-jsx-source': 7.19.6_@babel+core@7.21.0 + magic-string: 0.27.0 + react-refresh: 0.14.0 + vite: 4.1.4_@types+node@18.14.6 + transitivePeerDependencies: + - supports-color + dev: true + + /acorn-jsx/5.3.2_acorn@8.8.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.8.2 + dev: true + + /acorn-node/1.8.2: + resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} + dependencies: + acorn: 7.4.1 + acorn-walk: 7.2.0 + xtend: 4.0.2 + dev: true + + /acorn-walk/7.2.0: + resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn/7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /acorn/8.8.2: + resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ansi-escapes/4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + + /ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + + /ansi-styles/5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + /anymatch/3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + /arg/5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: true + + /argparse/1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-includes/3.1.6: + resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.1 + get-intrinsic: 1.2.0 + is-string: 1.0.7 + dev: true + + /array-union/2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /array.prototype.flatmap/1.3.1: + resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.1 + es-shim-unscopables: 1.0.0 + dev: true + + /array.prototype.tosorted/1.1.1: + resolution: {integrity: sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.1 + es-shim-unscopables: 1.0.0 + get-intrinsic: 1.2.0 + dev: true + + /autoprefixer/10.4.13_postcss@8.4.21: + resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.21.5 + caniuse-lite: 1.0.30001460 + fraction.js: 4.2.0 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.21 + postcss-value-parser: 4.2.0 + dev: true + + /available-typed-arrays/1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /babel-jest/29.4.3_@babel+core@7.21.0: + resolution: {integrity: sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@babel/core': 7.21.0 + '@jest/transform': 29.4.3 + '@types/babel__core': 7.20.0 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.4.3_@babel+core@7.21.0 + chalk: 4.1.2 + graceful-fs: 4.2.10 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + /babel-plugin-istanbul/6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + dependencies: + '@babel/helper-plugin-utils': 7.20.2 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + /babel-plugin-jest-hoist/29.4.3: + resolution: {integrity: sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/template': 7.20.7 + '@babel/types': 7.21.2 + '@types/babel__core': 7.20.0 + '@types/babel__traverse': 7.18.3 + + /babel-plugin-macros/3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + dependencies: + '@babel/runtime': 7.21.0 + cosmiconfig: 7.1.0 + resolve: 1.22.1 + dev: false + + /babel-preset-current-node-syntax/1.0.1_@babel+core@7.21.0: + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.21.0 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.21.0 + '@babel/plugin-syntax-bigint': 7.8.3_@babel+core@7.21.0 + '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.21.0 + '@babel/plugin-syntax-import-meta': 7.10.4_@babel+core@7.21.0 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.21.0 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.21.0 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.21.0 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.21.0 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.21.0 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.21.0 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.21.0 + '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.21.0 + + /babel-preset-jest/29.4.3_@babel+core@7.21.0: + resolution: {integrity: sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.21.0 + babel-plugin-jest-hoist: 29.4.3 + babel-preset-current-node-syntax: 1.0.1_@babel+core@7.21.0 + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + + /browserslist/4.21.5: + resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001460 + electron-to-chromium: 1.4.320 + node-releases: 2.0.10 + update-browserslist-db: 1.0.10_browserslist@4.21.5 + + /bs-logger/0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + dependencies: + fast-json-stable-stringify: 2.1.0 + dev: true + + /bser/2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + dependencies: + node-int64: 0.4.0 + + /buffer-from/1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + /call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.0 + dev: true + + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + /camelcase-css/2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + dev: true + + /camelcase/5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + /camelcase/6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + /caniuse-lite/1.0.30001460: + resolution: {integrity: sha512-Bud7abqjvEjipUkpLs4D7gR0l8hBYBHoa+tGtKJHvT2AYzLp1z7EmVkUT4ERpVUfca8S2HGIVs883D8pUH1ZzQ==} + + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + /char-regex/1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /ci-info/3.8.0: + resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} + engines: {node: '>=8'} + + /cjs-module-lexer/1.2.2: + resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} + + /cliui/8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + /clsx/1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + dev: false + + /co/4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + /collect-v8-coverage/1.0.1: + resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} + + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + + /color-name/1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /compute-scroll-into-view/1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + dev: false + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + /convert-source-map/1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + /convert-source-map/2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + /cosmiconfig/7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + dependencies: + '@types/parse-json': 4.0.0 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + dev: false + + /cross-spawn/7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + /cssesc/3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /csstype/3.1.1: + resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /dedent/0.7.0: + resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + + /deep-is/0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /deepmerge/4.3.0: + resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==} + engines: {node: '>=0.10.0'} + + /define-properties/1.2.0: + resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: true + + /defined/1.0.1: + resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} + dev: true + + /detect-newline/3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + /detective/5.2.1: + resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} + engines: {node: '>=0.8.0'} + hasBin: true + dependencies: + acorn-node: 1.8.2 + defined: 1.0.1 + minimist: 1.2.8 + dev: true + + /didyoumean/1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: true + + /diff-sequences/29.4.3: + resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + /dir-glob/3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /direction/1.0.4: + resolution: {integrity: sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==} + hasBin: true + dev: false + + /dlv/1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dev: true + + /doctrine/2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /doctrine/3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /dom-helpers/5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dependencies: + '@babel/runtime': 7.21.0 + csstype: 3.1.1 + dev: false + + /electron-to-chromium/1.4.320: + resolution: {integrity: sha512-h70iRscrNluMZPVICXYl5SSB+rBKo22XfuIS1ER0OQxQZpKTnFpuS6coj7wY9M/3trv7OR88rRMOlKmRvDty7Q==} + + /emittery/0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + /error-ex/1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + + /es-abstract/1.21.1: + resolution: {integrity: sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.2.0 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + is-array-buffer: 3.0.2 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.10 + is-weakref: 1.0.2 + object-inspect: 1.12.3 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.4.3 + safe-regex-test: 1.0.0 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.6 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.9 + dev: true + + /es-set-tostringtag/2.0.1: + resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.0 + has: 1.0.3 + has-tostringtag: 1.0.0 + dev: true + + /es-shim-unscopables/1.0.0: + resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + dependencies: + has: 1.0.3 + dev: true + + /es-to-primitive/1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /esbuild/0.16.17: + resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.16.17 + '@esbuild/android-arm64': 0.16.17 + '@esbuild/android-x64': 0.16.17 + '@esbuild/darwin-arm64': 0.16.17 + '@esbuild/darwin-x64': 0.16.17 + '@esbuild/freebsd-arm64': 0.16.17 + '@esbuild/freebsd-x64': 0.16.17 + '@esbuild/linux-arm': 0.16.17 + '@esbuild/linux-arm64': 0.16.17 + '@esbuild/linux-ia32': 0.16.17 + '@esbuild/linux-loong64': 0.16.17 + '@esbuild/linux-mips64el': 0.16.17 + '@esbuild/linux-ppc64': 0.16.17 + '@esbuild/linux-riscv64': 0.16.17 + '@esbuild/linux-s390x': 0.16.17 + '@esbuild/linux-x64': 0.16.17 + '@esbuild/netbsd-x64': 0.16.17 + '@esbuild/openbsd-x64': 0.16.17 + '@esbuild/sunos-x64': 0.16.17 + '@esbuild/win32-arm64': 0.16.17 + '@esbuild/win32-ia32': 0.16.17 + '@esbuild/win32-x64': 0.16.17 + dev: true + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + + /escape-string-regexp/1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + /escape-string-regexp/2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + /eslint-plugin-react/7.32.2_eslint@8.35.0: + resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + array-includes: 3.1.6 + array.prototype.flatmap: 1.3.1 + array.prototype.tosorted: 1.1.1 + doctrine: 2.1.0 + eslint: 8.35.0 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.3 + minimatch: 3.1.2 + object.entries: 1.1.6 + object.fromentries: 2.0.6 + object.hasown: 1.1.2 + object.values: 1.1.6 + prop-types: 15.8.1 + resolve: 2.0.0-next.4 + semver: 6.3.0 + string.prototype.matchall: 4.0.8 + dev: true + + /eslint-scope/5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: true + + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-utils/3.0.0_eslint@8.35.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.35.0 + eslint-visitor-keys: 2.1.0 + dev: true + + /eslint-visitor-keys/2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + dev: true + + /eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint/8.35.0: + resolution: {integrity: sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 2.0.0 + '@eslint/js': 8.35.0 + '@humanwhocodes/config-array': 0.11.8 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.35.0 + eslint-visitor-keys: 3.3.0 + espree: 9.4.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.20.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-sdsl: 4.3.0 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree/9.4.1: + resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.2 + acorn-jsx: 5.3.2_acorn@8.8.2 + eslint-visitor-keys: 3.3.0 + dev: true + + /esprima/4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + /esquery/1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse/4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse/4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true + + /estraverse/5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /execa/5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + /exit/0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + /expect/29.4.3: + resolution: {integrity: sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/expect-utils': 29.4.3 + jest-get-type: 29.4.3 + jest-matcher-utils: 29.4.3 + jest-message-util: 29.4.3 + jest-util: 29.4.3 + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-glob/3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + /fast-levenshtein/2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fastq/1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /fb-watchman/2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + dependencies: + bser: 2.1.1 + + /file-entry-cache/6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + + /find-root/1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + dev: false + + /find-up/4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + /find-up/5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat-cache/3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + dev: true + + /flatted/3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true + + /for-each/0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: true + + /fraction.js/4.2.0: + resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} + dev: true + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + optional: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /function.prototype.name/1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.1 + functions-have-names: 1.2.3 + dev: true + + /functions-have-names/1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /gensync/1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + /get-caller-file/2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + /get-intrinsic/1.2.0: + resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: true + + /get-package-type/0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + /get-stream/6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + /get-symbol-description/1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + dev: true + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob/7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + /globals/11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + /globals/13.20.0: + resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globalthis/1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.0 + dev: true + + /globby/11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /google-protobuf/3.21.2: + resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==} + dev: false + + /gopd/1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.0 + dev: true + + /graceful-fs/4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + + /grapheme-splitter/1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + + /has-bigints/1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + + /has-flag/3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + /has-property-descriptors/1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.2.0 + dev: true + + /has-proto/1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: true + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /hoist-non-react-statics/3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + dependencies: + react-is: 16.13.1 + dev: false + + /html-escaper/2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + /html-parse-stringify/3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + dependencies: + void-elements: 3.1.0 + dev: false + + /human-signals/2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + /i18next-browser-languagedetector/7.0.1: + resolution: {integrity: sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g==} + dependencies: + '@babel/runtime': 7.21.0 + dev: false + + /i18next/22.4.10: + resolution: {integrity: sha512-3EqgGK6fAJRjnGgfkNSStl4mYLCjUoJID338yVyLMj5APT67HUtWoqSayZewiiC5elzMUB1VEUwcmSCoeQcNEA==} + dependencies: + '@babel/runtime': 7.21.0 + dev: false + + /ignore/5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /immer/9.0.19: + resolution: {integrity: sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==} + dev: false + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + /import-local/3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} + hasBin: true + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + /imurmurhash/0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /internal-slot/1.0.5: + resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.0 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + + /is-array-buffer/3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + is-typed-array: 1.1.10 + dev: true + + /is-arrayish/0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + /is-bigint/1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-boolean-object/1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-callable/1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + + /is-core-module/2.11.0: + resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + dependencies: + has: 1.0.3 + + /is-date-object/1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + /is-generator-fn/2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-hotkey/0.1.8: + resolution: {integrity: sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==} + dev: false + + /is-hotkey/0.2.0: + resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==} + dev: false + + /is-negative-zero/2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object/1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + /is-path-inside/3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /is-plain-object/5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + dev: false + + /is-regex/1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-shared-array-buffer/1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-stream/2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + /is-string/1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol/1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-typed-array/1.1.10: + resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /is-weakref/1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + dev: true + + /isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + /istanbul-lib-coverage/3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} + + /istanbul-lib-instrument/5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + dependencies: + '@babel/core': 7.21.0 + '@babel/parser': 7.21.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.0 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + + /istanbul-lib-report/3.0.0: + resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} + engines: {node: '>=8'} + dependencies: + istanbul-lib-coverage: 3.2.0 + make-dir: 3.1.0 + supports-color: 7.2.0 + + /istanbul-lib-source-maps/4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + dependencies: + debug: 4.3.4 + istanbul-lib-coverage: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + /istanbul-reports/3.1.5: + resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.0 + + /jest-changed-files/29.4.3: + resolution: {integrity: sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + execa: 5.1.1 + p-limit: 3.1.0 + + /jest-circus/29.4.3: + resolution: {integrity: sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.4.3 + '@jest/expect': 29.4.3 + '@jest/test-result': 29.4.3 + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + chalk: 4.1.2 + co: 4.6.0 + dedent: 0.7.0 + is-generator-fn: 2.1.0 + jest-each: 29.4.3 + jest-matcher-utils: 29.4.3 + jest-message-util: 29.4.3 + jest-runtime: 29.4.3 + jest-snapshot: 29.4.3 + jest-util: 29.4.3 + p-limit: 3.1.0 + pretty-format: 29.4.3 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - supports-color + + /jest-cli/29.4.3_@types+node@18.14.6: + resolution: {integrity: sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.4.3 + '@jest/test-result': 29.4.3 + '@jest/types': 29.4.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.10 + import-local: 3.1.0 + jest-config: 29.4.3_@types+node@18.14.6 + jest-util: 29.4.3 + jest-validate: 29.4.3 + prompts: 2.4.2 + yargs: 17.7.1 + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node + + /jest-config/29.4.3_@types+node@18.14.6: + resolution: {integrity: sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.21.0 + '@jest/test-sequencer': 29.4.3 + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + babel-jest: 29.4.3_@babel+core@7.21.0 + chalk: 4.1.2 + ci-info: 3.8.0 + deepmerge: 4.3.0 + glob: 7.2.3 + graceful-fs: 4.2.10 + jest-circus: 29.4.3 + jest-environment-node: 29.4.3 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.4.3 + jest-runner: 29.4.3 + jest-util: 29.4.3 + jest-validate: 29.4.3 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.4.3 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + /jest-diff/29.4.3: + resolution: {integrity: sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 29.4.3 + jest-get-type: 29.4.3 + pretty-format: 29.4.3 + + /jest-docblock/29.4.3: + resolution: {integrity: sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + detect-newline: 3.1.0 + + /jest-each/29.4.3: + resolution: {integrity: sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.4.3 + chalk: 4.1.2 + jest-get-type: 29.4.3 + jest-util: 29.4.3 + pretty-format: 29.4.3 + + /jest-environment-node/29.4.3: + resolution: {integrity: sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.4.3 + '@jest/fake-timers': 29.4.3 + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + jest-mock: 29.4.3 + jest-util: 29.4.3 + + /jest-get-type/29.4.3: + resolution: {integrity: sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + /jest-haste-map/29.4.3: + resolution: {integrity: sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.4.3 + '@types/graceful-fs': 4.1.6 + '@types/node': 18.14.6 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.10 + jest-regex-util: 29.4.3 + jest-util: 29.4.3 + jest-worker: 29.4.3 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.2 + + /jest-leak-detector/29.4.3: + resolution: {integrity: sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.4.3 + pretty-format: 29.4.3 + + /jest-matcher-utils/29.4.3: + resolution: {integrity: sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 29.4.3 + jest-get-type: 29.4.3 + pretty-format: 29.4.3 + + /jest-message-util/29.4.3: + resolution: {integrity: sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/code-frame': 7.18.6 + '@jest/types': 29.4.3 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.10 + micromatch: 4.0.5 + pretty-format: 29.4.3 + slash: 3.0.0 + stack-utils: 2.0.6 + + /jest-mock/29.4.3: + resolution: {integrity: sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + jest-util: 29.4.3 + + /jest-pnp-resolver/1.2.3_jest-resolve@29.4.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 29.4.3 + + /jest-regex-util/29.4.3: + resolution: {integrity: sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + /jest-resolve-dependencies/29.4.3: + resolution: {integrity: sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-regex-util: 29.4.3 + jest-snapshot: 29.4.3 + transitivePeerDependencies: + - supports-color + + /jest-resolve/29.4.3: + resolution: {integrity: sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.10 + jest-haste-map: 29.4.3 + jest-pnp-resolver: 1.2.3_jest-resolve@29.4.3 + jest-util: 29.4.3 + jest-validate: 29.4.3 + resolve: 1.22.1 + resolve.exports: 2.0.0 + slash: 3.0.0 + + /jest-runner/29.4.3: + resolution: {integrity: sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.4.3 + '@jest/environment': 29.4.3 + '@jest/test-result': 29.4.3 + '@jest/transform': 29.4.3 + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.10 + jest-docblock: 29.4.3 + jest-environment-node: 29.4.3 + jest-haste-map: 29.4.3 + jest-leak-detector: 29.4.3 + jest-message-util: 29.4.3 + jest-resolve: 29.4.3 + jest-runtime: 29.4.3 + jest-util: 29.4.3 + jest-watcher: 29.4.3 + jest-worker: 29.4.3 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + /jest-runtime/29.4.3: + resolution: {integrity: sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.4.3 + '@jest/fake-timers': 29.4.3 + '@jest/globals': 29.4.3 + '@jest/source-map': 29.4.3 + '@jest/test-result': 29.4.3 + '@jest/transform': 29.4.3 + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + chalk: 4.1.2 + cjs-module-lexer: 1.2.2 + collect-v8-coverage: 1.0.1 + glob: 7.2.3 + graceful-fs: 4.2.10 + jest-haste-map: 29.4.3 + jest-message-util: 29.4.3 + jest-mock: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.4.3 + jest-snapshot: 29.4.3 + jest-util: 29.4.3 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + /jest-snapshot/29.4.3: + resolution: {integrity: sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.21.0 + '@babel/generator': 7.21.1 + '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.21.0 + '@babel/plugin-syntax-typescript': 7.20.0_@babel+core@7.21.0 + '@babel/traverse': 7.21.2 + '@babel/types': 7.21.2 + '@jest/expect-utils': 29.4.3 + '@jest/transform': 29.4.3 + '@jest/types': 29.4.3 + '@types/babel__traverse': 7.18.3 + '@types/prettier': 2.7.2 + babel-preset-current-node-syntax: 1.0.1_@babel+core@7.21.0 + chalk: 4.1.2 + expect: 29.4.3 + graceful-fs: 4.2.10 + jest-diff: 29.4.3 + jest-get-type: 29.4.3 + jest-haste-map: 29.4.3 + jest-matcher-utils: 29.4.3 + jest-message-util: 29.4.3 + jest-util: 29.4.3 + natural-compare: 1.4.0 + pretty-format: 29.4.3 + semver: 7.3.8 + transitivePeerDependencies: + - supports-color + + /jest-util/29.4.3: + resolution: {integrity: sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + chalk: 4.1.2 + ci-info: 3.8.0 + graceful-fs: 4.2.10 + picomatch: 2.3.1 + + /jest-validate/29.4.3: + resolution: {integrity: sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.4.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.4.3 + leven: 3.1.0 + pretty-format: 29.4.3 + + /jest-watcher/29.4.3: + resolution: {integrity: sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.4.3 + '@jest/types': 29.4.3 + '@types/node': 18.14.6 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.4.3 + string-length: 4.0.2 + + /jest-worker/29.4.3: + resolution: {integrity: sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@types/node': 18.14.6 + jest-util: 29.4.3 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + /jest/29.4.3_@types+node@18.14.6: + resolution: {integrity: sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.4.3 + '@jest/types': 29.4.3 + import-local: 3.1.0 + jest-cli: 29.4.3_@types+node@18.14.6 + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node + + /js-sdsl/4.3.0: + resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + dev: true + + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + /js-yaml/3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /jsesc/2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + /json-parse-even-better-errors/2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + /json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-stable-stringify-without-jsonify/1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /json5/2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + /jsx-ast-utils/3.3.3: + resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} + engines: {node: '>=4.0'} + dependencies: + array-includes: 3.1.6 + object.assign: 4.1.4 + dev: true + + /kleur/3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + /leven/3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + /levn/0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /lilconfig/2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + dev: true + + /lines-and-columns/1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + /locate-path/5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + + /locate-path/6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.memoize/4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + + /lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + + /loose-envify/1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + + /lru-cache/5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + dependencies: + yallist: 3.1.1 + + /lru-cache/6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + + /magic-string/0.27.0: + resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /make-dir/3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.0 + + /make-error/1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /makeerror/1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + dependencies: + tmpl: 1.0.5 + + /merge-stream/2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + /merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + + /mimic-fn/2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + + /minimist/1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /nanoid/4.0.1: + resolution: {integrity: sha512-udKGtCCUafD3nQtJg9wBhRP3KMbPglUsgV5JVsXhvyBs/oefqb4sqMEhKBBgqZncYowu58p1prsZQBYvAj/Gww==} + engines: {node: ^14 || ^16 || >=18} + hasBin: true + dev: false + + /natural-compare-lite/1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: true + + /natural-compare/1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + /node-int64/0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + /node-releases/2.0.10: + resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + /normalize-range/0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + dev: true + + /npm-run-path/4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + + /object-assign/4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + /object-hash/3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + dev: true + + /object-inspect/1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: true + + /object-keys/1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign/4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /object.entries/1.1.6: + resolution: {integrity: sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.1 + dev: true + + /object.fromentries/2.0.6: + resolution: {integrity: sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.1 + dev: true + + /object.hasown/1.1.2: + resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==} + dependencies: + define-properties: 1.2.0 + es-abstract: 1.21.1 + dev: true + + /object.values/1.1.6: + resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.1 + dev: true + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /onetime/5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + + /optionator/0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.3 + dev: true + + /p-limit/2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + + /p-limit/3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + + /p-locate/4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + + /p-locate/5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /p-try/2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + + /parse-json/5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.18.6 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + /path-exists/4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + /path-key/3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + /path-type/4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + /pify/2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: true + + /pirates/4.0.5: + resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} + engines: {node: '>= 6'} + + /pkg-dir/4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + + /postcss-import/14.1.0_postcss@8.4.21: + resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} + engines: {node: '>=10.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.21 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.1 + dev: true + + /postcss-js/4.0.1_postcss@8.4.21: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.21 + dev: true + + /postcss-load-config/3.1.4_postcss@8.4.21: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + postcss: 8.4.21 + yaml: 1.10.2 + dev: true + + /postcss-nested/6.0.0_postcss@8.4.21: + resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.21 + postcss-selector-parser: 6.0.11 + dev: true + + /postcss-selector-parser/6.0.11: + resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + + /postcss-value-parser/4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + dev: true + + /postcss/8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prettier-plugin-tailwindcss/0.2.4_prettier@2.8.4: + resolution: {integrity: sha512-wMyugRI2yD8gqmMpZSS8kTA0gGeKozX/R+w8iWE+yiCZL09zY0SvfiHfHabNhjGhzxlQ2S2VuTxPE3T72vppCQ==} + engines: {node: '>=12.17.0'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-php': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@shufo/prettier-plugin-blade': '*' + '@trivago/prettier-plugin-sort-imports': '*' + prettier: '>=2.2.0' + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-import-sort: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-style-order: '*' + prettier-plugin-svelte: '*' + prettier-plugin-twig-melody: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-php': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@shufo/prettier-plugin-blade': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-import-sort: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-style-order: + optional: true + prettier-plugin-svelte: + optional: true + prettier-plugin-twig-melody: + optional: true + dependencies: + prettier: 2.8.4 + dev: true + + /prettier/2.8.4: + resolution: {integrity: sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + + /pretty-format/29.4.3: + resolution: {integrity: sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.4.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + + /prompts/2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + /prop-types/15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + /punycode/2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + engines: {node: '>=6'} + dev: true + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /quick-lru/5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + dev: true + + /react-dom/18.2.0_react@18.2.0: + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: false + + /react-error-boundary/3.1.4_react@18.2.0: + resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: '>=16.13.1' + dependencies: + '@babel/runtime': 7.21.0 + react: 18.2.0 + dev: false + + /react-i18next/12.2.0_3yopsigl4h4eb2nqrqfsy65uwi: + resolution: {integrity: sha512-5XeVgSygaGfyFmDd2WcXvINRw2WEC1XviW1LXY/xLOEMzsCFRwKqfnHN+hUjla8ZipbVJR27GCMSuTr0BhBBBQ==} + peerDependencies: + i18next: '>= 19.0.0' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@babel/runtime': 7.21.0 + html-parse-stringify: 3.0.1 + i18next: 22.4.10 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /react-is/16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + /react-is/18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + + /react-redux/8.0.5_ctrls2ti7t7iutxbwkm5ipogyy: + resolution: {integrity: sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==} + peerDependencies: + '@types/react': ^16.8 || ^17.0 || ^18.0 + '@types/react-dom': ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + react-native: '>=0.59' + redux: ^4 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + react-dom: + optional: true + react-native: + optional: true + redux: + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@types/hoist-non-react-statics': 3.3.1 + '@types/react': 18.0.28 + '@types/react-dom': 18.0.11 + '@types/use-sync-external-store': 0.0.3 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-is: 18.2.0 + redux: 4.2.1 + use-sync-external-store: 1.2.0_react@18.2.0 + dev: false + + /react-refresh/0.14.0: + resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} + engines: {node: '>=0.10.0'} + dev: true + + /react-router-dom/6.8.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-N/oAF1Shd7g4tWy+75IIufCGsHBqT74tnzHQhbiUTYILYF0Blk65cg+HPZqwC+6SqEyx033nKqU7by38v3lBZg==} + engines: {node: '>=14'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + dependencies: + '@remix-run/router': 1.3.3 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-router: 6.8.2_react@18.2.0 + dev: false + + /react-router/6.8.2_react@18.2.0: + resolution: {integrity: sha512-lF7S0UmXI5Pd8bmHvMdPKI4u4S5McxmHnzJhrYi9ZQ6wE+DA8JN5BzVC5EEBuduWWDaiJ8u6YhVOCmThBli+rw==} + engines: {node: '>=14'} + peerDependencies: + react: '>=16.8' + dependencies: + '@remix-run/router': 1.3.3 + react: 18.2.0 + dev: false + + /react-transition-group/4.4.5_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.21.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /react/18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /react18-input-otp/1.1.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-E21NiPh/KH67Bq/uEAm78E8H+croiGAyX5WcXfX49qh0im1iKrk/3RCKCTESG6WUoJYyh/fj5JY0UrHm+Mm0eQ==} + peerDependencies: + react: 16.2.0 - 18 + react-dom: 16.2.0 - 18 + dependencies: + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /read-cache/1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + dev: true + + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /redux-thunk/2.4.2_redux@4.2.1: + resolution: {integrity: sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==} + peerDependencies: + redux: ^4 + dependencies: + redux: 4.2.1 + dev: false + + /redux/4.2.1: + resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} + dependencies: + '@babel/runtime': 7.21.0 + dev: false + + /regenerator-runtime/0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: false + + /regexp.prototype.flags/1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + functions-have-names: 1.2.3 + dev: true + + /regexpp/3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + dev: true + + /require-directory/2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + /reselect/4.1.7: + resolution: {integrity: sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==} + dev: false + + /resolve-cwd/3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + dependencies: + resolve-from: 5.0.0 + + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + /resolve-from/5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + /resolve.exports/2.0.0: + resolution: {integrity: sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==} + engines: {node: '>=10'} + + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + /resolve/2.0.0-next.4: + resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup/3.18.0: + resolution: {integrity: sha512-J8C6VfEBjkvYPESMQYxKHxNOh4A5a3FlP+0BETGo34HEcE4eTlgCrO2+eWzlu2a/sHs2QUkZco+wscH7jhhgWg==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /rxjs/7.8.0: + resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} + dependencies: + tslib: 2.5.0 + dev: false + + /safe-regex-test/1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + is-regex: 1.1.4 + dev: true + + /scheduler/0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + dependencies: + loose-envify: 1.4.0 + dev: false + + /scroll-into-view-if-needed/2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + dependencies: + compute-scroll-into-view: 1.0.20 + dev: false + + /semver/6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true + + /semver/7.3.8: + resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + + /shebang-command/2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + + /shebang-regex/3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + object-inspect: 1.12.3 + dev: true + + /signal-exit/3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + /sisteransi/1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + /slash/3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + /slate-react/0.91.9_6tgy34rvmll7duwkm4ydcekf3u: + resolution: {integrity: sha512-e3DvmZuUEKKrIB8Kb4RmJ+qzIL2IVJjN2NM/IHRi8W5MmjAtd8yhv46Ewz1sCf0F7uRODS5xvrQsJJAftQhH4Q==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + slate: '>=0.65.3' + dependencies: + '@juggle/resize-observer': 3.4.0 + '@types/is-hotkey': 0.1.7 + '@types/lodash': 4.14.191 + direction: 1.0.4 + is-hotkey: 0.1.8 + is-plain-object: 5.0.0 + lodash: 4.17.21 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + scroll-into-view-if-needed: 2.2.31 + slate: 0.91.4 + tiny-invariant: 1.0.6 + dev: false + + /slate/0.91.4: + resolution: {integrity: sha512-aUJ3rpjrdi5SbJ5G1Qjr3arytfRkEStTmHjBfWq2A2Q8MybacIzkScSvGJjQkdTk3djCK9C9SEOt39sSeZFwTw==} + dependencies: + immer: 9.0.19 + is-plain-object: 5.0.0 + tiny-warning: 1.0.3 + dev: false + + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map-support/0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + /source-map/0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + dev: false + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + /sprintf-js/1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + /stack-utils/2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 2.0.0 + + /string-length/4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + /string-width/4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + /string.prototype.matchall/4.0.8: + resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.1 + get-intrinsic: 1.2.0 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 + dev: true + + /string.prototype.trimend/1.0.6: + resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.1 + dev: true + + /string.prototype.trimstart/1.0.6: + resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.1 + dev: true + + /strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + + /strip-bom/4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + /strip-final-newline/2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + /stylis/4.1.3: + resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==} + dev: false + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + + /supports-color/8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + /tailwindcss/3.2.7_postcss@8.4.21: + resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==} + engines: {node: '>=12.13.0'} + hasBin: true + peerDependencies: + postcss: ^8.0.9 + dependencies: + arg: 5.0.2 + chokidar: 3.5.3 + color-name: 1.1.4 + detective: 5.2.1 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.2.12 + glob-parent: 6.0.2 + is-glob: 4.0.3 + lilconfig: 2.1.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.21 + postcss-import: 14.1.0_postcss@8.4.21 + postcss-js: 4.0.1_postcss@8.4.21 + postcss-load-config: 3.1.4_postcss@8.4.21 + postcss-nested: 6.0.0_postcss@8.4.21 + postcss-selector-parser: 6.0.11 + postcss-value-parser: 4.2.0 + quick-lru: 5.1.1 + resolve: 1.22.1 + transitivePeerDependencies: + - ts-node + dev: true + + /test-exclude/6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + /text-table/0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /tiny-invariant/1.0.6: + resolution: {integrity: sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==} + dev: false + + /tiny-warning/1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + dev: false + + /tmpl/1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + /to-fast-properties/2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + + /ts-jest/29.0.5_orzzknleilowtsz34rkaotjvzm: + resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.4.3_@types+node@18.14.6 + jest-util: 29.4.3 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.3.8 + typescript: 4.9.5 + yargs-parser: 21.1.1 + dev: true + + /ts-results/3.3.0: + resolution: {integrity: sha512-FWqxGX2NHp5oCyaMd96o2y2uMQmSu8Dey6kvyuFdRJ2AzfmWo3kWa4UsPlCGlfQ/qu03m09ZZtppMoY8EMHuiA==} + dev: false + + /tslib/1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true + + /tslib/2.5.0: + resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + dev: false + + /tsutils/3.21.0_typescript@4.9.5: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.9.5 + dev: true + + /type-check/0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-detect/4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + /type-fest/0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /type-fest/0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + /typed-array-length/1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + is-typed-array: 1.1.10 + dev: true + + /typescript/4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /unbox-primitive/1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /update-browserslist-db/1.0.10_browserslist@4.21.5: + resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.21.5 + escalade: 3.1.1 + picocolors: 1.0.0 + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.0 + dev: true + + /use-sync-external-store/1.2.0_react@18.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + + /utf8/3.0.0: + resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} + dev: false + + /util-deprecate/1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: true + + /v8-to-istanbul/9.1.0: + resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} + engines: {node: '>=10.12.0'} + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + '@types/istanbul-lib-coverage': 2.0.4 + convert-source-map: 1.9.0 + + /vite/4.1.4_@types+node@18.14.6: + resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 18.14.6 + esbuild: 0.16.17 + postcss: 8.4.21 + resolve: 1.22.1 + rollup: 3.18.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /void-elements/3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + dev: false + + /walker/1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + dependencies: + makeerror: 1.0.12 + + /which-boxed-primitive/1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-typed-array/1.1.9: + resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + is-typed-array: 1.1.10 + dev: true + + /which/2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + + /word-wrap/1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + dev: true + + /wrap-ansi/7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /write-file-atomic/4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + /xtend/4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: true + + /y18n/5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + /yallist/3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + /yallist/4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + /yaml/1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + /yargs-parser/21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + /yargs/17.7.1: + resolution: {integrity: sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + /yocto-queue/0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/HoveringToolbar/FormatButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/HoveringToolbar/FormatButton.tsx new file mode 100644 index 0000000000..1b17c0bd1b --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/HoveringToolbar/FormatButton.tsx @@ -0,0 +1,51 @@ +import { useSlate } from 'slate-react'; +import { toggleFormat, isFormatActive } from '$app/utils/editor/format'; +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +import { useMemo } from 'react'; +import { FormatBold, FormatUnderlined, FormatItalic, CodeOutlined, StrikethroughSOutlined } from '@mui/icons-material'; +import { command, iconSize } from '$app/constants/toolbar'; + +const FormatButton = ({ format, icon }: { format: string; icon: string }) => { + const editor = useSlate(); + + const renderComponent = useMemo(() => { + switch (icon) { + case 'bold': + return ; + case 'underlined': + return ; + case 'italic': + return ; + case 'code': + return ; + case 'strikethrough': + return ; + default: + break; + } + }, [icon]); + + return ( + + {command[format].title} + {command[format].key} + + } + placement='top-start' + > + toggleFormat(editor, format)} + > + {renderComponent} + + + ); +}; + +export default FormatButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/HoveringToolbar/components.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/HoveringToolbar/components.tsx new file mode 100644 index 0000000000..0a18c3f5e9 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/HoveringToolbar/components.tsx @@ -0,0 +1,5 @@ +import ReactDOM from 'react-dom'; +export const Portal = ({ blockId, children }: { blockId: string; children: JSX.Element }) => { + const root = document.querySelectorAll(`[data-block-id=${blockId}]`)[0]; + return typeof document === 'object' && root ? ReactDOM.createPortal(children, root) : null; +}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/HoveringToolbar/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/HoveringToolbar/index.tsx new file mode 100644 index 0000000000..5ad524c3b7 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/HoveringToolbar/index.tsx @@ -0,0 +1,50 @@ +import { useEffect, useRef } from 'react'; +import { useFocused, useSlate } from 'slate-react'; +import FormatButton from './FormatButton'; +import { Portal } from './components'; +import { calcToolbarPosition } from '$app/utils/editor/toolbar'; + +const HoveringToolbar = ({ blockId }: { blockId: string }) => { + const editor = useSlate(); + const inFocus = useFocused(); + const ref = useRef(null); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + const blockDom = document.querySelectorAll(`[data-block-id=${blockId}]`)[0]; + const blockRect = blockDom?.getBoundingClientRect(); + + const position = calcToolbarPosition(editor, el, blockRect); + + if (!position) { + el.style.opacity = '0'; + } else { + el.style.opacity = '1'; + el.style.top = position.top; + el.style.left = position.left; + } + }); + + if (!inFocus) return null; + + return ( + +
{ + // prevent toolbar from taking focus away from editor + e.preventDefault(); + }} + > + {['bold', 'italic', 'underlined', 'strikethrough', 'code'].map((format) => ( + + ))} +
+
+ ); +}; + +export default HoveringToolbar; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/auth/GetStarted/GetStarted.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/auth/GetStarted/GetStarted.tsx index 04cd2865f1..21c97d3199 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/auth/GetStarted/GetStarted.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/auth/GetStarted/GetStarted.tsx @@ -9,7 +9,7 @@ export const GetStarted = () => { <>
e.preventDefault()} method='POST'>
-
+
@@ -19,8 +19,8 @@ export const GetStarted = () => {
-
-
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/BlockComponent.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/BlockComponent.tsx new file mode 100644 index 0000000000..730e7ee742 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/BlockComponent.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Block, BlockType } from '$app/interfaces'; +import PageBlock from '../PageBlock'; +import TextBlock from '../TextBlock'; +import HeadingBlock from '../HeadingBlock'; +import ListBlock from '../ListBlock'; +import CodeBlock from '../CodeBlock'; + +export default function BlockComponent({ block }: { block: Block }) { + const renderComponent = () => { + switch (block.type) { + case BlockType.PageBlock: + return ; + case BlockType.TextBlock: + return ; + case BlockType.HeadingBlock: + return ; + case BlockType.ListBlock: + return ; + case BlockType.CodeBlock: + return ; + + default: + return null; + } + }; + + return ( +
+ {renderComponent()} +
+ ); +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/BlockList.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/BlockList.hooks.ts new file mode 100644 index 0000000000..8c9114f99d --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/BlockList.hooks.ts @@ -0,0 +1,28 @@ +import { useContext, useEffect, useState } from "react"; +import { BlockContext } from "$app/utils/block_context"; +import { buildTree } from "$app/utils/tree"; +import { Block } from "$app/interfaces"; + +export function useBlockList() { + const blockContext = useContext(BlockContext); + + const [blockList, setBlockList] = useState([]); + + const [title, setTitle] = useState(''); + + useEffect(() => { + if (!blockContext) return; + const { blocksMap, id } = blockContext; + if (!id || !blocksMap) return; + const root = buildTree(id, blocksMap); + if (!root) return; + console.log(root); + setTitle(root.data.title); + setBlockList(root.children || []); + }, [blockContext]); + + return { + title, + blockList + } +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/index.tsx new file mode 100644 index 0000000000..8364b6c75a --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/index.tsx @@ -0,0 +1,17 @@ +import { useBlockList } from './BlockList.hooks'; +import BlockComponent from './BlockComponent'; + +export default function BlockList() { + const { blockList, title } = useBlockList(); + + return ( +
+
{title}
+
+ {blockList?.map((block) => ( + + ))} +
+
+ ); +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/CodeBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/block/CodeBlock/index.tsx new file mode 100644 index 0000000000..57e74cf783 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/block/CodeBlock/index.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { Block } from '$app/interfaces'; + +export default function CodeBlock({ block }: { block: Block }) { + return
{block.data.text}
; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/HeadingBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/block/HeadingBlock/index.tsx new file mode 100644 index 0000000000..a7a39c57af --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/block/HeadingBlock/index.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Block } from '$app/interfaces'; +import TextBlock from '../TextBlock'; + +const fontSize: Record = { + 1: 'mt-8 text-3xl', + 2: 'mt-6 text-2xl', + 3: 'mt-4 text-xl', +}; +export default function HeadingBlock({ block }: { block: Block }) { + return ( +
+ +
+ ); +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/ListBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/block/ListBlock/index.tsx new file mode 100644 index 0000000000..703944bb23 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/block/ListBlock/index.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Block } from '$app/interfaces'; +import BlockComponent from '../BlockList/BlockComponent'; +import TextBlock from '../TextBlock'; + +export default function ListBlock({ block }: { block: Block }) { + const renderChildren = () => { + return block.children?.map((item) => ( +
  • + +
  • + )); + }; + + return ( +
    +
  • +
    + + {renderChildren()} +
    +
  • + ); +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/PageBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/block/PageBlock/index.tsx new file mode 100644 index 0000000000..349f6f7d9e --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/block/PageBlock/index.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { Block } from '$app/interfaces'; + +export default function PageBlock({ block }: { block: Block }) { + return
    {block.data.title}
    ; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/TextBlock/Leaf.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/block/TextBlock/Leaf.tsx new file mode 100644 index 0000000000..b1c73f1116 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/block/TextBlock/Leaf.tsx @@ -0,0 +1,28 @@ +import { RenderLeafProps } from 'slate-react'; + +const Leaf = ({ attributes, children, leaf }: RenderLeafProps) => { + let newChildren = children; + if ('bold' in leaf && leaf.bold) { + newChildren = {children}; + } + + if ('code' in leaf && leaf.code) { + newChildren = {newChildren}; + } + + if ('italic' in leaf && leaf.italic) { + newChildren = {newChildren}; + } + + if ('underlined' in leaf && leaf.underlined) { + newChildren = {newChildren}; + } + + return ( + + {newChildren} + + ); +}; + +export default Leaf; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/TextBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/block/TextBlock/index.tsx new file mode 100644 index 0000000000..723573de27 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/block/TextBlock/index.tsx @@ -0,0 +1,52 @@ +import React, { useState } from 'react'; +import { Block } from '$app/interfaces'; +import BlockComponent from '../BlockList/BlockComponent'; + +import { createEditor } from 'slate'; +import { Slate, Editable, withReact } from 'slate-react'; +import Leaf from './Leaf'; +import HoveringToolbar from '$app/components/HoveringToolbar'; +import { triggerHotkey } from '$app/utils/editor/hotkey'; + +export default function TextBlock({ block }: { block: Block }) { + const [editor] = useState(() => withReact(createEditor())); + + return ( +
    + console.log('===', e, editor.operations)} + value={[ + { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + type: 'paragraph', + children: [{ text: block.data.text }], + }, + ]} + > + + { + switch (event.key) { + case 'Enter': { + event.stopPropagation(); + event.preventDefault(); + return; + } + } + + triggerHotkey(event, editor); + }} + renderLeaf={(props) => } + placeholder='Enter some text...' + /> + +
    + {block.children?.map((item: Block) => ( + + ))} +
    +
    + ); +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.tsx index 8f79998027..2690af57f0 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.tsx @@ -13,7 +13,7 @@ export const PageOptions = () => { Share -
    diff --git a/frontend/appflowy_tauri/src/appflowy_app/constants/toolbar.ts b/frontend/appflowy_tauri/src/appflowy_app/constants/toolbar.ts new file mode 100644 index 0000000000..07cd1d8be9 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/constants/toolbar.ts @@ -0,0 +1,25 @@ + +export const iconSize = { width: 18, height: 18 }; + +export const command: Record = { + bold: { + title: 'Bold', + key: '⌘ + B', + }, + underlined: { + title: 'Underlined', + key: '⌘ + U', + }, + italic: { + title: 'Italic', + key: '⌘ + I', + }, + code: { + title: 'Mark as code', + key: '⌘ + E', + }, + strikethrough: { + title: 'Strike through', + key: '⌘ + Shift + S or ⌘ + Shift + X', + }, +}; \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/interfaces/index.ts b/frontend/appflowy_tauri/src/appflowy_app/interfaces/index.ts new file mode 100644 index 0000000000..7aedced472 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/interfaces/index.ts @@ -0,0 +1,51 @@ + +// eslint-disable-next-line no-shadow +export enum BlockType { + PageBlock = 0, + HeadingBlock = 1, + ListBlock = 2, + TextBlock = 3, + CodeBlock = 4, + EmbedBlock = 5, + QuoteBlock = 6, + DividerBlock = 7, + MediaBlock = 8, + TableBlock = 9, +} + + + +export type BlockData = T extends BlockType.TextBlock ? TextBlockData : +T extends BlockType.PageBlock ? PageBlockData : +T extends BlockType.HeadingBlock ? HeadingBlockData: +T extends BlockType.ListBlock ? ListBlockData : any; + +export interface Block { + id: string; + type: BlockType; + data: BlockData; + parent: string | null; + prev: string | null; + next: string | null; + firstChild: string | null; + lastChild: string | null; + children?: Block[]; +} + + +interface TextBlockData { + text: string; + attr: string; +} + +interface PageBlockData { + title: string; +} + +interface ListBlockData { + type: 'ul' | 'ol'; +} + +interface HeadingBlockData { + level: number; +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_bd_svc.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_bd_svc.ts index f2e8c9eb2a..d2fc339cdc 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_bd_svc.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_bd_svc.ts @@ -1,6 +1,4 @@ import { - CreateBoardCardPayloadPB, - DatabaseEventCreateBoardCard, DatabaseEventCreateRow, DatabaseEventGetDatabase, DatabaseEventGetFields, @@ -41,17 +39,12 @@ export class DatabaseBackendService { return FolderEventCloseView(payload); }; - createRow = async (rowId?: string) => { + createRow = async (rowId?: string, groupId?: string) => { const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId, start_row_id: rowId ?? undefined }); - return DatabaseEventCreateRow(payload); - }; - - createGroupRow = async (groupId: string, startRowId?: string) => { - const payload = CreateBoardCardPayloadPB.fromObject({ view_id: this.viewId, group_id: groupId }); - if (startRowId !== undefined) { - payload.start_row_id = startRowId; + if (groupId !== undefined) { + payload.group_id = groupId; } - return DatabaseEventCreateBoardCard(payload); + return DatabaseEventCreateRow(payload); }; moveRow = (rowId: string, groupId?: string) => { diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts index 395bdebb38..5077c3c017 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts @@ -94,7 +94,7 @@ export class DatabaseGroupController { }; createRow = async () => { - return this.databaseBackendSvc.createGroupRow(this.group.group_id); + return this.databaseBackendSvc.createRow(this.group.group_id); }; subscribe = (callbacks: GroupDataCallbacks) => { diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/activePageId/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/activePageId/slice.ts new file mode 100644 index 0000000000..f6ba120d63 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/activePageId/slice.ts @@ -0,0 +1,12 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +export const activePageIdSlice = createSlice({ + name: 'activePageId', + initialState: '', + reducers: { + setActivePageId(state, action: PayloadAction) { + return action.payload; + }, + }, +}); + +export const activePageIdActions = activePageIdSlice.actions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts index 9e476d875e..1f96485938 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts @@ -16,6 +16,7 @@ import { workspaceSlice } from './reducers/workspace/slice'; import { databaseSlice } from './reducers/database/slice'; import { boardSlice } from './reducers/board/slice'; import { errorSlice } from './reducers/error/slice'; +import { activePageIdSlice } from './reducers/activePageId/slice'; const listenerMiddlewareInstance = createListenerMiddleware({ onError: () => console.error, @@ -25,6 +26,7 @@ const store = configureStore({ reducer: { [foldersSlice.name]: foldersSlice.reducer, [pagesSlice.name]: pagesSlice.reducer, + [activePageIdSlice.name]: activePageIdSlice.reducer, [navigationWidthSlice.name]: navigationWidthSlice.reducer, [currentUserSlice.name]: currentUserSlice.reducer, [gridSlice.name]: gridSlice.reducer, diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/block_context.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/block_context.ts new file mode 100644 index 0000000000..7d5248902f --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/block_context.ts @@ -0,0 +1,8 @@ + +import { createContext } from 'react'; +import { Block, BlockType } from '../interfaces'; + +export const BlockContext = createContext<{ + id?: string; + blocksMap?: Record; +} | null>(null); diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/editor/format.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/editor/format.ts new file mode 100644 index 0000000000..fd36928b76 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/editor/format.ts @@ -0,0 +1,25 @@ +import { + Editor, + Transforms, + Text, + Node +} from 'slate'; + +export function toggleFormat(editor: Editor, format: string) { + const isActive = isFormatActive(editor, format) + Transforms.setNodes( + editor, + { [format]: isActive ? null : true }, + { match: Text.isText, split: true } + ) +} + +export const isFormatActive = (editor: Editor, format: string) => { + const [match] = Editor.nodes(editor, { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + match: (n: Node) => n[format] === true, + mode: 'all', + }) + return !!match +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/editor/hotkey.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/editor/hotkey.ts new file mode 100644 index 0000000000..fad418086d --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/editor/hotkey.ts @@ -0,0 +1,22 @@ +import isHotkey from 'is-hotkey'; +import { toggleFormat } from './format'; +import { Editor } from 'slate'; + +const HOTKEYS: Record = { + 'mod+b': 'bold', + 'mod+i': 'italic', + 'mod+u': 'underline', + 'mod+e': 'code', + 'mod+shift+X': 'strikethrough', + 'mod+shift+S': 'strikethrough', +}; + +export function triggerHotkey(event: React.KeyboardEvent, editor: Editor) { + for (const hotkey in HOTKEYS) { + if (isHotkey(hotkey, event)) { + event.preventDefault() + const format = HOTKEYS[hotkey] + toggleFormat(editor, format) + } + } +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/editor/toolbar.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/editor/toolbar.ts new file mode 100644 index 0000000000..80131a4d69 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/editor/toolbar.ts @@ -0,0 +1,28 @@ +import { Editor, Range } from 'slate'; +export function calcToolbarPosition(editor: Editor, el: HTMLDivElement, blockRect: DOMRect) { + const { selection } = editor; + + if (!selection || Range.isCollapsed(selection) || Editor.string(editor, selection) === '') { + return; + } + + const domSelection = window.getSelection(); + let domRange; + if (domSelection?.rangeCount === 0) { + domRange = document.createRange(); + domRange.setStart(el, domSelection?.anchorOffset); + domRange.setEnd(el, domSelection?.anchorOffset); + } else { + domRange = domSelection?.getRangeAt(0); + } + + const rect = domRange?.getBoundingClientRect() || { top: 0, left: 0, width: 0, height: 0 }; + + const top = `${-el.offsetHeight - 5}px`; + const left = `${rect.left - blockRect.left - el.offsetWidth / 2 + rect.width / 2}px`; + return { + top, + left, + } + +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/tree.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/tree.ts new file mode 100644 index 0000000000..6a3902f929 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/utils/tree.ts @@ -0,0 +1,36 @@ +import { Block } from "../interfaces"; + +export function buildTree(id: string, blocksMap: Record) { + const head = blocksMap[id]; + let node: Block | null = head; + while(node) { + + if (node.parent) { + const parent = blocksMap[node.parent]; + !parent.children && (parent.children = []); + parent.children.push(node); + } + + if (node.firstChild) { + node = blocksMap[node.firstChild]; + } else if (node.next) { + node = blocksMap[node.next]; + } else { + while(node && node?.parent) { + const parent: Block | null = blocksMap[node.parent]; + if (parent?.next) { + node = blocksMap[parent.next]; + break; + } else { + node = parent; + } + } + if (node.id === head.id) { + node = null; + break; + } + } + + } + return head; +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.hooks.ts index b2819b0b21..0a1abca58a 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.hooks.ts @@ -1,10 +1,15 @@ +import { useEffect, useState } from 'react'; import { DocumentEventGetDocument, DocumentVersionPB, OpenDocumentPayloadPB, } from '../../services/backend/events/flowy-document'; +import { Block, BlockType } from '../interfaces'; +import { useParams } from 'react-router-dom'; export const useDocument = () => { + const params = useParams(); + const [blocksMap, setBlocksMap] = useState>(); const loadDocument = async (id: string): Promise => { const getDocumentResult = await DocumentEventGetDocument( OpenDocumentPayloadPB.fromObject({ @@ -20,5 +25,149 @@ export const useDocument = () => { throw new Error('get document error'); } }; - return { loadDocument }; + + const loadBlockData = async (blockId: string): Promise> => { + return { + [blockId]: { + id: blockId, + type: BlockType.PageBlock, + data: { title: 'Document Title' }, + parent: null, + next: null, + prev: null, + firstChild: "A", + lastChild: "E" + }, + "A": { + id: "A", + type: BlockType.HeadingBlock, + data: { level: 1, text: 'A Heading-1' }, + parent: blockId, + prev: null, + next: "B", + firstChild: null, + lastChild: null, + }, + "B": { + id: "B", + type: BlockType.TextBlock, + data: { text: 'Hello', attr: '' }, + parent: blockId, + prev: "A", + next: "C", + firstChild: null, + lastChild: null, + }, + "C": { + id: "C", + type: BlockType.TextBlock, + data: { text: 'block c' }, + prev: null, + parent: blockId, + next: "D", + firstChild: "F", + lastChild: null, + }, + "D": { + id: "D", + type: BlockType.ListBlock, + data: { type: 'number_list', text: 'D List' }, + prev: "C", + parent: blockId, + next: null, + firstChild: "G", + lastChild: "H", + }, + "E": { + id: "E", + type: BlockType.TextBlock, + data: { text: 'World', attr: '' }, + prev: "D", + parent: blockId, + next: null, + firstChild: null, + lastChild: null, + }, + "F": { + id: "F", + type: BlockType.TextBlock, + data: { text: 'Heading', attr: '' }, + prev: null, + parent: "C", + next: null, + firstChild: null, + lastChild: null, + }, + "G": { + id: "G", + type: BlockType.TextBlock, + data: { text: 'Item 1', attr: '' }, + prev: null, + parent: "D", + next: "H", + firstChild: null, + lastChild: null, + }, + "H": { + id: "H", + type: BlockType.TextBlock, + data: { text: 'Item 2', attr: '' }, + prev: "G", + parent: "D", + next: "I", + firstChild: null, + lastChild: null, + }, + "I": { + id: "I", + type: BlockType.HeadingBlock, + data: { level: 2, text: 'B Heading-1' }, + parent: blockId, + prev: "H", + next: 'L', + firstChild: null, + lastChild: null, + }, + "L": { + id: "L", + type: BlockType.TextBlock, + data: { text: '456' }, + parent: blockId, + prev: "I", + next: 'J', + firstChild: null, + lastChild: null, + }, + "J": { + id: "J", + type: BlockType.HeadingBlock, + data: { level: 3, text: 'C Heading-1' }, + parent: blockId, + prev: "L", + next: "K", + firstChild: null, + lastChild: null, + }, + "K": { + id: "K", + type: BlockType.TextBlock, + data: { text: '123' }, + parent: blockId, + prev: "J", + next: null, + firstChild: null, + lastChild: null, + }, + } + } + + useEffect(() => { + void (async () => { + if (!params?.id) return; + const data = await loadBlockData(params.id); + console.log(data); + setBlocksMap(data); + })(); + }, [params]); + return { blocksMap, blockId: params.id }; }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx b/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx index 2b6cf4e9f6..70be86656d 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx @@ -1,17 +1,28 @@ -import { useParams } from 'react-router-dom'; -import { useEffect } from 'react'; import { useDocument } from './DocumentPage.hooks'; +import BlockList from '../components/block/BlockList'; +import { BlockContext } from '../utils/block_context'; +import { createTheme, ThemeProvider } from '@mui/material'; +const theme = createTheme({ + typography: { + fontFamily: ['Poppins'].join(','), + }, +}); export const DocumentPage = () => { - const params = useParams(); - const { loadDocument } = useDocument(); - useEffect(() => { - void (async () => { - if (!params?.id) return; - const content: any = await loadDocument(params.id); - console.log(content); - })(); - }, [params]); + const { blocksMap, blockId } = useDocument(); - return
    Document Page ID: {params.id}
    ; + return ( + +
    + + + +
    +
    + ); }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts b/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts index 11f02fe2a0..ed77210660 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts @@ -1 +1,2 @@ /// + diff --git a/frontend/appflowy_tauri/src/styles/template.css b/frontend/appflowy_tauri/src/styles/template.css index 165bd3712f..1c9c6191e7 100644 --- a/frontend/appflowy_tauri/src/styles/template.css +++ b/frontend/appflowy_tauri/src/styles/template.css @@ -16,6 +16,10 @@ body { font-family: Poppins; } +::selection { + @apply bg-[#E0F8FF] +} + .btn { @apply rounded-xl border border-gray-500 px-4 py-3; } diff --git a/frontend/appflowy_tauri/src/tests/user.test.ts b/frontend/appflowy_tauri/src/tests/user.test.ts index 41be820e97..44a2fa066a 100644 --- a/frontend/appflowy_tauri/src/tests/user.test.ts +++ b/frontend/appflowy_tauri/src/tests/user.test.ts @@ -1,42 +1,43 @@ -import { AuthBackendService, UserBackendService } from '../appflowy_app/stores/effects/user/user_bd_svc'; -import { randomFillSync } from 'crypto'; -import { nanoid } from '@reduxjs/toolkit'; +export {} +// import { AuthBackendService, UserBackendService } from '../appflowy_app/stores/effects/user/user_bd_svc'; +// import { randomFillSync } from 'crypto'; +// import { nanoid } from '@reduxjs/toolkit'; -beforeAll(() => { - //@ts-ignore - window.crypto = { - // @ts-ignore - getRandomValues: function (buffer) { - // @ts-ignore - return randomFillSync(buffer); - }, - }; -}); +// beforeAll(() => { +// //@ts-ignore +// window.crypto = { +// // @ts-ignore +// getRandomValues: function (buffer) { +// // @ts-ignore +// return randomFillSync(buffer); +// }, +// }; +// }); -describe('User backend service', () => { - it('sign up', async () => { - const service = new AuthBackendService(); - const result = await service.autoSignUp(); - expect(result.ok).toBeTruthy; - }); +// describe('User backend service', () => { +// it('sign up', async () => { +// const service = new AuthBackendService(); +// const result = await service.autoSignUp(); +// expect(result.ok).toBeTruthy; +// }); - it('sign in', async () => { - const authService = new AuthBackendService(); - const email = nanoid(4) + '@appflowy.io'; - const password = nanoid(10); - const signUpResult = await authService.signUp({ name: 'nathan', email: email, password: password }); - expect(signUpResult.ok).toBeTruthy; +// it('sign in', async () => { +// const authService = new AuthBackendService(); +// const email = nanoid(4) + '@appflowy.io'; +// const password = nanoid(10); +// const signUpResult = await authService.signUp({ name: 'nathan', email: email, password: password }); +// expect(signUpResult.ok).toBeTruthy; - const signInResult = await authService.signIn({ email: email, password: password }); - expect(signInResult.ok).toBeTruthy; - }); +// const signInResult = await authService.signIn({ email: email, password: password }); +// expect(signInResult.ok).toBeTruthy; +// }); - it('get user profile', async () => { - const service = new AuthBackendService(); - const result = await service.autoSignUp(); - const userProfile = result.unwrap(); +// it('get user profile', async () => { +// const service = new AuthBackendService(); +// const result = await service.autoSignUp(); +// const userProfile = result.unwrap(); - const userService = new UserBackendService(userProfile.id); - expect((await userService.getUserProfile()).unwrap()).toBe(userProfile); - }); -}); +// const userService = new UserBackendService(userProfile.id); +// expect((await userService.getUserProfile()).unwrap()).toBe(userProfile); +// }); +// }); diff --git a/frontend/appflowy_tauri/test/specs/example.e2e.ts b/frontend/appflowy_tauri/test/specs/example.e2e.ts deleted file mode 100644 index be4a485a82..0000000000 --- a/frontend/appflowy_tauri/test/specs/example.e2e.ts +++ /dev/null @@ -1,14 +0,0 @@ -describe('My Login application', () => { - it('should login with valid credentials', async () => { - await browser.url(`https://the-internet.herokuapp.com/login`) - - await $('#username').setValue('tomsmith') - await $('#password').setValue('SuperSecretPassword!') - await $('button[type="submit"]').click() - - await expect($('#flash')).toBeExisting() - await expect($('#flash')).toHaveTextContaining( - 'You logged into a secure area!') - }) -}) - diff --git a/frontend/appflowy_tauri/test/tsconfig.json b/frontend/appflowy_tauri/test/tsconfig.json deleted file mode 100644 index ddef7c61c3..0000000000 --- a/frontend/appflowy_tauri/test/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "moduleResolution": "node", - "module": "ESNext", - "types": [ - "node", - "@wdio/globals/types", - "expect-webdriverio", - "@wdio/mocha-framework" - ], - "target": "es2022" - } -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/tsconfig.json b/frontend/appflowy_tauri/tsconfig.json index a21d07380e..7e82aa0265 100644 --- a/frontend/appflowy_tauri/tsconfig.json +++ b/frontend/appflowy_tauri/tsconfig.json @@ -14,8 +14,15 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "types": ["node", "jest"], + "baseUrl": "./", + "paths": { + "@/*": ["src/*"], + "$app/*": ["src/appflowy_app/*"] + }, }, "include": ["src", "vite.config.ts", "../app_flowy/assets/translations"], + "exclude": ["node_modules"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/frontend/appflowy_tauri/vite.config.ts b/frontend/appflowy_tauri/vite.config.ts index b155feb0be..871e54f6d2 100644 --- a/frontend/appflowy_tauri/vite.config.ts +++ b/frontend/appflowy_tauri/vite.config.ts @@ -24,4 +24,10 @@ export default defineConfig({ // produce sourcemaps for debug builds sourcemap: !!process.env.TAURI_DEBUG, }, + resolve: { + alias: [ + { find: '@/', replacement: `${__dirname}/src/` }, + { find: '$app/', replacement: `${__dirname}/src/appflowy_app/` } + ], + }, }); diff --git a/frontend/appflowy_tauri/webdriver/selenium/package.json b/frontend/appflowy_tauri/webdriver/selenium/package.json new file mode 100644 index 0000000000..78bbd20aad --- /dev/null +++ b/frontend/appflowy_tauri/webdriver/selenium/package.json @@ -0,0 +1,13 @@ +{ + "name": "selenium", + "version": "1.0.0", + "private": true, + "scripts": { + "test": "mocha" + }, + "dependencies": { + "chai": "^4.3.4", + "mocha": "^9.0.3", + "selenium-webdriver": "^4.0.0-beta.4" + } +} diff --git a/frontend/appflowy_tauri/webdriver/selenium/test/test.cjs b/frontend/appflowy_tauri/webdriver/selenium/test/test.cjs new file mode 100644 index 0000000000..7a57bdbbaf --- /dev/null +++ b/frontend/appflowy_tauri/webdriver/selenium/test/test.cjs @@ -0,0 +1,76 @@ +const os = require("os"); +const path = require("path"); +const { expect } = require("chai"); +const { spawn, spawnSync } = require("child_process"); +const { Builder, By, Capabilities, until } = require("selenium-webdriver"); +const { elementIsVisible, elementLocated } = require("selenium-webdriver/lib/until.js"); + +// create the path to the expected application binary +const application = path.resolve( + __dirname, + "..", + "..", + "..", + "src-tauri", + "target", + "release", + "appflowy_tauri" +); + +// keep track of the webdriver instance we create +let driver; + +// keep track of the tauri-driver process we start +let tauriDriver; + +before(async function() { + // set timeout to 2 minutes to allow the program to build if it needs to + this.timeout(120000); + + // ensure the program has been built + spawnSync("cargo", ["build", "--release"]); + + // start tauri-driver + tauriDriver = spawn( + path.resolve(os.homedir(), ".cargo", "bin", "tauri-driver"), + [], + { stdio: [null, process.stdout, process.stderr] } + ); + + const capabilities = new Capabilities(); + capabilities.set("tauri:options", { application }); + capabilities.setBrowserName("wry"); + + // start the webdriver client + driver = await new Builder() + .withCapabilities(capabilities) + .usingServer("http://localhost:4444/") + .build(); +}); + +after(async function() { + // stop the webdriver session + await driver.quit(); + + // kill the tauri-driver process + tauriDriver.kill(); +}); + +describe("AppFlowy Unit Test", () => { + it("should find get started button", async () => { + // should sign out if already sign in + const getStartedButton = await driver.wait(until.elementLocated(By.xpath("//*[@id=\"root\"]/form/div/div[3]"))); + getStartedButton.click(); + }); + + it("should get sign out button", async (done) => { + // const optionButton = await driver.wait(until.elementLocated(By.css('*[test-id=option-button]'))); + // const optionButton = await driver.wait(until.elementLocated(By.id('option-button'))); + // const optionButton = await driver.wait(until.elementLocated(By.css('[aria-label=option]'))); + + // Currently, only the find className is work + const optionButton = await driver.wait(until.elementLocated(By.className("relative h-8 w-8"))); + optionButton.click(); + await new Promise((resolve) => setTimeout(resolve, 4000)); + }); +}); diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs index 180cec67dd..1dd12be853 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs @@ -1,7 +1,7 @@ use crate::errors::{SyncError, SyncResult}; use database_model::{ BuildDatabaseContext, DatabaseBlockMetaRevision, DatabaseBlockRevision, FieldRevision, - RowRevision, + LayoutSetting, RowRevision, }; use std::sync::Arc; @@ -30,6 +30,7 @@ impl DatabaseBuilder { pub fn new() -> Self { Self::default() } + pub fn add_field(&mut self, field: FieldRevision) { self.build_context.field_revs.push(Arc::new(field)); } @@ -54,6 +55,10 @@ impl DatabaseBuilder { &self.build_context.block_metas.first().unwrap().block_id } + pub fn set_layout_setting(&mut self, layout_setting: LayoutSetting) { + self.build_context.layout_setting = layout_setting; + } + pub fn build(self) -> BuildDatabaseContext { self.build_context } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs index 6fab22ea5a..f540c3f8bc 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs @@ -176,8 +176,8 @@ impl DatabaseRevisionPad { T: Into, { let new_field_type = new_field_type.into(); - self.modify_database(|grid_meta| { - match grid_meta + self.modify_database(|database_rev| { + match database_rev .fields .iter_mut() .find(|field_rev| field_rev.id == field_id) diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/database_view_revision_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/database_view_revision_pad.rs index 6859364d67..3cd6b7a50a 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/database_view_revision_pad.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_database/database_view_revision_pad.rs @@ -312,7 +312,7 @@ impl DatabaseViewRevisionPad { } /// updates the settings for the given layout type - pub fn update_layout_setting( + pub fn set_layout_setting( &mut self, layout: &LayoutRevision, settings: &T, @@ -364,14 +364,18 @@ pub struct DatabaseViewRevisionChangeset { pub md5: String, } -pub fn make_database_view_rev_json_str(grid_revision: &DatabaseViewRevision) -> SyncResult { - let json = serde_json::to_string(grid_revision).map_err(|err| { +pub fn make_database_view_rev_json_str( + database_view_rev: &DatabaseViewRevision, +) -> SyncResult { + let json = serde_json::to_string(database_view_rev).map_err(|err| { internal_sync_error(format!("Serialize grid view to json str failed. {:?}", err)) })?; Ok(json) } -pub fn make_database_view_operations(grid_view: &DatabaseViewRevision) -> DatabaseViewOperations { - let json = serde_json::to_string(grid_view).unwrap(); +pub fn make_database_view_operations( + database_view_rev: &DatabaseViewRevision, +) -> DatabaseViewOperations { + let json = serde_json::to_string(database_view_rev).unwrap(); DatabaseViewOperationsBuilder::new().insert(&json).build() } diff --git a/frontend/rust-lib/flowy-database/src/entities/calendar_entities.rs b/frontend/rust-lib/flowy-database/src/entities/calendar_entities.rs index 2a1406c21d..bbe50a87aa 100644 --- a/frontend/rust-lib/flowy-database/src/entities/calendar_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/calendar_entities.rs @@ -1,17 +1,15 @@ use crate::entities::parser::NotEmptyStr; +use database_model::{CalendarLayout, CalendarLayoutSetting}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use serde::{Deserialize, Serialize}; -use serde_repr::{Deserialize_repr, Serialize_repr}; -use std::convert::TryInto; #[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf)] -pub struct CalendarSettingsPB { +pub struct CalendarLayoutSettingsPB { #[pb(index = 1)] - pub view_id: String, + pub layout_field_id: String, #[pb(index = 2)] - pub layout_ty: CalendarLayout, + pub layout_ty: CalendarLayoutPB, #[pb(index = 3)] pub first_day_of_week: i32, @@ -23,51 +21,23 @@ pub struct CalendarSettingsPB { pub show_week_numbers: bool, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CalendarSettingsParams { - pub(crate) view_id: String, - layout_ty: CalendarLayout, - first_day_of_week: i32, - show_weekends: bool, - show_week_numbers: bool, -} - -const DEFAULT_FIRST_DAY_OF_WEEK: i32 = 0; -const DEFAULT_SHOW_WEEKENDS: bool = true; -const DEFAULT_SHOW_WEEK_NUMBERS: bool = true; - -impl CalendarSettingsParams { - pub fn default_with(view_id: String) -> Self { - CalendarSettingsParams { - view_id, - layout_ty: CalendarLayout::default(), - first_day_of_week: DEFAULT_FIRST_DAY_OF_WEEK, - show_weekends: DEFAULT_SHOW_WEEKENDS, - show_week_numbers: DEFAULT_SHOW_WEEK_NUMBERS, +impl std::convert::From for CalendarLayoutSetting { + fn from(pb: CalendarLayoutSettingsPB) -> Self { + CalendarLayoutSetting { + layout_ty: pb.layout_ty.into(), + first_day_of_week: pb.first_day_of_week, + show_weekends: pb.show_weekends, + show_week_numbers: pb.show_week_numbers, + layout_field_id: pb.layout_field_id, } } } -impl TryInto for CalendarSettingsPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?; - Ok(CalendarSettingsParams { - view_id: view_id.0, - layout_ty: self.layout_ty, - first_day_of_week: self.first_day_of_week, - show_weekends: self.show_weekends, - show_week_numbers: self.show_week_numbers, - }) - } -} - -impl std::convert::From for CalendarSettingsPB { - fn from(params: CalendarSettingsParams) -> Self { - CalendarSettingsPB { - view_id: params.view_id, - layout_ty: params.layout_ty, +impl std::convert::From for CalendarLayoutSettingsPB { + fn from(params: CalendarLayoutSetting) -> Self { + CalendarLayoutSettingsPB { + layout_field_id: params.layout_field_id, + layout_ty: params.layout_ty.into(), first_day_of_week: params.first_day_of_week, show_weekends: params.show_weekends, show_week_numbers: params.show_week_numbers, @@ -75,11 +45,92 @@ impl std::convert::From for CalendarSettingsPB { } } -#[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf_Enum, Serialize_repr, Deserialize_repr)] +#[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf_Enum)] #[repr(u8)] -pub enum CalendarLayout { +pub enum CalendarLayoutPB { #[default] MonthLayout = 0, WeekLayout = 1, DayLayout = 2, } + +impl std::convert::From for CalendarLayout { + fn from(pb: CalendarLayoutPB) -> Self { + match pb { + CalendarLayoutPB::MonthLayout => CalendarLayout::MonthLayout, + CalendarLayoutPB::WeekLayout => CalendarLayout::WeekLayout, + CalendarLayoutPB::DayLayout => CalendarLayout::DayLayout, + } + } +} +impl std::convert::From for CalendarLayoutPB { + fn from(layout: CalendarLayout) -> Self { + match layout { + CalendarLayout::MonthLayout => CalendarLayoutPB::MonthLayout, + CalendarLayout::WeekLayout => CalendarLayoutPB::WeekLayout, + CalendarLayout::DayLayout => CalendarLayoutPB::DayLayout, + } + } +} + +#[derive(Debug, Clone, Default, ProtoBuf)] +pub struct CalendarEventRequestPB { + #[pb(index = 1)] + pub view_id: String, + + // Currently, requesting the events within the specified month + // is not supported + #[pb(index = 2)] + pub month: String, +} + +#[derive(Debug, Clone, Default)] +pub struct CalendarEventRequestParams { + pub view_id: String, + pub month: String, +} + +impl TryInto for CalendarEventRequestPB { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?; + Ok(CalendarEventRequestParams { + view_id: view_id.0, + month: self.month, + }) + } +} + +#[derive(Debug, Clone, Default, ProtoBuf)] +pub struct CalendarEventPB { + #[pb(index = 1)] + pub row_id: String, + + #[pb(index = 2)] + pub title_field_id: String, + + #[pb(index = 3)] + pub title: String, + + #[pb(index = 4)] + pub timestamp: i64, +} + +#[derive(Debug, Clone, Default, ProtoBuf)] +pub struct RepeatedCalendarEventPB { + #[pb(index = 1)] + pub items: Vec, +} + +#[derive(Debug, Clone, Default, ProtoBuf)] +pub struct MoveCalendarEventPB { + #[pb(index = 1)] + pub row_id: String, + + #[pb(index = 2)] + pub field_id: String, + + #[pb(index = 3)] + pub timestamp: i64, +} diff --git a/frontend/rust-lib/flowy-database/src/entities/database_entities.rs b/frontend/rust-lib/flowy-database/src/entities/database_entities.rs index cd63afa250..5a43c188fe 100644 --- a/frontend/rust-lib/flowy-database/src/entities/database_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/database_entities.rs @@ -1,5 +1,5 @@ use crate::entities::parser::NotEmptyStr; -use crate::entities::{FieldIdPB, RowPB}; +use crate::entities::{FieldIdPB, LayoutTypePB, RowPB}; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; @@ -195,3 +195,11 @@ impl TryInto for DatabaseGroupIdPB { }) } } +#[derive(Clone, ProtoBuf, Default, Debug)] +pub struct DatabaseLayoutIdPB { + #[pb(index = 1)] + pub view_id: String, + + #[pb(index = 2)] + pub layout: LayoutTypePB, +} diff --git a/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs b/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs index f17ae80997..06eb1dade2 100644 --- a/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs +++ b/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs @@ -1,5 +1,5 @@ use crate::entities::parser::NotEmptyStr; -use crate::entities::{CreateRowParams, FieldType, LayoutTypePB, RowPB}; +use crate::entities::{FieldType, RowPB}; use crate::services::group::Group; use database_model::{FieldTypeRevision, GroupConfigurationRevision}; use flowy_derive::ProtoBuf; @@ -7,41 +7,6 @@ use flowy_error::ErrorCode; use std::convert::TryInto; use std::sync::Arc; -#[derive(ProtoBuf, Debug, Default, Clone)] -pub struct CreateBoardCardPayloadPB { - #[pb(index = 1)] - pub view_id: String, - - #[pb(index = 2)] - pub group_id: String, - - #[pb(index = 3, one_of)] - pub start_row_id: Option, -} - -impl TryInto for CreateBoardCardPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?; - let start_row_id = match self.start_row_id { - None => None, - Some(start_row_id) => Some( - NotEmptyStr::parse(start_row_id) - .map_err(|_| ErrorCode::RowIdIsEmpty)? - .0, - ), - }; - Ok(CreateRowParams { - view_id: view_id.0, - start_row_id, - group_id: Some(group_id.0), - layout: LayoutTypePB::Board, - }) - } -} - #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct GroupConfigurationPB { #[pb(index = 1)] diff --git a/frontend/rust-lib/flowy-database/src/entities/row_entities.rs b/frontend/rust-lib/flowy-database/src/entities/row_entities.rs index e85f172140..97dae4c727 100644 --- a/frontend/rust-lib/flowy-database/src/entities/row_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/row_entities.rs @@ -1,8 +1,9 @@ use crate::entities::parser::NotEmptyStr; -use crate::entities::LayoutTypePB; + use database_model::RowRevision; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; +use std::collections::HashMap; use std::sync::Arc; /// [RowPB] Describes a row. Has the id of the parent Block. Has the metadata of the row. @@ -177,6 +178,18 @@ pub struct CreateRowPayloadPB { #[pb(index = 2, one_of)] pub start_row_id: Option, + + #[pb(index = 3, one_of)] + pub group_id: Option, + + #[pb(index = 4, one_of)] + pub data: Option, +} + +#[derive(ProtoBuf, Default)] +pub struct RowDataPB { + #[pb(index = 1)] + pub cell_data_by_field_id: HashMap, } #[derive(Default)] @@ -184,7 +197,7 @@ pub struct CreateRowParams { pub view_id: String, pub start_row_id: Option, pub group_id: Option, - pub layout: LayoutTypePB, + pub cell_data_by_field_id: Option>, } impl TryInto for CreateRowPayloadPB { @@ -192,12 +205,20 @@ impl TryInto for CreateRowPayloadPB { fn try_into(self) -> Result { let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?; + let start_row_id = match self.start_row_id { + None => None, + Some(start_row_id) => Some( + NotEmptyStr::parse(start_row_id) + .map_err(|_| ErrorCode::RowIdIsEmpty)? + .0, + ), + }; Ok(CreateRowParams { view_id: view_id.0, - start_row_id: self.start_row_id, - group_id: None, - layout: LayoutTypePB::Grid, + start_row_id, + group_id: self.group_id, + cell_data_by_field_id: self.data.map(|data| data.cell_data_by_field_id), }) } } diff --git a/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs b/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs index 98cbed1640..49c4edbcbf 100644 --- a/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs @@ -1,11 +1,11 @@ use crate::entities::parser::NotEmptyStr; use crate::entities::{ - AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB, CalendarSettingsPB, - DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, - DeleteSortParams, DeleteSortPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB, - RepeatedGroupConfigurationPB, RepeatedSortPB, + AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB, + CalendarLayoutSettingsPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, + DeleteGroupPayloadPB, DeleteSortParams, DeleteSortPayloadPB, InsertGroupParams, + InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGroupConfigurationPB, RepeatedSortPB, }; -use database_model::LayoutRevision; +use database_model::{CalendarLayoutSetting, LayoutRevision}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; use std::convert::TryInto; @@ -159,10 +159,42 @@ impl DatabaseSettingChangesetParams { } } +#[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)] +pub struct UpdateLayoutSettingPB { + #[pb(index = 1)] + pub view_id: String, + + #[pb(index = 2)] + pub layout_setting: LayoutSettingPB, +} + +#[derive(Debug)] +pub struct UpdateLayoutSettingParams { + pub view_id: String, + pub layout_setting: LayoutSettingParams, +} + +impl TryInto for UpdateLayoutSettingPB { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id) + .map_err(|_| ErrorCode::ViewIdIsInvalid)? + .0; + + let layout_setting: LayoutSettingParams = self.layout_setting.into(); + + Ok(UpdateLayoutSettingParams { + view_id, + layout_setting, + }) + } +} + #[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)] pub struct LayoutSettingPB { #[pb(index = 1, one_of)] - pub calendar: Option, + pub calendar: Option, } impl LayoutSettingPB { @@ -170,3 +202,24 @@ impl LayoutSettingPB { Self::default() } } + +impl std::convert::From for LayoutSettingPB { + fn from(params: LayoutSettingParams) -> Self { + Self { + calendar: params.calendar.map(|calender| calender.into()), + } + } +} + +impl std::convert::From for LayoutSettingParams { + fn from(params: LayoutSettingPB) -> Self { + Self { + calendar: params.calendar.map(|calender| calender.into()), + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct LayoutSettingParams { + pub calendar: Option, +} diff --git a/frontend/rust-lib/flowy-database/src/event_handler.rs b/frontend/rust-lib/flowy-database/src/event_handler.rs index 631ff8b7b0..3e73790bb9 100644 --- a/frontend/rust-lib/flowy-database/src/event_handler.rs +++ b/frontend/rust-lib/flowy-database/src/event_handler.rs @@ -329,7 +329,7 @@ pub(crate) async fn move_row_handler( } #[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn create_table_row_handler( +pub(crate) async fn create_row_handler( data: AFPluginData, manager: AFPluginState>, ) -> DataResult { @@ -517,6 +517,7 @@ pub(crate) async fn update_date_cell_handler( let cell_changeset = DateCellChangeset { date: data.date, time: data.time, + include_time: data.include_time, is_utc: data.is_utc, }; @@ -549,17 +550,6 @@ pub(crate) async fn get_group_handler( data_result_ok(group) } -#[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn create_board_card_handler( - data: AFPluginData, - manager: AFPluginState>, -) -> DataResult { - let params: CreateRowParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(params.view_id.as_ref()).await?; - let row = editor.create_row(params).await?; - data_result_ok(row) -} - #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn move_group_handler( data: AFPluginData, @@ -599,23 +589,56 @@ pub(crate) async fn get_databases_handler( } #[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn set_calendar_setting_handler( - data: AFPluginData, +pub(crate) async fn set_layout_setting_handler( + data: AFPluginData, manager: AFPluginState>, ) -> FlowyResult<()> { - let params: CalendarSettingsParams = data.into_inner().try_into()?; - let _ = manager.get_database_editor(params.view_id.as_ref()).await?; - //TODO(nathan): - todo!("nathan: depends on the main branch refactoring") + let params: UpdateLayoutSettingParams = data.into_inner().try_into()?; + let database_editor = manager.get_database_editor(params.view_id.as_ref()).await?; + database_editor + .set_layout_setting(¶ms.view_id, params.layout_setting) + .await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn get_calendar_setting_handler( - data: AFPluginData, +pub(crate) async fn get_layout_setting_handler( + data: AFPluginData, manager: AFPluginState>, -) -> FlowyResult<()> { - let view_id = data.into_inner().value; - let _ = manager.get_database_editor(view_id.as_ref()).await?; - //TODO(nathan): - todo!("nathan: depends on the main branch refactoring") +) -> DataResult { + let params = data.into_inner(); + let database_editor = manager.get_database_editor(¶ms.view_id).await?; + let layout_setting = database_editor + .get_layout_setting(¶ms.view_id, params.layout) + .await?; + data_result_ok(layout_setting.into()) +} + +#[tracing::instrument(level = "debug", skip(data, manager), err)] +pub(crate) async fn get_calendar_events_handler( + data: AFPluginData, + manager: AFPluginState>, +) -> DataResult { + let params: CalendarEventRequestParams = data.into_inner().try_into()?; + let database_editor = manager.get_database_editor(¶ms.view_id).await?; + let events = database_editor + .get_all_calendar_events(¶ms.view_id) + .await; + data_result_ok(RepeatedCalendarEventPB { items: events }) +} + +#[tracing::instrument(level = "debug", skip(data, manager), err)] +pub(crate) async fn get_calendar_event_handler( + data: AFPluginData, + manager: AFPluginState>, +) -> DataResult { + let params: RowIdParams = data.into_inner().try_into()?; + let database_editor = manager.get_database_editor(¶ms.view_id).await?; + let event = database_editor + .get_calendar_event(¶ms.view_id, ¶ms.row_id) + .await; + match event { + None => Err(FlowyError::record_not_found()), + Some(event) => data_result_ok(event), + } } diff --git a/frontend/rust-lib/flowy-database/src/event_map.rs b/frontend/rust-lib/flowy-database/src/event_map.rs index aaf9b0fc95..6eb9b02218 100644 --- a/frontend/rust-lib/flowy-database/src/event_map.rs +++ b/frontend/rust-lib/flowy-database/src/event_map.rs @@ -28,7 +28,7 @@ pub fn init(database_manager: Arc) -> AFPlugin { .event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler) .event(DatabaseEvent::CreateTypeOption, create_field_type_option_data_handler) // Row - .event(DatabaseEvent::CreateRow, create_table_row_handler) + .event(DatabaseEvent::CreateRow, create_row_handler) .event(DatabaseEvent::GetRow, get_row_handler) .event(DatabaseEvent::DeleteRow, delete_row_handler) .event(DatabaseEvent::DuplicateRow, duplicate_row_handler) @@ -44,7 +44,6 @@ pub fn init(database_manager: Arc) -> AFPlugin { // Date .event(DatabaseEvent::UpdateDateCell, update_date_cell_handler) // Group - .event(DatabaseEvent::CreateBoardCard, create_board_card_handler) .event(DatabaseEvent::MoveGroup, move_group_handler) .event(DatabaseEvent::MoveGroupRow, move_group_row_handler) .event(DatabaseEvent::GetGroups, get_groups_handler) @@ -52,8 +51,11 @@ pub fn init(database_manager: Arc) -> AFPlugin { // Database .event(DatabaseEvent::GetDatabases, get_databases_handler) // Calendar - .event(DatabaseEvent::SetCalenderSetting, set_calendar_setting_handler) - .event(DatabaseEvent::GetCalendarSetting, get_calendar_setting_handler); + .event(DatabaseEvent::GetAllCalendarEvents, get_calendar_events_handler) + .event(DatabaseEvent::GetCalendarEvent, get_calendar_event_handler) + // Layout setting + .event(DatabaseEvent::SetLayoutSetting, set_layout_setting_handler) + .event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler); plugin } @@ -227,9 +229,6 @@ pub enum DatabaseEvent { #[event(input = "DatabaseGroupIdPB", output = "GroupPB")] GetGroup = 101, - #[event(input = "CreateBoardCardPayloadPB", output = "RowPB")] - CreateBoardCard = 110, - #[event(input = "MoveGroupPayloadPB")] MoveGroup = 111, @@ -242,9 +241,18 @@ pub enum DatabaseEvent { #[event(output = "RepeatedDatabaseDescPB")] GetDatabases = 114, - #[event(input = "CalendarSettingsPB")] - SetCalenderSetting = 115, + #[event(input = "UpdateLayoutSettingPB")] + SetLayoutSetting = 115, - #[event()] - GetCalendarSetting = 116, + #[event(input = "DatabaseLayoutIdPB", output = "LayoutSettingPB")] + GetLayoutSetting = 116, + + #[event(input = "CalendarEventRequestPB", output = "RepeatedCalendarEventPB")] + GetAllCalendarEvents = 117, + + #[event(input = "RowIdPB", output = "CalendarEventPB")] + GetCalendarEvent = 118, + + #[event(input = "MoveCalendarEventPB")] + MoveCalendarEvent = 119, } diff --git a/frontend/rust-lib/flowy-database/src/manager.rs b/frontend/rust-lib/flowy-database/src/manager.rs index afff510ddf..013208923f 100644 --- a/frontend/rust-lib/flowy-database/src/manager.rs +++ b/frontend/rust-lib/flowy-database/src/manager.rs @@ -371,6 +371,7 @@ pub async fn create_new_database( block_metas, blocks, database_view_data, + layout_setting, } = build_context; for block_meta_data in &blocks { @@ -405,11 +406,14 @@ pub async fn create_new_database( // Create database view tracing::trace!("Create new database view: {}", view_id); - let database_view_rev = if database_view_data.is_empty() { + let mut database_view_rev = if database_view_data.is_empty() { DatabaseViewRevision::new(database_id, view_id.to_owned(), true, name, layout.into()) } else { DatabaseViewRevision::from_json(database_view_data)? }; + + tracing::trace!("Initial calendar layout setting: {:?}", layout_setting); + database_view_rev.layout_settings = layout_setting; let database_view_ops = make_database_view_operations(&database_view_rev); let database_view_bytes = database_view_ops.json_bytes(); let revision = Revision::initial_revision(view_id, database_view_bytes); diff --git a/frontend/rust-lib/flowy-database/src/notification.rs b/frontend/rust-lib/flowy-database/src/notification.rs index ffbba67ff2..c0dd347411 100644 --- a/frontend/rust-lib/flowy-database/src/notification.rs +++ b/frontend/rust-lib/flowy-database/src/notification.rs @@ -31,8 +31,12 @@ pub enum DatabaseNotification { DidReorderSingleRow = 66, /// Trigger when the settings of the database are changed DidUpdateSettings = 70, - DidUpdateCalendarSettings = 80, - DidArrangeCalendarWithNewField = 81, + // Trigger when the layout setting of the database is updated + DidUpdateLayoutSettings = 80, + // Trigger when the layout field of the database is changed + DidSetNewLayoutField = 81, + + DidArrangeCalendarWithNewField = 82, } impl std::default::Default for DatabaseNotification { diff --git a/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs index 7c66f69572..b519a2943e 100644 --- a/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs @@ -39,7 +39,7 @@ pub trait CellDataChangeset: TypeOption { /// The changeset is able to parse into the concrete data struct if `TypeOption::CellChangeset` /// implements the `FromCellChangesetString` trait. /// For example,the SelectOptionCellChangeset,DateCellChangeset. etc. - /// + /// fn apply_changeset( &self, changeset: ::CellChangeset, @@ -142,7 +142,7 @@ where /// Decode the opaque cell data from one field type to another using the corresponding `TypeOption` /// -/// The cell data might become an empty string depends on the to_field_type's `TypeOption` +/// The cell data might become an empty string depends on the to_field_type's `TypeOption` /// support transform the from_field_type's cell data or not. /// /// # Arguments @@ -252,6 +252,7 @@ pub fn insert_date_cell(timestamp: i64, field_rev: &FieldRevision) -> CellRevisi let cell_data = serde_json::to_string(&DateCellChangeset { date: Some(timestamp.to_string()), time: None, + include_time: Some(false), is_utc: true, }) .unwrap(); @@ -279,7 +280,7 @@ pub fn delete_select_option_cell( CellRevision::new(data) } -/// Deserialize the String into cell specific data type. +/// Deserialize the String into cell specific data type. pub trait FromCellString { fn from_cell_str(s: &str) -> FlowyResult where diff --git a/frontend/rust-lib/flowy-database/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database/src/services/database/database_editor.rs index 46e836bbf1..7af6cdc44a 100644 --- a/frontend/rust-lib/flowy-database/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database/src/services/database/database_editor.rs @@ -425,7 +425,9 @@ impl DatabaseEditor { } pub async fn create_row(&self, params: CreateRowParams) -> FlowyResult { - let mut row_rev = self.create_row_rev().await?; + let mut row_rev = self + .create_row_rev(params.cell_data_by_field_id.clone()) + .await?; self .database_views @@ -915,6 +917,7 @@ impl DatabaseEditor { field_revs: duplicated_fields.into_iter().map(Arc::new).collect(), block_metas: duplicated_blocks, blocks: blocks_meta_data, + layout_setting: Default::default(), database_view_data, }) } @@ -929,12 +932,64 @@ impl DatabaseEditor { self.database_views.get_group(view_id, group_id).await } - async fn create_row_rev(&self) -> FlowyResult { + pub async fn get_layout_setting>( + &self, + view_id: &str, + layout_ty: T, + ) -> FlowyResult { + let layout_ty = layout_ty.into(); + self + .database_views + .get_layout_setting(view_id, &layout_ty) + .await + } + + pub async fn set_layout_setting( + &self, + view_id: &str, + layout_setting: LayoutSettingParams, + ) -> FlowyResult<()> { + self + .database_views + .set_layout_setting(view_id, layout_setting) + .await + } + + pub async fn get_all_calendar_events(&self, view_id: &str) -> Vec { + match self.database_views.get_view_editor(view_id).await { + Ok(view_editor) => view_editor + .v_get_all_calendar_events() + .await + .unwrap_or_default(), + Err(err) => { + tracing::error!("Get calendar event failed: {}", err); + vec![] + }, + } + } + + #[tracing::instrument(level = "trace", skip(self))] + pub async fn get_calendar_event(&self, view_id: &str, row_id: &str) -> Option { + let view_editor = self.database_views.get_view_editor(view_id).await.ok()?; + view_editor.v_get_calendar_event(row_id).await + } + + async fn create_row_rev( + &self, + cell_data_by_field_id: Option>, + ) -> FlowyResult { let field_revs = self.database_pad.read().await.get_field_revs(None)?; let block_id = self.block_id().await?; // insert empty row below the row whose id is upper_row_id - let row_rev = RowRevisionBuilder::new(&block_id, field_revs).build(); + let builder = match cell_data_by_field_id { + None => RowRevisionBuilder::new(&block_id, field_revs), + Some(cell_data_by_field_id) => { + RowRevisionBuilder::new_with_data(&block_id, field_revs, cell_data_by_field_id) + }, + }; + + let row_rev = builder.build(); Ok(row_rev) } diff --git a/frontend/rust-lib/flowy-database/src/services/database/trait_impl.rs b/frontend/rust-lib/flowy-database/src/services/database/trait_impl.rs index b7278f01f0..53777b3046 100644 --- a/frontend/rust-lib/flowy-database/src/services/database/trait_impl.rs +++ b/frontend/rust-lib/flowy-database/src/services/database/trait_impl.rs @@ -43,6 +43,16 @@ impl DatabaseViewData for DatabaseViewDataImpl { to_fut(async move { Some(pad.read().await.get_field_rev(&field_id)?.1.clone()) }) } + fn get_primary_field_rev(&self) -> Fut>> { + let pad = self.pad.clone(); + to_fut(async move { + let field_revs = pad.read().await.get_field_revs(None).ok()?; + field_revs + .into_iter() + .find(|field_rev| field_rev.is_primary) + }) + } + fn index_of_row(&self, row_id: &str) -> Fut> { let block_manager = self.blocks.clone(); let row_id = row_id.to_owned(); diff --git a/frontend/rust-lib/flowy-database/src/services/database_view/editor.rs b/frontend/rust-lib/flowy-database/src/services/database_view/editor.rs index 0a7ba9a38a..70188e5031 100644 --- a/frontend/rust-lib/flowy-database/src/services/database_view/editor.rs +++ b/frontend/rust-lib/flowy-database/src/services/database_view/editor.rs @@ -18,8 +18,9 @@ use crate::services::sort::{ DeletedSortType, SortChangeset, SortController, SortTaskHandler, SortType, }; use database_model::{ - gen_database_filter_id, gen_database_id, gen_database_sort_id, FieldRevision, FieldTypeRevision, - FilterRevision, LayoutRevision, RowChangeset, RowRevision, SortRevision, + gen_database_filter_id, gen_database_id, gen_database_sort_id, CalendarLayoutSetting, + FieldRevision, FieldTypeRevision, FilterRevision, LayoutRevision, RowChangeset, RowRevision, + SortRevision, }; use flowy_client_sync::client_database::{ make_database_view_operations, DatabaseViewRevisionChangeset, DatabaseViewRevisionPad, @@ -32,6 +33,7 @@ use lib_infra::future::Fut; use nanoid::nanoid; use revision_model::Revision; use std::borrow::Cow; +use std::collections::HashMap; use std::future::Future; use std::sync::Arc; use tokio::sync::{broadcast, RwLock}; @@ -43,6 +45,8 @@ pub trait DatabaseViewData: Send + Sync + 'static { /// Returns the field with the field_id fn get_field_rev(&self, field_id: &str) -> Fut>>; + fn get_primary_field_rev(&self) -> Fut>>; + /// Returns the index of the row with row_id fn index_of_row(&self, row_id: &str) -> Fut>; @@ -661,25 +665,84 @@ impl DatabaseViewEditor { } /// Returns the current calendar settings - pub async fn v_get_calendar_settings(&self) -> FlowyResult { - let settings = self - .pad - .read() - .await - .get_layout_setting(&LayoutRevision::Calendar) - .unwrap_or_else(|| CalendarSettingsParams::default_with(self.view_id.to_string())); - Ok(settings) + #[tracing::instrument(level = "debug", skip(self), err)] + pub async fn v_get_layout_settings( + &self, + layout_ty: &LayoutRevision, + ) -> FlowyResult { + let mut layout_setting = LayoutSettingParams::default(); + match layout_ty { + LayoutRevision::Grid => {}, + LayoutRevision::Board => {}, + LayoutRevision::Calendar => { + if let Some(calendar) = self + .pad + .read() + .await + .get_layout_setting::(layout_ty) + { + // Check the field exist or not + if let Some(field_rev) = self.delegate.get_field_rev(&calendar.layout_field_id).await { + let field_type: FieldType = field_rev.ty.into(); + + // Check the type of field is Datetime or not + if field_type == FieldType::DateTime { + layout_setting.calendar = Some(calendar); + } + } + } + }, + } + + tracing::debug!("{:?}", layout_setting); + Ok(layout_setting) } /// Update the calendar settings and send the notification to refresh the UI - pub async fn v_update_calendar_settings( - &self, - params: CalendarSettingsParams, - ) -> FlowyResult<()> { + pub async fn v_set_layout_settings(&self, params: LayoutSettingParams) -> FlowyResult<()> { // Maybe it needs no send notification to refresh the UI - self - .modify(|pad| Ok(pad.update_layout_setting(&LayoutRevision::Calendar, ¶ms)?)) - .await?; + if let Some(new_calendar_setting) = params.calendar { + if let Some(field_rev) = self + .delegate + .get_field_rev(&new_calendar_setting.layout_field_id) + .await + { + let field_type: FieldType = field_rev.ty.into(); + if field_type != FieldType::DateTime { + return Err(FlowyError::unexpect_calendar_field_type()); + } + + let layout_ty = LayoutRevision::Calendar; + let old_calender_setting = self.v_get_layout_settings(&layout_ty).await?.calendar; + self + .modify(|pad| Ok(pad.set_layout_setting(&layout_ty, &new_calendar_setting)?)) + .await?; + + let new_field_id = new_calendar_setting.layout_field_id.clone(); + let layout_setting_pb: LayoutSettingPB = LayoutSettingParams { + calendar: Some(new_calendar_setting), + } + .into(); + + if let Some(old_calendar_setting) = old_calender_setting { + // compare the new layout field id is equal to old layout field id + // if not equal, send the DidSetNewLayoutField notification + // if equal, send the DidUpdateLayoutSettings notification + if old_calendar_setting.layout_field_id != new_field_id { + send_notification(&self.view_id, DatabaseNotification::DidSetNewLayoutField) + .payload(layout_setting_pb) + .send(); + } else { + send_notification(&self.view_id, DatabaseNotification::DidUpdateLayoutSettings) + .payload(layout_setting_pb) + .send(); + } + } else { + tracing::warn!("Calendar setting should not be empty") + } + } + } + Ok(()) } @@ -772,6 +835,101 @@ impl DatabaseViewEditor { get_cells_for_field(self.delegate.clone(), field_id).await } + pub async fn v_get_calendar_event(&self, row_id: &str) -> Option { + let layout_ty = LayoutRevision::Calendar; + let calendar_setting = self + .v_get_layout_settings(&layout_ty) + .await + .ok()? + .calendar?; + + // Text + let primary_field = self.delegate.get_primary_field_rev().await?; + let text_cell = get_cell_for_row(self.delegate.clone(), &primary_field.id, row_id).await?; + + // Date + let date_field = self + .delegate + .get_field_rev(&calendar_setting.layout_field_id) + .await?; + + let date_cell = get_cell_for_row(self.delegate.clone(), &date_field.id, row_id).await?; + let title = text_cell + .into_text_field_cell_data() + .unwrap_or_default() + .into(); + + let timestamp = date_cell + .into_date_field_cell_data() + .unwrap_or_default() + .timestamp + .unwrap_or_default(); + + Some(CalendarEventPB { + row_id: row_id.to_string(), + title_field_id: primary_field.id.clone(), + title, + timestamp, + }) + } + + pub async fn v_get_all_calendar_events(&self) -> Option> { + let layout_ty = LayoutRevision::Calendar; + let calendar_setting = self + .v_get_layout_settings(&layout_ty) + .await + .ok()? + .calendar?; + + // Text + let primary_field = self.delegate.get_primary_field_rev().await?; + let text_cells = self.v_get_cells_for_field(&primary_field.id).await.ok()?; + + // Date + let timestamp_by_row_id = self + .v_get_cells_for_field(&calendar_setting.layout_field_id) + .await + .ok()? + .into_iter() + .map(|date_cell| { + let row_id = date_cell.row_id.clone(); + + // timestamp + let timestamp = date_cell + .into_date_field_cell_data() + .map(|date_cell_data| date_cell_data.timestamp.unwrap_or_default()) + .unwrap_or_default(); + + (row_id, timestamp) + }) + .collect::>(); + + let mut events: Vec = vec![]; + for text_cell in text_cells { + let title_field_id = text_cell.field_id.clone(); + let row_id = text_cell.row_id.clone(); + let timestamp = timestamp_by_row_id + .get(&row_id) + .cloned() + .unwrap_or_default(); + + let title = text_cell + .into_text_field_cell_data() + .unwrap_or_default() + .into(); + + let event = CalendarEventPB { + row_id, + title_field_id, + title, + timestamp, + }; + events.push(event); + } + + Some(events) + } + async fn notify_did_update_setting(&self) { let setting = self.v_get_setting().await; send_notification(&self.view_id, DatabaseNotification::DidUpdateSettings) @@ -851,12 +1009,38 @@ impl DatabaseViewEditor { } } } -/// Returns the list of cells corresponding to the given field. + +pub(crate) async fn get_cell_for_row( + delegate: Arc, + field_id: &str, + row_id: &str, +) -> Option { + let (_, row_rev) = delegate.get_row_rev(row_id).await?; + let mut cells = get_cells_for_field_in_rows(delegate, field_id, vec![row_rev]) + .await + .ok()?; + if cells.is_empty() { + None + } else { + assert_eq!(cells.len(), 1); + Some(cells.remove(0)) + } +} + +// Returns the list of cells corresponding to the given field. pub(crate) async fn get_cells_for_field( delegate: Arc, field_id: &str, ) -> FlowyResult> { let row_revs = delegate.get_row_revs(None).await; + get_cells_for_field_in_rows(delegate, field_id, row_revs).await +} + +pub(crate) async fn get_cells_for_field_in_rows( + delegate: Arc, + field_id: &str, + row_revs: Vec>, +) -> FlowyResult> { let field_rev = delegate.get_field_rev(field_id).await.unwrap(); let field_type: FieldType = field_rev.ty.into(); let mut cells = vec![]; diff --git a/frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs b/frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs index db06dc0188..75c00689d6 100644 --- a/frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs +++ b/frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs @@ -1,8 +1,8 @@ #![allow(clippy::while_let_loop)] use crate::entities::{ AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams, - DeleteGroupParams, DeleteSortParams, GroupPB, InsertGroupParams, MoveGroupParams, - RepeatedGroupPB, RowPB, + DeleteGroupParams, DeleteSortParams, GroupPB, InsertGroupParams, LayoutSettingParams, + MoveGroupParams, RepeatedGroupPB, RowPB, }; use crate::manager::DatabaseUser; use crate::services::cell::AtomicCellDataCache; @@ -16,7 +16,9 @@ use crate::services::filter::FilterType; use crate::services::persistence::rev_sqlite::{ SQLiteDatabaseRevisionSnapshotPersistence, SQLiteDatabaseViewRevisionPersistence, }; -use database_model::{FieldRevision, FilterRevision, RowChangeset, RowRevision, SortRevision}; +use database_model::{ + FieldRevision, FilterRevision, LayoutRevision, RowChangeset, RowRevision, SortRevision, +}; use flowy_client_sync::client_database::DatabaseViewRevisionPad; use flowy_error::FlowyResult; use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration}; @@ -208,6 +210,24 @@ impl DatabaseViews { view_editor.v_get_group(group_id).await } + pub async fn get_layout_setting( + &self, + view_id: &str, + layout_ty: &LayoutRevision, + ) -> FlowyResult { + let view_editor = self.get_view_editor(view_id).await?; + view_editor.v_get_layout_settings(layout_ty).await + } + + pub async fn set_layout_setting( + &self, + view_id: &str, + layout_setting: LayoutSettingParams, + ) -> FlowyResult<()> { + let view_editor = self.get_view_editor(view_id).await?; + view_editor.v_set_layout_settings(layout_setting).await + } + pub async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> { let view_editor = self.get_view_editor(¶ms.view_id).await?; view_editor.v_initialize_new_group(params).await diff --git a/frontend/rust-lib/flowy-database/src/services/database_view/trait_impl.rs b/frontend/rust-lib/flowy-database/src/services/database_view/trait_impl.rs index ecf5b5c77d..4efcfc4c8a 100644 --- a/frontend/rust-lib/flowy-database/src/services/database_view/trait_impl.rs +++ b/frontend/rust-lib/flowy-database/src/services/database_view/trait_impl.rs @@ -1,4 +1,4 @@ -use crate::entities::{CalendarSettingsParams, DatabaseViewSettingPB, LayoutSettingPB}; +use crate::entities::{DatabaseViewSettingPB, LayoutSettingPB}; use crate::services::database_view::{get_cells_for_field, DatabaseViewData}; use crate::services::field::RowSingleCellData; use crate::services::filter::{FilterController, FilterDelegate, FilterType}; @@ -7,8 +7,8 @@ use crate::services::row::DatabaseBlockRowRevision; use crate::services::sort::{SortDelegate, SortType}; use bytes::Bytes; use database_model::{ - FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, LayoutRevision, - RowRevision, SortRevision, + CalendarLayoutSetting, FieldRevision, FieldTypeRevision, FilterRevision, + GroupConfigurationRevision, LayoutRevision, RowRevision, SortRevision, }; use flowy_client_sync::client_database::{DatabaseViewRevisionChangeset, DatabaseViewRevisionPad}; use flowy_client_sync::make_operations_from_revisions; @@ -153,7 +153,7 @@ pub fn make_database_view_setting( LayoutRevision::Board => {}, LayoutRevision::Calendar => { layout_settings.calendar = view_pad - .get_layout_setting::(&layout_type) + .get_layout_setting::(&layout_type) .map(|params| params.into()); }, } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs index d7de02e9ef..d7dd7871ec 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs @@ -11,6 +11,10 @@ pub const UNCHECK: &str = "No"; pub struct CheckboxCellData(String); impl CheckboxCellData { + pub fn into_inner(self) -> bool { + self.is_check() + } + pub fn is_check(&self) -> bool { self.0 == CHECK } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs index 78c4d7e08e..afaa230d50 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs @@ -3,8 +3,9 @@ mod tests { use crate::entities::FieldType; use crate::services::cell::{CellDataChangeset, CellDataDecoder}; - use crate::services::field::*; - // use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOptionPB, TimeFormat}; + use crate::services::field::{ + DateCellChangeset, DateFormat, DateTypeOptionPB, FieldBuilder, TimeFormat, TypeOptionCellData, + }; use chrono::format::strftime::StrftimeItems; use chrono::{FixedOffset, NaiveDateTime}; use database_model::FieldRevision; @@ -18,16 +19,44 @@ mod tests { type_option.date_format = date_format; match date_format { DateFormat::Friendly => { - assert_date(&type_option, 1647251762, None, "Mar 14,2022", &field_rev); + assert_date( + &type_option, + 1647251762, + None, + "Mar 14,2022", + false, + &field_rev, + ); }, DateFormat::US => { - assert_date(&type_option, 1647251762, None, "2022/03/14", &field_rev); + assert_date( + &type_option, + 1647251762, + None, + "2022/03/14", + false, + &field_rev, + ); }, DateFormat::ISO => { - assert_date(&type_option, 1647251762, None, "2022-03-14", &field_rev); + assert_date( + &type_option, + 1647251762, + None, + "2022-03-14", + false, + &field_rev, + ); }, DateFormat::Local => { - assert_date(&type_option, 1647251762, None, "03/14/2022", &field_rev); + assert_date( + &type_option, + 1647251762, + None, + "03/14/2022", + false, + &field_rev, + ); }, } } @@ -41,25 +70,56 @@ mod tests { for time_format in TimeFormat::iter() { type_option.time_format = time_format; - type_option.include_time = true; match time_format { TimeFormat::TwentyFourHour => { - assert_date(&type_option, 1653609600, None, "May 27,2022", &field_rev); + assert_date( + &type_option, + 1653609600, + None, + "May 27,2022 00:00", + true, + &field_rev, + ); + assert_date( + &type_option, + 1653609600, + Some("9:00".to_owned()), + "May 27,2022 09:00", + true, + &field_rev, + ); assert_date( &type_option, 1653609600, Some("23:00".to_owned()), "May 27,2022 23:00", + true, &field_rev, ); }, TimeFormat::TwelveHour => { - assert_date(&type_option, 1653609600, None, "May 27,2022", &field_rev); + assert_date( + &type_option, + 1653609600, + None, + "May 27,2022 12:00 AM", + true, + &field_rev, + ); + assert_date( + &type_option, + 1653609600, + Some("9:00 AM".to_owned()), + "May 27,2022 09:00 AM", + true, + &field_rev, + ); assert_date( &type_option, 1653609600, Some("11:23 pm".to_owned()), "May 27,2022 11:23 PM", + true, &field_rev, ); }, @@ -72,14 +132,13 @@ mod tests { let type_option = DateTypeOptionPB::default(); let field_type = FieldType::DateTime; let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_date(&type_option, "abc", None, "", &field_rev); + assert_date(&type_option, "abc", None, "", false, &field_rev); } #[test] #[should_panic] fn date_type_option_invalid_include_time_str_test() { - let mut type_option = DateTypeOptionPB::new(); - type_option.include_time = true; + let type_option = DateTypeOptionPB::new(); let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); assert_date( @@ -87,31 +146,46 @@ mod tests { 1653609600, Some("1:".to_owned()), "May 27,2022 01:00", + true, &field_rev, ); } #[test] fn date_type_option_empty_include_time_str_test() { - let mut type_option = DateTypeOptionPB::new(); - type_option.include_time = true; + let type_option = DateTypeOptionPB::new(); let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); assert_date( &type_option, 1653609600, Some("".to_owned()), - "May 27,2022", + "May 27,2022 00:00", + true, &field_rev, ); } - /// The default time format is TwentyFourHour, so the include_time_str in twelve_hours_format will cause parser error. + #[test] + fn date_type_midnight_include_time_str_test() { + let type_option = DateTypeOptionPB::new(); + let field_type = FieldType::DateTime; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_date( + &type_option, + 1653609600, + Some("00:00".to_owned()), + "May 27,2022 00:00", + true, + &field_rev, + ); + } + + /// The default time format is TwentyFourHour, so the include_time_str in twelve_hours_format will cause parser error. #[test] #[should_panic] fn date_type_option_twelve_hours_include_time_str_in_twenty_four_hours_format() { - let mut type_option = DateTypeOptionPB::new(); - type_option.include_time = true; + let type_option = DateTypeOptionPB::new(); let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); assert_date( @@ -119,6 +193,25 @@ mod tests { 1653609600, Some("1:00 am".to_owned()), "May 27,2022 01:00 AM", + true, + &field_rev, + ); + } + + // Attempting to parse include_time_str as TwelveHour when TwentyFourHour format is given should cause parser error. + #[test] + #[should_panic] + fn date_type_option_twenty_four_hours_include_time_str_in_twelve_hours_format() { + let mut type_option = DateTypeOptionPB::new(); + type_option.time_format = TimeFormat::TwelveHour; + let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + + assert_date( + &type_option, + 1653609600, + Some("20:00".to_owned()), + "May 27,2022 08:00 PM", + true, &field_rev, ); } @@ -154,17 +247,19 @@ mod tests { timestamp: T, include_time_str: Option, expected_str: &str, + include_time: bool, field_rev: &FieldRevision, ) { let changeset = DateCellChangeset { date: Some(timestamp.to_string()), time: include_time_str, is_utc: false, + include_time: Some(include_time), }; let (cell_str, _) = type_option.apply_changeset(changeset, None).unwrap(); assert_eq!( - decode_cell_data(cell_str, type_option, field_rev), + decode_cell_data(cell_str, type_option, include_time, field_rev), expected_str.to_owned(), ); } @@ -172,13 +267,14 @@ mod tests { fn decode_cell_data( cell_str: String, type_option: &DateTypeOptionPB, + include_time: bool, field_rev: &FieldRevision, ) -> String { let decoded_data = type_option .decode_cell_str(cell_str, &FieldType::DateTime, field_rev) .unwrap(); let decoded_data = type_option.convert_to_protobuf(decoded_data); - if type_option.include_time { + if include_time { format!("{} {}", decoded_data.date, decoded_data.time) .trim_end() .to_owned() diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs index d3d351c9cd..431ee508cd 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs @@ -8,7 +8,7 @@ use crate::services::field::{ }; use bytes::Bytes; use chrono::format::strftime::StrftimeItems; -use chrono::{NaiveDateTime, Timelike}; +use chrono::NaiveDateTime; use database_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; use flowy_derive::ProtoBuf; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; @@ -58,97 +58,59 @@ impl DateTypeOptionPB { Self::default() } - fn today_desc_from_timestamp>(&self, timestamp: T) -> DateCellDataPB { - let timestamp = timestamp.into(); - let native = chrono::NaiveDateTime::from_timestamp_opt(timestamp, 0); - if native.is_none() { + fn today_desc_from_timestamp(&self, cell_data: DateCellData) -> DateCellDataPB { + let timestamp = cell_data.timestamp.unwrap_or_default(); + let include_time = cell_data.include_time; + + let naive = chrono::NaiveDateTime::from_timestamp_opt(timestamp, 0); + if naive.is_none() { return DateCellDataPB::default(); } - let native = native.unwrap(); - if native.timestamp() == 0 { + let naive = naive.unwrap(); + if timestamp == 0 { return DateCellDataPB::default(); } - - let time = native.time(); - let has_time = time.hour() != 0 || time.second() != 0; - - let utc = self.utc_date_time_from_native(native); let fmt = self.date_format.format_str(); - let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt))); + let date = format!("{}", naive.format_with_items(StrftimeItems::new(fmt))); - let mut time = "".to_string(); - if has_time && self.include_time { - let fmt = format!( - "{}{}", - self.date_format.format_str(), - self.time_format.format_str() - ); - time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, ""); - } + let time = if include_time { + let fmt = self.time_format.format_str(); + format!("{}", naive.format_with_items(StrftimeItems::new(&fmt))) + } else { + "".to_string() + }; - let timestamp = native.timestamp(); DateCellDataPB { date, time, + include_time, timestamp, } } - fn date_fmt(&self, time: &Option) -> String { - if self.include_time { - match time.as_ref() { - None => self.date_format.format_str().to_string(), - Some(time_str) => { - if time_str.is_empty() { - self.date_format.format_str().to_string() - } else { - format!( - "{} {}", - self.date_format.format_str(), - self.time_format.format_str() - ) - } - }, - } - } else { - self.date_format.format_str().to_string() - } - } - fn timestamp_from_utc_with_time( &self, - utc: &chrono::DateTime, - time: &Option, + naive_date: &NaiveDateTime, + time_str: &Option, ) -> FlowyResult { - if let Some(time_str) = time.as_ref() { + if let Some(time_str) = time_str.as_ref() { if !time_str.is_empty() { - let date_str = format!( - "{}{}", - utc.format_with_items(StrftimeItems::new(self.date_format.format_str())), - &time_str - ); + let naive_time = + chrono::NaiveTime::parse_from_str(&time_str, self.time_format.format_str()); - return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) { - Ok(native) => { - let utc = self.utc_date_time_from_native(native); - Ok(utc.timestamp()) + match naive_time { + Ok(naive_time) => { + return Ok(naive_date.date().and_time(naive_time).timestamp()); }, Err(_e) => { - let msg = format!("Parse {} failed", date_str); - Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg)) + let msg = format!("Parse {} failed", time_str); + return Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg)); }, }; } } - Ok(utc.timestamp()) - } - - fn utc_date_time_from_native( - &self, - naive: chrono::NaiveDateTime, - ) -> chrono::DateTime { - chrono::DateTime::::from_utc(naive, chrono::Utc) + Ok(naive_date.timestamp()) } } @@ -181,25 +143,40 @@ impl CellDataChangeset for DateTypeOptionPB { fn apply_changeset( &self, changeset: ::CellChangeset, - _type_cell_data: Option, + type_cell_data: Option, ) -> FlowyResult<(String, ::CellData)> { - let cell_data = match changeset.date_timestamp() { - None => 0, - Some(date_timestamp) => match (self.include_time, changeset.time) { - (true, Some(time)) => { - let time = Some(time.trim().to_uppercase()); - let native = NaiveDateTime::from_timestamp_opt(date_timestamp, 0); - if let Some(native) = native { - let utc = self.utc_date_time_from_native(native); - self.timestamp_from_utc_with_time(&utc, &time)? - } else { - date_timestamp - } - }, - _ => date_timestamp, + let (timestamp, include_time) = match type_cell_data { + None => (None, false), + Some(type_cell_data) => { + let cell_data = DateCellData::from_cell_str(&type_cell_data.cell_str).unwrap_or_default(); + (cell_data.timestamp, cell_data.include_time) }, }; - let date_cell_data = DateCellData(Some(cell_data)); + + let include_time = match changeset.include_time { + None => include_time, + Some(include_time) => include_time, + }; + let timestamp = match changeset.date_timestamp() { + None => timestamp, + Some(date_timestamp) => match (include_time, changeset.time) { + (true, Some(time)) => { + let time = Some(time.trim().to_uppercase()); + let naive = NaiveDateTime::from_timestamp_opt(date_timestamp, 0); + if let Some(naive) = naive { + Some(self.timestamp_from_utc_with_time(&naive, &time)?) + } else { + Some(date_timestamp) + } + }, + _ => Some(date_timestamp), + }, + }; + + let date_cell_data = DateCellData { + timestamp, + include_time, + }; Ok((date_cell_data.to_string(), date_cell_data)) } } @@ -215,7 +192,7 @@ impl TypeOptionCellDataFilter for DateTypeOptionPB { return true; } - filter.is_visible(cell_data.0) + filter.is_visible(cell_data.timestamp) } } @@ -225,7 +202,7 @@ impl TypeOptionCellDataCompare for DateTypeOptionPB { cell_data: &::CellData, other_cell_data: &::CellData, ) -> Ordering { - match (cell_data.0, other_cell_data.0) { + match (cell_data.timestamp, other_cell_data.timestamp) { (Some(left), Some(right)) => left.cmp(&right), (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs index fd11207bef..f1b888dcaf 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::entities::CellIdPB; use crate::services::cell::{ CellProtobufBlobParser, DecodedCellData, FromCellChangesetString, FromCellString, @@ -6,6 +8,7 @@ use crate::services::cell::{ use bytes::Bytes; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::{internal_error, FlowyResult}; +use serde::de::Visitor; use serde::{Deserialize, Serialize}; use strum_macros::EnumIter; @@ -19,6 +22,9 @@ pub struct DateCellDataPB { #[pb(index = 3)] pub timestamp: i64, + + #[pb(index = 4)] + pub include_time: bool, } #[derive(Clone, Debug, Default, ProtoBuf)] @@ -32,7 +38,10 @@ pub struct DateChangesetPB { #[pb(index = 3, one_of)] pub time: Option, - #[pb(index = 4)] + #[pb(index = 4, one_of)] + pub include_time: Option, + + #[pb(index = 5)] pub is_utc: bool, } @@ -40,6 +49,7 @@ pub struct DateChangesetPB { pub struct DateCellChangeset { pub date: Option, pub time: Option, + pub include_time: Option, pub is_utc: bool, } @@ -71,18 +81,74 @@ impl ToCellChangesetString for DateCellChangeset { } } -#[derive(Default, Clone, Debug)] -pub struct DateCellData(pub Option); - -impl std::convert::From for i64 { - fn from(timestamp: DateCellData) -> Self { - timestamp.0.unwrap_or(0) - } +#[derive(Default, Clone, Debug, Serialize)] +pub struct DateCellData { + pub timestamp: Option, + pub include_time: bool, } -impl std::convert::From for Option { - fn from(timestamp: DateCellData) -> Self { - timestamp.0 +impl<'de> serde::Deserialize<'de> for DateCellData { + fn deserialize(deserializer: D) -> core::result::Result + where + D: serde::Deserializer<'de>, + { + struct DateCellVisitor(); + + impl<'de> Visitor<'de> for DateCellVisitor { + type Value = DateCellData; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str( + "DateCellData with type: str containing either an integer timestamp or the JSON representation", + ) + } + + fn visit_i64(self, value: i64) -> Result + where + E: serde::de::Error, + { + Ok(DateCellData { + timestamp: Some(value), + include_time: false, + }) + } + + fn visit_u64(self, value: u64) -> Result + where + E: serde::de::Error, + { + self.visit_i64(value as i64) + } + + fn visit_map(self, mut map: M) -> Result + where + M: serde::de::MapAccess<'de>, + { + let mut timestamp: Option = None; + let mut include_time: Option = None; + + while let Some(key) = map.next_key()? { + match key { + "timestamp" => { + timestamp = map.next_value()?; + }, + "include_time" => { + include_time = map.next_value()?; + }, + _ => {}, + } + } + + let include_time = include_time.unwrap_or(false); + + Ok(DateCellData { + timestamp, + include_time, + }) + } + } + + deserializer.deserialize_any(DateCellVisitor()) } } @@ -91,17 +157,14 @@ impl FromCellString for DateCellData { where Self: Sized, { - let num = s.parse::().ok(); - Ok(DateCellData(num)) + let result: DateCellData = serde_json::from_str(s).unwrap(); + Ok(result) } } impl ToString for DateCellData { fn to_string(&self) -> String { - match self.0 { - None => "".to_string(), - Some(val) => val.to_string(), - } + serde_json::to_string(self).unwrap() } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs index 0e63708c6c..4099338acc 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs @@ -22,6 +22,21 @@ mod tests { ), "Mar 14,2022" ); + + let data = DateCellData { + timestamp: Some(1647251762), + include_time: true, + }; + + assert_eq!( + stringify_cell_data( + data.to_string(), + &FieldType::RichText, + &field_type, + &field_rev + ), + "Mar 14,2022" + ); } // Test parser the cell data which field's type is FieldType::SingleSelect to cell data diff --git a/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs b/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs index 24cec2cf4d..bc78c0b0af 100644 --- a/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs +++ b/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs @@ -1,8 +1,10 @@ use crate::services::cell::{ insert_checkbox_cell, insert_date_cell, insert_number_cell, insert_select_option_cell, - insert_text_cell, insert_url_cell, + insert_text_cell, insert_url_cell, FromCellString, }; +use crate::entities::FieldType; +use crate::services::field::{CheckboxCellData, SelectOptionIds}; use database_model::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT}; use indexmap::IndexMap; use std::collections::HashMap; @@ -16,7 +18,15 @@ pub struct RowRevisionBuilder { impl RowRevisionBuilder { pub fn new(block_id: &str, fields: Vec>) -> Self { - let field_rev_map = fields + Self::new_with_data(block_id, fields, Default::default()) + } + + pub fn new_with_data( + block_id: &str, + field_revs: Vec>, + cell_data_by_field_id: HashMap, + ) -> Self { + let field_rev_map = field_revs .iter() .map(|field| (field.id.clone(), field.clone())) .collect::>>(); @@ -29,12 +39,49 @@ impl RowRevisionBuilder { }; let block_id = block_id.to_string(); - - Self { + let mut builder = Self { block_id, field_rev_map, payload, + }; + + for (field_id, cell_data) in cell_data_by_field_id { + if let Some(field_rev) = builder.field_rev_map.get(&field_id) { + let field_type: FieldType = field_rev.ty.into(); + match field_type { + FieldType::RichText => builder.insert_text_cell(&field_id, cell_data), + FieldType::Number => { + if let Ok(num) = cell_data.parse::() { + builder.insert_date_cell(&field_id, num) + } + }, + FieldType::DateTime => { + if let Ok(timestamp) = cell_data.parse::() { + builder.insert_date_cell(&field_id, timestamp) + } + }, + FieldType::MultiSelect | FieldType::SingleSelect => { + if let Ok(ids) = SelectOptionIds::from_cell_str(&cell_data) { + builder.insert_select_option_cell(&field_id, ids.into_inner()); + } + }, + FieldType::Checkbox => { + if let Ok(value) = CheckboxCellData::from_cell_str(&cell_data) { + builder.insert_checkbox_cell(&field_id, value.into_inner()); + } + }, + FieldType::URL => { + builder.insert_url_cell(&field_id, cell_data); + }, + FieldType::Checklist => { + if let Ok(ids) = SelectOptionIds::from_cell_str(&cell_data) { + builder.insert_select_option_cell(&field_id, ids.into_inner()); + } + }, + } + } } + builder } pub fn insert_text_cell(&mut self, field_id: &str, data: String) { diff --git a/frontend/rust-lib/flowy-database/src/util.rs b/frontend/rust-lib/flowy-database/src/util.rs index b96046d3ef..269105549a 100644 --- a/frontend/rust-lib/flowy-database/src/util.rs +++ b/frontend/rust-lib/flowy-database/src/util.rs @@ -1,7 +1,7 @@ use crate::entities::FieldType; use crate::services::field::*; use crate::services::row::RowRevisionBuilder; -use database_model::BuildDatabaseContext; +use database_model::{BuildDatabaseContext, CalendarLayoutSetting, LayoutRevision, LayoutSetting}; use flowy_client_sync::client_database::DatabaseBuilder; pub fn make_default_grid() -> BuildDatabaseContext { @@ -80,7 +80,7 @@ pub fn make_default_calendar() -> BuildDatabaseContext { let mut database_builder = DatabaseBuilder::new(); // text let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Description") + .name("Title") .visibility(true) .primary(true) .build(); @@ -92,6 +92,7 @@ pub fn make_default_calendar() -> BuildDatabaseContext { .name("Date") .visibility(true) .build(); + let date_field_id = date_field.id.clone(); database_builder.add_field(date_field); // single select @@ -101,6 +102,12 @@ pub fn make_default_calendar() -> BuildDatabaseContext { .visibility(true) .build(); database_builder.add_field(multi_select_field); + + let calendar_layout_setting = CalendarLayoutSetting::new(date_field_id); + let mut layout_setting = LayoutSetting::new(); + let calendar_setting = serde_json::to_string(&calendar_layout_setting).unwrap(); + layout_setting.insert(LayoutRevision::Calendar, calendar_setting); + database_builder.set_layout_setting(layout_setting); database_builder.build() } diff --git a/frontend/rust-lib/flowy-database/tests/database/block_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/block_test/script.rs index 9f47a590d1..da13700b4d 100644 --- a/frontend/rust-lib/flowy-database/tests/database/block_test/script.rs +++ b/frontend/rust-lib/flowy-database/tests/database/block_test/script.rs @@ -4,7 +4,7 @@ use crate::database::database_editor::DatabaseEditorTest; use database_model::{ DatabaseBlockMetaRevision, DatabaseBlockMetaRevisionChangeset, RowChangeset, RowRevision, }; -use flowy_database::entities::{CellIdParams, CreateRowParams, FieldType, LayoutTypePB, RowPB}; +use flowy_database::entities::{CellIdParams, CreateRowParams, FieldType, RowPB}; use flowy_database::services::field::*; use flowy_database::services::row::DatabaseBlockRow; use std::collections::HashMap; @@ -81,7 +81,7 @@ impl DatabaseRowTest { view_id: self.editor.database_id.clone(), start_row_id: None, group_id: None, - layout: LayoutTypePB::Grid, + cell_data_by_field_id: None, }; let row_order = self.editor.create_row(params).await.unwrap(); self diff --git a/frontend/rust-lib/flowy-database/tests/database/block_test/util.rs b/frontend/rust-lib/flowy-database/tests/database/block_test/util.rs index 13731421a3..f2d1fc9a89 100644 --- a/frontend/rust-lib/flowy-database/tests/database/block_test/util.rs +++ b/frontend/rust-lib/flowy-database/tests/database/block_test/util.rs @@ -7,8 +7,6 @@ use flowy_database::services::field::{ use flowy_database::services::row::RowRevisionBuilder; use std::sync::Arc; -use strum::EnumCount; - pub struct DatabaseRowTestBuilder { field_revs: Vec>, inner_builder: RowRevisionBuilder, @@ -16,7 +14,6 @@ pub struct DatabaseRowTestBuilder { impl DatabaseRowTestBuilder { pub fn new(block_id: String, field_revs: Vec>) -> Self { - assert_eq!(field_revs.len(), FieldType::COUNT); let inner_builder = RowRevisionBuilder::new(&block_id, field_revs.clone()); Self { field_revs, @@ -46,6 +43,7 @@ impl DatabaseRowTestBuilder { date: Some(data.to_string()), time: None, is_utc: true, + include_time: Some(false), }) .unwrap(); let date_field = self.field_rev_with_type(&FieldType::DateTime); diff --git a/frontend/rust-lib/flowy-database/tests/database/database_editor.rs b/frontend/rust-lib/flowy-database/tests/database/database_editor.rs index 41f70c9d3d..f3d0635531 100644 --- a/frontend/rust-lib/flowy-database/tests/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database/tests/database/database_editor.rs @@ -33,6 +33,10 @@ impl DatabaseEditorTest { Self::new(LayoutTypePB::Board).await } + pub async fn new_calendar() -> Self { + Self::new(LayoutTypePB::Calendar).await + } + pub async fn new(layout: LayoutTypePB) -> Self { let sdk = FlowySDKTest::default(); let _ = sdk.init_user().await; @@ -64,10 +68,6 @@ impl DatabaseEditorTest { let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap(); assert_eq!(block_meta_revs.len(), 1); - // It seems like you should add the field in the make_test_grid() function. - // Because we assert the initialize count of the fields is equal to FieldType::COUNT. - assert_eq!(field_revs.len(), FieldType::COUNT); - let view_id = test.view.id; let app_id = test.app.id; Self { diff --git a/frontend/rust-lib/flowy-database/tests/database/field_test/util.rs b/frontend/rust-lib/flowy-database/tests/database/field_test/util.rs index 6fa0a93036..d13145a14e 100644 --- a/frontend/rust-lib/flowy-database/tests/database/field_test/util.rs +++ b/frontend/rust-lib/flowy-database/tests/database/field_test/util.rs @@ -64,6 +64,7 @@ pub fn make_date_cell_string(s: &str) -> String { date: Some(s.to_string()), time: None, is_utc: true, + include_time: Some(false), }) .unwrap() } diff --git a/frontend/rust-lib/flowy-database/tests/database/group_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/group_test/script.rs index bf6abcba27..a7496c0c6b 100644 --- a/frontend/rust-lib/flowy-database/tests/database/group_test/script.rs +++ b/frontend/rust-lib/flowy-database/tests/database/group_test/script.rs @@ -1,7 +1,7 @@ use crate::database::database_editor::DatabaseEditorTest; use database_model::{FieldRevision, RowChangeset}; use flowy_database::entities::{ - CreateRowParams, FieldType, GroupPB, LayoutTypePB, MoveGroupParams, MoveGroupRowParams, RowPB, + CreateRowParams, FieldType, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB, }; use flowy_database::services::cell::{ delete_select_option_cell, insert_select_option_cell, insert_url_cell, @@ -130,7 +130,7 @@ impl DatabaseGroupTest { view_id: self.view_id.clone(), start_row_id: None, group_id: Some(group.group_id.clone()), - layout: LayoutTypePB::Board, + cell_data_by_field_id: None, }; let _ = self.editor.create_row(params).await.unwrap(); }, diff --git a/frontend/rust-lib/flowy-database/tests/database/layout_test/mod.rs b/frontend/rust-lib/flowy-database/tests/database/layout_test/mod.rs new file mode 100644 index 0000000000..63d424afaf --- /dev/null +++ b/frontend/rust-lib/flowy-database/tests/database/layout_test/mod.rs @@ -0,0 +1,2 @@ +mod script; +mod test; diff --git a/frontend/rust-lib/flowy-database/tests/database/layout_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/layout_test/script.rs new file mode 100644 index 0000000000..1172899d3d --- /dev/null +++ b/frontend/rust-lib/flowy-database/tests/database/layout_test/script.rs @@ -0,0 +1,86 @@ +use crate::database::database_editor::DatabaseEditorTest; +use database_model::{CalendarLayoutSetting, FieldRevision, LayoutRevision}; +use flowy_database::entities::FieldType; +use std::sync::Arc; + +pub enum LayoutScript { + AssertCalendarLayoutSetting { expected: CalendarLayoutSetting }, + GetCalendarEvents, +} + +pub struct DatabaseLayoutTest { + database_test: DatabaseEditorTest, +} + +impl DatabaseLayoutTest { + pub async fn new_calendar() -> Self { + let database_test = DatabaseEditorTest::new_calendar().await; + Self { database_test } + } + + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn get_first_date_field(&self) -> Arc { + self + .database_test + .get_first_field_rev(FieldType::DateTime) + .clone() + } + + pub async fn run_script(&mut self, script: LayoutScript) { + match script { + LayoutScript::AssertCalendarLayoutSetting { expected } => { + let view_id = self.database_test.view_id.clone(); + let layout_ty = LayoutRevision::Calendar; + + let calendar_setting = self + .database_test + .editor + .get_layout_setting(&view_id, layout_ty) + .await + .unwrap() + .calendar + .unwrap(); + + assert_eq!(calendar_setting.layout_ty, expected.layout_ty); + assert_eq!( + calendar_setting.first_day_of_week, + expected.first_day_of_week + ); + assert_eq!(calendar_setting.show_weekends, expected.show_weekends); + }, + LayoutScript::GetCalendarEvents => { + let events = self + .database_test + .editor + .get_all_calendar_events(&self.database_test.view_id) + .await; + assert_eq!(events.len(), 5); + + for (index, event) in events.into_iter().enumerate() { + if index == 0 { + assert_eq!(event.title, "A"); + assert_eq!(event.timestamp, 1678090778); + } + + if index == 1 { + assert_eq!(event.title, "B"); + assert_eq!(event.timestamp, 1677917978); + } + if index == 2 { + assert_eq!(event.title, "C"); + assert_eq!(event.timestamp, 1679213978); + } + if index == 4 { + assert_eq!(event.title, "E"); + assert_eq!(event.timestamp, 1678695578); + } + } + }, + } + } +} diff --git a/frontend/rust-lib/flowy-database/tests/database/layout_test/test.rs b/frontend/rust-lib/flowy-database/tests/database/layout_test/test.rs new file mode 100644 index 0000000000..6e55134721 --- /dev/null +++ b/frontend/rust-lib/flowy-database/tests/database/layout_test/test.rs @@ -0,0 +1,21 @@ +use crate::database::layout_test::script::DatabaseLayoutTest; +use crate::database::layout_test::script::LayoutScript::*; +use database_model::CalendarLayoutSetting; + +#[tokio::test] +async fn calendar_initial_layout_setting_test() { + let mut test = DatabaseLayoutTest::new_calendar().await; + let date_field = test.get_first_date_field().await; + let default_calendar_setting = CalendarLayoutSetting::new(date_field.id.clone()); + let scripts = vec![AssertCalendarLayoutSetting { + expected: default_calendar_setting, + }]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn calendar_get_events_test() { + let mut test = DatabaseLayoutTest::new_calendar().await; + let scripts = vec![GetCalendarEvents]; + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-database/tests/database/mock_data/calendar_mock_data.rs b/frontend/rust-lib/flowy-database/tests/database/mock_data/calendar_mock_data.rs index 3aa7c1b984..b418649f72 100644 --- a/frontend/rust-lib/flowy-database/tests/database/mock_data/calendar_mock_data.rs +++ b/frontend/rust-lib/flowy-database/tests/database/mock_data/calendar_mock_data.rs @@ -1,6 +1,104 @@ -use database_model::BuildDatabaseContext; +use crate::database::block_test::util::DatabaseRowTestBuilder; +use database_model::{BuildDatabaseContext, CalendarLayoutSetting, LayoutRevision, LayoutSetting}; +use flowy_client_sync::client_database::DatabaseBuilder; +use flowy_database::entities::FieldType; +use flowy_database::services::field::{ + DateTypeOptionBuilder, FieldBuilder, MultiSelectTypeOptionBuilder, RichTextTypeOptionBuilder, +}; + +use strum::IntoEnumIterator; // Calendar unit test mock data pub fn make_test_calendar() -> BuildDatabaseContext { - todo!() + let mut database_builder = DatabaseBuilder::new(); + // text + let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Title") + .visibility(true) + .primary(true) + .build(); + let _text_field_id = text_field.id.clone(); + database_builder.add_field(text_field); + + // date + let date_type_option = DateTypeOptionBuilder::default(); + let date_field = FieldBuilder::new(date_type_option) + .name("Date") + .visibility(true) + .build(); + let date_field_id = date_field.id.clone(); + database_builder.add_field(date_field); + + // single select + let multi_select_type_option = MultiSelectTypeOptionBuilder::default(); + let multi_select_field = FieldBuilder::new(multi_select_type_option) + .name("Tags") + .visibility(true) + .build(); + database_builder.add_field(multi_select_field); + + let calendar_layout_setting = CalendarLayoutSetting::new(date_field_id); + let mut layout_setting = LayoutSetting::new(); + let calendar_setting = serde_json::to_string(&calendar_layout_setting).unwrap(); + layout_setting.insert(LayoutRevision::Calendar, calendar_setting); + database_builder.set_layout_setting(layout_setting); + + for i in 0..5 { + let block_id = database_builder.block_id().to_owned(); + let field_revs = database_builder.field_revs().clone(); + let mut row_builder = DatabaseRowTestBuilder::new(block_id.clone(), field_revs); + match i { + 0 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("A"), + FieldType::DateTime => row_builder.insert_date_cell("1678090778"), + _ => "".to_owned(), + }; + } + }, + 1 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("B"), + FieldType::DateTime => row_builder.insert_date_cell("1677917978"), + _ => "".to_owned(), + }; + } + }, + 2 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("C"), + FieldType::DateTime => row_builder.insert_date_cell("1679213978"), + _ => "".to_owned(), + }; + } + }, + 3 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("D"), + FieldType::DateTime => row_builder.insert_date_cell("1678695578"), + _ => "".to_owned(), + }; + } + }, + 4 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("E"), + FieldType::DateTime => row_builder.insert_date_cell("1678695578"), + _ => "".to_owned(), + }; + } + }, + _ => {}, + } + + let row_rev = row_builder.build(); + database_builder.add_row(row_rev); + } + + database_builder.build() } diff --git a/frontend/rust-lib/flowy-database/tests/database/mod.rs b/frontend/rust-lib/flowy-database/tests/database/mod.rs index b3aaf61ab5..0cb845d78c 100644 --- a/frontend/rust-lib/flowy-database/tests/database/mod.rs +++ b/frontend/rust-lib/flowy-database/tests/database/mod.rs @@ -5,6 +5,7 @@ mod database_ref_test; mod field_test; mod filter_test; mod group_test; +mod layout_test; mod snapshot_test; mod sort_test; diff --git a/frontend/rust-lib/flowy-error/src/code.rs b/frontend/rust-lib/flowy-error/src/code.rs index dd82d4bdfc..12e25b7dec 100644 --- a/frontend/rust-lib/flowy-error/src/code.rs +++ b/frontend/rust-lib/flowy-error/src/code.rs @@ -186,6 +186,9 @@ pub enum ErrorCode { #[error("Payload should not be empty")] UnexpectedEmptyPayload = 60, + + #[error("Only the date type can be used in calendar")] + UnexpectedCalendarFieldType = 61, } impl ErrorCode { diff --git a/frontend/rust-lib/flowy-error/src/errors.rs b/frontend/rust-lib/flowy-error/src/errors.rs index be02520fe5..46f078e2a8 100644 --- a/frontend/rust-lib/flowy-error/src/errors.rs +++ b/frontend/rust-lib/flowy-error/src/errors.rs @@ -82,6 +82,10 @@ impl FlowyError { static_flowy_error!(field_record_not_found, ErrorCode::FieldRecordNotFound); static_flowy_error!(payload_none, ErrorCode::UnexpectedEmptyPayload); static_flowy_error!(http, ErrorCode::HttpError); + static_flowy_error!( + unexpect_calendar_field_type, + ErrorCode::UnexpectedCalendarFieldType + ); } impl std::convert::From for FlowyError { diff --git a/frontend/scripts/makefile/protobuf.toml b/frontend/scripts/makefile/protobuf.toml index fff084fe22..463a1bd5f2 100644 --- a/frontend/scripts/makefile/protobuf.toml +++ b/frontend/scripts/makefile/protobuf.toml @@ -13,7 +13,7 @@ script_runner = "@shell" [tasks.install_tauri_protobuf.linux] script = """ -npm install -g protoc-gen-ts +sudo npm install -g protoc-gen-ts """ script_runner = "@shell" diff --git a/shared-lib/database-model/src/database_rev.rs b/shared-lib/database-model/src/database_rev.rs index e92abf83a8..b017ce6622 100644 --- a/shared-lib/database-model/src/database_rev.rs +++ b/shared-lib/database-model/src/database_rev.rs @@ -1,8 +1,9 @@ -use crate::DatabaseBlockRevision; +use crate::{DatabaseBlockRevision, LayoutSetting}; use bytes::Bytes; use indexmap::IndexMap; use nanoid::nanoid; use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; use std::sync::Arc; pub fn gen_database_id() -> String { @@ -195,6 +196,7 @@ pub struct BuildDatabaseContext { pub field_revs: Vec>, pub block_metas: Vec, pub blocks: Vec, + pub layout_setting: LayoutSetting, // String in JSON format. It can be deserialized into [GridViewRevision] pub database_view_data: String, @@ -223,3 +225,36 @@ impl std::convert::TryFrom for BuildDatabaseContext { } pub type FieldTypeRevision = u8; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CalendarLayoutSetting { + pub layout_ty: CalendarLayout, + pub first_day_of_week: i32, + pub show_weekends: bool, + pub show_week_numbers: bool, + pub layout_field_id: String, +} + +impl CalendarLayoutSetting { + pub fn new(layout_field_id: String) -> Self { + CalendarLayoutSetting { + layout_ty: CalendarLayout::default(), + first_day_of_week: DEFAULT_FIRST_DAY_OF_WEEK, + show_weekends: DEFAULT_SHOW_WEEKENDS, + show_week_numbers: DEFAULT_SHOW_WEEK_NUMBERS, + layout_field_id, + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum CalendarLayout { + #[default] + MonthLayout = 0, + WeekLayout = 1, + DayLayout = 2, +} + +pub const DEFAULT_FIRST_DAY_OF_WEEK: i32 = 0; +pub const DEFAULT_SHOW_WEEKENDS: bool = true; +pub const DEFAULT_SHOW_WEEK_NUMBERS: bool = true; diff --git a/shared-lib/database-model/src/view_rev.rs b/shared-lib/database-model/src/view_rev.rs index 934119b073..f2df22a605 100644 --- a/shared-lib/database-model/src/view_rev.rs +++ b/shared-lib/database-model/src/view_rev.rs @@ -47,8 +47,8 @@ pub struct DatabaseViewRevision { pub layout: LayoutRevision, #[serde(default)] - #[serde(skip_serializing_if = "LayoutSettings::is_empty")] - pub layout_settings: LayoutSettings, + #[serde(skip_serializing_if = "LayoutSetting::is_empty")] + pub layout_settings: LayoutSetting, #[serde(default)] pub filters: FilterConfiguration, @@ -90,18 +90,23 @@ impl DatabaseViewRevision { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(transparent)] -pub struct LayoutSettings { +pub struct LayoutSetting { #[serde(with = "indexmap::serde_seq")] inner: IndexMap, } -impl LayoutSettings { +impl LayoutSetting { + pub fn new() -> Self { + Self { + inner: Default::default(), + } + } pub fn is_empty(&self) -> bool { self.inner.is_empty() } } -impl std::ops::Deref for LayoutSettings { +impl std::ops::Deref for LayoutSetting { type Target = IndexMap; fn deref(&self) -> &Self::Target { @@ -109,7 +114,7 @@ impl std::ops::Deref for LayoutSettings { } } -impl std::ops::DerefMut for LayoutSettings { +impl std::ops::DerefMut for LayoutSetting { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } diff --git a/shared-lib/flowy-sync/src/errors.rs b/shared-lib/flowy-sync/src/errors.rs index 9958c3ed4a..cc2a822ea2 100644 --- a/shared-lib/flowy-sync/src/errors.rs +++ b/shared-lib/flowy-sync/src/errors.rs @@ -62,15 +62,15 @@ impl fmt::Display for SyncError { pub enum ErrorCode { DocumentIdInvalid = 0, DocumentNotfound = 1, - UndoFail = 200, - RedoFail = 201, - OutOfBound = 202, - RevisionConflict = 203, - RecordNotFound = 300, - CannotDeleteThePrimaryField = 301, - UnexpectedEmptyRevision = 302, - SerdeError = 999, - InternalError = 1000, + UndoFail = 2, + RedoFail = 3, + OutOfBound = 4, + RevisionConflict = 5, + RecordNotFound = 6, + CannotDeleteThePrimaryField = 7, + UnexpectedEmptyRevision = 8, + SerdeError = 100, + InternalError = 101, } impl std::convert::From for SyncError {