mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: reload UI (#2999)
* chore: reload folder * chore: reload folder * chore: init sync * chore: update tables * chore: update database * chore: load row * chore: update * chore: reload row * test: fit test * chore: retry * chore: support batch fetch * chore: enable sync * chore: sync switch * chore: sync switch * chore: migration user data * chore: migrate data * chore: migrate folder * chore: save user email * chore: refresh user profile * chore: fix test * chore: delete translation files * test: clippy format
This commit is contained in:
parent
5085ea115f
commit
f9e7b5ffa4
@ -1,30 +0,0 @@
|
|||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-config/entities.pb.dart';
|
|
||||||
|
|
||||||
class Config {
|
|
||||||
static Future<void> setSupabaseConfig({
|
|
||||||
required String url,
|
|
||||||
required String anonKey,
|
|
||||||
required String key,
|
|
||||||
required String secret,
|
|
||||||
required String pgUrl,
|
|
||||||
required String pgUser,
|
|
||||||
required String pgPassword,
|
|
||||||
required String pgPort,
|
|
||||||
}) async {
|
|
||||||
final postgresConfig = PostgresConfigurationPB.create()
|
|
||||||
..url = pgUrl
|
|
||||||
..userName = pgUser
|
|
||||||
..password = pgPassword
|
|
||||||
..port = int.parse(pgPort);
|
|
||||||
|
|
||||||
await ConfigEventSetSupabaseConfig(
|
|
||||||
SupabaseConfigPB.create()
|
|
||||||
..supabaseUrl = url
|
|
||||||
..key = key
|
|
||||||
..anonKey = anonKey
|
|
||||||
..jwtSecret = secret
|
|
||||||
..postgresConfig = postgresConfig,
|
|
||||||
).send();
|
|
||||||
}
|
|
||||||
}
|
|
7
frontend/appflowy_flutter/lib/env/env.dart
vendored
7
frontend/appflowy_flutter/lib/env/env.dart
vendored
@ -68,6 +68,13 @@ abstract class Env {
|
|||||||
defaultValue: '5432',
|
defaultValue: '5432',
|
||||||
)
|
)
|
||||||
static final String supabaseDbPort = _Env.supabaseDbPort;
|
static final String supabaseDbPort = _Env.supabaseDbPort;
|
||||||
|
|
||||||
|
@EnviedField(
|
||||||
|
obfuscate: true,
|
||||||
|
varName: 'ENABLE_SUPABASE_SYNC',
|
||||||
|
defaultValue: true,
|
||||||
|
)
|
||||||
|
static final bool enableSupabaseSync = _Env.enableSupabaseSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isSupabaseEnable =>
|
bool get isSupabaseEnable =>
|
||||||
|
@ -24,38 +24,38 @@ class CellCacheKey {
|
|||||||
/// We use GridCellCacheKey to index the cell in the cache.
|
/// We use GridCellCacheKey to index the cell in the cache.
|
||||||
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid
|
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid
|
||||||
/// for more information
|
/// for more information
|
||||||
class CellCache {
|
class CellMemCache {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
|
||||||
/// fieldId: {cacheKey: GridCell}
|
/// fieldId: {cacheKey: GridCell}
|
||||||
final Map<String, Map<RowId, dynamic>> _cellDataByFieldId = {};
|
final Map<String, Map<RowId, dynamic>> _cellByFieldId = {};
|
||||||
CellCache({
|
CellMemCache({
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
});
|
});
|
||||||
|
|
||||||
void removeCellWithFieldId(String fieldId) {
|
void removeCellWithFieldId(String fieldId) {
|
||||||
_cellDataByFieldId.remove(fieldId);
|
_cellByFieldId.remove(fieldId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(CellCacheKey key) {
|
void remove(CellCacheKey key) {
|
||||||
final map = _cellDataByFieldId[key.fieldId];
|
final map = _cellByFieldId[key.fieldId];
|
||||||
if (map != null) {
|
if (map != null) {
|
||||||
map.remove(key.rowId);
|
map.remove(key.rowId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void insert<T extends DatabaseCell>(CellCacheKey key, T value) {
|
void insert<T extends DatabaseCell>(CellCacheKey key, T value) {
|
||||||
var map = _cellDataByFieldId[key.fieldId];
|
var map = _cellByFieldId[key.fieldId];
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
_cellDataByFieldId[key.fieldId] = {};
|
_cellByFieldId[key.fieldId] = {};
|
||||||
map = _cellDataByFieldId[key.fieldId];
|
map = _cellByFieldId[key.fieldId];
|
||||||
}
|
}
|
||||||
|
|
||||||
map![key.rowId] = value.object;
|
map![key.rowId] = value.object;
|
||||||
}
|
}
|
||||||
|
|
||||||
T? get<T>(CellCacheKey key) {
|
T? get<T>(CellCacheKey key) {
|
||||||
final map = _cellDataByFieldId[key.fieldId];
|
final map = _cellByFieldId[key.fieldId];
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
@ -72,6 +72,6 @@ class CellCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
_cellDataByFieldId.clear();
|
_cellByFieldId.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import 'cell_service.dart';
|
|||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class CellController<T, D> extends Equatable {
|
class CellController<T, D> extends Equatable {
|
||||||
DatabaseCellContext _cellContext;
|
DatabaseCellContext _cellContext;
|
||||||
final CellCache _cellCache;
|
final CellMemCache _cellCache;
|
||||||
final CellCacheKey _cacheKey;
|
final CellCacheKey _cacheKey;
|
||||||
final FieldBackendService _fieldBackendSvc;
|
final FieldBackendService _fieldBackendSvc;
|
||||||
final CellDataLoader<T> _cellDataLoader;
|
final CellDataLoader<T> _cellDataLoader;
|
||||||
@ -54,7 +54,7 @@ class CellController<T, D> extends Equatable {
|
|||||||
|
|
||||||
CellController({
|
CellController({
|
||||||
required DatabaseCellContext cellContext,
|
required DatabaseCellContext cellContext,
|
||||||
required CellCache cellCache,
|
required CellMemCache cellCache,
|
||||||
required CellDataLoader<T> cellDataLoader,
|
required CellDataLoader<T> cellDataLoader,
|
||||||
required CellDataPersistence<D> cellDataPersistence,
|
required CellDataPersistence<D> cellDataPersistence,
|
||||||
}) : _cellContext = cellContext,
|
}) : _cellContext = cellContext,
|
||||||
@ -103,12 +103,15 @@ class CellController<T, D> extends Equatable {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
_rowMetaListener?.start(
|
// Only the primary can listen on the row meta changes.
|
||||||
callback: (newRowMeta) {
|
if (_cellContext.fieldInfo.isPrimary) {
|
||||||
_cellContext = _cellContext.copyWith(rowMeta: newRowMeta);
|
_rowMetaListener?.start(
|
||||||
_onRowMetaChanged?.call();
|
callback: (newRowMeta) {
|
||||||
},
|
_cellContext = _cellContext.copyWith(rowMeta: newRowMeta);
|
||||||
);
|
_onRowMetaChanged?.call();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Listen on the cell content or field changes
|
/// Listen on the cell content or field changes
|
||||||
|
@ -18,11 +18,11 @@ typedef URLCellController = CellController<URLCellDataPB, String>;
|
|||||||
|
|
||||||
class CellControllerBuilder {
|
class CellControllerBuilder {
|
||||||
final DatabaseCellContext _cellContext;
|
final DatabaseCellContext _cellContext;
|
||||||
final CellCache _cellCache;
|
final CellMemCache _cellCache;
|
||||||
|
|
||||||
CellControllerBuilder({
|
CellControllerBuilder({
|
||||||
required DatabaseCellContext cellContext,
|
required DatabaseCellContext cellContext,
|
||||||
required CellCache cellCache,
|
required CellMemCache cellCache,
|
||||||
}) : _cellCache = cellCache,
|
}) : _cellCache = cellCache,
|
||||||
_cellContext = cellContext;
|
_cellContext = cellContext;
|
||||||
|
|
||||||
|
@ -17,13 +17,13 @@ typedef OnDatabaseChanged = void Function(DatabasePB);
|
|||||||
typedef OnRowsCreated = void Function(List<RowId> ids);
|
typedef OnRowsCreated = void Function(List<RowId> ids);
|
||||||
typedef OnRowsUpdated = void Function(
|
typedef OnRowsUpdated = void Function(
|
||||||
List<RowId> ids,
|
List<RowId> ids,
|
||||||
RowsChangedReason reason,
|
ChangedReason reason,
|
||||||
);
|
);
|
||||||
typedef OnRowsDeleted = void Function(List<RowId> ids);
|
typedef OnRowsDeleted = void Function(List<RowId> ids);
|
||||||
typedef OnNumOfRowsChanged = void Function(
|
typedef OnNumOfRowsChanged = void Function(
|
||||||
UnmodifiableListView<RowInfo> rows,
|
UnmodifiableListView<RowInfo> rows,
|
||||||
UnmodifiableMapView<RowId, RowInfo> rowByRowId,
|
UnmodifiableMapView<RowId, RowInfo> rowByRowId,
|
||||||
RowsChangedReason reason,
|
ChangedReason reason,
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef OnError = void Function(FlowyError);
|
typedef OnError = void Function(FlowyError);
|
||||||
|
@ -687,27 +687,29 @@ class FieldController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RowDelegatesImpl extends RowFieldsDelegate with RowCacheDelegate {
|
class RowCacheDependenciesImpl extends RowFieldsDelegate with RowLifeCycle {
|
||||||
final FieldController _cache;
|
final FieldController _fieldController;
|
||||||
OnReceiveFields? _onFieldFn;
|
OnReceiveFields? _onFieldFn;
|
||||||
RowDelegatesImpl(FieldController cache) : _cache = cache;
|
RowCacheDependenciesImpl(FieldController cache) : _fieldController = cache;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
UnmodifiableListView<FieldInfo> get fields =>
|
UnmodifiableListView<FieldInfo> get fields =>
|
||||||
UnmodifiableListView(_cache.fieldInfos);
|
UnmodifiableListView(_fieldController.fieldInfos);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onFieldsChanged(void Function(List<FieldInfo>) callback) {
|
void onFieldsChanged(void Function(List<FieldInfo>) callback) {
|
||||||
_onFieldFn = (fieldInfos) {
|
if (_onFieldFn != null) {
|
||||||
callback(fieldInfos);
|
_fieldController.removeListener(onFieldsListener: _onFieldFn!);
|
||||||
};
|
}
|
||||||
_cache.addListener(onReceiveFields: _onFieldFn);
|
|
||||||
|
_onFieldFn = (fieldInfos) => callback(fieldInfos);
|
||||||
|
_fieldController.addListener(onReceiveFields: _onFieldFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onRowDispose() {
|
void onRowDisposed() {
|
||||||
if (_onFieldFn != null) {
|
if (_onFieldFn != null) {
|
||||||
_cache.removeListener(onFieldsListener: _onFieldFn!);
|
_fieldController.removeListener(onFieldsListener: _onFieldFn!);
|
||||||
_onFieldFn = null;
|
_onFieldFn = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,30 +13,25 @@ part 'row_cache.freezed.dart';
|
|||||||
|
|
||||||
typedef RowUpdateCallback = void Function();
|
typedef RowUpdateCallback = void Function();
|
||||||
|
|
||||||
|
/// A delegate that provides the fields of the row.
|
||||||
abstract class RowFieldsDelegate {
|
abstract class RowFieldsDelegate {
|
||||||
|
UnmodifiableListView<FieldInfo> get fields;
|
||||||
void onFieldsChanged(void Function(List<FieldInfo>) callback);
|
void onFieldsChanged(void Function(List<FieldInfo>) callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract mixin class RowCacheDelegate {
|
abstract mixin class RowLifeCycle {
|
||||||
UnmodifiableListView<FieldInfo> get fields;
|
void onRowDisposed();
|
||||||
void onRowDispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cache the rows in memory
|
|
||||||
/// Insert / delete / update row
|
|
||||||
///
|
|
||||||
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
|
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
|
||||||
|
|
||||||
class RowCache {
|
class RowCache {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
|
||||||
/// _rows contains the current block's rows
|
|
||||||
/// Use List to reverse the order of the GridRow.
|
|
||||||
final RowList _rowList = RowList();
|
final RowList _rowList = RowList();
|
||||||
|
final CellMemCache _cellMemCache;
|
||||||
final CellCache _cellCache;
|
final RowLifeCycle _rowLifeCycle;
|
||||||
final RowCacheDelegate _delegate;
|
final RowFieldsDelegate _fieldDelegate;
|
||||||
final RowChangesetNotifier _rowChangeReasonNotifier;
|
final RowChangesetNotifier _changedNotifier;
|
||||||
|
|
||||||
/// Returns a unmodifiable list of RowInfo
|
/// Returns a unmodifiable list of RowInfo
|
||||||
UnmodifiableListView<RowInfo> get rowInfos {
|
UnmodifiableListView<RowInfo> get rowInfos {
|
||||||
@ -44,29 +39,29 @@ class RowCache {
|
|||||||
return UnmodifiableListView(visibleRows);
|
return UnmodifiableListView(visibleRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a unmodifiable map of rowId to RowInfo
|
/// Returns a unmodifiable map of RowInfo
|
||||||
UnmodifiableMapView<RowId, RowInfo> get rowByRowId {
|
UnmodifiableMapView<RowId, RowInfo> get rowByRowId {
|
||||||
return UnmodifiableMapView(_rowList.rowInfoByRowId);
|
return UnmodifiableMapView(_rowList.rowInfoByRowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
CellCache get cellCache => _cellCache;
|
CellMemCache get cellCache => _cellMemCache;
|
||||||
|
ChangedReason get changeReason => _changedNotifier.reason;
|
||||||
RowsChangedReason get changeReason => _rowChangeReasonNotifier.reason;
|
|
||||||
|
|
||||||
RowCache({
|
RowCache({
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required RowFieldsDelegate fieldsDelegate,
|
required RowFieldsDelegate fieldsDelegate,
|
||||||
required RowCacheDelegate cacheDelegate,
|
required RowLifeCycle rowLifeCycle,
|
||||||
}) : _cellCache = CellCache(viewId: viewId),
|
}) : _cellMemCache = CellMemCache(viewId: viewId),
|
||||||
_rowChangeReasonNotifier = RowChangesetNotifier(),
|
_changedNotifier = RowChangesetNotifier(),
|
||||||
_delegate = cacheDelegate {
|
_rowLifeCycle = rowLifeCycle,
|
||||||
//
|
_fieldDelegate = fieldsDelegate {
|
||||||
|
// Listen on the changed of the fields. If the fields changed, we need to
|
||||||
|
// clear the cell cache with the given field id.
|
||||||
fieldsDelegate.onFieldsChanged((fieldInfos) {
|
fieldsDelegate.onFieldsChanged((fieldInfos) {
|
||||||
for (final fieldInfo in fieldInfos) {
|
for (final fieldInfo in fieldInfos) {
|
||||||
_cellCache.removeCellWithFieldId(fieldInfo.id);
|
_cellMemCache.removeCellWithFieldId(fieldInfo.id);
|
||||||
}
|
}
|
||||||
_rowChangeReasonNotifier
|
_changedNotifier.receive(const ChangedReason.fieldDidChange());
|
||||||
.receive(const RowsChangedReason.fieldDidChange());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,9 +77,9 @@ class RowCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
_delegate.onRowDispose();
|
_rowLifeCycle.onRowDisposed();
|
||||||
_rowChangeReasonNotifier.dispose();
|
_changedNotifier.dispose();
|
||||||
await _cellCache.dispose();
|
await _cellMemCache.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void applyRowsChanged(RowsChangePB changeset) {
|
void applyRowsChanged(RowsChangePB changeset) {
|
||||||
@ -100,7 +95,7 @@ class RowCache {
|
|||||||
|
|
||||||
void reorderAllRows(List<String> rowIds) {
|
void reorderAllRows(List<String> rowIds) {
|
||||||
_rowList.reorderWithRowIds(rowIds);
|
_rowList.reorderWithRowIds(rowIds);
|
||||||
_rowChangeReasonNotifier.receive(const RowsChangedReason.reorderRows());
|
_changedNotifier.receive(const ChangedReason.reorderRows());
|
||||||
}
|
}
|
||||||
|
|
||||||
void reorderSingleRow(ReorderSingleRowPB reorderRow) {
|
void reorderSingleRow(ReorderSingleRowPB reorderRow) {
|
||||||
@ -111,8 +106,8 @@ class RowCache {
|
|||||||
reorderRow.oldIndex,
|
reorderRow.oldIndex,
|
||||||
reorderRow.newIndex,
|
reorderRow.newIndex,
|
||||||
);
|
);
|
||||||
_rowChangeReasonNotifier.receive(
|
_changedNotifier.receive(
|
||||||
RowsChangedReason.reorderSingleRow(
|
ChangedReason.reorderSingleRow(
|
||||||
reorderRow,
|
reorderRow,
|
||||||
rowInfo,
|
rowInfo,
|
||||||
),
|
),
|
||||||
@ -124,7 +119,7 @@ class RowCache {
|
|||||||
for (final rowId in deletedRowIds) {
|
for (final rowId in deletedRowIds) {
|
||||||
final deletedRow = _rowList.remove(rowId);
|
final deletedRow = _rowList.remove(rowId);
|
||||||
if (deletedRow != null) {
|
if (deletedRow != null) {
|
||||||
_rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));
|
_changedNotifier.receive(ChangedReason.delete(deletedRow));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,8 +129,7 @@ class RowCache {
|
|||||||
final insertedIndex =
|
final insertedIndex =
|
||||||
_rowList.insert(insertedRow.index, buildGridRow(insertedRow.rowMeta));
|
_rowList.insert(insertedRow.index, buildGridRow(insertedRow.rowMeta));
|
||||||
if (insertedIndex != null) {
|
if (insertedIndex != null) {
|
||||||
_rowChangeReasonNotifier
|
_changedNotifier.receive(ChangedReason.insert(insertedIndex));
|
||||||
.receive(RowsChangedReason.insert(insertedIndex));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,7 +143,7 @@ class RowCache {
|
|||||||
fieldId: fieldId,
|
fieldId: fieldId,
|
||||||
rowId: updatedRow.rowId,
|
rowId: updatedRow.rowId,
|
||||||
);
|
);
|
||||||
_cellCache.remove(key);
|
_cellMemCache.remove(key);
|
||||||
}
|
}
|
||||||
if (updatedRow.hasRowMeta()) {
|
if (updatedRow.hasRowMeta()) {
|
||||||
updatedList.add(updatedRow.rowMeta);
|
updatedList.add(updatedRow.rowMeta);
|
||||||
@ -160,7 +154,7 @@ class RowCache {
|
|||||||
_rowList.updateRows(updatedList, (rowId) => buildGridRow(rowId));
|
_rowList.updateRows(updatedList, (rowId) => buildGridRow(rowId));
|
||||||
|
|
||||||
if (updatedIndexs.isNotEmpty) {
|
if (updatedIndexs.isNotEmpty) {
|
||||||
_rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs));
|
_changedNotifier.receive(ChangedReason.update(updatedIndexs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +162,7 @@ class RowCache {
|
|||||||
for (final rowId in invisibleRows) {
|
for (final rowId in invisibleRows) {
|
||||||
final deletedRow = _rowList.remove(rowId);
|
final deletedRow = _rowList.remove(rowId);
|
||||||
if (deletedRow != null) {
|
if (deletedRow != null) {
|
||||||
_rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));
|
_changedNotifier.receive(ChangedReason.delete(deletedRow));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,62 +172,45 @@ class RowCache {
|
|||||||
final insertedIndex =
|
final insertedIndex =
|
||||||
_rowList.insert(insertedRow.index, buildGridRow(insertedRow.rowMeta));
|
_rowList.insert(insertedRow.index, buildGridRow(insertedRow.rowMeta));
|
||||||
if (insertedIndex != null) {
|
if (insertedIndex != null) {
|
||||||
_rowChangeReasonNotifier
|
_changedNotifier.receive(ChangedReason.insert(insertedIndex));
|
||||||
.receive(RowsChangedReason.insert(insertedIndex));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRowsChanged(void Function(RowsChangedReason) onRowChanged) {
|
void onRowsChanged(void Function(ChangedReason) onRowChanged) {
|
||||||
_rowChangeReasonNotifier.addListener(() {
|
_changedNotifier.addListener(() {
|
||||||
onRowChanged(_rowChangeReasonNotifier.reason);
|
onRowChanged(_changedNotifier.reason);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
RowUpdateCallback addListener({
|
RowUpdateCallback addListener({
|
||||||
required RowId rowId,
|
required RowId rowId,
|
||||||
void Function(CellContextByFieldId, RowsChangedReason)? onCellUpdated,
|
void Function(CellContextByFieldId, ChangedReason)? onRowChanged,
|
||||||
bool Function()? listenWhen,
|
|
||||||
}) {
|
}) {
|
||||||
listenerHandler() async {
|
listenerHandler() async {
|
||||||
if (listenWhen != null && listenWhen() == false) {
|
if (onRowChanged != null) {
|
||||||
return;
|
final rowInfo = _rowList.get(rowId);
|
||||||
}
|
if (rowInfo != null) {
|
||||||
|
final cellDataMap = _makeCells(rowInfo.rowMeta);
|
||||||
notifyUpdate() {
|
onRowChanged(cellDataMap, _changedNotifier.reason);
|
||||||
if (onCellUpdated != null) {
|
|
||||||
final rowInfo = _rowList.get(rowId);
|
|
||||||
if (rowInfo != null) {
|
|
||||||
final CellContextByFieldId cellDataMap = _makeGridCells(
|
|
||||||
rowInfo.rowMeta,
|
|
||||||
);
|
|
||||||
onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_rowChangeReasonNotifier.reason.whenOrNull(
|
|
||||||
update: (indexs) {
|
|
||||||
if (indexs[rowId] != null) notifyUpdate();
|
|
||||||
},
|
|
||||||
fieldDidChange: () => notifyUpdate(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_rowChangeReasonNotifier.addListener(listenerHandler);
|
_changedNotifier.addListener(listenerHandler);
|
||||||
return listenerHandler;
|
return listenerHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeRowListener(VoidCallback callback) {
|
void removeRowListener(VoidCallback callback) {
|
||||||
_rowChangeReasonNotifier.removeListener(callback);
|
_changedNotifier.removeListener(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
CellContextByFieldId loadGridCells(RowMetaPB rowMeta) {
|
CellContextByFieldId loadCells(RowMetaPB rowMeta) {
|
||||||
final rowInfo = _rowList.get(rowMeta.id);
|
final rowInfo = _rowList.get(rowMeta.id);
|
||||||
if (rowInfo == null) {
|
if (rowInfo == null) {
|
||||||
_loadRow(rowMeta.id);
|
_loadRow(rowMeta.id);
|
||||||
}
|
}
|
||||||
return _makeGridCells(rowMeta);
|
return _makeCells(rowMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadRow(RowId rowId) async {
|
Future<void> _loadRow(RowId rowId) async {
|
||||||
@ -257,18 +234,17 @@ class RowCache {
|
|||||||
rowId: rowMetaPB.id,
|
rowId: rowMetaPB.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
_rowChangeReasonNotifier
|
_changedNotifier.receive(ChangedReason.update(updatedIndexs));
|
||||||
.receive(RowsChangedReason.update(updatedIndexs));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
CellContextByFieldId _makeGridCells(RowMetaPB rowMeta) {
|
CellContextByFieldId _makeCells(RowMetaPB rowMeta) {
|
||||||
// ignore: prefer_collection_literals
|
// ignore: prefer_collection_literals
|
||||||
final cellContextMap = CellContextByFieldId();
|
final cellContextMap = CellContextByFieldId();
|
||||||
for (final field in _delegate.fields) {
|
for (final field in _fieldDelegate.fields) {
|
||||||
if (field.visibility) {
|
if (field.visibility) {
|
||||||
cellContextMap[field.id] = DatabaseCellContext(
|
cellContextMap[field.id] = DatabaseCellContext(
|
||||||
rowMeta: rowMeta,
|
rowMeta: rowMeta,
|
||||||
@ -283,7 +259,7 @@ class RowCache {
|
|||||||
RowInfo buildGridRow(RowMetaPB rowMetaPB) {
|
RowInfo buildGridRow(RowMetaPB rowMetaPB) {
|
||||||
return RowInfo(
|
return RowInfo(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
fields: _delegate.fields,
|
fields: _fieldDelegate.fields,
|
||||||
rowId: rowMetaPB.id,
|
rowId: rowMetaPB.id,
|
||||||
rowMeta: rowMetaPB,
|
rowMeta: rowMetaPB,
|
||||||
);
|
);
|
||||||
@ -291,11 +267,11 @@ class RowCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RowChangesetNotifier extends ChangeNotifier {
|
class RowChangesetNotifier extends ChangeNotifier {
|
||||||
RowsChangedReason reason = const InitialListState();
|
ChangedReason reason = const InitialListState();
|
||||||
|
|
||||||
RowChangesetNotifier();
|
RowChangesetNotifier();
|
||||||
|
|
||||||
void receive(RowsChangedReason newReason) {
|
void receive(ChangedReason newReason) {
|
||||||
reason = newReason;
|
reason = newReason;
|
||||||
reason.map(
|
reason.map(
|
||||||
insert: (_) => notifyListeners(),
|
insert: (_) => notifyListeners(),
|
||||||
@ -326,14 +302,14 @@ typedef DeletedIndexs = List<DeletedIndex>;
|
|||||||
typedef UpdatedIndexMap = LinkedHashMap<RowId, UpdatedIndex>;
|
typedef UpdatedIndexMap = LinkedHashMap<RowId, UpdatedIndex>;
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class RowsChangedReason with _$RowsChangedReason {
|
class ChangedReason with _$ChangedReason {
|
||||||
const factory RowsChangedReason.insert(InsertedIndex item) = _Insert;
|
const factory ChangedReason.insert(InsertedIndex item) = _Insert;
|
||||||
const factory RowsChangedReason.delete(DeletedIndex item) = _Delete;
|
const factory ChangedReason.delete(DeletedIndex item) = _Delete;
|
||||||
const factory RowsChangedReason.update(UpdatedIndexMap indexs) = _Update;
|
const factory ChangedReason.update(UpdatedIndexMap indexs) = _Update;
|
||||||
const factory RowsChangedReason.fieldDidChange() = _FieldDidChange;
|
const factory ChangedReason.fieldDidChange() = _FieldDidChange;
|
||||||
const factory RowsChangedReason.initial() = InitialListState;
|
const factory ChangedReason.initial() = InitialListState;
|
||||||
const factory RowsChangedReason.reorderRows() = _ReorderRows;
|
const factory ChangedReason.reorderRows() = _ReorderRows;
|
||||||
const factory RowsChangedReason.reorderSingleRow(
|
const factory ChangedReason.reorderSingleRow(
|
||||||
ReorderSingleRowPB reorderRow,
|
ReorderSingleRowPB reorderRow,
|
||||||
RowInfo rowInfo,
|
RowInfo rowInfo,
|
||||||
) = _ReorderSingleRow;
|
) = _ReorderSingleRow;
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import '../cell/cell_service.dart';
|
import '../cell/cell_service.dart';
|
||||||
import 'row_cache.dart';
|
import 'row_cache.dart';
|
||||||
|
|
||||||
typedef OnRowChanged = void Function(CellContextByFieldId, RowsChangedReason);
|
typedef OnRowChanged = void Function(CellContextByFieldId, ChangedReason);
|
||||||
|
|
||||||
class RowController {
|
class RowController {
|
||||||
final RowMetaPB rowMeta;
|
final RowMetaPB rowMeta;
|
||||||
@ -24,13 +24,13 @@ class RowController {
|
|||||||
}) : _rowCache = rowCache;
|
}) : _rowCache = rowCache;
|
||||||
|
|
||||||
CellContextByFieldId loadData() {
|
CellContextByFieldId loadData() {
|
||||||
return _rowCache.loadGridCells(rowMeta);
|
return _rowCache.loadCells(rowMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addListener({OnRowChanged? onRowChanged}) {
|
void addListener({OnRowChanged? onRowChanged}) {
|
||||||
final fn = _rowCache.addListener(
|
final fn = _rowCache.addListener(
|
||||||
rowId: rowMeta.id,
|
rowId: rowMeta.id,
|
||||||
onCellUpdated: onRowChanged,
|
onRowChanged: onRowChanged,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add the listener to the list so that we can remove it later.
|
// Add the listener to the list so that we can remove it later.
|
@ -0,0 +1,68 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:appflowy/core/notification/grid_notification.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
|
||||||
|
typedef DidFetchRowCallback = void Function(DidFetchRowPB);
|
||||||
|
typedef RowMetaCallback = void Function(RowMetaPB);
|
||||||
|
|
||||||
|
class RowListener {
|
||||||
|
final String rowId;
|
||||||
|
DidFetchRowCallback? _onRowFetchedCallback;
|
||||||
|
RowMetaCallback? _onMetaChangedCallback;
|
||||||
|
DatabaseNotificationListener? _listener;
|
||||||
|
RowListener(this.rowId);
|
||||||
|
|
||||||
|
/// OnMetaChanged will be called when the row meta is changed.
|
||||||
|
/// OnRowFetched will be called when the row is fetched from remote storage
|
||||||
|
void start({
|
||||||
|
RowMetaCallback? onMetaChanged,
|
||||||
|
DidFetchRowCallback? onRowFetched,
|
||||||
|
}) {
|
||||||
|
_onMetaChangedCallback = onMetaChanged;
|
||||||
|
_onRowFetchedCallback = onRowFetched;
|
||||||
|
_listener = DatabaseNotificationListener(
|
||||||
|
objectId: rowId,
|
||||||
|
handler: _handler,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handler(
|
||||||
|
DatabaseNotification ty,
|
||||||
|
Either<Uint8List, FlowyError> result,
|
||||||
|
) {
|
||||||
|
switch (ty) {
|
||||||
|
case DatabaseNotification.DidUpdateRowMeta:
|
||||||
|
result.fold(
|
||||||
|
(payload) {
|
||||||
|
if (_onMetaChangedCallback != null) {
|
||||||
|
_onMetaChangedCallback!(RowMetaPB.fromBuffer(payload));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => Log.error(error),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case DatabaseNotification.DidFetchRow:
|
||||||
|
result.fold(
|
||||||
|
(payload) {
|
||||||
|
if (_onRowFetchedCallback != null) {
|
||||||
|
_onRowFetchedCallback!(DidFetchRowPB.fromBuffer(payload));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => Log.error(error),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> stop() async {
|
||||||
|
await _listener?.stop();
|
||||||
|
_onMetaChangedCallback = null;
|
||||||
|
_onRowFetchedCallback = null;
|
||||||
|
}
|
||||||
|
}
|
@ -46,11 +46,11 @@ class DatabaseViewCache {
|
|||||||
required this.viewId,
|
required this.viewId,
|
||||||
required FieldController fieldController,
|
required FieldController fieldController,
|
||||||
}) : _databaseViewListener = DatabaseViewListener(viewId: viewId) {
|
}) : _databaseViewListener = DatabaseViewListener(viewId: viewId) {
|
||||||
final delegate = RowDelegatesImpl(fieldController);
|
final depsImpl = RowCacheDependenciesImpl(fieldController);
|
||||||
_rowCache = RowCache(
|
_rowCache = RowCache(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
fieldsDelegate: delegate,
|
fieldsDelegate: depsImpl,
|
||||||
cacheDelegate: delegate,
|
rowLifeCycle: depsImpl,
|
||||||
);
|
);
|
||||||
|
|
||||||
_databaseViewListener.start(
|
_databaseViewListener.start(
|
||||||
|
@ -6,7 +6,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
||||||
@ -96,7 +96,10 @@ class BoardPage extends StatelessWidget {
|
|||||||
(_) => BoardContent(
|
(_) => BoardContent(
|
||||||
onEditStateChanged: onEditStateChanged,
|
onEditStateChanged: onEditStateChanged,
|
||||||
),
|
),
|
||||||
(err) => FlowyErrorPage.message(err.toString(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),),
|
(err) => FlowyErrorPage.message(
|
||||||
|
err.toString(),
|
||||||
|
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -24,7 +24,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
|||||||
// Getters
|
// Getters
|
||||||
String get viewId => databaseController.viewId;
|
String get viewId => databaseController.viewId;
|
||||||
FieldController get fieldController => databaseController.fieldController;
|
FieldController get fieldController => databaseController.fieldController;
|
||||||
CellCache get cellCache => databaseController.rowCache.cellCache;
|
CellMemCache get cellCache => databaseController.rowCache.cellCache;
|
||||||
RowCache get rowCache => databaseController.rowCache;
|
RowCache get rowCache => databaseController.rowCache;
|
||||||
|
|
||||||
CalendarBloc({required ViewPB view, required this.databaseController})
|
CalendarBloc({required ViewPB view, required this.databaseController})
|
||||||
|
@ -21,7 +21,7 @@ class UnscheduleEventsBloc
|
|||||||
// Getters
|
// Getters
|
||||||
String get viewId => databaseController.viewId;
|
String get viewId => databaseController.viewId;
|
||||||
FieldController get fieldController => databaseController.fieldController;
|
FieldController get fieldController => databaseController.fieldController;
|
||||||
CellCache get cellCache => databaseController.rowCache.cellCache;
|
CellMemCache get cellCache => databaseController.rowCache.cellCache;
|
||||||
RowCache get rowCache => databaseController.rowCache;
|
RowCache get rowCache => databaseController.rowCache;
|
||||||
|
|
||||||
UnscheduleEventsBloc({
|
UnscheduleEventsBloc({
|
||||||
|
@ -14,7 +14,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../application/row/row_cache.dart';
|
import '../../application/row/row_cache.dart';
|
||||||
import '../../application/row/row_data_controller.dart';
|
import '../../application/row/row_controller.dart';
|
||||||
import '../../widgets/row/cell_builder.dart';
|
import '../../widgets/row/cell_builder.dart';
|
||||||
import '../../widgets/row/row_detail.dart';
|
import '../../widgets/row/row_detail.dart';
|
||||||
import 'calendar_day.dart';
|
import 'calendar_day.dart';
|
||||||
|
@ -153,7 +153,7 @@ class GridEvent with _$GridEvent {
|
|||||||
const factory GridEvent.moveRow(int from, int to) = _MoveRow;
|
const factory GridEvent.moveRow(int from, int to) = _MoveRow;
|
||||||
const factory GridEvent.didLoadRows(
|
const factory GridEvent.didLoadRows(
|
||||||
List<RowInfo> rows,
|
List<RowInfo> rows,
|
||||||
RowsChangedReason reason,
|
ChangedReason reason,
|
||||||
) = _DidReceiveRowUpdate;
|
) = _DidReceiveRowUpdate;
|
||||||
const factory GridEvent.didReceiveFieldUpdate(
|
const factory GridEvent.didReceiveFieldUpdate(
|
||||||
List<FieldInfo> fields,
|
List<FieldInfo> fields,
|
||||||
@ -179,7 +179,7 @@ class GridState with _$GridState {
|
|||||||
required int rowCount,
|
required int rowCount,
|
||||||
required GridLoadingState loadingState,
|
required GridLoadingState loadingState,
|
||||||
required bool reorderable,
|
required bool reorderable,
|
||||||
required RowsChangedReason reason,
|
required ChangedReason reason,
|
||||||
required List<SortInfo> sorts,
|
required List<SortInfo> sorts,
|
||||||
required List<FilterInfo> filters,
|
required List<FilterInfo> filters,
|
||||||
}) = _GridState;
|
}) = _GridState;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/row/row_listener.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@ -7,7 +9,7 @@ import 'dart:async';
|
|||||||
import '../../../application/cell/cell_service.dart';
|
import '../../../application/cell/cell_service.dart';
|
||||||
import '../../../application/field/field_controller.dart';
|
import '../../../application/field/field_controller.dart';
|
||||||
import '../../../application/row/row_cache.dart';
|
import '../../../application/row/row_cache.dart';
|
||||||
import '../../../application/row/row_data_controller.dart';
|
import '../../../application/row/row_controller.dart';
|
||||||
import '../../../application/row/row_service.dart';
|
import '../../../application/row/row_service.dart';
|
||||||
|
|
||||||
part 'row_bloc.freezed.dart';
|
part 'row_bloc.freezed.dart';
|
||||||
@ -15,6 +17,7 @@ part 'row_bloc.freezed.dart';
|
|||||||
class RowBloc extends Bloc<RowEvent, RowState> {
|
class RowBloc extends Bloc<RowEvent, RowState> {
|
||||||
final RowBackendService _rowBackendSvc;
|
final RowBackendService _rowBackendSvc;
|
||||||
final RowController _dataController;
|
final RowController _dataController;
|
||||||
|
final RowListener _rowListener;
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final String rowId;
|
final String rowId;
|
||||||
|
|
||||||
@ -24,6 +27,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
required RowController dataController,
|
required RowController dataController,
|
||||||
}) : _rowBackendSvc = RowBackendService(viewId: viewId),
|
}) : _rowBackendSvc = RowBackendService(viewId: viewId),
|
||||||
_dataController = dataController,
|
_dataController = dataController,
|
||||||
|
_rowListener = RowListener(rowId),
|
||||||
super(RowState.initial(dataController.loadData())) {
|
super(RowState.initial(dataController.loadData())) {
|
||||||
on<RowEvent>(
|
on<RowEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
@ -46,6 +50,9 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
reloadRow: (DidFetchRowPB row) {
|
||||||
|
emit(state.copyWith(rowSource: RowSourece.remote(row)));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -54,6 +61,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
_dataController.dispose();
|
_dataController.dispose();
|
||||||
|
await _rowListener.stop();
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +73,14 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_rowListener.start(
|
||||||
|
onRowFetched: (fetchRow) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(RowEvent.reloadRow(fetchRow));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,9 +88,10 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
class RowEvent with _$RowEvent {
|
class RowEvent with _$RowEvent {
|
||||||
const factory RowEvent.initial() = _InitialRow;
|
const factory RowEvent.initial() = _InitialRow;
|
||||||
const factory RowEvent.createRow() = _CreateRow;
|
const factory RowEvent.createRow() = _CreateRow;
|
||||||
|
const factory RowEvent.reloadRow(DidFetchRowPB row) = _ReloadRow;
|
||||||
const factory RowEvent.didReceiveCells(
|
const factory RowEvent.didReceiveCells(
|
||||||
CellContextByFieldId cellsByFieldId,
|
CellContextByFieldId cellsByFieldId,
|
||||||
RowsChangedReason reason,
|
ChangedReason reason,
|
||||||
) = _DidReceiveCells;
|
) = _DidReceiveCells;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +100,8 @@ class RowState with _$RowState {
|
|||||||
const factory RowState({
|
const factory RowState({
|
||||||
required CellContextByFieldId cellByFieldId,
|
required CellContextByFieldId cellByFieldId,
|
||||||
required UnmodifiableListView<GridCellEquatable> cells,
|
required UnmodifiableListView<GridCellEquatable> cells,
|
||||||
RowsChangedReason? changeReason,
|
required RowSourece rowSource,
|
||||||
|
ChangedReason? changeReason,
|
||||||
}) = _RowState;
|
}) = _RowState;
|
||||||
|
|
||||||
factory RowState.initial(
|
factory RowState.initial(
|
||||||
@ -96,6 +114,7 @@ class RowState with _$RowState {
|
|||||||
.map((e) => GridCellEquatable(e.fieldInfo))
|
.map((e) => GridCellEquatable(e.fieldInfo))
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
|
rowSource: const RowSourece.disk(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,3 +131,11 @@ class GridCellEquatable extends Equatable {
|
|||||||
_fieldContext.width,
|
_fieldContext.width,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class RowSourece with _$RowSourece {
|
||||||
|
const factory RowSourece.disk() = _Disk;
|
||||||
|
const factory RowSourece.remote(
|
||||||
|
DidFetchRowPB row,
|
||||||
|
) = _Remote;
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import '../../../application/cell/cell_service.dart';
|
import '../../../application/cell/cell_service.dart';
|
||||||
import '../../../application/field/field_service.dart';
|
import '../../../application/field/field_service.dart';
|
||||||
import '../../../application/row/row_data_controller.dart';
|
import '../../../application/row/row_controller.dart';
|
||||||
part 'row_detail_bloc.freezed.dart';
|
part 'row_detail_bloc.freezed.dart';
|
||||||
|
|
||||||
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||||
|
@ -18,7 +18,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
||||||
import '../../application/field/field_controller.dart';
|
import '../../application/field/field_controller.dart';
|
||||||
import '../../application/row/row_cache.dart';
|
import '../../application/row/row_cache.dart';
|
||||||
import '../../application/row/row_data_controller.dart';
|
import '../../application/row/row_controller.dart';
|
||||||
import '../application/grid_bloc.dart';
|
import '../application/grid_bloc.dart';
|
||||||
import '../../application/database_controller.dart';
|
import '../../application/database_controller.dart';
|
||||||
import 'grid_scroll.dart';
|
import 'grid_scroll.dart';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
|
||||||
@ -65,7 +65,9 @@ class _GridRowState extends State<GridRow> {
|
|||||||
child: _RowEnterRegion(
|
child: _RowEnterRegion(
|
||||||
child: BlocBuilder<RowBloc, RowState>(
|
child: BlocBuilder<RowBloc, RowState>(
|
||||||
// The row need to rebuild when the cell count changes.
|
// The row need to rebuild when the cell count changes.
|
||||||
buildWhen: (p, c) => p.cellByFieldId.length != c.cellByFieldId.length,
|
buildWhen: (p, c) =>
|
||||||
|
p.cellByFieldId.length != c.cellByFieldId.length ||
|
||||||
|
p.rowSource != c.rowSource,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final content = Expanded(
|
final content = Expanded(
|
||||||
child: RowContent(
|
child: RowContent(
|
||||||
@ -78,6 +80,7 @@ class _GridRowState extends State<GridRow> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
|
key: ValueKey(state.rowSource),
|
||||||
children: [
|
children: [
|
||||||
_RowLeading(
|
_RowLeading(
|
||||||
index: widget.index,
|
index: widget.index,
|
||||||
|
@ -29,7 +29,7 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
|
|||||||
_rowCache = rowCache,
|
_rowCache = rowCache,
|
||||||
super(
|
super(
|
||||||
RowCardState.initial(
|
RowCardState.initial(
|
||||||
_makeCells(groupFieldId, rowCache.loadGridCells(rowMeta)),
|
_makeCells(groupFieldId, rowCache.loadCells(rowMeta)),
|
||||||
isEditing,
|
isEditing,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
@ -78,7 +78,7 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
|
|||||||
Future<void> _startListening() async {
|
Future<void> _startListening() async {
|
||||||
_rowCallback = _rowCache.addListener(
|
_rowCallback = _rowCache.addListener(
|
||||||
rowId: rowMeta.id,
|
rowId: rowMeta.id,
|
||||||
onCellUpdated: (cellMap, reason) {
|
onRowChanged: (cellMap, reason) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
final cells = _makeCells(groupFieldId, cellMap);
|
final cells = _makeCells(groupFieldId, cellMap);
|
||||||
add(RowCardEvent.didReceiveCells(cells, reason));
|
add(RowCardEvent.didReceiveCells(cells, reason));
|
||||||
@ -112,7 +112,7 @@ class RowCardEvent with _$RowCardEvent {
|
|||||||
const factory RowCardEvent.setIsEditing(bool isEditing) = _IsEditing;
|
const factory RowCardEvent.setIsEditing(bool isEditing) = _IsEditing;
|
||||||
const factory RowCardEvent.didReceiveCells(
|
const factory RowCardEvent.didReceiveCells(
|
||||||
List<DatabaseCellContext> cells,
|
List<DatabaseCellContext> cells,
|
||||||
RowsChangedReason reason,
|
ChangedReason reason,
|
||||||
) = _DidReceiveCells;
|
) = _DidReceiveCells;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ class RowCardState with _$RowCardState {
|
|||||||
const factory RowCardState({
|
const factory RowCardState({
|
||||||
required List<DatabaseCellContext> cells,
|
required List<DatabaseCellContext> cells,
|
||||||
required bool isEditing,
|
required bool isEditing,
|
||||||
RowsChangedReason? changeReason,
|
ChangedReason? changeReason,
|
||||||
}) = _RowCardState;
|
}) = _RowCardState;
|
||||||
|
|
||||||
factory RowCardState.initial(
|
factory RowCardState.initial(
|
||||||
|
@ -14,7 +14,7 @@ import 'cells/url_card_cell.dart';
|
|||||||
|
|
||||||
// T represents as the Generic card data
|
// T represents as the Generic card data
|
||||||
class CardCellBuilder<CustomCardData> {
|
class CardCellBuilder<CustomCardData> {
|
||||||
final CellCache cellCache;
|
final CellMemCache cellCache;
|
||||||
final Map<FieldType, CardCellStyle>? styles;
|
final Map<FieldType, CardCellStyle>? styles;
|
||||||
|
|
||||||
CardCellBuilder(this.cellCache, {this.styles});
|
CardCellBuilder(this.cellCache, {this.styles});
|
||||||
|
@ -16,7 +16,7 @@ import 'cells/url_cell/url_cell.dart';
|
|||||||
|
|
||||||
/// Build the cell widget in Grid style.
|
/// Build the cell widget in Grid style.
|
||||||
class GridCellBuilder {
|
class GridCellBuilder {
|
||||||
final CellCache cellCache;
|
final CellMemCache cellCache;
|
||||||
GridCellBuilder({
|
GridCellBuilder({
|
||||||
required this.cellCache,
|
required this.cellCache,
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
|
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
@ -10,10 +10,11 @@ import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
|||||||
import 'package:appflowy/plugins/document/presentation/export_page_widget.dart';
|
import 'package:appflowy/plugins/document/presentation/export_page_widget.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/util/base64_string.dart';
|
import 'package:appflowy/util/base64_string.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart'
|
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart'
|
||||||
hide DocumentEvent;
|
hide DocumentEvent;
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -66,10 +67,13 @@ class _DocumentPageState extends State<DocumentPage> {
|
|||||||
return state.loadingState.when(
|
return state.loadingState.when(
|
||||||
loading: () => const SizedBox.shrink(),
|
loading: () => const SizedBox.shrink(),
|
||||||
finish: (result) => result.fold(
|
finish: (result) => result.fold(
|
||||||
(error) => FlowyErrorPage.message(
|
(error) {
|
||||||
error.toString(),
|
Log.error(error);
|
||||||
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
return FlowyErrorPage.message(
|
||||||
),
|
error.toString(),
|
||||||
|
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||||
|
);
|
||||||
|
},
|
||||||
(data) {
|
(data) {
|
||||||
if (state.forceClose) {
|
if (state.forceClose) {
|
||||||
widget.onDeleted();
|
widget.onDeleted();
|
||||||
|
@ -38,6 +38,7 @@ AppFlowyEnv getAppFlowyEnv() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final supabaseConfig = SupabaseConfiguration(
|
final supabaseConfig = SupabaseConfiguration(
|
||||||
|
enable_sync: Env.enableSupabaseSync,
|
||||||
url: Env.supabaseUrl,
|
url: Env.supabaseUrl,
|
||||||
key: Env.supabaseKey,
|
key: Env.supabaseKey,
|
||||||
jwt_secret: Env.supabaseJwtSecret,
|
jwt_secret: Env.supabaseJwtSecret,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:appflowy/core/config/config.dart';
|
|
||||||
import 'package:appflowy/env/env.dart';
|
import 'package:appflowy/env/env.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
|
||||||
@ -22,16 +21,6 @@ class InitSupabaseTask extends LaunchTask {
|
|||||||
debug: false,
|
debug: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Config.setSupabaseConfig(
|
|
||||||
url: Env.supabaseUrl,
|
|
||||||
key: Env.supabaseKey,
|
|
||||||
secret: Env.supabaseJwtSecret,
|
|
||||||
anonKey: Env.supabaseAnonKey,
|
|
||||||
pgPassword: Env.supabaseDbPassword,
|
|
||||||
pgPort: Env.supabaseDbPort,
|
|
||||||
pgUrl: Env.supabaseDb,
|
|
||||||
pgUser: Env.supabaseDbUser,
|
|
||||||
);
|
|
||||||
isSupabaseInitialized = true;
|
isSupabaseInitialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,8 +51,7 @@ class AppFlowyAuthService implements AuthService {
|
|||||||
AuthTypePB authType = AuthTypePB.Local,
|
AuthTypePB authType = AuthTypePB.Local,
|
||||||
Map<String, String> map = const {},
|
Map<String, String> map = const {},
|
||||||
}) async {
|
}) async {
|
||||||
final payload = SignOutPB()..authType = authType;
|
await UserEventSignOut().send();
|
||||||
await UserEventSignOut(payload).send();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +60,7 @@ class AppFlowyAuthService implements AuthService {
|
|||||||
AuthTypePB authType = AuthTypePB.Local,
|
AuthTypePB authType = AuthTypePB.Local,
|
||||||
Map<String, String> map = const {},
|
Map<String, String> map = const {},
|
||||||
}) {
|
}) {
|
||||||
const password = "AppFlowy123@";
|
const password = "Guest!@123456";
|
||||||
final uid = uuid();
|
final uid = uuid();
|
||||||
final userEmail = "$uid@appflowy.io";
|
final userEmail = "$uid@appflowy.io";
|
||||||
return signUp(
|
return signUp(
|
||||||
|
@ -8,6 +8,7 @@ class AuthServiceMapKeys {
|
|||||||
|
|
||||||
// for supabase auth use only.
|
// for supabase auth use only.
|
||||||
static const String uuid = 'uuid';
|
static const String uuid = 'uuid';
|
||||||
|
static const String email = 'email';
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AuthService {
|
abstract class AuthService {
|
||||||
@ -42,9 +43,7 @@ abstract class AuthService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
///
|
///
|
||||||
Future<void> signOut({
|
Future<void> signOut();
|
||||||
AuthTypePB authType,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Returns [UserProfilePB] if the user has sign in, otherwise returns null.
|
/// Returns [UserProfilePB] if the user has sign in, otherwise returns null.
|
||||||
Future<Either<FlowyError, UserProfilePB>> getUser();
|
Future<Either<FlowyError, UserProfilePB>> getUser();
|
||||||
|
@ -115,7 +115,10 @@ class SupabaseAuthService implements AuthService {
|
|||||||
completer.complete(left(AuthError.supabaseSignInWithOauthError));
|
completer.complete(left(AuthError.supabaseSignInWithOauthError));
|
||||||
} else {
|
} else {
|
||||||
final Either<FlowyError, UserProfilePB> response = await setupAuth(
|
final Either<FlowyError, UserProfilePB> response = await setupAuth(
|
||||||
map: {AuthServiceMapKeys.uuid: user.id},
|
map: {
|
||||||
|
AuthServiceMapKeys.uuid: user.id,
|
||||||
|
AuthServiceMapKeys.email: user.email ?? user.newEmail ?? ''
|
||||||
|
},
|
||||||
);
|
);
|
||||||
completer.complete(response);
|
completer.complete(response);
|
||||||
}
|
}
|
||||||
|
@ -58,9 +58,8 @@ class UserBackendService {
|
|||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> signOut(AuthTypePB authType) {
|
Future<Either<Unit, FlowyError>> signOut() {
|
||||||
final payload = SignOutPB()..authType = authType;
|
return UserEventSignOut().send();
|
||||||
return UserEventSignOut(payload).send();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> initUser() async {
|
Future<Either<Unit, FlowyError>> initUser() async {
|
||||||
|
@ -85,9 +85,9 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
|||||||
|
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
Future<void> _fetchApps(Emitter<MenuState> emit) async {
|
Future<void> _fetchApps(Emitter<MenuState> emit) async {
|
||||||
final appsOrFail = await _workspaceService.getViews();
|
final viewsOrError = await _workspaceService.getViews();
|
||||||
emit(
|
emit(
|
||||||
appsOrFail.fold(
|
viewsOrError.fold(
|
||||||
(views) => state.copyWith(views: views),
|
(views) => state.copyWith(views: views),
|
||||||
(error) {
|
(error) {
|
||||||
Log.error(error);
|
Log.error(error);
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
|
part 'setting_supabase_bloc.freezed.dart';
|
||||||
|
|
||||||
|
class SettingSupabaseBloc
|
||||||
|
extends Bloc<SettingSupabaseEvent, SettingSupabaseState> {
|
||||||
|
SettingSupabaseBloc() : super(SettingSupabaseState.initial()) {
|
||||||
|
on<SettingSupabaseEvent>((event, emit) async {
|
||||||
|
await event.when(
|
||||||
|
initial: () async {
|
||||||
|
await getSupabaseConfig();
|
||||||
|
},
|
||||||
|
enableSync: (bool enable) async {
|
||||||
|
final oldConfig = state.config;
|
||||||
|
if (oldConfig != null) {
|
||||||
|
oldConfig.freeze();
|
||||||
|
final newConfig = oldConfig.rebuild((config) {
|
||||||
|
config.enableSync = enable;
|
||||||
|
});
|
||||||
|
updateSupabaseConfig(newConfig);
|
||||||
|
emit(state.copyWith(config: newConfig));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
didReceiveSupabseConfig: (SupabaseConfigPB config) {
|
||||||
|
emit(state.copyWith(config: config));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateSupabaseConfig(SupabaseConfigPB config) async {
|
||||||
|
await UserEventSetSupabaseConfig(config).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getSupabaseConfig() async {
|
||||||
|
final result = await UserEventGetSupabaseConfig().send();
|
||||||
|
result.fold(
|
||||||
|
(config) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(SettingSupabaseEvent.didReceiveSupabseConfig(config));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(r) => Log.error(r),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SettingSupabaseEvent with _$SettingSupabaseEvent {
|
||||||
|
const factory SettingSupabaseEvent.initial() = _Initial;
|
||||||
|
const factory SettingSupabaseEvent.didReceiveSupabseConfig(
|
||||||
|
SupabaseConfigPB config,
|
||||||
|
) = _DidReceiveSupabaseConfig;
|
||||||
|
const factory SettingSupabaseEvent.enableSync(bool enable) = _EnableSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SettingSupabaseState with _$SettingSupabaseState {
|
||||||
|
const factory SettingSupabaseState({
|
||||||
|
SupabaseConfigPB? config,
|
||||||
|
required Either<Unit, String> successOrFailure,
|
||||||
|
}) = _SettingSupabaseState;
|
||||||
|
|
||||||
|
factory SettingSupabaseState.initial() => SettingSupabaseState(
|
||||||
|
successOrFailure: left(unit),
|
||||||
|
);
|
||||||
|
}
|
@ -13,6 +13,7 @@ enum SettingsPage {
|
|||||||
language,
|
language,
|
||||||
files,
|
files,
|
||||||
user,
|
user,
|
||||||
|
supabaseSetting,
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsDialogBloc
|
class SettingsDialogBloc
|
||||||
|
@ -53,7 +53,7 @@ class WorkspaceService {
|
|||||||
final payload = WorkspaceIdPB.create()..value = workspaceId;
|
final payload = WorkspaceIdPB.create()..value = workspaceId;
|
||||||
return FolderEventReadWorkspaceViews(payload).send().then((result) {
|
return FolderEventReadWorkspaceViews(payload).send().then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(apps) => left(apps.items),
|
(views) => left(views.items),
|
||||||
(error) => right(error),
|
(error) => right(error),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_view.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_view.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_view.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_system_view.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_system_view.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
||||||
@ -85,6 +86,8 @@ class SettingsDialog extends StatelessWidget {
|
|||||||
return const SettingsFileSystemView();
|
return const SettingsFileSystemView();
|
||||||
case SettingsPage.user:
|
case SettingsPage.user:
|
||||||
return SettingsUserView(user);
|
return SettingsUserView(user);
|
||||||
|
case SettingsPage.supabaseSetting:
|
||||||
|
return const SupabaseSettingView();
|
||||||
default:
|
default:
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import 'package:appflowy/workspace/application/settings/setting_supabase_bloc.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class SupabaseSettingView extends StatelessWidget {
|
||||||
|
const SupabaseSettingView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) =>
|
||||||
|
SettingSupabaseBloc()..add(const SettingSupabaseEvent.initial()),
|
||||||
|
child: BlocBuilder<SettingSupabaseBloc, SettingSupabaseState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: Switch(
|
||||||
|
onChanged: (bool value) {
|
||||||
|
context.read<SettingSupabaseBloc>().add(
|
||||||
|
SettingSupabaseEvent.enableSync(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
value: state.config?.enableSync ?? false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||||
|
import 'package:appflowy/user/presentation/sign_in_screen.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class SettingThirdPartyLogin extends StatelessWidget {
|
||||||
|
const SettingThirdPartyLogin({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => getIt<SignInBloc>(),
|
||||||
|
child: BlocConsumer<SignInBloc, SignInState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
state.successOrFail.fold(
|
||||||
|
() => null,
|
||||||
|
(result) => _handleSuccessOrFail(result, context),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
builder: (_, __) => const ThirdPartySignInButtons(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleSuccessOrFail(
|
||||||
|
Either<UserProfilePB, FlowyError> result,
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
result.fold(
|
||||||
|
(user) {
|
||||||
|
// TODO(Lucas): push to home screen
|
||||||
|
},
|
||||||
|
(error) => showSnapBar(context, error.msg),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,11 @@
|
|||||||
|
import 'package:appflowy/env/env.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/auth.pbenum.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class SettingsMenu extends StatelessWidget {
|
class SettingsMenu extends StatelessWidget {
|
||||||
const SettingsMenu({
|
const SettingsMenu({
|
||||||
@ -55,6 +58,18 @@ class SettingsMenu extends StatelessWidget {
|
|||||||
icon: Icons.account_box_outlined,
|
icon: Icons.account_box_outlined,
|
||||||
changeSelectedPage: changeSelectedPage,
|
changeSelectedPage: changeSelectedPage,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Only show supabase setting if supabase is enabled and the current auth type is not local
|
||||||
|
if (isSupabaseEnable &&
|
||||||
|
context.read<SettingsDialogBloc>().state.userProfile.authType !=
|
||||||
|
AuthTypePB.Local)
|
||||||
|
SettingsMenuElement(
|
||||||
|
page: SettingsPage.supabaseSetting,
|
||||||
|
selectedPage: currentPage,
|
||||||
|
label: LocaleKeys.settings_menu_supabaseSetting.tr(),
|
||||||
|
icon: Icons.sync,
|
||||||
|
changeSelectedPage: changeSelectedPage,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:appflowy/env/env.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/startup/entry_point.dart';
|
import 'package:appflowy/startup/entry_point.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
@ -15,6 +16,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'setting_third_party_login.dart';
|
||||||
|
|
||||||
const defaultUserAvatar = '1F600';
|
const defaultUserAvatar = '1F600';
|
||||||
const _iconSize = Size(60, 60);
|
const _iconSize = Size(60, 60);
|
||||||
|
|
||||||
@ -38,7 +41,7 @@ class SettingsUserView extends StatelessWidget {
|
|||||||
const VSpace(20),
|
const VSpace(20),
|
||||||
_renderCurrentOpenaiKey(context),
|
_renderCurrentOpenaiKey(context),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
_renderLogoutButton(context),
|
_renderLoginOrLogoutButton(context, state),
|
||||||
const VSpace(20),
|
const VSpace(20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -46,6 +49,21 @@ class SettingsUserView extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _renderLoginOrLogoutButton(
|
||||||
|
BuildContext context,
|
||||||
|
SettingsUserState state,
|
||||||
|
) {
|
||||||
|
if (!isSupabaseEnable) {
|
||||||
|
return _renderLogoutButton(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.userProfile.authType == AuthTypePB.Local) {
|
||||||
|
return const SettingThirdPartyLogin();
|
||||||
|
} else {
|
||||||
|
return _renderLogoutButton(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _renderUserNameInput(BuildContext context) {
|
Widget _renderUserNameInput(BuildContext context) {
|
||||||
final String name =
|
final String name =
|
||||||
context.read<SettingsUserViewBloc>().state.userProfile.name;
|
context.read<SettingsUserViewBloc>().state.userProfile.name;
|
||||||
@ -74,8 +92,7 @@ class SettingsUserView extends StatelessWidget {
|
|||||||
'Logout',
|
'Logout',
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await getIt<AuthService>().signOut(authType: AuthTypePB.Supabase);
|
await getIt<AuthService>().signOut();
|
||||||
await getIt<AuthService>().signOut(authType: AuthTypePB.Local);
|
|
||||||
await FlowyRunner.run(
|
await FlowyRunner.run(
|
||||||
FlowyApp(),
|
FlowyApp(),
|
||||||
integrationEnv(),
|
integrationEnv(),
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
// Run `dart run build_runner build` to generate the json serialization
|
// Run `dart run build_runner build` to generate the json serialization If the
|
||||||
|
// file `env_serde.i.dart` is existed, delete it first.
|
||||||
|
//
|
||||||
// the file `env_serde.g.dart` will be generated in the same directory. Rename
|
// the file `env_serde.g.dart` will be generated in the same directory. Rename
|
||||||
// the file to `env_serde.i.dart` because the file is ignored by default.
|
// the file to `env_serde.i.dart` because the file is ignored by default.
|
||||||
part 'env_serde.i.dart';
|
part 'env_serde.i.dart';
|
||||||
@ -9,7 +11,9 @@ part 'env_serde.i.dart';
|
|||||||
class AppFlowyEnv {
|
class AppFlowyEnv {
|
||||||
final SupabaseConfiguration supabase_config;
|
final SupabaseConfiguration supabase_config;
|
||||||
|
|
||||||
AppFlowyEnv({required this.supabase_config});
|
AppFlowyEnv({
|
||||||
|
required this.supabase_config,
|
||||||
|
});
|
||||||
|
|
||||||
factory AppFlowyEnv.fromJson(Map<String, dynamic> json) =>
|
factory AppFlowyEnv.fromJson(Map<String, dynamic> json) =>
|
||||||
_$AppFlowyEnvFromJson(json);
|
_$AppFlowyEnvFromJson(json);
|
||||||
@ -19,12 +23,15 @@ class AppFlowyEnv {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class SupabaseConfiguration {
|
class SupabaseConfiguration {
|
||||||
|
/// Indicates whether the sync feature is enabled.
|
||||||
|
final bool enable_sync;
|
||||||
final String url;
|
final String url;
|
||||||
final String key;
|
final String key;
|
||||||
final String jwt_secret;
|
final String jwt_secret;
|
||||||
final PostgresConfiguration postgres_config;
|
final PostgresConfiguration postgres_config;
|
||||||
|
|
||||||
SupabaseConfiguration({
|
SupabaseConfiguration({
|
||||||
|
this.enable_sync = true,
|
||||||
required this.url,
|
required this.url,
|
||||||
required this.key,
|
required this.key,
|
||||||
required this.jwt_secret,
|
required this.jwt_secret,
|
||||||
|
@ -19,6 +19,7 @@ Map<String, dynamic> _$AppFlowyEnvToJson(AppFlowyEnv instance) =>
|
|||||||
SupabaseConfiguration _$SupabaseConfigurationFromJson(
|
SupabaseConfiguration _$SupabaseConfigurationFromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
SupabaseConfiguration(
|
SupabaseConfiguration(
|
||||||
|
enable_sync: json['enable_sync'] as bool? ?? true,
|
||||||
url: json['url'] as String,
|
url: json['url'] as String,
|
||||||
key: json['key'] as String,
|
key: json['key'] as String,
|
||||||
jwt_secret: json['jwt_secret'] as String,
|
jwt_secret: json['jwt_secret'] as String,
|
||||||
@ -29,6 +30,7 @@ SupabaseConfiguration _$SupabaseConfigurationFromJson(
|
|||||||
Map<String, dynamic> _$SupabaseConfigurationToJson(
|
Map<String, dynamic> _$SupabaseConfigurationToJson(
|
||||||
SupabaseConfiguration instance) =>
|
SupabaseConfiguration instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
|
'enable_sync': instance.enable_sync,
|
||||||
'url': instance.url,
|
'url': instance.url,
|
||||||
'key': instance.key,
|
'key': instance.key,
|
||||||
'jwt_secret': instance.jwt_secret,
|
'jwt_secret': instance.jwt_secret,
|
||||||
|
@ -5,7 +5,7 @@ import 'package:appflowy/plugins/database_view/application/field/field_editor_bl
|
|||||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/board/board.dart';
|
import 'package:appflowy/plugins/database_view/board/board.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
|
||||||
|
@ -6,7 +6,7 @@ import 'package:appflowy/plugins/database_view/application/field/field_service.d
|
|||||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
|
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
|
12
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
12
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -105,7 +105,6 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "appflowy-integrate"
|
name = "appflowy-integrate"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -1030,7 +1029,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -1048,7 +1046,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-client-ws"
|
name = "collab-client-ws"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"collab-sync",
|
"collab-sync",
|
||||||
@ -1066,7 +1063,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-database"
|
name = "collab-database"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1093,7 +1089,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-derive"
|
name = "collab-derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -1105,7 +1100,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-document"
|
name = "collab-document"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -1124,7 +1118,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-folder"
|
name = "collab-folder"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1144,7 +1137,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-persistence"
|
name = "collab-persistence"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1164,7 +1156,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-plugins"
|
name = "collab-plugins"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1198,7 +1189,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-sync"
|
name = "collab-sync"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"collab",
|
"collab",
|
||||||
@ -2072,6 +2062,8 @@ dependencies = [
|
|||||||
"async-stream",
|
"async-stream",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"collab-document",
|
||||||
|
"collab-folder",
|
||||||
"config",
|
"config",
|
||||||
"deadpool-postgres",
|
"deadpool-postgres",
|
||||||
"flowy-database2",
|
"flowy-database2",
|
||||||
|
@ -34,12 +34,12 @@ default = ["custom-protocol"]
|
|||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
|
|
||||||
#collab = { path = "../../AppFlowy-Collab/collab" }
|
#collab = { path = "../../AppFlowy-Collab/collab" }
|
||||||
#collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }
|
#collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }
|
||||||
|
@ -2,7 +2,6 @@ import { nanoid } from '@reduxjs/toolkit';
|
|||||||
import {
|
import {
|
||||||
AppearanceSettingsPB,
|
AppearanceSettingsPB,
|
||||||
AuthTypePB,
|
AuthTypePB,
|
||||||
SignOutPB,
|
|
||||||
ThemeModePB,
|
ThemeModePB,
|
||||||
UserEventGetAppearanceSetting,
|
UserEventGetAppearanceSetting,
|
||||||
UserEventGetUserProfile,
|
UserEventGetUserProfile,
|
||||||
@ -91,9 +90,7 @@ export class UserBackendService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
signOut = () => {
|
signOut = () => {
|
||||||
const payload = SignOutPB.fromObject({ auth_type: AuthTypePB.Local });
|
return UserEventSignOut();
|
||||||
|
|
||||||
return UserEventSignOut(payload);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setAppearanceSettings = (params: ReturnType<typeof AppearanceSettingsPB.prototype.toObject>) => {
|
setAppearanceSettings = (params: ReturnType<typeof AppearanceSettingsPB.prototype.toObject>) => {
|
||||||
@ -125,9 +122,7 @@ export class AuthBackendService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
signOut = () => {
|
signOut = () => {
|
||||||
const payload = SignOutPB.fromObject({ auth_type: AuthTypePB.Local });
|
return UserEventSignOut();
|
||||||
|
|
||||||
return UserEventSignOut(payload);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
autoSignUp = () => {
|
autoSignUp = () => {
|
||||||
|
8
frontend/flowy-server-config/Cargo.toml
Normal file
8
frontend/flowy-server-config/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "flowy-server-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
14
frontend/flowy-server-config/src/lib.rs
Normal file
14
frontend/flowy-server-config/src/lib.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
pub fn add(left: usize, right: usize) -> usize {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
@ -198,7 +198,8 @@
|
|||||||
"language": "Language",
|
"language": "Language",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"files": "Files",
|
"files": "Files",
|
||||||
"open": "Open Settings"
|
"open": "Open Settings",
|
||||||
|
"supabaseSetting": "Supabase Setting"
|
||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
"fontFamily": {
|
"fontFamily": {
|
||||||
|
39
frontend/rust-lib/Cargo.lock
generated
39
frontend/rust-lib/Cargo.lock
generated
@ -85,7 +85,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "appflowy-integrate"
|
name = "appflowy-integrate"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -897,7 +897,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -915,7 +915,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-client-ws"
|
name = "collab-client-ws"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"collab-sync",
|
"collab-sync",
|
||||||
@ -933,7 +933,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-database"
|
name = "collab-database"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -960,7 +960,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-derive"
|
name = "collab-derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -972,7 +972,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-document"
|
name = "collab-document"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -991,7 +991,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-folder"
|
name = "collab-folder"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1011,7 +1011,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-persistence"
|
name = "collab-persistence"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1031,7 +1031,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-plugins"
|
name = "collab-plugins"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1065,7 +1065,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-sync"
|
name = "collab-sync"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"collab",
|
"collab",
|
||||||
@ -1314,6 +1314,7 @@ dependencies = [
|
|||||||
"flowy-net",
|
"flowy-net",
|
||||||
"flowy-notification",
|
"flowy-notification",
|
||||||
"flowy-server",
|
"flowy-server",
|
||||||
|
"flowy-server-config",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
"log",
|
"log",
|
||||||
@ -1670,6 +1671,7 @@ dependencies = [
|
|||||||
"flowy-folder2",
|
"flowy-folder2",
|
||||||
"flowy-net",
|
"flowy-net",
|
||||||
"flowy-server",
|
"flowy-server",
|
||||||
|
"flowy-server-config",
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
"flowy-task",
|
"flowy-task",
|
||||||
"flowy-user",
|
"flowy-user",
|
||||||
@ -1868,6 +1870,8 @@ dependencies = [
|
|||||||
"async-stream",
|
"async-stream",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"collab-document",
|
||||||
|
"collab-folder",
|
||||||
"config",
|
"config",
|
||||||
"deadpool-postgres",
|
"deadpool-postgres",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
@ -1875,6 +1879,7 @@ dependencies = [
|
|||||||
"flowy-document2",
|
"flowy-document2",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
"flowy-folder2",
|
"flowy-folder2",
|
||||||
|
"flowy-server-config",
|
||||||
"flowy-user",
|
"flowy-user",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -1898,6 +1903,14 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flowy-server-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"flowy-error",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flowy-sqlite"
|
name = "flowy-sqlite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1952,6 +1965,7 @@ dependencies = [
|
|||||||
"flowy-net",
|
"flowy-net",
|
||||||
"flowy-notification",
|
"flowy-notification",
|
||||||
"flowy-server",
|
"flowy-server",
|
||||||
|
"flowy-server-config",
|
||||||
"flowy-user",
|
"flowy-user",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
@ -1965,6 +1979,7 @@ dependencies = [
|
|||||||
"tempdir",
|
"tempdir",
|
||||||
"thread-id",
|
"thread-id",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-postgres",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
@ -1975,6 +1990,8 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"appflowy-integrate",
|
"appflowy-integrate",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"collab",
|
||||||
|
"collab-folder",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel_derives",
|
"diesel_derives",
|
||||||
"fake",
|
"fake",
|
||||||
@ -1983,6 +2000,7 @@ dependencies = [
|
|||||||
"flowy-derive",
|
"flowy-derive",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
"flowy-notification",
|
"flowy-notification",
|
||||||
|
"flowy-server-config",
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
@ -2004,6 +2022,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
"uuid",
|
||||||
"validator",
|
"validator",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ members = [
|
|||||||
"flowy-database2",
|
"flowy-database2",
|
||||||
"flowy-task",
|
"flowy-task",
|
||||||
"flowy-server",
|
"flowy-server",
|
||||||
|
"flowy-server-config",
|
||||||
"flowy-config",
|
"flowy-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -33,11 +34,11 @@ opt-level = 3
|
|||||||
incremental = false
|
incremental = false
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
|
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
|
||||||
|
|
||||||
#collab = { path = "../AppFlowy-Collab/collab" }
|
#collab = { path = "../AppFlowy-Collab/collab" }
|
||||||
#collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
|
#collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
|
||||||
|
@ -32,6 +32,7 @@ flowy-notification = { path = "../flowy-notification" }
|
|||||||
flowy-net = { path = "../flowy-net" }
|
flowy-net = { path = "../flowy-net" }
|
||||||
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
|
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
|
||||||
flowy-server = { path = "../flowy-server" }
|
flowy-server = { path = "../flowy-server" }
|
||||||
|
flowy-server-config = { path = "../flowy-server-config" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["dart", "rev-sqlite"]
|
default = ["dart", "rev-sqlite"]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use flowy_server::supabase::SupabaseConfiguration;
|
use flowy_server_config::supabase_config::SupabaseConfiguration;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct AppFlowyEnv {
|
pub struct AppFlowyEnv {
|
||||||
@ -10,7 +10,6 @@ pub struct AppFlowyEnv {
|
|||||||
impl AppFlowyEnv {
|
impl AppFlowyEnv {
|
||||||
pub fn parser(env_str: &str) {
|
pub fn parser(env_str: &str) {
|
||||||
if let Ok(env) = serde_json::from_str::<AppFlowyEnv>(env_str) {
|
if let Ok(env) = serde_json::from_str::<AppFlowyEnv>(env_str) {
|
||||||
tracing::trace!("{:?}", env);
|
|
||||||
env.supabase_config.write_env();
|
env.supabase_config.write_env();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,6 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
|
|||||||
let config =
|
let config =
|
||||||
AppFlowyCoreConfig::new(path, DEFAULT_NAME.to_string()).log_filter("info", log_crates);
|
AppFlowyCoreConfig::new(path, DEFAULT_NAME.to_string()).log_filter("info", log_crates);
|
||||||
*APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config));
|
*APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config));
|
||||||
|
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ use appflowy_integrate::config::AWSDynamoDBConfig;
|
|||||||
|
|
||||||
use flowy_derive::ProtoBuf;
|
use flowy_derive::ProtoBuf;
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::FlowyError;
|
||||||
use flowy_server::supabase::{PostgresConfiguration, SupabaseConfiguration};
|
|
||||||
|
|
||||||
#[derive(Default, ProtoBuf)]
|
#[derive(Default, ProtoBuf)]
|
||||||
pub struct KeyValuePB {
|
pub struct KeyValuePB {
|
||||||
@ -19,38 +18,6 @@ pub struct KeyPB {
|
|||||||
pub key: String,
|
pub key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, ProtoBuf)]
|
|
||||||
pub struct SupabaseConfigPB {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
supabase_url: String,
|
|
||||||
|
|
||||||
#[pb(index = 2)]
|
|
||||||
anon_key: String,
|
|
||||||
|
|
||||||
#[pb(index = 3)]
|
|
||||||
key: String,
|
|
||||||
|
|
||||||
#[pb(index = 4)]
|
|
||||||
jwt_secret: String,
|
|
||||||
|
|
||||||
#[pb(index = 5)]
|
|
||||||
pub postgres_config: PostgresConfigurationPB,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<SupabaseConfigPB> for SupabaseConfiguration {
|
|
||||||
type Error = FlowyError;
|
|
||||||
|
|
||||||
fn try_from(config: SupabaseConfigPB) -> Result<Self, Self::Error> {
|
|
||||||
let postgres_config = PostgresConfiguration::try_from(config.postgres_config)?;
|
|
||||||
Ok(SupabaseConfiguration {
|
|
||||||
url: config.supabase_url,
|
|
||||||
key: config.key,
|
|
||||||
jwt_secret: config.jwt_secret,
|
|
||||||
postgres_config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, ProtoBuf)]
|
#[derive(Default, ProtoBuf)]
|
||||||
pub struct CollabPluginConfigPB {
|
pub struct CollabPluginConfigPB {
|
||||||
#[pb(index = 1, one_of)]
|
#[pb(index = 1, one_of)]
|
||||||
@ -81,31 +48,3 @@ impl TryFrom<AWSDynamoDBConfigPB> for AWSDynamoDBConfig {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, ProtoBuf)]
|
|
||||||
pub struct PostgresConfigurationPB {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
pub url: String,
|
|
||||||
|
|
||||||
#[pb(index = 2)]
|
|
||||||
pub user_name: String,
|
|
||||||
|
|
||||||
#[pb(index = 3)]
|
|
||||||
pub password: String,
|
|
||||||
|
|
||||||
#[pb(index = 4)]
|
|
||||||
pub port: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<PostgresConfigurationPB> for PostgresConfiguration {
|
|
||||||
type Error = FlowyError;
|
|
||||||
|
|
||||||
fn try_from(config: PostgresConfigurationPB) -> Result<Self, Self::Error> {
|
|
||||||
Ok(Self {
|
|
||||||
url: config.url,
|
|
||||||
user_name: config.user_name,
|
|
||||||
password: config.password,
|
|
||||||
port: config.port as u16,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use appflowy_integrate::config::AWSDynamoDBConfig;
|
use appflowy_integrate::config::AWSDynamoDBConfig;
|
||||||
|
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use flowy_server::supabase::SupabaseConfiguration;
|
|
||||||
use flowy_sqlite::kv::KV;
|
use flowy_sqlite::kv::KV;
|
||||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, DataResult};
|
use lib_dispatch::prelude::{data_result_ok, AFPluginData, DataResult};
|
||||||
|
|
||||||
use crate::entities::{CollabPluginConfigPB, KeyPB, KeyValuePB, SupabaseConfigPB};
|
use crate::entities::{CollabPluginConfigPB, KeyPB, KeyValuePB};
|
||||||
|
|
||||||
pub(crate) async fn set_key_value_handler(data: AFPluginData<KeyValuePB>) -> FlowyResult<()> {
|
pub(crate) async fn set_key_value_handler(data: AFPluginData<KeyValuePB>) -> FlowyResult<()> {
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
@ -35,14 +34,6 @@ pub(crate) async fn remove_key_value_handler(data: AFPluginData<KeyPB>) -> Flowy
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn set_supabase_config_handler(
|
|
||||||
data: AFPluginData<SupabaseConfigPB>,
|
|
||||||
) -> FlowyResult<()> {
|
|
||||||
let config = SupabaseConfiguration::try_from(data.into_inner())?;
|
|
||||||
config.write_env();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn set_collab_plugin_config_handler(
|
pub(crate) async fn set_collab_plugin_config_handler(
|
||||||
data: AFPluginData<CollabPluginConfigPB>,
|
data: AFPluginData<CollabPluginConfigPB>,
|
||||||
) -> FlowyResult<()> {
|
) -> FlowyResult<()> {
|
||||||
|
@ -11,7 +11,6 @@ pub fn init() -> AFPlugin {
|
|||||||
.event(ConfigEvent::SetKeyValue, set_key_value_handler)
|
.event(ConfigEvent::SetKeyValue, set_key_value_handler)
|
||||||
.event(ConfigEvent::GetKeyValue, get_key_value_handler)
|
.event(ConfigEvent::GetKeyValue, get_key_value_handler)
|
||||||
.event(ConfigEvent::RemoveKeyValue, remove_key_value_handler)
|
.event(ConfigEvent::RemoveKeyValue, remove_key_value_handler)
|
||||||
.event(ConfigEvent::SetSupabaseConfig, set_supabase_config_handler)
|
|
||||||
.event(
|
.event(
|
||||||
ConfigEvent::SetCollabPluginConfig,
|
ConfigEvent::SetCollabPluginConfig,
|
||||||
set_collab_plugin_config_handler,
|
set_collab_plugin_config_handler,
|
||||||
@ -30,11 +29,6 @@ pub enum ConfigEvent {
|
|||||||
#[event(input = "KeyPB")]
|
#[event(input = "KeyPB")]
|
||||||
RemoveKeyValue = 2,
|
RemoveKeyValue = 2,
|
||||||
|
|
||||||
/// Set the supabase config. It will be written to the environment variables.
|
|
||||||
/// Check out the `write_to_env` of [SupabaseConfigPB].
|
|
||||||
#[event(input = "SupabaseConfigPB")]
|
|
||||||
SetSupabaseConfig = 3,
|
|
||||||
|
|
||||||
#[event(input = "CollabPluginConfigPB")]
|
#[event(input = "CollabPluginConfigPB")]
|
||||||
SetCollabPluginConfig = 4,
|
SetCollabPluginConfig = 4,
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ flowy-document2 = { path = "../flowy-document2" }
|
|||||||
flowy-error = { path = "../flowy-error" }
|
flowy-error = { path = "../flowy-error" }
|
||||||
flowy-task = { path = "../flowy-task" }
|
flowy-task = { path = "../flowy-task" }
|
||||||
flowy-server = { path = "../flowy-server" }
|
flowy-server = { path = "../flowy-server" }
|
||||||
|
flowy-server-config = { path = "../flowy-server-config" }
|
||||||
flowy-config = { path = "../flowy-config" }
|
flowy-config = { path = "../flowy-config" }
|
||||||
appflowy-integrate = { version = "0.1.0" }
|
appflowy-integrate = { version = "0.1.0" }
|
||||||
diesel = { version = "1.4.8", features = ["sqlite"] }
|
diesel = { version = "1.4.8", features = ["sqlite"] }
|
||||||
|
@ -17,11 +17,11 @@ use lib_infra::util::timestamp;
|
|||||||
pub struct SnapshotDBImpl(pub Weak<UserSession>);
|
pub struct SnapshotDBImpl(pub Weak<UserSession>);
|
||||||
|
|
||||||
impl SnapshotPersistence for SnapshotDBImpl {
|
impl SnapshotPersistence for SnapshotDBImpl {
|
||||||
fn get_snapshots(&self, _uid: i64, object_id: &str) -> Vec<CollabSnapshot> {
|
fn get_snapshots(&self, uid: i64, object_id: &str) -> Vec<CollabSnapshot> {
|
||||||
match self.0.upgrade() {
|
match self.0.upgrade() {
|
||||||
None => vec![],
|
None => vec![],
|
||||||
Some(user_session) => user_session
|
Some(user_session) => user_session
|
||||||
.db_pool()
|
.db_pool(uid)
|
||||||
.and_then(|pool| Ok(pool.get()?))
|
.and_then(|pool| Ok(pool.get()?))
|
||||||
.and_then(|conn| {
|
.and_then(|conn| {
|
||||||
CollabSnapshotTableSql::get_all_snapshots(object_id, &conn)
|
CollabSnapshotTableSql::get_all_snapshots(object_id, &conn)
|
||||||
@ -43,7 +43,7 @@ impl SnapshotPersistence for SnapshotDBImpl {
|
|||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
if let Some(pool) = weak_user_session
|
if let Some(pool) = weak_user_session
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.and_then(|user_session| user_session.db_pool().ok())
|
.and_then(|user_session| user_session.db_pool(uid).ok())
|
||||||
{
|
{
|
||||||
let conn = pool
|
let conn = pool
|
||||||
.get()
|
.get()
|
||||||
|
@ -47,11 +47,11 @@ impl DatabaseUser2 for DatabaseUserImpl {
|
|||||||
.token()
|
.token()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError> {
|
fn collab_db(&self, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError> {
|
||||||
self
|
self
|
||||||
.0
|
.0
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||||
.get_collab_db()
|
.get_collab_db(uid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,11 +44,11 @@ impl DocumentUser for DocumentUserImpl {
|
|||||||
.token()
|
.token()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError> {
|
fn collab_db(&self, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError> {
|
||||||
self
|
self
|
||||||
.0
|
.0
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||||
.get_collab_db()
|
.get_collab_db(uid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,12 +81,12 @@ impl FolderUser for FolderUserImpl {
|
|||||||
.token()
|
.token()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError> {
|
fn collab_db(&self, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError> {
|
||||||
self
|
self
|
||||||
.0
|
.0
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||||
.get_collab_db()
|
.get_collab_db(uid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
|
|||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
match manager.delete_document(&view_id) {
|
match manager.delete_document(&view_id) {
|
||||||
Ok(_) => tracing::trace!("Delete document: {}", view_id),
|
Ok(_) => tracing::trace!("Delete document: {}", view_id),
|
||||||
Err(e) => tracing::error!("Failed to delete document: {}", e),
|
Err(e) => tracing::error!("🔴delete document failed: {}", e),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@ -149,7 +149,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
|
|||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
let view_id = view_id.to_string();
|
let view_id = view_id.to_string();
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
let data: DocumentDataPB = manager.get_document_data(&view_id)?.into();
|
let data: DocumentDataPB = manager.get_document_data(&view_id).await?.into();
|
||||||
let data_bytes = data.into_bytes().map_err(|_| FlowyError::invalid_data())?;
|
let data_bytes = data.into_bytes().map_err(|_| FlowyError::invalid_data())?;
|
||||||
Ok(data_bytes)
|
Ok(data_bytes)
|
||||||
})
|
})
|
||||||
@ -235,7 +235,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
|
|||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
match database_manager.delete_database_view(&view_id).await {
|
match database_manager.delete_database_view(&view_id).await {
|
||||||
Ok(_) => tracing::trace!("Delete database view: {}", view_id),
|
Ok(_) => tracing::trace!("Delete database view: {}", view_id),
|
||||||
Err(e) => tracing::error!("Failed to delete database: {}", e),
|
Err(e) => tracing::error!("🔴delete database failed: {}", e),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
@ -9,3 +9,4 @@ mod folder2_deps;
|
|||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
mod database_deps;
|
mod database_deps;
|
||||||
|
mod user_deps;
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
|
@ -6,20 +6,25 @@ use appflowy_integrate::RemoteCollabStorage;
|
|||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use serde_repr::*;
|
use serde_repr::*;
|
||||||
|
|
||||||
use flowy_database2::deps::{DatabaseCloudService, DatabaseSnapshot};
|
use flowy_database2::deps::{
|
||||||
use flowy_document2::deps::{DocumentCloudService, DocumentSnapshot};
|
CollabObjectUpdate, CollabObjectUpdateByOid, DatabaseCloudService, DatabaseSnapshot,
|
||||||
|
};
|
||||||
|
use flowy_document2::deps::{DocumentCloudService, DocumentData, DocumentSnapshot};
|
||||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||||
use flowy_folder2::deps::{FolderCloudService, FolderSnapshot, Workspace};
|
use flowy_folder2::deps::{FolderCloudService, FolderData, FolderSnapshot, Workspace};
|
||||||
use flowy_server::local_server::LocalServer;
|
use flowy_server::local_server::LocalServer;
|
||||||
use flowy_server::self_host::configuration::self_host_server_configuration;
|
use flowy_server::self_host::configuration::self_host_server_configuration;
|
||||||
use flowy_server::self_host::SelfHostServer;
|
use flowy_server::self_host::SelfHostServer;
|
||||||
use flowy_server::supabase::{SupabaseConfiguration, SupabaseServer};
|
use flowy_server::supabase::SupabaseServer;
|
||||||
use flowy_server::AppFlowyServer;
|
use flowy_server::AppFlowyServer;
|
||||||
|
use flowy_server_config::supabase_config::SupabaseConfiguration;
|
||||||
use flowy_sqlite::kv::KV;
|
use flowy_sqlite::kv::KV;
|
||||||
use flowy_user::event_map::{UserAuthService, UserCloudServiceProvider};
|
use flowy_user::event_map::{UserAuthService, UserCloudServiceProvider};
|
||||||
use flowy_user::services::AuthType;
|
use flowy_user::services::AuthType;
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
|
|
||||||
|
use crate::AppFlowyCoreConfig;
|
||||||
|
|
||||||
const SERVER_PROVIDER_TYPE_KEY: &str = "server_provider_type";
|
const SERVER_PROVIDER_TYPE_KEY: &str = "server_provider_type";
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
|
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
|
||||||
@ -42,13 +47,20 @@ pub enum ServerProviderType {
|
|||||||
/// exist.
|
/// exist.
|
||||||
/// Each server implements the [AppFlowyServer] trait, which provides the [UserAuthService], etc.
|
/// Each server implements the [AppFlowyServer] trait, which provides the [UserAuthService], etc.
|
||||||
pub struct AppFlowyServerProvider {
|
pub struct AppFlowyServerProvider {
|
||||||
|
config: AppFlowyCoreConfig,
|
||||||
provider_type: RwLock<ServerProviderType>,
|
provider_type: RwLock<ServerProviderType>,
|
||||||
providers: RwLock<HashMap<ServerProviderType, Arc<dyn AppFlowyServer>>>,
|
providers: RwLock<HashMap<ServerProviderType, Arc<dyn AppFlowyServer>>>,
|
||||||
|
supabase_config: RwLock<Option<SupabaseConfiguration>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppFlowyServerProvider {
|
impl AppFlowyServerProvider {
|
||||||
pub fn new() -> Self {
|
pub fn new(config: AppFlowyCoreConfig, supabase_config: Option<SupabaseConfiguration>) -> Self {
|
||||||
Self::default()
|
Self {
|
||||||
|
config,
|
||||||
|
provider_type: RwLock::new(current_server_provider()),
|
||||||
|
providers: RwLock::new(HashMap::new()),
|
||||||
|
supabase_config: RwLock::new(supabase_config),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn provider_type(&self) -> ServerProviderType {
|
pub fn provider_type(&self) -> ServerProviderType {
|
||||||
@ -64,7 +76,33 @@ impl AppFlowyServerProvider {
|
|||||||
return Ok(provider.clone());
|
return Ok(provider.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let server = server_from_auth_type(provider_type)?;
|
let server = match provider_type {
|
||||||
|
ServerProviderType::Local => {
|
||||||
|
let server = Arc::new(LocalServer::new(&self.config.storage_path));
|
||||||
|
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
|
||||||
|
},
|
||||||
|
ServerProviderType::SelfHosted => {
|
||||||
|
let config = self_host_server_configuration().map_err(|e| {
|
||||||
|
FlowyError::new(
|
||||||
|
ErrorCode::InvalidAuthConfig,
|
||||||
|
format!(
|
||||||
|
"Missing self host config: {:?}. Error: {:?}",
|
||||||
|
provider_type, e
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let server = Arc::new(SelfHostServer::new(config));
|
||||||
|
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
|
||||||
|
},
|
||||||
|
ServerProviderType::Supabase => {
|
||||||
|
let config = self.supabase_config.read().clone().ok_or(FlowyError::new(
|
||||||
|
ErrorCode::InvalidAuthConfig,
|
||||||
|
"Missing supabase config".to_string(),
|
||||||
|
))?;
|
||||||
|
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(Arc::new(SupabaseServer::new(config)))
|
||||||
|
},
|
||||||
|
}?;
|
||||||
|
|
||||||
self
|
self
|
||||||
.providers
|
.providers
|
||||||
.write()
|
.write()
|
||||||
@ -73,16 +111,19 @@ impl AppFlowyServerProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppFlowyServerProvider {
|
impl UserCloudServiceProvider for AppFlowyServerProvider {
|
||||||
fn default() -> Self {
|
fn update_supabase_config(&self, supabase_config: &SupabaseConfiguration) {
|
||||||
Self {
|
self
|
||||||
provider_type: RwLock::new(current_server_provider()),
|
.supabase_config
|
||||||
providers: RwLock::new(HashMap::new()),
|
.write()
|
||||||
|
.replace(supabase_config.clone());
|
||||||
|
|
||||||
|
supabase_config.write_env();
|
||||||
|
if let Ok(provider) = self.get_provider(&self.provider_type.read()) {
|
||||||
|
provider.enable_sync(supabase_config.enable_sync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl UserCloudServiceProvider for AppFlowyServerProvider {
|
|
||||||
/// When user login, the provider type is set by the [AuthType] and save to disk for next use.
|
/// When user login, the provider type is set by the [AuthType] and save to disk for next use.
|
||||||
///
|
///
|
||||||
/// Each [AuthType] has a corresponding [ServerProviderType]. The [ServerProviderType] is used
|
/// Each [AuthType] has a corresponding [ServerProviderType]. The [ServerProviderType] is used
|
||||||
@ -119,6 +160,17 @@ impl FolderCloudService for AppFlowyServerProvider {
|
|||||||
FutureResult::new(async move { server?.folder_service().create_workspace(uid, &name).await })
|
FutureResult::new(async move { server?.folder_service().create_workspace(uid, &name).await })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_folder_data(&self, workspace_id: &str) -> FutureResult<Option<FolderData>, FlowyError> {
|
||||||
|
let server = self.get_provider(&self.provider_type.read());
|
||||||
|
let workspace_id = workspace_id.to_string();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
server?
|
||||||
|
.folder_service()
|
||||||
|
.get_folder_data(&workspace_id)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn get_folder_latest_snapshot(
|
fn get_folder_latest_snapshot(
|
||||||
&self,
|
&self,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
@ -133,40 +185,64 @@ impl FolderCloudService for AppFlowyServerProvider {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_folder_updates(&self, workspace_id: &str) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
|
fn get_folder_updates(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
uid: i64,
|
||||||
|
) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
|
||||||
let workspace_id = workspace_id.to_string();
|
let workspace_id = workspace_id.to_string();
|
||||||
let server = self.get_provider(&self.provider_type.read());
|
let server = self.get_provider(&self.provider_type.read());
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
server?
|
server?
|
||||||
.folder_service()
|
.folder_service()
|
||||||
.get_folder_updates(&workspace_id)
|
.get_folder_updates(&workspace_id, uid)
|
||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn service_name(&self) -> String {
|
||||||
|
self
|
||||||
|
.get_provider(&self.provider_type.read())
|
||||||
|
.map(|provider| provider.folder_service().service_name())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseCloudService for AppFlowyServerProvider {
|
impl DatabaseCloudService for AppFlowyServerProvider {
|
||||||
fn get_database_updates(&self, database_id: &str) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
|
fn get_collab_update(&self, object_id: &str) -> FutureResult<CollabObjectUpdate, FlowyError> {
|
||||||
let server = self.get_provider(&self.provider_type.read());
|
let server = self.get_provider(&self.provider_type.read());
|
||||||
let database_id = database_id.to_string();
|
let database_id = object_id.to_string();
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
server?
|
server?
|
||||||
.database_service()
|
.database_service()
|
||||||
.get_database_updates(&database_id)
|
.get_collab_update(&database_id)
|
||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_database_latest_snapshot(
|
fn batch_get_collab_updates(
|
||||||
&self,
|
&self,
|
||||||
database_id: &str,
|
object_ids: Vec<String>,
|
||||||
) -> FutureResult<Option<DatabaseSnapshot>, FlowyError> {
|
) -> FutureResult<CollabObjectUpdateByOid, FlowyError> {
|
||||||
let server = self.get_provider(&self.provider_type.read());
|
let server = self.get_provider(&self.provider_type.read());
|
||||||
let database_id = database_id.to_string();
|
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
server?
|
server?
|
||||||
.database_service()
|
.database_service()
|
||||||
.get_database_latest_snapshot(&database_id)
|
.batch_get_collab_updates(object_ids)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_collab_latest_snapshot(
|
||||||
|
&self,
|
||||||
|
object_id: &str,
|
||||||
|
) -> FutureResult<Option<DatabaseSnapshot>, FlowyError> {
|
||||||
|
let server = self.get_provider(&self.provider_type.read());
|
||||||
|
let database_id = object_id.to_string();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
server?
|
||||||
|
.database_service()
|
||||||
|
.get_collab_latest_snapshot(&database_id)
|
||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -197,6 +273,17 @@ impl DocumentCloudService for AppFlowyServerProvider {
|
|||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_document_data(&self, document_id: &str) -> FutureResult<Option<DocumentData>, FlowyError> {
|
||||||
|
let server = self.get_provider(&self.provider_type.read());
|
||||||
|
let document_id = document_id.to_string();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
server?
|
||||||
|
.document_service()
|
||||||
|
.get_document_data(&document_id)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CollabStorageProvider for AppFlowyServerProvider {
|
impl CollabStorageProvider for AppFlowyServerProvider {
|
||||||
@ -214,30 +301,14 @@ impl CollabStorageProvider for AppFlowyServerProvider {
|
|||||||
.and_then(|provider| provider.collab_storage()),
|
.and_then(|provider| provider.collab_storage()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn server_from_auth_type(
|
fn is_sync_enabled(&self) -> bool {
|
||||||
provider: &ServerProviderType,
|
self
|
||||||
) -> Result<Arc<dyn AppFlowyServer>, FlowyError> {
|
.supabase_config
|
||||||
match provider {
|
.read()
|
||||||
ServerProviderType::Local => {
|
.as_ref()
|
||||||
let server = Arc::new(LocalServer::new());
|
.map(|config| config.enable_sync)
|
||||||
Ok(server)
|
.unwrap_or(false)
|
||||||
},
|
|
||||||
ServerProviderType::SelfHosted => {
|
|
||||||
let config = self_host_server_configuration().map_err(|e| {
|
|
||||||
FlowyError::new(
|
|
||||||
ErrorCode::InvalidAuthConfig,
|
|
||||||
format!("Missing self host config: {:?}. Error: {:?}", provider, e),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let server = Arc::new(SelfHostServer::new(config));
|
|
||||||
Ok(server)
|
|
||||||
},
|
|
||||||
ServerProviderType::Supabase => {
|
|
||||||
let config = SupabaseConfiguration::from_env()?;
|
|
||||||
Ok(Arc::new(SupabaseServer::new(config)))
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,12 +16,12 @@ use tracing::debug;
|
|||||||
use flowy_database2::DatabaseManager2;
|
use flowy_database2::DatabaseManager2;
|
||||||
use flowy_document2::manager::DocumentManager as DocumentManager2;
|
use flowy_document2::manager::DocumentManager as DocumentManager2;
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
use flowy_folder2::manager::FolderManager;
|
use flowy_folder2::manager::{FolderInitializeData, FolderManager};
|
||||||
use flowy_sqlite::kv::KV;
|
use flowy_sqlite::kv::KV;
|
||||||
use flowy_task::{TaskDispatcher, TaskRunner};
|
use flowy_task::{TaskDispatcher, TaskRunner};
|
||||||
use flowy_user::entities::UserProfile;
|
use flowy_user::entities::UserProfile;
|
||||||
use flowy_user::event_map::{UserCloudServiceProvider, UserStatusCallback};
|
use flowy_user::event_map::{SignUpContext, UserCloudServiceProvider, UserStatusCallback};
|
||||||
use flowy_user::services::{AuthType, UserSession, UserSessionConfig};
|
use flowy_user::services::{get_supabase_config, AuthType, UserSession, UserSessionConfig};
|
||||||
use lib_dispatch::prelude::*;
|
use lib_dispatch::prelude::*;
|
||||||
use lib_dispatch::runtime::tokio_default_runtime;
|
use lib_dispatch::runtime::tokio_default_runtime;
|
||||||
use lib_infra::future::{to_fut, Fut};
|
use lib_infra::future::{to_fut, Fut};
|
||||||
@ -141,7 +141,10 @@ impl AppFlowyCore {
|
|||||||
let task_dispatcher = Arc::new(RwLock::new(task_scheduler));
|
let task_dispatcher = Arc::new(RwLock::new(task_scheduler));
|
||||||
runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
|
runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
|
||||||
|
|
||||||
let server_provider = Arc::new(AppFlowyServerProvider::new());
|
let server_provider = Arc::new(AppFlowyServerProvider::new(
|
||||||
|
config.clone(),
|
||||||
|
get_supabase_config(),
|
||||||
|
));
|
||||||
|
|
||||||
let (user_session, folder_manager, server_provider, database_manager, document_manager2) =
|
let (user_session, folder_manager, server_provider, database_manager, document_manager2) =
|
||||||
runtime.block_on(async {
|
runtime.block_on(async {
|
||||||
@ -260,6 +263,21 @@ struct UserStatusCallbackImpl {
|
|||||||
impl UserStatusCallback for UserStatusCallbackImpl {
|
impl UserStatusCallback for UserStatusCallbackImpl {
|
||||||
fn auth_type_did_changed(&self, _auth_type: AuthType) {}
|
fn auth_type_did_changed(&self, _auth_type: AuthType) {}
|
||||||
|
|
||||||
|
fn did_init(&self, user_id: i64, workspace_id: &str) -> Fut<FlowyResult<()>> {
|
||||||
|
let user_id = user_id.to_owned();
|
||||||
|
let workspace_id = workspace_id.to_owned();
|
||||||
|
let folder_manager = self.folder_manager.clone();
|
||||||
|
let database_manager = self.database_manager.clone();
|
||||||
|
|
||||||
|
to_fut(async move {
|
||||||
|
folder_manager
|
||||||
|
.initialize(user_id, &workspace_id, FolderInitializeData::Empty)
|
||||||
|
.await?;
|
||||||
|
database_manager.initialize(user_id).await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn did_sign_in(&self, user_id: i64, workspace_id: &str) -> Fut<FlowyResult<()>> {
|
fn did_sign_in(&self, user_id: i64, workspace_id: &str) -> Fut<FlowyResult<()>> {
|
||||||
let user_id = user_id.to_owned();
|
let user_id = user_id.to_owned();
|
||||||
let workspace_id = workspace_id.to_owned();
|
let workspace_id = workspace_id.to_owned();
|
||||||
@ -267,22 +285,29 @@ impl UserStatusCallback for UserStatusCallbackImpl {
|
|||||||
let database_manager = self.database_manager.clone();
|
let database_manager = self.database_manager.clone();
|
||||||
|
|
||||||
to_fut(async move {
|
to_fut(async move {
|
||||||
folder_manager.initialize(user_id, &workspace_id).await?;
|
folder_manager
|
||||||
|
.initialize_when_sign_in(user_id, &workspace_id)
|
||||||
|
.await?;
|
||||||
database_manager.initialize(user_id).await?;
|
database_manager.initialize(user_id).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn did_sign_up(&self, is_new: bool, user_profile: &UserProfile) -> Fut<FlowyResult<()>> {
|
fn did_sign_up(
|
||||||
|
&self,
|
||||||
|
context: SignUpContext,
|
||||||
|
user_profile: &UserProfile,
|
||||||
|
) -> Fut<FlowyResult<()>> {
|
||||||
let user_profile = user_profile.clone();
|
let user_profile = user_profile.clone();
|
||||||
let folder_manager = self.folder_manager.clone();
|
let folder_manager = self.folder_manager.clone();
|
||||||
let database_manager = self.database_manager.clone();
|
let database_manager = self.database_manager.clone();
|
||||||
to_fut(async move {
|
to_fut(async move {
|
||||||
folder_manager
|
folder_manager
|
||||||
.initialize_with_new_user(
|
.initialize_when_sign_up(
|
||||||
user_profile.id,
|
user_profile.id,
|
||||||
&user_profile.token,
|
&user_profile.token,
|
||||||
is_new,
|
context.is_new,
|
||||||
|
context.local_folder,
|
||||||
&user_profile.workspace_id,
|
&user_profile.workspace_id,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use appflowy_integrate::RocksCollabDB;
|
use appflowy_integrate::RocksCollabDB;
|
||||||
|
pub use collab_database::user::CollabObjectUpdate;
|
||||||
|
pub use collab_database::user::CollabObjectUpdateByOid;
|
||||||
|
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::FlowyError;
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
@ -8,18 +10,23 @@ use lib_infra::future::FutureResult;
|
|||||||
pub trait DatabaseUser2: Send + Sync {
|
pub trait DatabaseUser2: Send + Sync {
|
||||||
fn user_id(&self) -> Result<i64, FlowyError>;
|
fn user_id(&self) -> Result<i64, FlowyError>;
|
||||||
fn token(&self) -> Result<Option<String>, FlowyError>;
|
fn token(&self) -> Result<Option<String>, FlowyError>;
|
||||||
fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
|
fn collab_db(&self, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for database cloud service.
|
/// A trait for database cloud service.
|
||||||
/// Each kind of server should implement this trait. Check out the [AppFlowyServerProvider] of
|
/// Each kind of server should implement this trait. Check out the [AppFlowyServerProvider] of
|
||||||
/// [flowy-server] crate for more information.
|
/// [flowy-server] crate for more information.
|
||||||
pub trait DatabaseCloudService: Send + Sync {
|
pub trait DatabaseCloudService: Send + Sync {
|
||||||
fn get_database_updates(&self, database_id: &str) -> FutureResult<Vec<Vec<u8>>, FlowyError>;
|
fn get_collab_update(&self, object_id: &str) -> FutureResult<CollabObjectUpdate, FlowyError>;
|
||||||
|
|
||||||
fn get_database_latest_snapshot(
|
fn batch_get_collab_updates(
|
||||||
&self,
|
&self,
|
||||||
database_id: &str,
|
object_ids: Vec<String>,
|
||||||
|
) -> FutureResult<CollabObjectUpdateByOid, FlowyError>;
|
||||||
|
|
||||||
|
fn get_collab_latest_snapshot(
|
||||||
|
&self,
|
||||||
|
object_id: &str,
|
||||||
) -> FutureResult<Option<DatabaseSnapshot>, FlowyError>;
|
) -> FutureResult<Option<DatabaseSnapshot>, FlowyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ macro_rules! impl_into_field_type {
|
|||||||
8 => FieldType::LastEditedTime,
|
8 => FieldType::LastEditedTime,
|
||||||
9 => FieldType::CreatedTime,
|
9 => FieldType::CreatedTime,
|
||||||
_ => {
|
_ => {
|
||||||
tracing::error!("Can't parser FieldType from value: {}", ty);
|
tracing::error!("🔴Can't parser FieldType from value: {}", ty);
|
||||||
FieldType::RichText
|
FieldType::RichText
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -324,15 +324,6 @@ impl TryInto<RowIdParams> for RowIdPB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
|
||||||
pub struct BlockRowIdPB {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
pub block_id: String,
|
|
||||||
|
|
||||||
#[pb(index = 2)]
|
|
||||||
pub row_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(ProtoBuf, Default)]
|
#[derive(ProtoBuf, Default)]
|
||||||
pub struct CreateRowPayloadPB {
|
pub struct CreateRowPayloadPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
use collab_database::rows::RowDetail;
|
||||||
|
|
||||||
use flowy_derive::ProtoBuf;
|
use flowy_derive::ProtoBuf;
|
||||||
|
|
||||||
use crate::entities::{InsertedRowPB, UpdatedRowPB};
|
use crate::entities::{InsertedRowPB, RowMetaPB, UpdatedRowPB};
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
#[derive(Debug, Default, Clone, ProtoBuf)]
|
||||||
pub struct RowsVisibilityChangePB {
|
pub struct RowsVisibilityChangePB {
|
||||||
@ -17,53 +19,76 @@ pub struct RowsVisibilityChangePB {
|
|||||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
#[derive(Debug, Default, Clone, ProtoBuf)]
|
||||||
pub struct RowsChangePB {
|
pub struct RowsChangePB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
pub view_id: String,
|
|
||||||
|
|
||||||
#[pb(index = 2)]
|
|
||||||
pub inserted_rows: Vec<InsertedRowPB>,
|
pub inserted_rows: Vec<InsertedRowPB>,
|
||||||
|
|
||||||
#[pb(index = 3)]
|
#[pb(index = 2)]
|
||||||
pub deleted_rows: Vec<String>,
|
pub deleted_rows: Vec<String>,
|
||||||
|
|
||||||
#[pb(index = 4)]
|
#[pb(index = 3)]
|
||||||
pub updated_rows: Vec<UpdatedRowPB>,
|
pub updated_rows: Vec<UpdatedRowPB>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RowsChangePB {
|
impl RowsChangePB {
|
||||||
pub fn from_insert(view_id: String, inserted_row: InsertedRowPB) -> Self {
|
pub fn from_insert(inserted_row: InsertedRowPB) -> Self {
|
||||||
Self {
|
Self {
|
||||||
view_id,
|
|
||||||
inserted_rows: vec![inserted_row],
|
inserted_rows: vec![inserted_row],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_delete(view_id: String, deleted_row: String) -> Self {
|
pub fn from_delete(deleted_row: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
view_id,
|
|
||||||
deleted_rows: vec![deleted_row],
|
deleted_rows: vec![deleted_row],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_update(view_id: String, updated_row: UpdatedRowPB) -> Self {
|
pub fn from_update(updated_row: UpdatedRowPB) -> Self {
|
||||||
Self {
|
Self {
|
||||||
view_id,
|
|
||||||
updated_rows: vec![updated_row],
|
updated_rows: vec![updated_row],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_move(
|
pub fn from_move(deleted_rows: Vec<String>, inserted_rows: Vec<InsertedRowPB>) -> Self {
|
||||||
view_id: String,
|
|
||||||
deleted_rows: Vec<String>,
|
|
||||||
inserted_rows: Vec<InsertedRowPB>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
view_id,
|
|
||||||
inserted_rows,
|
inserted_rows,
|
||||||
deleted_rows,
|
deleted_rows,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, ProtoBuf)]
|
||||||
|
pub struct DidFetchRowPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub row_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub height: i32,
|
||||||
|
|
||||||
|
#[pb(index = 3)]
|
||||||
|
pub visibility: bool,
|
||||||
|
|
||||||
|
#[pb(index = 4)]
|
||||||
|
pub created_at: i64,
|
||||||
|
|
||||||
|
#[pb(index = 5)]
|
||||||
|
pub modified_at: i64,
|
||||||
|
|
||||||
|
#[pb(index = 6)]
|
||||||
|
pub meta: RowMetaPB,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RowDetail> for DidFetchRowPB {
|
||||||
|
fn from(value: RowDetail) -> Self {
|
||||||
|
Self {
|
||||||
|
row_id: value.row.id.to_string(),
|
||||||
|
height: value.row.height,
|
||||||
|
visibility: value.row.visibility,
|
||||||
|
created_at: value.row.created_at,
|
||||||
|
modified_at: value.row.modified_at,
|
||||||
|
meta: RowMetaPB::from(value.meta),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Deref;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||||
use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB};
|
use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB};
|
||||||
use collab::core::collab::MutexCollab;
|
use collab::core::collab::{CollabRawData, MutexCollab};
|
||||||
use collab_database::database::DatabaseData;
|
use collab_database::blocks::BlockEvent;
|
||||||
use collab_database::user::{DatabaseCollabBuilder, UserDatabase as InnerUserDatabase};
|
use collab_database::database::{DatabaseData, YrsDocAction};
|
||||||
|
use collab_database::error::DatabaseError;
|
||||||
|
use collab_database::user::{
|
||||||
|
make_workspace_database_id, CollabFuture, CollabObjectUpdate, CollabObjectUpdateByOid,
|
||||||
|
DatabaseCollabService, WorkspaceDatabase,
|
||||||
|
};
|
||||||
use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLayout};
|
use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLayout};
|
||||||
use parking_lot::Mutex;
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use flowy_error::{internal_error, FlowyError, FlowyResult};
|
use flowy_error::{internal_error, FlowyError, FlowyResult};
|
||||||
@ -16,15 +19,17 @@ use flowy_task::TaskDispatcher;
|
|||||||
|
|
||||||
use crate::deps::{DatabaseCloudService, DatabaseUser2};
|
use crate::deps::{DatabaseCloudService, DatabaseUser2};
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
DatabaseDescriptionPB, DatabaseLayoutPB, DatabaseSnapshotPB, RepeatedDatabaseDescriptionPB,
|
DatabaseDescriptionPB, DatabaseLayoutPB, DatabaseSnapshotPB, DidFetchRowPB,
|
||||||
|
RepeatedDatabaseDescriptionPB,
|
||||||
};
|
};
|
||||||
use crate::services::database::{DatabaseEditor, MutexDatabase};
|
use crate::notification::{send_notification, DatabaseNotification};
|
||||||
|
use crate::services::database::DatabaseEditor;
|
||||||
use crate::services::database_view::DatabaseLayoutDepsResolver;
|
use crate::services::database_view::DatabaseLayoutDepsResolver;
|
||||||
use crate::services::share::csv::{CSVFormat, CSVImporter, ImportResult};
|
use crate::services::share::csv::{CSVFormat, CSVImporter, ImportResult};
|
||||||
|
|
||||||
pub struct DatabaseManager2 {
|
pub struct DatabaseManager2 {
|
||||||
user: Arc<dyn DatabaseUser2>,
|
user: Arc<dyn DatabaseUser2>,
|
||||||
user_database: UserDatabase,
|
workspace_database: Arc<RwLock<Option<Arc<WorkspaceDatabase>>>>,
|
||||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||||
editors: RwLock<HashMap<String, Arc<DatabaseEditor>>>,
|
editors: RwLock<HashMap<String, Arc<DatabaseEditor>>>,
|
||||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||||
@ -40,7 +45,7 @@ impl DatabaseManager2 {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
user: database_user,
|
user: database_user,
|
||||||
user_database: UserDatabase::default(),
|
workspace_database: Default::default(),
|
||||||
task_scheduler,
|
task_scheduler,
|
||||||
editors: Default::default(),
|
editors: Default::default(),
|
||||||
collab_builder,
|
collab_builder,
|
||||||
@ -48,16 +53,53 @@ impl DatabaseManager2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn initialize(&self, user_id: i64) -> FlowyResult<()> {
|
fn is_collab_exist(&self, uid: i64, collab_db: &Arc<RocksCollabDB>, object_id: &str) -> bool {
|
||||||
|
let read_txn = collab_db.read_txn();
|
||||||
|
read_txn.is_exist(uid, object_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn initialize(&self, uid: i64) -> FlowyResult<()> {
|
||||||
|
let collab_db = self.user.collab_db(uid)?;
|
||||||
|
let workspace_database_id = make_workspace_database_id(uid);
|
||||||
|
let collab_builder = UserDatabaseCollabServiceImpl {
|
||||||
|
collab_builder: self.collab_builder.clone(),
|
||||||
|
cloud_service: self.cloud_service.clone(),
|
||||||
|
};
|
||||||
let config = CollabPersistenceConfig::new().snapshot_per_update(10);
|
let config = CollabPersistenceConfig::new().snapshot_per_update(10);
|
||||||
let db = self.user.collab_db()?;
|
let mut collab_raw_data = CollabRawData::default();
|
||||||
*self.user_database.lock() = Some(InnerUserDatabase::new(
|
|
||||||
user_id,
|
// If the workspace database not exist in disk, try to fetch from remote.
|
||||||
db,
|
if !self.is_collab_exist(uid, &collab_db, &workspace_database_id) {
|
||||||
config,
|
tracing::trace!("workspace database not exist, try to fetch from remote");
|
||||||
UserDatabaseCollabBuilderImpl(self.collab_builder.clone()),
|
match self
|
||||||
));
|
.cloud_service
|
||||||
// do nothing
|
.get_collab_update(&workspace_database_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(updates) => collab_raw_data = updates,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(FlowyError::record_not_found().context(format!(
|
||||||
|
"get workspace database :{} failed: {}",
|
||||||
|
workspace_database_id, err,
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the workspace database.
|
||||||
|
tracing::trace!("open workspace database: {}", &workspace_database_id);
|
||||||
|
let collab = collab_builder.build_collab_with_config(
|
||||||
|
uid,
|
||||||
|
&workspace_database_id,
|
||||||
|
"databases",
|
||||||
|
collab_db.clone(),
|
||||||
|
collab_raw_data,
|
||||||
|
&config,
|
||||||
|
);
|
||||||
|
let workspace_database =
|
||||||
|
WorkspaceDatabase::open(uid, collab, collab_db, config, collab_builder);
|
||||||
|
subscribe_block_event(&workspace_database);
|
||||||
|
*self.workspace_database.write().await = Some(Arc::new(workspace_database));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,17 +109,15 @@ impl DatabaseManager2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_databases_description(&self) -> RepeatedDatabaseDescriptionPB {
|
pub async fn get_all_databases_description(&self) -> RepeatedDatabaseDescriptionPB {
|
||||||
let databases_description = self.with_user_database(vec![], |database| {
|
let mut items = vec![];
|
||||||
database
|
if let Ok(wdb) = self.get_workspace_database().await {
|
||||||
|
items = wdb
|
||||||
.get_all_databases()
|
.get_all_databases()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(DatabaseDescriptionPB::from)
|
.map(DatabaseDescriptionPB::from)
|
||||||
.collect()
|
.collect();
|
||||||
});
|
|
||||||
|
|
||||||
RepeatedDatabaseDescriptionPB {
|
|
||||||
items: databases_description,
|
|
||||||
}
|
}
|
||||||
|
RepeatedDatabaseDescriptionPB { items }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_database_with_view_id(&self, view_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
|
pub async fn get_database_with_view_id(&self, view_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
|
||||||
@ -86,12 +126,11 @@ impl DatabaseManager2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_database_id_with_view_id(&self, view_id: &str) -> FlowyResult<String> {
|
pub async fn get_database_id_with_view_id(&self, view_id: &str) -> FlowyResult<String> {
|
||||||
let database_id = self.with_user_database(Err(FlowyError::internal()), |database| {
|
let wdb = self.get_workspace_database().await?;
|
||||||
database
|
wdb.get_database_id_with_view_id(view_id).ok_or_else(|| {
|
||||||
.get_database_id_with_view_id(view_id)
|
FlowyError::record_not_found()
|
||||||
.ok_or_else(FlowyError::record_not_found)
|
.context(format!("The database for view id: {} not found", view_id))
|
||||||
})?;
|
})
|
||||||
Ok(database_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_database(&self, database_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
|
pub async fn get_database(&self, database_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
|
||||||
@ -104,14 +143,12 @@ impl DatabaseManager2 {
|
|||||||
pub async fn open_database(&self, database_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
|
pub async fn open_database(&self, database_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
|
||||||
tracing::trace!("create new editor for database {}", database_id);
|
tracing::trace!("create new editor for database {}", database_id);
|
||||||
let mut editors = self.editors.write().await;
|
let mut editors = self.editors.write().await;
|
||||||
let database = MutexDatabase::new(self.with_user_database(
|
|
||||||
Err(FlowyError::record_not_found()),
|
let wdb = self.get_workspace_database().await?;
|
||||||
|database| {
|
let database = wdb
|
||||||
database
|
.get_database(database_id)
|
||||||
.get_database(database_id)
|
.await
|
||||||
.ok_or_else(FlowyError::record_not_found)
|
.ok_or_else(FlowyError::record_not_found)?;
|
||||||
},
|
|
||||||
)?);
|
|
||||||
|
|
||||||
let editor = Arc::new(DatabaseEditor::new(database, self.task_scheduler.clone()).await?);
|
let editor = Arc::new(DatabaseEditor::new(database, self.task_scheduler.clone()).await?);
|
||||||
editors.insert(database_id.to_string(), editor.clone());
|
editors.insert(database_id.to_string(), editor.clone());
|
||||||
@ -122,13 +159,11 @@ impl DatabaseManager2 {
|
|||||||
pub async fn close_database_view<T: AsRef<str>>(&self, view_id: T) -> FlowyResult<()> {
|
pub async fn close_database_view<T: AsRef<str>>(&self, view_id: T) -> FlowyResult<()> {
|
||||||
// TODO(natan): defer closing the database if the sync is not finished
|
// TODO(natan): defer closing the database if the sync is not finished
|
||||||
let view_id = view_id.as_ref();
|
let view_id = view_id.as_ref();
|
||||||
let database_id = self.with_user_database(None, |databases| {
|
let wdb = self.get_workspace_database().await?;
|
||||||
let database_id = databases.get_database_id_with_view_id(view_id);
|
let database_id = wdb.get_database_id_with_view_id(view_id);
|
||||||
if database_id.is_some() {
|
if database_id.is_some() {
|
||||||
databases.close_database(database_id.as_ref().unwrap());
|
wdb.close_database(database_id.as_ref().unwrap());
|
||||||
}
|
}
|
||||||
database_id
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(database_id) = database_id {
|
if let Some(database_id) = database_id {
|
||||||
let mut editors = self.editors.write().await;
|
let mut editors = self.editors.write().await;
|
||||||
@ -150,13 +185,10 @@ impl DatabaseManager2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn duplicate_database(&self, view_id: &str) -> FlowyResult<Vec<u8>> {
|
pub async fn duplicate_database(&self, view_id: &str) -> FlowyResult<Vec<u8>> {
|
||||||
let database_data = self.with_user_database(Err(FlowyError::internal()), |database| {
|
let wdb = self.get_workspace_database().await?;
|
||||||
let data = database.get_database_duplicated_data(view_id)?;
|
let data = wdb.get_database_duplicated_data(view_id).await?;
|
||||||
let json_bytes = data.to_json_bytes()?;
|
let json_bytes = data.to_json_bytes()?;
|
||||||
Ok(json_bytes)
|
Ok(json_bytes)
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(database_data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new database with the given data that can be deserialized to [DatabaseData].
|
/// Create a new database with the given data that can be deserialized to [DatabaseData].
|
||||||
@ -168,24 +200,15 @@ impl DatabaseManager2 {
|
|||||||
) -> FlowyResult<()> {
|
) -> FlowyResult<()> {
|
||||||
let mut database_data = DatabaseData::from_json_bytes(data)?;
|
let mut database_data = DatabaseData::from_json_bytes(data)?;
|
||||||
database_data.view.id = view_id.to_string();
|
database_data.view.id = view_id.to_string();
|
||||||
self.with_user_database(
|
|
||||||
Err(FlowyError::internal().context("Create database with data failed")),
|
let wdb = self.get_workspace_database().await?;
|
||||||
|database| {
|
let _ = wdb.create_database_with_data(database_data)?;
|
||||||
let database = database.create_database_with_data(database_data)?;
|
|
||||||
Ok(database)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_database_with_params(&self, params: CreateDatabaseParams) -> FlowyResult<()> {
|
pub async fn create_database_with_params(&self, params: CreateDatabaseParams) -> FlowyResult<()> {
|
||||||
let _ = self.with_user_database(
|
let wdb = self.get_workspace_database().await?;
|
||||||
Err(FlowyError::internal().context("Create database with params failed")),
|
let _ = wdb.create_database(params)?;
|
||||||
|user_database| {
|
|
||||||
let database = user_database.create_database(params)?;
|
|
||||||
Ok(database)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,23 +221,18 @@ impl DatabaseManager2 {
|
|||||||
database_id: String,
|
database_id: String,
|
||||||
database_view_id: String,
|
database_view_id: String,
|
||||||
) -> FlowyResult<()> {
|
) -> FlowyResult<()> {
|
||||||
self.with_user_database(
|
let wdb = self.get_workspace_database().await?;
|
||||||
Err(FlowyError::internal().context("Create database view failed")),
|
let mut params = CreateViewParams::new(database_id.clone(), database_view_id, name, layout);
|
||||||
|user_database| {
|
if let Some(database) = wdb.get_database(&database_id).await {
|
||||||
let mut params = CreateViewParams::new(database_id.clone(), database_view_id, name, layout);
|
if let Some((field, layout_setting)) = DatabaseLayoutDepsResolver::new(database, layout)
|
||||||
if let Some(database) = user_database.get_database(&database_id) {
|
.resolve_deps_when_create_database_linked_view()
|
||||||
if let Some((field, layout_setting)) = DatabaseLayoutDepsResolver::new(database, layout)
|
{
|
||||||
.resolve_deps_when_create_database_linked_view()
|
params = params
|
||||||
{
|
.with_deps_fields(vec![field])
|
||||||
params = params
|
.with_layout_setting(layout_setting);
|
||||||
.with_deps_fields(vec![field])
|
}
|
||||||
.with_layout_setting(layout_setting);
|
};
|
||||||
}
|
wdb.create_database_linked_view(params).await?;
|
||||||
};
|
|
||||||
user_database.create_database_linked_view(params)?;
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +286,7 @@ impl DatabaseManager2 {
|
|||||||
let mut snapshots = vec![];
|
let mut snapshots = vec![];
|
||||||
if let Some(snapshot) = self
|
if let Some(snapshot) = self
|
||||||
.cloud_service
|
.cloud_service
|
||||||
.get_database_latest_snapshot(&database_id)
|
.get_collab_latest_snapshot(&database_id)
|
||||||
.await?
|
.await?
|
||||||
.map(|snapshot| DatabaseSnapshotPB {
|
.map(|snapshot| DatabaseSnapshotPB {
|
||||||
snapshot_id: snapshot.snapshot_id,
|
snapshot_id: snapshot.snapshot_id,
|
||||||
@ -283,14 +301,11 @@ impl DatabaseManager2 {
|
|||||||
Ok(snapshots)
|
Ok(snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_user_database<F, Output>(&self, default_value: Output, f: F) -> Output
|
async fn get_workspace_database(&self) -> FlowyResult<Arc<WorkspaceDatabase>> {
|
||||||
where
|
let database = self.workspace_database.read().await;
|
||||||
F: FnOnce(&InnerUserDatabase) -> Output,
|
|
||||||
{
|
|
||||||
let database = self.user_database.lock();
|
|
||||||
match &*database {
|
match &*database {
|
||||||
None => default_value,
|
None => Err(FlowyError::internal().context("Workspace database not initialized")),
|
||||||
Some(folder) => f(folder),
|
Some(user_database) => Ok(user_database.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,33 +316,97 @@ impl DatabaseManager2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
/// Send notification to all clients that are listening to the given object.
|
||||||
pub struct UserDatabase(Arc<Mutex<Option<InnerUserDatabase>>>);
|
fn subscribe_block_event(workspace_database: &WorkspaceDatabase) {
|
||||||
|
let mut block_event_rx = workspace_database.subscribe_block_event();
|
||||||
impl Deref for UserDatabase {
|
tokio::spawn(async move {
|
||||||
type Target = Arc<Mutex<Option<InnerUserDatabase>>>;
|
while let Ok(event) = block_event_rx.recv().await {
|
||||||
fn deref(&self) -> &Self::Target {
|
match event {
|
||||||
&self.0
|
BlockEvent::DidFetchRow(row_details) => {
|
||||||
}
|
for row_detail in row_details {
|
||||||
|
tracing::trace!("Did fetch row: {:?}", row_detail.row.id);
|
||||||
|
let row_id = row_detail.row.id.clone();
|
||||||
|
let pb = DidFetchRowPB::from(row_detail);
|
||||||
|
send_notification(&row_id, DatabaseNotification::DidFetchRow)
|
||||||
|
.payload(pb)
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Sync for UserDatabase {}
|
struct UserDatabaseCollabServiceImpl {
|
||||||
|
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||||
|
cloud_service: Arc<dyn DatabaseCloudService>,
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl Send for UserDatabase {}
|
impl DatabaseCollabService for UserDatabaseCollabServiceImpl {
|
||||||
|
fn get_collab_update(
|
||||||
|
&self,
|
||||||
|
object_id: &str,
|
||||||
|
) -> CollabFuture<Result<CollabObjectUpdate, DatabaseError>> {
|
||||||
|
let object_id = object_id.to_string();
|
||||||
|
let weak_cloud_service = Arc::downgrade(&self.cloud_service);
|
||||||
|
Box::pin(async move {
|
||||||
|
match weak_cloud_service.upgrade() {
|
||||||
|
None => {
|
||||||
|
tracing::warn!("Cloud service is dropped");
|
||||||
|
Ok(vec![])
|
||||||
|
},
|
||||||
|
Some(cloud_service) => {
|
||||||
|
let updates = cloud_service
|
||||||
|
.get_collab_update(&object_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| DatabaseError::Internal(Box::new(e)))?;
|
||||||
|
Ok(updates)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
struct UserDatabaseCollabBuilderImpl(Arc<AppFlowyCollabBuilder>);
|
fn batch_get_collab_update(
|
||||||
|
&self,
|
||||||
|
object_ids: Vec<String>,
|
||||||
|
) -> CollabFuture<Result<CollabObjectUpdateByOid, DatabaseError>> {
|
||||||
|
let weak_cloud_service = Arc::downgrade(&self.cloud_service);
|
||||||
|
Box::pin(async move {
|
||||||
|
match weak_cloud_service.upgrade() {
|
||||||
|
None => {
|
||||||
|
tracing::warn!("Cloud service is dropped");
|
||||||
|
Ok(CollabObjectUpdateByOid::default())
|
||||||
|
},
|
||||||
|
Some(cloud_service) => {
|
||||||
|
let updates = cloud_service
|
||||||
|
.batch_get_collab_updates(object_ids)
|
||||||
|
.await
|
||||||
|
.map_err(|e| DatabaseError::Internal(Box::new(e)))?;
|
||||||
|
Ok(updates)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl DatabaseCollabBuilder for UserDatabaseCollabBuilderImpl {
|
fn build_collab_with_config(
|
||||||
fn build_with_config(
|
|
||||||
&self,
|
&self,
|
||||||
uid: i64,
|
uid: i64,
|
||||||
object_id: &str,
|
object_id: &str,
|
||||||
object_name: &str,
|
object_name: &str,
|
||||||
db: Arc<RocksCollabDB>,
|
collab_db: Arc<RocksCollabDB>,
|
||||||
|
collab_raw_data: CollabRawData,
|
||||||
config: &CollabPersistenceConfig,
|
config: &CollabPersistenceConfig,
|
||||||
) -> Arc<MutexCollab> {
|
) -> Arc<MutexCollab> {
|
||||||
self
|
self
|
||||||
.0
|
.collab_builder
|
||||||
.build_with_config(uid, object_id, object_name, db, config)
|
.build_with_config(
|
||||||
|
uid,
|
||||||
|
object_id,
|
||||||
|
object_name,
|
||||||
|
collab_db,
|
||||||
|
collab_raw_data,
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,9 @@ const DATABASE_OBSERVABLE_SOURCE: &str = "Database";
|
|||||||
pub enum DatabaseNotification {
|
pub enum DatabaseNotification {
|
||||||
#[default]
|
#[default]
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
|
/// Fetch row data from the remote server. It will be triggered if the backend support remote
|
||||||
|
/// storage.
|
||||||
|
DidFetchRow = 19,
|
||||||
/// Trigger after inserting/deleting/updating a row
|
/// Trigger after inserting/deleting/updating a row
|
||||||
DidUpdateViewRows = 20,
|
DidUpdateViewRows = 20,
|
||||||
/// Trigger when the visibility of the row was changed. For example, updating the filter will trigger the notification
|
/// Trigger when the visibility of the row was changed. For example, updating the filter will trigger the notification
|
||||||
@ -58,6 +61,7 @@ impl std::convert::From<DatabaseNotification> for i32 {
|
|||||||
impl std::convert::From<i32> for DatabaseNotification {
|
impl std::convert::From<i32> for DatabaseNotification {
|
||||||
fn from(notification: i32) -> Self {
|
fn from(notification: i32) -> Self {
|
||||||
match notification {
|
match notification {
|
||||||
|
19 => DatabaseNotification::DidFetchRow,
|
||||||
20 => DatabaseNotification::DidUpdateViewRows,
|
20 => DatabaseNotification::DidUpdateViewRows,
|
||||||
21 => DatabaseNotification::DidUpdateViewRowsVisibility,
|
21 => DatabaseNotification::DidUpdateViewRowsVisibility,
|
||||||
22 => DatabaseNotification::DidUpdateFields,
|
22 => DatabaseNotification::DidUpdateFields,
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Deref;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use collab_database::database::Database as InnerDatabase;
|
use collab_database::database::MutexDatabase;
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowId};
|
use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetail, RowId};
|
||||||
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use parking_lot::Mutex;
|
|
||||||
use tokio::sync::{broadcast, RwLock};
|
use tokio::sync::{broadcast, RwLock};
|
||||||
|
|
||||||
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
|
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
|
||||||
@ -21,7 +19,7 @@ use crate::services::cell::{
|
|||||||
apply_cell_changeset, get_cell_protobuf, AnyTypeCache, CellCache, ToCellChangeset,
|
apply_cell_changeset, get_cell_protobuf, AnyTypeCache, CellCache, ToCellChangeset,
|
||||||
};
|
};
|
||||||
use crate::services::database::util::database_view_setting_pb_from_view;
|
use crate::services::database::util::database_view_setting_pb_from_view;
|
||||||
use crate::services::database::{RowDetail, UpdatedRow};
|
use crate::services::database::UpdatedRow;
|
||||||
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews};
|
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews};
|
||||||
use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
|
use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
|
||||||
use crate::services::field::{
|
use crate::services::field::{
|
||||||
@ -38,14 +36,14 @@ use crate::services::sort::Sort;
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DatabaseEditor {
|
pub struct DatabaseEditor {
|
||||||
database: MutexDatabase,
|
database: Arc<MutexDatabase>,
|
||||||
pub cell_cache: CellCache,
|
pub cell_cache: CellCache,
|
||||||
database_views: Arc<DatabaseViews>,
|
database_views: Arc<DatabaseViews>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseEditor {
|
impl DatabaseEditor {
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
database: MutexDatabase,
|
database: Arc<MutexDatabase>,
|
||||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||||
) -> FlowyResult<Self> {
|
) -> FlowyResult<Self> {
|
||||||
let cell_cache = AnyTypeCache::<u64>::new();
|
let cell_cache = AnyTypeCache::<u64>::new();
|
||||||
@ -76,7 +74,7 @@ impl DatabaseEditor {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(snapshot_state) = snapshot_state.next().await {
|
while let Some(snapshot_state) = snapshot_state.next().await {
|
||||||
if let Some(new_snapshot_id) = snapshot_state.snapshot_id() {
|
if let Some(new_snapshot_id) = snapshot_state.snapshot_id() {
|
||||||
tracing::debug!("Did create database snapshot: {}", new_snapshot_id);
|
tracing::debug!("Did create database remote snapshot: {}", new_snapshot_id);
|
||||||
send_notification(
|
send_notification(
|
||||||
&database_id,
|
&database_id,
|
||||||
DatabaseNotification::DidUpdateDatabaseSnapshotState,
|
DatabaseNotification::DidUpdateDatabaseSnapshotState,
|
||||||
@ -415,8 +413,7 @@ impl DatabaseEditor {
|
|||||||
|
|
||||||
let delete_row_id = from.into_inner();
|
let delete_row_id = from.into_inner();
|
||||||
let insert_row = InsertedRowPB::new(RowMetaPB::from(&row_meta)).with_index(to_index as i32);
|
let insert_row = InsertedRowPB::new(RowMetaPB::from(&row_meta)).with_index(to_index as i32);
|
||||||
let changes =
|
let changes = RowsChangePB::from_move(vec![delete_row_id], vec![insert_row]);
|
||||||
RowsChangePB::from_move(view_id.to_string(), vec![delete_row_id], vec![insert_row]);
|
|
||||||
send_notification(view_id, DatabaseNotification::DidUpdateViewRows)
|
send_notification(view_id, DatabaseNotification::DidUpdateViewRows)
|
||||||
.payload(changes)
|
.payload(changes)
|
||||||
.send();
|
.send();
|
||||||
@ -437,7 +434,7 @@ impl DatabaseEditor {
|
|||||||
tracing::trace!("create row: {:?} at {}", row_order, index);
|
tracing::trace!("create row: {:?} at {}", row_order, index);
|
||||||
let row = self.database.lock().get_row(&row_order.id);
|
let row = self.database.lock().get_row(&row_order.id);
|
||||||
let row_meta = self.database.lock().get_row_meta(&row_order.id);
|
let row_meta = self.database.lock().get_row_meta(&row_order.id);
|
||||||
if let (Some(row), Some(meta)) = (row, row_meta) {
|
if let Some(meta) = row_meta {
|
||||||
let row_detail = RowDetail { row, meta };
|
let row_detail = RowDetail { row, meta };
|
||||||
for view in self.database_views.editors().await {
|
for view in self.database_views.editors().await {
|
||||||
view.v_did_create_row(&row_detail, &group_id, index).await;
|
view.v_did_create_row(&row_detail, &group_id, index).await;
|
||||||
@ -527,7 +524,7 @@ impl DatabaseEditor {
|
|||||||
|
|
||||||
pub fn get_row(&self, view_id: &str, row_id: &RowId) -> Option<Row> {
|
pub fn get_row(&self, view_id: &str, row_id: &RowId) -> Option<Row> {
|
||||||
if self.database.lock().views.is_row_exist(view_id, row_id) {
|
if self.database.lock().views.is_row_exist(view_id, row_id) {
|
||||||
self.database.lock().get_row(row_id)
|
Some(self.database.lock().get_row(row_id))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -551,7 +548,7 @@ impl DatabaseEditor {
|
|||||||
pub fn get_row_detail(&self, view_id: &str, row_id: &RowId) -> Option<RowDetail> {
|
pub fn get_row_detail(&self, view_id: &str, row_id: &RowId) -> Option<RowDetail> {
|
||||||
if self.database.lock().views.is_row_exist(view_id, row_id) {
|
if self.database.lock().views.is_row_exist(view_id, row_id) {
|
||||||
let meta = self.database.lock().get_row_meta(row_id)?;
|
let meta = self.database.lock().get_row_meta(row_id)?;
|
||||||
let row = self.database.lock().get_row(row_id)?;
|
let row = self.database.lock().get_row(row_id);
|
||||||
Some(RowDetail { row, meta })
|
Some(RowDetail { row, meta })
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id);
|
tracing::warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id);
|
||||||
@ -597,40 +594,23 @@ impl DatabaseEditor {
|
|||||||
let field_type = FieldType::from(field.field_type);
|
let field_type = FieldType::from(field.field_type);
|
||||||
// If the cell data is referenced, return the reference data. Otherwise, return an empty cell.
|
// If the cell data is referenced, return the reference data. Otherwise, return an empty cell.
|
||||||
match field_type {
|
match field_type {
|
||||||
FieldType::LastEditedTime | FieldType::CreatedTime => database
|
FieldType::LastEditedTime | FieldType::CreatedTime => {
|
||||||
.get_row(row_id)
|
let row = database.get_row(row_id);
|
||||||
.map(|row| {
|
let cell_data = if field_type.is_created_time() {
|
||||||
if field_type.is_created_time() {
|
DateCellData::new(row.created_at, true)
|
||||||
DateCellData::new(row.created_at, true)
|
} else {
|
||||||
} else {
|
DateCellData::new(row.modified_at, true)
|
||||||
DateCellData::new(row.modified_at, true)
|
};
|
||||||
}
|
Some(Cell::from(cell_data))
|
||||||
})
|
},
|
||||||
.map(Cell::from),
|
|
||||||
_ => database.get_cell(field_id, row_id).cell,
|
_ => database.get_cell(field_id, row_id).cell,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_cell_pb(&self, field_id: &str, row_id: &RowId) -> Option<CellPB> {
|
pub async fn get_cell_pb(&self, field_id: &str, row_id: &RowId) -> Option<CellPB> {
|
||||||
let (field, cell) = {
|
let (field, cell) = {
|
||||||
let database = self.database.lock();
|
let cell = self.get_cell(field_id, row_id).await?;
|
||||||
let field = database.fields.get_field(field_id)?;
|
let field = self.database.lock().fields.get_field(field_id)?;
|
||||||
let field_type = FieldType::from(field.field_type);
|
|
||||||
// If the cell data is referenced, return the reference data. Otherwise, return an empty cell.
|
|
||||||
let cell = match field_type {
|
|
||||||
FieldType::LastEditedTime | FieldType::CreatedTime => database
|
|
||||||
.get_row(row_id)
|
|
||||||
.map(|row| {
|
|
||||||
if field_type.is_created_time() {
|
|
||||||
DateCellData::new(row.created_at, true)
|
|
||||||
} else {
|
|
||||||
DateCellData::new(row.modified_at, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(Cell::from),
|
|
||||||
_ => database.get_cell(field_id, row_id).cell,
|
|
||||||
}?;
|
|
||||||
|
|
||||||
(field, cell)
|
(field, cell)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -723,7 +703,7 @@ impl DatabaseEditor {
|
|||||||
if let Some(new_row_detail) = option_row {
|
if let Some(new_row_detail) = option_row {
|
||||||
let updated_row =
|
let updated_row =
|
||||||
UpdatedRow::new(&new_row_detail.row.id).with_field_ids(vec![field_id.to_string()]);
|
UpdatedRow::new(&new_row_detail.row.id).with_field_ids(vec![field_id.to_string()]);
|
||||||
let changes = RowsChangePB::from_update(view_id.to_string(), updated_row.into());
|
let changes = RowsChangePB::from_update(updated_row.into());
|
||||||
send_notification(view_id, DatabaseNotification::DidUpdateViewRows)
|
send_notification(view_id, DatabaseNotification::DidUpdateViewRows)
|
||||||
.payload(changes)
|
.payload(changes)
|
||||||
.send();
|
.send();
|
||||||
@ -1154,35 +1134,15 @@ fn cell_changesets_from_cell_by_field_id(
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MutexDatabase(Arc<Mutex<Arc<InnerDatabase>>>);
|
|
||||||
|
|
||||||
impl MutexDatabase {
|
|
||||||
pub(crate) fn new(database: Arc<InnerDatabase>) -> Self {
|
|
||||||
Self(Arc::new(Mutex::new(database)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for MutexDatabase {
|
|
||||||
type Target = Arc<Mutex<Arc<InnerDatabase>>>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Sync for MutexDatabase {}
|
|
||||||
|
|
||||||
unsafe impl Send for MutexDatabase {}
|
|
||||||
|
|
||||||
struct DatabaseViewDataImpl {
|
struct DatabaseViewDataImpl {
|
||||||
database: MutexDatabase,
|
database: Arc<MutexDatabase>,
|
||||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||||
cell_cache: CellCache,
|
cell_cache: CellCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseViewData for DatabaseViewDataImpl {
|
impl DatabaseViewData for DatabaseViewDataImpl {
|
||||||
fn get_database(&self) -> Arc<InnerDatabase> {
|
fn get_database(&self) -> Arc<MutexDatabase> {
|
||||||
self.database.lock().clone()
|
self.database.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>> {
|
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>> {
|
||||||
@ -1245,8 +1205,8 @@ impl DatabaseViewData for DatabaseViewDataImpl {
|
|||||||
let row = self.database.lock().get_row(row_id);
|
let row = self.database.lock().get_row(row_id);
|
||||||
let row_meta = self.database.lock().get_row_meta(row_id);
|
let row_meta = self.database.lock().get_row_meta(row_id);
|
||||||
to_fut(async move {
|
to_fut(async move {
|
||||||
match (index, row, row_meta) {
|
match (index, row_meta) {
|
||||||
(Some(index), Some(row), Some(row_meta)) => {
|
(Some(index), Some(row_meta)) => {
|
||||||
let row_detail = RowDetail {
|
let row_detail = RowDetail {
|
||||||
row,
|
row,
|
||||||
meta: row_meta,
|
meta: row_meta,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use collab_database::rows::{Row, RowId, RowMeta};
|
use collab_database::rows::{RowId, RowMeta};
|
||||||
use collab_database::views::DatabaseLayout;
|
use collab_database::views::DatabaseLayout;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -64,9 +64,3 @@ pub struct CreateDatabaseViewParams {
|
|||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
pub layout_type: DatabaseLayout,
|
pub layout_type: DatabaseLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RowDetail {
|
|
||||||
pub row: Row,
|
|
||||||
pub meta: RowMeta,
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use collab_database::database::{gen_field_id, MutexDatabase};
|
||||||
|
|
||||||
use collab_database::database::{gen_field_id, Database};
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::views::{DatabaseLayout, LayoutSetting};
|
use collab_database::views::{DatabaseLayout, LayoutSetting};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::entities::FieldType;
|
use crate::entities::FieldType;
|
||||||
use crate::services::field::DateTypeOption;
|
use crate::services::field::DateTypeOption;
|
||||||
@ -11,13 +10,13 @@ use crate::services::setting::CalendarLayoutSetting;
|
|||||||
/// When creating a database, we need to resolve the dependencies of the views. Different database
|
/// When creating a database, we need to resolve the dependencies of the views. Different database
|
||||||
/// view has different dependencies. For example, a calendar view depends on a date field.
|
/// view has different dependencies. For example, a calendar view depends on a date field.
|
||||||
pub struct DatabaseLayoutDepsResolver {
|
pub struct DatabaseLayoutDepsResolver {
|
||||||
pub database: Arc<Database>,
|
pub database: Arc<MutexDatabase>,
|
||||||
/// The new database layout.
|
/// The new database layout.
|
||||||
pub database_layout: DatabaseLayout,
|
pub database_layout: DatabaseLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseLayoutDepsResolver {
|
impl DatabaseLayoutDepsResolver {
|
||||||
pub fn new(database: Arc<Database>, database_layout: DatabaseLayout) -> Self {
|
pub fn new(database: Arc<MutexDatabase>, database_layout: DatabaseLayout) -> Self {
|
||||||
Self {
|
Self {
|
||||||
database,
|
database,
|
||||||
database_layout,
|
database_layout,
|
||||||
@ -39,7 +38,7 @@ impl DatabaseLayoutDepsResolver {
|
|||||||
/// If the new layout type is a calendar and there is not date field in the database, it will add
|
/// If the new layout type is a calendar and there is not date field in the database, it will add
|
||||||
/// a new date field to the database and create the corresponding layout setting.
|
/// a new date field to the database and create the corresponding layout setting.
|
||||||
pub fn resolve_deps_when_update_layout_type(&self, view_id: &str) {
|
pub fn resolve_deps_when_update_layout_type(&self, view_id: &str) {
|
||||||
let fields = self.database.get_fields(None);
|
let fields = self.database.lock().get_fields(None);
|
||||||
// Insert the layout setting if it's not exist
|
// Insert the layout setting if it's not exist
|
||||||
match &self.database_layout {
|
match &self.database_layout {
|
||||||
DatabaseLayout::Grid => {},
|
DatabaseLayout::Grid => {},
|
||||||
@ -53,7 +52,7 @@ impl DatabaseLayoutDepsResolver {
|
|||||||
tracing::trace!("Create a new date field after layout type change");
|
tracing::trace!("Create a new date field after layout type change");
|
||||||
let field = self.create_date_field();
|
let field = self.create_date_field();
|
||||||
let field_id = field.id.clone();
|
let field_id = field.id.clone();
|
||||||
self.database.create_field(field);
|
self.database.lock().create_field(field);
|
||||||
field_id
|
field_id
|
||||||
},
|
},
|
||||||
Some(date_field) => date_field.id,
|
Some(date_field) => date_field.id,
|
||||||
@ -66,12 +65,14 @@ impl DatabaseLayoutDepsResolver {
|
|||||||
fn create_calendar_layout_setting_if_need(&self, view_id: &str, field_id: &str) {
|
fn create_calendar_layout_setting_if_need(&self, view_id: &str, field_id: &str) {
|
||||||
if self
|
if self
|
||||||
.database
|
.database
|
||||||
|
.lock()
|
||||||
.get_layout_setting::<CalendarLayoutSetting>(view_id, &self.database_layout)
|
.get_layout_setting::<CalendarLayoutSetting>(view_id, &self.database_layout)
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
let layout_setting = CalendarLayoutSetting::new(field_id.to_string());
|
let layout_setting = CalendarLayoutSetting::new(field_id.to_string());
|
||||||
self
|
self
|
||||||
.database
|
.database
|
||||||
|
.lock()
|
||||||
.insert_layout_setting(view_id, &self.database_layout, layout_setting);
|
.insert_layout_setting(view_id, &self.database_layout, layout_setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ use std::borrow::Cow;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::database::{gen_database_filter_id, gen_database_sort_id, Database};
|
use collab_database::database::{gen_database_filter_id, gen_database_sort_id, MutexDatabase};
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cells, Row, RowCell, RowId, RowMeta};
|
use collab_database::rows::{Cells, Row, RowCell, RowDetail, RowId, RowMeta};
|
||||||
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
||||||
use tokio::sync::{broadcast, RwLock};
|
use tokio::sync::{broadcast, RwLock};
|
||||||
|
|
||||||
@ -20,9 +20,7 @@ use crate::entities::{
|
|||||||
};
|
};
|
||||||
use crate::notification::{send_notification, DatabaseNotification};
|
use crate::notification::{send_notification, DatabaseNotification};
|
||||||
use crate::services::cell::CellCache;
|
use crate::services::cell::CellCache;
|
||||||
use crate::services::database::{
|
use crate::services::database::{database_view_setting_pb_from_view, DatabaseRowEvent, UpdatedRow};
|
||||||
database_view_setting_pb_from_view, DatabaseRowEvent, RowDetail, UpdatedRow,
|
|
||||||
};
|
|
||||||
use crate::services::database_view::view_filter::make_filter_controller;
|
use crate::services::database_view::view_filter::make_filter_controller;
|
||||||
use crate::services::database_view::view_group::{
|
use crate::services::database_view::view_group::{
|
||||||
get_cell_for_row, get_cells_for_field, new_group_controller, new_group_controller_with_field,
|
get_cell_for_row, get_cells_for_field, new_group_controller, new_group_controller_with_field,
|
||||||
@ -44,7 +42,7 @@ use crate::services::setting::CalendarLayoutSetting;
|
|||||||
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
|
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
|
||||||
|
|
||||||
pub trait DatabaseViewData: Send + Sync + 'static {
|
pub trait DatabaseViewData: Send + Sync + 'static {
|
||||||
fn get_database(&self) -> Arc<Database>;
|
fn get_database(&self) -> Arc<MutexDatabase>;
|
||||||
|
|
||||||
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
|
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
|
||||||
/// If the field_ids is None, then it will return all the field revisions
|
/// If the field_ids is None, then it will return all the field revisions
|
||||||
@ -204,7 +202,7 @@ impl DatabaseViewEditor {
|
|||||||
|
|
||||||
pub async fn v_did_update_row_meta(&self, row_id: &RowId, row_meta: &RowMeta) {
|
pub async fn v_did_update_row_meta(&self, row_id: &RowId, row_meta: &RowMeta) {
|
||||||
let update_row = UpdatedRow::new(row_id.as_str()).with_row_meta(row_meta.clone());
|
let update_row = UpdatedRow::new(row_id.as_str()).with_row_meta(row_meta.clone());
|
||||||
let changeset = RowsChangePB::from_update(self.view_id.clone(), update_row.into());
|
let changeset = RowsChangePB::from_update(update_row.into());
|
||||||
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||||
.payload(changeset)
|
.payload(changeset)
|
||||||
.send();
|
.send();
|
||||||
@ -221,7 +219,7 @@ impl DatabaseViewEditor {
|
|||||||
match group_id.as_ref() {
|
match group_id.as_ref() {
|
||||||
None => {
|
None => {
|
||||||
let row = InsertedRowPB::new(RowMetaPB::from(&row_detail.meta)).with_index(index as i32);
|
let row = InsertedRowPB::new(RowMetaPB::from(&row_detail.meta)).with_index(index as i32);
|
||||||
changes = RowsChangePB::from_insert(self.view_id.clone(), row);
|
changes = RowsChangePB::from_insert(row);
|
||||||
},
|
},
|
||||||
Some(group_id) => {
|
Some(group_id) => {
|
||||||
self
|
self
|
||||||
@ -239,7 +237,7 @@ impl DatabaseViewEditor {
|
|||||||
let changeset =
|
let changeset =
|
||||||
GroupRowsNotificationPB::insert(group_id.clone(), vec![inserted_row.clone()]);
|
GroupRowsNotificationPB::insert(group_id.clone(), vec![inserted_row.clone()]);
|
||||||
notify_did_update_group_rows(changeset).await;
|
notify_did_update_group_rows(changeset).await;
|
||||||
changes = RowsChangePB::from_insert(self.view_id.clone(), inserted_row);
|
changes = RowsChangePB::from_insert(inserted_row);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,7 +261,7 @@ impl DatabaseViewEditor {
|
|||||||
notify_did_update_group_rows(changeset).await;
|
notify_did_update_group_rows(changeset).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let changes = RowsChangePB::from_delete(self.view_id.clone(), row.id.clone().into_inner());
|
let changes = RowsChangePB::from_delete(row.id.clone().into_inner());
|
||||||
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||||
.payload(changes)
|
.payload(changes)
|
||||||
.send();
|
.send();
|
||||||
@ -311,7 +309,7 @@ impl DatabaseViewEditor {
|
|||||||
} else {
|
} else {
|
||||||
let update_row =
|
let update_row =
|
||||||
UpdatedRow::new(&row_detail.row.id).with_field_ids(vec![field_id.to_string()]);
|
UpdatedRow::new(&row_detail.row.id).with_field_ids(vec![field_id.to_string()]);
|
||||||
let changeset = RowsChangePB::from_update(self.view_id.clone(), update_row.into());
|
let changeset = RowsChangePB::from_update(update_row.into());
|
||||||
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||||
.payload(changeset)
|
.payload(changeset)
|
||||||
.send();
|
.send();
|
||||||
@ -880,23 +878,13 @@ impl DatabaseViewEditor {
|
|||||||
|
|
||||||
pub async fn handle_row_event(&self, event: Cow<'_, DatabaseRowEvent>) {
|
pub async fn handle_row_event(&self, event: Cow<'_, DatabaseRowEvent>) {
|
||||||
let changeset = match event.into_owned() {
|
let changeset = match event.into_owned() {
|
||||||
DatabaseRowEvent::InsertRow(row) => {
|
DatabaseRowEvent::InsertRow(row) => RowsChangePB::from_insert(row.into()),
|
||||||
RowsChangePB::from_insert(self.view_id.clone(), row.into())
|
DatabaseRowEvent::UpdateRow(row) => RowsChangePB::from_update(row.into()),
|
||||||
},
|
DatabaseRowEvent::DeleteRow(row_id) => RowsChangePB::from_delete(row_id.into_inner()),
|
||||||
DatabaseRowEvent::UpdateRow(row) => {
|
|
||||||
RowsChangePB::from_update(self.view_id.clone(), row.into())
|
|
||||||
},
|
|
||||||
DatabaseRowEvent::DeleteRow(row_id) => {
|
|
||||||
RowsChangePB::from_delete(self.view_id.clone(), row_id.into_inner())
|
|
||||||
},
|
|
||||||
DatabaseRowEvent::Move {
|
DatabaseRowEvent::Move {
|
||||||
deleted_row_id,
|
deleted_row_id,
|
||||||
inserted_row,
|
inserted_row,
|
||||||
} => RowsChangePB::from_move(
|
} => RowsChangePB::from_move(vec![deleted_row_id.into_inner()], vec![inserted_row.into()]),
|
||||||
self.view_id.clone(),
|
|
||||||
vec![deleted_row_id.into_inner()],
|
|
||||||
vec![inserted_row.into()],
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::RowId;
|
use collab_database::rows::{RowDetail, RowId};
|
||||||
|
|
||||||
use lib_infra::future::{to_fut, Fut};
|
use lib_infra::future::{to_fut, Fut};
|
||||||
|
|
||||||
use crate::services::cell::CellCache;
|
use crate::services::cell::CellCache;
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::database_view::{
|
use crate::services::database_view::{
|
||||||
gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewData,
|
gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewData,
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
|
use collab_database::rows::RowDetail;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use lib_infra::future::{to_fut, Fut};
|
use lib_infra::future::{to_fut, Fut};
|
||||||
|
|
||||||
use crate::services::cell::CellCache;
|
use crate::services::cell::CellCache;
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::database_view::{
|
use crate::services::database_view::{
|
||||||
gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewData,
|
gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewData,
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use collab_database::database::MutexDatabase;
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::RowId;
|
use collab_database::rows::{RowDetail, RowId};
|
||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
use tokio::sync::{broadcast, RwLock};
|
use tokio::sync::{broadcast, RwLock};
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ use flowy_error::FlowyResult;
|
|||||||
use lib_infra::future::Fut;
|
use lib_infra::future::Fut;
|
||||||
|
|
||||||
use crate::services::cell::CellCache;
|
use crate::services::cell::CellCache;
|
||||||
use crate::services::database::{DatabaseRowEvent, MutexDatabase, RowDetail};
|
use crate::services::database::DatabaseRowEvent;
|
||||||
use crate::services::database_view::{DatabaseViewData, DatabaseViewEditor};
|
use crate::services::database_view::{DatabaseViewData, DatabaseViewEditor};
|
||||||
use crate::services::group::RowChangeset;
|
use crate::services::group::RowChangeset;
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ pub type RowEventReceiver = broadcast::Receiver<DatabaseRowEvent>;
|
|||||||
|
|
||||||
pub struct DatabaseViews {
|
pub struct DatabaseViews {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
database: MutexDatabase,
|
database: Arc<MutexDatabase>,
|
||||||
cell_cache: CellCache,
|
cell_cache: CellCache,
|
||||||
database_view_data: Arc<dyn DatabaseViewData>,
|
database_view_data: Arc<dyn DatabaseViewData>,
|
||||||
editor_map: Arc<RwLock<HashMap<String, Arc<DatabaseViewEditor>>>>,
|
editor_map: Arc<RwLock<HashMap<String, Arc<DatabaseViewEditor>>>>,
|
||||||
@ -27,7 +28,7 @@ pub struct DatabaseViews {
|
|||||||
|
|
||||||
impl DatabaseViews {
|
impl DatabaseViews {
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
database: MutexDatabase,
|
database: Arc<MutexDatabase>,
|
||||||
cell_cache: CellCache,
|
cell_cache: CellCache,
|
||||||
database_view_data: Arc<dyn DatabaseViewData>,
|
database_view_data: Arc<dyn DatabaseViewData>,
|
||||||
) -> FlowyResult<Self> {
|
) -> FlowyResult<Self> {
|
||||||
|
@ -9,7 +9,6 @@ use crate::entities::{FieldType, SelectOptionCellDataPB};
|
|||||||
use crate::services::cell::{
|
use crate::services::cell::{
|
||||||
CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, ToCellChangeset,
|
CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, ToCellChangeset,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper;
|
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper;
|
||||||
use crate::services::field::{
|
use crate::services::field::{
|
||||||
make_selected_options, CheckboxCellData, MultiSelectTypeOption, SelectOption,
|
make_selected_options, CheckboxCellData, MultiSelectTypeOption, SelectOption,
|
||||||
@ -168,7 +167,7 @@ pub fn select_type_option_from_field(
|
|||||||
Ok(Box::new(type_option))
|
Ok(Box::new(type_option))
|
||||||
},
|
},
|
||||||
ty => {
|
ty => {
|
||||||
tracing::error!("Unsupported field type: {:?} for this handler", ty);
|
tracing::error!("🔴Unsupported field type: {:?} for this handler", ty);
|
||||||
Err(ErrorCode::FieldInvalidOperation.into())
|
Err(ErrorCode::FieldInvalidOperation.into())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use std::str::FromStr;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{Cell, Row, RowId};
|
use collab_database::rows::{Cell, Row, RowDetail, RowId};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
@ -15,7 +15,6 @@ use lib_infra::future::Fut;
|
|||||||
use crate::entities::filter_entities::*;
|
use crate::entities::filter_entities::*;
|
||||||
use crate::entities::{FieldType, InsertedRowPB, RowMetaPB};
|
use crate::entities::{FieldType, InsertedRowPB, RowMetaPB};
|
||||||
use crate::services::cell::{AnyTypeCache, CellCache, CellFilterCache};
|
use crate::services::cell::{AnyTypeCache, CellCache, CellFilterCache};
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier};
|
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier};
|
||||||
use crate::services::field::*;
|
use crate::services::field::*;
|
||||||
use crate::services::filter::{Filter, FilterChangeset, FilterResult, FilterResultNotification};
|
use crate::services::filter::{Filter, FilterChangeset, FilterResult, FilterResultNotification};
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{Cell, Row};
|
use collab_database::rows::{Cell, Row, RowDetail};
|
||||||
|
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
|
|
||||||
use crate::entities::{GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
|
use crate::entities::{GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
|
||||||
use crate::services::cell::DecodedCellData;
|
use crate::services::cell::DecodedCellData;
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::group::controller::MoveGroupRowContext;
|
use crate::services::group::controller::MoveGroupRowContext;
|
||||||
use crate::services::group::{GroupData, GroupSettingChangeset};
|
use crate::services::group::{GroupData, GroupSettingChangeset};
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ use std::marker::PhantomData;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cell, Cells, Row, RowId};
|
use collab_database::rows::{Cell, Cells, Row, RowDetail, RowId};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@ -13,7 +13,6 @@ use crate::entities::{
|
|||||||
FieldType, GroupChangesPB, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB,
|
FieldType, GroupChangesPB, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB,
|
||||||
};
|
};
|
||||||
use crate::services::cell::{get_cell_protobuf, CellProtobufBlobParser, DecodedCellData};
|
use crate::services::cell::{get_cell_protobuf, CellProtobufBlobParser, DecodedCellData};
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::group::action::{
|
use crate::services::group::action::{
|
||||||
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation, GroupCustomize,
|
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation, GroupCustomize,
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row};
|
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB};
|
use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB};
|
||||||
use crate::services::cell::insert_checkbox_cell;
|
use crate::services::cell::insert_checkbox_cell;
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::field::{
|
use crate::services::field::{
|
||||||
CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOption, CHECK, UNCHECK,
|
CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOption, CHECK, UNCHECK,
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{Cells, Row};
|
use collab_database::rows::{Cells, Row, RowDetail};
|
||||||
|
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
|
|
||||||
use crate::entities::GroupChangesPB;
|
use crate::entities::GroupChangesPB;
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::group::action::{
|
use crate::services::group::action::{
|
||||||
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation,
|
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation,
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row};
|
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
|
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
|
||||||
use crate::services::cell::insert_select_option_cell;
|
use crate::services::cell::insert_select_option_cell;
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::field::{MultiSelectTypeOption, SelectOptionCellDataParser};
|
use crate::services::field::{MultiSelectTypeOption, SelectOptionCellDataParser};
|
||||||
use crate::services::group::action::GroupCustomize;
|
use crate::services::group::action::GroupCustomize;
|
||||||
use crate::services::group::controller::{
|
use crate::services::group::controller::{
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row};
|
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
|
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
|
||||||
use crate::services::cell::insert_select_option_cell;
|
use crate::services::cell::insert_select_option_cell;
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::field::{SelectOptionCellDataParser, SingleSelectTypeOption};
|
use crate::services::field::{SelectOptionCellDataParser, SingleSelectTypeOption};
|
||||||
use crate::services::group::action::GroupCustomize;
|
use crate::services::group::action::GroupCustomize;
|
||||||
use crate::services::group::controller::{
|
use crate::services::group::controller::{
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{Cell, Row};
|
use collab_database::rows::{Cell, Row, RowDetail};
|
||||||
|
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB, SelectOptionCellDataPB,
|
FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB, SelectOptionCellDataPB,
|
||||||
};
|
};
|
||||||
use crate::services::cell::{insert_checkbox_cell, insert_select_option_cell, insert_url_cell};
|
use crate::services::cell::{insert_checkbox_cell, insert_select_option_cell, insert_url_cell};
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::field::{SelectOption, CHECK};
|
use crate::services::field::{SelectOption, CHECK};
|
||||||
use crate::services::group::controller::MoveGroupRowContext;
|
use crate::services::group::controller::MoveGroupRowContext;
|
||||||
use crate::services::group::{GeneratedGroupConfig, Group, GroupData};
|
use crate::services::group::{GeneratedGroupConfig, Group, GroupData};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row};
|
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
@ -11,7 +11,6 @@ use crate::entities::{
|
|||||||
URLCellDataPB,
|
URLCellDataPB,
|
||||||
};
|
};
|
||||||
use crate::services::cell::insert_url_cell;
|
use crate::services::cell::insert_url_cell;
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::field::{URLCellData, URLCellDataParser, URLTypeOption};
|
use crate::services::field::{URLCellData, URLCellDataParser, URLTypeOption};
|
||||||
use crate::services::group::action::GroupCustomize;
|
use crate::services::group::action::GroupCustomize;
|
||||||
use crate::services::group::configuration::GroupContext;
|
use crate::services::group::configuration::GroupContext;
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use collab::core::any_map::AnyMapExtension;
|
use collab::core::any_map::AnyMapExtension;
|
||||||
use collab_database::database::gen_database_group_id;
|
use collab_database::database::gen_database_group_id;
|
||||||
use collab_database::rows::RowId;
|
use collab_database::rows::{RowDetail, RowId};
|
||||||
use collab_database::views::{GroupMap, GroupMapBuilder, GroupSettingBuilder, GroupSettingMap};
|
use collab_database::views::{GroupMap, GroupMapBuilder, GroupSettingBuilder, GroupSettingMap};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct GroupSetting {
|
pub struct GroupSetting {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
|
use collab_database::rows::RowDetail;
|
||||||
use collab_database::views::DatabaseLayout;
|
use collab_database::views::DatabaseLayout;
|
||||||
|
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
|
|
||||||
use crate::entities::FieldType;
|
use crate::entities::FieldType;
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::group::configuration::GroupSettingReader;
|
use crate::services::group::configuration::GroupSettingReader;
|
||||||
use crate::services::group::controller::GroupController;
|
use crate::services::group::controller::GroupController;
|
||||||
use crate::services::group::{
|
use crate::services::group::{
|
||||||
|
@ -4,7 +4,7 @@ use std::str::FromStr;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{Cell, Row, RowId};
|
use collab_database::rows::{Cell, Row, RowDetail, RowId};
|
||||||
use rayon::prelude::ParallelSliceMut;
|
use rayon::prelude::ParallelSliceMut;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
@ -16,7 +16,6 @@ use lib_infra::future::Fut;
|
|||||||
use crate::entities::FieldType;
|
use crate::entities::FieldType;
|
||||||
use crate::entities::SortChangesetNotificationPB;
|
use crate::entities::SortChangesetNotificationPB;
|
||||||
use crate::services::cell::CellCache;
|
use crate::services::cell::CellCache;
|
||||||
use crate::services::database::RowDetail;
|
|
||||||
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier};
|
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier};
|
||||||
use crate::services::field::{default_order, TypeOptionCellExt};
|
use crate::services::field::{default_order, TypeOptionCellExt};
|
||||||
use crate::services::sort::{
|
use crate::services::sort::{
|
||||||
|
@ -3,12 +3,12 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use collab_database::database::{gen_database_view_id, timestamp};
|
use collab_database::database::{gen_database_view_id, timestamp};
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{CreateRowParams, RowId};
|
use collab_database::rows::{CreateRowParams, RowDetail, RowId};
|
||||||
use strum::EnumCount;
|
use strum::EnumCount;
|
||||||
|
|
||||||
use flowy_database2::entities::{FieldType, FilterPB, RowMetaPB, SelectOptionPB};
|
use flowy_database2::entities::{FieldType, FilterPB, RowMetaPB, SelectOptionPB};
|
||||||
use flowy_database2::services::cell::{CellBuilder, ToCellChangeset};
|
use flowy_database2::services::cell::{CellBuilder, ToCellChangeset};
|
||||||
use flowy_database2::services::database::{DatabaseEditor, RowDetail};
|
use flowy_database2::services::database::DatabaseEditor;
|
||||||
use flowy_database2::services::field::checklist_type_option::{
|
use flowy_database2::services::field::checklist_type_option::{
|
||||||
ChecklistCellChangeset, ChecklistTypeOption,
|
ChecklistCellChangeset, ChecklistTypeOption,
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use appflowy_integrate::RocksCollabDB;
|
use appflowy_integrate::RocksCollabDB;
|
||||||
|
pub use collab_document::blocks::DocumentData;
|
||||||
|
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::FlowyError;
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
@ -8,7 +9,7 @@ use lib_infra::future::FutureResult;
|
|||||||
pub trait DocumentUser: Send + Sync {
|
pub trait DocumentUser: Send + Sync {
|
||||||
fn user_id(&self) -> Result<i64, FlowyError>;
|
fn user_id(&self) -> Result<i64, FlowyError>;
|
||||||
fn token(&self) -> Result<Option<String>, FlowyError>; // unused now.
|
fn token(&self) -> Result<Option<String>, FlowyError>; // unused now.
|
||||||
fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
|
fn collab_db(&self, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for document cloud service.
|
/// A trait for document cloud service.
|
||||||
@ -21,6 +22,8 @@ pub trait DocumentCloudService: Send + Sync + 'static {
|
|||||||
&self,
|
&self,
|
||||||
document_id: &str,
|
document_id: &str,
|
||||||
) -> FutureResult<Option<DocumentSnapshot>, FlowyError>;
|
) -> FutureResult<Option<DocumentSnapshot>, FlowyError>;
|
||||||
|
|
||||||
|
fn get_document_data(&self, document_id: &str) -> FutureResult<Option<DocumentData>, FlowyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DocumentSnapshot {
|
pub struct DocumentSnapshot {
|
||||||
|
@ -70,7 +70,7 @@ fn subscribe_document_snapshot_state(collab: &Arc<MutexCollab>) {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(snapshot_state) = snapshot_state.next().await {
|
while let Some(snapshot_state) = snapshot_state.next().await {
|
||||||
if let Some(new_snapshot_id) = snapshot_state.snapshot_id() {
|
if let Some(new_snapshot_id) = snapshot_state.snapshot_id() {
|
||||||
tracing::debug!("Did create document snapshot: {}", new_snapshot_id);
|
tracing::debug!("Did create document remote snapshot: {}", new_snapshot_id);
|
||||||
send_notification(
|
send_notification(
|
||||||
&document_id,
|
&document_id,
|
||||||
DocumentNotification::DidUpdateDocumentSnapshotState,
|
DocumentNotification::DidUpdateDocumentSnapshotState,
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
use collab::core::collab_state::SyncState;
|
|
||||||
use collab_document::blocks::{BlockAction, DocumentData};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::parse::{NotEmptyStr, NotEmptyVec};
|
use collab::core::collab_state::SyncState;
|
||||||
|
use collab_document::blocks::{BlockAction, DocumentData};
|
||||||
|
|
||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
use flowy_error::ErrorCode;
|
use flowy_error::ErrorCode;
|
||||||
|
|
||||||
|
use crate::parse::{NotEmptyStr, NotEmptyVec};
|
||||||
|
|
||||||
#[derive(Default, ProtoBuf)]
|
#[derive(Default, ProtoBuf)]
|
||||||
pub struct OpenDocumentPayloadPB {
|
pub struct OpenDocumentPayloadPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
@ -270,7 +272,7 @@ impl From<i32> for ExportType {
|
|||||||
1 => ExportType::Markdown,
|
1 => ExportType::Markdown,
|
||||||
2 => ExportType::Link,
|
2 => ExportType::Link,
|
||||||
_ => {
|
_ => {
|
||||||
tracing::error!("Invalid export type: {}", val);
|
tracing::error!("🔴Invalid export type: {}", val);
|
||||||
ExportType::Text
|
ExportType::Text
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -306,7 +308,7 @@ impl From<i32> for ConvertType {
|
|||||||
match val {
|
match val {
|
||||||
0 => ConvertType::Json,
|
0 => ConvertType::Json,
|
||||||
_ => {
|
_ => {
|
||||||
tracing::error!("Invalid export type: {}", val);
|
tracing::error!("🔴Invalid export type: {}", val);
|
||||||
ConvertType::Json
|
ConvertType::Json
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ pub(crate) async fn open_document_handler(
|
|||||||
) -> DataResult<DocumentDataPB, FlowyError> {
|
) -> DataResult<DocumentDataPB, FlowyError> {
|
||||||
let params: OpenDocumentParams = data.into_inner().try_into()?;
|
let params: OpenDocumentParams = data.into_inner().try_into()?;
|
||||||
let doc_id = params.document_id;
|
let doc_id = params.document_id;
|
||||||
let document = manager.get_document(&doc_id)?;
|
let document = manager.get_document(&doc_id).await?;
|
||||||
let document_data = document.lock().get_document_data()?;
|
let document_data = document.lock().get_document_data()?;
|
||||||
data_result_ok(DocumentDataPB::from(document_data))
|
data_result_ok(DocumentDataPB::from(document_data))
|
||||||
}
|
}
|
||||||
@ -57,7 +57,7 @@ pub(crate) async fn get_document_data_handler(
|
|||||||
) -> DataResult<DocumentDataPB, FlowyError> {
|
) -> DataResult<DocumentDataPB, FlowyError> {
|
||||||
let params: OpenDocumentParams = data.into_inner().try_into()?;
|
let params: OpenDocumentParams = data.into_inner().try_into()?;
|
||||||
let doc_id = params.document_id;
|
let doc_id = params.document_id;
|
||||||
let document_data = manager.get_document_data(&doc_id)?;
|
let document_data = manager.get_document_data(&doc_id).await?;
|
||||||
data_result_ok(DocumentDataPB::from(document_data))
|
data_result_ok(DocumentDataPB::from(document_data))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ pub(crate) async fn apply_action_handler(
|
|||||||
) -> FlowyResult<()> {
|
) -> FlowyResult<()> {
|
||||||
let params: ApplyActionParams = data.into_inner().try_into()?;
|
let params: ApplyActionParams = data.into_inner().try_into()?;
|
||||||
let doc_id = params.document_id;
|
let doc_id = params.document_id;
|
||||||
let document = manager.get_document(&doc_id)?;
|
let document = manager.get_document(&doc_id).await?;
|
||||||
let actions = params.actions;
|
let actions = params.actions;
|
||||||
document.lock().apply_action(actions);
|
document.lock().apply_action(actions);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -104,7 +104,7 @@ pub(crate) async fn redo_handler(
|
|||||||
) -> DataResult<DocumentRedoUndoResponsePB, FlowyError> {
|
) -> DataResult<DocumentRedoUndoResponsePB, FlowyError> {
|
||||||
let params: DocumentRedoUndoParams = data.into_inner().try_into()?;
|
let params: DocumentRedoUndoParams = data.into_inner().try_into()?;
|
||||||
let doc_id = params.document_id;
|
let doc_id = params.document_id;
|
||||||
let document = manager.get_document(&doc_id)?;
|
let document = manager.get_document(&doc_id).await?;
|
||||||
let document = document.lock();
|
let document = document.lock();
|
||||||
let redo = document.redo();
|
let redo = document.redo();
|
||||||
let can_redo = document.can_redo();
|
let can_redo = document.can_redo();
|
||||||
@ -122,7 +122,7 @@ pub(crate) async fn undo_handler(
|
|||||||
) -> DataResult<DocumentRedoUndoResponsePB, FlowyError> {
|
) -> DataResult<DocumentRedoUndoResponsePB, FlowyError> {
|
||||||
let params: DocumentRedoUndoParams = data.into_inner().try_into()?;
|
let params: DocumentRedoUndoParams = data.into_inner().try_into()?;
|
||||||
let doc_id = params.document_id;
|
let doc_id = params.document_id;
|
||||||
let document = manager.get_document(&doc_id)?;
|
let document = manager.get_document(&doc_id).await?;
|
||||||
let document = document.lock();
|
let document = document.lock();
|
||||||
let undo = document.undo();
|
let undo = document.undo();
|
||||||
let can_redo = document.can_redo();
|
let can_redo = document.can_redo();
|
||||||
@ -140,7 +140,7 @@ pub(crate) async fn can_undo_redo_handler(
|
|||||||
) -> DataResult<DocumentRedoUndoResponsePB, FlowyError> {
|
) -> DataResult<DocumentRedoUndoResponsePB, FlowyError> {
|
||||||
let params: DocumentRedoUndoParams = data.into_inner().try_into()?;
|
let params: DocumentRedoUndoParams = data.into_inner().try_into()?;
|
||||||
let doc_id = params.document_id;
|
let doc_id = params.document_id;
|
||||||
let document = manager.get_document(&doc_id)?;
|
let document = manager.get_document(&doc_id).await?;
|
||||||
let document = document.lock();
|
let document = document.lock();
|
||||||
let can_redo = document.can_redo();
|
let can_redo = document.can_redo();
|
||||||
let can_undo = document.can_undo();
|
let can_undo = document.can_undo();
|
||||||
|
@ -45,28 +45,35 @@ impl DocumentManager {
|
|||||||
data: Option<DocumentData>,
|
data: Option<DocumentData>,
|
||||||
) -> FlowyResult<Arc<MutexDocument>> {
|
) -> FlowyResult<Arc<MutexDocument>> {
|
||||||
tracing::trace!("create a document: {:?}", doc_id);
|
tracing::trace!("create a document: {:?}", doc_id);
|
||||||
let collab = self.collab_for_document(doc_id)?;
|
let collab = self.collab_for_document(doc_id, vec![])?;
|
||||||
let data = data.unwrap_or_else(default_document_data);
|
let data = data.unwrap_or_else(default_document_data);
|
||||||
let document = Arc::new(MutexDocument::create_with_data(collab, data)?);
|
let document = Arc::new(MutexDocument::create_with_data(collab, data)?);
|
||||||
Ok(document)
|
Ok(document)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the document
|
/// Return the document
|
||||||
pub fn get_document(&self, doc_id: &str) -> FlowyResult<Arc<MutexDocument>> {
|
pub async fn get_document(&self, doc_id: &str) -> FlowyResult<Arc<MutexDocument>> {
|
||||||
if let Some(doc) = self.documents.read().get(doc_id) {
|
if let Some(doc) = self.documents.read().get(doc_id) {
|
||||||
return Ok(doc.clone());
|
return Ok(doc.clone());
|
||||||
}
|
}
|
||||||
// Check if the document exists. If not, return error.
|
let mut updates = vec![];
|
||||||
if !self.is_doc_exist(doc_id)? {
|
if !self.is_doc_exist(doc_id)? {
|
||||||
return Err(
|
// Try to get the document from the cloud service
|
||||||
FlowyError::record_not_found().context(format!("document: {} is not exist", doc_id)),
|
if let Ok(document_updates) = self.cloud_service.get_document_updates(doc_id).await {
|
||||||
);
|
updates = document_updates;
|
||||||
|
} else {
|
||||||
|
return Err(
|
||||||
|
FlowyError::record_not_found().context(format!("document: {} is not exist", doc_id)),
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("open_document: {:?}", doc_id);
|
tracing::debug!("open_document: {:?}", doc_id);
|
||||||
let uid = self.user.user_id()?;
|
let uid = self.user.user_id()?;
|
||||||
let db = self.user.collab_db()?;
|
let db = self.user.collab_db(uid)?;
|
||||||
let collab = self.collab_builder.build(uid, doc_id, "document", db);
|
let collab = self
|
||||||
|
.collab_builder
|
||||||
|
.build(uid, doc_id, "document", updates, db)?;
|
||||||
let document = Arc::new(MutexDocument::open(doc_id, collab)?);
|
let document = Arc::new(MutexDocument::open(doc_id, collab)?);
|
||||||
|
|
||||||
// save the document to the memory and read it from the memory if we open the same document again.
|
// save the document to the memory and read it from the memory if we open the same document again.
|
||||||
@ -78,14 +85,19 @@ impl DocumentManager {
|
|||||||
Ok(document)
|
Ok(document)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_document_data(&self, doc_id: &str) -> FlowyResult<DocumentData> {
|
pub async fn get_document_data(&self, doc_id: &str) -> FlowyResult<DocumentData> {
|
||||||
|
let mut updates = vec![];
|
||||||
if !self.is_doc_exist(doc_id)? {
|
if !self.is_doc_exist(doc_id)? {
|
||||||
return Err(
|
if let Ok(document_updates) = self.cloud_service.get_document_updates(doc_id).await {
|
||||||
FlowyError::record_not_found().context(format!("document: {} is not exist", doc_id)),
|
updates = document_updates;
|
||||||
);
|
} else {
|
||||||
|
return Err(
|
||||||
|
FlowyError::record_not_found().context(format!("document: {} is not exist", doc_id)),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let collab = self.collab_for_document(doc_id)?;
|
let collab = self.collab_for_document(doc_id, updates)?;
|
||||||
Document::open(collab)?
|
Document::open(collab)?
|
||||||
.get_document_data()
|
.get_document_data()
|
||||||
.map_err(internal_error)
|
.map_err(internal_error)
|
||||||
@ -98,7 +110,7 @@ impl DocumentManager {
|
|||||||
|
|
||||||
pub fn delete_document(&self, doc_id: &str) -> FlowyResult<()> {
|
pub fn delete_document(&self, doc_id: &str) -> FlowyResult<()> {
|
||||||
let uid = self.user.user_id()?;
|
let uid = self.user.user_id()?;
|
||||||
let db = self.user.collab_db()?;
|
let db = self.user.collab_db(uid)?;
|
||||||
let _ = db.with_write_txn(|txn| {
|
let _ = db.with_write_txn(|txn| {
|
||||||
txn.delete_doc(uid, &doc_id)?;
|
txn.delete_doc(uid, &doc_id)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -130,15 +142,22 @@ impl DocumentManager {
|
|||||||
Ok(snapshots)
|
Ok(snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collab_for_document(&self, doc_id: &str) -> FlowyResult<Arc<MutexCollab>> {
|
fn collab_for_document(
|
||||||
|
&self,
|
||||||
|
doc_id: &str,
|
||||||
|
updates: Vec<Vec<u8>>,
|
||||||
|
) -> FlowyResult<Arc<MutexCollab>> {
|
||||||
let uid = self.user.user_id()?;
|
let uid = self.user.user_id()?;
|
||||||
let db = self.user.collab_db()?;
|
let db = self.user.collab_db(uid)?;
|
||||||
Ok(self.collab_builder.build(uid, doc_id, "document", db))
|
let collab = self
|
||||||
|
.collab_builder
|
||||||
|
.build(uid, doc_id, "document", updates, db)?;
|
||||||
|
Ok(collab)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_doc_exist(&self, doc_id: &str) -> FlowyResult<bool> {
|
fn is_doc_exist(&self, doc_id: &str) -> FlowyResult<bool> {
|
||||||
let uid = self.user.user_id()?;
|
let uid = self.user.user_id()?;
|
||||||
let db = self.user.collab_db()?;
|
let db = self.user.collab_db(uid)?;
|
||||||
let read_txn = db.read_txn();
|
let read_txn = db.read_txn();
|
||||||
Ok(read_txn.is_exist(uid, doc_id))
|
Ok(read_txn.is_exist(uid, doc_id))
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user