Merge branch 'feat/appflowy_tauri_3' into feat/appflowy_tauri_3

This commit is contained in:
Nathan.fooo 2023-03-04 19:18:17 +08:00 committed by GitHub
commit 0b2dc415af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
109 changed files with 1402 additions and 709 deletions

View File

@ -24,7 +24,6 @@
"svgviewer.showzoominout": true, "svgviewer.showzoominout": true,
"editor.wordWrapColumn": 80, "editor.wordWrapColumn": 80,
"editor.minimap.maxColumn": 140, "editor.minimap.maxColumn": 140,
"prettier.printWidth": 140,
"editor.wordWrap": "wordWrapColumn", "editor.wordWrap": "wordWrapColumn",
"dart.lineLength": 80, "dart.lineLength": 80,
"typescript.validate.enable": true, "typescript.validate.enable": true,

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

View File

@ -353,7 +353,15 @@
"smartEditFixSpelling": "Fix spelling", "smartEditFixSpelling": "Fix spelling",
"smartEditSummarize": "Summarize", "smartEditSummarize": "Summarize",
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI", "smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key" "smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
"cover": {
"changeCover": "Change Cover",
"colors": "Colors",
"images": "Images",
"abstract": "Abstract",
"addCover": "Add Cover",
"addLocalImage": "Add local image"
}
} }
}, },
"board": { "board": {
@ -371,4 +379,4 @@
"nextMonth": "Next Month" "nextMonth": "Next Month"
} }
} }
} }

View File

@ -1,118 +1,16 @@
part of 'cell_service.dart'; import 'dart:async';
import 'package:appflowy/plugins/database_view/application/field/field_listener.dart';
typedef TextCellController = CellController<String, String>; import 'package:appflowy_backend/log.dart';
typedef CheckboxCellController = CellController<String, String>; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
typedef NumberCellController = CellController<String, String>; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
typedef SelectOptionCellController import 'package:dartz/dartz.dart';
= CellController<SelectOptionCellDataPB, String>; import 'package:equatable/equatable.dart';
typedef ChecklistCellController import 'package:flutter/foundation.dart';
= CellController<SelectOptionCellDataPB, String>; import '../field/field_controller.dart';
typedef DateCellController = CellController<DateCellDataPB, CalendarData>; import '../field/field_service.dart';
typedef URLCellController = CellController<URLCellDataPB, String>; import '../field/type_option/type_option_context.dart';
import 'cell_listener.dart';
abstract class CellControllerBuilderDelegate { import 'cell_service.dart';
CellFieldNotifier buildFieldNotifier();
}
class CellControllerBuilder {
final CellIdentifier _cellId;
final CellCache _cellCache;
final CellControllerBuilderDelegate delegate;
CellControllerBuilder({
required this.delegate,
required CellIdentifier cellId,
required CellCache cellCache,
}) : _cellCache = cellCache,
_cellId = cellId;
CellController build() {
final cellFieldNotifier = delegate.buildFieldNotifier();
switch (_cellId.fieldType) {
case FieldType.Checkbox:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: StringCellDataParser(),
);
return TextCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
fieldNotifier: cellFieldNotifier,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.DateTime:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: DateCellDataParser(),
reloadOnFieldChanged: true,
);
return DateCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
fieldNotifier: cellFieldNotifier,
cellDataPersistence: DateCellDataPersistence(cellId: _cellId),
);
case FieldType.Number:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: StringCellDataParser(),
reloadOnFieldChanged: true,
);
return NumberCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
fieldNotifier: cellFieldNotifier,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.RichText:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: StringCellDataParser(),
);
return TextCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
fieldNotifier: cellFieldNotifier,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.MultiSelect:
case FieldType.SingleSelect:
case FieldType.Checklist:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: SelectOptionCellDataParser(),
reloadOnFieldChanged: true,
);
return SelectOptionCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
fieldNotifier: cellFieldNotifier,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.URL:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: URLCellDataParser(),
);
return URLCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
fieldNotifier: cellFieldNotifier,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
}
throw UnimplementedError;
}
}
/// IGridCellController is used to manipulate the cell and receive notifications. /// IGridCellController is used to manipulate the cell and receive notifications.
/// * Read/Write cell data /// * Read/Write cell data
@ -124,40 +22,19 @@ class CellControllerBuilder {
// ignore: must_be_immutable // ignore: must_be_immutable
class CellController<T, D> extends Equatable { class CellController<T, D> extends Equatable {
final CellIdentifier cellId; final CellIdentifier cellId;
final CellCache _cellsCache; final CellCache _cellCache;
final CellCacheKey _cacheKey; final CellCacheKey _cacheKey;
final FieldBackendService _fieldBackendSvc; final FieldBackendService _fieldBackendSvc;
final CellFieldNotifier _fieldNotifier; final SingleFieldListener _fieldListener;
final CellDataLoader<T> _cellDataLoader; final CellDataLoader<T> _cellDataLoader;
final CellDataPersistence<D> _cellDataPersistence; final CellDataPersistence<D> _cellDataPersistence;
CellListener? _cellListener; CellListener? _cellListener;
CellDataNotifier<T?>? _cellDataNotifier; CellDataNotifier<T?>? _cellDataNotifier;
bool isListening = false; VoidCallback? _onCellFieldChanged;
VoidCallback? _onFieldChangedFn;
Timer? _loadDataOperation; Timer? _loadDataOperation;
Timer? _saveDataOperation; Timer? _saveDataOperation;
bool _isDispose = false;
CellController({
required this.cellId,
required CellCache cellCache,
required CellFieldNotifier fieldNotifier,
required CellDataLoader<T> cellDataLoader,
required CellDataPersistence<D> cellDataPersistence,
}) : _cellsCache = cellCache,
_cellDataLoader = cellDataLoader,
_cellDataPersistence = cellDataPersistence,
_fieldNotifier = fieldNotifier,
_fieldBackendSvc = FieldBackendService(
viewId: cellId.viewId,
fieldId: cellId.fieldInfo.id,
),
_cacheKey = CellCacheKey(
rowId: cellId.rowId,
fieldId: cellId.fieldInfo.id,
);
String get viewId => cellId.viewId; String get viewId => cellId.viewId;
@ -169,34 +46,28 @@ class CellController<T, D> extends Equatable {
FieldType get fieldType => cellId.fieldInfo.fieldType; FieldType get fieldType => cellId.fieldInfo.fieldType;
/// Listen on the cell content or field changes CellController({
/// required this.cellId,
/// An optional [listenWhenOnCellChanged] can be implemented for more required CellCache cellCache,
/// granular control over when [listener] is called. required CellDataLoader<T> cellDataLoader,
/// [listenWhenOnCellChanged] will be invoked on each [onCellChanged] required CellDataPersistence<D> cellDataPersistence,
/// get called. }) : _cellCache = cellCache,
/// [listenWhenOnCellChanged] takes the previous `value` and current _cellDataLoader = cellDataLoader,
/// `value` and must return a [bool] which determines whether or not _cellDataPersistence = cellDataPersistence,
/// the [onCellChanged] function will be invoked. _fieldListener = SingleFieldListener(fieldId: cellId.fieldId),
/// [onCellChanged] is optional and if omitted, it will default to `true`. _fieldBackendSvc = FieldBackendService(
/// viewId: cellId.viewId,
VoidCallback? startListening({ fieldId: cellId.fieldInfo.id,
required void Function(T?) onCellChanged, ),
bool Function(T? oldValue, T? newValue)? listenWhenOnCellChanged, _cacheKey = CellCacheKey(
VoidCallback? onCellFieldChanged, rowId: cellId.rowId,
}) { fieldId: cellId.fieldInfo.id,
if (isListening) { ) {
Log.error("Already started. It seems like you should call clone first"); _cellDataNotifier = CellDataNotifier(value: _cellCache.get(_cacheKey));
return null; _cellListener = CellListener(
} rowId: cellId.rowId,
isListening = true; fieldId: cellId.fieldInfo.id,
_cellDataNotifier = CellDataNotifier(
value: _cellsCache.get(_cacheKey),
listenWhen: listenWhenOnCellChanged,
); );
_cellListener =
CellListener(rowId: cellId.rowId, fieldId: cellId.fieldInfo.id);
/// 1.Listen on user edit event and load the new cell data if needed. /// 1.Listen on user edit event and load the new cell data if needed.
/// For example: /// For example:
@ -205,7 +76,7 @@ class CellController<T, D> extends Equatable {
_cellListener?.start(onCellChanged: (result) { _cellListener?.start(onCellChanged: (result) {
result.fold( result.fold(
(_) { (_) {
_cellsCache.remove(_cacheKey); _cellCache.remove(_cacheKey);
_loadData(); _loadData();
}, },
(err) => Log.error(err), (err) => Log.error(err),
@ -213,20 +84,25 @@ class CellController<T, D> extends Equatable {
}); });
/// 2.Listen on the field event and load the cell data if needed. /// 2.Listen on the field event and load the cell data if needed.
_onFieldChangedFn = () { _fieldListener.start(onFieldChanged: (result) {
if (onCellFieldChanged != null) { result.fold((fieldPB) {
onCellFieldChanged(); /// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed
} /// For example:
/// 12 -> $12
if (_cellDataLoader.reloadOnFieldChanged) {
_loadData();
}
_onCellFieldChanged?.call();
}, (err) => Log.error(err));
});
}
/// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed /// Listen on the cell content or field changes
/// For example: VoidCallback? startListening({
/// 12 -> $12 required void Function(T?) onCellChanged,
if (_cellDataLoader.reloadOnFieldChanged) { VoidCallback? onCellFieldChanged,
_loadData(); }) {
} _onCellFieldChanged = onCellFieldChanged;
};
_fieldNotifier.register(_cacheKey, _onFieldChangedFn!);
/// Notify the listener, the cell data was changed. /// Notify the listener, the cell data was changed.
onCellChangedFn() => onCellChanged(_cellDataNotifier?.value); onCellChangedFn() => onCellChanged(_cellDataNotifier?.value);
@ -244,7 +120,7 @@ class CellController<T, D> extends Equatable {
/// The cell data will be read from the Cache first, and load from disk if it does not exist. /// The cell data will be read from the Cache first, and load from disk if it does not exist.
/// You can set [loadIfNotExist] to false (default is true) to disable loading the cell data. /// You can set [loadIfNotExist] to false (default is true) to disable loading the cell data.
T? getCellData({bool loadIfNotExist = true}) { T? getCellData({bool loadIfNotExist = true}) {
final data = _cellsCache.get(_cacheKey); final data = _cellCache.get(_cacheKey);
if (data == null && loadIfNotExist) { if (data == null && loadIfNotExist) {
_loadData(); _loadData();
} }
@ -294,9 +170,9 @@ class CellController<T, D> extends Equatable {
_loadDataOperation = Timer(const Duration(milliseconds: 10), () { _loadDataOperation = Timer(const Duration(milliseconds: 10), () {
_cellDataLoader.loadData().then((data) { _cellDataLoader.loadData().then((data) {
if (data != null) { if (data != null) {
_cellsCache.insert(_cacheKey, GridBaseCell(object: data)); _cellCache.insert(_cacheKey, GridBaseCell(object: data));
} else { } else {
_cellsCache.remove(_cacheKey); _cellCache.remove(_cacheKey);
} }
_cellDataNotifier?.value = data; _cellDataNotifier?.value = data;
@ -305,54 +181,17 @@ class CellController<T, D> extends Equatable {
} }
Future<void> dispose() async { Future<void> dispose() async {
if (_isDispose) {
Log.error("$this should only dispose once");
return;
}
_isDispose = true;
await _cellListener?.stop(); await _cellListener?.stop();
_loadDataOperation?.cancel(); _loadDataOperation?.cancel();
_saveDataOperation?.cancel(); _saveDataOperation?.cancel();
_cellDataNotifier?.dispose(); _cellDataNotifier?.dispose();
await _fieldListener.stop();
_cellDataNotifier = null; _cellDataNotifier = null;
if (_onFieldChangedFn != null) {
_fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!);
await _fieldNotifier.dispose();
_onFieldChangedFn = null;
}
} }
@override @override
List<Object> get props => List<Object> get props =>
[_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.fieldInfo.id]; [_cellCache.get(_cacheKey) ?? "", cellId.rowId + cellId.fieldInfo.id];
}
class GridCellFieldNotifierImpl extends ICellFieldNotifier {
final FieldController _fieldController;
OnReceiveUpdateFields? _onChangesetFn;
GridCellFieldNotifierImpl(FieldController cache) : _fieldController = cache;
@override
void onCellDispose() {
if (_onChangesetFn != null) {
_fieldController.removeListener(onChangesetListener: _onChangesetFn!);
_onChangesetFn = null;
}
}
@override
void onCellFieldChanged(void Function(FieldInfo) callback) {
_onChangesetFn = (List<FieldInfo> filedInfos) {
for (final field in filedInfos) {
callback(field);
}
};
_fieldController.addListener(
onReceiveFields: _onChangesetFn,
);
}
} }
class CellDataNotifier<T> extends ChangeNotifier { class CellDataNotifier<T> extends ChangeNotifier {

View File

@ -0,0 +1,108 @@
import 'package:appflowy_backend/protobuf/flowy-database/date_type_option_entities.pb.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:appflowy_backend/protobuf/flowy-database/url_type_option_entities.pb.dart';
import 'cell_controller.dart';
import 'cell_service.dart';
typedef TextCellController = CellController<String, String>;
typedef CheckboxCellController = CellController<String, String>;
typedef NumberCellController = CellController<String, String>;
typedef SelectOptionCellController
= CellController<SelectOptionCellDataPB, String>;
typedef ChecklistCellController
= CellController<SelectOptionCellDataPB, String>;
typedef DateCellController = CellController<DateCellDataPB, CalendarData>;
typedef URLCellController = CellController<URLCellDataPB, String>;
class CellControllerBuilder {
final CellIdentifier _cellId;
final CellCache _cellCache;
CellControllerBuilder({
required CellIdentifier cellId,
required CellCache cellCache,
}) : _cellCache = cellCache,
_cellId = cellId;
CellController build() {
switch (_cellId.fieldType) {
case FieldType.Checkbox:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: StringCellDataParser(),
);
return TextCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.DateTime:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: DateCellDataParser(),
reloadOnFieldChanged: true,
);
return DateCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
cellDataPersistence: DateCellDataPersistence(cellId: _cellId),
);
case FieldType.Number:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: StringCellDataParser(),
reloadOnFieldChanged: true,
);
return NumberCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.RichText:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: StringCellDataParser(),
);
return TextCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.MultiSelect:
case FieldType.SingleSelect:
case FieldType.Checklist:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: SelectOptionCellDataParser(),
reloadOnFieldChanged: true,
);
return SelectOptionCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.URL:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: URLCellDataParser(),
);
return URLCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
}
throw UnimplementedError;
}
}

View File

@ -1,63 +0,0 @@
import 'package:flutter/foundation.dart';
import '../field/field_controller.dart';
import 'cell_service.dart';
abstract class ICellFieldNotifier {
void onCellFieldChanged(void Function(FieldInfo) callback);
void onCellDispose();
}
/// DatabasePB's cell helper wrapper that enables each cell will get notified when the corresponding field was changed.
/// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen.
class CellFieldNotifier {
final ICellFieldNotifier notifier;
/// fieldId: {objectId: callback}
final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId =
{};
CellFieldNotifier({required this.notifier}) {
notifier.onCellFieldChanged(
(field) {
final map = _fieldListenerByFieldId[field.id];
if (map != null) {
for (final callbacks in map.values) {
for (final callback in callbacks) {
callback();
}
}
}
},
);
}
///
void register(CellCacheKey cacheKey, VoidCallback onFieldChanged) {
var map = _fieldListenerByFieldId[cacheKey.fieldId];
if (map == null) {
_fieldListenerByFieldId[cacheKey.fieldId] = {};
map = _fieldListenerByFieldId[cacheKey.fieldId];
map![cacheKey.rowId] = [onFieldChanged];
} else {
var objects = map[cacheKey.rowId];
if (objects == null) {
map[cacheKey.rowId] = [onFieldChanged];
} else {
objects.add(onFieldChanged);
}
}
}
void unregister(CellCacheKey cacheKey, VoidCallback fn) {
var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.rowId];
final index = callbacks?.indexWhere((callback) => callback == fn);
if (index != null && index != -1) {
callbacks?.removeAt(index);
}
}
Future<void> dispose() async {
notifier.onCellDispose();
_fieldListenerByFieldId.clear();
}
}

View File

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
@ -15,13 +14,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:convert' show utf8; import 'dart:convert' show utf8;
import '../field/field_controller.dart'; import '../field/field_controller.dart';
import '../field/field_service.dart';
import '../field/type_option/type_option_context.dart';
import 'cell_field_notifier.dart';
import 'cell_listener.dart';
part 'cell_service.freezed.dart'; part 'cell_service.freezed.dart';
part 'cell_data_loader.dart'; part 'cell_data_loader.dart';
part 'cell_controller.dart';
part 'cell_cache.dart'; part 'cell_cache.dart';
part 'cell_data_persistence.dart'; part 'cell_data_persistence.dart';

View File

@ -10,7 +10,7 @@ import 'type_option/type_option_data_controller.dart';
part 'field_editor_bloc.freezed.dart'; part 'field_editor_bloc.freezed.dart';
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> { class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
final TypeOptionDataController dataController; final TypeOptionController dataController;
FieldEditorBloc({ FieldEditorBloc({
required String viewId, required String viewId,
@ -18,7 +18,7 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
required bool isGroupField, required bool isGroupField,
required IFieldTypeOptionLoader loader, required IFieldTypeOptionLoader loader,
}) : dataController = }) : dataController =
TypeOptionDataController(viewId: viewId, loader: loader), TypeOptionController(viewId: viewId, loader: loader),
super(FieldEditorState.initial(viewId, fieldName, isGroupField)) { super(FieldEditorState.initial(viewId, fieldName, isGroupField)) {
on<FieldEditorEvent>( on<FieldEditorEvent>(
(event, emit) async { (event, emit) async {

View File

@ -8,10 +8,10 @@ part 'field_type_option_edit_bloc.freezed.dart';
class FieldTypeOptionEditBloc class FieldTypeOptionEditBloc
extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> { extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> {
final TypeOptionDataController _dataController; final TypeOptionController _dataController;
void Function()? _fieldListenFn; void Function()? _fieldListenFn;
FieldTypeOptionEditBloc(TypeOptionDataController dataController) FieldTypeOptionEditBloc(TypeOptionController dataController)
: _dataController = dataController, : _dataController = dataController,
super(FieldTypeOptionEditState.initial(dataController)) { super(FieldTypeOptionEditState.initial(dataController)) {
on<FieldTypeOptionEditEvent>( on<FieldTypeOptionEditEvent>(
@ -58,9 +58,9 @@ class FieldTypeOptionEditState with _$FieldTypeOptionEditState {
}) = _FieldTypeOptionEditState; }) = _FieldTypeOptionEditState;
factory FieldTypeOptionEditState.initial( factory FieldTypeOptionEditState.initial(
TypeOptionDataController typeOptionDataController, TypeOptionController typeOptionController,
) => ) =>
FieldTypeOptionEditState( FieldTypeOptionEditState(
field: typeOptionDataController.field, field: typeOptionController.field,
); );
} }

View File

@ -109,11 +109,11 @@ class ChecklistTypeOptionWidgetDataParser
class TypeOptionContext<T extends GeneratedMessage> { class TypeOptionContext<T extends GeneratedMessage> {
T? _typeOptionObject; T? _typeOptionObject;
final TypeOptionParser<T> dataParser; final TypeOptionParser<T> dataParser;
final TypeOptionDataController _dataController; final TypeOptionController _dataController;
TypeOptionContext({ TypeOptionContext({
required this.dataParser, required this.dataParser,
required TypeOptionDataController dataController, required TypeOptionController dataController,
}) : _dataController = dataController; }) : _dataController = dataController;
String get viewId => _dataController.viewId; String get viewId => _dataController.viewId;

View File

@ -9,25 +9,25 @@ import 'package:appflowy_backend/log.dart';
import '../field_service.dart'; import '../field_service.dart';
import 'type_option_context.dart'; import 'type_option_context.dart';
class TypeOptionDataController { class TypeOptionController {
final String viewId; final String viewId;
late TypeOptionPB _typeOption;
final IFieldTypeOptionLoader loader; final IFieldTypeOptionLoader loader;
late TypeOptionPB _typeOptiondata;
final PublishNotifier<FieldPB> _fieldNotifier = PublishNotifier(); final PublishNotifier<FieldPB> _fieldNotifier = PublishNotifier();
/// Returns a [TypeOptionDataController] used to modify the specified /// Returns a [TypeOptionController] used to modify the specified
/// [FieldPB]'s data /// [FieldPB]'s data
/// ///
/// Should call [loadTypeOptionData] if the passed-in [FieldInfo] /// Should call [loadTypeOptionData] if the passed-in [FieldInfo]
/// is null /// is null
/// ///
TypeOptionDataController({ TypeOptionController({
required this.viewId, required this.viewId,
required this.loader, required this.loader,
FieldInfo? fieldInfo, FieldInfo? fieldInfo,
}) { }) {
if (fieldInfo != null) { if (fieldInfo != null) {
_typeOptiondata = TypeOptionPB.create() _typeOption = TypeOptionPB.create()
..viewId = viewId ..viewId = viewId
..field_2 = fieldInfo.field; ..field_2 = fieldInfo.field;
} }
@ -38,7 +38,7 @@ class TypeOptionDataController {
return result.fold( return result.fold(
(data) { (data) {
data.freeze(); data.freeze();
_typeOptiondata = data; _typeOption = data;
_fieldNotifier.value = data.field_2; _fieldNotifier.value = data.field_2;
return left(data); return left(data);
}, },
@ -50,28 +50,28 @@ class TypeOptionDataController {
} }
FieldPB get field { FieldPB get field {
return _typeOptiondata.field_2; return _typeOption.field_2;
} }
T getTypeOption<T>(TypeOptionParser<T> parser) { T getTypeOption<T>(TypeOptionParser<T> parser) {
return parser.fromBuffer(_typeOptiondata.typeOptionData); return parser.fromBuffer(_typeOption.typeOptionData);
} }
set fieldName(String name) { set fieldName(String name) {
_typeOptiondata = _typeOptiondata.rebuild((rebuildData) { _typeOption = _typeOption.rebuild((rebuildData) {
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) { rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
rebuildField.name = name; rebuildField.name = name;
}); });
}); });
_fieldNotifier.value = _typeOptiondata.field_2; _fieldNotifier.value = _typeOption.field_2;
FieldBackendService(viewId: viewId, fieldId: field.id) FieldBackendService(viewId: viewId, fieldId: field.id)
.updateField(name: name); .updateField(name: name);
} }
set typeOptionData(List<int> typeOptionData) { set typeOptionData(List<int> typeOptionData) {
_typeOptiondata = _typeOptiondata.rebuild((rebuildData) { _typeOption = _typeOption.rebuild((rebuildData) {
if (typeOptionData.isNotEmpty) { if (typeOptionData.isNotEmpty) {
rebuildData.typeOptionData = typeOptionData; rebuildData.typeOptionData = typeOptionData;
} }

View File

@ -1,24 +1,20 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../grid/presentation/widgets/cell/cell_builder.dart';
import '../cell/cell_field_notifier.dart';
import '../cell/cell_service.dart'; import '../cell/cell_service.dart';
import '../field/field_controller.dart';
import 'row_cache.dart'; import 'row_cache.dart';
typedef OnRowChanged = void Function(CellByFieldId, RowsChangedReason); typedef OnRowChanged = void Function(CellByFieldId, RowsChangedReason);
class RowDataController extends GridCellBuilderDelegate { class RowDataController {
final RowInfo rowInfo; final RowInfo rowInfo;
final List<VoidCallback> _onRowChangedListeners = []; final List<VoidCallback> _onRowChangedListeners = [];
final FieldController _fieldController;
final RowCache _rowCache; final RowCache _rowCache;
get cellCache => _rowCache.cellCache;
RowDataController({ RowDataController({
required this.rowInfo, required this.rowInfo,
required FieldController fieldController,
required RowCache rowCache, required RowCache rowCache,
}) : _fieldController = fieldController, }) : _rowCache = rowCache;
_rowCache = rowCache;
CellByFieldId loadData() { CellByFieldId loadData() {
return _rowCache.loadGridCells(rowInfo.rowPB.id); return _rowCache.loadGridCells(rowInfo.rowPB.id);
@ -36,14 +32,4 @@ class RowDataController extends GridCellBuilderDelegate {
_rowCache.removeRowListener(fn); _rowCache.removeRowListener(fn);
} }
} }
// GridCellBuilderDelegate implementation
@override
CellFieldNotifier buildFieldNotifier() {
return CellFieldNotifier(
notifier: GridCellFieldNotifierImpl(_fieldController));
}
@override
CellCache get cellCache => _rowCache.cellCache;
} }

View File

@ -20,18 +20,18 @@ import 'group_controller.dart';
part 'board_bloc.freezed.dart'; part 'board_bloc.freezed.dart';
class BoardBloc extends Bloc<BoardEvent, BoardState> { class BoardBloc extends Bloc<BoardEvent, BoardState> {
final BoardDataController _gridDataController; final BoardDataController _boardDataController;
late final AppFlowyBoardController boardController; late final AppFlowyBoardController boardController;
final GroupBackendService _groupBackendSvc; final GroupBackendService _groupBackendSvc;
final LinkedHashMap<String, GroupController> groupControllers = final LinkedHashMap<String, GroupController> groupControllers =
LinkedHashMap(); LinkedHashMap();
FieldController get fieldController => _gridDataController.fieldController; FieldController get fieldController => _boardDataController.fieldController;
String get viewId => _gridDataController.viewId; String get viewId => _boardDataController.viewId;
BoardBloc({required ViewPB view}) BoardBloc({required ViewPB view})
: _groupBackendSvc = GroupBackendService(viewId: view.id), : _groupBackendSvc = GroupBackendService(viewId: view.id),
_gridDataController = BoardDataController(view: view), _boardDataController = BoardDataController(view: view),
super(BoardState.initial(view.id)) { super(BoardState.initial(view.id)) {
boardController = AppFlowyBoardController( boardController = AppFlowyBoardController(
onMoveGroup: ( onMoveGroup: (
@ -72,7 +72,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
}, },
createBottomRow: (groupId) async { createBottomRow: (groupId) async {
final startRowId = groupControllers[groupId]?.lastRow()?.id; final startRowId = groupControllers[groupId]?.lastRow()?.id;
final result = await _gridDataController.createBoardCard( final result = await _boardDataController.createBoardCard(
groupId, groupId,
startRowId: startRowId, startRowId: startRowId,
); );
@ -82,7 +82,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
); );
}, },
createHeaderRow: (String groupId) async { createHeaderRow: (String groupId) async {
final result = await _gridDataController.createBoardCard(groupId); final result = await _boardDataController.createBoardCard(groupId);
result.fold( result.fold(
(_) {}, (_) {},
(err) => Log.error(err), (err) => Log.error(err),
@ -178,7 +178,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
@override @override
Future<void> close() async { Future<void> close() async {
await _gridDataController.dispose(); await _boardDataController.dispose();
for (final controller in groupControllers.values) { for (final controller in groupControllers.values) {
controller.dispose(); controller.dispose();
} }
@ -204,11 +204,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
} }
RowCache? getRowCache(String blockId) { RowCache? getRowCache(String blockId) {
return _gridDataController.rowCache; return _boardDataController.rowCache;
} }
void _startListening() { void _startListening() {
_gridDataController.addListener( _boardDataController.addListener(
onDatabaseChanged: (grid) { onDatabaseChanged: (grid) {
if (!isClosed) { if (!isClosed) {
add(BoardEvent.didReceiveGridUpdate(grid)); add(BoardEvent.didReceiveGridUpdate(grid));
@ -264,7 +264,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
} }
Future<void> _openGrid(Emitter<BoardState> emit) async { Future<void> _openGrid(Emitter<BoardState> emit) async {
final result = await _gridDataController.openGrid(); final result = await _boardDataController.openGrid();
result.fold( result.fold(
(grid) => emit( (grid) => emit(
state.copyWith(loadingState: GridLoadingState.finish(left(unit))), state.copyWith(loadingState: GridLoadingState.finish(left(unit))),

View File

@ -21,7 +21,7 @@ typedef OnResetGroups = void Function(List<GroupPB>);
class BoardDataController { class BoardDataController {
final String viewId; final String viewId;
final DatabaseBackendService _databaseFFIService; final DatabaseBackendService _databaseSvc;
final FieldController fieldController; final FieldController fieldController;
final BoardListener _listener; final BoardListener _listener;
late DatabaseViewCache _viewCache; late DatabaseViewCache _viewCache;
@ -38,7 +38,7 @@ class BoardDataController {
BoardDataController({required ViewPB view}) BoardDataController({required ViewPB view})
: viewId = view.id, : viewId = view.id,
_listener = BoardListener(view.id), _listener = BoardListener(view.id),
_databaseFFIService = DatabaseBackendService(viewId: view.id), _databaseSvc = DatabaseBackendService(viewId: view.id),
fieldController = FieldController(viewId: view.id) { fieldController = FieldController(viewId: view.id) {
// //
_viewCache = DatabaseViewCache( _viewCache = DatabaseViewCache(
@ -100,7 +100,7 @@ class BoardDataController {
} }
Future<Either<Unit, FlowyError>> openGrid() async { Future<Either<Unit, FlowyError>> openGrid() async {
final result = await _databaseFFIService.openGrid(); final result = await _databaseSvc.openGrid();
return result.fold( return result.fold(
(grid) async { (grid) async {
_onDatabaseChanged?.call(grid); _onDatabaseChanged?.call(grid);
@ -121,17 +121,17 @@ class BoardDataController {
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId, Future<Either<RowPB, FlowyError>> createBoardCard(String groupId,
{String? startRowId}) { {String? startRowId}) {
return _databaseFFIService.createBoardCard(groupId, startRowId); return _databaseSvc.createBoardCard(groupId, startRowId);
} }
Future<void> dispose() async { Future<void> dispose() async {
await _viewCache.dispose(); await _viewCache.dispose();
await _databaseFFIService.closeView(); await _databaseSvc.closeView();
await fieldController.dispose(); await fieldController.dispose();
} }
Future<void> _loadGroups() async { Future<void> _loadGroups() async {
final result = await _databaseFFIService.loadGroups(); final result = await _databaseSvc.loadGroups();
return Future( return Future(
() => result.fold( () => result.fold(
(groups) { (groups) {

View File

@ -1,8 +1,7 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
import '../../../application/cell/cell_controller_builder.dart';
import '../../../application/cell/cell_service.dart';
part 'board_checkbox_cell_bloc.freezed.dart'; part 'board_checkbox_cell_bloc.freezed.dart';

View File

@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
import '../../../application/cell/cell_service.dart'; import '../../../application/cell/cell_controller_builder.dart';
import '../../../application/field/field_controller.dart'; import '../../../application/field/field_controller.dart';
part 'board_date_cell_bloc.freezed.dart'; part 'board_date_cell_bloc.freezed.dart';

View File

@ -1,7 +1,8 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
import '../../../application/cell/cell_service.dart';
import '../../../application/cell/cell_controller_builder.dart';
part 'board_number_cell_bloc.freezed.dart'; part 'board_number_cell_bloc.freezed.dart';

View File

@ -1,5 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';

View File

@ -1,10 +1,9 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-database/url_type_option_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/url_type_option_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
import '../../../application/cell/cell_service.dart';
part 'board_url_cell_bloc.freezed.dart'; part 'board_url_cell_bloc.freezed.dart';
class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> { class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {

View File

@ -1,9 +1,7 @@
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import '../../../application/cell/cell_field_notifier.dart';
import '../../../application/cell/cell_service.dart'; import '../../../application/cell/cell_service.dart';
import '../../../application/field/field_controller.dart';
import '../../../application/row/row_cache.dart'; import '../../../application/row/row_cache.dart';
import '../../presentation/card/card_cell_builder.dart'; import '../../presentation/card/card_cell_builder.dart';
@ -11,16 +9,13 @@ typedef OnCardChanged = void Function(CellByFieldId, RowsChangedReason);
class CardDataController extends BoardCellBuilderDelegate { class CardDataController extends BoardCellBuilderDelegate {
final RowPB rowPB; final RowPB rowPB;
final FieldController _fieldController;
final RowCache _rowCache; final RowCache _rowCache;
final List<VoidCallback> _onCardChangedListeners = []; final List<VoidCallback> _onCardChangedListeners = [];
CardDataController({ CardDataController({
required this.rowPB, required this.rowPB,
required FieldController fieldController,
required RowCache rowCache, required RowCache rowCache,
}) : _fieldController = fieldController, }) : _rowCache = rowCache;
_rowCache = rowCache;
CellByFieldId loadData() { CellByFieldId loadData() {
return _rowCache.loadGridCells(rowPB.id); return _rowCache.loadGridCells(rowPB.id);
@ -39,12 +34,6 @@ class CardDataController extends BoardCellBuilderDelegate {
} }
} }
@override
CellFieldNotifier buildFieldNotifier() {
return CellFieldNotifier(
notifier: GridCellFieldNotifierImpl(_fieldController));
}
@override @override
CellCache get cellCache => _rowCache.cellCache; CellCache get cellCache => _rowCache.cellCache;
} }

View File

@ -229,7 +229,6 @@ class _BoardContentState extends State<BoardContent> {
final fieldController = context.read<BoardBloc>().fieldController; final fieldController = context.read<BoardBloc>().fieldController;
final viewId = context.read<BoardBloc>().viewId; final viewId = context.read<BoardBloc>().viewId;
final cardController = CardDataController( final cardController = CardDataController(
fieldController: fieldController,
rowCache: rowCache, rowCache: rowCache,
rowPB: rowPB, rowPB: rowPB,
); );
@ -306,7 +305,6 @@ class _BoardContentState extends State<BoardContent> {
final dataController = RowDataController( final dataController = RowDataController(
rowInfo: rowInfo, rowInfo: rowInfo,
fieldController: fieldController,
rowCache: rowCache, rowCache: rowCache,
); );
@ -314,7 +312,7 @@ class _BoardContentState extends State<BoardContent> {
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return RowDetailPage( return RowDetailPage(
cellBuilder: GridCellBuilder(delegate: dataController), cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
dataController: dataController, dataController: dataController,
); );
}, },

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; 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:appflowy/plugins/database_view/board/application/card/board_checkbox_cell_bloc.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart';

View File

@ -1,7 +1,7 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../application/cell/cell_service.dart';
import '../../../grid/application/cell/checklist_cell_bloc.dart'; import '../../../grid/application/cell/checklist_cell_bloc.dart';
import '../../../grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart'; import '../../../grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart';

View File

@ -1,9 +1,9 @@
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:appflowy/plugins/database_view/board/application/card/board_date_cell_bloc.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../application/cell/cell_service.dart';
import 'define.dart'; import 'define.dart';
class BoardDateCell extends StatefulWidget { class BoardDateCell extends StatefulWidget {

View File

@ -1,7 +1,7 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../application/cell/cell_service.dart';
import '../../application/card/board_number_cell_bloc.dart'; import '../../application/card/board_number_cell_bloc.dart';
import 'define.dart'; import 'define.dart';

View File

@ -1,9 +1,8 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../application/cell/cell_service.dart';
import '../../../grid/presentation/widgets/cell/select_option_cell/extension.dart'; import '../../../grid/presentation/widgets/cell/select_option_cell/extension.dart';
import '../../../grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart'; import '../../../grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
import '../../application/card/board_select_option_cell_bloc.dart'; import '../../application/card/board_select_option_cell_bloc.dart';

View File

@ -1,10 +1,10 @@
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:appflowy/plugins/database_view/grid/presentation/widgets/cell/cell_builder.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'package:textstyle_extensions/textstyle_extensions.dart';
import '../../../application/cell/cell_service.dart';
import '../../application/card/board_text_cell_bloc.dart'; import '../../application/card/board_text_cell_bloc.dart';
import 'board_cell.dart'; import 'board_cell.dart';
import 'define.dart'; import 'define.dart';

View File

@ -1,9 +1,9 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'package:textstyle_extensions/textstyle_extensions.dart';
import '../../../application/cell/cell_service.dart';
import '../../application/card/board_url_cell_bloc.dart'; import '../../application/card/board_url_cell_bloc.dart';
import 'define.dart'; import 'define.dart';

View File

@ -1,3 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -11,7 +12,7 @@ import 'board_select_option_cell.dart';
import 'board_text_cell.dart'; import 'board_text_cell.dart';
import 'board_url_cell.dart'; import 'board_url_cell.dart';
abstract class BoardCellBuilderDelegate extends CellControllerBuilderDelegate { abstract class BoardCellBuilderDelegate {
CellCache get cellCache; CellCache get cellCache;
} }
@ -26,7 +27,6 @@ class BoardCellBuilder {
EditableCellNotifier cellNotifier, EditableCellNotifier cellNotifier,
) { ) {
final cellControllerBuilder = CellControllerBuilder( final cellControllerBuilder = CellControllerBuilder(
delegate: delegate,
cellId: cellId, cellId: cellId,
cellCache: delegate.cellCache, cellCache: delegate.cellCache,
); );

View File

@ -1,3 +1,4 @@
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/cell/cell_service.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -10,12 +10,12 @@ part 'checklist_cell_bloc.freezed.dart';
class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> { class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
final ChecklistCellController cellController; final ChecklistCellController cellController;
final SelectOptionFFIService _selectOptionService; final SelectOptionBackendService _selectOptionSvc;
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
ChecklistCellBloc({ ChecklistCellBloc({
required this.cellController, required this.cellController,
}) : _selectOptionService = }) : _selectOptionSvc =
SelectOptionFFIService(cellId: cellController.cellId), SelectOptionBackendService(cellId: cellController.cellId),
super(ChecklistCellState.initial(cellController)) { super(ChecklistCellState.initial(cellController)) {
on<ChecklistCellEvent>( on<ChecklistCellEvent>(
(event, emit) async { (event, emit) async {
@ -60,7 +60,7 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
} }
void _loadOptions() { void _loadOptions() {
_selectOptionService.getOptionContext().then((result) { _selectOptionSvc.getCellData().then((result) {
if (isClosed) return; if (isClosed) return;
return result.fold( return result.fold(

View File

@ -1,25 +1,24 @@
import 'dart:async'; import 'dart:async';
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'select_option_service.dart'; import 'select_option_service.dart';
part 'checklist_cell_editor_bloc.freezed.dart'; part 'checklist_cell_editor_bloc.freezed.dart';
class ChecklistCellEditorBloc class ChecklistCellEditorBloc
extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> { extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> {
final SelectOptionFFIService _selectOptionService; final SelectOptionBackendService _selectOptionService;
final ChecklistCellController cellController; final ChecklistCellController cellController;
ChecklistCellEditorBloc({ ChecklistCellEditorBloc({
required this.cellController, required this.cellController,
}) : _selectOptionService = }) : _selectOptionService =
SelectOptionFFIService(cellId: cellController.cellId), SelectOptionBackendService(cellId: cellController.cellId),
super(ChecklistCellEditorState.initial(cellController)) { super(ChecklistCellEditorState.initial(cellController)) {
on<ChecklistCellEditorEvent>( on<ChecklistCellEditorEvent>(
(event, emit) async { (event, emit) async {
@ -87,7 +86,7 @@ class ChecklistCellEditorBloc
} }
void _loadOptions() { void _loadOptions() {
_selectOptionService.getOptionContext().then((result) { _selectOptionService.getCellData().then((result) {
if (isClosed) return; if (isClosed) return;
return result.fold( return result.fold(

View File

@ -1,4 +1,5 @@
import 'package:appflowy/generated/locale_keys.g.dart'; 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/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart'; import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:easy_localization/easy_localization.dart' import 'package:easy_localization/easy_localization.dart'

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy_backend/protobuf/flowy-database/date_type_option_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/date_type_option_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@ -49,22 +49,13 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
} }
void _startListening() { void _startListening() {
_onCellChangedFn = _onCellChangedFn = cellController.startListening(
cellController.startListening(onCellChanged: ((cellContent) { onCellChanged: ((cellContent) {
if (!isClosed) { if (!isClosed) {
add(NumberCellEvent.didReceiveCellUpdate(cellContent)); add(NumberCellEvent.didReceiveCellUpdate(cellContent));
} }
}), listenWhenOnCellChanged: (oldValue, newValue) { }),
// If the new value is not the same as the content, which means the );
// backend formatted the content that user enter. For example:
//
// state.cellContent: "abc"
// oldValue: ""
// newValue: ""
// The oldValue is the same as newValue. So the [onCellChanged] won't
// get called. So just return true to refresh the cell content
return true;
});
} }
} }

View File

@ -1,5 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';

View File

@ -1,5 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart';
@ -11,13 +11,13 @@ part 'select_option_editor_bloc.freezed.dart';
class SelectOptionCellEditorBloc class SelectOptionCellEditorBloc
extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> { extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
final SelectOptionFFIService _selectOptionService; final SelectOptionBackendService _selectOptionService;
final SelectOptionCellController cellController; final SelectOptionCellController cellController;
SelectOptionCellEditorBloc({ SelectOptionCellEditorBloc({
required this.cellController, required this.cellController,
}) : _selectOptionService = }) : _selectOptionService =
SelectOptionFFIService(cellId: cellController.cellId), SelectOptionBackendService(cellId: cellController.cellId),
super(SelectOptionEditorState.initial(cellController)) { super(SelectOptionEditorState.initial(cellController)) {
on<SelectOptionEditorEvent>( on<SelectOptionEditorEvent>(
(event, emit) async { (event, emit) async {
@ -159,7 +159,7 @@ class SelectOptionCellEditorBloc
} }
Future<void> _loadOptions() async { Future<void> _loadOptions() async {
final result = await _selectOptionService.getOptionContext(); final result = await _selectOptionService.getCellData();
if (isClosed) { if (isClosed) {
Log.warn("Unexpected closing the bloc"); Log.warn("Unexpected closing the bloc");
return; return;

View File

@ -6,9 +6,9 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/cell_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/cell_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart';
class SelectOptionFFIService { class SelectOptionBackendService {
final CellIdentifier cellId; final CellIdentifier cellId;
SelectOptionFFIService({required this.cellId}); SelectOptionBackendService({required this.cellId});
String get viewId => cellId.viewId; String get viewId => cellId.viewId;
String get fieldId => cellId.fieldInfo.id; String get fieldId => cellId.fieldInfo.id;
@ -60,7 +60,7 @@ class SelectOptionFFIService {
return DatabaseEventUpdateSelectOption(payload).send(); return DatabaseEventUpdateSelectOption(payload).send();
} }
Future<Either<SelectOptionCellDataPB, FlowyError>> getOptionContext() { Future<Either<SelectOptionCellDataPB, FlowyError>> getCellData() {
final payload = CellIdPB.create() final payload = CellIdPB.create()
..viewId = viewId ..viewId = viewId
..fieldId = fieldId ..fieldId = fieldId

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-database/url_type_option_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/url_type_option_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-database/url_type_option_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/url_type_option_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';

View File

@ -13,14 +13,11 @@ class ChecklistFilterEditorBloc
extends Bloc<ChecklistFilterEditorEvent, ChecklistFilterEditorState> { extends Bloc<ChecklistFilterEditorEvent, ChecklistFilterEditorState> {
final FilterInfo filterInfo; final FilterInfo filterInfo;
final FilterBackendService _filterBackendSvc; final FilterBackendService _filterBackendSvc;
// final SelectOptionFFIService _selectOptionService;
final FilterListener _listener; final FilterListener _listener;
ChecklistFilterEditorBloc({ ChecklistFilterEditorBloc({
required this.filterInfo, required this.filterInfo,
}) : _filterBackendSvc = FilterBackendService(viewId: filterInfo.viewId), }) : _filterBackendSvc = FilterBackendService(viewId: filterInfo.viewId),
// _selectOptionService =
// SelectOptionFFIService(cellId: cellController.cellId)
_listener = FilterListener( _listener = FilterListener(
viewId: filterInfo.viewId, viewId: filterInfo.viewId,
filterId: filterInfo.filter.id, filterId: filterInfo.filter.id,

View File

@ -15,10 +15,9 @@ import 'dart:collection';
part 'grid_bloc.freezed.dart'; part 'grid_bloc.freezed.dart';
class GridBloc extends Bloc<GridEvent, GridState> { class GridBloc extends Bloc<GridEvent, GridState> {
final DatabaseController gridController; final DatabaseController databaseController;
void Function()? _createRowOperation;
GridBloc({required ViewPB view, required this.gridController}) GridBloc({required ViewPB view, required this.databaseController})
: super(GridState.initial(view.id)) { : super(GridState.initial(view.id)) {
on<GridEvent>( on<GridEvent>(
(event, emit) async { (event, emit) async {
@ -28,12 +27,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
await _openGrid(emit); await _openGrid(emit);
}, },
createRow: () { createRow: () {
state.loadingState.when( databaseController.createRow();
loading: () {
_createRowOperation = () => gridController.createRow();
},
finish: (_) => gridController.createRow(),
);
}, },
deleteRow: (rowInfo) async { deleteRow: (rowInfo) async {
final rowService = RowBackendService( final rowService = RowBackendService(
@ -63,16 +57,16 @@ class GridBloc extends Bloc<GridEvent, GridState> {
@override @override
Future<void> close() async { Future<void> close() async {
await gridController.dispose(); await databaseController.dispose();
return super.close(); return super.close();
} }
RowCache? getRowCache(String blockId, String rowId) { RowCache? getRowCache(String blockId, String rowId) {
return gridController.rowCache; return databaseController.rowCache;
} }
void _startListening() { void _startListening() {
gridController.addListener( databaseController.addListener(
onGridChanged: (grid) { onGridChanged: (grid) {
if (!isClosed) { if (!isClosed) {
add(GridEvent.didReceiveGridUpdate(grid)); add(GridEvent.didReceiveGridUpdate(grid));
@ -92,13 +86,9 @@ class GridBloc extends Bloc<GridEvent, GridState> {
} }
Future<void> _openGrid(Emitter<GridState> emit) async { Future<void> _openGrid(Emitter<GridState> emit) async {
final result = await gridController.openGrid(); final result = await databaseController.openGrid();
result.fold( result.fold(
(grid) { (grid) {
if (_createRowOperation != null) {
_createRowOperation?.call();
_createRowOperation = null;
}
emit( emit(
state.copyWith(loadingState: GridLoadingState.finish(left(unit))), state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
); );

View File

@ -35,11 +35,11 @@ class GridPage extends StatefulWidget {
required this.view, required this.view,
this.onDeleted, this.onDeleted,
Key? key, Key? key,
}) : gridController = DatabaseController(view: view), }) : databaseController = DatabaseController(view: view),
super(key: key); super(key: key);
final ViewPB view; final ViewPB view;
final DatabaseController gridController; final DatabaseController databaseController;
final VoidCallback? onDeleted; final VoidCallback? onDeleted;
@override @override
@ -54,19 +54,19 @@ class _GridPageState extends State<GridPage> {
BlocProvider<GridBloc>( BlocProvider<GridBloc>(
create: (context) => GridBloc( create: (context) => GridBloc(
view: widget.view, view: widget.view,
gridController: widget.gridController, databaseController: widget.databaseController,
)..add(const GridEvent.initial()), )..add(const GridEvent.initial()),
), ),
BlocProvider<GridFilterMenuBloc>( BlocProvider<GridFilterMenuBloc>(
create: (context) => GridFilterMenuBloc( create: (context) => GridFilterMenuBloc(
viewId: widget.view.id, viewId: widget.view.id,
fieldController: widget.gridController.fieldController, fieldController: widget.databaseController.fieldController,
)..add(const GridFilterMenuEvent.initial()), )..add(const GridFilterMenuEvent.initial()),
), ),
BlocProvider<SortMenuBloc>( BlocProvider<SortMenuBloc>(
create: (context) => SortMenuBloc( create: (context) => SortMenuBloc(
viewId: widget.view.id, viewId: widget.view.id,
fieldController: widget.gridController.fieldController, fieldController: widget.databaseController.fieldController,
)..add(const SortMenuEvent.initial()), )..add(const SortMenuEvent.initial()),
), ),
BlocProvider<DatabaseSettingBloc>( BlocProvider<DatabaseSettingBloc>(
@ -190,7 +190,7 @@ class _FlowyGridState extends State<FlowyGrid> {
Widget _gridHeader(BuildContext context, String viewId) { Widget _gridHeader(BuildContext context, String viewId) {
final fieldController = final fieldController =
context.read<GridBloc>().gridController.fieldController; context.read<GridBloc>().databaseController.fieldController;
return GridHeaderSliverAdaptor( return GridHeaderSliverAdaptor(
viewId: viewId, viewId: viewId,
fieldController: fieldController, fieldController: fieldController,
@ -274,10 +274,9 @@ class _GridRowsState extends State<_GridRows> {
if (rowCache == null) return const SizedBox(); if (rowCache == null) return const SizedBox();
final fieldController = final fieldController =
context.read<GridBloc>().gridController.fieldController; context.read<GridBloc>().databaseController.fieldController;
final dataController = RowDataController( final dataController = RowDataController(
rowInfo: rowInfo, rowInfo: rowInfo,
fieldController: fieldController,
rowCache: rowCache, rowCache: rowCache,
); );
@ -286,7 +285,7 @@ class _GridRowsState extends State<_GridRows> {
child: GridRowWidget( child: GridRowWidget(
rowInfo: rowInfo, rowInfo: rowInfo,
dataController: dataController, dataController: dataController,
cellBuilder: GridCellBuilder(delegate: dataController), cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
openDetailPage: (context, cellBuilder) { openDetailPage: (context, cellBuilder) {
_openRowDetailPage( _openRowDetailPage(
context, context,
@ -310,7 +309,6 @@ class _GridRowsState extends State<_GridRows> {
) { ) {
final dataController = RowDataController( final dataController = RowDataController(
rowInfo: rowInfo, rowInfo: rowInfo,
fieldController: fieldController,
rowCache: rowCache, rowCache: rowCache,
); );

View File

@ -1,3 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -13,21 +14,16 @@ import 'select_option_cell/select_option_cell.dart';
import 'text_cell.dart'; import 'text_cell.dart';
import 'url_cell/url_cell.dart'; import 'url_cell/url_cell.dart';
abstract class GridCellBuilderDelegate extends CellControllerBuilderDelegate {
CellCache get cellCache;
}
class GridCellBuilder { class GridCellBuilder {
final GridCellBuilderDelegate delegate; final CellCache cellCache;
GridCellBuilder({ GridCellBuilder({
required this.delegate, required this.cellCache,
}); });
GridCellWidget build(CellIdentifier cellId, {GridCellStyle? style}) { GridCellWidget build(CellIdentifier cellId, {GridCellStyle? style}) {
final cellControllerBuilder = CellControllerBuilder( final cellControllerBuilder = CellControllerBuilder(
cellId: cellId, cellId: cellId,
cellCache: delegate.cellCache, cellCache: cellCache,
delegate: delegate,
); );
final key = cellId.key(); final key = cellId.key();

View File

@ -1,9 +1,9 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../application/cell/cell_service.dart';
import '../../../application/cell/checkbox_cell_bloc.dart'; import '../../../application/cell/checkbox_cell_bloc.dart';
import '../../layout/sizes.dart'; import '../../layout/sizes.dart';
import 'cell_builder.dart'; import 'cell_builder.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; 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/application/cell/checklist_cell_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';

View File

@ -1,3 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -8,7 +9,6 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../application/cell/cell_service.dart';
import '../../../../application/cell/checklist_cell_editor_bloc.dart'; import '../../../../application/cell/checklist_cell_editor_bloc.dart';
import '../../../layout/sizes.dart'; import '../../../layout/sizes.dart';
import '../../header/type_option/select_option_editor.dart'; import '../../header/type_option/select_option_editor.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; 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:appflowy/plugins/database_view/grid/application/cell/date_cell_bloc.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';

View File

@ -1,4 +1,5 @@
import 'package:appflowy/generated/locale_keys.g.dart'; 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/application/field/type_option/type_option_context.dart';
import 'package:appflowy/plugins/database_view/grid/application/cell/date_cal_bloc.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.dart';
@ -20,7 +21,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:table_calendar/table_calendar.dart'; import 'package:table_calendar/table_calendar.dart';
import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'package:textstyle_extensions/textstyle_extensions.dart';
import '../../../../../application/cell/cell_service.dart';
import '../../../layout/sizes.dart'; import '../../../layout/sizes.dart';
import '../../common/type_option_separator.dart'; import '../../common/type_option_separator.dart';
import '../../header/type_option/date.dart'; import '../../header/type_option/date.dart';

View File

@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../application/cell/cell_service.dart';
import '../../../application/cell/number_cell_bloc.dart'; import '../../../application/cell/number_cell_bloc.dart';
import '../../layout/sizes.dart'; import '../../layout/sizes.dart';
import 'cell_builder.dart'; import 'cell_builder.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -1,4 +1,5 @@
import 'dart:collection'; 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/plugins/database_view/grid/application/cell/select_option_editor_bloc.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
@ -15,7 +16,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:textfield_tags/textfield_tags.dart'; import 'package:textfield_tags/textfield_tags.dart';
import '../../../../../application/cell/cell_service.dart';
import '../../../layout/sizes.dart'; import '../../../layout/sizes.dart';
import '../../common/type_option_separator.dart'; import '../../common/type_option_separator.dart';
import '../../header/type_option/select_option_editor.dart'; import '../../header/type_option/select_option_editor.dart';

View File

@ -1,5 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; 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/grid/application/cell/text_cell_bloc.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -1,9 +1,8 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:async'; import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../application/cell/cell_service.dart';
import '../../../../application/cell/url_cell_editor_bloc.dart'; import '../../../../application/cell/url_cell_editor_bloc.dart';
class URLCellEditor extends StatefulWidget { class URLCellEditor extends StatefulWidget {

View File

@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';

View File

@ -23,11 +23,11 @@ typedef SwitchToFieldCallback = Future<Either<TypeOptionPB, FlowyError>>
); );
class FieldTypeOptionEditor extends StatelessWidget { class FieldTypeOptionEditor extends StatelessWidget {
final TypeOptionDataController _dataController; final TypeOptionController _dataController;
final PopoverMutex popoverMutex; final PopoverMutex popoverMutex;
const FieldTypeOptionEditor({ const FieldTypeOptionEditor({
required TypeOptionDataController dataController, required TypeOptionController dataController,
required this.popoverMutex, required this.popoverMutex,
Key? key, Key? key,
}) : _dataController = dataController, }) : _dataController = dataController,

View File

@ -48,7 +48,7 @@ abstract class TypeOptionWidgetBuilder {
Widget? makeTypeOptionWidget({ Widget? makeTypeOptionWidget({
required BuildContext context, required BuildContext context,
required TypeOptionDataController dataController, required TypeOptionController dataController,
required PopoverMutex popoverMutex, required PopoverMutex popoverMutex,
}) { }) {
final builder = makeTypeOptionWidgetBuilder( final builder = makeTypeOptionWidgetBuilder(
@ -59,7 +59,7 @@ Widget? makeTypeOptionWidget({
} }
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
required TypeOptionDataController dataController, required TypeOptionController dataController,
required PopoverMutex popoverMutex, required PopoverMutex popoverMutex,
}) { }) {
final viewId = dataController.viewId; final viewId = dataController.viewId;
@ -144,7 +144,7 @@ TypeOptionContext<T> makeTypeOptionContext<T extends GeneratedMessage>({
required FieldInfo fieldInfo, required FieldInfo fieldInfo,
}) { }) {
final loader = FieldTypeOptionLoader(viewId: viewId, field: fieldInfo.field); final loader = FieldTypeOptionLoader(viewId: viewId, field: fieldInfo.field);
final dataController = TypeOptionDataController( final dataController = TypeOptionController(
viewId: viewId, viewId: viewId,
loader: loader, loader: loader,
fieldInfo: fieldInfo, fieldInfo: fieldInfo,
@ -178,7 +178,7 @@ TypeOptionContext<T> makeSelectTypeOptionContext<T extends GeneratedMessage>({
viewId: viewId, viewId: viewId,
field: fieldPB, field: fieldPB,
); );
final dataController = TypeOptionDataController( final dataController = TypeOptionController(
viewId: viewId, viewId: viewId,
loader: loader, loader: loader,
); );
@ -194,7 +194,7 @@ TypeOptionContext<T>
makeTypeOptionContextWithDataController<T extends GeneratedMessage>({ makeTypeOptionContextWithDataController<T extends GeneratedMessage>({
required String viewId, required String viewId,
required FieldType fieldType, required FieldType fieldType,
required TypeOptionDataController dataController, required TypeOptionController dataController,
}) { }) {
switch (fieldType) { switch (fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:

View File

@ -35,7 +35,7 @@ class _SettingButtonState extends State<SettingButton> {
return BlocSelector<GridBloc, GridState, GridSettingContext>( return BlocSelector<GridBloc, GridState, GridSettingContext>(
selector: (state) { selector: (state) {
final fieldController = final fieldController =
context.read<GridBloc>().gridController.fieldController; context.read<GridBloc>().databaseController.fieldController;
return GridSettingContext( return GridSettingContext(
viewId: state.viewId, viewId: state.viewId,
fieldController: fieldController, fieldController: fieldController,

View File

@ -1,5 +1,6 @@
import 'package:appflowy/plugins/document/presentation/plugins/board/board_menu_item.dart'; import 'package:appflowy/plugins/document/presentation/plugins/board/board_menu_item.dart';
import 'package:appflowy/plugins/document/presentation/plugins/board/board_node_widget.dart'; import 'package:appflowy/plugins/document/presentation/plugins/board/board_node_widget.dart';
import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart';
import 'package:appflowy/plugins/document/presentation/plugins/grid/grid_menu_item.dart'; import 'package:appflowy/plugins/document/presentation/plugins/grid/grid_menu_item.dart';
import 'package:appflowy/plugins/document/presentation/plugins/grid/grid_node_widget.dart'; import 'package:appflowy/plugins/document/presentation/plugins/grid/grid_node_widget.dart';
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart'; import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart';
@ -9,6 +10,7 @@ import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/sm
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:dartz/dartz.dart' as dartz;
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -126,9 +128,11 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final autoFocusParamters = _autoFocusParamters();
final editor = AppFlowyEditor( final editor = AppFlowyEditor(
editorState: editorState, editorState: editorState,
autoFocus: editorState.document.isEmpty, autoFocus: autoFocusParamters.value1,
focusedSelection: autoFocusParamters.value2,
customBuilders: { customBuilders: {
// Divider // Divider
kDividerType: DividerWidgetBuilder(), kDividerType: DividerWidgetBuilder(),
@ -144,6 +148,8 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
kCalloutType: CalloutNodeWidgetBuilder(), kCalloutType: CalloutNodeWidgetBuilder(),
// Auto Generator, // Auto Generator,
kAutoCompletionInputType: AutoCompletionInputBuilder(), kAutoCompletionInputType: AutoCompletionInputBuilder(),
// Cover
kCoverType: CoverNodeWidgetBuilder(),
// Smart Edit, // Smart Edit,
kSmartEditType: SmartEditInputBuilder(), kSmartEditType: SmartEditInputBuilder(),
}, },
@ -174,10 +180,12 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
// enable open ai features if needed. // enable open ai features if needed.
if (openAIKey != null && openAIKey!.isNotEmpty) ...[ if (openAIKey != null && openAIKey!.isNotEmpty) ...[
autoGeneratorMenuItem, autoGeneratorMenuItem,
] ],
], ],
toolbarItems: [ toolbarItems: [
smartEditItem, if (openAIKey != null && openAIKey!.isNotEmpty) ...[
smartEditItem,
]
], ],
themeData: theme.copyWith(extensions: [ themeData: theme.copyWith(extensions: [
...theme.extensions.values, ...theme.extensions.values,
@ -227,4 +235,18 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
await editorState.apply(transaction, withUpdateCursor: false); await editorState.apply(transaction, withUpdateCursor: false);
} }
} }
dartz.Tuple2<bool, Selection?> _autoFocusParamters() {
if (editorState.document.isEmpty) {
return dartz.Tuple2(true, Selection.single(path: [0], startOffset: 0));
}
final texts = editorState.document.root.children.whereType<TextNode>();
if (texts.every((element) => element.toPlainText().isEmpty)) {
return dartz.Tuple2(
true,
Selection.single(path: texts.first.path, startOffset: 0),
);
}
return const dartz.Tuple2(false, null);
}
} }

View File

@ -9,7 +9,7 @@ EditorStyle customEditorTheme(BuildContext context) {
? EditorStyle.dark ? EditorStyle.dark
: EditorStyle.light; : EditorStyle.light;
editorStyle = editorStyle.copyWith( editorStyle = editorStyle.copyWith(
padding: const EdgeInsets.symmetric(horizontal: 100, vertical: 28), padding: const EdgeInsets.symmetric(horizontal: 100, vertical: 0),
textStyle: editorStyle.textStyle?.copyWith( textStyle: editorStyle.textStyle?.copyWith(
fontFamily: 'poppins', fontFamily: 'poppins',
fontSize: documentStyle.fontSize, fontSize: documentStyle.fontSize,

View File

@ -0,0 +1,376 @@
import 'dart:io';
import 'dart:ui';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/file_picker/file_picker_service.dart';
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:file_picker/file_picker.dart' show FileType;
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
const String kLocalImagesKey = 'local_images';
List<String> get builtInAssetImages => [
"assets/images/app_flowy_abstract_cover_1.jpg",
"assets/images/app_flowy_abstract_cover_2.jpg"
];
class ChangeCoverPopover extends StatefulWidget {
final EditorState editorState;
final Node node;
final Function(
CoverSelectionType selectionType,
String selection,
) onCoverChanged;
const ChangeCoverPopover({
super.key,
required this.editorState,
required this.onCoverChanged,
required this.node,
});
@override
State<ChangeCoverPopover> createState() => _ChangeCoverPopoverState();
}
class ColorOption {
final String colorHex;
final String name;
const ColorOption({
required this.colorHex,
required this.name,
});
}
class CoverColorPicker extends StatefulWidget {
final String? selectedBackgroundColorHex;
final Color pickerBackgroundColor;
final Color pickerItemHoverColor;
final void Function(String color) onSubmittedbackgroundColorHex;
final List<ColorOption> backgroundColorOptions;
const CoverColorPicker({
super.key,
this.selectedBackgroundColorHex,
required this.pickerBackgroundColor,
required this.backgroundColorOptions,
required this.pickerItemHoverColor,
required this.onSubmittedbackgroundColorHex,
});
@override
State<CoverColorPicker> createState() => _CoverColorPickerState();
}
class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
late Future<List<String>>? fileImages;
@override
void initState() {
super.initState();
fileImages = _getPreviouslyPickedImagePaths();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(15),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlowyText.semibold(LocaleKeys.document_plugins_cover_colors.tr()),
const SizedBox(height: 10),
_buildColorPickerList(),
const SizedBox(height: 10),
FlowyText.semibold(LocaleKeys.document_plugins_cover_images.tr()),
const SizedBox(height: 10),
_buildFileImagePicker(),
const SizedBox(height: 10),
FlowyText.semibold(LocaleKeys.document_plugins_cover_abstract.tr()),
const SizedBox(height: 10),
_buildAbstractImagePicker(),
],
),
),
);
}
Widget _buildAbstractImagePicker() {
return GridView.builder(
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1 / 0.65,
crossAxisSpacing: 7,
mainAxisSpacing: 7),
itemCount: builtInAssetImages.length,
itemBuilder: (BuildContext ctx, index) {
return InkWell(
onTap: () {
widget.onCoverChanged(
CoverSelectionType.asset,
builtInAssetImages[index],
);
},
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(builtInAssetImages[index]),
fit: BoxFit.cover,
),
borderRadius: Corners.s8Border,
),
),
);
},
);
}
Widget _buildColorPickerList() {
return CoverColorPicker(
pickerBackgroundColor:
widget.editorState.editorStyle.selectionMenuBackgroundColor ??
Colors.white,
pickerItemHoverColor:
widget.editorState.editorStyle.selectionMenuItemSelectedColor ??
Colors.blue.withOpacity(0.3),
selectedBackgroundColorHex:
widget.node.attributes[kCoverSelectionTypeAttribute] ==
CoverSelectionType.color.toString()
? widget.node.attributes[kCoverSelectionAttribute]
: "ffffff",
backgroundColorOptions:
_generateBackgroundColorOptions(widget.editorState),
onSubmittedbackgroundColorHex: (color) {
widget.onCoverChanged(CoverSelectionType.color, color);
setState(() {});
},
);
}
Widget _buildFileImagePicker() {
return FutureBuilder<List<String>>(
future: _getPreviouslyPickedImagePaths(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<String> images = snapshot.data!;
return GridView.builder(
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1 / 0.65,
crossAxisSpacing: 7,
mainAxisSpacing: 7,
),
itemCount: images.length + 1,
itemBuilder: (BuildContext ctx, index) {
if (index == 0) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.15),
border: Border.all(
color: Theme.of(context).colorScheme.primary,
),
borderRadius: Corners.s8Border,
),
child: FlowyIconButton(
iconPadding: EdgeInsets.zero,
icon: Icon(
Icons.add,
color: Theme.of(context).colorScheme.primary,
),
width: 20,
onPressed: () {
_pickImages();
},
),
);
}
return InkWell(
onTap: () {
widget.onCoverChanged(
CoverSelectionType.file,
images[index - 1],
);
},
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: FileImage(File(images[index - 1])),
fit: BoxFit.cover,
),
borderRadius: Corners.s8Border,
),
),
);
},
);
} else {
return Container();
}
});
}
List<ColorOption> _generateBackgroundColorOptions(EditorState editorState) {
return FlowyTint.values
.map((t) => ColorOption(
colorHex: t.color(context).toHex(),
name: t.tintName(AppFlowyEditorLocalizations.current),
))
.toList();
}
Future<List<String>> _getPreviouslyPickedImagePaths() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final imageNames = prefs.getStringList(kLocalImagesKey) ?? [];
final removeNames = [];
for (final name in imageNames) {
if (!File(name).existsSync()) {
removeNames.add(name);
}
}
imageNames.removeWhere((element) => removeNames.contains(element));
prefs.setStringList(kLocalImagesKey, imageNames);
return imageNames;
}
Future<void> _pickImages() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
List<String> imageNames = prefs.getStringList(kLocalImagesKey) ?? [];
FilePickerResult? result = await getIt<FilePickerService>().pickFiles(
dialogTitle: LocaleKeys.document_plugins_cover_addLocalImage.tr(),
allowMultiple: false,
type: FileType.image,
allowedExtensions: ['jpg', 'png', 'jpeg'],
);
if (result != null && result.files.isNotEmpty) {
final path = result.files.first.path;
if (path != null) {
final directory = await _coverPath();
final newPath = await File(path).copy(
'$directory/${path.split('/').last}',
);
imageNames.add(newPath.path);
}
}
await prefs.setStringList(kLocalImagesKey, imageNames);
setState(() {});
}
Future<String> _coverPath() async {
final directory = await getIt<SettingsLocationCubit>().fetchLocation();
return Directory('$directory/covers')
.create(recursive: true)
.then((value) => value.path);
}
}
class _CoverColorPickerState extends State<CoverColorPicker> {
final scrollController = ScrollController();
@override
Widget build(BuildContext context) {
return Container(
height: 30,
alignment: Alignment.center,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
}, platform: TargetPlatform.windows),
child: ListView.builder(
controller: scrollController,
shrinkWrap: true,
itemCount: widget.backgroundColorOptions.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return _buildColorItems(
widget.backgroundColorOptions,
widget.selectedBackgroundColorHex,
);
},
),
),
);
}
@override
void dispose() {
super.dispose();
scrollController.dispose();
}
Widget _buildColorItem(ColorOption option, bool isChecked) {
return InkWell(
customBorder: const RoundedRectangleBorder(
borderRadius: Corners.s6Border,
),
hoverColor: widget.pickerItemHoverColor,
onTap: () {
widget.onSubmittedbackgroundColorHex(option.colorHex);
},
child: Padding(
padding: const EdgeInsets.only(right: 10.0),
child: SizedBox.square(
dimension: 25,
child: Container(
decoration: BoxDecoration(
color: isChecked
? Colors.transparent
: Color(int.tryParse(option.colorHex) ?? 0xFFFFFFFF),
border: isChecked
? Border.all(
color: Color(int.tryParse(option.colorHex) ?? 0xFFFFFF))
: null,
shape: BoxShape.circle,
),
child: isChecked
? SizedBox.square(
dimension: 25,
child: Container(
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
color:
Color(int.tryParse(option.colorHex) ?? 0xFFFFFFFF),
shape: BoxShape.circle,
),
),
)
: null,
),
),
),
);
}
Widget _buildColorItems(List<ColorOption> options, String? selectedColor) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: options
.map((e) => _buildColorItem(e, e.colorHex == selectedColor))
.toList(),
);
}
}
extension on Color {
String toHex() {
return '0x${value.toRadixString(16)}';
}
}

View File

@ -0,0 +1,302 @@
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/plugins/cover/change_cover_popover.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/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';
const String kCoverType = 'cover';
const String kCoverSelectionTypeAttribute = 'cover_selection_type';
const String kCoverSelectionAttribute = 'cover_selection';
enum CoverSelectionType {
initial,
color,
file,
asset;
static CoverSelectionType fromString(String? value) {
if (value == null) {
return CoverSelectionType.initial;
}
return CoverSelectionType.values.firstWhere(
(e) => e.toString() == value,
orElse: () => CoverSelectionType.initial,
);
}
}
class CoverNodeWidgetBuilder implements NodeWidgetBuilder {
@override
Widget build(NodeWidgetContext<Node> context) {
return _CoverImageNodeWidget(
key: context.node.key,
node: context.node,
editorState: context.editorState,
);
}
@override
NodeValidator<Node> get nodeValidator => (node) {
return true;
};
}
class _CoverImageNodeWidget extends StatefulWidget {
const _CoverImageNodeWidget({
Key? key,
required this.node,
required this.editorState,
}) : super(key: key);
final Node node;
final EditorState editorState;
@override
State<_CoverImageNodeWidget> createState() => _CoverImageNodeWidgetState();
}
class _CoverImageNodeWidgetState extends State<_CoverImageNodeWidget> {
CoverSelectionType get selectionType => CoverSelectionType.fromString(
widget.node.attributes[kCoverSelectionTypeAttribute],
);
@override
Widget build(BuildContext context) {
if (selectionType == CoverSelectionType.initial) {
return _AddCoverButton(
onTap: () {
_insertCover(CoverSelectionType.asset, builtInAssetImages.first);
},
);
} else {
return _CoverImage(
editorState: widget.editorState,
node: widget.node,
onCoverChanged: (type, value) {
_insertCover(type, value);
},
);
}
}
Future<void> _insertCover(CoverSelectionType type, dynamic cover) async {
final transaction = widget.editorState.transaction;
transaction.updateNode(widget.node, {
kCoverSelectionTypeAttribute: type.toString(),
kCoverSelectionAttribute: cover,
});
return widget.editorState.apply(transaction);
}
}
class _AddCoverButton extends StatefulWidget {
const _AddCoverButton({
required this.onTap,
});
final VoidCallback onTap;
@override
State<_AddCoverButton> createState() => _AddCoverButtonState();
}
class _AddCoverButtonState extends State<_AddCoverButton> {
bool isHidden = true;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (event) {
setHidden(false);
},
onExit: (event) {
setHidden(true);
},
child: Container(
height: 50.0,
width: double.infinity,
padding: const EdgeInsets.only(top: 20, bottom: 5),
// color: Colors.red,
child: isHidden
? const SizedBox()
: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// Add Cover Button.
FlowyButton(
leftIconSize: const Size.square(18),
onTap: widget.onTap,
useIntrinsicWidth: true,
leftIcon: svgWidget(
'editor/image',
color: Theme.of(context).colorScheme.onSurface,
),
text: FlowyText.regular(
LocaleKeys.document_plugins_cover_addCover.tr(),
),
)
// Add Icon Button.
// ...
],
),
),
);
}
void setHidden(bool value) {
if (isHidden == value) return;
setState(() {
isHidden = value;
});
}
}
class _CoverImage extends StatefulWidget {
const _CoverImage({
required this.editorState,
required this.node,
required this.onCoverChanged,
});
final Node node;
final EditorState editorState;
final Function(
CoverSelectionType selectionType,
dynamic selection,
) onCoverChanged;
@override
State<_CoverImage> createState() => _CoverImageState();
}
class _CoverImageState extends State<_CoverImage> {
final popoverController = PopoverController();
CoverSelectionType get selectionType => CoverSelectionType.fromString(
widget.node.attributes[kCoverSelectionTypeAttribute],
);
Color get color =>
Color(int.tryParse(widget.node.attributes[kCoverSelectionAttribute]) ??
0xFFFFFFFF);
bool isOverlayButtonsHidden = true;
@override
Widget build(BuildContext context) {
return Stack(
children: [
_buildCoverImage(context),
_buildCoverOverlayButtons(context),
],
);
}
Widget _buildCoverOverlayButtons(BuildContext context) {
return Positioned(
bottom: 22,
right: 12,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AppFlowyPopover(
offset: const Offset(-125, 10),
controller: popoverController,
direction: PopoverDirection.bottomWithCenterAligned,
constraints: BoxConstraints.loose(const Size(380, 450)),
margin: EdgeInsets.zero,
child: RoundedTextButton(
onPressed: () {
popoverController.show();
},
hoverColor: Theme.of(context).colorScheme.surface,
textColor: Theme.of(context).colorScheme.onSurface,
fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.8),
width: 120,
height: 28,
title: LocaleKeys.document_plugins_cover_changeCover.tr(),
),
popupBuilder: (BuildContext popoverContext) {
return ChangeCoverPopover(
node: widget.node,
editorState: widget.editorState,
onCoverChanged: widget.onCoverChanged,
);
},
),
const SizedBox(width: 10),
FlowyIconButton(
fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.8),
hoverColor: Theme.of(context).colorScheme.surface,
iconPadding: const EdgeInsets.all(5),
width: 28,
icon: svgWidget(
'editor/delete',
color: Theme.of(context).colorScheme.onSurface,
),
onPressed: () {
widget.onCoverChanged(CoverSelectionType.initial, null);
},
),
],
),
);
}
Widget _buildCoverImage(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
const height = 200.0;
final Widget coverImage;
switch (selectionType) {
case CoverSelectionType.file:
coverImage = Image.file(
File(widget.node.attributes[kCoverSelectionAttribute]),
fit: BoxFit.cover,
);
break;
case CoverSelectionType.asset:
coverImage = Image.asset(
widget.node.attributes[kCoverSelectionAttribute],
fit: BoxFit.cover,
);
break;
case CoverSelectionType.color:
coverImage = Container(
decoration: BoxDecoration(
color: color,
borderRadius: Corners.s6Border,
),
alignment: Alignment.center,
);
break;
case CoverSelectionType.initial:
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,
),
);
}
void setOverlayButtonsHidden(bool value) {
if (isOverlayButtonsHidden == value) return;
setState(() {
isOverlayButtonsHidden = value;
});
}
}

View File

@ -37,7 +37,7 @@ abstract class OpenAIRepository {
Future<Either<OpenAIError, TextCompletionResponse>> getCompletions({ Future<Either<OpenAIError, TextCompletionResponse>> getCompletions({
required String prompt, required String prompt,
String? suffix, String? suffix,
int maxTokens = 50, int maxTokens = 500,
double temperature = .3, double temperature = .3,
}); });
@ -72,7 +72,7 @@ class HttpOpenAIRepository implements OpenAIRepository {
Future<Either<OpenAIError, TextCompletionResponse>> getCompletions({ Future<Either<OpenAIError, TextCompletionResponse>> getCompletions({
required String prompt, required String prompt,
String? suffix, String? suffix,
int maxTokens = 50, int maxTokens = 500,
double temperature = 0.3, double temperature = 0.3,
}) async { }) async {
final parameters = { final parameters = {
@ -102,12 +102,14 @@ class HttpOpenAIRepository implements OpenAIRepository {
required String input, required String input,
required String instruction, required String instruction,
double temperature = 0.3, double temperature = 0.3,
int n = 1,
}) async { }) async {
final parameters = { final parameters = {
'model': 'text-davinci-edit-001', 'model': 'text-davinci-edit-001',
'input': input, 'input': input,
'instruction': instruction, 'instruction': instruction,
'temperature': temperature, 'temperature': temperature,
'n': n,
}; };
final response = await client.post( final response = await client.post(

View File

@ -10,9 +10,9 @@ enum SmartEditAction {
String get toInstruction { String get toInstruction {
switch (this) { switch (this) {
case SmartEditAction.summarize: case SmartEditAction.summarize:
return 'Summarize'; return 'Make it shorter';
case SmartEditAction.fixSpelling: case SmartEditAction.fixSpelling:
return 'Fix the spelling mistakes'; return 'Fix all the spelling mistakes';
} }
} }
} }

View File

@ -254,6 +254,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
final edits = await openAIRepository.getEdits( final edits = await openAIRepository.getEdits(
input: input, input: input,
instruction: instruction, instruction: instruction,
n: input.split('\n').length,
); );
return edits.fold((error) async { return edits.fold((error) async {
return dartz.Left( return dartz.Left(

View File

@ -42,11 +42,13 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
.toList(), .toList(),
buildChild: (controller) { buildChild: (controller) {
return FlowyIconButton( return FlowyIconButton(
hoverColor: Colors.transparent,
tooltipText: 'Smart Edit', tooltipText: 'Smart Edit',
preferBelow: false, preferBelow: false,
icon: const Icon( icon: const Icon(
Icons.edit, Icons.lightbulb_outline,
size: 14, size: 13,
color: Colors.white,
), ),
onPressed: () { onPressed: () {
controller.show(); controller.show();
@ -55,39 +57,44 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
}, },
onSelected: (action, controller) { onSelected: (action, controller) {
controller.close(); controller.close();
final selection = _insertSmartEditNode(action);
widget.editorState.service.selectionService.currentSelection.value;
if (selection == null) {
return;
}
final textNodes = widget
.editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>()
.toList(growable: false);
final input = widget.editorState.getTextInSelection(
textNodes.normalized,
selection.normalized,
);
final transaction = widget.editorState.transaction;
transaction.insertNode(
selection.normalized.end.path.next,
Node(
type: kSmartEditType,
attributes: {
kSmartEditInstructionType: action.inner.toInstruction,
kSmartEditInputType: input,
},
),
);
widget.editorState.apply(
transaction,
options: const ApplyOptions(
recordUndo: false,
recordRedo: false,
),
withUpdateCursor: false,
);
}, },
); );
} }
Future<void> _insertSmartEditNode(
SmartEditActionWrapper actionWrapper) async {
final selection =
widget.editorState.service.selectionService.currentSelection.value;
if (selection == null) {
return;
}
final textNodes = widget
.editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>()
.toList(growable: false);
final input = widget.editorState.getTextInSelection(
textNodes.normalized,
selection.normalized,
);
final transaction = widget.editorState.transaction;
transaction.insertNode(
selection.normalized.end.path.next,
Node(
type: kSmartEditType,
attributes: {
kSmartEditInstructionType: actionWrapper.inner.toInstruction,
kSmartEditInputType: input,
},
),
);
return widget.editorState.apply(
transaction,
options: const ApplyOptions(
recordUndo: false,
recordRedo: false,
),
withUpdateCursor: false,
);
}
} }

View File

@ -1,4 +1,5 @@
import 'package:appflowy/core/network_monitor.dart'; 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/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_action_sheet_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';

View File

@ -1,9 +1,10 @@
import 'package:appflowy/plugins/document/document.dart'; import 'package:appflowy/plugins/document/document.dart';
import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart';
import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/presentation/home/menu/app/header/import/import_panel.dart'; import 'package:appflowy/workspace/presentation/home/menu/app/header/import/import_panel.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_editor/appflowy_editor.dart' show Document; import 'package:appflowy_editor/appflowy_editor.dart' show Document, Node;
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart';
@ -60,7 +61,12 @@ class AddButton extends StatelessWidget {
}, },
onSelected: (action, controller) { onSelected: (action, controller) {
if (action is AddButtonActionWrapper) { if (action is AddButtonActionWrapper) {
onSelected(action.pluginBuilder, null); Document? document;
if (action.pluginType == PluginType.editor) {
// initialize the document if needed.
document = buildInitialDocument();
}
onSelected(action.pluginBuilder, document);
} }
if (action is ImportActionWrapper) { if (action is ImportActionWrapper) {
showImportPanel(context, (document) { showImportPanel(context, (document) {
@ -74,6 +80,12 @@ class AddButton extends StatelessWidget {
}, },
); );
} }
Document buildInitialDocument() {
final document = Document.empty();
document.insert([0], [Node(type: kCoverType)]);
return document;
}
} }
class AddButtonActionWrapper extends ActionCell { class AddButtonActionWrapper extends ActionCell {
@ -87,6 +99,8 @@ class AddButtonActionWrapper extends ActionCell {
@override @override
String get name => pluginBuilder.menuName; String get name => pluginBuilder.menuName;
PluginType get pluginType => pluginBuilder.pluginType;
} }
class ImportActionWrapper extends ActionCell { class ImportActionWrapper extends ActionCell {

View File

@ -1,4 +1,3 @@
#include "include/appflowy_backend/appflowy_flutter_backend_plugin.h"
// This must be included before many other Windows headers. // This must be included before many other Windows headers.
#include <windows.h> #include <windows.h>
@ -13,70 +12,85 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include "include/appflowy_backend/app_flowy_backend_plugin.h"
namespace { namespace
{
class AppFlowyBackendPlugin : public flutter::Plugin { class AppFlowyBackendPlugin : public flutter::Plugin
public: {
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
AppFlowyBackendPlugin(); AppFlowyBackendPlugin();
virtual ~AppFlowyBackendPlugin(); virtual ~AppFlowyBackendPlugin();
private: private:
// Called when a method is called on this plugin's channel from Dart. // Called when a method is called on this plugin's channel from Dart.
void HandleMethodCall( void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call, const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result); std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
}; };
// static // static
void AppFlowyBackendPlugin::RegisterWithRegistrar( void AppFlowyBackendPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows *registrar) { flutter::PluginRegistrarWindows *registrar)
auto channel = {
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>( auto channel =
registrar->messenger(), "appflowy_backend", std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
&flutter::StandardMethodCodec::GetInstance()); registrar->messenger(), "appflowy_backend",
&flutter::StandardMethodCodec::GetInstance());
auto plugin = std::make_unique<AppFlowyBackendPlugin>(); auto plugin = std::make_unique<AppFlowyBackendPlugin>();
channel->SetMethodCallHandler( channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto &call, auto result) { [plugin_pointer = plugin.get()](const auto &call, auto result)
plugin_pointer->HandleMethodCall(call, std::move(result)); {
}); plugin_pointer->HandleMethodCall(call, std::move(result));
});
registrar->AddPlugin(std::move(plugin)); registrar->AddPlugin(std::move(plugin));
}
AppFlowyBackendPlugin::AppFlowyBackendPlugin() {}
AppFlowyBackendPlugin::~AppFlowyBackendPlugin() {}
void AppFlowyBackendPlugin::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
if (method_call.method_name().compare("getPlatformVersion") == 0) {
std::ostringstream version_stream;
version_stream << "Windows ";
if (IsWindows10OrGreater()) {
version_stream << "10+";
} else if (IsWindows8OrGreater()) {
version_stream << "8";
} else if (IsWindows7OrGreater()) {
version_stream << "7";
}
result->Success(flutter::EncodableValue(version_stream.str()));
} else {
result->NotImplemented();
} }
}
} // namespace AppFlowyBackendPlugin::AppFlowyBackendPlugin() {}
AppFlowyBackendPlugin::~AppFlowyBackendPlugin() {}
void AppFlowyBackendPlugin::HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
{
if (method_call.method_name().compare("getPlatformVersion") == 0)
{
std::ostringstream version_stream;
version_stream << "Windows ";
if (IsWindows10OrGreater())
{
version_stream << "10+";
}
else if (IsWindows8OrGreater())
{
version_stream << "8";
}
else if (IsWindows7OrGreater())
{
version_stream << "7";
}
result->Success(flutter::EncodableValue(version_stream.str()));
}
else
{
result->NotImplemented();
}
}
} // namespace
void AppFlowyBackendPluginRegisterWithRegistrar( void AppFlowyBackendPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) { FlutterDesktopPluginRegistrarRef registrar)
{
AppFlowyBackendPlugin::RegisterWithRegistrar( AppFlowyBackendPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarManager::GetInstance() flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar)); ->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
} }

View File

@ -5,8 +5,9 @@
#include "appflowy_flutter_backend_plugin.h" #include "appflowy_flutter_backend_plugin.h"
void AppFlowyBackendPluginCApiRegisterWithRegistrar( void AppFlowyBackendPluginCApiRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) { FlutterDesktopPluginRegistrarRef registrar)
appflowy_backend::AppFlowyBackendPlugin::RegisterWithRegistrar( {
flutter::PluginRegistrarManager::GetInstance() appflowy_backend::AppFlowyBackendPlugin::RegisterWithRegistrar(
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar)); flutter::PluginRegistrarManager::GetInstance()
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
} }

View File

@ -59,12 +59,18 @@ extension CommandExtension on EditorState {
List<String> res = []; List<String> res = [];
if (!selection.isCollapsed) { if (!selection.isCollapsed) {
for (var i = 0; i < textNodes.length; i++) { for (var i = 0; i < textNodes.length; i++) {
final plainText = textNodes[i].toPlainText();
if (i == 0) { if (i == 0) {
res.add(textNodes[i].toPlainText().substring(selection.startIndex)); res.add(
plainText.substring(
selection.startIndex,
plainText.length,
),
);
} else if (i == textNodes.length - 1) { } else if (i == textNodes.length - 1) {
res.add(textNodes[i].toPlainText().substring(0, selection.endIndex)); res.add(plainText.substring(0, selection.endIndex));
} else { } else {
res.add(textNodes[i].toPlainText()); res.add(plainText);
} }
} }
} }

View File

@ -32,6 +32,7 @@ class AppFlowyEditor extends StatefulWidget {
this.toolbarItems = const [], this.toolbarItems = const [],
this.editable = true, this.editable = true,
this.autoFocus = false, this.autoFocus = false,
this.focusedSelection,
this.customActionMenuBuilder, this.customActionMenuBuilder,
ThemeData? themeData, ThemeData? themeData,
}) : super(key: key) { }) : super(key: key) {
@ -60,6 +61,7 @@ class AppFlowyEditor extends StatefulWidget {
/// Set the value to true to focus the editor on the start of the document. /// Set the value to true to focus the editor on the start of the document.
final bool autoFocus; final bool autoFocus;
final Selection? focusedSelection;
final Positioned Function(BuildContext context, List<ActionMenuItem> items)? final Positioned Function(BuildContext context, List<ActionMenuItem> items)?
customActionMenuBuilder; customActionMenuBuilder;
@ -89,7 +91,8 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (widget.editable && widget.autoFocus) { if (widget.editable && widget.autoFocus) {
editorState.service.selectionService.updateSelection( editorState.service.selectionService.updateSelection(
Selection.single(path: [0], startOffset: 0), widget.focusedSelection ??
Selection.single(path: [0], startOffset: 0),
); );
} }
}); });

View File

@ -12,3 +12,5 @@ export 'src/divider/divider_shortcut_event.dart';
export 'src/emoji_picker/emoji_menu_item.dart'; export 'src/emoji_picker/emoji_menu_item.dart';
// Math Equation // Math Equation
export 'src/math_ equation/math_equation_node_widget.dart'; export 'src/math_ equation/math_equation_node_widget.dart';
export 'src/extensions/theme_extension.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; 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/application/setting/group_bloc.dart';
import 'package:appflowy/plugins/database_view/board/application/board_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/grid/application/cell/select_option_editor_bloc.dart';

View File

@ -1,4 +1,5 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart'; import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart'; import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
@ -105,11 +106,9 @@ class BoardTestContext {
) async { ) async {
final RowInfo rowInfo = rowInfos.last; final RowInfo rowInfo = rowInfos.last;
final rowCache = _boardDataController.rowCache; final rowCache = _boardDataController.rowCache;
final fieldController = _boardDataController.fieldController;
final rowDataController = RowDataController( final rowDataController = RowDataController(
rowInfo: rowInfo, rowInfo: rowInfo,
fieldController: fieldController,
rowCache: rowCache, rowCache: rowCache,
); );
@ -122,7 +121,6 @@ class BoardTestContext {
return CellControllerBuilder( return CellControllerBuilder(
cellId: rowBloc.state.cellByFieldId[fieldId]!, cellId: rowBloc.state.cellByFieldId[fieldId]!,
cellCache: rowCache.cellCache, cellCache: rowCache.cellCache,
delegate: rowDataController,
); );
} }

View File

@ -44,7 +44,7 @@ void main() {
); );
await gridResponseFuture(); await gridResponseFuture();
assert(context.fieldController.filterInfos.isEmpty); expect(context.fieldController.filterInfos.length, 0);
}); });
test('filter rows with condition: text is empty', () async { test('filter rows with condition: text is empty', () async {
@ -53,7 +53,7 @@ void main() {
final gridController = DatabaseController(view: context.gridView); final gridController = DatabaseController(view: context.gridView);
final gridBloc = GridBloc( final gridBloc = GridBloc(
view: context.gridView, view: context.gridView,
gridController: gridController, databaseController: gridController,
)..add(const GridEvent.initial()); )..add(const GridEvent.initial());
await gridResponseFuture(); await gridResponseFuture();
@ -64,7 +64,7 @@ void main() {
content: ""); content: "");
await gridResponseFuture(); await gridResponseFuture();
assert(gridBloc.state.rowInfos.length == 3); expect(gridBloc.state.rowInfos.length, 3);
}); });
test('filter rows with condition: text is empty(After edit the row)', test('filter rows with condition: text is empty(After edit the row)',
@ -74,7 +74,7 @@ void main() {
final gridController = DatabaseController(view: context.gridView); final gridController = DatabaseController(view: context.gridView);
final gridBloc = GridBloc( final gridBloc = GridBloc(
view: context.gridView, view: context.gridView,
gridController: gridController, databaseController: gridController,
)..add(const GridEvent.initial()); )..add(const GridEvent.initial());
await gridResponseFuture(); await gridResponseFuture();
@ -115,7 +115,7 @@ void main() {
final gridController = DatabaseController(view: context.gridView); final gridController = DatabaseController(view: context.gridView);
final gridBloc = GridBloc( final gridBloc = GridBloc(
view: context.gridView, view: context.gridView,
gridController: gridController, databaseController: gridController,
)..add(const GridEvent.initial()); )..add(const GridEvent.initial());
await gridResponseFuture(); await gridResponseFuture();
@ -134,7 +134,7 @@ void main() {
final gridController = DatabaseController(view: context.gridView); final gridController = DatabaseController(view: context.gridView);
final gridBloc = GridBloc( final gridBloc = GridBloc(
view: context.gridView, view: context.gridView,
gridController: gridController, databaseController: gridController,
)..add(const GridEvent.initial()); )..add(const GridEvent.initial());
await gridResponseFuture(); await gridResponseFuture();

View File

@ -20,7 +20,7 @@ void main() {
"create a row", "create a row",
build: () => GridBloc( build: () => GridBloc(
view: context.gridView, view: context.gridView,
gridController: DatabaseController(view: context.gridView)) databaseController: DatabaseController(view: context.gridView))
..add(const GridEvent.initial()), ..add(const GridEvent.initial()),
act: (bloc) => bloc.add(const GridEvent.createRow()), act: (bloc) => bloc.add(const GridEvent.createRow()),
wait: const Duration(milliseconds: 300), wait: const Duration(milliseconds: 300),
@ -33,7 +33,7 @@ void main() {
"delete the last row", "delete the last row",
build: () => GridBloc( build: () => GridBloc(
view: context.gridView, view: context.gridView,
gridController: DatabaseController(view: context.gridView)) databaseController: DatabaseController(view: context.gridView))
..add(const GridEvent.initial()), ..add(const GridEvent.initial()),
act: (bloc) async { act: (bloc) async {
await gridResponseFuture(); await gridResponseFuture();

View File

@ -1,4 +1,5 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart'; import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart'; import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
@ -66,11 +67,9 @@ class GridTestContext {
) async { ) async {
final RowInfo rowInfo = rowInfos[rowIndex]; final RowInfo rowInfo = rowInfos[rowIndex];
final rowCache = gridController.rowCache; final rowCache = gridController.rowCache;
final fieldController = gridController.fieldController;
final rowDataController = RowDataController( final rowDataController = RowDataController(
rowInfo: rowInfo, rowInfo: rowInfo,
fieldController: fieldController,
rowCache: rowCache, rowCache: rowCache,
); );
@ -83,7 +82,6 @@ class GridTestContext {
return CellControllerBuilder( return CellControllerBuilder(
cellId: rowBloc.state.cellByFieldId[fieldId]!, cellId: rowBloc.state.cellByFieldId[fieldId]!,
cellCache: rowCache.cellCache, cellCache: rowCache.cellCache,
delegate: rowDataController,
); );
} }

View File

@ -1,7 +1,6 @@
import { Routes, Route, BrowserRouter } from 'react-router-dom'; import { Routes, Route, BrowserRouter } from 'react-router-dom';
import { TestColors } from './components/TestColors/TestColors'; import { TestColors } from './components/TestColors/TestColors';
import { Welcome } from './views/Welcome';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { store } from './stores/store'; import { store } from './stores/store';
import { DocumentPage } from './views/DocumentPage'; import { DocumentPage } from './views/DocumentPage';
@ -29,7 +28,6 @@ const App = () => {
<Route path={'/page/document/:id'} element={<DocumentPage />} /> <Route path={'/page/document/:id'} element={<DocumentPage />} />
<Route path={'/page/board/:id'} element={<BoardPage />} /> <Route path={'/page/board/:id'} element={<BoardPage />} />
<Route path={'/page/grid/:id'} element={<GridPage />} /> <Route path={'/page/grid/:id'} element={<GridPage />} />
<Route path={'/'} element={<Welcome />} />
</Route> </Route>
<Route path={'/auth/login'} element={<LoginPage />}></Route> <Route path={'/auth/login'} element={<LoginPage />}></Route>
<Route path={'/auth/getStarted'} element={<GetStarted />}></Route> <Route path={'/auth/getStarted'} element={<GetStarted />}></Route>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import TestApiButton from './TestApiButton';
import { import {
TestCreateGrid, TestCreateGrid,
TestCreateNewField, TestCreateNewField,
@ -18,7 +17,7 @@ export const TestAPI = () => {
return ( return (
<React.Fragment> <React.Fragment>
<ul className='m-6, space-y-2'> <ul className='m-6, space-y-2'>
<TestApiButton></TestApiButton> {/*<TestApiButton></TestApiButton>*/}
<TestCreateGrid></TestCreateGrid> <TestCreateGrid></TestCreateGrid>
<TestCreateRow></TestCreateRow> <TestCreateRow></TestCreateRow>
<TestDeleteRow></TestDeleteRow> <TestDeleteRow></TestDeleteRow>

View File

@ -126,8 +126,10 @@ export const TestCreateSelectOptionInCell = () => {
); );
await cellController.subscribeChanged({ await cellController.subscribeChanged({
onCellChanged: (value) => { onCellChanged: (value) => {
const option: SelectOptionCellDataPB = value.unwrap(); if (value.some) {
console.log(option); const option: SelectOptionCellDataPB = value.unwrap();
console.log(option);
}
}, },
}); });
const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier); const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);

View File

@ -1,16 +1,56 @@
import { Navigate, Outlet, useLocation } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import { useAuth } from './auth.hooks'; import { useAuth } from './auth.hooks';
import { Screen } from '../layout/Screen'; import { Screen } from '../layout/Screen';
import { useEffect, useState } from 'react';
import { GetStarted } from './GetStarted/GetStarted';
import { AppflowyLogo } from '../_shared/svg/AppflowyLogo';
export const ProtectedRoutes = () => { export const ProtectedRoutes = () => {
const location = useLocation(); const { currentUser, checkUser } = useAuth();
const { currentUser } = useAuth(); const [isLoading, setIsLoading] = useState(true);
return currentUser.isAuthenticated ? ( useEffect(() => {
<Screen> void checkUser().then(async (result) => {
<Outlet /> await new Promise(() =>
</Screen> setTimeout(() => {
) : ( setIsLoading(false);
<Navigate to='/auth/getStarted' replace state={{ from: location }} /> }, 1200)
);
if (result.err) {
throw new Error(result.val.msg);
}
});
}, []);
if (isLoading) {
// It's better to make a fading effect to disappear the loading page
return <StartLoading />;
} else {
return <SplashScreen isAuthenticated={currentUser.isAuthenticated} />;
}
};
const StartLoading = () => {
return (
<div className='flex h-screen w-full flex-col items-center justify-center'>
<div className='h-40 w-40 justify-center'>
<AppflowyLogo />
</div>
</div>
); );
}; };
const SplashScreen = ({ isAuthenticated }: { isAuthenticated: boolean }) => {
if (isAuthenticated) {
return (
<Screen>
<Outlet />
</Screen>
);
} else {
return <GetStarted></GetStarted>;
}
};

View File

@ -1,20 +1,46 @@
import { currentUserActions } from '../../stores/reducers/current-user/slice'; import { currentUserActions } from '../../stores/reducers/current-user/slice';
import { useAppDispatch, useAppSelector } from '../../stores/store'; import { useAppDispatch, useAppSelector } from '../../stores/store';
import { UserProfilePB } from '../../../services/backend/events/flowy-user'; import { UserProfilePB } from '../../../services/backend/events/flowy-user';
import { AuthBackendService } from '../../stores/effects/user/user_bd_svc'; import { AuthBackendService, UserBackendService } from '../../stores/effects/user/user_bd_svc';
import { FolderEventReadCurrentWorkspace } from '../../../services/backend/events/flowy-folder'; import { FolderEventReadCurrentWorkspace } from '../../../services/backend/events/flowy-folder';
import { WorkspaceSettingPB } from '../../../services/backend/models/flowy-folder/workspace'; import { WorkspaceSettingPB } from '../../../services/backend/models/flowy-folder/workspace';
import { Log } from '../../utils/log';
export const useAuth = () => { export const useAuth = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const currentUser = useAppSelector((state) => state.currentUser); const currentUser = useAppSelector((state) => state.currentUser);
const authBackendService = new AuthBackendService(); const authBackendService = new AuthBackendService();
async function checkUser() {
const result = await UserBackendService.checkUser();
if (result.ok) {
const userProfile = result.val;
const workspaceSetting = await _openWorkspace().then((r) => {
if (r.ok) {
return r.val;
} else {
return undefined;
}
});
dispatch(
currentUserActions.checkUser({
id: userProfile.id,
token: userProfile.token,
email: userProfile.email,
displayName: userProfile.name,
isAuthenticated: true,
workspaceSetting: workspaceSetting,
})
);
}
return result;
}
async function register(email: string, password: string, name: string): Promise<UserProfilePB> { async function register(email: string, password: string, name: string): Promise<UserProfilePB> {
const authResult = await authBackendService.signUp({ email, password, name }); const authResult = await authBackendService.signUp({ email, password, name });
if (authResult.ok) { if (authResult.ok) {
const { id, token } = authResult.val; const userProfile = authResult.val;
// Get the workspace setting after user registered. The workspace setting // Get the workspace setting after user registered. The workspace setting
// contains the latest visiting view and the current workspace data. // contains the latest visiting view and the current workspace data.
const openWorkspaceResult = await _openWorkspace(); const openWorkspaceResult = await _openWorkspace();
@ -22,10 +48,10 @@ export const useAuth = () => {
const workspaceSetting: WorkspaceSettingPB = openWorkspaceResult.val; const workspaceSetting: WorkspaceSettingPB = openWorkspaceResult.val;
dispatch( dispatch(
currentUserActions.updateUser({ currentUserActions.updateUser({
id: id, id: userProfile.id,
token: token, token: userProfile.token,
email, email: userProfile.email,
displayName: name, displayName: userProfile.name,
isAuthenticated: true, isAuthenticated: true,
workspaceSetting: workspaceSetting, workspaceSetting: workspaceSetting,
}) })
@ -33,7 +59,7 @@ export const useAuth = () => {
} }
return authResult.val; return authResult.val;
} else { } else {
console.error(authResult.val.msg); Log.error(authResult.val.msg);
throw new Error(authResult.val.msg); throw new Error(authResult.val.msg);
} }
} }
@ -53,7 +79,7 @@ export const useAuth = () => {
); );
return result.val; return result.val;
} else { } else {
console.error(result.val.msg); Log.error(result.val.msg);
throw new Error(result.val.msg); throw new Error(result.val.msg);
} }
} }
@ -67,5 +93,5 @@ export const useAuth = () => {
return FolderEventReadCurrentWorkspace(); return FolderEventReadCurrentWorkspace();
} }
return { currentUser, register, login, logout }; return { currentUser, checkUser, register, login, logout };
}; };

View File

@ -1,5 +1,5 @@
import { AppLogo } from '../AppLogo'; import { AppLogo } from '../AppLogo';
import { Workspace } from '../Workspace'; import { WorkspaceUser } from '../WorkspaceUser';
import { FolderItem } from './FolderItem'; import { FolderItem } from './FolderItem';
import { PluginsButton } from './PluginsButton'; import { PluginsButton } from './PluginsButton';
import { TrashButton } from './TrashButton'; import { TrashButton } from './TrashButton';
@ -52,7 +52,7 @@ export const NavigationFloatingPanel = ({
<div className={'flex flex-col'}> <div className={'flex flex-col'}>
<AppLogo iconToShow={'show'} onShowMenuClick={onFixNavigationClick}></AppLogo> <AppLogo iconToShow={'show'} onShowMenuClick={onFixNavigationClick}></AppLogo>
<Workspace></Workspace> <WorkspaceUser></WorkspaceUser>
<div className={'flex flex-col px-2'}> <div className={'flex flex-col px-2'}>
{folders.map((folder, index) => ( {folders.map((folder, index) => (

View File

@ -1,12 +1,13 @@
import { Workspace } from '../Workspace'; import { WorkspaceUser } from '../WorkspaceUser';
import { AppLogo } from '../AppLogo'; import { AppLogo } from '../AppLogo';
import { FolderItem } from './FolderItem'; import { FolderItem } from './FolderItem';
import { PluginsButton } from './PluginsButton';
import { TrashButton } from './TrashButton'; import { TrashButton } from './TrashButton';
import { NewFolderButton } from './NewFolderButton'; import { NewFolderButton } from './NewFolderButton';
import { NavigationResizer } from './NavigationResizer'; import { NavigationResizer } from './NavigationResizer';
import { IFolder } from '../../../stores/reducers/folders/slice'; import { IFolder } from '../../../stores/reducers/folders/slice';
import { IPage } from '../../../stores/reducers/pages/slice'; import { IPage } from '../../../stores/reducers/pages/slice';
import { useNavigate } from 'react-router-dom';
import React from 'react';
export const NavigationPanel = ({ export const NavigationPanel = ({
onCollapseNavigationClick, onCollapseNavigationClick,
@ -26,27 +27,22 @@ export const NavigationPanel = ({
<div className={'flex flex-col justify-between bg-surface-1 text-sm'} style={{ width: `${width}px` }}> <div className={'flex flex-col justify-between bg-surface-1 text-sm'} style={{ width: `${width}px` }}>
<div className={'flex flex-col'}> <div className={'flex flex-col'}>
<AppLogo iconToShow={'hide'} onHideMenuClick={onCollapseNavigationClick}></AppLogo> <AppLogo iconToShow={'hide'} onHideMenuClick={onCollapseNavigationClick}></AppLogo>
<WorkspaceUser></WorkspaceUser>
<Workspace></Workspace> <WorkspaceApps folders={folders} pages={pages} onPageClick={onPageClick} />
<div className={'flex flex-col px-2'}>
{folders.map((folder, index) => (
<FolderItem
key={index}
folder={folder}
pages={pages.filter((page) => page.folderId === folder.id)}
onPageClick={onPageClick}
></FolderItem>
))}
</div>
</div> </div>
<div className={'flex flex-col'}> <div className={'flex flex-col'}>
<div className={'border-b border-shade-6 px-2 pb-4'}> <div className={'border-b border-shade-6 px-2 pb-4'}>
<PluginsButton></PluginsButton> {/*<PluginsButton></PluginsButton>*/}
<DesignSpec></DesignSpec>
<TestBackendButton></TestBackendButton>
{/*Trash Button*/}
<TrashButton></TrashButton> <TrashButton></TrashButton>
</div> </div>
{/*New Folder Button*/}
<NewFolderButton></NewFolderButton> <NewFolderButton></NewFolderButton>
</div> </div>
</div> </div>
@ -54,3 +50,47 @@ export const NavigationPanel = ({
</> </>
); );
}; };
type AppsContext = {
folders: IFolder[];
pages: IPage[];
onPageClick: (page: IPage) => void;
};
const WorkspaceApps: React.FC<AppsContext> = ({ folders, pages, onPageClick }) => (
<div className={'flex flex-col px-2'}>
{folders.map((folder, index) => (
<FolderItem
key={index}
folder={folder}
pages={pages.filter((page) => page.folderId === folder.id)}
onPageClick={onPageClick}
></FolderItem>
))}
</div>
);
export const TestBackendButton = () => {
const navigate = useNavigate();
return (
<button
onClick={() => navigate('/page/api-test')}
className={'flex w-full items-center rounded-lg px-4 py-2 hover:bg-surface-2'}
>
APITest
</button>
);
};
export const DesignSpec = () => {
const navigate = useNavigate();
return (
<button
onClick={() => navigate('page/colors')}
className={'flex w-full items-center rounded-lg px-4 py-2 hover:bg-surface-2'}
>
Design Specs
</button>
);
};

View File

@ -1,6 +1,6 @@
import { useAppSelector } from '../../stores/store'; import { useAppSelector } from '../../stores/store';
export const Workspace = () => { export const WorkspaceUser = () => {
const currentUser = useAppSelector((state) => state.currentUser); const currentUser = useAppSelector((state) => state.currentUser);
return ( return (

View File

@ -13,7 +13,7 @@ type Callbacks<T> = { onCellChanged: (value: Option<T>) => void; onFieldChanged?
export class CellController<T, D> { export class CellController<T, D> {
private fieldBackendService: FieldBackendService; private fieldBackendService: FieldBackendService;
private cellDataNotifier: CellDataNotifier<Option<T>>; private cellDataNotifier: CellDataNotifier<T>;
private cellObserver: CellObserver; private cellObserver: CellObserver;
private readonly cacheKey: CellCacheKey; private readonly cacheKey: CellCacheKey;
private readonly fieldNotifier: DatabaseFieldObserver; private readonly fieldNotifier: DatabaseFieldObserver;
@ -59,7 +59,7 @@ export class CellController<T, D> {
this.subscribeCallbacks = callbacks; this.subscribeCallbacks = callbacks;
this.cellDataNotifier.observer.subscribe((cellData) => { this.cellDataNotifier.observer.subscribe((cellData) => {
if (cellData !== null) { if (cellData !== null) {
callbacks.onCellChanged(cellData); callbacks.onCellChanged(Some(cellData));
} }
}); });
}; };
@ -95,8 +95,11 @@ export class CellController<T, D> {
private _loadCellData = () => { private _loadCellData = () => {
return this.cellDataLoader.loadData().then((result) => { return this.cellDataLoader.loadData().then((result) => {
if (result.ok) { if (result.ok) {
this.cellCache.insert(this.cacheKey, result.val); const cellData = result.val;
this.cellDataNotifier.cellData = Some(result.val); if (cellData.some) {
this.cellCache.insert(this.cacheKey, cellData.val);
this.cellDataNotifier.cellData = cellData;
}
} else { } else {
this.cellCache.remove(this.cacheKey); this.cellCache.remove(this.cacheKey);
this.cellDataNotifier.cellData = None; this.cellDataNotifier.cellData = None;
@ -110,10 +113,10 @@ export class CellController<T, D> {
}; };
} }
class CellDataNotifier<T> extends ChangeNotifier<T | null> { class CellDataNotifier<T> extends ChangeNotifier<T> {
_cellData: Option<T>; _cellData: Option<T>;
constructor(cellData: T) { constructor(cellData: Option<T>) {
super(); super();
this._cellData = Some(cellData); this._cellData = Some(cellData);
} }

View File

@ -1,5 +1,6 @@
import { nanoid } from '@reduxjs/toolkit'; import { nanoid } from '@reduxjs/toolkit';
import { import {
UserEventCheckUser,
UserEventGetUserProfile, UserEventGetUserProfile,
UserEventSignIn, UserEventSignIn,
UserEventSignOut, UserEventSignOut,
@ -29,6 +30,10 @@ export class UserBackendService {
return UserEventGetUserProfile(); return UserEventGetUserProfile();
}; };
static checkUser = () => {
return UserEventCheckUser();
};
updateUserProfile = (params: { name?: string; password?: string; email?: string; openAIKey?: string }) => { updateUserProfile = (params: { name?: string; password?: string; email?: string; openAIKey?: string }) => {
const payload = UpdateUserProfilePayloadPB.fromObject({ id: this.userId }); const payload = UpdateUserProfilePayloadPB.fromObject({ id: this.userId });

View File

@ -12,17 +12,16 @@ export interface ICurrentUser {
} }
const initialState: ICurrentUser | null = { const initialState: ICurrentUser | null = {
id: nanoid(8), isAuthenticated: false,
displayName: 'Me 😃',
email: `${nanoid(4)}@gmail.com`,
token: nanoid(8),
isAuthenticated: true,
}; };
export const currentUserSlice = createSlice({ export const currentUserSlice = createSlice({
name: 'currentUser', name: 'currentUser',
initialState: initialState, initialState: initialState,
reducers: { reducers: {
checkUser: (state, action: PayloadAction<ICurrentUser>) => {
return action.payload;
},
updateUser: (state, action: PayloadAction<ICurrentUser>) => { updateUser: (state, action: PayloadAction<ICurrentUser>) => {
return action.payload; return action.payload;
}, },

View File

@ -1,15 +0,0 @@
import { Link } from 'react-router-dom';
export const Welcome = () => {
return (
<div className={'p-4'}>
<div className={'mb-8 text-2xl'}>Welcome</div>
<div className={'mb-4'}>
<Link to={'/page/colors'}>Color Palette</Link>
</div>
<div className={'mb-4'}>
<Link to={'/page/api-test'}>Testing API</Link>
</div>
</div>
);
};

View File

@ -132,7 +132,7 @@ fn generate_ts_protobuf_files(
}; };
if result.is_err() { if result.is_err() {
panic!("Generate dart pb file failed with: {}, {:?}", path, result) panic!("Generate ts pb file failed with: {}, {:?}", path, result)
}; };
}); });

View File

@ -107,7 +107,7 @@ fn create_log_filter(level: String, with_crates: Vec<String>) -> String {
#[cfg(feature = "profiling")] #[cfg(feature = "profiling")]
filters.push(format!("runtime={}", level)); filters.push(format!("runtime={}", level));
filters.push(format!("tokio=trace,runtime=trace"));
filters.join(",") filters.join(",")
} }

View File

@ -534,8 +534,8 @@ pub(crate) async fn get_groups_handler(
) -> DataResult<RepeatedGroupPB, FlowyError> { ) -> DataResult<RepeatedGroupPB, FlowyError> {
let params: DatabaseViewIdPB = data.into_inner(); let params: DatabaseViewIdPB = data.into_inner();
let editor = manager.get_database_editor(&params.value).await?; let editor = manager.get_database_editor(&params.value).await?;
let group = editor.load_groups(&params.value).await?; let groups = editor.load_groups(&params.value).await?;
data_result_ok(group) data_result_ok(groups)
} }
#[tracing::instrument(level = "debug", skip(data, manager), err)] #[tracing::instrument(level = "debug", skip(data, manager), err)]

View File

@ -203,28 +203,45 @@ impl DatabaseManager {
database_id: &str, database_id: &str,
view_id: &str, view_id: &str,
) -> FlowyResult<Arc<DatabaseEditor>> { ) -> FlowyResult<Arc<DatabaseEditor>> {
if let Some(database_editor) = self.editors_by_database_id.read().await.get(database_id) { let user = self.database_user.clone();
let user_id = self.database_user.user_id()?; let create_view_editor = |database_editor: Arc<DatabaseEditor>| async move {
let (view_pad, view_rev_manager) = let user_id = user.user_id()?;
make_database_view_revision_pad(view_id, self.database_user.clone()).await?; let (view_pad, view_rev_manager) = make_database_view_revision_pad(view_id, user).await?;
return DatabaseViewEditor::from_pad(
let view_editor = DatabaseViewEditor::from_pad(
&user_id, &user_id,
database_editor.database_view_data.clone(), database_editor.database_view_data.clone(),
database_editor.cell_data_cache.clone(), database_editor.cell_data_cache.clone(),
view_rev_manager, view_rev_manager,
view_pad, view_pad,
) )
.await?; .await;
database_editor.open_view_editor(view_editor).await; };
return Ok(database_editor.clone());
} let database_editor = self
// Lock the database_editors .editors_by_database_id
let mut editors_by_database_id = self.editors_by_database_id.write().await; .read()
let db_pool = self.database_user.db_pool()?; .await
let editor = self.make_database_rev_editor(view_id, db_pool).await?; .get(database_id)
editors_by_database_id.insert(database_id.to_string(), editor.clone()); .cloned();
Ok(editor)
return match database_editor {
None => {
let mut editors_by_database_id = self.editors_by_database_id.write().await;
let db_pool = self.database_user.db_pool()?;
let database_editor = self.make_database_rev_editor(view_id, db_pool).await?;
editors_by_database_id.insert(database_id.to_string(), database_editor.clone());
Ok(database_editor)
},
Some(database_editor) => {
let is_open = database_editor.is_view_open(view_id).await;
if !is_open {
let database_view_editor = create_view_editor(database_editor.clone()).await?;
database_editor.open_view_editor(database_view_editor).await;
}
Ok(database_editor)
},
};
} }
#[tracing::instrument(level = "trace", skip(self, pool), err)] #[tracing::instrument(level = "trace", skip(self, pool), err)]

View File

@ -131,6 +131,9 @@ impl DatabaseEditor {
self.database_views.number_of_views().await self.database_views.number_of_views().await
} }
pub async fn is_view_open(&self, view_id: &str) -> bool {
self.database_views.is_view_exist(view_id).await
}
/// Save the type-option data to disk and send a `DatabaseNotification::DidUpdateField` notification /// Save the type-option data to disk and send a `DatabaseNotification::DidUpdateField` notification
/// to dart side. /// to dart side.
/// ///

Some files were not shown because too many files have changed in this diff Show More