mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge remote-tracking branch 'origin/feat/appflowy_tauri_3' into feat/appflowy_tauri_3
# Conflicts: # frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx
This commit is contained in:
commit
fb8a81eca5
1
frontend/.vscode/settings.json
vendored
1
frontend/.vscode/settings.json
vendored
@ -24,7 +24,6 @@
|
||||
"svgviewer.showzoominout": true,
|
||||
"editor.wordWrapColumn": 80,
|
||||
"editor.minimap.maxColumn": 140,
|
||||
"prettier.printWidth": 140,
|
||||
"editor.wordWrap": "wordWrapColumn",
|
||||
"dart.lineLength": 80,
|
||||
"typescript.validate.enable": true,
|
||||
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 164 KiB |
Binary file not shown.
After Width: | Height: | Size: 275 KiB |
@ -353,7 +353,15 @@
|
||||
"smartEditFixSpelling": "Fix spelling",
|
||||
"smartEditSummarize": "Summarize",
|
||||
"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": {
|
||||
@ -371,4 +379,4 @@
|
||||
"nextMonth": "Next Month"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,118 +1,16 @@
|
||||
part of '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>;
|
||||
|
||||
abstract class CellControllerBuilderDelegate {
|
||||
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;
|
||||
}
|
||||
}
|
||||
import 'dart:async';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_listener.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../field/field_controller.dart';
|
||||
import '../field/field_service.dart';
|
||||
import '../field/type_option/type_option_context.dart';
|
||||
import 'cell_listener.dart';
|
||||
import 'cell_service.dart';
|
||||
|
||||
/// IGridCellController is used to manipulate the cell and receive notifications.
|
||||
/// * Read/Write cell data
|
||||
@ -124,40 +22,19 @@ class CellControllerBuilder {
|
||||
// ignore: must_be_immutable
|
||||
class CellController<T, D> extends Equatable {
|
||||
final CellIdentifier cellId;
|
||||
final CellCache _cellsCache;
|
||||
final CellCache _cellCache;
|
||||
final CellCacheKey _cacheKey;
|
||||
final FieldBackendService _fieldBackendSvc;
|
||||
final CellFieldNotifier _fieldNotifier;
|
||||
final SingleFieldListener _fieldListener;
|
||||
final CellDataLoader<T> _cellDataLoader;
|
||||
final CellDataPersistence<D> _cellDataPersistence;
|
||||
|
||||
CellListener? _cellListener;
|
||||
CellDataNotifier<T?>? _cellDataNotifier;
|
||||
|
||||
bool isListening = false;
|
||||
VoidCallback? _onFieldChangedFn;
|
||||
VoidCallback? _onCellFieldChanged;
|
||||
Timer? _loadDataOperation;
|
||||
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;
|
||||
|
||||
@ -169,34 +46,28 @@ class CellController<T, D> extends Equatable {
|
||||
|
||||
FieldType get fieldType => cellId.fieldInfo.fieldType;
|
||||
|
||||
/// Listen on the cell content or field changes
|
||||
///
|
||||
/// An optional [listenWhenOnCellChanged] can be implemented for more
|
||||
/// granular control over when [listener] is called.
|
||||
/// [listenWhenOnCellChanged] will be invoked on each [onCellChanged]
|
||||
/// get called.
|
||||
/// [listenWhenOnCellChanged] takes the previous `value` and current
|
||||
/// `value` and must return a [bool] which determines whether or not
|
||||
/// the [onCellChanged] function will be invoked.
|
||||
/// [onCellChanged] is optional and if omitted, it will default to `true`.
|
||||
///
|
||||
VoidCallback? startListening({
|
||||
required void Function(T?) onCellChanged,
|
||||
bool Function(T? oldValue, T? newValue)? listenWhenOnCellChanged,
|
||||
VoidCallback? onCellFieldChanged,
|
||||
}) {
|
||||
if (isListening) {
|
||||
Log.error("Already started. It seems like you should call clone first");
|
||||
return null;
|
||||
}
|
||||
isListening = true;
|
||||
|
||||
_cellDataNotifier = CellDataNotifier(
|
||||
value: _cellsCache.get(_cacheKey),
|
||||
listenWhen: listenWhenOnCellChanged,
|
||||
CellController({
|
||||
required this.cellId,
|
||||
required CellCache cellCache,
|
||||
required CellDataLoader<T> cellDataLoader,
|
||||
required CellDataPersistence<D> cellDataPersistence,
|
||||
}) : _cellCache = cellCache,
|
||||
_cellDataLoader = cellDataLoader,
|
||||
_cellDataPersistence = cellDataPersistence,
|
||||
_fieldListener = SingleFieldListener(fieldId: cellId.fieldId),
|
||||
_fieldBackendSvc = FieldBackendService(
|
||||
viewId: cellId.viewId,
|
||||
fieldId: cellId.fieldInfo.id,
|
||||
),
|
||||
_cacheKey = CellCacheKey(
|
||||
rowId: cellId.rowId,
|
||||
fieldId: cellId.fieldInfo.id,
|
||||
) {
|
||||
_cellDataNotifier = CellDataNotifier(value: _cellCache.get(_cacheKey));
|
||||
_cellListener = CellListener(
|
||||
rowId: cellId.rowId,
|
||||
fieldId: cellId.fieldInfo.id,
|
||||
);
|
||||
_cellListener =
|
||||
CellListener(rowId: cellId.rowId, fieldId: cellId.fieldInfo.id);
|
||||
|
||||
/// 1.Listen on user edit event and load the new cell data if needed.
|
||||
/// For example:
|
||||
@ -205,7 +76,7 @@ class CellController<T, D> extends Equatable {
|
||||
_cellListener?.start(onCellChanged: (result) {
|
||||
result.fold(
|
||||
(_) {
|
||||
_cellsCache.remove(_cacheKey);
|
||||
_cellCache.remove(_cacheKey);
|
||||
_loadData();
|
||||
},
|
||||
(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.
|
||||
_onFieldChangedFn = () {
|
||||
if (onCellFieldChanged != null) {
|
||||
onCellFieldChanged();
|
||||
}
|
||||
_fieldListener.start(onFieldChanged: (result) {
|
||||
result.fold((fieldPB) {
|
||||
/// 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
|
||||
/// For example:
|
||||
/// ¥12 -> $12
|
||||
if (_cellDataLoader.reloadOnFieldChanged) {
|
||||
_loadData();
|
||||
}
|
||||
};
|
||||
|
||||
_fieldNotifier.register(_cacheKey, _onFieldChangedFn!);
|
||||
/// Listen on the cell content or field changes
|
||||
VoidCallback? startListening({
|
||||
required void Function(T?) onCellChanged,
|
||||
VoidCallback? onCellFieldChanged,
|
||||
}) {
|
||||
_onCellFieldChanged = onCellFieldChanged;
|
||||
|
||||
/// Notify the listener, the cell data was changed.
|
||||
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.
|
||||
/// You can set [loadIfNotExist] to false (default is true) to disable loading the cell data.
|
||||
T? getCellData({bool loadIfNotExist = true}) {
|
||||
final data = _cellsCache.get(_cacheKey);
|
||||
final data = _cellCache.get(_cacheKey);
|
||||
if (data == null && loadIfNotExist) {
|
||||
_loadData();
|
||||
}
|
||||
@ -294,9 +170,9 @@ class CellController<T, D> extends Equatable {
|
||||
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
||||
_cellDataLoader.loadData().then((data) {
|
||||
if (data != null) {
|
||||
_cellsCache.insert(_cacheKey, GridBaseCell(object: data));
|
||||
_cellCache.insert(_cacheKey, GridBaseCell(object: data));
|
||||
} else {
|
||||
_cellsCache.remove(_cacheKey);
|
||||
_cellCache.remove(_cacheKey);
|
||||
}
|
||||
|
||||
_cellDataNotifier?.value = data;
|
||||
@ -305,54 +181,17 @@ class CellController<T, D> extends Equatable {
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
if (_isDispose) {
|
||||
Log.error("$this should only dispose once");
|
||||
return;
|
||||
}
|
||||
_isDispose = true;
|
||||
await _cellListener?.stop();
|
||||
_loadDataOperation?.cancel();
|
||||
_saveDataOperation?.cancel();
|
||||
_cellDataNotifier?.dispose();
|
||||
await _fieldListener.stop();
|
||||
_cellDataNotifier = null;
|
||||
|
||||
if (_onFieldChangedFn != null) {
|
||||
_fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!);
|
||||
await _fieldNotifier.dispose();
|
||||
_onFieldChangedFn = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props =>
|
||||
[_cellsCache.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,
|
||||
);
|
||||
}
|
||||
[_cellCache.get(_cacheKey) ?? "", cellId.rowId + cellId.fieldInfo.id];
|
||||
}
|
||||
|
||||
class CellDataNotifier<T> extends ChangeNotifier {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.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 '../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_data_loader.dart';
|
||||
part 'cell_controller.dart';
|
||||
part 'cell_cache.dart';
|
||||
part 'cell_data_persistence.dart';
|
||||
|
||||
|
@ -10,7 +10,7 @@ import 'type_option/type_option_data_controller.dart';
|
||||
part 'field_editor_bloc.freezed.dart';
|
||||
|
||||
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
final TypeOptionDataController dataController;
|
||||
final TypeOptionController dataController;
|
||||
|
||||
FieldEditorBloc({
|
||||
required String viewId,
|
||||
@ -18,7 +18,7 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
required bool isGroupField,
|
||||
required IFieldTypeOptionLoader loader,
|
||||
}) : dataController =
|
||||
TypeOptionDataController(viewId: viewId, loader: loader),
|
||||
TypeOptionController(viewId: viewId, loader: loader),
|
||||
super(FieldEditorState.initial(viewId, fieldName, isGroupField)) {
|
||||
on<FieldEditorEvent>(
|
||||
(event, emit) async {
|
||||
|
@ -8,10 +8,10 @@ part 'field_type_option_edit_bloc.freezed.dart';
|
||||
|
||||
class FieldTypeOptionEditBloc
|
||||
extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> {
|
||||
final TypeOptionDataController _dataController;
|
||||
final TypeOptionController _dataController;
|
||||
void Function()? _fieldListenFn;
|
||||
|
||||
FieldTypeOptionEditBloc(TypeOptionDataController dataController)
|
||||
FieldTypeOptionEditBloc(TypeOptionController dataController)
|
||||
: _dataController = dataController,
|
||||
super(FieldTypeOptionEditState.initial(dataController)) {
|
||||
on<FieldTypeOptionEditEvent>(
|
||||
@ -58,9 +58,9 @@ class FieldTypeOptionEditState with _$FieldTypeOptionEditState {
|
||||
}) = _FieldTypeOptionEditState;
|
||||
|
||||
factory FieldTypeOptionEditState.initial(
|
||||
TypeOptionDataController typeOptionDataController,
|
||||
TypeOptionController typeOptionController,
|
||||
) =>
|
||||
FieldTypeOptionEditState(
|
||||
field: typeOptionDataController.field,
|
||||
field: typeOptionController.field,
|
||||
);
|
||||
}
|
||||
|
@ -109,11 +109,11 @@ class ChecklistTypeOptionWidgetDataParser
|
||||
class TypeOptionContext<T extends GeneratedMessage> {
|
||||
T? _typeOptionObject;
|
||||
final TypeOptionParser<T> dataParser;
|
||||
final TypeOptionDataController _dataController;
|
||||
final TypeOptionController _dataController;
|
||||
|
||||
TypeOptionContext({
|
||||
required this.dataParser,
|
||||
required TypeOptionDataController dataController,
|
||||
required TypeOptionController dataController,
|
||||
}) : _dataController = dataController;
|
||||
|
||||
String get viewId => _dataController.viewId;
|
||||
|
@ -9,25 +9,25 @@ import 'package:appflowy_backend/log.dart';
|
||||
import '../field_service.dart';
|
||||
import 'type_option_context.dart';
|
||||
|
||||
class TypeOptionDataController {
|
||||
class TypeOptionController {
|
||||
final String viewId;
|
||||
late TypeOptionPB _typeOption;
|
||||
final IFieldTypeOptionLoader loader;
|
||||
late TypeOptionPB _typeOptiondata;
|
||||
final PublishNotifier<FieldPB> _fieldNotifier = PublishNotifier();
|
||||
|
||||
/// Returns a [TypeOptionDataController] used to modify the specified
|
||||
/// Returns a [TypeOptionController] used to modify the specified
|
||||
/// [FieldPB]'s data
|
||||
///
|
||||
/// Should call [loadTypeOptionData] if the passed-in [FieldInfo]
|
||||
/// is null
|
||||
///
|
||||
TypeOptionDataController({
|
||||
TypeOptionController({
|
||||
required this.viewId,
|
||||
required this.loader,
|
||||
FieldInfo? fieldInfo,
|
||||
}) {
|
||||
if (fieldInfo != null) {
|
||||
_typeOptiondata = TypeOptionPB.create()
|
||||
_typeOption = TypeOptionPB.create()
|
||||
..viewId = viewId
|
||||
..field_2 = fieldInfo.field;
|
||||
}
|
||||
@ -38,7 +38,7 @@ class TypeOptionDataController {
|
||||
return result.fold(
|
||||
(data) {
|
||||
data.freeze();
|
||||
_typeOptiondata = data;
|
||||
_typeOption = data;
|
||||
_fieldNotifier.value = data.field_2;
|
||||
return left(data);
|
||||
},
|
||||
@ -50,28 +50,28 @@ class TypeOptionDataController {
|
||||
}
|
||||
|
||||
FieldPB get field {
|
||||
return _typeOptiondata.field_2;
|
||||
return _typeOption.field_2;
|
||||
}
|
||||
|
||||
T getTypeOption<T>(TypeOptionParser<T> parser) {
|
||||
return parser.fromBuffer(_typeOptiondata.typeOptionData);
|
||||
return parser.fromBuffer(_typeOption.typeOptionData);
|
||||
}
|
||||
|
||||
set fieldName(String name) {
|
||||
_typeOptiondata = _typeOptiondata.rebuild((rebuildData) {
|
||||
_typeOption = _typeOption.rebuild((rebuildData) {
|
||||
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
|
||||
rebuildField.name = name;
|
||||
});
|
||||
});
|
||||
|
||||
_fieldNotifier.value = _typeOptiondata.field_2;
|
||||
_fieldNotifier.value = _typeOption.field_2;
|
||||
|
||||
FieldBackendService(viewId: viewId, fieldId: field.id)
|
||||
.updateField(name: name);
|
||||
}
|
||||
|
||||
set typeOptionData(List<int> typeOptionData) {
|
||||
_typeOptiondata = _typeOptiondata.rebuild((rebuildData) {
|
||||
_typeOption = _typeOption.rebuild((rebuildData) {
|
||||
if (typeOptionData.isNotEmpty) {
|
||||
rebuildData.typeOptionData = typeOptionData;
|
||||
}
|
||||
|
@ -1,24 +1,20 @@
|
||||
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 '../field/field_controller.dart';
|
||||
import 'row_cache.dart';
|
||||
|
||||
typedef OnRowChanged = void Function(CellByFieldId, RowsChangedReason);
|
||||
|
||||
class RowDataController extends GridCellBuilderDelegate {
|
||||
class RowDataController {
|
||||
final RowInfo rowInfo;
|
||||
final List<VoidCallback> _onRowChangedListeners = [];
|
||||
final FieldController _fieldController;
|
||||
final RowCache _rowCache;
|
||||
|
||||
get cellCache => _rowCache.cellCache;
|
||||
|
||||
RowDataController({
|
||||
required this.rowInfo,
|
||||
required FieldController fieldController,
|
||||
required RowCache rowCache,
|
||||
}) : _fieldController = fieldController,
|
||||
_rowCache = rowCache;
|
||||
}) : _rowCache = rowCache;
|
||||
|
||||
CellByFieldId loadData() {
|
||||
return _rowCache.loadGridCells(rowInfo.rowPB.id);
|
||||
@ -36,14 +32,4 @@ class RowDataController extends GridCellBuilderDelegate {
|
||||
_rowCache.removeRowListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
// GridCellBuilderDelegate implementation
|
||||
@override
|
||||
CellFieldNotifier buildFieldNotifier() {
|
||||
return CellFieldNotifier(
|
||||
notifier: GridCellFieldNotifierImpl(_fieldController));
|
||||
}
|
||||
|
||||
@override
|
||||
CellCache get cellCache => _rowCache.cellCache;
|
||||
}
|
||||
|
@ -20,18 +20,18 @@ import 'group_controller.dart';
|
||||
part 'board_bloc.freezed.dart';
|
||||
|
||||
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
final BoardDataController _gridDataController;
|
||||
final BoardDataController _boardDataController;
|
||||
late final AppFlowyBoardController boardController;
|
||||
final GroupBackendService _groupBackendSvc;
|
||||
final LinkedHashMap<String, GroupController> groupControllers =
|
||||
LinkedHashMap();
|
||||
|
||||
FieldController get fieldController => _gridDataController.fieldController;
|
||||
String get viewId => _gridDataController.viewId;
|
||||
FieldController get fieldController => _boardDataController.fieldController;
|
||||
String get viewId => _boardDataController.viewId;
|
||||
|
||||
BoardBloc({required ViewPB view})
|
||||
: _groupBackendSvc = GroupBackendService(viewId: view.id),
|
||||
_gridDataController = BoardDataController(view: view),
|
||||
_boardDataController = BoardDataController(view: view),
|
||||
super(BoardState.initial(view.id)) {
|
||||
boardController = AppFlowyBoardController(
|
||||
onMoveGroup: (
|
||||
@ -72,7 +72,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
},
|
||||
createBottomRow: (groupId) async {
|
||||
final startRowId = groupControllers[groupId]?.lastRow()?.id;
|
||||
final result = await _gridDataController.createBoardCard(
|
||||
final result = await _boardDataController.createBoardCard(
|
||||
groupId,
|
||||
startRowId: startRowId,
|
||||
);
|
||||
@ -82,7 +82,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
},
|
||||
createHeaderRow: (String groupId) async {
|
||||
final result = await _gridDataController.createBoardCard(groupId);
|
||||
final result = await _boardDataController.createBoardCard(groupId);
|
||||
result.fold(
|
||||
(_) {},
|
||||
(err) => Log.error(err),
|
||||
@ -178,7 +178,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _gridDataController.dispose();
|
||||
await _boardDataController.dispose();
|
||||
for (final controller in groupControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
@ -204,11 +204,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
}
|
||||
|
||||
RowCache? getRowCache(String blockId) {
|
||||
return _gridDataController.rowCache;
|
||||
return _boardDataController.rowCache;
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_gridDataController.addListener(
|
||||
_boardDataController.addListener(
|
||||
onDatabaseChanged: (grid) {
|
||||
if (!isClosed) {
|
||||
add(BoardEvent.didReceiveGridUpdate(grid));
|
||||
@ -264,7 +264,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
}
|
||||
|
||||
Future<void> _openGrid(Emitter<BoardState> emit) async {
|
||||
final result = await _gridDataController.openGrid();
|
||||
final result = await _boardDataController.openGrid();
|
||||
result.fold(
|
||||
(grid) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
|
@ -21,7 +21,7 @@ typedef OnResetGroups = void Function(List<GroupPB>);
|
||||
|
||||
class BoardDataController {
|
||||
final String viewId;
|
||||
final DatabaseBackendService _databaseFFIService;
|
||||
final DatabaseBackendService _databaseSvc;
|
||||
final FieldController fieldController;
|
||||
final BoardListener _listener;
|
||||
late DatabaseViewCache _viewCache;
|
||||
@ -38,7 +38,7 @@ class BoardDataController {
|
||||
BoardDataController({required ViewPB view})
|
||||
: viewId = view.id,
|
||||
_listener = BoardListener(view.id),
|
||||
_databaseFFIService = DatabaseBackendService(viewId: view.id),
|
||||
_databaseSvc = DatabaseBackendService(viewId: view.id),
|
||||
fieldController = FieldController(viewId: view.id) {
|
||||
//
|
||||
_viewCache = DatabaseViewCache(
|
||||
@ -100,7 +100,7 @@ class BoardDataController {
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> openGrid() async {
|
||||
final result = await _databaseFFIService.openGrid();
|
||||
final result = await _databaseSvc.openGrid();
|
||||
return result.fold(
|
||||
(grid) async {
|
||||
_onDatabaseChanged?.call(grid);
|
||||
@ -121,17 +121,17 @@ class BoardDataController {
|
||||
|
||||
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId,
|
||||
{String? startRowId}) {
|
||||
return _databaseFFIService.createBoardCard(groupId, startRowId);
|
||||
return _databaseSvc.createBoardCard(groupId, startRowId);
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _viewCache.dispose();
|
||||
await _databaseFFIService.closeView();
|
||||
await _databaseSvc.closeView();
|
||||
await fieldController.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadGroups() async {
|
||||
final result = await _databaseFFIService.loadGroups();
|
||||
final result = await _databaseSvc.loadGroups();
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(groups) {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import '../../../application/cell/cell_controller_builder.dart';
|
||||
|
||||
part 'board_checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
|
@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import '../../../application/cell/cell_controller_builder.dart';
|
||||
import '../../../application/field/field_controller.dart';
|
||||
part 'board_date_cell_bloc.freezed.dart';
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
|
||||
import '../../../application/cell/cell_controller_builder.dart';
|
||||
|
||||
part 'board_number_cell_bloc.freezed.dart';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
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:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
@ -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:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
@ -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:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
|
||||
part 'board_url_cell_bloc.freezed.dart';
|
||||
|
||||
class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
|
||||
|
@ -1,9 +1,7 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../../application/cell/cell_field_notifier.dart';
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import '../../../application/field/field_controller.dart';
|
||||
import '../../../application/row/row_cache.dart';
|
||||
import '../../presentation/card/card_cell_builder.dart';
|
||||
|
||||
@ -11,16 +9,13 @@ typedef OnCardChanged = void Function(CellByFieldId, RowsChangedReason);
|
||||
|
||||
class CardDataController extends BoardCellBuilderDelegate {
|
||||
final RowPB rowPB;
|
||||
final FieldController _fieldController;
|
||||
final RowCache _rowCache;
|
||||
final List<VoidCallback> _onCardChangedListeners = [];
|
||||
|
||||
CardDataController({
|
||||
required this.rowPB,
|
||||
required FieldController fieldController,
|
||||
required RowCache rowCache,
|
||||
}) : _fieldController = fieldController,
|
||||
_rowCache = rowCache;
|
||||
}) : _rowCache = rowCache;
|
||||
|
||||
CellByFieldId loadData() {
|
||||
return _rowCache.loadGridCells(rowPB.id);
|
||||
@ -39,12 +34,6 @@ class CardDataController extends BoardCellBuilderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
CellFieldNotifier buildFieldNotifier() {
|
||||
return CellFieldNotifier(
|
||||
notifier: GridCellFieldNotifierImpl(_fieldController));
|
||||
}
|
||||
|
||||
@override
|
||||
CellCache get cellCache => _rowCache.cellCache;
|
||||
}
|
||||
|
@ -229,7 +229,6 @@ class _BoardContentState extends State<BoardContent> {
|
||||
final fieldController = context.read<BoardBloc>().fieldController;
|
||||
final viewId = context.read<BoardBloc>().viewId;
|
||||
final cardController = CardDataController(
|
||||
fieldController: fieldController,
|
||||
rowCache: rowCache,
|
||||
rowPB: rowPB,
|
||||
);
|
||||
@ -306,7 +305,6 @@ class _BoardContentState extends State<BoardContent> {
|
||||
|
||||
final dataController = RowDataController(
|
||||
rowInfo: rowInfo,
|
||||
fieldController: fieldController,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
@ -314,7 +312,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return RowDetailPage(
|
||||
cellBuilder: GridCellBuilder(delegate: dataController),
|
||||
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
|
||||
dataController: dataController,
|
||||
);
|
||||
},
|
||||
|
@ -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:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import '../../../grid/application/cell/checklist_cell_bloc.dart';
|
||||
import '../../../grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart';
|
||||
|
||||
|
@ -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:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import 'define.dart';
|
||||
|
||||
class BoardDateCell extends StatefulWidget {
|
||||
|
@ -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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import '../../application/card/board_number_cell_bloc.dart';
|
||||
import 'define.dart';
|
||||
|
||||
|
@ -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:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.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/select_option_editor.dart';
|
||||
import '../../application/card/board_select_option_cell_bloc.dart';
|
||||
|
@ -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:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:textstyle_extensions/textstyle_extensions.dart';
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import '../../application/card/board_text_cell_bloc.dart';
|
||||
import 'board_cell.dart';
|
||||
import 'define.dart';
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:textstyle_extensions/textstyle_extensions.dart';
|
||||
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import '../../application/card/board_url_cell_bloc.dart';
|
||||
import 'define.dart';
|
||||
|
||||
|
@ -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:flutter/material.dart';
|
||||
|
||||
@ -11,7 +12,7 @@ import 'board_select_option_cell.dart';
|
||||
import 'board_text_cell.dart';
|
||||
import 'board_url_cell.dart';
|
||||
|
||||
abstract class BoardCellBuilderDelegate extends CellControllerBuilderDelegate {
|
||||
abstract class BoardCellBuilderDelegate {
|
||||
CellCache get cellCache;
|
||||
}
|
||||
|
||||
@ -26,7 +27,6 @@ class BoardCellBuilder {
|
||||
EditableCellNotifier cellNotifier,
|
||||
) {
|
||||
final cellControllerBuilder = CellControllerBuilder(
|
||||
delegate: delegate,
|
||||
cellId: cellId,
|
||||
cellCache: delegate.cellCache,
|
||||
);
|
||||
|
@ -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:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
@ -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/protobuf/flowy-database/select_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -10,12 +10,12 @@ part 'checklist_cell_bloc.freezed.dart';
|
||||
|
||||
class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
final ChecklistCellController cellController;
|
||||
final SelectOptionFFIService _selectOptionService;
|
||||
final SelectOptionBackendService _selectOptionSvc;
|
||||
void Function()? _onCellChangedFn;
|
||||
ChecklistCellBloc({
|
||||
required this.cellController,
|
||||
}) : _selectOptionService =
|
||||
SelectOptionFFIService(cellId: cellController.cellId),
|
||||
}) : _selectOptionSvc =
|
||||
SelectOptionBackendService(cellId: cellController.cellId),
|
||||
super(ChecklistCellState.initial(cellController)) {
|
||||
on<ChecklistCellEvent>(
|
||||
(event, emit) async {
|
||||
@ -60,7 +60,7 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
}
|
||||
|
||||
void _loadOptions() {
|
||||
_selectOptionService.getOptionContext().then((result) {
|
||||
_selectOptionSvc.getCellData().then((result) {
|
||||
if (isClosed) return;
|
||||
|
||||
return result.fold(
|
||||
|
@ -1,25 +1,24 @@
|
||||
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:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'select_option_service.dart';
|
||||
|
||||
part 'checklist_cell_editor_bloc.freezed.dart';
|
||||
|
||||
class ChecklistCellEditorBloc
|
||||
extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> {
|
||||
final SelectOptionFFIService _selectOptionService;
|
||||
final SelectOptionBackendService _selectOptionService;
|
||||
final ChecklistCellController cellController;
|
||||
|
||||
ChecklistCellEditorBloc({
|
||||
required this.cellController,
|
||||
}) : _selectOptionService =
|
||||
SelectOptionFFIService(cellId: cellController.cellId),
|
||||
SelectOptionBackendService(cellId: cellController.cellId),
|
||||
super(ChecklistCellEditorState.initial(cellController)) {
|
||||
on<ChecklistCellEditorEvent>(
|
||||
(event, emit) async {
|
||||
@ -87,7 +86,7 @@ class ChecklistCellEditorBloc
|
||||
}
|
||||
|
||||
void _loadOptions() {
|
||||
_selectOptionService.getOptionContext().then((result) {
|
||||
_selectOptionService.getCellData().then((result) {
|
||||
if (isClosed) return;
|
||||
|
||||
return result.fold(
|
||||
|
@ -1,4 +1,5 @@
|
||||
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/field/field_service.dart';
|
||||
import 'package:easy_localization/easy_localization.dart'
|
||||
|
@ -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_backend/protobuf/flowy-database/date_type_option_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -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:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@ -49,22 +49,13 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn =
|
||||
cellController.startListening(onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
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;
|
||||
});
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(NumberCellEvent.didReceiveCellUpdate(cellContent));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
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:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
@ -1,5 +1,5 @@
|
||||
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:appflowy_backend/log.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
|
||||
extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
|
||||
final SelectOptionFFIService _selectOptionService;
|
||||
final SelectOptionBackendService _selectOptionService;
|
||||
final SelectOptionCellController cellController;
|
||||
|
||||
SelectOptionCellEditorBloc({
|
||||
required this.cellController,
|
||||
}) : _selectOptionService =
|
||||
SelectOptionFFIService(cellId: cellController.cellId),
|
||||
SelectOptionBackendService(cellId: cellController.cellId),
|
||||
super(SelectOptionEditorState.initial(cellController)) {
|
||||
on<SelectOptionEditorEvent>(
|
||||
(event, emit) async {
|
||||
@ -159,7 +159,7 @@ class SelectOptionCellEditorBloc
|
||||
}
|
||||
|
||||
Future<void> _loadOptions() async {
|
||||
final result = await _selectOptionService.getOptionContext();
|
||||
final result = await _selectOptionService.getCellData();
|
||||
if (isClosed) {
|
||||
Log.warn("Unexpected closing the bloc");
|
||||
return;
|
||||
|
@ -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/select_type_option.pb.dart';
|
||||
|
||||
class SelectOptionFFIService {
|
||||
class SelectOptionBackendService {
|
||||
final CellIdentifier cellId;
|
||||
SelectOptionFFIService({required this.cellId});
|
||||
SelectOptionBackendService({required this.cellId});
|
||||
|
||||
String get viewId => cellId.viewId;
|
||||
String get fieldId => cellId.fieldInfo.id;
|
||||
@ -60,7 +60,7 @@ class SelectOptionFFIService {
|
||||
return DatabaseEventUpdateSelectOption(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<SelectOptionCellDataPB, FlowyError>> getOptionContext() {
|
||||
Future<Either<SelectOptionCellDataPB, FlowyError>> getCellData() {
|
||||
final payload = CellIdPB.create()
|
||||
..viewId = viewId
|
||||
..fieldId = fieldId
|
||||
|
@ -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:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
@ -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:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
@ -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:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
@ -13,14 +13,11 @@ class ChecklistFilterEditorBloc
|
||||
extends Bloc<ChecklistFilterEditorEvent, ChecklistFilterEditorState> {
|
||||
final FilterInfo filterInfo;
|
||||
final FilterBackendService _filterBackendSvc;
|
||||
// final SelectOptionFFIService _selectOptionService;
|
||||
final FilterListener _listener;
|
||||
|
||||
ChecklistFilterEditorBloc({
|
||||
required this.filterInfo,
|
||||
}) : _filterBackendSvc = FilterBackendService(viewId: filterInfo.viewId),
|
||||
// _selectOptionService =
|
||||
// SelectOptionFFIService(cellId: cellController.cellId)
|
||||
_listener = FilterListener(
|
||||
viewId: filterInfo.viewId,
|
||||
filterId: filterInfo.filter.id,
|
||||
|
@ -15,10 +15,9 @@ import 'dart:collection';
|
||||
part 'grid_bloc.freezed.dart';
|
||||
|
||||
class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
final DatabaseController gridController;
|
||||
void Function()? _createRowOperation;
|
||||
final DatabaseController databaseController;
|
||||
|
||||
GridBloc({required ViewPB view, required this.gridController})
|
||||
GridBloc({required ViewPB view, required this.databaseController})
|
||||
: super(GridState.initial(view.id)) {
|
||||
on<GridEvent>(
|
||||
(event, emit) async {
|
||||
@ -28,12 +27,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
await _openGrid(emit);
|
||||
},
|
||||
createRow: () {
|
||||
state.loadingState.when(
|
||||
loading: () {
|
||||
_createRowOperation = () => gridController.createRow();
|
||||
},
|
||||
finish: (_) => gridController.createRow(),
|
||||
);
|
||||
databaseController.createRow();
|
||||
},
|
||||
deleteRow: (rowInfo) async {
|
||||
final rowService = RowBackendService(
|
||||
@ -63,16 +57,16 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await gridController.dispose();
|
||||
await databaseController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
RowCache? getRowCache(String blockId, String rowId) {
|
||||
return gridController.rowCache;
|
||||
return databaseController.rowCache;
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
gridController.addListener(
|
||||
databaseController.addListener(
|
||||
onGridChanged: (grid) {
|
||||
if (!isClosed) {
|
||||
add(GridEvent.didReceiveGridUpdate(grid));
|
||||
@ -92,13 +86,9 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
}
|
||||
|
||||
Future<void> _openGrid(Emitter<GridState> emit) async {
|
||||
final result = await gridController.openGrid();
|
||||
final result = await databaseController.openGrid();
|
||||
result.fold(
|
||||
(grid) {
|
||||
if (_createRowOperation != null) {
|
||||
_createRowOperation?.call();
|
||||
_createRowOperation = null;
|
||||
}
|
||||
emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
);
|
||||
|
@ -35,11 +35,11 @@ class GridPage extends StatefulWidget {
|
||||
required this.view,
|
||||
this.onDeleted,
|
||||
Key? key,
|
||||
}) : gridController = DatabaseController(view: view),
|
||||
}) : databaseController = DatabaseController(view: view),
|
||||
super(key: key);
|
||||
|
||||
final ViewPB view;
|
||||
final DatabaseController gridController;
|
||||
final DatabaseController databaseController;
|
||||
final VoidCallback? onDeleted;
|
||||
|
||||
@override
|
||||
@ -54,19 +54,19 @@ class _GridPageState extends State<GridPage> {
|
||||
BlocProvider<GridBloc>(
|
||||
create: (context) => GridBloc(
|
||||
view: widget.view,
|
||||
gridController: widget.gridController,
|
||||
databaseController: widget.databaseController,
|
||||
)..add(const GridEvent.initial()),
|
||||
),
|
||||
BlocProvider<GridFilterMenuBloc>(
|
||||
create: (context) => GridFilterMenuBloc(
|
||||
viewId: widget.view.id,
|
||||
fieldController: widget.gridController.fieldController,
|
||||
fieldController: widget.databaseController.fieldController,
|
||||
)..add(const GridFilterMenuEvent.initial()),
|
||||
),
|
||||
BlocProvider<SortMenuBloc>(
|
||||
create: (context) => SortMenuBloc(
|
||||
viewId: widget.view.id,
|
||||
fieldController: widget.gridController.fieldController,
|
||||
fieldController: widget.databaseController.fieldController,
|
||||
)..add(const SortMenuEvent.initial()),
|
||||
),
|
||||
BlocProvider<DatabaseSettingBloc>(
|
||||
@ -190,7 +190,7 @@ class _FlowyGridState extends State<FlowyGrid> {
|
||||
|
||||
Widget _gridHeader(BuildContext context, String viewId) {
|
||||
final fieldController =
|
||||
context.read<GridBloc>().gridController.fieldController;
|
||||
context.read<GridBloc>().databaseController.fieldController;
|
||||
return GridHeaderSliverAdaptor(
|
||||
viewId: viewId,
|
||||
fieldController: fieldController,
|
||||
@ -274,10 +274,9 @@ class _GridRowsState extends State<_GridRows> {
|
||||
if (rowCache == null) return const SizedBox();
|
||||
|
||||
final fieldController =
|
||||
context.read<GridBloc>().gridController.fieldController;
|
||||
context.read<GridBloc>().databaseController.fieldController;
|
||||
final dataController = RowDataController(
|
||||
rowInfo: rowInfo,
|
||||
fieldController: fieldController,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
@ -286,7 +285,7 @@ class _GridRowsState extends State<_GridRows> {
|
||||
child: GridRowWidget(
|
||||
rowInfo: rowInfo,
|
||||
dataController: dataController,
|
||||
cellBuilder: GridCellBuilder(delegate: dataController),
|
||||
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
|
||||
openDetailPage: (context, cellBuilder) {
|
||||
_openRowDetailPage(
|
||||
context,
|
||||
@ -310,7 +309,6 @@ class _GridRowsState extends State<_GridRows> {
|
||||
) {
|
||||
final dataController = RowDataController(
|
||||
rowInfo: rowInfo,
|
||||
fieldController: fieldController,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
|
@ -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:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -13,21 +14,16 @@ import 'select_option_cell/select_option_cell.dart';
|
||||
import 'text_cell.dart';
|
||||
import 'url_cell/url_cell.dart';
|
||||
|
||||
abstract class GridCellBuilderDelegate extends CellControllerBuilderDelegate {
|
||||
CellCache get cellCache;
|
||||
}
|
||||
|
||||
class GridCellBuilder {
|
||||
final GridCellBuilderDelegate delegate;
|
||||
final CellCache cellCache;
|
||||
GridCellBuilder({
|
||||
required this.delegate,
|
||||
required this.cellCache,
|
||||
});
|
||||
|
||||
GridCellWidget build(CellIdentifier cellId, {GridCellStyle? style}) {
|
||||
final cellControllerBuilder = CellControllerBuilder(
|
||||
cellId: cellId,
|
||||
cellCache: delegate.cellCache,
|
||||
delegate: delegate,
|
||||
cellCache: cellCache,
|
||||
);
|
||||
|
||||
final key = cellId.key();
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../../application/cell/cell_service.dart';
|
||||
import '../../../application/cell/checkbox_cell_bloc.dart';
|
||||
import '../../layout/sizes.dart';
|
||||
import 'cell_builder.dart';
|
||||
|
@ -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/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
|
@ -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:flowy_infra/image.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_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../../application/cell/cell_service.dart';
|
||||
import '../../../../application/cell/checklist_cell_editor_bloc.dart';
|
||||
import '../../../layout/sizes.dart';
|
||||
import '../../header/type_option/select_option_editor.dart';
|
||||
|
@ -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:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/cell/date_cal_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
@ -20,7 +21,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
import 'package:textstyle_extensions/textstyle_extensions.dart';
|
||||
import '../../../../../application/cell/cell_service.dart';
|
||||
import '../../../layout/sizes.dart';
|
||||
import '../../common/type_option_separator.dart';
|
||||
import '../../header/type_option/date.dart';
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../application/cell/cell_service.dart';
|
||||
import '../../../application/cell/number_cell_bloc.dart';
|
||||
import '../../layout/sizes.dart';
|
||||
import 'cell_builder.dart';
|
||||
|
@ -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_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:collection';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/cell/select_option_editor_bloc.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
@ -15,7 +16,6 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:textfield_tags/textfield_tags.dart';
|
||||
|
||||
import '../../../../../application/cell/cell_service.dart';
|
||||
import '../../../layout/sizes.dart';
|
||||
import '../../common/type_option_separator.dart';
|
||||
import '../../header/type_option/select_option_editor.dart';
|
||||
|
@ -1,5 +1,5 @@
|
||||
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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -1,9 +1,8 @@
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../../application/cell/cell_service.dart';
|
||||
import '../../../../application/cell/url_cell_editor_bloc.dart';
|
||||
|
||||
class URLCellEditor extends StatefulWidget {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'dart:async';
|
||||
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_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
@ -23,11 +23,11 @@ typedef SwitchToFieldCallback = Future<Either<TypeOptionPB, FlowyError>>
|
||||
);
|
||||
|
||||
class FieldTypeOptionEditor extends StatelessWidget {
|
||||
final TypeOptionDataController _dataController;
|
||||
final TypeOptionController _dataController;
|
||||
final PopoverMutex popoverMutex;
|
||||
|
||||
const FieldTypeOptionEditor({
|
||||
required TypeOptionDataController dataController,
|
||||
required TypeOptionController dataController,
|
||||
required this.popoverMutex,
|
||||
Key? key,
|
||||
}) : _dataController = dataController,
|
||||
|
@ -48,7 +48,7 @@ abstract class TypeOptionWidgetBuilder {
|
||||
|
||||
Widget? makeTypeOptionWidget({
|
||||
required BuildContext context,
|
||||
required TypeOptionDataController dataController,
|
||||
required TypeOptionController dataController,
|
||||
required PopoverMutex popoverMutex,
|
||||
}) {
|
||||
final builder = makeTypeOptionWidgetBuilder(
|
||||
@ -59,7 +59,7 @@ Widget? makeTypeOptionWidget({
|
||||
}
|
||||
|
||||
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
|
||||
required TypeOptionDataController dataController,
|
||||
required TypeOptionController dataController,
|
||||
required PopoverMutex popoverMutex,
|
||||
}) {
|
||||
final viewId = dataController.viewId;
|
||||
@ -144,7 +144,7 @@ TypeOptionContext<T> makeTypeOptionContext<T extends GeneratedMessage>({
|
||||
required FieldInfo fieldInfo,
|
||||
}) {
|
||||
final loader = FieldTypeOptionLoader(viewId: viewId, field: fieldInfo.field);
|
||||
final dataController = TypeOptionDataController(
|
||||
final dataController = TypeOptionController(
|
||||
viewId: viewId,
|
||||
loader: loader,
|
||||
fieldInfo: fieldInfo,
|
||||
@ -178,7 +178,7 @@ TypeOptionContext<T> makeSelectTypeOptionContext<T extends GeneratedMessage>({
|
||||
viewId: viewId,
|
||||
field: fieldPB,
|
||||
);
|
||||
final dataController = TypeOptionDataController(
|
||||
final dataController = TypeOptionController(
|
||||
viewId: viewId,
|
||||
loader: loader,
|
||||
);
|
||||
@ -194,7 +194,7 @@ TypeOptionContext<T>
|
||||
makeTypeOptionContextWithDataController<T extends GeneratedMessage>({
|
||||
required String viewId,
|
||||
required FieldType fieldType,
|
||||
required TypeOptionDataController dataController,
|
||||
required TypeOptionController dataController,
|
||||
}) {
|
||||
switch (fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
|
@ -35,7 +35,7 @@ class _SettingButtonState extends State<SettingButton> {
|
||||
return BlocSelector<GridBloc, GridState, GridSettingContext>(
|
||||
selector: (state) {
|
||||
final fieldController =
|
||||
context.read<GridBloc>().gridController.fieldController;
|
||||
context.read<GridBloc>().databaseController.fieldController;
|
||||
return GridSettingContext(
|
||||
viewId: state.viewId,
|
||||
fieldController: fieldController,
|
||||
|
@ -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_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_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_editor/appflowy_editor.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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -126,9 +128,11 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final autoFocusParamters = _autoFocusParamters();
|
||||
final editor = AppFlowyEditor(
|
||||
editorState: editorState,
|
||||
autoFocus: editorState.document.isEmpty,
|
||||
autoFocus: autoFocusParamters.value1,
|
||||
focusedSelection: autoFocusParamters.value2,
|
||||
customBuilders: {
|
||||
// Divider
|
||||
kDividerType: DividerWidgetBuilder(),
|
||||
@ -144,6 +148,8 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
|
||||
kCalloutType: CalloutNodeWidgetBuilder(),
|
||||
// Auto Generator,
|
||||
kAutoCompletionInputType: AutoCompletionInputBuilder(),
|
||||
// Cover
|
||||
kCoverType: CoverNodeWidgetBuilder(),
|
||||
// Smart Edit,
|
||||
kSmartEditType: SmartEditInputBuilder(),
|
||||
},
|
||||
@ -174,10 +180,12 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
|
||||
// enable open ai features if needed.
|
||||
if (openAIKey != null && openAIKey!.isNotEmpty) ...[
|
||||
autoGeneratorMenuItem,
|
||||
]
|
||||
],
|
||||
],
|
||||
toolbarItems: [
|
||||
smartEditItem,
|
||||
if (openAIKey != null && openAIKey!.isNotEmpty) ...[
|
||||
smartEditItem,
|
||||
]
|
||||
],
|
||||
themeData: theme.copyWith(extensions: [
|
||||
...theme.extensions.values,
|
||||
@ -227,4 +235,18 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ EditorStyle customEditorTheme(BuildContext context) {
|
||||
? EditorStyle.dark
|
||||
: EditorStyle.light;
|
||||
editorStyle = editorStyle.copyWith(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 100, vertical: 28),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 100, vertical: 0),
|
||||
textStyle: editorStyle.textStyle?.copyWith(
|
||||
fontFamily: 'poppins',
|
||||
fontSize: documentStyle.fontSize,
|
||||
|
@ -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)}';
|
||||
}
|
||||
}
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ abstract class OpenAIRepository {
|
||||
Future<Either<OpenAIError, TextCompletionResponse>> getCompletions({
|
||||
required String prompt,
|
||||
String? suffix,
|
||||
int maxTokens = 50,
|
||||
int maxTokens = 500,
|
||||
double temperature = .3,
|
||||
});
|
||||
|
||||
@ -72,7 +72,7 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
||||
Future<Either<OpenAIError, TextCompletionResponse>> getCompletions({
|
||||
required String prompt,
|
||||
String? suffix,
|
||||
int maxTokens = 50,
|
||||
int maxTokens = 500,
|
||||
double temperature = 0.3,
|
||||
}) async {
|
||||
final parameters = {
|
||||
@ -102,12 +102,14 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
||||
required String input,
|
||||
required String instruction,
|
||||
double temperature = 0.3,
|
||||
int n = 1,
|
||||
}) async {
|
||||
final parameters = {
|
||||
'model': 'text-davinci-edit-001',
|
||||
'input': input,
|
||||
'instruction': instruction,
|
||||
'temperature': temperature,
|
||||
'n': n,
|
||||
};
|
||||
|
||||
final response = await client.post(
|
||||
|
@ -10,9 +10,9 @@ enum SmartEditAction {
|
||||
String get toInstruction {
|
||||
switch (this) {
|
||||
case SmartEditAction.summarize:
|
||||
return 'Summarize';
|
||||
return 'Make it shorter';
|
||||
case SmartEditAction.fixSpelling:
|
||||
return 'Fix the spelling mistakes';
|
||||
return 'Fix all the spelling mistakes';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -254,6 +254,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
||||
final edits = await openAIRepository.getEdits(
|
||||
input: input,
|
||||
instruction: instruction,
|
||||
n: input.split('\n').length,
|
||||
);
|
||||
return edits.fold((error) async {
|
||||
return dartz.Left(
|
||||
|
@ -42,11 +42,13 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
||||
.toList(),
|
||||
buildChild: (controller) {
|
||||
return FlowyIconButton(
|
||||
hoverColor: Colors.transparent,
|
||||
tooltipText: 'Smart Edit',
|
||||
preferBelow: false,
|
||||
icon: const Icon(
|
||||
Icons.edit,
|
||||
size: 14,
|
||||
Icons.lightbulb_outline,
|
||||
size: 13,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
controller.show();
|
||||
@ -55,39 +57,44 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
||||
},
|
||||
onSelected: (action, controller) {
|
||||
controller.close();
|
||||
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: action.inner.toInstruction,
|
||||
kSmartEditInputType: input,
|
||||
},
|
||||
),
|
||||
);
|
||||
widget.editorState.apply(
|
||||
transaction,
|
||||
options: const ApplyOptions(
|
||||
recordUndo: false,
|
||||
recordRedo: false,
|
||||
),
|
||||
withUpdateCursor: false,
|
||||
);
|
||||
_insertSmartEditNode(action);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/core/network_monitor.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_action_sheet_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
|
@ -1,9 +1,10 @@
|
||||
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/startup.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_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:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
@ -60,7 +61,12 @@ class AddButton extends StatelessWidget {
|
||||
},
|
||||
onSelected: (action, controller) {
|
||||
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) {
|
||||
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 {
|
||||
@ -87,6 +99,8 @@ class AddButtonActionWrapper extends ActionCell {
|
||||
|
||||
@override
|
||||
String get name => pluginBuilder.menuName;
|
||||
|
||||
PluginType get pluginType => pluginBuilder.pluginType;
|
||||
}
|
||||
|
||||
class ImportActionWrapper extends ActionCell {
|
||||
|
@ -1,4 +1,3 @@
|
||||
#include "include/appflowy_backend/appflowy_flutter_backend_plugin.h"
|
||||
|
||||
// This must be included before many other Windows headers.
|
||||
#include <windows.h>
|
||||
@ -13,70 +12,85 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include "include/appflowy_backend/app_flowy_backend_plugin.h"
|
||||
|
||||
namespace {
|
||||
namespace
|
||||
{
|
||||
|
||||
class AppFlowyBackendPlugin : public flutter::Plugin {
|
||||
public:
|
||||
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
|
||||
class AppFlowyBackendPlugin : public flutter::Plugin
|
||||
{
|
||||
public:
|
||||
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
|
||||
|
||||
AppFlowyBackendPlugin();
|
||||
AppFlowyBackendPlugin();
|
||||
|
||||
virtual ~AppFlowyBackendPlugin();
|
||||
virtual ~AppFlowyBackendPlugin();
|
||||
|
||||
private:
|
||||
// Called when a method is called on this plugin's channel from Dart.
|
||||
void HandleMethodCall(
|
||||
const flutter::MethodCall<flutter::EncodableValue> &method_call,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
|
||||
};
|
||||
private:
|
||||
// Called when a method is called on this plugin's channel from Dart.
|
||||
void HandleMethodCall(
|
||||
const flutter::MethodCall<flutter::EncodableValue> &method_call,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
|
||||
};
|
||||
|
||||
// static
|
||||
void AppFlowyBackendPlugin::RegisterWithRegistrar(
|
||||
flutter::PluginRegistrarWindows *registrar) {
|
||||
auto channel =
|
||||
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
|
||||
registrar->messenger(), "appflowy_backend",
|
||||
&flutter::StandardMethodCodec::GetInstance());
|
||||
// static
|
||||
void AppFlowyBackendPlugin::RegisterWithRegistrar(
|
||||
flutter::PluginRegistrarWindows *registrar)
|
||||
{
|
||||
auto channel =
|
||||
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
|
||||
registrar->messenger(), "appflowy_backend",
|
||||
&flutter::StandardMethodCodec::GetInstance());
|
||||
|
||||
auto plugin = std::make_unique<AppFlowyBackendPlugin>();
|
||||
auto plugin = std::make_unique<AppFlowyBackendPlugin>();
|
||||
|
||||
channel->SetMethodCallHandler(
|
||||
[plugin_pointer = plugin.get()](const auto &call, auto result) {
|
||||
plugin_pointer->HandleMethodCall(call, std::move(result));
|
||||
});
|
||||
channel->SetMethodCallHandler(
|
||||
[plugin_pointer = plugin.get()](const auto &call, auto result)
|
||||
{
|
||||
plugin_pointer->HandleMethodCall(call, std::move(result));
|
||||
});
|
||||
|
||||
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();
|
||||
registrar->AddPlugin(std::move(plugin));
|
||||
}
|
||||
}
|
||||
|
||||
} // 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(
|
||||
FlutterDesktopPluginRegistrarRef registrar) {
|
||||
FlutterDesktopPluginRegistrarRef registrar)
|
||||
{
|
||||
AppFlowyBackendPlugin::RegisterWithRegistrar(
|
||||
flutter::PluginRegistrarManager::GetInstance()
|
||||
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
|
||||
}
|
||||
}
|
@ -5,8 +5,9 @@
|
||||
#include "appflowy_flutter_backend_plugin.h"
|
||||
|
||||
void AppFlowyBackendPluginCApiRegisterWithRegistrar(
|
||||
FlutterDesktopPluginRegistrarRef registrar) {
|
||||
appflowy_backend::AppFlowyBackendPlugin::RegisterWithRegistrar(
|
||||
flutter::PluginRegistrarManager::GetInstance()
|
||||
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
|
||||
FlutterDesktopPluginRegistrarRef registrar)
|
||||
{
|
||||
appflowy_backend::AppFlowyBackendPlugin::RegisterWithRegistrar(
|
||||
flutter::PluginRegistrarManager::GetInstance()
|
||||
->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
|
||||
}
|
||||
|
@ -59,12 +59,18 @@ extension CommandExtension on EditorState {
|
||||
List<String> res = [];
|
||||
if (!selection.isCollapsed) {
|
||||
for (var i = 0; i < textNodes.length; i++) {
|
||||
final plainText = textNodes[i].toPlainText();
|
||||
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) {
|
||||
res.add(textNodes[i].toPlainText().substring(0, selection.endIndex));
|
||||
res.add(plainText.substring(0, selection.endIndex));
|
||||
} else {
|
||||
res.add(textNodes[i].toPlainText());
|
||||
res.add(plainText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ class AppFlowyEditor extends StatefulWidget {
|
||||
this.toolbarItems = const [],
|
||||
this.editable = true,
|
||||
this.autoFocus = false,
|
||||
this.focusedSelection,
|
||||
this.customActionMenuBuilder,
|
||||
ThemeData? themeData,
|
||||
}) : 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.
|
||||
final bool autoFocus;
|
||||
final Selection? focusedSelection;
|
||||
|
||||
final Positioned Function(BuildContext context, List<ActionMenuItem> items)?
|
||||
customActionMenuBuilder;
|
||||
@ -89,7 +91,8 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
if (widget.editable && widget.autoFocus) {
|
||||
editorState.service.selectionService.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
widget.focusedSelection ??
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -12,3 +12,5 @@ export 'src/divider/divider_shortcut_event.dart';
|
||||
export 'src/emoji_picker/emoji_menu_item.dart';
|
||||
// Math Equation
|
||||
export 'src/math_ equation/math_equation_node_widget.dart';
|
||||
|
||||
export 'src/extensions/theme_extension.dart';
|
||||
|
@ -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/board/application/board_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/cell/select_option_editor_bloc.dart';
|
||||
|
@ -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_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||
@ -105,11 +106,9 @@ class BoardTestContext {
|
||||
) async {
|
||||
final RowInfo rowInfo = rowInfos.last;
|
||||
final rowCache = _boardDataController.rowCache;
|
||||
final fieldController = _boardDataController.fieldController;
|
||||
|
||||
final rowDataController = RowDataController(
|
||||
rowInfo: rowInfo,
|
||||
fieldController: fieldController,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
@ -122,7 +121,6 @@ class BoardTestContext {
|
||||
return CellControllerBuilder(
|
||||
cellId: rowBloc.state.cellByFieldId[fieldId]!,
|
||||
cellCache: rowCache.cellCache,
|
||||
delegate: rowDataController,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ void main() {
|
||||
);
|
||||
await gridResponseFuture();
|
||||
|
||||
assert(context.fieldController.filterInfos.isEmpty);
|
||||
expect(context.fieldController.filterInfos.length, 0);
|
||||
});
|
||||
|
||||
test('filter rows with condition: text is empty', () async {
|
||||
@ -53,7 +53,7 @@ void main() {
|
||||
final gridController = DatabaseController(view: context.gridView);
|
||||
final gridBloc = GridBloc(
|
||||
view: context.gridView,
|
||||
gridController: gridController,
|
||||
databaseController: gridController,
|
||||
)..add(const GridEvent.initial());
|
||||
await gridResponseFuture();
|
||||
|
||||
@ -64,7 +64,7 @@ void main() {
|
||||
content: "");
|
||||
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)',
|
||||
@ -74,7 +74,7 @@ void main() {
|
||||
final gridController = DatabaseController(view: context.gridView);
|
||||
final gridBloc = GridBloc(
|
||||
view: context.gridView,
|
||||
gridController: gridController,
|
||||
databaseController: gridController,
|
||||
)..add(const GridEvent.initial());
|
||||
await gridResponseFuture();
|
||||
|
||||
@ -115,7 +115,7 @@ void main() {
|
||||
final gridController = DatabaseController(view: context.gridView);
|
||||
final gridBloc = GridBloc(
|
||||
view: context.gridView,
|
||||
gridController: gridController,
|
||||
databaseController: gridController,
|
||||
)..add(const GridEvent.initial());
|
||||
|
||||
await gridResponseFuture();
|
||||
@ -134,7 +134,7 @@ void main() {
|
||||
final gridController = DatabaseController(view: context.gridView);
|
||||
final gridBloc = GridBloc(
|
||||
view: context.gridView,
|
||||
gridController: gridController,
|
||||
databaseController: gridController,
|
||||
)..add(const GridEvent.initial());
|
||||
|
||||
await gridResponseFuture();
|
||||
|
@ -20,7 +20,7 @@ void main() {
|
||||
"create a row",
|
||||
build: () => GridBloc(
|
||||
view: context.gridView,
|
||||
gridController: DatabaseController(view: context.gridView))
|
||||
databaseController: DatabaseController(view: context.gridView))
|
||||
..add(const GridEvent.initial()),
|
||||
act: (bloc) => bloc.add(const GridEvent.createRow()),
|
||||
wait: const Duration(milliseconds: 300),
|
||||
@ -33,7 +33,7 @@ void main() {
|
||||
"delete the last row",
|
||||
build: () => GridBloc(
|
||||
view: context.gridView,
|
||||
gridController: DatabaseController(view: context.gridView))
|
||||
databaseController: DatabaseController(view: context.gridView))
|
||||
..add(const GridEvent.initial()),
|
||||
act: (bloc) async {
|
||||
await gridResponseFuture();
|
||||
|
@ -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_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||
@ -66,11 +67,9 @@ class GridTestContext {
|
||||
) async {
|
||||
final RowInfo rowInfo = rowInfos[rowIndex];
|
||||
final rowCache = gridController.rowCache;
|
||||
final fieldController = gridController.fieldController;
|
||||
|
||||
final rowDataController = RowDataController(
|
||||
rowInfo: rowInfo,
|
||||
fieldController: fieldController,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
@ -83,7 +82,6 @@ class GridTestContext {
|
||||
return CellControllerBuilder(
|
||||
cellId: rowBloc.state.cellByFieldId[fieldId]!,
|
||||
cellCache: rowCache.cellCache,
|
||||
delegate: rowDataController,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Routes, Route, BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import { TestColors } from './components/TestColors/TestColors';
|
||||
import { Welcome } from './views/Welcome';
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from './stores/store';
|
||||
import { DocumentPage } from './views/DocumentPage';
|
||||
@ -29,7 +28,6 @@ const App = () => {
|
||||
<Route path={'/page/document/:id'} element={<DocumentPage />} />
|
||||
<Route path={'/page/board/:id'} element={<BoardPage />} />
|
||||
<Route path={'/page/grid/:id'} element={<GridPage />} />
|
||||
<Route path={'/'} element={<Welcome />} />
|
||||
</Route>
|
||||
<Route path={'/auth/login'} element={<LoginPage />}></Route>
|
||||
<Route path={'/auth/getStarted'} element={<GetStarted />}></Route>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import TestApiButton from './TestApiButton';
|
||||
import {
|
||||
TestCreateGrid,
|
||||
TestCreateNewField,
|
||||
@ -18,7 +17,7 @@ export const TestAPI = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ul className='m-6, space-y-2'>
|
||||
<TestApiButton></TestApiButton>
|
||||
{/*<TestApiButton></TestApiButton>*/}
|
||||
<TestCreateGrid></TestCreateGrid>
|
||||
<TestCreateRow></TestCreateRow>
|
||||
<TestDeleteRow></TestDeleteRow>
|
||||
|
@ -126,8 +126,10 @@ export const TestCreateSelectOptionInCell = () => {
|
||||
);
|
||||
await cellController.subscribeChanged({
|
||||
onCellChanged: (value) => {
|
||||
const option: SelectOptionCellDataPB = value.unwrap();
|
||||
console.log(option);
|
||||
if (value.some) {
|
||||
const option: SelectOptionCellDataPB = value.unwrap();
|
||||
console.log(option);
|
||||
}
|
||||
},
|
||||
});
|
||||
const backendSvc = new SelectOptionCellBackendService(cellController.cellIdentifier);
|
||||
|
@ -1,16 +1,56 @@
|
||||
import { Navigate, Outlet, useLocation } from 'react-router-dom';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { useAuth } from './auth.hooks';
|
||||
import { Screen } from '../layout/Screen';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { GetStarted } from './GetStarted/GetStarted';
|
||||
import { AppflowyLogo } from '../_shared/svg/AppflowyLogo';
|
||||
|
||||
|
||||
export const ProtectedRoutes = () => {
|
||||
const location = useLocation();
|
||||
const { currentUser } = useAuth();
|
||||
const { currentUser, checkUser } = useAuth();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
return currentUser.isAuthenticated ? (
|
||||
<Screen>
|
||||
<Outlet />
|
||||
</Screen>
|
||||
) : (
|
||||
<Navigate to='/auth/getStarted' replace state={{ from: location }} />
|
||||
useEffect(() => {
|
||||
void checkUser().then(async (result) => {
|
||||
await new Promise(() =>
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 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>;
|
||||
}
|
||||
};
|
||||
|
@ -1,20 +1,46 @@
|
||||
import { currentUserActions } from '../../stores/reducers/current-user/slice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/store';
|
||||
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 { WorkspaceSettingPB } from '../../../services/backend/models/flowy-folder/workspace';
|
||||
import { Log } from '../../utils/log';
|
||||
|
||||
export const useAuth = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const currentUser = useAppSelector((state) => state.currentUser);
|
||||
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> {
|
||||
const authResult = await authBackendService.signUp({ email, password, name });
|
||||
|
||||
if (authResult.ok) {
|
||||
const { id, token } = authResult.val;
|
||||
const userProfile = authResult.val;
|
||||
// Get the workspace setting after user registered. The workspace setting
|
||||
// contains the latest visiting view and the current workspace data.
|
||||
const openWorkspaceResult = await _openWorkspace();
|
||||
@ -22,10 +48,10 @@ export const useAuth = () => {
|
||||
const workspaceSetting: WorkspaceSettingPB = openWorkspaceResult.val;
|
||||
dispatch(
|
||||
currentUserActions.updateUser({
|
||||
id: id,
|
||||
token: token,
|
||||
email,
|
||||
displayName: name,
|
||||
id: userProfile.id,
|
||||
token: userProfile.token,
|
||||
email: userProfile.email,
|
||||
displayName: userProfile.name,
|
||||
isAuthenticated: true,
|
||||
workspaceSetting: workspaceSetting,
|
||||
})
|
||||
@ -33,7 +59,7 @@ export const useAuth = () => {
|
||||
}
|
||||
return authResult.val;
|
||||
} else {
|
||||
console.error(authResult.val.msg);
|
||||
Log.error(authResult.val.msg);
|
||||
throw new Error(authResult.val.msg);
|
||||
}
|
||||
}
|
||||
@ -53,7 +79,7 @@ export const useAuth = () => {
|
||||
);
|
||||
return result.val;
|
||||
} else {
|
||||
console.error(result.val.msg);
|
||||
Log.error(result.val.msg);
|
||||
throw new Error(result.val.msg);
|
||||
}
|
||||
}
|
||||
@ -67,5 +93,5 @@ export const useAuth = () => {
|
||||
return FolderEventReadCurrentWorkspace();
|
||||
}
|
||||
|
||||
return { currentUser, register, login, logout };
|
||||
return { currentUser, checkUser, register, login, logout };
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { AppLogo } from '../AppLogo';
|
||||
import { Workspace } from '../Workspace';
|
||||
import { WorkspaceUser } from '../WorkspaceUser';
|
||||
import { FolderItem } from './FolderItem';
|
||||
import { PluginsButton } from './PluginsButton';
|
||||
import { TrashButton } from './TrashButton';
|
||||
@ -52,7 +52,7 @@ export const NavigationFloatingPanel = ({
|
||||
<div className={'flex flex-col'}>
|
||||
<AppLogo iconToShow={'show'} onShowMenuClick={onFixNavigationClick}></AppLogo>
|
||||
|
||||
<Workspace></Workspace>
|
||||
<WorkspaceUser></WorkspaceUser>
|
||||
|
||||
<div className={'flex flex-col px-2'}>
|
||||
{folders.map((folder, index) => (
|
||||
|
@ -1,26 +1,22 @@
|
||||
import { Workspace } from '../Workspace';
|
||||
import { WorkspaceUser } from '../WorkspaceUser';
|
||||
import { AppLogo } from '../AppLogo';
|
||||
import { FolderItem } from './FolderItem';
|
||||
import { PluginsButton } from './PluginsButton';
|
||||
import { TrashButton } from './TrashButton';
|
||||
import { NewFolderButton } from './NewFolderButton';
|
||||
import { NavigationResizer } from './NavigationResizer';
|
||||
import { IFolder } from '../../../stores/reducers/folders/slice';
|
||||
import { IPage } from '../../../stores/reducers/pages/slice';
|
||||
|
||||
const MINIMUM_WIDTH = 200;
|
||||
const ANIMATION_DURATION = 300;
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
export const NavigationPanel = ({
|
||||
onHideMenuClick,
|
||||
menuHidden,
|
||||
onCollapseNavigationClick,
|
||||
width,
|
||||
folders,
|
||||
pages,
|
||||
onPageClick,
|
||||
}: {
|
||||
onHideMenuClick: () => void;
|
||||
menuHidden: boolean;
|
||||
onCollapseNavigationClick: () => void;
|
||||
width: number;
|
||||
folders: IFolder[];
|
||||
pages: IPage[];
|
||||
@ -28,41 +24,73 @@ export const NavigationPanel = ({
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`absolute inset-0 flex flex-col justify-between bg-surface-1 text-sm`}
|
||||
style={{
|
||||
transition: `left ${ANIMATION_DURATION}ms ease-out`,
|
||||
width: `${width}px`,
|
||||
left: `${menuHidden ? -width : 0}px`,
|
||||
}}
|
||||
>
|
||||
<div className={'flex flex-col justify-between bg-surface-1 text-sm'} style={{ width: `${width}px` }}>
|
||||
<div className={'flex flex-col'}>
|
||||
<AppLogo iconToShow={'hide'} onHideMenuClick={onHideMenuClick}></AppLogo>
|
||||
|
||||
<Workspace></Workspace>
|
||||
|
||||
<div className={'flex flex-col overflow-auto px-2'} style={{ height: 'calc(100vh - 280px)' }}>
|
||||
{folders.map((folder, index) => (
|
||||
<FolderItem
|
||||
key={index}
|
||||
folder={folder}
|
||||
pages={pages.filter((page) => page.folderId === folder.id)}
|
||||
onPageClick={onPageClick}
|
||||
></FolderItem>
|
||||
))}
|
||||
</div>
|
||||
<AppLogo iconToShow={'hide'} onHideMenuClick={onCollapseNavigationClick}></AppLogo>
|
||||
<WorkspaceUser></WorkspaceUser>
|
||||
<WorkspaceApps folders={folders} pages={pages} onPageClick={onPageClick} />
|
||||
</div>
|
||||
|
||||
<div className={'flex flex-col'}>
|
||||
<div className={'border-b border-shade-6 px-2 pb-4'}>
|
||||
<PluginsButton></PluginsButton>
|
||||
{/*<PluginsButton></PluginsButton>*/}
|
||||
|
||||
<DesignSpec></DesignSpec>
|
||||
<TestBackendButton></TestBackendButton>
|
||||
|
||||
{/*Trash Button*/}
|
||||
<TrashButton></TrashButton>
|
||||
</div>
|
||||
|
||||
{/*New Folder Button*/}
|
||||
<NewFolderButton></NewFolderButton>
|
||||
</div>
|
||||
</div>
|
||||
<NavigationResizer minWidth={MINIMUM_WIDTH}></NavigationResizer>
|
||||
<NavigationResizer></NavigationResizer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useAppSelector } from '../../stores/store';
|
||||
|
||||
export const Workspace = () => {
|
||||
export const WorkspaceUser = () => {
|
||||
const currentUser = useAppSelector((state) => state.currentUser);
|
||||
|
||||
return (
|
@ -13,7 +13,7 @@ type Callbacks<T> = { onCellChanged: (value: Option<T>) => void; onFieldChanged?
|
||||
|
||||
export class CellController<T, D> {
|
||||
private fieldBackendService: FieldBackendService;
|
||||
private cellDataNotifier: CellDataNotifier<Option<T>>;
|
||||
private cellDataNotifier: CellDataNotifier<T>;
|
||||
private cellObserver: CellObserver;
|
||||
private readonly cacheKey: CellCacheKey;
|
||||
private readonly fieldNotifier: DatabaseFieldObserver;
|
||||
@ -59,7 +59,7 @@ export class CellController<T, D> {
|
||||
this.subscribeCallbacks = callbacks;
|
||||
this.cellDataNotifier.observer.subscribe((cellData) => {
|
||||
if (cellData !== null) {
|
||||
callbacks.onCellChanged(cellData);
|
||||
callbacks.onCellChanged(Some(cellData));
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -95,8 +95,11 @@ export class CellController<T, D> {
|
||||
private _loadCellData = () => {
|
||||
return this.cellDataLoader.loadData().then((result) => {
|
||||
if (result.ok) {
|
||||
this.cellCache.insert(this.cacheKey, result.val);
|
||||
this.cellDataNotifier.cellData = Some(result.val);
|
||||
const cellData = result.val;
|
||||
if (cellData.some) {
|
||||
this.cellCache.insert(this.cacheKey, cellData.val);
|
||||
this.cellDataNotifier.cellData = cellData;
|
||||
}
|
||||
} else {
|
||||
this.cellCache.remove(this.cacheKey);
|
||||
this.cellDataNotifier.cellData = None;
|
||||
@ -110,12 +113,12 @@ export class CellController<T, D> {
|
||||
};
|
||||
}
|
||||
|
||||
class CellDataNotifier<T> extends ChangeNotifier<T | null> {
|
||||
class CellDataNotifier<T> extends ChangeNotifier<T> {
|
||||
_cellData: Option<T>;
|
||||
|
||||
constructor(cellData: T) {
|
||||
constructor(cellData: Option<T>) {
|
||||
super();
|
||||
this._cellData = Some(cellData);
|
||||
this._cellData = cellData;
|
||||
}
|
||||
|
||||
set cellData(data: Option<T>) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { nanoid } from '@reduxjs/toolkit';
|
||||
import {
|
||||
UserEventCheckUser,
|
||||
UserEventGetUserProfile,
|
||||
UserEventSignIn,
|
||||
UserEventSignOut,
|
||||
@ -29,6 +30,10 @@ export class UserBackendService {
|
||||
return UserEventGetUserProfile();
|
||||
};
|
||||
|
||||
static checkUser = () => {
|
||||
return UserEventCheckUser();
|
||||
};
|
||||
|
||||
updateUserProfile = (params: { name?: string; password?: string; email?: string; openAIKey?: string }) => {
|
||||
const payload = UpdateUserProfilePayloadPB.fromObject({ id: this.userId });
|
||||
|
||||
|
@ -12,17 +12,16 @@ export interface ICurrentUser {
|
||||
}
|
||||
|
||||
const initialState: ICurrentUser | null = {
|
||||
id: nanoid(8),
|
||||
displayName: 'Me 😃',
|
||||
email: `${nanoid(4)}@gmail.com`,
|
||||
token: nanoid(8),
|
||||
isAuthenticated: true,
|
||||
isAuthenticated: false,
|
||||
};
|
||||
|
||||
export const currentUserSlice = createSlice({
|
||||
name: 'currentUser',
|
||||
initialState: initialState,
|
||||
reducers: {
|
||||
checkUser: (state, action: PayloadAction<ICurrentUser>) => {
|
||||
return action.payload;
|
||||
},
|
||||
updateUser: (state, action: PayloadAction<ICurrentUser>) => {
|
||||
return action.payload;
|
||||
},
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -132,7 +132,7 @@ fn generate_ts_protobuf_files(
|
||||
};
|
||||
|
||||
if result.is_err() {
|
||||
panic!("Generate dart pb file failed with: {}, {:?}", path, result)
|
||||
panic!("Generate ts pb file failed with: {}, {:?}", path, result)
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -107,7 +107,7 @@ fn create_log_filter(level: String, with_crates: Vec<String>) -> String {
|
||||
|
||||
#[cfg(feature = "profiling")]
|
||||
filters.push(format!("runtime={}", level));
|
||||
filters.push(format!("tokio=trace,runtime=trace"));
|
||||
|
||||
filters.join(",")
|
||||
}
|
||||
|
||||
|
@ -534,8 +534,8 @@ pub(crate) async fn get_groups_handler(
|
||||
) -> DataResult<RepeatedGroupPB, FlowyError> {
|
||||
let params: DatabaseViewIdPB = data.into_inner();
|
||||
let editor = manager.get_database_editor(¶ms.value).await?;
|
||||
let group = editor.load_groups(¶ms.value).await?;
|
||||
data_result_ok(group)
|
||||
let groups = editor.load_groups(¶ms.value).await?;
|
||||
data_result_ok(groups)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
|
@ -203,28 +203,45 @@ impl DatabaseManager {
|
||||
database_id: &str,
|
||||
view_id: &str,
|
||||
) -> FlowyResult<Arc<DatabaseEditor>> {
|
||||
if let Some(database_editor) = self.editors_by_database_id.read().await.get(database_id) {
|
||||
let user_id = self.database_user.user_id()?;
|
||||
let (view_pad, view_rev_manager) =
|
||||
make_database_view_revision_pad(view_id, self.database_user.clone()).await?;
|
||||
|
||||
let view_editor = DatabaseViewEditor::from_pad(
|
||||
let user = self.database_user.clone();
|
||||
let create_view_editor = |database_editor: Arc<DatabaseEditor>| async move {
|
||||
let user_id = user.user_id()?;
|
||||
let (view_pad, view_rev_manager) = make_database_view_revision_pad(view_id, user).await?;
|
||||
return DatabaseViewEditor::from_pad(
|
||||
&user_id,
|
||||
database_editor.database_view_data.clone(),
|
||||
database_editor.cell_data_cache.clone(),
|
||||
view_rev_manager,
|
||||
view_pad,
|
||||
)
|
||||
.await?;
|
||||
database_editor.open_view_editor(view_editor).await;
|
||||
return Ok(database_editor.clone());
|
||||
}
|
||||
// Lock the database_editors
|
||||
let mut editors_by_database_id = self.editors_by_database_id.write().await;
|
||||
let db_pool = self.database_user.db_pool()?;
|
||||
let editor = self.make_database_rev_editor(view_id, db_pool).await?;
|
||||
editors_by_database_id.insert(database_id.to_string(), editor.clone());
|
||||
Ok(editor)
|
||||
.await;
|
||||
};
|
||||
|
||||
let database_editor = self
|
||||
.editors_by_database_id
|
||||
.read()
|
||||
.await
|
||||
.get(database_id)
|
||||
.cloned();
|
||||
|
||||
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)]
|
||||
|
@ -131,6 +131,9 @@ impl DatabaseEditor {
|
||||
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
|
||||
/// to dart side.
|
||||
///
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user